commit ff569bd2ae2a849a1145be4a9479246b6cd8d0e3 Author: loop <126372784+tribes2@users.noreply.github.com> Date: Sun Jan 7 04:36:33 2024 +0000 t2 engine svn checkout diff --git a/Engine.dsp b/Engine.dsp new file mode 100644 index 0000000..70cb7e7 --- /dev/null +++ b/Engine.dsp @@ -0,0 +1,3133 @@ +# Microsoft Developer Studio Project File - Name="Engine" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) External Target" 0x0106 + +CFG=Engine - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Engine.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Engine.mak" CFG="Engine - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Engine - Win32 Release" (based on "Win32 (x86) External Target") +!MESSAGE "Engine - Win32 Debug" (based on "Win32 (x86) External Target") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" + +!IF "$(CFG)" == "Engine - Win32 Release" + +# PROP BASE Use_MFC +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Cmd_Line "NMAKE /f Engine.mak" +# PROP BASE Rebuild_Opt "/a" +# PROP BASE Target_File "Engine.exe" +# PROP BASE Bsc_Name "Engine.bsc" +# PROP BASE Target_Dir "" +# PROP Use_MFC +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Cmd_Line "make OS=WIN32 COMPILER=VC6 BUILD=RELEASE DIR.OBJ=out.VC6.RELEASE" +# PROP Rebuild_Opt "/a" +# PROP Target_File "v12_RELEASE.exe" +# PROP Bsc_Name "" +# PROP Target_Dir "" + +!ELSEIF "$(CFG)" == "Engine - Win32 Debug" + +# PROP BASE Use_MFC +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Cmd_Line "NMAKE /f Engine.mak" +# PROP BASE Rebuild_Opt "/a" +# PROP BASE Target_File "Engine.exe" +# PROP BASE Bsc_Name "Engine.bsc" +# PROP BASE Target_Dir "" +# PROP Use_MFC +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Cmd_Line "make OS=WIN32 COMPILER=VC6 BUILD=DEBUG DIR.OBJ=out.VC6.DEBUG" +# PROP Rebuild_Opt "/a" +# PROP Target_File "v12_DEBUG.exe" +# PROP Bsc_Name "" +# PROP Target_Dir "" + +!ENDIF + +# Begin Target + +# Name "Engine - Win32 Release" +# Name "Engine - Win32 Debug" + +!IF "$(CFG)" == "Engine - Win32 Release" + +!ELSEIF "$(CFG)" == "Engine - Win32 Debug" + +!ENDIF + +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Group "ai" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\ai\aiConnection.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\aiConsole.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\aiDebug.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\aiNavJetting.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\aiNavStep.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\aiObjective.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\aiStep.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\aiTask.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graph.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBase.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBridge.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBuildLOS.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphConjoin.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphData.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphDebug.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphDijkstra.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphFind.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphFloorPlan.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphFloorRender.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphForceField.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGenUtils.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGroundPlan.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphIndoors.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphIsland.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphJetting.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphLocate.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphLOS.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphMake.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphMath.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphOutdoors.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphPartition.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphPath.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphQueries.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphRender.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphSearchLOS.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphSmooth.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphSpawn.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphThreats.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphTransient.cc +# End Source File +# Begin Source File + +SOURCE=.\ai\graphVolume.cc +# End Source File +# End Group +# Begin Group "audio" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\audio\audio.cc +# End Source File +# Begin Source File + +SOURCE=.\audio\audioBuffer.cc +# End Source File +# Begin Source File + +SOURCE=.\audio\audioCodec.cc +# End Source File +# Begin Source File + +SOURCE=.\audio\audioCodecMiles.cc +# End Source File +# Begin Source File + +SOURCE=.\audio\audioDataBlock.cc +# End Source File +# Begin Source File + +SOURCE=.\audio\audioMss.cc +# End Source File +# Begin Source File + +SOURCE=.\audio\audioNet.cc +# End Source File +# Begin Source File + +SOURCE=.\audio\audioThread.cc +# End Source File +# Begin Source File + +SOURCE=.\audio\bufferQueue.cc +# End Source File +# End Group +# Begin Group "collision" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\collision\abstractPolyList.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\boxConvex.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\clippedPolyList.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\convex.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\depthSortList.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\earlyOutPolyList.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\extrudedPolyList.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\gjk.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\planeExtractor.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\polyhedron.cc +# End Source File +# Begin Source File + +SOURCE=.\collision\polytope.cc +# End Source File +# End Group +# Begin Group "console" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\console\compiledEval.cc +# End Source File +# Begin Source File + +SOURCE=.\console\compiler.cc +# End Source File +# Begin Source File + +SOURCE=.\console\console.cc +# End Source File +# Begin Source File + +SOURCE=.\console\consoleFunctions.cc +# End Source File +# Begin Source File + +SOURCE=.\console\consoleInternal.cc +# End Source File +# Begin Source File + +SOURCE=.\console\consoleObject.cc +# End Source File +# Begin Source File + +SOURCE=.\console\consoleTypes.cc +# End Source File +# Begin Source File + +SOURCE=.\console\gram.cc +# End Source File +# Begin Source File + +SOURCE=.\console\scan.cc +# End Source File +# Begin Source File + +SOURCE=.\console\scriptObject.cc +# End Source File +# Begin Source File + +SOURCE=.\console\simBase.cc +# End Source File +# Begin Source File + +SOURCE=.\console\simDictionary.cc +# End Source File +# Begin Source File + +SOURCE=.\console\simManager.cc +# End Source File +# Begin Source File + +SOURCE=.\console\telnetConsole.cc +# End Source File +# Begin Source File + +SOURCE=.\console\telnetDebugger.cc +# End Source File +# End Group +# Begin Group "core" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\core\bitRender.cc +# End Source File +# Begin Source File + +SOURCE=.\core\bitStream.cc +# End Source File +# Begin Source File + +SOURCE=.\core\BitTables.cc +# End Source File +# Begin Source File + +SOURCE=.\core\dataChunker.cc +# End Source File +# Begin Source File + +SOURCE=.\core\dnet.cc +# End Source File +# Begin Source File + +SOURCE=.\core\fileObject.cc +# End Source File +# Begin Source File + +SOURCE=.\core\fileStream.cc +# End Source File +# Begin Source File + +SOURCE=.\core\filterStream.cc +# End Source File +# Begin Source File + +SOURCE=.\core\findMatch.cc +# End Source File +# Begin Source File + +SOURCE=.\core\idGenerator.cc +# End Source File +# Begin Source File + +SOURCE=.\core\memStream.cc +# End Source File +# Begin Source File + +SOURCE=.\core\nStream.cc +# End Source File +# Begin Source File + +SOURCE=.\core\nTypes.cc +# End Source File +# Begin Source File + +SOURCE=.\core\resDictionary.cc +# End Source File +# Begin Source File + +SOURCE=.\core\resizeStream.cc +# End Source File +# Begin Source File + +SOURCE=.\core\resManager.cc +# End Source File +# Begin Source File + +SOURCE=.\core\stringTable.cc +# End Source File +# Begin Source File + +SOURCE=.\core\tagDictionary.cc +# End Source File +# Begin Source File + +SOURCE=.\core\tVector.cc +# End Source File +# Begin Source File + +SOURCE=.\core\zipAggregate.cc +# End Source File +# Begin Source File + +SOURCE=.\core\zipHeaders.cc +# End Source File +# Begin Source File + +SOURCE=.\core\zipSubStream.cc +# End Source File +# End Group +# Begin Group "crypt" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\crypt\cryptMGF.cc +# End Source File +# Begin Source File + +SOURCE=.\crypt\cryptRandPool.cc +# End Source File +# Begin Source File + +SOURCE=.\crypt\cryptSHA1.cc +# End Source File +# End Group +# Begin Group "dgl" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\dgl\bitmapBM8.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapBMP.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapGIF.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapJpeg.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapPng.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\dgl.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\dglMatrix.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\gBitmap.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\gFont.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\gPalette.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\gTexManager.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\lensFlare.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialList.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialPropertyMap.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\rectClipper.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\splineUtil.cc +# End Source File +# Begin Source File + +SOURCE=.\dgl\stripCache.cc +# End Source File +# End Group +# Begin Group "editor" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\editor\compTest.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\creator.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\editor.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\editorButtonCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\editorCheckboxCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\editTSCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\guiTerrPreviewCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\missionAreaEditor.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformer.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformer_noise.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformerTexture.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\terrainActions.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\terrainEditor.cc +# End Source File +# Begin Source File + +SOURCE=.\editor\worldEditor.cc +# End Source File +# End Group +# Begin Group "game" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\game\ambientAudioManager.cc +# End Source File +# Begin Source File + +SOURCE=.\game\audioEmitter.cc +# End Source File +# Begin Source File + +SOURCE=.\game\badWordFilter.cc +# End Source File +# Begin Source File + +SOURCE=.\game\banList.cc +# End Source File +# Begin Source File + +SOURCE=.\game\bombSight.cc +# End Source File +# Begin Source File + +SOURCE=.\game\camera.cc +# End Source File +# Begin Source File + +SOURCE=.\game\cameraFXMgr.cc +# End Source File +# Begin Source File + +SOURCE=.\game\collisionTest.cc +# End Source File +# Begin Source File + +SOURCE=.\game\commanderMapIcon.cc +# End Source File +# Begin Source File + +SOURCE=.\game\debris.cc +# End Source File +# Begin Source File + +SOURCE=.\game\debugView.cc +# End Source File +# Begin Source File + +SOURCE=.\game\explosion.cc +# End Source File +# Begin Source File + +SOURCE=.\game\fireballAtmosphere.cc +# End Source File +# Begin Source File + +SOURCE=.\game\flyingVehicle.cc +# End Source File +# Begin Source File + +SOURCE=.\game\forceFieldBare.cc +# End Source File +# Begin Source File + +SOURCE=.\game\game.cc +# End Source File +# Begin Source File + +SOURCE=.\game\gameBase.cc +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnection.cc +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnectionEvents.cc +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnectionMoves.cc +# End Source File +# Begin Source File + +SOURCE=.\game\gameFunctions.cc +# End Source File +# Begin Source File + +SOURCE=.\game\gameProcess.cc +# End Source File +# Begin Source File + +SOURCE=.\game\gameTSCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\game\guiNoMouseCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\game\guiPlayerView.cc +# End Source File +# Begin Source File + +SOURCE=.\game\guiServerBrowser.cc +# End Source File +# Begin Source File + +SOURCE=.\game\hoverVehicle.cc +# End Source File +# Begin Source File + +SOURCE=.\game\httpObject.cc +# End Source File +# Begin Source File + +SOURCE=.\game\item.cc +# End Source File +# Begin Source File + +SOURCE=.\game\lightning.cc +# End Source File +# Begin Source File + +SOURCE=.\game\linearProjectile.cc +# End Source File +# Begin Source File + +SOURCE=.\game\missionArea.cc +# End Source File +# Begin Source File + +SOURCE=.\game\missionMarker.cc +# End Source File +# Begin Source File + +SOURCE=.\game\motionBlurLine.cc +# End Source File +# Begin Source File + +SOURCE=.\game\net.cc +# End Source File +# Begin Source File + +SOURCE=.\game\netDispatch.cc +# End Source File +# Begin Source File + +SOURCE=.\game\netTest.cc +# End Source File +# Begin Source File + +SOURCE=.\game\particleEmitter.cc +# End Source File +# Begin Source File + +SOURCE=.\game\particleEngine.cc +# End Source File +# Begin Source File + +SOURCE=.\game\physicalZone.cc +# End Source File +# Begin Source File + +SOURCE=.\game\platTest.cc +# End Source File +# Begin Source File + +SOURCE=.\game\player.cc +# End Source File +# Begin Source File + +SOURCE=.\game\precipitation.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projBomb.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projectile.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projELF.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projEnergy.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projFlareGrenade.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projGrenade.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projLinearFlare.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projRepair.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projSeeker.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projShockLance.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projSniper.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projTargeting.cc +# End Source File +# Begin Source File + +SOURCE=.\game\projTracer.cc +# End Source File +# Begin Source File + +SOURCE=.\game\rigid.cc +# End Source File +# Begin Source File + +SOURCE=.\game\scopeAlwaysShape.cc +# End Source File +# Begin Source File + +SOURCE=.\game\sensor.cc +# End Source File +# Begin Source File + +SOURCE=.\game\serverQuery.cc +# End Source File +# Begin Source File + +SOURCE=.\game\shadow.cc +# End Source File +# Begin Source File + +SOURCE=.\game\shapeBase.cc +# End Source File +# Begin Source File + +SOURCE=.\game\shapeCollision.cc +# End Source File +# Begin Source File + +SOURCE=.\game\shapeImage.cc +# End Source File +# Begin Source File + +SOURCE=.\game\shieldImpact.cc +# End Source File +# Begin Source File + +SOURCE=.\game\shockwave.cc +# End Source File +# Begin Source File + +SOURCE=.\game\showTSShape.cc +# End Source File +# Begin Source File + +SOURCE=.\game\sphere.cc +# End Source File +# Begin Source File + +SOURCE=.\game\splash.cc +# End Source File +# Begin Source File + +SOURCE=.\game\staticShape.cc +# End Source File +# Begin Source File + +SOURCE=.\game\targetManager.cc +# End Source File +# Begin Source File + +SOURCE=.\game\tcpObject.cc +# End Source File +# Begin Source File + +SOURCE=.\game\trigger.cc +# End Source File +# Begin Source File + +SOURCE=.\game\tsStatic.cc +# End Source File +# Begin Source File + +SOURCE=.\game\turret.cc +# End Source File +# Begin Source File + +SOURCE=.\game\underLava.cc +# End Source File +# Begin Source File + +SOURCE=.\game\vehicle.cc +# End Source File +# Begin Source File + +SOURCE=.\game\vehicleBlocker.cc +# End Source File +# Begin Source File + +SOURCE=.\game\version.cc +# End Source File +# Begin Source File + +SOURCE=.\game\weaponBeam.cc +# End Source File +# Begin Source File + +SOURCE=.\game\wheeledVehicle.cc +# End Source File +# End Group +# Begin Group "gui" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\gui\channelVector.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiArrayCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiAviBitmapCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBackgroundCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBitmapCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBubbleTextCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiButtonCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiCanvas.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChannelVectorCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChatMenuTreeCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiCheckBoxCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChunkedBitmapCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsole.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsoleEditCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsoleTextCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiControl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiControlListPopup.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiDebugger.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiEditCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiFilterCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiFrameCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiInputCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiInspector.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMessageVectorCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMLTextCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMLTextEditCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMouseEventCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiPopUpCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiProgressCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiRadioCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiScrollCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiSliderCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextEditCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextEditSliderCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextListCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTreeViewCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTSControl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTypes.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiVoteCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\guiWindowCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\gui\messageVector.cc +# End Source File +# End Group +# Begin Group "hud" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\hud\hudBitmapCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\hud\hudBitmapFrameCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\hud\hudClock.cc +# End Source File +# Begin Source File + +SOURCE=.\hud\hudCompass.cc +# End Source File +# Begin Source File + +SOURCE=.\hud\hudCrosshair.cc +# End Source File +# Begin Source File + +SOURCE=.\hud\hudCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\hud\hudEnergyDamage.cc +# End Source File +# End Group +# Begin Group "interior" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\interior\FloorPlanRes.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\forceField.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interior.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorCollision.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorDebug.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorInstance.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorIO.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorLightAnim.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorLMManager.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorRender.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorRes.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorResObjects.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorSubObject.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\itfdump.asm +# End Source File +# Begin Source File + +SOURCE=.\interior\lightUpdateGrouper.cc +# End Source File +# Begin Source File + +SOURCE=.\interior\mirrorSubObject.cc +# End Source File +# End Group +# Begin Group "math" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\math\mathTypes.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mathUtils.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mBox.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mConsoleFunctions.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mMath_C.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mMathAMD.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mMathFn.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mMathSSE.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mMatrix.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mPlaneTransformer.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mQuadPatch.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mQuat.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mRandom.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mSolver.cc +# End Source File +# Begin Source File + +SOURCE=.\math\mSplinePatch.cc +# End Source File +# End Group +# Begin Group "platform" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\platform\gameInterface.cc +# End Source File +# Begin Source File + +SOURCE=.\platform\platformAssert.cc +# End Source File +# Begin Source File + +SOURCE=.\platform\platformMemory.cc +# End Source File +# Begin Source File + +SOURCE=.\platform\platformRedBook.cc +# End Source File +# Begin Source File + +SOURCE=.\platform\platformVideo.cc +# End Source File +# Begin Source File + +SOURCE=.\platform\profiler.cc +# End Source File +# End Group +# Begin Group "platformWIN32" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\platformWIN32\winAsmBlit.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winConsole.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winCPUInfo.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winD3DVideo.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDInputDevice.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDirectInput.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winFileio.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winFont.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winGL.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winInput.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMath.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMath_ASM.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMemory.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMutex.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winNet.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winOGLVideo.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winOpenAL.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winProcessControl.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winRedbook.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winSemaphore.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winStrings.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winThread.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winTime.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winV2Video.cc +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winWindow.cc +# End Source File +# End Group +# Begin Group "scenegraph" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\scenegraph\detailManager.cc +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\lightManager.cc +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneGraph.cc +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneLighting.cc +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneRoot.cc +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneState.cc +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneTraversal.cc +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sgUtil.cc +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\shadowVolumeBSP.cc +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\windingClipper.cc +# End Source File +# End Group +# Begin Group "shell" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\shell\shellFancyArray.cc +# End Source File +# Begin Source File + +SOURCE=.\shell\shellFancyTextList.cc +# End Source File +# Begin Source File + +SOURCE=.\shell\shellScrollCtrl.cc +# End Source File +# Begin Source File + +SOURCE=.\shell\shellTextEditCtrl.cc +# End Source File +# End Group +# Begin Group "sim" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\sim\actionMap.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\cannedChatDataBlock.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\decalManager.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\frameAllocator.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\netConnection.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\netEvent.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\netGhost.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\netObject.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\netStringTable.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\pathManager.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\sceneObject.cc +# End Source File +# Begin Source File + +SOURCE=.\sim\simPath.cc +# End Source File +# End Group +# Begin Group "terrain" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\terrain\blender.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\bvQuadTree.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\FluidQuadTree.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\FluidRender.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\FluidSupport.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sky.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sun.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrCollision.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrData.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrLighting.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrRender.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrRender2.cc +# End Source File +# Begin Source File + +SOURCE=.\terrain\waterBlock.cc +# End Source File +# End Group +# Begin Group "ts" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\ts\tsAnimate.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsCollision.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsDecal.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsDump.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsIntegerSet.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsLastDetail.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsMaterialList.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsMesh.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsPartInstance.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShape.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeAlloc.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeConstruct.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeInstance.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeOldRead.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsSortedMesh.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsThread.cc +# End Source File +# Begin Source File + +SOURCE=.\ts\tsTransform.cc +# End Source File +# End Group +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Group "ai headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\ai\aiConnection.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiNavJetting.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiNavStep.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiObjective.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiStep.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiTask.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graph.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBase.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBridge.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBSP.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphData.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphDefines.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphFloorPlan.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphForceField.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGenUtils.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGroundPlan.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGroundVisit.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphJetting.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphLocate.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphLOS.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphMath.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphNodes.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphPartition.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphPath.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphSearches.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphThreats.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphTransient.h +# End Source File +# Begin Source File + +SOURCE=.\ai\oVector.h +# End Source File +# Begin Source File + +SOURCE=.\ai\tBinHeap.h +# End Source File +# Begin Source File + +SOURCE=.\ai\texturePreload.h +# End Source File +# End Group +# Begin Group "audio headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\audio\audio.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioBuffer.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioCodec.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioCodecMiles.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioDataBlock.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioMss.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioNet.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioThread.h +# End Source File +# Begin Source File + +SOURCE=.\audio\bufferQueue.h +# End Source File +# End Group +# Begin Group "collsion headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\collision\abstractPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\boxConvex.h +# End Source File +# Begin Source File + +SOURCE=.\collision\clippedPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\collision.h +# End Source File +# Begin Source File + +SOURCE=.\collision\convex.h +# End Source File +# Begin Source File + +SOURCE=.\collision\depthSortList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\earlyOutPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\extrudedPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\gjk.h +# End Source File +# Begin Source File + +SOURCE=.\collision\planeExtractor.h +# End Source File +# Begin Source File + +SOURCE=.\collision\polyhedron.h +# End Source File +# Begin Source File + +SOURCE=.\collision\polytope.h +# End Source File +# End Group +# Begin Group "console headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\console\ast.h +# End Source File +# Begin Source File + +SOURCE=.\console\compiler.h +# End Source File +# Begin Source File + +SOURCE=.\console\console.h +# End Source File +# Begin Source File + +SOURCE=.\console\consoleInternal.h +# End Source File +# Begin Source File + +SOURCE=.\console\consoleObject.h +# End Source File +# Begin Source File + +SOURCE=.\console\consoleTypes.h +# End Source File +# Begin Source File + +SOURCE=.\console\gram.h +# End Source File +# Begin Source File + +SOURCE=.\console\objectTypes.h +# End Source File +# Begin Source File + +SOURCE=.\console\simBase.h +# End Source File +# Begin Source File + +SOURCE=.\console\simDictionary.h +# End Source File +# Begin Source File + +SOURCE=.\console\telnetConsole.h +# End Source File +# Begin Source File + +SOURCE=.\console\telnetDebugger.h +# End Source File +# End Group +# Begin Group "core headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\core\bitMatrix.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitRender.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitSet.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\BitTables.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitVector.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitVectorW.h +# End Source File +# Begin Source File + +SOURCE=.\core\color.h +# End Source File +# Begin Source File + +SOURCE=.\core\coreRes.h +# End Source File +# Begin Source File + +SOURCE=.\core\dataChunker.h +# End Source File +# Begin Source File + +SOURCE=.\core\dnet.h +# End Source File +# Begin Source File + +SOURCE=.\core\fileio.h +# End Source File +# Begin Source File + +SOURCE=.\core\fileObject.h +# End Source File +# Begin Source File + +SOURCE=.\core\fileStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\filterStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\findMatch.h +# End Source File +# Begin Source File + +SOURCE=.\core\idGenerator.h +# End Source File +# Begin Source File + +SOURCE=.\core\llist.h +# End Source File +# Begin Source File + +SOURCE=.\core\memstream.h +# End Source File +# Begin Source File + +SOURCE=.\core\polyList.h +# End Source File +# Begin Source File + +SOURCE=.\core\realComp.h +# End Source File +# Begin Source File + +SOURCE=.\core\resizeStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\resManager.h +# End Source File +# Begin Source File + +SOURCE=.\core\stream.h +# End Source File +# Begin Source File + +SOURCE=.\core\stringTable.h +# End Source File +# Begin Source File + +SOURCE=.\core\tagDictionary.h +# End Source File +# Begin Source File + +SOURCE=.\core\tAlgorithm.h +# End Source File +# Begin Source File + +SOURCE=.\core\tSparseArray.h +# End Source File +# Begin Source File + +SOURCE=.\core\tVector.h +# End Source File +# Begin Source File + +SOURCE=.\core\zipAggregate.h +# End Source File +# Begin Source File + +SOURCE=.\core\zipHeaders.h +# End Source File +# Begin Source File + +SOURCE=.\core\zipSubStream.h +# End Source File +# End Group +# Begin Group "crypt headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\crypt\cryptMGF.h +# End Source File +# Begin Source File + +SOURCE=.\crypt\cryptRandPool.h +# End Source File +# Begin Source File + +SOURCE=.\crypt\cryptSHA1.h +# End Source File +# End Group +# Begin Group "dgl headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\dgl\dgl.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gBitmap.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gChunkedTexManager.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gFont.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gPalette.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gTexManager.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\lensFlare.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialList.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialPropertyMap.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\rectClipper.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\splineUtil.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\stripCache.h +# End Source File +# End Group +# Begin Group "editor headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\editor\creator.h +# End Source File +# Begin Source File + +SOURCE=.\editor\editor.h +# End Source File +# Begin Source File + +SOURCE=.\editor\editorButtonCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\editor\editorCheckboxCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\editor\editTSCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\editor\guiTerrPreviewCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\editor\missionAreaEditor.h +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformer.h +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformer_noise.h +# End Source File +# Begin Source File + +SOURCE=.\editor\terrainActions.h +# End Source File +# Begin Source File + +SOURCE=.\editor\terrainEditor.h +# End Source File +# Begin Source File + +SOURCE=.\editor\worldEditor.h +# End Source File +# End Group +# Begin Group "game headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\game\ambientAudioManager.h +# End Source File +# Begin Source File + +SOURCE=.\game\audioEmitter.h +# End Source File +# Begin Source File + +SOURCE=.\game\auth.h +# End Source File +# Begin Source File + +SOURCE=.\game\badWordFilter.h +# End Source File +# Begin Source File + +SOURCE=.\game\banList.h +# End Source File +# Begin Source File + +SOURCE=.\game\bombSight.h +# End Source File +# Begin Source File + +SOURCE=.\game\camera.h +# End Source File +# Begin Source File + +SOURCE=.\game\cameraFXMgr.h +# End Source File +# Begin Source File + +SOURCE=.\game\collisionTest.h +# End Source File +# Begin Source File + +SOURCE=.\game\commanderMapIcon.h +# End Source File +# Begin Source File + +SOURCE=.\game\debris.h +# End Source File +# Begin Source File + +SOURCE=.\game\debugView.h +# End Source File +# Begin Source File + +SOURCE=.\game\explosion.h +# End Source File +# Begin Source File + +SOURCE=.\game\fireballAtmosphere.h +# End Source File +# Begin Source File + +SOURCE=.\game\flyingVehicle.h +# End Source File +# Begin Source File + +SOURCE=.\game\forceFieldBare.h +# End Source File +# Begin Source File + +SOURCE=.\game\game.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameBase.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnection.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnectionEvents.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameFunctions.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameTSCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\game\guiPlayerView.h +# End Source File +# Begin Source File + +SOURCE=.\game\guiServerBrowser.h +# End Source File +# Begin Source File + +SOURCE=.\game\hoverVehicle.h +# End Source File +# Begin Source File + +SOURCE=.\game\httpObject.h +# End Source File +# Begin Source File + +SOURCE=.\game\item.h +# End Source File +# Begin Source File + +SOURCE=.\game\lightning.h +# End Source File +# Begin Source File + +SOURCE=.\game\linearProjectile.h +# End Source File +# Begin Source File + +SOURCE=.\game\missionArea.h +# End Source File +# Begin Source File + +SOURCE=.\game\missionMarker.h +# End Source File +# Begin Source File + +SOURCE=.\game\motionBlurLine.h +# End Source File +# Begin Source File + +SOURCE=.\game\moveManager.h +# End Source File +# Begin Source File + +SOURCE=.\game\netDispatch.h +# End Source File +# Begin Source File + +SOURCE=.\game\objectTypes.h +# End Source File +# Begin Source File + +SOURCE=.\game\particleEmitter.h +# End Source File +# Begin Source File + +SOURCE=.\game\particleEngine.h +# End Source File +# Begin Source File + +SOURCE=.\game\physicalZone.h +# End Source File +# Begin Source File + +SOURCE=.\game\player.h +# End Source File +# Begin Source File + +SOURCE=.\game\precipitation.h +# End Source File +# Begin Source File + +SOURCE=.\game\projBomb.h +# End Source File +# Begin Source File + +SOURCE=.\game\projectile.h +# End Source File +# Begin Source File + +SOURCE=.\game\projELF.h +# End Source File +# Begin Source File + +SOURCE=.\game\projEnergy.h +# End Source File +# Begin Source File + +SOURCE=.\game\projFlareGrenade.h +# End Source File +# Begin Source File + +SOURCE=.\game\projGrenade.h +# End Source File +# Begin Source File + +SOURCE=.\game\projLinearFlare.h +# End Source File +# Begin Source File + +SOURCE=.\game\projRepair.h +# End Source File +# Begin Source File + +SOURCE=.\game\projSeeker.h +# End Source File +# Begin Source File + +SOURCE=.\game\projShockLance.h +# End Source File +# Begin Source File + +SOURCE=.\game\projSniper.h +# End Source File +# Begin Source File + +SOURCE=.\game\projTargeting.h +# End Source File +# Begin Source File + +SOURCE=.\game\projTracer.h +# End Source File +# Begin Source File + +SOURCE=.\game\resource.h +# End Source File +# Begin Source File + +SOURCE=.\game\rigid.h +# End Source File +# Begin Source File + +SOURCE=.\game\sensor.h +# End Source File +# Begin Source File + +SOURCE=.\game\serverQuery.h +# End Source File +# Begin Source File + +SOURCE=.\game\shadow.h +# End Source File +# Begin Source File + +SOURCE=.\game\shapeBase.h +# End Source File +# Begin Source File + +SOURCE=.\game\shieldImpact.h +# End Source File +# Begin Source File + +SOURCE=.\game\shockwave.h +# End Source File +# Begin Source File + +SOURCE=.\game\showTSShape.h +# End Source File +# Begin Source File + +SOURCE=.\game\sphere.h +# End Source File +# Begin Source File + +SOURCE=.\game\splash.h +# End Source File +# Begin Source File + +SOURCE=.\game\staticShape.h +# End Source File +# Begin Source File + +SOURCE=.\game\targetManager.h +# End Source File +# Begin Source File + +SOURCE=.\game\tcpObject.h +# End Source File +# Begin Source File + +SOURCE=.\game\tribesGame.h +# End Source File +# Begin Source File + +SOURCE=.\game\trigger.h +# End Source File +# Begin Source File + +SOURCE=.\game\tsStatic.h +# End Source File +# Begin Source File + +SOURCE=.\game\turret.h +# End Source File +# Begin Source File + +SOURCE=.\game\underLava.h +# End Source File +# Begin Source File + +SOURCE=.\game\vehicle.h +# End Source File +# Begin Source File + +SOURCE=.\game\vehicleBlocker.h +# End Source File +# Begin Source File + +SOURCE=.\game\version.h +# End Source File +# Begin Source File + +SOURCE=.\game\weaponBeam.h +# End Source File +# Begin Source File + +SOURCE=.\game\wheeledVehicle.h +# End Source File +# End Group +# Begin Group "gui headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\gui\channelVector.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiArrayCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiAviBitmapCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBackgroundCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBitmapCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBubbleTextCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiButtonCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiCanvas.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChannelVectorCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChatMenuTreeCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiCheckBoxCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsole.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsoleEditCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsoleTextCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiControl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiDebugger.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiEditCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiFilterCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiFrameCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiHelpCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiInputCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiInspector.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMessageVectorCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMLTextCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMLTextEditCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMouseEventCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiPopUpCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiProgressCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiRadioCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiScrollCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiSliderCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextEditCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextEditSliderCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextListCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTreeViewCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTSControl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTypes.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiVoteCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiWindowCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\messageVector.h +# End Source File +# End Group +# Begin Group "hud headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\hud\hudBitmapCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\hud\hudBitmapFrameCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\hud\hudCtrl.h +# End Source File +# End Group +# Begin Group "interior headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\interior\FloorPlanRes.h +# End Source File +# Begin Source File + +SOURCE=.\interior\forceField.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interior.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorInstance.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorLMManager.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorRes.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorResObjects.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorSubObject.h +# End Source File +# Begin Source File + +SOURCE=.\interior\lightUpdateGrouper.h +# End Source File +# Begin Source File + +SOURCE=.\interior\mirrorSubObject.h +# End Source File +# End Group +# Begin Group "math headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\math\mathIO.h +# End Source File +# Begin Source File + +SOURCE=.\math\mathTypes.h +# End Source File +# Begin Source File + +SOURCE=.\math\mathUtils.h +# End Source File +# Begin Source File + +SOURCE=.\math\mBox.h +# End Source File +# Begin Source File + +SOURCE=.\math\mConstants.h +# End Source File +# Begin Source File + +SOURCE=.\math\mMath.h +# End Source File +# Begin Source File + +SOURCE=.\math\mMathFn.h +# End Source File +# Begin Source File + +SOURCE=.\math\mMatrix.h +# End Source File +# Begin Source File + +SOURCE=.\math\mPlane.h +# End Source File +# Begin Source File + +SOURCE=.\math\mPlaneTransformer.h +# End Source File +# Begin Source File + +SOURCE=.\math\mPoint.h +# End Source File +# Begin Source File + +SOURCE=.\math\mQuadPatch.h +# End Source File +# Begin Source File + +SOURCE=.\math\mQuat.h +# End Source File +# Begin Source File + +SOURCE=.\math\mRandom.h +# End Source File +# Begin Source File + +SOURCE=.\math\mRect.h +# End Source File +# Begin Source File + +SOURCE=.\math\mSphere.h +# End Source File +# Begin Source File + +SOURCE=.\math\mSplinePatch.h +# End Source File +# Begin Source File + +SOURCE=.\math\mTrig.h +# End Source File +# End Group +# Begin Group "platform headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\platform\3DFX.H +# End Source File +# Begin Source File + +SOURCE=.\platform\event.h +# End Source File +# Begin Source File + +SOURCE=.\platform\gameInterface.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platform.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformAssert.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformAudio.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformInput.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformMutex.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformRedBook.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformSemaphore.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformThread.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformVideo.h +# End Source File +# Begin Source File + +SOURCE=.\platform\profiler.h +# End Source File +# Begin Source File + +SOURCE=.\platform\types.h +# End Source File +# Begin Source File + +SOURCE=.\platform\typesLinux.h +# End Source File +# Begin Source File + +SOURCE=.\platform\typesPPC.h +# End Source File +# Begin Source File + +SOURCE=.\platform\typesWin32.h +# End Source File +# End Group +# Begin Group "platformWIN32 headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\platformWIN32\gllist.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\platformAL.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\platformGL.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\platformWin32.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winConsole.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winD3DVideo.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDInputDevice.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDirectInput.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winOGLVideo.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winV2Video.h +# End Source File +# End Group +# Begin Group "scenegraph headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\scenegraph\detailManager.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\lightManager.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneGraph.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneLighting.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneRoot.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneState.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sgUtil.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\shadowVolumeBSP.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\windingClipper.h +# End Source File +# End Group +# Begin Group "shell headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\shell\shellFancyArray.h +# End Source File +# Begin Source File + +SOURCE=.\shell\shellFancyTextList.h +# End Source File +# Begin Source File + +SOURCE=.\shell\shellScrollCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\shell\shellTextEditCtrl.h +# End Source File +# End Group +# Begin Group "sim headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\sim\actionMap.h +# End Source File +# Begin Source File + +SOURCE=.\sim\cannedChatDataBlock.h +# End Source File +# Begin Source File + +SOURCE=.\sim\decalManager.h +# End Source File +# Begin Source File + +SOURCE=.\sim\frameAllocator.h +# End Source File +# Begin Source File + +SOURCE=.\sim\netConnection.h +# End Source File +# Begin Source File + +SOURCE=.\sim\netObject.h +# End Source File +# Begin Source File + +SOURCE=.\sim\netStringTable.h +# End Source File +# Begin Source File + +SOURCE=.\sim\pathManager.h +# End Source File +# Begin Source File + +SOURCE=.\sim\sceneObject.h +# End Source File +# Begin Source File + +SOURCE=.\sim\simPath.h +# End Source File +# End Group +# Begin Group "terrain headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\terrain\blender.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\bvQuadTree.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\Fluid.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sky.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sun.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrData.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrRender.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\waterBlock.h +# End Source File +# End Group +# Begin Group "ts headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\ts\tsDecal.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsIntegerSet.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsLastDetail.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsMesh.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsPartInstance.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShape.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeAlloc.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeConstruct.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeInstance.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsSortedMesh.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsTransform.h +# End Source File +# End Group +# End Group +# Begin Source File + +SOURCE=.\targets.v12.mk +# End Source File +# End Target +# End Project diff --git a/Engine.plg b/Engine.plg new file mode 100644 index 0000000..4dc73a0 --- /dev/null +++ b/Engine.plg @@ -0,0 +1,34 @@ + + +
+

Build Log

+

+--------------------Configuration: Engine - Win32 Release-------------------- +

+ +Compiling interior/itfdump.asm +mBox.cc +mConsoleFunctions.cc +mMathFn.cc +mMath_C.cc +mMatrix.cc +mPlaneTransformer.cc +mQuadPatch.cc +mQuat.cc +mRandom.cc +mSolver.cc +mSplinePatch.cc +mathTypes.cc +mathUtils.cc +mMathAMD.cc +mMathSSE.cc +Linking out.VC6.RELEASE/v12_RELEASE.exe +cp out.VC6.RELEASE/v12_RELEASE* ../example + + + +

Results

+v12_RELEASE.exe - 0 error(s), 0 warning(s) +
+ + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cbdb6c1 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ + +#-------------------------------------- +# include and verify the users mk/conf.mk + +-include ../mk/conf.mk + +ifndef CONFIG_STATUS +doConfigure: + $(warning Configuration file not defined) + #@make --no-print-directory -f ../mk/configure.mk +else +ifeq ($(CONFIG_STATUS),INVALID) +doConfigure: + $(warning Invalid Configuration file) + #@make --no-print-directory -f mk/configure.mk +else + include ../mk/conf.$(COMPILER).$(OS).mk + include ../mk/conf.$(COMPILER).mk +endif +endif + + +include targets.v12.mk + +include ../mk/conf.common.mk + + +#default: +# echo default. + +ifneq ($(MAKECMDGOALS),clean) +-include $(addprefix $(DIR.OBJ)/, $(addsuffix $(DEP), $(basename $(filter %.cc %.c,$(SOURCES))))) +endif + diff --git a/ai/aiConnection.cc b/ai/aiConnection.cc new file mode 100644 index 0000000..83a464b --- /dev/null +++ b/ai/aiConnection.cc @@ -0,0 +1,2975 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "core/realComp.h" +#include "math/mMatrix.h" +#include "console/console.h" +#include "game/gameBase.h" +#include "ai/aiConnection.h" +#include "ai/aiStep.h" +#include "ai/aiNavStep.h" +#include "ai/aiTask.h" +#include "ai/graphMath.h" +#include "terrain/waterBlock.h" +#include "scenegraph/sceneGraph.h" +#include "game/vehicle.h" +#include "platform/profiler.h" + +IMPLEMENT_CONOBJECT(AIConnection); + +static S32 gAIDetectionOffset = 0; + +AIConnection::AIConnection() +{ + mAIControlled = true; + mMoveMode = mMoveModePending = ModeStop; + mMoveLocation.set(0, 0, 0); + mNodeLocation.set(0, 0, 0); + mAimLocation.set(0, 0, 0); + mMoveSpeed = 0.0f; + mMoveTolerance = 0.25f; + mStep = NULL; + mCurrentTask = NULL; + mCurrentTaskTime = 0; + mPathDest.set(0, 0, 0); + mNewPath = false; + + //clear the triggers + for (int i = 0; i < MaxTriggerKeys; i++) + mTriggers[i] = false; + + mPrevNodeLocation.set(0, 0, 0); + mInitialLocation.set(0, 0, 0); + + mEnergyReserve = 0.0f; + mEnergyFloat = 0.10f; + mEnergyRecharge = false; + + //init engage vars + mEngageState = ChooseWeapon; + mProjectileName = StringTable->insert(""); + mProjectile = NULL; + mEngageMinDistance = 20; + mEngageMaxDistance = 75; + mDistToTarg2D = -1.0f; + mLookAtTargetTimeMS = 0; + mFiring = false; + mTriggerCounter = 0; + mScriptTriggerCounter = -1; + mVictimTime = 0; + mSkillLevel = 0.5; + mChangeWeaponCounter = 0; + mTurretMountedId = 0; + mMountedImage = NULL; + + //piloting vars + mPilotDestination.set(0, 0, 0); + mPilotAimLocation.set(0, 0, 0); + mPilotSpeed = 1.0f; + mPitchUpMax = -0.25; + mPitchDownMax = 0.1f; + mPitchIncMax = 0.05f; + mCurrentPitch = 0; + mPreviousPitch = 0; + mDesiredPitch = 0; + mPitchIncrement = 0; + + mTargStillTimeMS = 0; + mTargPrevTimeMS = 0; + mTargPrevLocation[0].set(0, 0, 0); + mTargPrevLocation[1].set(0, 0, 0); + mTargPrevLocation[2].set(0, 0, 0); + mTargPrevLocation[3].set(0, 0, 0); + + mPlayerDetectionIndex = 0; + mPlayerDetectionCounter = gAIDetectionOffset; + gAIDetectionOffset++; + if (gAIDetectionOffset >= 3) + gAIDetectionOffset = 0; + mDetectHiddenPeriod = 6000; + mBlindedTimer = 0; + + //init target object vars + mTargetObject = NULL; + mObjectMode = DestroyObject; + mDistToObject2D = -1.0f; + + //these are used for both mEngageTarget and mTargetObject + mRangeToTarget = 30; + mCheckTargetLOSCounter = 0; + mTargetInRange = false; + mTargetInSight = false; + mWeaponEnergy = 0.0f; + mWeaponErrorFactor = 1.0f; + + //init evading vars + mEnemyProjectile = NULL; + mEvadingCounter = 0; + mIsEvading = false; + + mAvoidingObject = NULL; + mStuckInitialized = false; + + // LH. Just for the heck of it (and as way to familiarize with code) - added in all + // these that weren't being constructed. + mMoveDestination.set(0, 0, 0); + mEvadeLocation.set(0, 0, 0); + mImpactLocation.set(0, 0, 0); + mCorpseLocation.set(0, 0, 0); + mStateCounter = 0; + mDelayCounter = 0; + mPackCheckCounter = 0; + mAimAtLazedTarget = false; + mProjectileCounter = 0; + mPath.setTeam(getSensorGroup()); + mStuckDestination.set(0,0,0); + mTargetPlayer = NULL; + mLocation.set(0,0,0); + mVelocity = mVelocity2D = mRotation = mHeadRotation = + mMuzzlePosition = mEyePosition = mTargLocation = + mTargVelocity = mTargVelocity2D = mTargRotation = mLocation; + mTargEnergy = 1.0; + mTargDamage = 0; +} + +AIConnection::~AIConnection() +{ +} + +void AIConnection::setSkillLevel(F32 level) +{ + mSkillLevel = getMax(0.0f, getMin(1.0f, level)); +} + +void AIConnection::setMoveSpeed(F32 speed) +{ + if (speed <= 0.0f) + mMoveSpeed = 0.0f; + else + mMoveSpeed = getMin(1.0f, speed); +} + +void AIConnection::setMoveMode(S32 mode, bool abortStuckCode) +{ + if (mode < 0 || mode >= ModeCount) + mode = 0; + + //if we're setting mode::express and we're currently stuck, let the stuck code + //finish "unsticking" the bot... calling setMoveDestination will abort the stuck code + if (mMoveMode == ModeStuck && mode != ModeStop && !abortStuckCode) + return; + + //make sure we're not moving if in the middle of a jet... + mMoveModePending = mode; + if (!mNavUsingJet) + mMoveMode = mode; +} + +void AIConnection::setMoveTolerance(F32 tolerance) +{ + mMoveTolerance = getMax(0.1f, tolerance); +} + +void AIConnection::setMoveDestination(const Point3F &location) +{ + if (mMoveDestination != location && mMoveMode == ModeStuck) + { + setMoveMode(ModeExpress, true); + mStuckLocation.set(0, 0, 0); + } + mMoveDestination = location; +} + +void AIConnection::setMoveLocation(const Point3F &location) +{ + mMoveLocation = location; +} + +void AIConnection::setAimLocation(const Point3F &location) +{ + mAimLocation = location; +} + +bool AIConnection::setScriptAimLocation(const Point3F &location, S32 duration) +{ + //can't set through scripts if the bots are aiming at either an object, or an engage player + if (mTargetPlayer || bool(mTargetObject)) + return false; + + setAimLocation(location); + mLookAtTargetTimeMS = getMax(mLookAtTargetTimeMS, (S32)Sim::getCurrentTime() + duration); + + return true; +} + +void AIConnection::setPilotPitchRange(F32 pitchUpMax, F32 pitchDownMax, F32 pitchIncMax) +{ + mPitchUpMax = pitchUpMax; + mPitchDownMax = pitchDownMax; + mPitchIncMax = pitchIncMax; +} + +void AIConnection::setPilotDestination(const Point3F &dest, F32 maxSpeed) +{ + //must always aim where you're flying to + mPilotDestination = dest; + mPilotAimLocation = dest; + mPilotSpeed = getMax(0.0f, getMin(1.0f, maxSpeed)); +} + +void AIConnection::setPilotAimLocation(const Point3F &aimLocation) +{ + //if you're aiming, you can't move + mPilotAimLocation = aimLocation; + mPilotSpeed = 0; +} + +void AIConnection::setWeaponInfo(const char *projectile, S32 minDist, S32 maxDist, S32 triggerCount, F32 energyRequired, F32 errorFactor) +{ + //set the non-pointer params + mEngageMinDistance = minDist; + mEngageMaxDistance = maxDist; + mTriggerCounter = triggerCount; + mScriptTriggerCounter = -1; + mWeaponEnergy = energyRequired; + mWeaponErrorFactor = errorFactor; + + if ((! projectile) || (! projectile[0]) || (! dStricmp(projectile, "NoAmmo"))) + mProjectile = NULL; + else + { + if (! Sim::findObject(projectile, mProjectile)) + { + Con::printf("setWeaponInfo() failed - unable to find datablock: %s", projectile); + mProjectile = NULL; + } + } + if (mProjectile) + mProjectileName = StringTable->insert(projectile); + else + mProjectileName = StringTable->insert(""); +} + +void AIConnection::setEnergyLevels(F32 eReserve, F32 eFloat) +{ + mEnergyReserve = eReserve; + mEnergyFloat = eFloat; +} + +void AIConnection::setEngageTarget(GameConnection *target) +{ + //reset the bools if we're aiming at someone new... + //note, these extraneous retarded checks are because mTargetObject is a SimObjectPtr... + //won't compile just comparing target != mEngageTarget... + if (target != NULL && ((! bool(mEngageTarget)) || (target->getId() != mEngageTarget->getId()))) + { + //reset some engagement vars + mTargetInRange = false; + mTargetInSight = false; + mEngageState = ChooseWeapon; + mFiring = false; + mTriggerCounter = 0; + mScriptTriggerCounter = -1; + } + + mEngageTarget = target; + mTargetPlayer = NULL; + if ((! bool(mEngageTarget)) || (! mEngageTarget->getControlObject())) + mEngageTarget = NULL; + else + mTargetPlayer = dynamic_cast(mEngageTarget->getControlObject()); + + //make sure we actually got a target + if (! mTargetPlayer) + mEngageTarget = NULL; + +} + +S32 AIConnection::getEngageTarget() +{ + //see if have someone to shoot at + Player *targetPlayer = NULL; + if ((! bool(mEngageTarget)) || (! mEngageTarget->getControlObject())) + { + mTargetPlayer = NULL; + mEngageTarget = NULL; + return -1; + } + else + targetPlayer = dynamic_cast(mEngageTarget->getControlObject()); + + //see if the target is dead + if (! targetPlayer || ! dStricmp(targetPlayer->getStateName(), "dead")) + { + mTargetPlayer = NULL; + mEngageTarget = NULL; + return -1; + } + + //return the id of the target + return mEngageTarget->getId(); +} + +void AIConnection::setVictim(GameConnection *victim, Player *corpse) +{ + mVictim = victim; + mCorpse = corpse; + mVictimTime = Sim::getCurrentTime(); +} + +S32 AIConnection::getVictimCorpse() +{ + if (bool(mCorpse)) + return mCorpse->getId(); + else + return -1; +} + +S32 AIConnection::getVictimTime() +{ + return mVictimTime; +} + +void AIConnection::setTargetObject(ShapeBase *targetObject, F32 range, S32 objectMode) +{ + //reset the bools if we're not aiming at a player, and we have a new target object + //note, these extraneous retarded checks are because mTargetObject is a SimObjectPtr... + //won't compile just comparing targetObject != mTargetObject... + if (! mTargetPlayer && (! targetObject || ! bool(mTargetObject) || targetObject->getId() != mTargetObject->getId())) + { + mTargetInRange = false; + mTargetInSight = false; + mEngageState = ChooseWeapon; + mFiring = false; + mTriggerCounter = 0; + mScriptTriggerCounter = -1; + } + + mTargetObject = targetObject; + mRangeToTarget = range; + mObjectMode = objectMode; +} + +S32 AIConnection::getTargetObject() +{ + if (bool(mTargetObject)) + return mTargetObject->getId(); + else + return -1; +} + +void AIConnection::setPathDest(const Point3F * dest) +{ + if( dest ) + mPathDest = * dest; + mNewPath = true; +} + +const Point3F * AIConnection::getPathDest() +{ + if( mNewPath ){ + mNewPath = false; + return & mPathDest; + } + return NULL; +} + +// Pass down jetting abilities to the path machinery- +void AIConnection::setPathCapabilities(Player * ) +{ + PROFILE_START(AI_setPathCapabilities); + JetManager::Ability ability; + +// Tribes player jetting was remove from the player class. +#if 0 + player->getJetAbility(ability.acc, ability.dur, ability.v0); +#else + ability.acc = 0; + ability.dur = 0; + ability.v0 = 0; +#endif + + mPath.setJetAbility(ability); + PROFILE_END(); +} + +F32 AIConnection::getPathDistance(const Point3F &destination, const Point3F &source) +{ + //find our current location + Point3F sourceLocation = source; + if (sourceLocation == Point3F(-1, -1, -1)) + { + //make sure we have a valid player control object + Player *myPlayer = NULL; + if (! getControlObject()) + return -1; + myPlayer = dynamic_cast(getControlObject()); + if (! myPlayer) + return -1; + + setPathCapabilities(myPlayer); + + MatrixF const& tempTransform = myPlayer->getTransform(); + tempTransform.getColumn(3, &sourceLocation); + + //make sure the client can actually get to the destination + if (! mPath.canReachLoc(destination)) + return -1; + } + + //this is a weird change - want the bots to avoid vertical movement, so the distance function will + //exaggerate the z component... + Point3F distVec = sourceLocation - destination; + return mSqrt(distVec.x * distVec.x + distVec.y * distVec.y + 9 * distVec.z * distVec.z); + + //this is just a euclidean distance anyways... if it becomes an actual path distance again, use it... + //return NavigationGraph::fastDistance(sourceLocation, destination); +} + +F32 AIConnection::getPathDistRemaining(F32 maxDist) +{ + //if the path is current, use the path distRemaining function + if (mPath.isPathCurrent()) + return mPath.distRemaining(maxDist); + + //otherwise, return the euclidean dist + else + return getMin(maxDist, (mLocation - mMoveDestination).len()); +} + +Point3F AIConnection::getLOSLocation(const Point3F &targetPoint, F32 minDistance, F32 maxDistance, const Point3F &nearPoint) +{ + //find our current location + Point3F sourceLocation; + Player *myPlayer = NULL; + if (! getControlObject()) + return targetPoint; + myPlayer = dynamic_cast(getControlObject()); + if (! myPlayer) + return targetPoint; + MatrixF const& tempTransform = myPlayer->getTransform(); + tempTransform.getColumn(3, &sourceLocation); + + Point3F nearLocation = nearPoint; + if (nearLocation == Point3F(-1, -1, -1)) + nearLocation = sourceLocation; + + Point3F graphPoint; + if (minDistance < 0) + graphPoint = NavigationGraph::findLOSLocation(sourceLocation, targetPoint, 0, SphereF(nearLocation, 1e6), maxDistance); + else + graphPoint = NavigationGraph::findLOSLocation(sourceLocation, targetPoint, minDistance, SphereF(nearLocation, minDistance / 2.0f), maxDistance); + return graphPoint; +} + +Point3F AIConnection::getHideLocation(const Point3F &targetPoint, F32 range, const Point3F &nearPoint, F32 hideLength) +{ + //find our current location + Point3F sourceLocation = nearPoint; + if (sourceLocation == Point3F(-1, -1, -1)) + { + Player *myPlayer = NULL; + if (! getControlObject()) + return targetPoint; + myPlayer = dynamic_cast(getControlObject()); + if (! myPlayer) + return targetPoint; + MatrixF const& tempTransform = myPlayer->getTransform(); + tempTransform.getColumn(3, &sourceLocation); + } + + if (hideLength <= 0) + return NavigationGraph::hideOnSlope(sourceLocation, targetPoint, range, 20); + else + return NavigationGraph::hideOnDistance(sourceLocation, targetPoint, range, hideLength); +} + +void AIConnection::process(ShapeBase *ctrlObject) +{ + if (!ctrlObject) + return; + + PROFILE_START(AI_process); + + //update the task queue + AITask *highestWeightTask = NULL; + S32 i; + for (i = 0; i < mTaskList.size(); i++) + { + AITask *task = mTaskList[i]; + task->calcWeight(this); + + //see if it's the highest so far + if ((! highestWeightTask) || (task->getWeight() > highestWeightTask->getWeight())) + highestWeightTask = task; + } + + // Path needs team for avoiding threats- + mPath.setTeam(getSensorGroup()); + + //now see if we have a new task + if (highestWeightTask && mCurrentTask != highestWeightTask) + { + if (bool(mCurrentTask)) + mCurrentTask->retire(this); + highestWeightTask->assume(this); + mCurrentTask = highestWeightTask; + mCurrentTaskTime = Sim::getCurrentTime(); + } + + //monitor the current task + if (mCurrentTask) { + PROFILE_START(AI_MonitorTask); + mCurrentTask->monitor(this); + PROFILE_END(); + } + + //see if our control object is a player + Player *myPlayer = NULL; + myPlayer = dynamic_cast(ctrlObject); + if (myPlayer) + { + //initialize the process vars + initProcessVars(myPlayer); + + //next, process the current step + if (bool(mStep)) + mStep->process(this, myPlayer); + + //process the engagement + if (mTurretMountedId <= 0) { + PROFILE_START(AI_EngagementOuter); + processEngagement(myPlayer); + PROFILE_END(); + } + + //finally, process the movement itself + if (!myPlayer->isMounted()) + processMovement(myPlayer); + else + processVehicleMovement(myPlayer); + } + + //else see if we're trying to pilot a vehicle + else + { + Vehicle *myVehicle; + myVehicle = dynamic_cast(ctrlObject); + if (myVehicle) + { + processPilotVehicle(myVehicle); + } + } + + //debug + aiDebugStuff(); + PROFILE_END(); +} + +// LH- changed this to not normalize when there's a zero length. Dividing by zero +// results in interupts. Instead do normalization here checking lengths first. +F32 AIConnection::get2DDot(const Point3F &vec1, const Point3F &vec2) +{ + //create the 2D vectors and check for zero length. + Point3F vec1_2D(vec1.x, vec1.y, 0); + F32 len1 = vec1_2D.len(); + if (len1 < __EQUAL_CONST_F) + return 1; + + Point3F vec2_2D(vec2.x, vec2.y, 0); + F32 len2 = vec2_2D.len(); + if (len2 < __EQUAL_CONST_F) + return 1; + + // Normalize and return the dot- + return mDot(vec1_2D /= len1, vec2_2D /= len2); +} + +F32 AIConnection::get2DAngle(const Point3F &endPt, const Point3F &basePt) +{ + Point3F direction2D = endPt - basePt; + F32 angularDirection; + direction2D.z = 0; + + if (! isZero(direction2D.y)) + angularDirection = mAtan(direction2D.x, direction2D.y); + else if (direction2D.x < 0) + angularDirection = -M_PI / 2.0f; + else + angularDirection = M_PI / 2.0f; + + if (angularDirection < 0) + angularDirection += M_2PI; + else if (angularDirection >= M_2PI) + angularDirection -= M_2PI; + + //note: return value is always between 0 and (2 * Pi) + return angularDirection; +} + +Point3F AIConnection::dopeAimLocation(const Point3F &startLocation, const Point3F &aimLocation) +{ + //find the "horizontal" orthogonal vector + Point3F horzOrth; + if (! findCrossVector(startLocation - aimLocation, Point3F(0, 0, 1), &horzOrth)) + return aimLocation; + + //now find the "vertical" orthogonal vector + Point3F vertOrth; + if (! findCrossVector(startLocation - aimLocation, horzOrth, &vertOrth)) + return aimLocation; + + //now we have the horizonal and vertical components of the plane which is perpendicular to + //the direction we are firing... + + //determine the radius factor + F32 radiusFactor; + if (mSkillLevel == 1.0f) + radiusFactor = 0.0f; + else + radiusFactor = 0.04 + (1.0f - mSkillLevel) * 0.2; + + //calculate the radius error + F32 radiusError = (startLocation - aimLocation).len() * radiusFactor * mWeaponErrorFactor; + if (! mProjectile->isBallistic && mTargStillTimeMS > 0 && radiusError > 0) + { + //begin honing in after 3 seconds, and over the next 3 seconds + S32 elapsedTime = Sim::getCurrentTime() - mTargStillTimeMS - 3000; + if (elapsedTime > 0) + radiusError = radiusError * (F32(3000 - getMin(elapsedTime, 3000)) / 3000.0f); + } + + F32 horzError = gRandGen.randF() * radiusError * (gRandGen.randF() < 0.5f ? -1.0f : 1.0f); + F32 vertError = gRandGen.randF() * radiusError * (gRandGen.randF() < 0.5f ? -1.0f : 1.0f); + + Point3F dopedAimLocation = aimLocation + (horzOrth * horzError) + (vertOrth * vertError); + return dopedAimLocation; +} + +F32 AIConnection::getOutdoorRadius(const Point3F &location) +{ + F32 freedomRadius; + if (mPath.locationIsOutdoors(location, &freedomRadius)) + return freedomRadius; + else + return -1; +} + +void AIConnection::initProcessVars(Player *player) +{ + PROFILE_START(AI_initProcessVars); + + //find my current location (global coords), velocity, energy, damage, etc... + MatrixF const& tempTransform = player->getTransform(); + tempTransform.getColumn(3, &mLocation); + mVelocity = player->getVelocity(); + mVelocity2D = mVelocity; + mVelocity2D.z = 0; + mRotation = player->getRotation(); + mHeadRotation = player->getHeadRotation(); + mEnergy = player->getEnergyValue(); + mDamage = player->getDamageValue(); + + if (mEnergy < mEnergyReserve) + mEnergyRecharge = true; + else if (mEnergy > mEnergyReserve + mEnergyFloat) + mEnergyRecharge = false; + + if (((mEnergy > mEnergyReserve + mEnergyFloat) || (mEnergy > mEnergyReserve && !mEnergyRecharge)) && + (mEnergy > mWeaponEnergy - mEnergyFloat)) + mEnergyAvailable = true; + else + mEnergyAvailable = false; + + //find the muzzle point + //player->getMuzzlePoint(0, &mMuzzlePosition); + player->getMuzzlePointAI(0, &mMuzzlePosition); + + //find the eye transform + MatrixF eyeTransform; + player->getEyeTransform(&eyeTransform); + eyeTransform.getColumn(3, &mEyePosition); + + //find out far we have to go, and if we're heading in the right direction + mDistToNode2D = (Point3F(mNodeLocation.x, mNodeLocation.y, 0) - + Point3F(mLocation.x, mLocation.y, 0)).len(); + + //find out how far off course our velocity is taking us + mDotOffCourse = get2DDot(mNodeLocation - mLocation, mVelocity2D); + mDotOffCourse = mClampF(mDotOffCourse, -1.0f, 1.0f); + + //find out the difference between them + F32 dummy; + bool outdoors = mPath.locationIsOutdoors(mLocation, &dummy); + mHeadingDownhill = false; + if (mVelocity2D.len() >= player->getMaxForwardVelocity() * 0.85f && outdoors && !mInWater) + { + //make sure we're heading in approximately the right direction (+- 30 deg or so) + if (mDotOffCourse > 0.85) + { + Point3F myDirection2D = mVelocity2D; + if (myDirection2D.len() < 0.001f) + myDirection2D.set(0, 1, 0); + myDirection2D.normalize(); + U32 mask = TerrainObjectType | InteriorObjectType | WaterObjectType; + RayInfo ray1Info, ray2Info; + + Point3F startPt = mLocation; + Point3F endPt = mLocation; + endPt.z -= 5.0f; + + //if we're not within 1.0 m of the ground, we can't ski... + player->disableCollision(); + if (! gServerContainer.castRay(startPt, endPt, mask, &ray1Info)) + mHeadingDownhill = true; + else + { + //run another LOS to see if we are heading downhill + startPt += myDirection2D; + endPt += myDirection2D; + + //if we don't hit anything, we're (on the edge of a cliff?) heading downhill + if (! gServerContainer.castRay(startPt, endPt, mask, &ray2Info)) + mHeadingDownhill = true; + else if (ray1Info.point.z > ray2Info.point.z) + mHeadingDownhill = true; + } + player->enableCollision(); + } + } + + //see if we have a target object + if (bool(mTargetObject)) + { + MatrixF const& objTransform = mTargetObject->getTransform(); + objTransform.getColumn(3, &mObjectLocation); + mDistToObject2D = (Point3F(mLocation.x, mLocation.y, 0.0f) - + Point3F(mObjectLocation.x, mObjectLocation.y, 0.0f)).len(); + } + + //see if have someone to shoot at + mTargetPlayer = NULL; + if ((! bool(mEngageTarget)) || (! mEngageTarget->getControlObject())) + mEngageTarget = NULL; + else + mTargetPlayer = dynamic_cast(mEngageTarget->getControlObject()); + + if (mTargetPlayer) + { + //find the target location (global coords), velocity, energy, damage, etc... + MatrixF const& targTransform = mTargetPlayer->getTransform(); + targTransform.getColumn(3, &mTargLocation); + mTargVelocity = mTargetPlayer->getVelocity(); + mTargVelocity2D = mTargVelocity; + mTargVelocity2D.z = 0; + mTargRotation = mTargetPlayer->getRotation(); + mTargEnergy = mTargetPlayer->getEnergyValue(); + mTargDamage = mTargetPlayer->getDamageValue(); + + //see if the target is standing still + if (mTargVelocity.len() > 4.0f) + mTargStillTimeMS = 0; + else if (mTargStillTimeMS == 0) + mTargStillTimeMS = Sim::getCurrentTime(); + + //keep an array of prev locations to simulate a slower response time... (up to 600 ms) + if (Sim::getCurrentTime() - mTargPrevTimeMS > 300) + { + mTargPrevTimeMS = Sim::getCurrentTime(); + mTargPrevLocation[3] = mTargPrevLocation[2]; + mTargPrevLocation[2] = mTargPrevLocation[1]; + mTargPrevLocation[1] = mTargPrevLocation[0]; + mTargetPlayer->getWorldBox().getCenter(&mTargPrevLocation[0]); + } + } + else + mEngageTarget = NULL; + + if (mTargetPlayer) + { + //see if the target is dead + if (! dStricmp(mTargetPlayer->getStateName(), "dead")) + { + mTargetPlayer = NULL; + mEngageTarget = NULL; + } + else + { + //find the 2D distance between you and the target + Point3F dist2DVector = mTargLocation - mLocation; + dist2DVector.z = 0; + mDistToTarg2D = dist2DVector.len(); + } + } + + //see if we're outdoors + mOutdoors = (getOutdoorRadius(mLocation) > 0); + + //see if either we or the targ is in water + mInWater = false; + mTargInWater = false; + mObjectInWater = false; + SimpleQueryList sql; + + gServerSceneGraph->getWaterObjectList(sql); + +// gServerContainer.findObjects(WaterObjectType, SimpleQueryList::insertionCallback, S32(&sql)); + + for (U32 i = 0; i < sql.mList.size(); i++) + { + WaterBlock* pBlock = dynamic_cast(sql.mList[i]); + if (pBlock) + { + if (pBlock->isPointSubmergedSimple(mLocation) || pBlock->isPointSubmergedSimple(mMuzzlePosition)) + mInWater = true; + + if (mTargetPlayer) + { + if (pBlock->isPointSubmergedSimple(mTargLocation)) + mTargInWater = true; + } + + if (bool(mTargetObject)) + { + if (pBlock->isPointSubmergedSimple(mObjectLocation)) + mObjectInWater = true; + } + } + } + + //now see if we're within range of either the engageTarget, or the targetObject + if (--mCheckTargetLOSCounter <= 0) + { + mCheckTargetLOSCounter = 10; + mTargetInSight = false; + if (mTargetPlayer || bool(mTargetObject)) + { + F32 rangeDist; + Point3F rangeLocation; + if (mTargetPlayer) + { + rangeDist = mDistToTarg2D; + mTargetPlayer->getWorldBox().getCenter(&rangeLocation); + } + else + { + rangeDist = mDistToObject2D; + mTargetObject->getWorldBox().getCenter(&rangeLocation); + } + if (rangeDist <= mRangeToTarget) + { + //see if we have line of site + RayInfo rayInfo; + Point3F startPt = mMuzzlePosition; + Point3F endPt = rangeLocation; + U32 mask = TerrainObjectType | InteriorObjectType; + if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo)) + mTargetInSight = true; + } + } + } + + //reset the weaponEnergy level if req'd + if (! bool(mTargetPlayer)) + mWeaponEnergy = 0.0f; + + //set the corpse vars + if (bool(mCorpse)) + { + MatrixF const& corpseTransform = mCorpse->getTransform(); + corpseTransform.getColumn(3, &mCorpseLocation); + } + PROFILE_END(); +} + +void AIConnection::updateDetectionTable(Player *player) +{ + //if the player has been blinded, no updates to the table can be made + if (Sim::getCurrentTime() < mBlindedTimer) + return; + + //time slice the detection LOS calls... + mPlayerDetectionCounter--; + if (mPlayerDetectionCounter <= 0) + { + SimGroup *clientGroup = Sim::getClientGroup(); + AssertFatal(clientGroup, "Unable to get the client group"); + + //make sure we have more than one client + if (clientGroup->size() <= 1) + return; + + //find the next client index + mPlayerDetectionIndex++; + if (mPlayerDetectionIndex >= clientGroup->size()) + mPlayerDetectionIndex = 0; + + //get the client from the group + GameConnection *targClient = static_cast((*clientGroup)[mPlayerDetectionIndex]); + + //make sure it's not me... + S32 targClientId = targClient->getId(); + if (getId() == targClientId) + { + mPlayerDetectionIndex++; + if (mPlayerDetectionIndex >= clientGroup->size()) + mPlayerDetectionIndex = 0; + targClient = static_cast((*clientGroup)[mPlayerDetectionIndex]); + targClientId = targClient->getId(); + } + + //now find this target in the table + PlayerDetectionEntry *targEntry = NULL; + for (S32 i = 0; i < mPlayerDetectionTable.size(); i++) + { + if (mPlayerDetectionTable[i].playerId == targClientId) + targEntry = &(mPlayerDetectionTable[i]); + } + //if the entry wasn't found, create one... (and yes, this table will never shrink) + if (!targEntry) + { + PlayerDetectionEntry tempEntry; + tempEntry.playerId = targClientId; + tempEntry.playerLOS = false; + tempEntry.playerLOSTime = 0; + tempEntry.playerLastPosition.set(0, 0, 0); + mPlayerDetectionTable.push_front(tempEntry); + targEntry = &(mPlayerDetectionTable.first()); + } + + //make sure the client has a player + Player *targPlayer = NULL; + if (! targClient->getControlObject()) + { + if (targEntry->playerLOS) + { + targEntry->playerLOS = false; + targEntry->playerLOSTime = Sim::getCurrentTime(); + } + return; + } + targPlayer = dynamic_cast(targClient->getControlObject()); + if (! targPlayer) + { + if (targEntry->playerLOS) + { + targEntry->playerLOS = false; + targEntry->playerLOSTime = Sim::getCurrentTime(); + } + return; + } + + //now cast line of sight, see if we can see the player + MatrixF myEyeTransform, targEyeTransform; + Point3F myEyePosition, targEyePosition; + player->getEyeTransform(&myEyeTransform); + myEyeTransform.getColumn(3, &myEyePosition); + targPlayer->getEyeTransform(&targEyeTransform); + targEyeTransform.getColumn(3, &targEyePosition); + + //if the target player is cloaked, they can't be detected + bool targPlayerIsCloaked = targPlayer->getCloakedState(); + + Point3F losVector = targEyePosition - myEyePosition; + F32 distToTarg = losVector.len(); + bool clearLOSToTarg; + if (mSkillLevel >= 1.0f) + clearLOSToTarg = true; + else if (distToTarg < 0.5f) + clearLOSToTarg = true; + else if (targPlayerIsCloaked) + clearLOSToTarg = false; + else if (distToTarg > 300.0f) + clearLOSToTarg = false; + else + { + //first, see if we're facing the right way... + Point3F facingVector = mAimLocation - mLocation; + F32 visibleRange = 0.4 - (0.4 * mSkillLevel); + if (facingVector.len() < 0.5f) + clearLOSToTarg = false; + else if (get2DDot(facingVector, losVector) < visibleRange && distToTarg > 1.5f) + clearLOSToTarg = false; + else + { + //see if we have line of site + if (losVector.len() < 0.001f) + losVector.set(0, 1, 0); + losVector.normalize(); + RayInfo rayInfo; + Point3F startPt = myEyePosition; + Point3F endPt = myEyePosition + (getMin(300.0f, distToTarg) * losVector); + U32 mask = TerrainObjectType | InteriorObjectType; + if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo)) + clearLOSToTarg = true; + else + clearLOSToTarg = false; + } + } + + //set the time if the status changed + if (clearLOSToTarg != targEntry->playerLOS) + targEntry->playerLOSTime = Sim::getCurrentTime(); + + //set the bool + targEntry->playerLOS = clearLOSToTarg; + + //update the position if required + if (clearLOSToTarg) + targPlayer->getWorldBox().getCenter(&targEntry->playerLastPosition); + + //don't forget to set the timeslice counter + mPlayerDetectionCounter = 30 / (clientGroup->size() - 1); + if (mPlayerDetectionCounter < 3) + mPlayerDetectionCounter = 3; + } +} + +void AIConnection::setBlinded(S32 duration) +{ + //can't blind the Kidney Bot!!! + if (mSkillLevel >= 1.0f) + return; + + //first, set the blinded timer + mBlindedTimer = Sim::getCurrentTime() + duration; + + //now loop through the table, and anyone who he has LOS to, he now doesn't + PlayerDetectionEntry *targEntry = NULL; + for (S32 i = 0; i < mPlayerDetectionTable.size(); i++) + { + targEntry = &(mPlayerDetectionTable[i]); + if (targEntry->playerLOS) + { + targEntry->playerLOS = false; + targEntry->playerLOSTime = Sim::getCurrentTime(); + } + } + + //next, set the evade location to make us react... + Point3F dangerLocation = mLocation; + dangerLocation.x += -2.0f + (gRandGen.randF() * 4.0f); + dangerLocation.y += -2.0f + (gRandGen.randF() * 4.0f); + setEvadeLocation(dangerLocation); + mEvadingCounter = 30; +} + +void AIConnection::clientDetected(S32 targId) +{ + //first, make sure we're not detecting ourself + if (getId() == targId) + return; + + SimGroup *clientGroup = Sim::getClientGroup(); + AssertFatal(clientGroup, "Unable to get the client group"); + + GameConnection *targClient = NULL; + for (SimGroup::iterator itr = clientGroup->begin(); itr != clientGroup->end(); itr++) + { + GameConnection *client = static_cast(*itr); + if (client->getId() == targId) + { + targClient = client; + break; + } + } + if (! targClient) + return; + + //now find this target in the table + PlayerDetectionEntry *targEntry = NULL; + for (S32 i = 0; i < mPlayerDetectionTable.size(); i++) + { + if (mPlayerDetectionTable[i].playerId == targId) + targEntry = &(mPlayerDetectionTable[i]); + } + //if the entry wasn't found, create one... (and yes, this table will never shrink) + if (!targEntry) + { + PlayerDetectionEntry tempEntry; + tempEntry.playerId = targId; + tempEntry.playerLOS = false; + tempEntry.playerLOSTime = 0; + tempEntry.playerLastPosition.set(0, 0, 0); + mPlayerDetectionTable.push_front(tempEntry); + targEntry = &(mPlayerDetectionTable.first()); + } + + //make sure the client has a player + Player *targPlayer = NULL; + if (! targClient->getControlObject()) + return; + targPlayer = dynamic_cast(targClient->getControlObject()); + if (! targPlayer) + return; + + //set the time if the status changed + if (!targEntry->playerLOS) + targEntry->playerLOSTime = Sim::getCurrentTime(); + + //set the bool + targEntry->playerLOS = true; + + //update the position + targPlayer->getWorldBox().getCenter(&targEntry->playerLastPosition); +} + +bool AIConnection::hasLOSToClient(S32 clientId, S32 &losTime, Point3F &lastLocation) +{ + //now find this target in the table + PlayerDetectionEntry *targEntry = NULL; + for (S32 i = 0; i < mPlayerDetectionTable.size(); i++) + { + if (mPlayerDetectionTable[i].playerId == clientId) + targEntry = &(mPlayerDetectionTable[i]); + } + + if (! targEntry) + { + losTime = Sim::getCurrentTime(); + lastLocation.set(0, 0, 0); + return false; + } + else + { + losTime = Sim::getCurrentTime() - targEntry->playerLOSTime; + lastLocation = targEntry->playerLastPosition; + return targEntry->playerLOS; + } +} + +// LH- put script calls into separate methods so profiler can measure them. +// Then added slicing since it was weighing in on profiles. +void AIConnection::scriptProcessEngagement() +{ + if (!gScriptEngageSlicer.ready(mPackCheckCounter, 6)) + return; + + char idStr[32], targetIdStr[32], targetTypeStr[32], projectileStr[64]; + dSprintf(idStr, sizeof(idStr), "%d", getId()); + if (mTargetPlayer) + { + dSprintf(targetTypeStr, sizeof(targetTypeStr), "%s", "player"); + dSprintf(targetIdStr, sizeof(targetIdStr), "%d", mEngageTarget->getId()); + } + else if (bool(mTargetObject) && mObjectMode == AIConnection::DestroyObject) + { + dSprintf(targetTypeStr, sizeof(targetTypeStr), "%s", "object"); + dSprintf(targetIdStr, sizeof(targetIdStr), "%d", mTargetObject->getId()); + } + else + { + dSprintf(targetTypeStr, sizeof(targetTypeStr), "%s", "none"); + dSprintf(targetIdStr, sizeof(targetIdStr), "%d", -1); + } + if (bool(mEnemyProjectile) && mEvadingCounter > 0 && mEvadingCounter < 45) + dSprintf(projectileStr, sizeof(projectileStr), "%d", mEnemyProjectile->getId()); + else + dSprintf(projectileStr, sizeof(projectileStr), "%d", -1); + + Con::executef(5, "AIProcessEngagement", idStr, targetIdStr, targetTypeStr, projectileStr); +} + +void AIConnection::scriptChooseEngageWeapon(F32 distToTarg) +{ + char idStr[32], targetIdStr[32], distTotargStr[32]; + + dSprintf(idStr, sizeof(idStr), "%d", getId()); + dSprintf(targetIdStr, sizeof(targetIdStr), "%d", mEngageTarget->getId()); + dSprintf(distTotargStr, sizeof(distTotargStr), "%f", distToTarg); + const char* canUseEnergyStr = mNavUsingJet ? "false" : "true"; + const char* environmentStr = (mInWater || mTargInWater ? "water" : (mOutdoors ? "outdoors" : "indoors")); + Con::executef(6, "AIChooseEngageWeapon", idStr, targetIdStr, distTotargStr, canUseEnergyStr, environmentStr); +} + +void AIConnection::scriptChooseObjectWeapon(F32 distToTarg) +{ + char idStr[32], targetIdStr[32], distTotargStr[32]; + + dSprintf(idStr, sizeof(idStr), "%d", getId()); + dSprintf(targetIdStr, sizeof(targetIdStr), "%d", mTargetObject->getId()); + dSprintf(distTotargStr, sizeof(distTotargStr), "%f", distToTarg); + const char* canUseEnergyStr = mNavUsingJet ? "false" : "true"; + const char* environmentStr = (mInWater || mTargInWater ? "water" : (mOutdoors ? "outdoors" : "indoors")); + Con::executef(7, "AIChooseObjectWeapon", idStr, targetIdStr, distTotargStr, gTargetObjectMode[mObjectMode], canUseEnergyStr, environmentStr); +} + +void AIConnection::setEvadeLocation(const Point3F &dangerLocation, S32 durationTicks) +{ + //find a new location orthogonal to the danger location to head to + Point3F dangerVector = dangerLocation - mLocation; + Point3F myVector = mMoveLocation - mLocation; + if (dangerVector.len() < 0.001f) + dangerVector.set(0, 1, 0); + dangerVector.normalize(); + if (myVector.len() < 0.001f) + myVector.set(0, 1, 0); + myVector.normalize(); + Point3F orthDanger; + mCross(dangerVector, Point3F(0, 0, 1), &orthDanger); + if (orthDanger.len() < 0.001f) + orthDanger.set(0, 1, 0); + orthDanger.normalize(); + + //if we have a NAN vector, the danger location is us! + if (orthDanger.x != orthDanger.x || orthDanger.y != orthDanger.y) + { + dangerVector = mTargLocation - mLocation; + if (dangerVector.len() < 0.001f) + dangerVector.set(0, 1, 0); + dangerVector.normalize(); + mCross(dangerVector, Point3F(0, 0, 1), &orthDanger); + orthDanger.normalize(); + + //if we have *STILL* have a NAN vector, head north! + if (orthDanger.x != orthDanger.x || orthDanger.y != orthDanger.y) + orthDanger.set(0, 1, 0); + } + + //choose the point closest to the direction we're already moving... + F32 myAngle = get2DAngle(mLocation + mVelocity2D, mLocation); + F32 tempAngle1 = get2DAngle(mLocation, mTargLocation + orthDanger); + F32 tempAngle2 = get2DAngle(mLocation, mTargLocation - orthDanger); + F32 angleDiff1, angleDiff2; + if (myAngle < tempAngle1) + angleDiff1 = getMin(tempAngle1 - myAngle, 256 + myAngle - tempAngle1); + else + angleDiff1 = getMin(myAngle - tempAngle1, 256 + tempAngle1 - myAngle); + if (myAngle < tempAngle2) + angleDiff2 = getMin(tempAngle2 - myAngle, 256 + myAngle - tempAngle2); + else + tempAngle2 = getMin(myAngle - tempAngle2, 256 + tempAngle2 - myAngle); + + if (angleDiff1 < angleDiff2) + mEvadeLocation = mLocation + (orthDanger * 30.0f); + else + mEvadeLocation = mLocation - (orthDanger * 30.0f); + + if (durationTicks > 0) + mEvadingCounter = durationTicks; +} + +void AIConnection::processEngagement(Player *player) +{ + //updatate the detection table + PROFILE_START(AI_updateDetectionTable); + updateDetectionTable(player); + PROFILE_END(); + + //call the script function once each frame to handle packs, grenades, health kits, etc... + scriptProcessEngagement(); + + //see if we need to reset the script trigger counter (if we changed weapons...) + ShapeBaseImageData* tempImage = player->getMountedImage(0); + if (tempImage != mMountedImage) + mScriptTriggerCounter = -1; + mMountedImage = tempImage; + + //if the script wants us to fire (using the repair pack to self repair mainly)... + if (mScriptTriggerCounter-- >= 0) + pressFire(true); + + //make sure we have someone to shoot at + if (! mTargetPlayer && ! bool(mTargetObject)) + return; + + //if we have something or someone to shoot at, let the engage code determine whether to fire + pressFire(false); + + //reset the counter + mLookAtTargetTimeMS = Sim::getCurrentTime() + 1500; + + //see if we're shooting a player, or a static object + bool engagingPlayer; + Point3F targetLocation; + F32 distToTarg; + if (mTargetPlayer) + { + engagingPlayer = true; + + //if the player has been blinded, use the last known location only + if (Sim::getCurrentTime() < mBlindedTimer) + { + S32 dummyTime; + hasLOSToClient(mEngageTarget->getId(), dummyTime, targetLocation); + } + else + { + //see if we have LOS to the client + S32 losTime; + Point3F losLocation; + bool hasLOS = hasLOSToClient(mEngageTarget->getId(), losTime, losLocation); + + //if we have just lost LOS to the target, assume he went around a corner, and fire at the last known location + if (!hasLOS && losTime < 2500) + targetLocation = losLocation; + + //this simulates slower response - the bot doesn't quite keep up to the target's current location + else if (mSkillLevel >= 0.9f) + targetLocation = mTargPrevLocation[0]; + else if (mSkillLevel >= 0.7f) + targetLocation = mTargPrevLocation[1]; + else if (mSkillLevel >= 0.3f) + targetLocation = mTargPrevLocation[2]; + else + targetLocation = mTargPrevLocation[3]; + } + + //now calculate the 2D distance + distToTarg = (Point3F(targetLocation.x, targetLocation.y, 0) - Point3F(mLocation.x, mLocation.y, 0)).len(); + } + else + { + engagingPlayer = false; + + //if a repair node has been added to the object, use that, otherwise, use the worldBoxCenter + targetLocation = mTargetObject->getAIRepairPoint(); + if (targetLocation == Point3F(0, 0, 0)) + mTargetObject->getWorldBox().getCenter(&targetLocation); + distToTarg = mDistToObject2D; + } + + //detect the projectiles from the target + mProjectileCounter--; + if (engagingPlayer) + { + const char *incoming = mEngageTarget->getDataField(StringTable->insert("projectile"), NULL); + if (incoming && incoming[0]) + { + Projectile *projectile; + if (Sim::findObject(incoming, projectile)) + { + //see if it's a new threat, or time to re-evaluate the current one + if (projectile != (Projectile*)mEnemyProjectile || mProjectileCounter <= 0) + { + //reset the projectile counter + mProjectileCounter = 15; + + F32 timeToImpact; + ProjectileData* projData = projectile->getDataBlock() != NULL ? + dynamic_cast(projectile->getDataBlock()) : + NULL; + if (projData && projectile->calculateImpact(4.0f, mImpactLocation, timeToImpact)) { + //see if the impact location is within range + Point3F predictMyLocation = mLocation + mVelocity * timeToImpact; + F32 distToDanger = (predictMyLocation - mImpactLocation).len(); + if (distToDanger < getMax(projData->damageRadius, 2.0f)) + { + setEvadeLocation(mImpactLocation); + mEnemyProjectile = projectile; + + //set the evade counter - any value above 45 is simulated response time + S32 responseTime = S32(30 * (1.0f - mSkillLevel)); + mEvadingCounter = 45 + responseTime; + } + } + } + } + } + } + + //if we're in the middle of a NavGraph Jet, we can't shoot... + Point3F dummyPoint; + if (mNavUsingJet && mJetting.shouldAimAt(dummyPoint)) + return; + + //here we only fire at someone if we've seen them recently enough + if (bool(mEngageTarget)) + { + S32 detectLOSTime; + Point3F lastLOSLocation; + bool hasLOSDetect = hasLOSToClient(mEngageTarget->getId(), detectLOSTime, lastLOSLocation); + if (!hasLOSDetect && (detectLOSTime > mDetectHiddenPeriod)) + return; + } + + //process the engagement state machine + switch (mEngageState) + { + case ChooseWeapon: + { + //set the aim location + if (! mTargetInRange) + setAimLocation(targetLocation); + else + setAimLocation(Point3F(targetLocation.x, targetLocation.y, mAimLocation.z)); + + //set the bool + mFiring = false; + + //limit changing the weapon too often for low skill + if (--mChangeWeaponCounter > 0) + { + mEngageState = ReloadWeapon; + mStateCounter = 45; + + //set the delay counter + mDelayCounter = S32(20.0f * (1.0f - mSkillLevel)); + } + + //else reset the counter + else + mChangeWeaponCounter = S32((1.0f - mSkillLevel) / 0.10f); + + //keep track of the previous weapon + StringTableEntry prevWeapon = mProjectileName; + + //call the script function to set the weapon vars + if (engagingPlayer) + scriptChooseEngageWeapon(distToTarg); + else + scriptChooseObjectWeapon(distToTarg); + + if (distToTarg > mEngageMaxDistance) + { + mEngageState = OutOfRange; + mStateCounter = 30; + } + else + { + mEngageState = ReloadWeapon; + mStateCounter = 60; + + //set the delay counter + mDelayCounter = S32(20.0f * (1.0f - mSkillLevel)); + + //see if we've switched weapons + if (dStricmp(mProjectileName, prevWeapon)) + mDelayCounter *= 3; + } + break; + } + + case OutOfRange: + { + //set the aim location + if (! mTargetInRange) + setAimLocation(targetLocation); + else + setAimLocation(Point3F(targetLocation.x, targetLocation.y, mAimLocation.z)); + + if (distToTarg <= mEngageMaxDistance || (--mStateCounter <= 0)) + mEngageState = ChooseWeapon; + break; + } + + case ReloadWeapon: + { + //set the aim location + if (! mTargetInRange) + setAimLocation(targetLocation); + else + setAimLocation(Point3F(targetLocation.x, targetLocation.y, mAimLocation.z)); + + //make sure we're not stuck in this state + if (--mStateCounter <= 0) + { + mEngageState = ChooseWeapon; + break; + } + + //make sure we're not trying to use an energy weapon while jetting + if (mWeaponEnergy > 0 && mNavUsingJet) + { + mEngageState = ChooseWeapon; + break; + } + + //if the weapon is ready, time to aim + bool weaponReady = player->isImageReady(0); + bool hasEnergy = (mEnergy >= mWeaponEnergy); + + //add in factor so they're not unloading on you at lower skill settings... + F32 skillVelocityFactor = 6.0f + (mSkillLevel * mSkillLevel * mSkillLevel * 300.0f); + bool mustSlowDown = (mVelocity.len() > skillVelocityFactor); + + if (weaponReady && hasEnergy && !mustSlowDown) + { + mStateCounter = 15; + if (--mDelayCounter <= 0) + mEngageState = FindTargetPoint; + } + + //if firing is sustained, keep firing + if (mFiring && mTriggerCounter > 0) + { + mTriggerCounter--; + pressFire(); + } + + break; + } + + case FindTargetPoint: + { + //set the aim location + if (! mTargetInRange) + setAimLocation(targetLocation); + else + setAimLocation(Point3F(targetLocation.x, targetLocation.y, mAimLocation.z)); + + //make sure we're not stuck in this state + if (--mStateCounter <= 0) + { + mEngageState = ChooseWeapon; + break; + } + + //make sure we're not trying to use an energy weapon while jetting + if (mWeaponEnergy > 0 && mNavUsingJet) + { + mEngageState = ChooseWeapon; + break; + } + + //should we go for center mass, or splash damage, or are we shooting at a lazed target + Point3F aimAtTargetPoint; + mAimAtLazedTarget = false; + + //only shoot at lazed targets if we're using a ballistic weapon + if (mProjectile && mProjectile->isBallistic) + { + //see if we have a lazed target point... + SimSet *targetSet = Sim::getServerTargetSet(); + if (targetSet) + { + SimSet::iterator i; + for (i = targetSet->begin(); i != targetSet->end(); i++) + { + GameBase *target = static_cast(*i); + Point3F targetPoint; + U32 dummyTeam; //when the sensor network is finished, then the target's + //visibility can be properly determined. + target->getTarget(&targetPoint, &dummyTeam); + //see if the target is within 20m of what we're trying to shoot at... + if ((targetPoint - targetLocation).len() < 10.0f) + { + aimAtTargetPoint = targetPoint; + mAimAtLazedTarget = true; + } + } + } + } + + if (! mAimAtLazedTarget) + { + //see if we should aim for the target's feet + if (engagingPlayer && mProjectile && mProjectile->hasDamageRadius && mProjectile->damageRadius > 5.0f) + aimAtTargetPoint = mTargLocation; + + //else aim for the target's center mass + else + aimAtTargetPoint = targetLocation; + } + + //find the aim vector + Point3F aimVectorMin, aimVectorMax; + F32 timeMin, timeMax; + if (mProjectile) + { + bool canShoot; + if (engagingPlayer) + canShoot = mProjectile->calculateAim(aimAtTargetPoint, mTargVelocity, mMuzzlePosition, mVelocity, &aimVectorMin, &timeMin, &aimVectorMax, &timeMax); + else + { + canShoot = false; + if (mObjectMode != MissileVehicle || player->getLockedTargetId() == mTargetObject->getId()) + canShoot = mProjectile->calculateAim(aimAtTargetPoint, Point3F(0, 0, 0), mMuzzlePosition, mVelocity, &aimVectorMin, &timeMin, &aimVectorMax, &timeMax); + } + if (canShoot) + { + //find the aimlocation from the normalized aim vector + Point3F aimLocation = mMuzzlePosition + aimVectorMin * (aimAtTargetPoint - mMuzzlePosition).len(); + + //now add the tone down the accuracy for moving targets + bool needToDope = mVelocity.len() > 4.0f || engagingPlayer || (mProjectile->isBallistic && ! mAimAtLazedTarget); + if (needToDope) + aimLocation = dopeAimLocation(mMuzzlePosition, aimLocation); + + //set the aim location + setAimLocation(aimLocation); + + mEngageState = AimAtTarget; + mStateCounter = 15; + + //call processEngagement again immediately + PROFILE_START(AI_EngagementInner); + processEngagement(player); + PROFILE_END(); + } + else + { + //reset the bool + mTargetInRange = false; + + //if firing is sustained, keep firing + if (mFiring && mTriggerCounter > 0) + { + mTriggerCounter--; + pressFire(); + } + } + } + + //if no projectile, we need to find one... + else + { + mEngageState = ChooseWeapon; + mTargetInRange = false; + } + break; + } + + case AimAtTarget: + { + //make sure we're not trying to use an energy weapon while jetting + if (mWeaponEnergy > 0 && mNavUsingJet) + { + mEngageState = ChooseWeapon; + break; + } + + //the process order is: fire, then move + aim, we need to aim as if we're + //shooting next frame + if (engagingPlayer) + { + Point3F nextFrameAimPoint = mAimLocation - mVelocity / 30.0f; + setAimLocation(nextFrameAimPoint); + } + + //see if we have line of site + bool clearLOSToTarget = false; + RayInfo rayInfo; + Point3F startPt = mMuzzlePosition + mVelocity / 30.0f; //as if it's next frame already + Point3F endPt = mAimLocation; + U32 mask = TerrainObjectType | InteriorObjectType | PlayerObjectType | ForceFieldObjectType; + + //if the player is mounted on a vehicle, mask in vehicle types as well... + if (player->isMounted()) + mask |= VehicleObjectType; + + player->disableCollision(); + if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo)) + clearLOSToTarget = true; + else if (engagingPlayer && bool(rayInfo.object) && bool(mTargetPlayer)) + { + if (rayInfo.object->getId() == mTargetPlayer->getId()) + clearLOSToTarget = true; + } + else if (!engagingPlayer && bool(rayInfo.object) && bool(mTargetObject)) + { + if (rayInfo.object->getId() == mTargetObject->getId()) + clearLOSToTarget = true; + } + player->enableCollision(); + + bool readyToFire = false; + //if we have a clear LOS, or if we're firing a ballistic weapon and have enough skill to shoot from behind hills + if (clearLOSToTarget || (mProjectile->isBallistic && mSkillLevel >= 0.7f)) + readyToFire = true; + + //else if we're firing a linear weapon with splash damage + else if (! clearLOSToTarget && ! mProjectile->isBallistic) + { + //see if the impact point is within the damage radius of the projectile + if (mProjectile->hasDamageRadius && (rayInfo.point - mAimLocation).len() < getMax(10.0f, mProjectile->damageRadius)) + { + //need to see if the impact point has LOS to the target, otherwise, bot will try to shoot through walls... + //readyToFire = true; + readyToFire = false; + } + } + if (readyToFire) + { + mTargetInRange = true; + mEngageState = FireWeapon; + } + else + { + mTargetInRange = false; + mEngageState = FindTargetPoint; + } + + //if firing is sustained, keep firing + if (mFiring && mTriggerCounter > 0) + { + mTriggerCounter--; + pressFire(); + } + + break; + } + + case FireWeapon: + { + //make sure we're not trying to use an energy weapon while jetting + if (mWeaponEnergy > 0 && mNavUsingJet) + { + mEngageState = ChooseWeapon; + break; + } + + //if we're firing, we must be in range + mTargetInRange = true; + + //press fire and move to reload + pressFire(); + mFiring = true; + mTriggerCounter--; + + //see if firing is to be sustained (chaingun, elf gun, etc...) + if (mTriggerCounter > 0) + { + mEngageState = FindTargetPoint; + mStateCounter = 15; + } + else + { + mEngageState = ChooseWeapon; + mStateCounter = 60; + } + + break; + } + } +} + +F32 AIConnection::angleDifference(F32 angle1, F32 angle2) +{ + F32 tempAngle1 = getMin(angle1, angle2); + F32 tempAngle2 = getMax(angle1, angle2); + F32 difference = getMin(F32(tempAngle2 - tempAngle1), + F32(tempAngle1 + M_2PI - tempAngle2)); + return difference; +} + +Point3F AIConnection::correctHeading() +{ + Point3F newLocation = mNodeLocation; + + //make sure we're heading in approximately the right direction (+- 30 deg or so) + //no need to overcorrect if we're not moving very fast + if (mVelocity2D.len() > 4) + { + //if we're heading more than 90 deg in the wrong direction, or we're heading *way* too fast + //to hit the node, choose the exact opposite direction of the velocity + if (mDotOffCourse <= 0) + { + //find out our current heading (sub 90 deg since our axis is rotated) + F32 heading = mAtan(mVelocity2D.x, mVelocity2D.y) - (M_PI / 2.0f); + F32 newHeading = heading + M_PI; + + newLocation.x = mLocation.x + mDistToNode2D * mCos(newHeading); + newLocation.y = mLocation.y - mDistToNode2D * mSin(newHeading); + } + + //else mirror the angle through the desired heading + else + { + //find out our current heading (sub 90 deg since our axis is rotated) + F32 heading = mAtan(mVelocity2D.x, mVelocity2D.y) - (M_PI / 2.0f); + + //find out what angle we're currently off by + F32 headingDiff = mAcos(mDotOffCourse); + + //determine the "overcompensate" factor + F32 overCompFactor; + if (mDotOffCourse > 0.85) + overCompFactor = 3.0f; + else + overCompFactor = 2.0f; + + //now see find out which direction + F32 angle1 = heading + headingDiff; + F32 angle2 = heading - headingDiff; + Point3F newVec1(mCos(angle1), -mSin(angle1), 0); + Point3F newVec2(mCos(angle2), -mSin(angle2), 0); + + //the dot with one of these angles should have a dot near 1 - + //create the "correcting" angle to overcompensate the headingDiff + F32 newAngle; + F32 angle1Dot = get2DDot(mNodeLocation - mLocation, newVec1); + F32 angle2Dot = get2DDot(mNodeLocation - mLocation, newVec2); + if (angle1Dot > angle2Dot) + newAngle = heading + overCompFactor * headingDiff; + else + newAngle = heading - overCompFactor * headingDiff; + + //find the new point + newLocation.x = mLocation.x + mDistToNode2D * mCos(newAngle); + newLocation.y = mLocation.y - mDistToNode2D * mSin(newAngle); + } + } + + return newLocation; +} + +Point3F AIConnection::avoidPlayers(Player *player, const Point3F &desiredDestination, bool destIsFinal) +{ + F32 avoidObjectAngle = -1; + //see if we're near another player + Box3F queryBox; + queryBox.min = mLocation; + queryBox.max = mLocation; + queryBox.min -= Point3F(2.0f, 2.0f, 0.5f); + queryBox.max += Point3F(2.0f, 2.0f, 2.5f); + + ShapeBase *closestObject = NULL; + F32 closestDist = 32767; + Point3F closestLocation; + SimpleQueryList result; + //U32 mask = PlayerObjectType; + U32 mask = ShapeBaseObjectType | StaticTSObjectType; + gServerContainer.findObjects(queryBox, mask, SimpleQueryList::insertionCallback, S32(&result)); + if (result.mList.size() > 1) + { + //find out if the closest person is to the left or right... + for (S32 i = 0; i < result.mList.size(); i++) + { + ShapeBase *neighborObject = static_cast(result.mList[i]); + + //can't bump into yourself + if (neighborObject->getId() == player->getId()) + continue; + + //if it's not a static TS object, see if it's a shape base with the aiAvoidThis flag set... + if (!(neighborObject->getType() & StaticTSObjectType)) + { + ShapeBaseData *db = static_cast(neighborObject->getDataBlock()); + if (!db || ! db->aiAvoidThis) + continue; + } + + //make sure it's not a corpse! + if (neighborObject->getType() & CorpseObjectType) + continue; + + //now we see if it's actually in our way, or if it's just the bounding box... + Point3F tempVector = neighborObject->getBoxCenter() - player->getBoxCenter(); + if (tempVector.len() > 1.0f) + { + tempVector.normalize(); + player->disableCollision(); + RayInfo rayInfo; + Point3F startPt = player->getBoxCenter(); + Point3F endPt = startPt + (tempVector * 2.0f); + bool losResult = gServerContainer.castRay(startPt, endPt, neighborObject->getType(), &rayInfo); + player->enableCollision(); + + //we disregard this object if we didn't intersect with it + if (!losResult || (rayInfo.object->getId() != neighborObject->getId())) + continue; + } + + MatrixF const& tempTransform = neighborObject->getTransform(); + Point3F tempLocation; + tempTransform.getColumn(3, &tempLocation); + F32 tempDist = (tempLocation - mLocation).len(); + if (tempDist < closestDist) + { + closestDist = tempDist; + closestObject = neighborObject; + closestLocation = tempLocation; + } + } + } + + //if we didn't find anyone, return the desired dest + if (! closestObject) + { + mAvoidingObject = NULL; + return desiredDestination; + } + + //otherwise, we've got a neighbor... + else + { + //update the detection table for this bot + Player *closestPlayer = dynamic_cast(closestObject); + if (bool(closestPlayer)) + { + GameConnection *closestClient = closestPlayer->getControllingClient(); + if (closestClient) + clientDetected(closestClient->getId()); + } + + //find out how close we are to our destination + Point3F newLocation; + F32 newHeading; + F32 distToDest2D = (Point3F(desiredDestination.x, desiredDestination.y, 0.0f) - + Point3F(mLocation.x, mLocation.y, 0.0f)).len(); + + //create and normalize the vectors + Point3F directionVector = desiredDestination - mLocation; + Point3F neighborVector = closestLocation - mLocation; + directionVector.z = 0; + + //normalize the vector + if (directionVector.len() < 0.001f) + directionVector.set(0, 1, 0); + directionVector.normalize(); + + //make sure we didn't get any -NAN values... + if (directionVector.x != directionVector.x || directionVector.y != directionVector.y) + directionVector.set(0, 1, 0); + + neighborVector.z = 0; + + //normalize the vector + if (neighborVector.len() < 0.001f) + neighborVector.set(0, 1, 0); + neighborVector.normalize(); + + //make sure we didn't get any -NAN values... + if (neighborVector.x != neighborVector.x || neighborVector.y != neighborVector.y) + neighborVector.set(0, 1, 0); + + //dot the direction vector with the neighbor vector - if directly in our path, move at 90 deg... + F32 neighborDot = get2DDot(neighborVector, directionVector); + + //if the neighbor is behind us, not at all in our way, and we're still moving + if (! destIsFinal && neighborDot < 0.5) + { + mAvoidingObject = NULL; + return desiredDestination; + } + + //we've stopped - move at 45 away from target + else if (destIsFinal) + { + F32 neighborHeading = mAtan(neighborVector.x, neighborVector.y) - (M_PI / 2.0f); + F32 angle1 = neighborHeading + ((M_PI / 2.0f) + (M_PI / 4.0f)); + F32 angle2 = neighborHeading - ((M_PI / 2.0f) + (M_PI / 4.0f)); + Point3F newVec1(mCos(angle1), -mSin(angle1), 0); + Point3F newVec2(mCos(angle2), -mSin(angle2), 0); + + //find out which newVec will take us furthest away from the neighbor (smallest dot product) + F32 angle1Dot = get2DDot(neighborVector, newVec1); + F32 angle2Dot = get2DDot(neighborVector, newVec2); + if (angle1Dot < angle2Dot) + newHeading = angle1; + else + newHeading = angle2; + + //now that we've chosen the new heading, calculate the new destination location + newLocation.x = mLocation.x + getMax(6.0f, distToDest2D) * mCos(newHeading); + newLocation.y = mLocation.y - getMax(6.0f, distToDest2D) * mSin(newHeading); + newLocation.z = desiredDestination.z; + + //note that since we're already at our final destination, all we need to do is + //get bumped out of the way, so no need to save the avoidance vars... + mAvoidingObject = NULL; + } + + else if ((GameBase*)mAvoidingObject != closestObject || desiredDestination != mAvoidDestinationPoint) + { + + F32 neighborHeading = mAtan(neighborVector.x, neighborVector.y) - (M_PI / 2.0f); + F32 angle1 = neighborHeading + (M_PI / 2.0f); + F32 angle2 = neighborHeading - (M_PI / 2.0f); + Point3F newVec1(mCos(angle1), -mSin(angle1), 0); + Point3F newVec2(mCos(angle2), -mSin(angle2), 0); + F32 angle1Dot = get2DDot(directionVector, newVec1); + F32 angle2Dot = get2DDot(directionVector, newVec2); + if (angle1Dot > angle2Dot) + newHeading = angle1; + else + newHeading = angle2; + + //now that we've chosen the new heading, calculate the new destination location + newLocation.x = mLocation.x + getMax(6.0f, distToDest2D) * mCos(newHeading); + newLocation.y = mLocation.y - getMax(6.0f, distToDest2D) * mSin(newHeading); + newLocation.z = desiredDestination.z; + + //now save off the avoidance vars + mAvoidingObject = closestObject; + mAvoidSourcePoint = mLocation; + mAvoidDestinationPoint = desiredDestination; + mAvoidMovePoint = newLocation; + mAvoidForcedPath = false; + } + + //otherwise we hit this object the last time we were trying to move to our dest + else + { + if ((mAvoidMovePoint - mLocation).len() < 1.5f) + { + if (! mAvoidForcedPath) + { + mPath.forceSearch(); + mAvoidForcedPath = true; + } + newLocation = desiredDestination; + } + else + newLocation = mAvoidMovePoint; + } + + return newLocation; + } +} + +void AIConnection::processVehicleMovement(Player *player) +{ + //first, kill the nav jetting state machine... + if (mNavUsingJet) + { + mNavUsingJet = false; + mJetting.reset(); + mPath.forceSearch(); + } + + //now set the aim location if the bot is a passenger. + //note - if the passenger is in a fixed mounted position, this will be ignored anyways... + if (player->isMounted() && bool(player->getObjectMount())) + { + Point3F vec; + MatrixF vehicleMat; + player->getObjectMount()->getMountTransform(player->getMountNode(), &vehicleMat); + vehicleMat.getColumn(1,&vec); + F32 vehicleRot = mAtan(vec.x,vec.y) - (M_PI / 2.0f); + + //choose a point so the bot will face the same direction as the vehicle... + Point3F aimVector(mCos(vehicleRot), -mSin(vehicleRot), 0); + Point3F aimLocation = mLocation + 30.0f * aimVector; + aimLocation.z += 2.0f; + setScriptAimLocation(aimLocation, 1000); + } + + //finally, the script callback (mainly to handle vehicle firing, etc...) + char idStr[32]; + dSprintf(idStr, sizeof(idStr), "%d", getId()); + Con::executef(2, "AIProcessVehicle", idStr); +} + +void AIConnection::processPilotVehicle(Vehicle *myVehicle) +{ + //eventually, I may move some of the pilot code from ::getMoveList() to here... + myVehicle; + + //kill the nav jetting state machine... + if (mNavUsingJet) + { + mNavUsingJet = false; + mJetting.reset(); + mPath.forceSearch(); + } + + //finally, the script callback (mainly to handle vehicle firing, etc...) + char idStr[32]; + dSprintf(idStr, sizeof(idStr), "%d", getId()); + Con::executef(2, "AIPilotVehicle", idStr); +} + +void AIConnection::processMovement(Player *player) +{ + player->updateWorkingCollisionSet(); + + // Inform path machinery about our jetting ability. + setPathCapabilities(player); + + //if we're avoiding danger, override the current destination and move mode... + //note, if mEvadingCounter > 45, we use this to slow down reaction time... + mEvadingCounter--; + if (mEvadingCounter > 0 && mEvadingCounter < 45) + { + //make sure we don't get stuckin in the nav jet state machine + mJetting.reset(); + mNavUsingJet = false; + + mIsEvading = true; + setMoveLocation(Point3F(mEvadeLocation.x, mEvadeLocation.y, 0)); + setMoveSpeed(1.0f); + + //jump if we can - should encourage skiing + if (player->canJump()) + pressJump(); + else + pressJet(); + } + + //regular navigation... + else + { + // For debugging movement code of single bot (lh)- + S32 focusOnBot = Con::getIntVariable("$AIFocusOnBot"); + if (focusOnBot) + { + if (focusOnBot != getId()) + return; + else + { + F32 E = mPath.jetWillNeedEnergy(12.0); + if (E > 0.0) + Con::printf("%d wants %f energy", focusOnBot, E); + } + } + + //reset the nav jet stuff if we've just finished evading + if (mIsEvading) + { + mIsEvading = false; + mNavUsingJet = false; + mPath.forceSearch(); + } + + mPath.setDestMounted(false); + if (!mNavUsingJet) + mMoveMode = mMoveModePending; + + switch (mMoveMode) + { + case ModeStop: + { + //update the path anyways - mainly to correct aiming probs... + mPath.updateLocations(mLocation, mMoveDestination); + mNodeLocation = mPath.getSeekLoc(mVelocity); + + mNavUsingJet = false; + //even stopped, we need to move out of the way of teammates + Point3F moveLocation = avoidPlayers(player, mLocation, true); + if (moveLocation != mLocation) + { + setMoveSpeed(0.6f); + setMoveLocation(moveLocation); + } + else + { + setMoveSpeed(0.0f); + } + break; + } + + case ModeGainHeight: + { + mNavUsingJet = false; + //jump if we can - should encourage skiing + if (player->canJump()) + pressJump(); + else + pressJet(); + setMoveLocation(mLocation); + break; + } + + case ModeMountVehicle: + //set the path var + mPath.setDestMounted(true); + + //if we're already mounted, no need to keep moving... + if (player->isMounted()) + { + setMoveSpeed(0.0f); + break; + } + + //fall through to mode express... + case ModeWalk: + case ModeExpress: + { + //see if we have a corpse to walk to first + mPath.updateLocations(mLocation, mMoveDestination); + mNodeLocation = mPath.getSeekLoc(mVelocity); + + //see if we have a brand new node location... + if (mNodeLocation != mPrevNodeLocation) + { + mPrevNodeLocation = mNodeLocation; + mInitialLocation = mLocation; + } + + //else see if we've overshot the node and have to recalc the path + else + { + if (!mNavUsingJet) + { + Point3F vec1 = mInitialLocation - mNodeLocation; + Point3F vec2 = mLocation - mNodeLocation; + // Moved 2d check here - problems picking up packs with 3d dist check. + // Also, vectors don't need to be normalized if dot compare is with 0. + vec2.z = vec1.z = 0; + if (vec1.lenSquared() > 1.0f && vec2.lenSquared() > 1.0f) + if (mDot(vec1, vec2) < 0.0f) + { + mPrevNodeLocation.set(0, 0, 0); + mPath.forceSearch(); + } + } + } + + + if (mPath.userMustJet() && !mInWater) + { + mNavUsingJet = true; + if (mJetting.status() == AIJetWorking) + { + mJetting.process(this, player); + if (mJetting.status() != AIJetWorking) + { + if (mJetting.status() == AIJetSuccess) + mPath.informJetDone(); + else + { + if (mMoveMode == ModeMountVehicle) + pressJump(); + mPath.forceSearch(); + } + mJetting.reset(); + } + else if (mJetting.badTimeToSearch()) + mPath.informJetBusy(); + } + else + mJetting.init(mNodeLocation, mPath.intoMount(), mPath.getJetInfo()); + } + else + { + if (mNavUsingJet) + { + //force a path search after jetting is done... + mPath.forceSearch(); + mNavUsingJet = false; + } + //jet if we're falling too quickly + if (mVelocity.z < -20.0f) + pressJet(); + + //see if we're getting near the end of the path + //F32 distToEnd = getMax(mPath.distRemaining(2 * mMoveTolerance), (mLocation - mMoveDestination).len()); + bool pathIsCurrent = mPath.isPathCurrent(); + F32 distToEnd = (pathIsCurrent ? mPath.distRemaining(2 * mMoveTolerance) : (2 * mMoveTolerance)); + F32 tolerance = distToEnd > mMoveTolerance ? 0.25f : mMoveTolerance; + F32 jetEnergy = mPath.jetWillNeedEnergy(30); + + //find out where we're really heading... + Point3F moveLocation = mNodeLocation; + if (mDistToNode2D > 0.25f) + moveLocation = correctHeading(); + + //calculate the distance to where we're moving + F32 distToMove2D = (Point3F(mLocation.x, mLocation.y, 0) - + Point3F(moveLocation.x, moveLocation.y, 0)).len(); + F32 distToEnd2D = (Point3F(mLocation.x, mLocation.y, 0) - + Point3F(mMoveDestination.x, mMoveDestination.y, 0)).len(); + distToEnd = getMax(distToEnd, distToEnd2D); + + //now adjust the heading if we're bumping into other players + if (mMoveMode != ModeMountVehicle || distToEnd > 3.0f) + moveLocation = avoidPlayers(player, moveLocation, false); + + //now move in the calculated direction + setMoveLocation(moveLocation); + + //see if we're stuck... + if (distToEnd > tolerance && distToEnd > 1.0f) + { + Point3F checkStuck2D = mLocation - mStuckLocation; + checkStuck2D.z = 0.0f; + if ((mNodeLocation - mStuckDestination).len() > 1.0f || (checkStuck2D).len() > 1.0f) + { + mStuckLocation = mLocation; + mStuckDestination = mNodeLocation; + mStuckTimer = Sim::getCurrentTime(); + } + else if (Sim::getCurrentTime() - mStuckTimer > 2000) + { + setMoveMode(ModeStuck); + mStuckInitialized = false; + mStuckTryJump = true; + mStuckJumpInitialized = false; + return; + } + else + { + // Know we want to be moving- inform Path of how well we're doing- + mPath.informProgress(mVelocity); + } + } + + //look ahead in the path, and see if the next point is collinear with our current destination + Point3F nextNodeLocation; + bool isCollinearPath = false; + if (mPath.getPathNodeLoc(1, nextNodeLocation)) + { + F32 tempDot = get2DDot(moveLocation - mLocation, nextNodeLocation - moveLocation); + if (tempDot > 0.9f) + isCollinearPath = true; + } + + //add jumping and jetting to speed the player up... + if (distToMove2D > 30.0f) + { + //full speed with skiing + setMoveSpeed(1.0f); + + //jump if we can - should encourage skiing + if (mHeadingDownhill) + { + if (player->canJump() && mSkillLevel >= 0.25f) + pressJump(); + } + else + { + if (mEnergyAvailable && mEnergy > jetEnergy && mMoveMode != ModeWalk) + { + if (player->canJump()) + pressJump(); + else + pressJet(); + } + } + } + else if (distToEnd > 10.0f && isCollinearPath) + { + //full speed - no skiing + setMoveSpeed(1.0f); + + if (mEnergyAvailable && mEnergy > jetEnergy && mMoveMode != ModeWalk) + pressJet(); + } + + else if (distToMove2D > getMax(4.0f, tolerance)) + { + //full speed - no skiing + setMoveSpeed(1.0f); + } + else if (distToMove2D > tolerance) + { + //slower speed with no skiing + setMoveSpeed(0.4f); + } + else if (distToEnd < tolerance) + { + //stopped - we've arrived + setMoveMode(ModeStop); + } + } + } + break; + + case ModeStuck: + { + if (mStuckTryJump) + { + if (! mStuckJumpInitialized) + { + mStuckJumpInitialized = true; + mStuckJumpTimer = Sim::getCurrentTime(); + setMoveSpeed(0.0f); + setMoveLocation(mLocation); + } + else if (Sim::getCurrentTime() - mStuckJumpTimer < 500) + { + if (mEnergy < 0.25f) + mStuckJumpTimer = Sim::getCurrentTime(); + } + else if (Sim::getCurrentTime() - mStuckJumpTimer < 1000) + { + pressJump(); + pressJet(); + } + else if (Sim::getCurrentTime() - mStuckJumpTimer < 2000) + { + setMoveLocation(mStuckDestination); + setMoveSpeed(1.0f); + pressJet(); + } + else + { + //set the bool so it'll try to back up if we're still stuck... + mStuckTryJump = false; + + //see if that unstuck us... + if ((mLocation - mStuckLocation).len() > 1.0f) + { + mPath.forceSearch(); + setMoveSpeed(1.0f); + setMoveMode(ModeExpress, true); + mStuckLocation.set(0, 0, 0); + return; + } + } + } + + //else try backing up, and researching the path + else + { + //I guess we're still stuck... try backing up + if (! mStuckInitialized) + { + mStuckInitialized = true; + + //find a new direction to move + Point3F newLocation; + F32 foundLength = 32767; + Point3F curHeading = mNodeLocation - mLocation; + F32 curAngle = mAtan(curHeading.x, curHeading.y) - (M_PI / 2.0f); + + for (F32 i = 0.0f; i < 5.0f; i += 1.0f) + { + //run an LOS to see if the way is clear... + F32 testAngle = curAngle + ((i + 2.0) * M_PI / 4.0f); + Point3F newVec(mCos(testAngle), -mSin(testAngle), 0.0f); + U32 mask = TerrainObjectType | InteriorObjectType; + RayInfo rayInfo; + Point3F startPt = mLocation; + Point3F endPt = mLocation + (4.0f * newVec); + startPt.z += 0.3f; + endPt.z += 0.3f; + if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo)) + { + newLocation = endPt; + break; + } + else if ((rayInfo.point - startPt).len() < foundLength) + { + foundLength = (rayInfo.point - startPt).len(); + newLocation = endPt; + } + } + + //set the move location, and set the time stamp + mStuckTimer = Sim::getCurrentTime(); + setMoveLocation(newLocation); + setMoveSpeed(1.0f); + } + + //see if our time has run out, or if we're close to our dest + if ((mLocation - mMoveLocation).len() < 1.0f || Sim::getCurrentTime() - mStuckTimer > 2000) + { + mStuckLocation.set(0, 0, 0); + mPath.informStuck(mStuckLocation, mStuckDestination); + setMoveMode(ModeExpress, true); + } + } + } + break; + + } + } + + + //if we're in the middle of a nav jet, aim at the landing place... + Point3F jetAimLocation; + if (mNavUsingJet && mJetting.shouldAimAt(jetAimLocation)) + { + setAimLocation(jetAimLocation); + } + + //else set the aiming if we don't have a target to shoot at + else if (mMoveMode != ModeStop && mPath.isPathCurrent() && !bool(mTargetPlayer) && !bool(mTargetObject) && (Sim::getCurrentTime() > mLookAtTargetTimeMS)) + { + //set the player to always look 10m ahead on the path + Point3F directionVector; + Point3F aimPoint; + F32 distToEnd = mPath.distRemaining(10); + if (distToEnd >= 10) + aimPoint = mPath.getLocOnPath(10); + else + aimPoint = mNodeLocation; + + //now extend that point by 30m so we don't look down + directionVector = aimPoint - mLocation; + directionVector.z = 0; + F32 directionLen = directionVector.len(); + // LH pulled apart the normalize() to avoid (interupt causing) divides by zero + if (directionLen < 0.001) + { + aimPoint.x = mLocation.x + 30.0f * mCos(mRotation.z); + aimPoint.y = mLocation.y - 30.0f * mSin(mRotation.z); + aimPoint.z = mMuzzlePosition.z; + } + else + { // LH- here's the normalize: + aimPoint.x = mLocation.x + 30.0f * (directionVector.x / directionLen); + aimPoint.y = mLocation.y + 30.0f * (directionVector.y / directionLen); + aimPoint.z = mMuzzlePosition.z; + } + setAimLocation(aimPoint); + } +} + + +void AIConnection::scriptSustainFire(S32 count) +{ + mScriptTriggerCounter = count; +} + +static Point3F extractRotation(const MatrixF & matrix) +{ + const F32 * mat = (const F32*)matrix; + + Point3F r; + r.x = mAsin(mat[MatrixF::idx(2,1)]); + + if(mCos(r.x) != 0.f) + { + r.y = mAtan(-mat[MatrixF::idx(2,0)], mat[MatrixF::idx(2,2)]); + r.z = mAtan(-mat[MatrixF::idx(0,1)], mat[MatrixF::idx(1,1)]); + } + else + { + r.y = 0.f; + r.z = mAtan(mat[MatrixF::idx(1,0)], mat[MatrixF::idx(0,0)]); + } + + return(r); +} + +void AIConnection::getMoveList(Move** movePtr,U32* numMoves) +{ + //initialize the move structure and return pointers + mMove = NullMove; + *movePtr = &mMove; + *numMoves = 1; + + //if the system is not enabled, return having set the movePtr to a NullMove + if (!gAISystemEnabled) + return; + + //movement is done in the object's rotation - create a matrix + MatrixF moveMatrix; + moveMatrix.set(EulerF(0, 0, 0)); + moveMatrix.setColumn(3, Point3F(0, 0, 0)); + moveMatrix.transpose(); + + //aiming variables + Point3F curLocation; + F32 curYaw, curPitch; + F32 newYaw, newPitch; + Point3F rotation; + F32 xDiff, yDiff, zDiff; + + //make sure we have a valid control object + ShapeBase *ctrlObject = getControlObject(); + if (! ctrlObject) + return; + + //get the control object + Player *myPlayer = NULL; + myPlayer = dynamic_cast(ctrlObject); + + //make sure we're either controlling a player or a vehicle + if (myPlayer) + { + //if we're dead, no need to call any of the script functions... + if (! dStricmp(myPlayer->getStateName(), "dead")) + return; + + //process the ai tasks and steps + process(myPlayer); + + F32 vehicleRot = 0; + // Find the rotation around the Z axis from the mounted vehicle + if (myPlayer->isMounted() && bool(myPlayer->getObjectMount())) + { + Point3F vec, pos; + MatrixF vehicleMat; + myPlayer->getObjectMount()->getMountTransform(myPlayer->getMountNode(), &vehicleMat); + vehicleMat.getColumn(1,&vec); + vehicleMat.getColumn(3,&pos); + vehicleRot = -mAtan(-vec.x,vec.y); + } + + //get the current location (global coords) + MatrixF const& myTransform = myPlayer->getTransform(); + myTransform.getColumn(3, &curLocation); + + //initialize the aiming variables + rotation = myPlayer->getRotation(); + Point3F headRotation = myPlayer->getHeadRotation(); + curYaw = rotation.z + vehicleRot; + curPitch = headRotation.x; + xDiff = mAimLocation.x - curLocation.x; + yDiff = mAimLocation.y - curLocation.y; + + //first do Yaw + if (! isZero(xDiff) || ! isZero(yDiff)) + { + //use the cur yaw between -Pi and Pi + while (curYaw > M_2PI) + curYaw -= M_2PI; + while (curYaw < -M_2PI) + curYaw += M_2PI; + + //find the new yaw + newYaw = mAtan(xDiff, yDiff); + + //find the yaw diff + F32 yawDiff = newYaw - curYaw; + + //make it between 0 and 2PI + if (yawDiff < 0.0f) + yawDiff += M_2PI; + else if (yawDiff >= M_2PI) + yawDiff -= M_2PI; + + //now make sure we take the short way around the circle + if (yawDiff > M_PI) + yawDiff -= M_2PI; + else if (yawDiff < -M_PI) + yawDiff += M_2PI; + + mMove.yaw = yawDiff; + + //set up the movement matrix + moveMatrix.set(EulerF(0, 0, newYaw)); + } + else + moveMatrix.set(EulerF(0, 0, curYaw)); + + //next do pitch +// F32 horzDist = Point2F(yDiff, xDiff).len(); +// F32 horzDist = Point2F(mAimLocation.x - mMuzzlePosition.x, mAimLocation.y - mMuzzlePosition.y).len(); + F32 horzDist = Point2F(mAimLocation.x - mEyePosition.x, mAimLocation.y - mEyePosition.y).len(); + if (! isZero(horzDist)) + { + //we shoot from the gun, not the eye... +// F32 vertDist = mAimLocation.z - mMuzzlePosition.z; + F32 vertDist = mAimLocation.z - mEyePosition.z; + + newPitch = mAtan(horzDist, vertDist) - (M_PI / 2.0f); + + F32 pitchDiff = newPitch - curPitch; + mMove.pitch = pitchDiff; + } + + //finally, move towards mMoveLocation + xDiff = mMoveLocation.x - curLocation.x; + yDiff = mMoveLocation.y - curLocation.y; + if (((mFabs(xDiff) > 0) || (mFabs(yDiff) > 0)) && (! isZero(mMoveSpeed))) + { + if (isZero(xDiff)) + mMove.y = (curLocation.y > mMoveLocation.y ? -mMoveSpeed : mMoveSpeed); + else if (isZero(yDiff)) + mMove.x = (curLocation.x > mMoveLocation.x ? -mMoveSpeed : mMoveSpeed); + else if (mFabs(xDiff) > mFabs(yDiff)) + { + F32 value = mFabs(yDiff / xDiff) * mMoveSpeed; + mMove.y = (curLocation.y > mMoveLocation.y ? -value : value); + mMove.x = (curLocation.x > mMoveLocation.x ? -mMoveSpeed : mMoveSpeed); + } + else + { + F32 value = mFabs(xDiff / yDiff) * mMoveSpeed; + mMove.x = (curLocation.x > mMoveLocation.x ? -value : value); + mMove.y = (curLocation.y > mMoveLocation.y ? -mMoveSpeed : mMoveSpeed); + } + + //now multiply the move vector by the transpose of the object rotation matrix + moveMatrix.transpose(); + Point3F newMove; + moveMatrix.mulP(Point3F(mMove.x, mMove.y, 0), &newMove); + + //and sub the result back in the move structure + mMove.x = newMove.x; + mMove.y = newMove.y; + } + } + + else + { + Vehicle *myVehicle; + myVehicle = dynamic_cast(ctrlObject); + if (myVehicle) + { + //process the ai tasks and steps (well, not steps get ignored for piloting vehicles...) + process(myVehicle); + + //get the current location (global coords) + MatrixF const& myTransform = myVehicle->getTransform(); + myTransform.getColumn(3, &curLocation); + + //initialize the aiming variables + rotation = extractRotation(myTransform); + xDiff = mPilotAimLocation.x - curLocation.x; + yDiff = mPilotAimLocation.y - curLocation.y; + zDiff = mPilotAimLocation.z - curLocation.z; + F32 dist2D = Point2F(yDiff, xDiff).len(); + + //first do Yaw + curYaw = rotation.z; + F32 yawDiff = 0; + Point3F velocity = myVehicle->getVelocity(); + Point3F velocity2D(velocity.x, velocity.y, 0.0f); + F32 velocityDot = get2DDot(velocity2D, (mPilotDestination - curLocation)); + + //if ((mFabs(xDiff) > 5.0f || mFabs(yDiff) > 5.0f) && (mPilotSpeed == 0.0f || (velocityDot < 0.8f && velocity2D.len() > 5.0f))) + if (mFabs(xDiff) > 5.0f || mFabs(yDiff) > 5.0f) + { + //use the cur yaw between -Pi and Pi + while (curYaw > M_2PI) + curYaw -= M_2PI; + while (curYaw < -M_2PI) + curYaw += M_2PI; + + //find the new yaw + newYaw = mAtan(xDiff, yDiff); + + //find the yaw diff + yawDiff = newYaw - curYaw; + + //make it between 0 and 2PI + if (yawDiff < 0.0f) + yawDiff += M_2PI; + else if (yawDiff >= M_2PI) + yawDiff -= M_2PI; + + //now make sure we take the short way around the circle + if (yawDiff > M_PI) + yawDiff -= M_2PI; + else if (yawDiff < -M_PI) + yawDiff += M_2PI; + + //set up the movement matrix + mMove.yaw = yawDiff; + moveMatrix.set(EulerF(0, 0, newYaw)); + } + else + moveMatrix.set(EulerF(0, 0, curYaw)); + + //both pitch and destination should use the pilotDestination, not the aim location + xDiff = mPilotDestination.x - curLocation.x; + yDiff = mPilotDestination.y - curLocation.y; + zDiff = mPilotDestination.z - curLocation.z; + dist2D = Point2F(yDiff, xDiff).len(); + + //get the yAxis - the vertical slope of which is our current pitch + Point3F yAxis; + myTransform.mulV(Point3F(0, 1, 0), &yAxis); + yAxis.normalize(); + curPitch = mAtan(1.0f, yAxis.z) - (M_PI / 2.0f); + + //if we're below our destination, and don't have enough vertical velocity, pitch up... + newPitch = mAtan(dist2D, zDiff) - (M_PI / 2.0f); + newPitch = getMax(mPitchUpMax, getMin(mPitchDownMax, newPitch)); + + //use a pitch delta to prevent from pitching too fast... + S32 pitchDir = 0; + F32 pitchDelta = curPitch - mPreviousPitch; + mPreviousPitch = curPitch; + + //make sure we've got some horizontal speed + if (mPilotSpeed > 0.0f && velocity2D.len() > 5.0f) + { + + //at our current rate of ascent/descent, + F32 timeToDest = dist2D / velocity2D.len(); + F32 vertDist = velocity.z * timeToDest; + + //if we're below our dest and our vertical lift is too low, pitch up + if (zDiff > 5.0f && (vertDist < zDiff - 2.0f || curPitch > 0.0f)) + pitchDir = 1; + + //else if we're higher than our dest, and our vertical drop is too low, pitch down + else if (zDiff < -5.0f && (vertDist > zDiff + 2.0f || curPitch < 0.0f)) + pitchDir = -1; + + //make sure we're within our pitch limits, and that we're not pitching too fast + if (pitchDir > 0 && (curPitch < mPitchUpMax || pitchDelta < -mPitchIncMax)) + pitchDir = -1; + else if (pitchDir < 0 && (curPitch > mPitchDownMax || pitchDelta > mPitchIncMax)) + pitchDir = 1; + + //if we didn't meet the above conditions, our velocity is on track, and we should level out + if (pitchDir == 0) + { + //if we're pitched up, and our change in pitch is also in the up direction + if (curPitch < 0.0f && pitchDelta < 0) + pitchDir = -1; + else if (curPitch > 0.0f && pitchDelta > 0) + pitchDir = 1; + } + + //now put the pitch into the pitch move struct + if (pitchDir > 0) + mMove.pitch = mPitchUpMax; + else if (pitchDir < 0) + mMove.pitch = mPitchDownMax; + } + + //now calculate the actual movement towards mPilotDestination + //first calculate speed - scale by whether we're already heading in the right direction... + F32 speed = 0.0f; + if ((mFabs(xDiff) > 5.0f) || (mFabs(yDiff) > 5.0f)) + { + //if we're heading within a 45 deg cone, add some speed + if (mFabs(yawDiff < 0.8f)) + speed = 1.0f - (mFabs(yawDiff) / M_PI); + } + + //slow our movespeed down according to how far we are from our target... + if (speed > 0.0f && dist2D >= 5.0f && dist2D <= 20.0f) + speed *= 0.3f * getMin(0.7f, dist2D / 20.0f); + + //see if we need to reverse thrust... + if (dist2D < 6.0f && velocity2D.len() > 5.0f && mFabs(velocityDot) >= 0.78f) + speed = getMax(-1.0f, -2.0f * speed); + + //now factor in our pilot speed + speed *= mPilotSpeed; + + //see if we should add some jetting + if (velocity2D.len() < 8.0f && curPitch <= 0.05f && zDiff > 5.0f) + mTriggers[JetTrigger] = true; + + //set some debugging vars + mVehicleLocation = curLocation; + mCurrentPitch = curPitch; + mDesiredPitch = newPitch; + mPitchIncrement = mMove.pitch; + mPilotDistToDest2D = dist2D; + mPilotCurVelocity = myVehicle->getVelocity().len(); + mPilotCurThrust = speed; + mCurrentYaw = curYaw; + + //fill in the move struction + if (((mFabs(xDiff) > 5.0f) || (mFabs(yDiff) > 5.0f)) && (! isZero(speed))) + { + if (mFabs(xDiff) <= 5.0f) + mMove.y = (curLocation.y > mPilotDestination.y ? -speed : speed); + else if (mFabs(yDiff) <= 5.0f) + mMove.x = (curLocation.x > mPilotDestination.x ? -speed : speed); + else if (mFabs(xDiff) > mFabs(yDiff)) + { + F32 value = mFabs(yDiff / xDiff) * speed; + mMove.y = (curLocation.y > mPilotDestination.y ? -value : value); + mMove.x = (curLocation.x > mPilotDestination.x ? -speed : speed); + } + else + { + F32 value = mFabs(xDiff / yDiff) * speed; + mMove.x = (curLocation.x > mPilotDestination.x ? -value : value); + mMove.y = (curLocation.y > mPilotDestination.y ? -speed : speed); + } + + //now multiply the move vector by the transpose of the object rotation matrix + moveMatrix.transpose(); + Point3F newMove; + moveMatrix.mulP(Point3F(mMove.x, mMove.y, 0), &newMove); + + //and sub the result back in the move structure + mMove.x = newMove.x; + mMove.y = newMove.y; + } + } + } + + //copy the triggers into the move + for (int i = 0; i < MaxTriggerKeys; i++) + { + mMove.trigger[i] = mTriggers[i]; + mTriggers[i] = false; + } +} + +//----------------------------------------------------------------------------- +//STEP AND TASK FUNCTIONS + +void AIConnection::clearStep() +{ + if (mStep) + mStep->deleteObject(); + mStep = NULL; +} + +void AIConnection::setStep(AIStep *step) +{ + clearStep(); + mStep = step; +} + +const char *AIConnection::getStepStatus() +{ + if (! mStep) + return "Finished"; + else + { + int status = mStep->getStatus(); + switch (status) + { + case AIStep::InProgress: + return "InProgress"; + + case AIStep::Failed: + return "Failed"; + + case AIStep::Finished: + default: + return "Finished"; + } + } +} + +const char *AIConnection::getStepName() +{ + if (! mStep) + return "NONE"; + else + { + const char *stepName = mStep->getName(); + if (!stepName || !stepName[0]) + return "NONE"; + else + return stepName; + } +} + +void AIConnection::clearTasks() +{ + while (mTaskList.size()) + { + AITask *tempPtr = mTaskList[0]; + mTaskList.pop_front(); + tempPtr->deleteObject(); + } + mCurrentTask = NULL; +} + +void AIConnection::addTask(AITask *task) +{ + if (! task) + return; + + mTaskList.push_back(task); +} + +void AIConnection::removeTask(S32 id) +{ + for (S32 i = 0; i < mTaskList.size(); i++) + { + if (mTaskList[i]->getId() == id) + { + if (mCurrentTask == mTaskList[i]) + { + mCurrentTask->retire(this); + mCurrentTask = NULL; + } + mTaskList[i]->deleteObject(); + mTaskList.erase(i); + break; + } + } +} + +void AIConnection::listTasks() +{ + for (S32 i = 0; i < mTaskList.size(); i++) + { + Con::printf("%d: %s", mTaskList[i]->getId(), mTaskList[i]->getName()); + } +} + +void AIConnection::missionCycleCleanup() +{ + setMoveMode(ModeStop); + clearTasks(); + clearStep(); + mPath.forceSearch(); + mPath.missionCycleCleanup(); +} + diff --git a/ai/aiConnection.h b/ai/aiConnection.h new file mode 100644 index 0000000..8ac37d9 --- /dev/null +++ b/ai/aiConnection.h @@ -0,0 +1,403 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AICONNECTION_H_ +#define _AICONNECTION_H_ + +#ifndef _MPOINT_H_ +#include "math/mPoint.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GAMECONNECTION_H_ +#include "game/gameConnection.h" +#endif +#ifndef _MOVEMANAGER_H_ +#include "game/moveManager.h" +#endif +#ifndef _PLAYER_H_ +#include "game/player.h" +#endif +#ifndef _PROJECTILE_H_ +#include "game/projectile.h" +#endif +#ifndef _GRAPH_H_ +#include "ai/graph.h" +#endif +#ifndef _AINAVJETTING_H_ +#include "ai/aiNavJetting.h" +#endif + +//#define DEBUG_AI 1 + +class AIStep; +class AITask; +class AIConnection : public GameConnection +{ + typedef GameConnection Parent; + + //moves for the AI are not queued up, since they are moved on the server + Move mMove; + + F32 mMoveSpeed; + S32 mMoveMode; + S32 mMoveModePending; + F32 mMoveTolerance; //how close to the destination before we stop + Point3F mMoveDestination; //this is the desired final destination + Point3F mNodeLocation; //this is a node along the path towards our final destination + Point3F mMoveLocation; //this is direction to take to arrive at the node, or the destination + Point3F mEvadeLocation; //if mEvadingCounter > 0, this location will override the above two + Point3F mAimLocation; + S32 mLookAtTargetTimeMS; //used to slow down the aim twitching... + + //vars used to determine when to force a path recalc + Point3F mPrevNodeLocation; + Point3F mInitialLocation; + + //Engagement vars - should be able to shoot at anyone regardless of step/task + SimObjectPtr mEngageTarget; + enum EngageStates + { + ChooseWeapon, + OutOfRange, + ReloadWeapon, + FindTargetPoint, + AimAtTarget, + FireWeapon, + Finished + }; + S32 mEngageState; + S32 mStateCounter; + S32 mDelayCounter; + S32 mPackCheckCounter; + bool mAimAtLazedTarget; + S32 mTurretMountedId; + + //vehicle piloting vars + Point3F mPilotDestination; + Point3F mPilotAimLocation; + F32 mPilotSpeed; + F32 mPitchUpMax; + F32 mPitchDownMax; + F32 mPitchIncMax; + + //piloting debug vars + Point3F mVehicleLocation; + F32 mPilotDistToDest2D; + F32 mPilotCurVelocity; + F32 mPilotCurThrust; + F32 mCurrentPitch; + F32 mPreviousPitch; + F32 mDesiredPitch; + F32 mPitchIncrement; + F32 mCurrentYaw; + + StringTableEntry mProjectileName; + ProjectileData *mProjectile; + + SimObjectPtr mEnemyProjectile; + S32 mProjectileCounter; + S32 mEvadingCounter; + bool mIsEvading; + Point3F mImpactLocation; //not really used for anything but debugging... + + SimObjectPtr mVictim; + SimObjectPtr mCorpse; + Point3F mCorpseLocation; + S32 mVictimTime; + + //if no engagement target, we may have a gamebase object to destroy/repair + SimObjectPtr mTargetObject; + S32 mObjectMode; + + //these vars are used for both engagetarget and targetobject + F32 mRangeToTarget; + S32 mCheckTargetLOSCounter; + bool mTargetInRange; + bool mTargetInSight; + +public: + enum ObjectModes + { + DestroyObject = 0, + RepairObject, + LazeObject, + MortarObject, + MissileVehicle, + MissileNoLock, + AttackMode1, + AttackMode2, + AttackMode3, + AttackMode4, + NumObjectModes + }; + +private: + //NAV Graph vars + NavigationPath mPath; + AIJetting mJetting; + Point3F mPathDest; + bool mNewPath; + bool mNavUsingJet; + +public: + S32 mEngageMinDistance; + S32 mEngageMaxDistance; + S32 mTriggerCounter; + S32 mScriptTriggerCounter; + F32 mWeaponEnergy; + F32 mWeaponErrorFactor; + bool mFiring; + ShapeBaseImageData* mMountedImage; + + //temp vars recalculated and used each process loop + Point3F mLocation; + Point3F mVelocity; + Point3F mVelocity2D; + Point3F mRotation; + Point3F mHeadRotation; + Point3F mMuzzlePosition; + Point3F mEyePosition; + F32 mDamage; + F32 mEnergy; + F32 mEnergyReserve; //navigation must not use energy below the reserve + F32 mEnergyFloat; //used to determine whether we are recharging + bool mEnergyRecharge; + bool mEnergyAvailable; + bool mHeadingDownhill; + bool mInWater; + bool mOutdoors; + F32 mDotOffCourse; + + Player *mTargetPlayer; + Point3F mTargLocation; + Point3F mTargVelocity; + Point3F mTargVelocity2D; + Point3F mTargRotation; + F32 mTargEnergy; + F32 mTargDamage; + bool mTargInWater; + + Point3F mObjectLocation; + bool mObjectInWater; + + //skill related vars + F32 mSkillLevel; + S32 mTargStillTimeMS; + F32 mTargPrevTimeMS; + Point3F mTargPrevLocation[4]; + S32 mChangeWeaponCounter; + + F32 mDistToTarg2D; + F32 mDistToNode2D; + F32 mDistToObject2D; + + enum + { + ModeStop = 0, + ModeWalk, + ModeGainHeight, + ModeExpress, + ModeMountVehicle, + ModeStuck, + ModeCount + }; + + //vars to determine/handle if we're stuck + Point3F mStuckLocation; + Point3F mStuckDestination; + bool mStuckInitialized; + S32 mStuckTimer; + bool mStuckTryJump; + bool mStuckJumpInitialized; + S32 mStuckJumpTimer; + + SimObjectPtr mAvoidingObject; + Point3F mAvoidSourcePoint; + Point3F mAvoidDestinationPoint; + Point3F mAvoidMovePoint; + bool mAvoidForcedPath; + +protected: + void scriptProcessEngagement(); + void scriptChooseEngageWeapon(F32 distToTarg); + void scriptChooseObjectWeapon(F32 distToTarg); + void aiDebugStuff(); + +private: + enum + { + FireTrigger = 0, + JumpTrigger = 2, + JetTrigger = 3, + GrenadeTrigger = 4, + MineTrigger = 5 + }; + bool mTriggers[MaxTriggerKeys]; + + //Step and Task members + AIStep *mStep; + + Vector mTaskList; + AITask *mCurrentTask; + S32 mCurrentTaskTime; + + //DETECTION MEMBERS + struct PlayerDetectionEntry + { + S32 playerId; + bool playerLOS; + S32 playerLOSTime; + Point3F playerLastPosition; + }; + Vector mPlayerDetectionTable; + S32 mPlayerDetectionIndex; + S32 mPlayerDetectionCounter; + S32 mDetectHiddenPeriod; //even without LOS, the player will know where the enemy is until + //this detectHiddenPeriod is over... after that, they'll only use + //the last known location... + S32 mBlindedTimer; //used + +public: + AIConnection(); + ~AIConnection(); + +public: + DECLARE_CONOBJECT(AIConnection); + static void consoleInit(); + + F32 fixRadAngle(F32 angle); + static F32 get2DDot(const Point3F &vec1, const Point3F &vec2); + static F32 get2DAngle(const Point3F &endPt, const Point3F &basePt); + F32 getOutdoorRadius(const Point3F &location); //returns the outdoor freedom radius, or -1 if indoors + + void setSkillLevel(F32 level); + F32 getSkillLevel() { return mSkillLevel; } + + void setMoveSpeed(F32 speed); + void setMoveMode(S32 mode, bool abortStuckCode = false); + void setMoveTolerance(F32 tolerance); + void setMoveLocation(const Point3F &location); + void setMoveDestination(const Point3F &location); + void setTurretMounted(S32 turretId) { mTurretMountedId = turretId; } + void setAimLocation(const Point3F &location); + void setWeaponInfo(StringTableEntry projectile, S32 minDist, S32 maxDist, S32 triggerCount, F32 energyRequired, F32 errorFactor = 1.0f); + void setEnergyLevels(F32 eReserve, F32 eFloat = 0.10f); + + bool setScriptAimLocation(const Point3F &location, S32 duration); //returns false if we have a target object/player + + //here are the vehicle piloting methods + void setPilotPitchRange(F32 pitchUpMax, F32 pitchDownMax, F32 pitchIncMax); + void setPilotDestination(const Point3F &dest, F32 maxSpeed); + void setPilotAimLocation(const Point3F &dest); + + //bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result); + Point3F dopeAimLocation(const Point3F &startLocation, const Point3F &aimLocation); + + F32 angleDifference(F32 angle1, F32 angle2); + Point3F correctHeading(); + Point3F avoidPlayers(Player *player, const Point3F &desiredDestination, bool destIsFinal); + + void setEngageTarget(GameConnection *target); + S32 getEngageTarget(); + void setTargetObject(ShapeBase *targetObject, F32 range = 35.0f, S32 objectMode = DestroyObject); + S32 getTargetObject(); + bool TargetInRange() { return mTargetInRange || mTargetInSight; } + bool TargetInSight() { return mTargetInSight; } + + void setVictim(GameConnection *victim, Player *corpse); + S32 getVictimCorpse(); + S32 getVictimTime(); + + F32 getMoveSpeed() { return mMoveSpeed; } + S32 getMoveMode() { return mMoveMode; } + F32 getMoveTolerance() { return mMoveTolerance; } + Point3F getMoveDestination() { return mMoveDestination; } + Point3F getMoveLocation() { return mMoveLocation; } + Point3F getAimLocation() { return mAimLocation; } + bool scriptIsAiming() { return mLookAtTargetTimeMS > Sim::getCurrentTime(); } + + void setPathDest(const Point3F * dest = NULL); + const Point3F * getPathDest(); + void setPathCapabilities(Player * player); + F32 getPathDistance(const Point3F &destination, const Point3F &source); + F32 getPathDistRemaining(F32 maxDist); + Point3F getLOSLocation(const Point3F &targetPoint, F32 minDistance, F32 maxDistance, const Point3F &nearPoint); + Point3F getHideLocation(const Point3F &targetPoint, F32 range, const Point3F &nearPoint, F32 hideLength); + + void scriptSustainFire(S32 count = 1); + void pressFire(bool value = true) { mTriggers[FireTrigger] = value; } + void pressJump() { mTriggers[JumpTrigger] = true; } + void pressJet() { mTriggers[JetTrigger] = true; } + void pressGrenade() { mTriggers[GrenadeTrigger] = true; } + void pressMine() { mTriggers[MineTrigger] = true; } + + bool isFiring() { return mTriggers[FireTrigger]; } + bool isJumping() { return mTriggers[JumpTrigger]; } + bool isJetting() { return mTriggers[JetTrigger]; } + + void getMoveList(Move**,U32* numMoves); + void clearMoves(U32) { } + bool areMovesPending() { return true; } + + //function to update tasks, etc... + void process(ShapeBase *ctrlObject); + void processMovement(Player *player); + void processVehicleMovement(Player *player); + void processPilotVehicle(Vehicle *myVehicle); + void processEngagement(Player *player); + void setEvadeLocation(const Point3F &dangerLocation, S32 durationTicks = 0); + void initProcessVars(Player *player); + void updateDetectionTable(Player *player); + bool hasLOSToClient(S32 clientId, S32 &losTime, Point3F &lastLocation); + void clientDetected(S32 targId); + void setDetectPeriod(S32 value) { mDetectHiddenPeriod = value; } + S32 getDetectPeriod() { return mDetectHiddenPeriod; } + void setBlinded(S32 duration); + + //step and task methods + void setStep(AIStep *step); + void clearStep(); + const char *getStepStatus(); + const char *getStepName(); + + void clearTasks(); + void addTask(AITask *task); + void removeTask(S32 id); + AITask *getCurrentTask() { return mCurrentTask; } + S32 getTaskTime() { return mCurrentTaskTime; } + void listTasks(); + + void missionCycleCleanup(); +}; + +extern const char *gTargetObjectMode[AIConnection::NumObjectModes]; + +class AISlicer +{ + // MRandomLCG mRand; + // bool mEnabled; + U32 mDelay; + U32 mLastTime; + // U32 mCapPileUp; + U32 mDebugTotal; + F32 mBudget; + + public: + AISlicer(); + void init(U32 delay = 22, S32 maxPerTick = 1); + void reset(); + bool ready(S32 &counter, S32 resetVal); +}; + +extern AISlicer gCalcWeightSlicer; +extern AISlicer gScriptEngageSlicer; +extern bool gAISystemEnabled; + + +#endif diff --git a/ai/aiConsole.cc b/ai/aiConsole.cc new file mode 100644 index 0000000..a2cad2b --- /dev/null +++ b/ai/aiConsole.cc @@ -0,0 +1,766 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMatrix.h" +#include "console/console.h" +#include "console/simBase.h" +#include "game/gameBase.h" +#include "game/moveManager.h" +#include "game/player.h" +#include "ai/aiConnection.h" +#include "ai/aiStep.h" +#include "ai/aiNavStep.h" +#include "ai/aiTask.h" +#include "core/idGenerator.h" + +static S32 cAIConnect(SimObject *, S32 argc, const char** argv) +{ + //create the connection + AIConnection *aiConnection = new AIConnection(); + aiConnection->registerObject(); + + //add the AI to the client group + SimGroup *g = Sim::getClientGroup(); + g->addObject(aiConnection); + + //execute the console function + const char *teamStr = "-2"; + const char *skillStr = "0.5"; + const char *offenseStr = "1"; + const char *voicePitchStr = "0"; + const char *voiceStr = ""; + if (argc >= 3) + teamStr = argv[2]; + if (argc >= 4) + skillStr = argv[3]; + if (argc >= 5) + { + if (!dStricmp(argv[4], "true") || dAtoi(argv[4]) > 0) + offenseStr = "1"; + else + offenseStr = "0"; + } + if (argc >= 6) + voiceStr = argv[5]; + if (argc >= 7) + voicePitchStr = argv[6]; + Con::executef(aiConnection, 7, "onAIConnect", argv[1], teamStr, skillStr, offenseStr, voiceStr, voicePitchStr); + + return aiConnection->getId(); +} + +static void cAIDrop(SimObject *obj, S32, const char**) +{ + //call the script function + AIConnection *ai = static_cast(obj); + Con::executef(ai, 1, "onAIDrop"); + + //next, call the game disconnection code... + Con::printf("AI Client %d is disconnected.", ai->getId()); + ai->setDisconnectReason( "Removed" ); + + //finally, delete the object + ai->deleteObject(); +} + +static F32 cAIGetGraphDistance(SimObject *, S32, const char** argv) +{ + Point3F dest(0, 0, 0); + dSscanf(argv[1], "%f %f %f", &dest.x, &dest.y, &dest.z); + Point3F source(0, 0, 0); + dSscanf(argv[2], "%f %f %f", &source.x, &source.y, &source.z); + return NavigationGraph::fastDistance(source, dest); +} + +// Intended for initialization of whatever management is central to all bots. +AISlicer gCalcWeightSlicer; +AISlicer gScriptEngageSlicer; + +static bool cAISlicerInit(SimObject *, S32, const char**) +{ + gCalcWeightSlicer.init(30, 1); + gScriptEngageSlicer.init(15, 2); + return true; +} + +static bool cAISlicerReset(SimObject *, S32, const char**) +{ + gCalcWeightSlicer.reset(); + gScriptEngageSlicer.reset(); + return true; +} + +static void cAISystemEnabled(SimObject *, S32 argc, const char **argv) +{ + bool status = true; + if (argc == 2) + status = ((!dStricmp(argv[1], "true")) || (dAtoi(argv[1]) != 0)); + gAISystemEnabled = status; +} + +bool gAISystemEnabled = false; + +void AIInit() +{ + Con::addCommand("aiConnect", cAIConnect, "aiConnect(name [, team , skill, offense, voice, voicePitch]);", 2, 7); + Con::addCommand("AIGetPathDistance", cAIGetGraphDistance, "AIGetPathDistance(fromPoint, toPoint);", 3, 3); + Con::addCommand("AISlicerInit", cAISlicerInit, "AISlicerInit();", 1, 1); + Con::addCommand("AISlicerReset", cAISlicerReset, "AISlicerReset();", 1, 1); + Con::addCommand("AISystemEnabled", cAISystemEnabled, "AISystemEnabled([bool]);", 1, 2); +} + +static void cAISetSkillLevel(SimObject *obj, S32, const char** argv) +{ + AIConnection *ai = static_cast(obj); + ai->setSkillLevel(dAtof(argv[2])); +} + +static F32 cAIGetSkillLevel(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + return ai->getSkillLevel(); +} + +static void cAISetMoveSpeed(SimObject *obj, S32, const char** argv) +{ + AIConnection *ai = static_cast(obj); + ai->setMoveSpeed(dAtoi(argv[2])); +} + +static void cAIStop(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + ai->setMoveMode(AIConnection::ModeStop); +} + +static void cAIStepMove(SimObject *obj, S32 argc, const char** argv) +{ + AIConnection *ai = static_cast(obj); + + //first, clear the current step + ai->clearStep(); + + //set the "move to" destination + VectorF v(0,0,0); + dSscanf(argv[2], "%f %f %f", &v.x, &v.y, &v.z); + ai->setMoveDestination(Point3F(v.x, v.y, v.z)); + + //set the move mode if given + if (argc >= 4) + ai->setMoveTolerance(dAtof(argv[3])); + else + ai->setMoveTolerance(0.25f); + + //don't change the move mode if we're stuck... + if (ai->getMoveMode() != AIConnection::ModeStuck) + { + if (argc >= 5) + ai->setMoveMode(dAtoi(argv[4])); + else + ai->setMoveMode(AIConnection::ModeExpress); + } +} + +static bool cAIAimAtLocation(SimObject *obj, S32 argc, const char** argv) +{ + AIConnection *ai = static_cast(obj); + VectorF v(0,0,0); + dSscanf(argv[2], "%f %f %f", &v.x, &v.y, &v.z); + S32 duration = 1500; + if (argc == 4) + duration = getMax(0, dAtoi(argv[3])); + return ai->setScriptAimLocation(Point3F(v.x, v.y, v.z), duration); +} + +static const char* cAIGetAimLocation(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + Point3F aimPoint = ai->getAimLocation(); + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%f %f %f", aimPoint.x, aimPoint.y, aimPoint.z); + return returnBuffer; +} + +static bool cAIIsMountingVehicle(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + return (ai->getMoveMode() == AIConnection::ModeMountVehicle); +} + +static void cAISetTurretMounted(SimObject *obj, S32, const char**argv) +{ + AIConnection *ai = static_cast(obj); + ai->setTurretMounted(dAtoi(argv[2])); +} + +static void cAISetPilotDestination(SimObject *obj, S32 argc, const char**argv) +{ + AIConnection *ai = static_cast(obj); + + VectorF v(0,0,0); + dSscanf(argv[2], "%f %f %f", &v.x, &v.y, &v.z); + + F32 speed = 1.0f; + if (argc >= 4) + speed = dAtof(argv[3]); + ai->setPilotDestination(v, speed); +} + +static void cAISetPilotAimLocation(SimObject *obj, S32, const char**argv) +{ + AIConnection *ai = static_cast(obj); + VectorF v(0,0,0); + dSscanf(argv[2], "%f %f %f", &v.x, &v.y, &v.z); + ai->setPilotAimLocation(v); +} + +static void cAISetPilotPitchRange(SimObject *obj, S32, const char**argv) +{ + AIConnection *ai = static_cast(obj); + ai->setPilotPitchRange(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4])); +} + +static F32 cAIGetPathDistance(SimObject *obj, S32 argc, const char** argv) +{ + AIConnection *ai = static_cast(obj); + Point3F dest(0, 0, 0); + dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z); + Point3F source(-1, -1, -1); + if (argc == 4) + dSscanf(argv[3], "%f %f %f", &source.x, &source.y, &source.z); + return ai->getPathDistance(dest, source); +} + +static F32 cAIPathDistRemaining(SimObject *obj, S32, const char** argv) +{ + AIConnection *ai = static_cast(obj); + return ai->getPathDistRemaining(dAtof(argv[2])); +} + +static const char* cAIGetLOSLocation(SimObject *obj, S32 argc, const char** argv) +{ + AIConnection *ai = static_cast(obj); + Point3F dest(0, 0, 0); + dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z); + F32 minDist = -1; + if (argc >= 4) + minDist = dAtof(argv[3]); + F32 maxDist = 1e6; + if (argc >= 5) + maxDist = dAtof(argv[4]); + Point3F nearPoint(-1, -1, -1); + if (argc >= 6) + dSscanf(argv[5], "%f %f %f", &nearPoint.x, &nearPoint.y, &nearPoint.z); + + Point3F graphPoint = ai->getLOSLocation(dest, minDist, maxDist, nearPoint); + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%f %f %f", graphPoint.x, graphPoint.y, graphPoint.z); + return returnBuffer; +} + +static const char* cAIGetHideLocation(SimObject *obj, S32, const char** argv) +{ + AIConnection *ai = static_cast(obj); + Point3F dest(0, 0, 0); + dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z); + F32 range = dAtof(argv[3]); + Point3F nearPoint(0, 0, 0); + dSscanf(argv[4], "%f %f %f", &nearPoint.x, &nearPoint.y, &nearPoint.z); + F32 hideLength = dAtof(argv[5]); + + Point3F graphPoint = ai->getHideLocation(dest, range, nearPoint, hideLength); + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%f %f %f", graphPoint.x, graphPoint.y, graphPoint.z); + return returnBuffer; +} + +static void cAISetEngageTarget(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + + //find the target + GameConnection *target; + if (Sim::findObject(argv[2], target)) + ai->setEngageTarget(target); + else + ai->setEngageTarget(NULL); +} + +static const char* cAIGetEngageTarget(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + S32 targetId = ai->getEngageTarget(); + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d", targetId); + return returnBuffer; +} + +static void cAISetVictim(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + + //find the victim client + GameConnection *victim; + if (! Sim::findObject(argv[2], victim)) + return; + + //find the corpse + Player *corpse; + if (! Sim::findObject(argv[3], corpse)) + return; + + ai->setVictim(victim, corpse); +} + +static S32 cAIGetVictimCorpse(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + return ai->getVictimCorpse(); +} + +static S32 cAIGetVictimTime(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + return ai->getVictimTime(); +} + +static bool cAIHasLOSToClient(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + S32 losTime; + Point3F losLocation; + return ai->hasLOSToClient(dAtoi(argv[2]), losTime, losLocation); +} + +static S32 cAIGetClientLOSTime(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + S32 losTime; + Point3F losLocation; + ai->hasLOSToClient(dAtoi(argv[2]), losTime, losLocation); + return losTime; +} + +static const char* cAIGetDetectLocation(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + S32 losTime; + Point3F losLocation(0, 0, 0); + ai->hasLOSToClient(dAtoi(argv[2]), losTime, losLocation); + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%f %f %f", losLocation.x, losLocation.y, losLocation.z); + return returnBuffer; +} + +static void cAIClientDetected(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + ai->clientDetected(dAtoi(argv[2])); +} + +static void cAISetDetectPeriod(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + ai->setDetectPeriod(dAtoi(argv[2])); +} + +static S32 cAIGetDetectPeriod(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + return ai->getDetectPeriod(); +} + +static void cAISetBlinded(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + ai->setBlinded(dAtoi(argv[2])); +} + +static void cAISetDangerLocation(SimObject *obj, S32 argc, const char **argv) +{ + AIConnection *ai = static_cast(obj); + Point3F dest(0, 0, 0); + dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z); + S32 duration = 0; + if (argc == 4) + duration = dAtoi(argv[3]); + ai->setEvadeLocation(dest, duration); +} + +const char *gTargetObjectMode[AIConnection::NumObjectModes] = +{ + "Destroy", + "Repair", + "Laze", + "Mortar", + "Missile", + "MissileNoLock", + "AttackMode1", + "AttackMode2", + "AttackMode3", + "AttackMode4" +}; + +static void cAISetTargetObject(SimObject *obj, S32 argc, const char **argv) +{ + AIConnection *ai = static_cast(obj); + + //find the target + ShapeBase *targetObject; + if (Sim::findObject(argv[2], targetObject)) + { + F32 range = 30; + if (argc >= 4) + range = dAtof(argv[3]); + S32 objectMode = AIConnection::DestroyObject; + if (argc == 5) + { + for (int i = 0; i < AIConnection::NumObjectModes; i++) + { + if (! dStricmp(argv[4], gTargetObjectMode[i])) + { + objectMode = i; + break; + } + } + } + ai->setTargetObject(targetObject, range, objectMode); + } + else + ai->setTargetObject(NULL); +} + +static const char* cAIGetTargetObject(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + S32 targetId = ai->getTargetObject(); + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d", targetId); + return returnBuffer; +} + +static void cAIStepRangeObject(SimObject *obj, S32 argc, const char **argv) +{ + AIConnection *ai = static_cast(obj); + + //find the target + GameBase *targetObject; + if (Sim::findObject(argv[2], targetObject)) + { + F32 minDist, maxDist; + minDist = dAtof(argv[4]); + maxDist = dAtof(argv[5]); + Point3F fromLocation(0, 0, 0); + AIStep *step; + if (argc == 7) + { + dSscanf(argv[6], "%f %f %f", &fromLocation.x, &fromLocation.y, &fromLocation.z); + step = new AIStepRangeObject(targetObject, argv[3], &minDist, &maxDist, &fromLocation); + } + else + { + step = new AIStepRangeObject(targetObject, argv[3], &minDist, &maxDist, NULL); + } + step->registerObject("AIStepRangeObject"); + ai->addObject(step); + ai->setStep(step); + } +} + +static bool cAITargetInSight(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + return ai->TargetInSight(); +} + +static bool cAITargetInRange(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + return ai->TargetInRange(); +} + +static void cAIPressFire(SimObject *obj, S32 argc, const char** argv) +{ + AIConnection *ai = static_cast(obj); + S32 count = 1; + if (argc == 3) + count = dAtoi(argv[2]); + ai->scriptSustainFire(count); +} + +static void cAIPressJump(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + ai->pressJump(); +} + +static void cAIPressJet(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + ai->pressJet(); +} + +static void cAIPressGrenade(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + ai->pressGrenade(); +} + +static void cAIPressMine(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + ai->pressMine(); +} + +static void cAISetWeaponInfo(SimObject *obj, S32 argc, const char **argv) +{ + AIConnection *ai = static_cast(obj); + S32 triggerCount = (argc >= 6 ? dAtoi(argv[5]) : 1); + F32 requiredEnergy = (argc >= 7 ? dAtof(argv[6]) : 0.0); + F32 errorFactor = (argc >= 8 ? dAtof(argv[7]) : 1.0); + ai->setWeaponInfo(argv[2], dAtoi(argv[3]), dAtoi(argv[4]), triggerCount, requiredEnergy, errorFactor); +} + +static void cAIClearStep(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + ai->clearStep(); +} + +static void cAIStepEscort(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + ai->clearStep(); + + //find the target + GameConnection *target; + if (Sim::findObject(argv[2], target)) + { + AIStep *step = new AIStepEscort(target); + step->registerObject("AIStepEscort"); + ai->addObject(step); + ai->setStep(step); + } +} + +static void cAIStepJet(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + + Point3F dest(0,0,0); + dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z); + + if (AIStep * step = new AIStepJet(dest)) { + ai->clearStep(); + step->registerObject("AIStepJet"); + ai->addObject(step); + ai->setStep(step); + } +} + +static void cAISetPath(SimObject *obj, S32 argc, const char **argv) +{ + if (AIConnection * ai = dynamic_cast(obj)) { + if (argc == 3) { + Point3F dest(0,0,0); + dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z); + ai->setPathDest(&dest); + } + else + ai->setPathDest(); + } +} + +static void cAIStepEngage(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + + //the engage step functions better if it can be left to run uninterrupted - + //find out if we're already engaging this target before resetting the step... + GameConnection *target; + if (Sim::findObject(argv[2], target)) + { + S32 curTarget = ai->getEngageTarget(); + const char *curStepName = ai->getStepName(); + if (!dStricmp(curStepName, "AIStepEngage") && (curTarget == target->getId())) + return; + + //must be someone new we're to engage... + AIStep *step = new AIStepEngage(target); + step->registerObject("AIStepEngage"); + ai->addObject(step); + ai->setStep(step); + } + else + ai->clearStep(); +} + +static void cAIStepIdle(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + ai->clearStep(); + + Point3F idleLocation(0,0,0); + dSscanf(argv[2], "%f %f %f", &idleLocation.x, &idleLocation.y, &idleLocation.z); + + AIStep *step = new AIStepIdlePatrol(&idleLocation); + step->registerObject("AIStepIdlePatrol"); + ai->addObject(step); + ai->setStep(step); +} + +static const char* cAIStepGetStatus(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + return ai->getStepStatus(); +} + +static const char* cAIStepGetName(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + return ai->getStepName(); +} + +static void cAIClearTasks(SimObject *obj, S32, const char**) +{ + AIConnection *ai = static_cast(obj); + ai->clearTasks(); +} + +static const char* cAIAddTask(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + AITask *newTask = new AITask(); + newTask->registerObject(argv[2]); + ai->addObject(newTask); + ai->addTask(newTask); + return avar("%d", newTask->getId()); +} + +static void cAIRemoveTask(SimObject *obj, S32, const char **argv) +{ + AIConnection *ai = static_cast(obj); + ai->removeTask(dAtoi(argv[2])); +} + +static void cAIListTasks(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + ai->listTasks(); +} + +static S32 cAIGetTaskId(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + AITask *curTask = ai->getCurrentTask(); + if (bool(curTask)) + return curTask->getId(); + else + return -1; +} + +static const char* cAIGetTaskName(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + AITask *curTask = ai->getCurrentTask(); + if (bool(curTask)) + return curTask->getName(); + else + return ""; +} + +static S32 cAIGetTaskTime(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + return ai->getTaskTime(); +} + +static void cAIMissionCycleCleanup(SimObject *obj, S32, const char **) +{ + AIConnection *ai = static_cast(obj); + ai->missionCycleCleanup(); +} + +void AIConnection::consoleInit() +{ + Con::addCommand("AIConnection", "drop", cAIDrop, "ai.drop()", 2, 2); + + Con::addCommand("AIConnection", "setSkillLevel", cAISetSkillLevel, "ai.setSkillLevel(float)", 3, 3); + Con::addCommand("AIConnection", "getSkillLevel", cAIGetSkillLevel, "ai.getSkillLevel()", 2, 2); + + Con::addCommand("AIConnection", "setEngageTarget", cAISetEngageTarget, "ai.setEngageTarget(client)", 3, 3); + Con::addCommand("AIConnection", "getEngageTarget", cAIGetEngageTarget, "ai.getEngageTarget()", 2, 2); + Con::addCommand("AIConnection", "setVictim", cAISetVictim, "ai.setVictim(client, corpseObject)", 4, 4); + Con::addCommand("AIConnection", "getVictimCorpse", cAIGetVictimCorpse, "ai.getVictimCorpse()", 2, 2); + Con::addCommand("AIConnection", "getVictimTime", cAIGetVictimTime, "ai.getVictimTime()", 2, 2); + + Con::addCommand("AIConnection", "hasLOSToClient", cAIHasLOSToClient, "ai.hasLOSToClient(client)", 3, 3); + Con::addCommand("AIConnection", "getClientLOSTime", cAIGetClientLOSTime, "ai.getClientLOSTime(client)", 3, 3); + Con::addCommand("AIConnection", "getDetectLocation", cAIGetDetectLocation, "ai.getDetectLocation(client)", 3, 3); + Con::addCommand("AIConnection", "clientDetected", cAIClientDetected, "ai.clientDetected(client)", 3, 3); + Con::addCommand("AIConnection", "setDetectPeriod", cAISetDetectPeriod, "ai.setDetectPeriod()", 3, 3); + Con::addCommand("AIConnection", "getDetectPeriod", cAIGetDetectPeriod, "ai.getDetectPeriod()", 2, 2); + Con::addCommand("AIConnection", "setBlinded", cAISetBlinded, "ai.setBlinded(durationMS)", 3, 3); + Con::addCommand("AIConnection", "setDangerLocation", cAISetDangerLocation, "ai.setDangerLocation(point3F [, durationTicks])", 3, 4); + + Con::addCommand("AIConnection", "setTargetObject", cAISetTargetObject, "ai.setTargetObject(object [, range, mode: destroy/repair/laze])", 3, 5); + Con::addCommand("AIConnection", "getTargetObject", cAIGetTargetObject, "ai.getTargetObject()", 2, 2); + Con::addCommand("AIConnection", "targetInSight", cAITargetInSight, "ai.targetInSight()", 2, 2); + Con::addCommand("AIConnection", "targetInRange", cAITargetInRange, "ai.targetInRange()", 2, 2); + + Con::addCommand("AIConnection", "pressFire", cAIPressFire, "ai.pressFire([sustain count])", 2, 3); + Con::addCommand("AIConnection", "pressJump", cAIPressJump, "ai.pressJump()", 2, 2); + Con::addCommand("AIConnection", "pressJet", cAIPressJet, "ai.pressJet()", 2, 2); + Con::addCommand("AIConnection", "pressGrenade", cAIPressGrenade, "ai.pressGrenade()", 2, 2); + Con::addCommand("AIConnection", "pressMine", cAIPressMine, "ai.pressMine()", 2, 2); + + Con::addCommand("AIConnection", "setWeaponInfo", cAISetWeaponInfo, "ai.setWeaponInfo(projectile, minDist, maxDist [, triggerCount, requiredEnergy, errorFactor]);", 5, 8); + + Con::addCommand("AIConnection", "aimAt", cAIAimAtLocation, "bool ai.aimAt(point [, duration MS])", 3, 4); + Con::addCommand("AIConnection", "getAimLocation", cAIGetAimLocation, "ai.getAimLocation()", 2, 2); + + Con::addCommand("AIConnection", "isMountingVehicle", cAIIsMountingVehicle, "ai.isMountingVehicle()", 2, 2); + Con::addCommand("AIConnection", "setTurretMounted", cAISetTurretMounted, "ai.setTurretMounted(turretId)", 3, 3); + + Con::addCommand("AIConnection", "setPilotDestination", cAISetPilotDestination, "ai.setPilotDestination(point3F [, maxSpeed])", 3, 4); + Con::addCommand("AIConnection", "setPilotAim", cAISetPilotAimLocation, "ai.setPilotAim(point3F)", 3, 3); + Con::addCommand("AIConnection", "setPilotPitchRange", cAISetPilotPitchRange, "ai.setPilotPitchRange(pitchUpMax, pitchDownMax, pitchIncMax)", 5, 5); + + Con::addCommand("AIConnection", "getPathDistance", cAIGetPathDistance, "ai.getPathDistance(destination [, source])", 3, 4); + Con::addCommand("AIConnection", "pathDistRemaining", cAIPathDistRemaining, "ai.pathDistRemaining(maxDist)", 3, 3); + Con::addCommand("AIConnection", "getLOSLocation", cAIGetLOSLocation, "ai.getLOSLocation(targetPoint [, minDist, maxDist, nearPoint])", 3, 6); + Con::addCommand("AIConnection", "getHideLocation", cAIGetHideLocation, "ai.getHideLocation(targetPoint, range, nearPoint, hideLength)", 6, 6); + + //Step script fuctions + Con::addCommand("AIConnection", "clearStep", cAIClearStep, "ai.clearStep()", 2, 2); + Con::addCommand("AIConnection", "getStepStatus", cAIStepGetStatus, "ai.getStepStatus()", 2, 2); + Con::addCommand("AIConnection", "getStepName", cAIStepGetName, "ai.getStepName()", 2, 2); + + Con::addCommand("AIConnection", "stop", cAIStop, "ai.stop()", 2, 2); + Con::addCommand("AIConnection", "stepMove", cAIStepMove, "ai.stepMove(point3 [, tolerance, mode])", 3, 5); + Con::addCommand("AIConnection", "stepEscort", cAIStepEscort, "ai.stepEscort(client)", 3, 3); + Con::addCommand("AIConnection", "stepEngage", cAIStepEngage, "ai.stepEngage(client)", 3, 3); + Con::addCommand("AIConnection", "stepRangeObject", cAIStepRangeObject, "ai.stepRangeObject(object, weapon, minDist, maxDist [, nearLocation])", 6, 7); + Con::addCommand("AIConnection", "stepIdle", cAIStepIdle, "ai.stepIdle(point3)", 3, 3); + Con::addCommand("AIConnection", "stepJet", cAIStepJet, "ai.stepJet(toLoc)", 3, 3); + Con::addCommand("AIConnection", "setPath", cAISetPath, "ai.setPath([toLoc])", 2, 3); + + Con::addCommand("AIConnection", "clearTasks", cAIClearTasks, "ai.clearTasks()", 2, 2); + Con::addCommand("AIConnection", "addTask", cAIAddTask, "ai.addTask(taskName)", 3, 3); + Con::addCommand("AIConnection", "removeTask", cAIRemoveTask, "ai.removeTask(id)", 3, 3); + Con::addCommand("AIConnection", "listTasks", cAIListTasks, "ai.listTasks()", 2, 2); + Con::addCommand("AIConnection", "getTaskId", cAIGetTaskId, "ai.getTaskId()", 2, 2); + Con::addCommand("AIConnection", "getTaskName", cAIGetTaskName, "ai.getTaskName()", 2, 2); + Con::addCommand("AIConnection", "getTaskTime", cAIGetTaskTime, "ai.getTaskTime()", 2, 2); + + Con::addCommand("AIConnection", "missionCycleCleanup", cAIMissionCycleCleanup, "ai.missionCycleCleanup()", 2, 2); +} diff --git a/ai/aiDebug.cc b/ai/aiDebug.cc new file mode 100644 index 0000000..7e8d313 --- /dev/null +++ b/ai/aiDebug.cc @@ -0,0 +1,246 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/aiConnection.h" +#include "ai/aiTask.h" +#include "ai/aiStep.h" +#include "game/vehicle.h" + +void AIConnection::aiDebugStuff() +{ + //DEBUG - setup + S32 lineNum = 16; + char idBuf[16]; + dSprintf(idBuf, sizeof(idBuf), "%d", getId()); + + //DEBUG - check if we're debugging this client + if (Con::getIntVariable("$AIDebugClient") != getId()) + return; + + //make sure we have a valid control object + ShapeBase *ctrlObject = getControlObject(); + if (! ctrlObject) + { + Con::executef(4, "aiDebugText", idBuf, "15", "CLIENT IS DEAD!"); + return; + } + + //get the control object + Player *myPlayer = NULL; + myPlayer = dynamic_cast(ctrlObject); + + Vehicle *myVehicle = NULL; + myVehicle = dynamic_cast(ctrlObject); + + //DEBUG - Clear text lines + char textBuf[256]; + for (S32 i = lineNum; i < 64; i++) + Con::executef(4, "aiDebugText", idBuf, avar("%d", i), ""); + + //DEBUG - current task + if (mCurrentTask) + dSprintf(textBuf, sizeof(textBuf), "Current task: %d: %s %d", mCurrentTask->getId(), mCurrentTask->getName(), mCurrentTask->getWeight()); + else + dSprintf(textBuf, sizeof(textBuf), "NO TASK!"); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - current step + if (mStep) + dSprintf(textBuf, sizeof(textBuf), "Current step: %s", mStep->getName()); + else + dSprintf(textBuf, sizeof(textBuf), "NO STEP!"); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //find out if the control object is a player + if (myPlayer) + { + //Debug - Move Mode + switch (mMoveMode) + { + case ModeStop: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: Stop", mMoveMode); break; + case ModeWalk: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: Walk", mMoveMode); break; + case ModeGainHeight: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: GainHeight", mMoveMode); break; + case ModeExpress: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: Express", mMoveMode); break; + case ModeMountVehicle: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: MountVehicle", mMoveMode); break; + case ModeStuck: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: Stuck", mMoveMode); break; + default: dSprintf(textBuf, sizeof(textBuf), "Move Mode unknown"); break; + } + //if we're using the jet state machine, indicate this as well + if (mNavUsingJet) + dStrcpy(&textBuf[dStrlen(textBuf)], " USING JET STATE MACHINE"); + + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - distance to destination + dSprintf(textBuf, sizeof(textBuf), "Dist to node = %.3f", mDistToNode2D); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - final destination + dSprintf(textBuf, sizeof(textBuf), "Destination is %.3f %.3f %.3f", mMoveDestination.x, mMoveDestination.y, mMoveDestination.z); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - velocity + dSprintf(textBuf, sizeof(textBuf), "Velocity = %.3f", mVelocity.len()); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - energy + F32 jetEnergy = mPath.jetWillNeedEnergy(4); + dSprintf(textBuf, sizeof(textBuf), "Energy = %.3f, jet will require: %.3f", mEnergy, jetEnergy); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - damage + dSprintf(textBuf, sizeof(textBuf), "Damage = %.3f", mDamage); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - downhill + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mHeadingDownhill ? "heading DOWNHILL" : "heading UPHILL"); + + //DEBUG - outdoors + F32 freedomRadius; + if (mPath.locationIsOutdoors(mLocation, &freedomRadius)) + dSprintf(textBuf, sizeof(textBuf), "OUTDOORS: %.3f", freedomRadius); + else + dSprintf(textBuf, sizeof(textBuf), "Indoors"); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - me/targ in water + dSprintf(textBuf, sizeof(textBuf), "%s | %s", mInWater ? "IN WATER" : "not in water", mTargInWater ? "TARGET IN WATER" : " targ not in water"); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - jetting + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mTriggers[JetTrigger] ? "JETTING" : ""); + + //DEBUG - is stuck + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mMoveMode == ModeStuck ? "STUCK!!!" : ""); + + //DEBUG - engage target + dSprintf(textBuf, sizeof(textBuf), "Engage Target: %d", bool(mEngageTarget) ? mEngageTarget->getId() : -1); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - target object + dSprintf(textBuf, sizeof(textBuf), "Target Object: %d", bool(mTargetObject) ? mTargetObject->getId() : -1); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - target object + dSprintf(textBuf, sizeof(textBuf), "%s %s", (mTargetInSight ? "TARG IN SIGHT" : "targ not in sight"), + (mTargetInRange ? "TARG IN RANGE" : "targ not in range")); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - engage state + switch (mEngageState) + { + case ChooseWeapon: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: ChooseWeapon", mEngageState); break; + case OutOfRange: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: OutOfRange", mEngageState); break; + case ReloadWeapon: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: ReloadWeapon", mEngageState); break; + case FindTargetPoint: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: FindTargetPoint", mEngageState); break; + case AimAtTarget: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: AimAtTarget", mEngageState); break; + case FireWeapon: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: FireWeapon", mEngageState); break; + default: dSprintf(textBuf, sizeof(textBuf), "Engage State unknown"); break; + } + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - current weapon + dSprintf(textBuf, sizeof(textBuf), "Current weapon: %s", mProjectileName); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - firing + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mTriggers[FireTrigger] ? "FIRING" : ""); + + //DEBUG - distance to target + if (bool(mTargetPlayer)) + dSprintf(textBuf, sizeof(textBuf), "Dist to target = %.3f", mDistToTarg2D); + else if (bool(mTargetObject)) + dSprintf(textBuf, sizeof(textBuf), "Dist to target = %.3f", mDistToObject2D); + else + dSprintf(textBuf, sizeof(textBuf), "Dist to target = NO TARGET"); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - clear lines + Con::executef(2, "aiDebugClearLines", idBuf); + + //DEBUG - draw the projectile path + char startPt[64], endPt[64]; + if (bool(mEnemyProjectile)) + { + Point3F projLocation; + MatrixF const& projTransform = mEnemyProjectile->getTransform(); + projTransform.getColumn(3, &projLocation); + dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", projLocation.x, projLocation.y, projLocation.z); + dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mImpactLocation.x, mImpactLocation.y, mImpactLocation.z); + Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "1.0 0.0 0.0"); + } + + //Debug - draw the node location + dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mLocation.x, mLocation.y, mLocation.z + 0.1f); + dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mNodeLocation.x, mNodeLocation.y, mNodeLocation.z + 0.1f); + Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "1.0 0.0 1.0"); + + //DEBUG - draw the move location + dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mLocation.x, mLocation.y, mLocation.z + 0.2f); + dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mMoveLocation.x, mMoveLocation.y, mMoveLocation.z + 0.2f); + Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "0.0 1.0 1.0"); + + //Debug - draw the move destination + dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mLocation.x, mLocation.y, mLocation.z); + dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mMoveDestination.x, mMoveDestination.y, mMoveDestination.z); + Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "0.0 0.0 1.0"); + + //Debug - draw the aim location + dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mMuzzlePosition.x, mMuzzlePosition.y, mMuzzlePosition.z); + dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mAimLocation.x, mAimLocation.y, mAimLocation.z); + Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "1.0 0.0 0.0"); + } + + //debugging code for if the bot is piloting a vehicle + else if (myVehicle) + { + //Debug - destination + dSprintf(textBuf, sizeof(textBuf), "Destination: %.3f %.3f %.3f", mPilotDestination.x, mPilotDestination.y, mPilotDestination.z); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //Debug - distance to dest + dSprintf(textBuf, sizeof(textBuf), "Distance to dest2D: %.3f", mPilotDistToDest2D); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //Debug - aim location + dSprintf(textBuf, sizeof(textBuf), "Destination: %.3f %.3f %.3f", mPilotAimLocation.x, mPilotAimLocation.y, mPilotAimLocation.z); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //Debug - current speed/thrust + dSprintf(textBuf, sizeof(textBuf), "Velocity: %.3f, Thrust: %.3f", mPilotCurVelocity, mPilotCurThrust); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //Debug - current pitch, desired pitch, pitch inc + dSprintf(textBuf, sizeof(textBuf), "current pitch: %.3f, desired pitch: %.3f, pitch inc: %.3f", mCurrentPitch, mDesiredPitch, mPitchIncrement); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //Debug - current pitch, desired pitch, pitch inc + dSprintf(textBuf, sizeof(textBuf), "pitchUpMax: %.3f, pitchDownMax: %.3f", mPitchUpMax, mPitchDownMax); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //Debug - current yaw, yaw diff + dSprintf(textBuf, sizeof(textBuf), "curYaw: %.3f", mCurrentYaw); + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf); + + //DEBUG - jetting + Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mTriggers[JetTrigger] ? "JETTING" : ""); + + //DEBUG - clear lines + char startPt[64], endPt[64]; + Con::executef(2, "aiDebugClearLines", idBuf); + + //Debug - draw the vehicle destination + dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mVehicleLocation.x, mVehicleLocation.y, mVehicleLocation.z); + dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mPilotDestination.x, mPilotDestination.y, mPilotDestination.z); + Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "0.0 0.0 1.0"); + + //Debug - draw the vehicle aim location + dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mVehicleLocation.x, mVehicleLocation.y, mVehicleLocation.z + 0.1f); + dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mPilotAimLocation.x, mPilotAimLocation.y, mPilotAimLocation.z + 0.1f); + Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "1.0 0.0 0.0"); + } +} diff --git a/ai/aiMath.cc b/ai/aiMath.cc new file mode 100644 index 0000000..f45b95f --- /dev/null +++ b/ai/aiMath.cc @@ -0,0 +1,341 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/aiMath.h" +#include "ai/tBinHeap.h" +#include "core/realComp.h" + +//------------------------------------------------------------------------------------- + +F32 solveForZ(const PlaneF& plane, const Point3F& point) +{ + AssertFatal(mFabs(plane.z) > 0.0001, "solveForZ() expects non-vertical plane"); + return (-plane.d - plane.x * point.x - plane.y * point.y) / plane.z; +} + +//------------------------------------------------------------------------------------- + +// Simple "iterator", used as in- +// for (mArea.start(step); mArea.pointInRect(step); mArea.step(step)) +// ... +bool GridArea::start(Point2I& steppingPoint) const +{ + if (isValidRect()) + { + steppingPoint = point; + return true; + } + return false; +} +bool GridArea::step(Point2I& steppingPoint) const +{ + if (++steppingPoint.x >= (point.x + extent.x)) + { + steppingPoint.x = point.x; + if (++steppingPoint.y >= (point.y + extent.y)) + return false; + } + return true; +} + +//------------------------------------------------------------------------------------- + +GridVisitor::GridVisitor(const GridArea & area) + : mArea(area.point, area.extent) +{ + mPostCheck = mPreCheck = true; +} + +// Default versions of virtuals. The Before and After callbacks just remove themselves: +bool GridVisitor::beforeDivide(const GridArea&,S32) {return !(mPreCheck = false);} +bool GridVisitor::atLevelZero(const GridArea&) {return true; } +bool GridVisitor::afterDivide(const GridArea&,S32,bool) {return !(mPostCheck= false);} + +// Recursive region traversal. +// R is a box of width 2^L, and aligned on like boundary (L low bits all zero) +bool GridVisitor::recurse(GridArea R, S32 L) +{ + bool success = true; + + if (! R.overlaps(mArea)) { + success = false; + } + else if (L == 0) { + success = atLevelZero(R); + } + else if (mPreCheck && mArea.contains(R) && !beforeDivide(R, L)) { // early out? + success = false; + } + else { + // Made it to sub-division! + S32 half = 1 << (L - 1); + for (S32 y = half; y >= 0; y -= half) + for (S32 x = half; x >= 0; x -= half) + if (!recurse(GridArea(R.point.x + x, R.point.y + y, half, half), L - 1)) + success = false; + + // Post order stuff- + if (mPostCheck && mArea.contains(R) && !afterDivide(R, L, success)) + success = false; + } + return success; +} + +bool GridVisitor::traverse() +{ + S32 level = 1; + GridArea powerTwoRect (-1, -1, 2, 2); + + // Find the power-of-two-sized rect that encloses user-supplied grid. + while (!powerTwoRect.contains(mArea)) + if (++level < 31) { + powerTwoRect.point *= 2; + powerTwoRect.extent *= 2; + } + else + return false; + + // We have our rect and starting level, perform the recursive traversal: + return recurse(powerTwoRect, level); +} + + +//------------------------------------------------------------------------------------- +// Find distance of point from the segment. If the point's projection onto +// line isn't within the segment, then take distance from the endpoints. +// +// This also leaves the closest point set in soln. +// +F32 LineSegment::distance(const Point3F & p) +{ + VectorF vec1 = b - a, vec2 = p - a; + F32 len = vec1.len(), dist = vec2.len(); + + soln = a; // good starting default. + + // First check for case where A and B are basically same (avoid overflow) - + // can return distance from either point. + if( len > 0.001 ) + { + // normalize vector from A to B and get projection of AP along it. + F32 dot = mDot( vec1 *= (1/len), vec2 ); + if(dot >= 0) + { + if(dot <= len) + { + F32 sideSquared = (dist * dist) - (dot * dot); + if (sideSquared <= 0) // slight imprecision led to NaN + dist = sideSquared = 0; + else + dist = mSqrt(sideSquared); + soln += (vec1 * dot); + } + else + { + soln = b; + dist = (b - p).len(); + } + } + } + + return dist; +} + +// Special check which does different math in 3D and 2D. If the test is passed +// in 3d, then do a 2d check. +bool LineSegment::botDistCheck(const Point3F& p, F32 dist3, F32 dist2) +{ + F32 d3 = distance(p); + if (d3 < dist3) + { + Point2F proj2d(soln.x - p.x, soln.y - p.y); + F32 d2 = proj2d.lenSquared(); + dist2 *= dist2; + return (d2 < dist2); + } + return false; +} + + +//------------------------------------------------------------------------------------- +// Get a list of grid offsets that "spirals" out in order of closeness. +// This is used by the navigation (preprocess) code to find nearest +// obstructed grid location, and it uses this for its search order. + +S32 getGridSpiral(Vector & spiral, Vector & dists, S32 radius) +{ + S32 x, y, i; + Vector pool; + BinHeap heap; + + // Build all the points that we'll want to consider. We're going to + // leave off those points that are outside of circle. + for( y = -radius; y <= radius; y++ ) + for( x = -radius; x <= radius; x++ ) + { + F32 dist = Point2F( F32(x), F32(y) ).len(); + if( dist <= F32(radius) + 0.00001 ) + { + pool.push_back( Point2I(x,y) ); + heap.insert( -dist ); // negate for sort order + } + } + + // Get the elements out in order. + heap.buildHeap(); + while( (i = heap.headIndex()) >= 0 ) + { + spiral.push_back( pool[i] ); + dists.push_back( -heap[i] ); // get the (sign-restored) distance + heap.removeHead(); + } + + return spiral.size(); +} + + +//----------------------------------------------------------------- +// Line Stepper class. + +void LineStepper::init(const Point3F & a, const Point3F & b) +{ + solution = A = a, B = b; + total = (dir1 = B - A).len(); + soFar = advance = 0.0f; + + if( total > 0.00001 ) + { + dir1 *= (1 / total); + } + else + { + total = 0.0f; + dir1.set(0,0,0); + } +} + +// +// Finds the intersection of the line with the sphere on the way out. If there +// are two intersections, and we're starting outside, this should find the +// second. +// +F32 LineStepper::getOutboundIntersection(const SphereF & S) +{ + // get projection of sphere center along our line + F32 project = mDot( S.center - A, dir1 ); + + // find point nearest to center of sphere + Point3F nearPoint = A + (dir1 * project); + + // if this isn't in the sphere, then there's no intersection. negative return flags this. + if( ! S.isContained(nearPoint) ) + return -1.0f; + + // there is an intersection, figure how far from nearest point it is + F32 length = mSqrt( S.radius*S.radius - (nearPoint-S.center).lenSquared() ); + + // find the solution point and advance amount (which is return value) + solution = nearPoint + dir1 * length; + return( advance = length + project ); +} + +const Point3F& LineStepper::advanceToSolution() +{ + if( advance != 0.0f ) + { + soFar += advance; + A = solution; + advance = 0.0f; + } + return solution; +} + +//--------------------------------------------------------------- +// Finds the center of Mass of a CONVEX polygon. +// Verts should contain an array of Point2F's, in order, +// and of size n, that will represent the polygon. +//--------------------------------------------------------------- +bool polygonCM(S32 n, Point2F *verts, Point2F *CM) +{ + if(!verts || n < 3) + return false; + + F32 area, area2 = 0.0; + Point2F cent; + CM->x = 0; + CM->y = 0; + + U32 i; + for(i = 1; i < (n-1); i++) + { + triCenter(verts[0], verts[i], verts[i+1], cent); + area = triArea(verts[0], verts[i], verts[i+1]); + CM->x += area * cent.x; + CM->y += area * cent.y; + area2 += area; + } + + CM->x /= 3 * area2; + CM->y /= 3 * area2; + return true; +} + +//------------------------------------------------------------------------------------- + +// Lifted from tools/morian/CSGBrush.cc. This converts to doubles for insurance, which +// shouldn't pose a speed problem since this is mainly used in graph preprocess. +bool intersectPlanes(const PlaneF& p, const PlaneF& q, const PlaneF& r, Point3F* pOut) +{ + Point3D p1(p.x, p.y, p.z); + Point3D p2(q.x, q.y, q.z); + Point3D p3(r.x, r.y, r.z); + F64 d1 = p.d, d2 = q.d, d3 = r.d; + + F64 bc = (p2.y * p3.z) - (p3.y * p2.z); + F64 ac = (p2.x * p3.z) - (p3.x * p2.z); + F64 ab = (p2.x * p3.y) - (p3.x * p2.y); + F64 det = (p1.x * bc) - (p1.y * ac) + (p1.z * ab); + + // Parallel planes + if (mFabsD(det) < 1e-5) + return false; + + F64 dc = (d2 * p3.z) - (d3 * p2.z); + F64 db = (d2 * p3.y) - (d3 * p2.y); + F64 ad = (d3 * p2.x) - (d2 * p3.x); + F64 detInv = 1.0 / det; + + pOut->x = ((p1.y * dc) - (d1 * bc) - (p1.z * db)) * detInv; + pOut->y = ((d1 * ac) - (p1.x * dc) - (p1.z * ad)) * detInv; + pOut->z = ((p1.y * ad) + (p1.x * db) - (d1 * ab)) * detInv; + + return true; +} + +bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result) +{ + if (v1.isZero() || v2.isZero() || (result == NULL)) + return false; + + Point3F v1Norm = v1, v2Norm = v2; + v1Norm.normalize(); + v2Norm.normalize(); + + if ((! isZero(1.0f - v1Norm.len())) || (! isZero(1.0f - v2Norm.len()))) + return false; + + //make sure the vectors are non-colinear + F32 dot = mDot(v1Norm, v2Norm); + if (dot > 0.999f || dot < -0.999f) + return false; + + //find the cross product, and normalize the result + mCross(v1Norm, v2Norm, result); + result->normalize(); + + return true; +} diff --git a/ai/aiMath.h b/ai/aiMath.h new file mode 100644 index 0000000..b0108d5 --- /dev/null +++ b/ai/aiMath.h @@ -0,0 +1,349 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHMATH_H_ +#define _GRAPHMATH_H_ + +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif +#ifndef _MSPHERE_H_ +#include "math/mSphere.h" +#endif +#ifndef _MATHIO_H_ +#include "math/mathIO.h" +#endif + +//------------------------------------------------------------------------------------- +// One-line convenience functions: + +inline bool validArrayIndex(S32 index, S32 arrayLength) +{ + return U32(index) < U32(arrayLength); // (Unsigned compare handles < 0 case too) +} + +inline F32 triArea(const Point2F& a, const Point2F& b, const Point2F& c) +{ + return mFabs((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y)); +} + +inline void triCenter(const Point2F& p1, const Point2F& p2, const Point2F& p3, Point2F &c) +{ + c.x = p1.x + p2.x + p3.x; + c.y = p1.y + p2.y + p3.y; +} + +// prototype for finding the CM(center of mass) of a convex poly +bool polygonCM(S32 n, Point2F *verts, Point2F *CM); + +// Find point at intersection of three planes if such exists (returns true if so). +bool intersectPlanes(const PlaneF& p, const PlaneF& q, const PlaneF& r, Point3F* pOut); + +// Lookup into NxN table in which one entry is stored for each pair of unique indices. +inline S32 triangularTableIndex(S32 i1, S32 i2) { + if (i1 > i2) + return (((i1 - 1) * i1) >> 1) + i2; + else + return (((i2 - 1) * i2) >> 1) + i1; +} + +//------------------------------------------------------------------------------------- + +// Where vertical line through point hits plane (whose normal.z != 0): +F32 solveForZ(const PlaneF& plane, const Point3F& point); + +// Cross product, but with a check for bad input (parallel vectors): +bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result); + +//------------------------------------------------------------------------------------- +// Encapsulate some common operations on the grid rectangles +class GridArea : public RectI +{ + public: + GridArea() { } + GridArea(const Point2I& pt,const Point2I& ext) : RectI(pt,ext) {} + GridArea(S32 L, S32 T, S32 W, S32 H) : RectI(L,T,W,H) {} + GridArea(const GridArea & g) : RectI(g.point,g.extent) {} + + // grid to index, and vice versa: + S32 getIndex(Point2I pos) const; + Point2I getPos(S32 index) const; + + // step through grid in index order (Y outer loop): + bool start(Point2I &p) const; + bool step(Point2I &p) const; +}; + +inline S32 GridArea::getIndex(Point2I p) const +{ + p -= point; + if( validArrayIndex(p.x, extent.x) ) + if( validArrayIndex(p.y, extent.y) ) + return p.y * extent.x + p.x; + return -1; +} + +inline Point2I GridArea::getPos(S32 index) const +{ + Point2I P; + P.x = point.x + index % extent.x; + P.y = point.y + index / extent.x; + return P; +} + +//------------------------------------------------------------------------------------- +// Linear range mapping. + +// Get value that is pct way between first and last. Note this is probably a better +// way of getting midpoints than (A+B)*.5 due to rounding. +template T scaleBetween(T first, T last, F32 pct) +{ + return first + (pct * (last - first)); +} + +// Get percent of way of value between min and max, clamping return to range [0,1] +template F32 getPercentBetween(T value, T min, T max) +{ + if(mFabs((max - min)) < 0.0001) + return 0.0f; + else if(min < max){ + if (value <= min) return 0.0f; + else if (value >= max) return 1.0f; + } + else if (min > max){ + if (value >= min) return 0.0f; + else if (value <= max) return 1.0f; + } + return F32((value - min) / (max - min)); +} + +// Map value from domain into the given range, capping on ends- +template T mapValueLinear(T value, T dmin, T dmax, T rmin, T rmax) +{ + return scaleBetween(rmin, rmax, getPercentBetween(value, dmin, dmax)); +} + +template T mapValueQuadratic(T value, T dmin, T dmax, T rmin, T rmax) +{ + F32 pct = getPercentBetween(value, dmin, dmax); + return scaleBetween(rmin, rmax, pct * pct); +} + +template T mapValueSqrt(T value, T dmin, T dmax, T rmin, T rmax) +{ + F32 pct = getPercentBetween(value, dmin, dmax); + return scaleBetween(rmin, rmax, mSqrt(pct)); +} + +//------------------------------------------------------------------------------------- +// An object to do a quad-tree traversal of a grid region. + +class GridVisitor +{ + protected: + bool mPreCheck; + bool mPostCheck; + + bool recurse(GridArea rect, S32 level); + + public: + const GridArea mArea; + GridVisitor(const GridArea & area); + + // virtuals - how you get visited: + virtual bool beforeDivide(const GridArea& R, S32 level); + virtual bool atLevelZero(const GridArea& R); + virtual bool afterDivide(const GridArea& R, S32 level, bool success); + + // call the traversal: + bool traverse(); +}; + +//------------------------------------------------------------------------------------- +// Return if the two points are within the given threshold distance. + +template +bool within(PointType p1, const PointType & p2, DistType thresh) +{ + return (p1 -= p2).lenSquared() <= (thresh * thresh); +} + +inline bool within_2D(const Point3F & A, const Point3F & B, F32 D) +{ + Point2F A2d( A.x, A.y ); + Point2F B2d( B.x, B.y ); + return within( A2d, B2d, D ); +} + +//------------------------------------------------------------------------------------- +// Get a list of grid offsets that "spirals" out in order of closeness. + +S32 getGridSpiral(Vector & spiral, Vector & dists, S32 radius); + +//------------------------------------------------------------------------------------- +// Given a point, used to find closest distance/point on segment. + +class LineSegment +{ + Point3F a, b; + Point3F soln; + public: + LineSegment(const Point3F& _a, const Point3F& _b) {soln=a =_a;b =_b;} + LineSegment() {soln=a=b=Point3F(0,0,0);} + public: + void set(const Point3F& _a, const Point3F& _b) {soln=a =_a;b =_b;} + F32 distance(const Point3F& p); + bool botDistCheck(const Point3F& p, F32 d3, F32 d2); + Point3F solution() {return soln;} + Point3F getEnd(bool which) {return which ? b : a;} +}; + +//------------------------------------------------------------------------------------- + +class LineStepper +{ + Point3F A, B; + Point3F dir1; // unit direction vector from A to B. + F32 total; // total dist from A to B. + F32 soFar; // how far we've come. + + F32 advance; // solutions to queries are kept until the user + Point3F solution; // tells us to "officially" advance. + + public: + LineStepper(const Point3F & a, const Point3F & b) { init(a,b); } + + const Point3F & getSolution() { return solution; } + F32 distSoFar() { return soFar; } + F32 totalDist() { return total; } + F32 remainingDist() { return total-soFar; } + + // Methods that actually do stuff: + void init(const Point3F & a, const Point3F & b); + F32 getOutboundIntersection(const SphereF & s); + const Point3F & advanceToSolution(); +}; + +//------------------------------------------------------------------------------------- +// reverse elements in place + +template Vector & reverseVec( Vector & vector ) +{ + for(S32 halfWay = (vector.size() >> 1); halfWay; /* dec'd in loop */ ) + { + T & farOne = * (vector.end() - halfWay--); + T & nearOne = * (vector.begin() + halfWay); + T tmp=farOne; farOne=nearOne; nearOne=tmp; // swap + } + return vector; +} + +//-------------------------------------------------------------------------- +// Read/Write a vector of items, using T.read(stream), T.write(stream). +// Skip function reads same amount as readVector1(), bypassing data. + +template bool readVector1(Stream & stream, Vector & vec) +{ + S32 num, i; + bool Ok = stream.read( & num ); + for( i = 0, vec.setSize( num ); i < num && Ok; i++ ) + Ok = vec[i].read( stream ); + return Ok; +} +template bool writeVector1(Stream & stream, const Vector & vec) +{ + bool Ok = stream.write( vec.size() ); + for( U32 i = 0; i < vec.size() && Ok; i++ ) + Ok = vec[i].write( stream ); + return Ok; +} + +//------------------------------------------------------------------------------------- +// Read/Write a vector of items, using stream.read(T), stream.write(T). + +template bool readVector2(Stream & stream, Vector & vec) +{ + S32 num, i; + bool Ok = stream.read( & num ); + for( i = 0, vec.setSize( num ); i < num && Ok; i++ ) + Ok = stream.read(&vec[i]); + return Ok; +} +template bool writeVector2(Stream & stream, const Vector & vec) +{ + bool Ok = stream.write( vec.size() ); + for( S32 i = 0; i < vec.size() && Ok; i++ ) + Ok = stream.write(vec[i]); + return Ok; +} + +//------------------------------------------------------------------------------------- +// Functions to read / write a vector of items that define mathRead() / mathWrite(). + +template bool mathReadVector(Vector& vec, Stream& stream) +{ + S32 num, i; + bool Ok = stream.read(&num); + for (i = 0, vec.setSize(num); i < num && Ok; i++ ) + Ok = mathRead(stream, &vec[i]); + return Ok; +} + +template bool mathWriteVector(const Vector& vec, Stream& stream) +{ + bool Ok = stream.write(vec.size()); + for (S32 i = 0; i < vec.size() && Ok; i++) + Ok = mathWrite(stream, vec[i]); + return Ok; +} + +//------------------------------------------------------------------------------------- +// Perform setSize() and force construction of all the elements. Done to address some +// difficulty experienced in getting virtual function table pointer constructed. + +template +void setSizeAndConstruct(Vector & vector, S32 size) +{ + vector.setSize(size); + T * tList = new T [size]; + S32 memSize = size * sizeof(T); + dMemcpy(vector.address(), tList, memSize); + delete [] tList; +} + +template +void destructAndClear(Vector & vector) +{ + for (S32 i = vector.size() - 1; i >= 0; i--) + vector[i].~T(); + vector.clear(); +} + +template +void setSizeAndClear(Vector & vector, S32 size, S32 value=0) +{ + vector.setSize( size ); + dMemset(vector.address(), value, vector.memSize()); +} + +//-------------------------------------------------------------------------- +// Read a vector with construction + +template bool constructVector(Stream & stream, Vector & vec) +{ + S32 num, i; + bool Ok = stream.read(&num); + if(num && Ok) + { + setSizeAndConstruct(vec, num); + for (i = 0; i < num && Ok; i++) + Ok = vec[i].read(stream); + } + return Ok; +} + +#endif diff --git a/ai/aiNavJetting.cc b/ai/aiNavJetting.cc new file mode 100644 index 0000000..0c8c8fb --- /dev/null +++ b/ai/aiNavJetting.cc @@ -0,0 +1,485 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/aiConnection.h" +#include "ai/aiNavJetting.h" +#include "ai/graphLOS.h" + +//------------------------------------------------------------------------------------- + +#define GravityConstant 20.0 +#define AmountInFront 0.37 +#define FastVelocity 10.0 +#define PullInPercent 0.63 +#define HitPointThresh 1.2 +#define JumpWaitCount 3 +#define SeekAboveChute 17.0f + +//------------------------------------------------------------------------------------- + +bool AIJetting::init(const Point3F& dest, bool intoMount, NavJetting * jetInfo) +{ + mSeekDest = dest; + mFirstTime = true; + mWillBonk = false; + mLaunchSpeed = 0.1; + mIntoMount = intoMount; + mStatus = AIJetWorking; + mJetInfo = jetInfo; + return true; +} + +//------------------------------------------------------------------------------------- +// Run LOS to see if we can safely jump to our destination. + +static const U32 scBonkMask = InteriorObjectType|StaticShapeObjectType + |StaticObjectType|TerrainObjectType; + +// We don't worry about this if we're jetting up more than 2 meters (in that case +// there shouldn't be a chance of bonking). Also, we don't want to suppress +// take-off jump for long hops (these edges should already be well checked). Mainly +// this check is for those chute connections which had the bonk check suppressed... +bool AIJetting::willBonk(Point3F src, Point3F dst) +{ + if (dst.z - src.z < 2.0) + { + Point2F vec2D(src.x-dst.x, src.y-dst.y); + if (vec2D.lenSquared() < (LiberalBonkXY * LiberalBonkXY)) + { + src.z = dst.z = (getMax(src.z, dst.z) + 3.7); + + Loser los(scBonkMask); + + if (!los.haveLOS(src, dst)) + return false; // Disable temporarily... + } + } + return false; +} + +//------------------------------------------------------------------------------------- +// Called whenever we move the state along. + +void AIJetting::newState(S16 state, S16 counter /*=0*/) +{ + mCounter = counter; + mState = state; +} + +//------------------------------------------------------------------------------------- + +// Get distance from landing "wall" - with (dist < 0) meaning we're that much beyond it +F32 AIJetting::distFromWall(const Point3F& loc) +{ + VectorF vecToWall = (mWallPoint - loc); + vecToWall.z = 0; + return mDot(vecToWall, mWallNormal); +} + +// Assuming we're jetting up - see if it looks like the top of a chute up there. +static bool upChute(const Point3F& from, F32 top, const VectorF& normal, Point3F& soln) +{ + RayInfo coll; + Point3F to(from.x, from.y, top + 20.0f); + + if (gServerContainer.castRay(from, to, InteriorObjectType, &coll)) { + if (mDot(normal, coll.normal) > 0.05) { + coll.normal.z = 0; + coll.normal.normalize(); + if (mDot(normal, coll.normal) > 0.9) { // ~ 25 deg + soln = coll.point; + // return true; + return false; // this has problems with larger chutes... oops. + } + } + } + return false; +} + +bool AIJetting::figureLandingDist(AIConnection* ai, F32& dist) +{ + F32 A = -(GravityConstant * 0.5); + F32 B = ai->mVelocity.z; + F32 C = (ai->mLocation.z - mLandPoint.z); + F32 solutions[2]; + U32 N = mSolveQuadratic(A, B, C, solutions); + + if (N > 0) + { + // use the larger time solution (first will be negative, or coming up on soln) + F32 T = solutions[N-1]; + + // see where this will put us in XY - + dist = T * ai->mVelocity2D.len(); + return true; + } + return false; +} + +// Do first time setup, plus handle other processing on each frame. +bool AIJetting::firstTimeStuff(AIConnection* ai, Player * player) +{ + if (mFirstTime) + { + mFirstTime = false; + mCanInterupt = true; + mWillBonk = false; + mLandPoint = mSeekDest; + mWallNormal = (mSeekDest - ai->mLocation); + mWallNormal.z = 0; + if ((mTotal2D = mWallNormal.len()) < GraphJetFailXY) { + mStatus = AIJetFail; + return false; + } + + F32 zDiff = (mSeekDest.z - ai->mLocation.z); + + mSlope = (zDiff / mTotal2D); + + // Set our desired launch speed based on slope. Zero slope -> full speed, + // Steep slope -> Zero speed. Maps negative slopes to full speed. + mLaunchSpeed = mapValueQuadratic(mSlope, 1.3f, 0.0f, 0.0f, 1.0f); + + // Wall normal points along our path in XY plane. + mWallNormal /= mTotal2D; + + mUpChute = (mJetInfo && mJetInfo->mChuteUp); + + // our dest will be a unit or so beyond for sake of aiming + mWallPoint = mLandPoint; + mSeekDest += (mWallNormal * 1.1); + + // Hop over walls- + if (mJetInfo) + mSeekDest.z += mJetInfo->mHopOver; + + newState(AssureClear); + } + + // Variable that is set when they should look where their heading for right effect- + mShouldAim = false; + + if (mIntoMount && player->isMounted()) { + mStatus = AIJetSuccess; + return false; + } + + return true; +} + +//------------------------------------------------------------------------------------- + +// Local function - call it when you should aim - bool variable is always cleared +// unless this is called. +void AIJetting::setAim(const Point3F& /*always mSeekDest now*/) +{ + mShouldAim = true; +} + +// Public function - find out if should and where at. +bool AIJetting::shouldAimAt(Point3F& atWhere) +{ + if (mShouldAim) + atWhere = mSeekDest; + return mShouldAim; +} + +//------------------------------------------------------------------------------------- +// We may still need to nudge a little bit to assure we have clearance (the generated +// jetting edges need to sometimes hug a bit close (else some makeable connections get +// get thrown out) so this is how we handle the occasional problem). + +static const U32 sLOSMask = InteriorObjectType | StaticShapeObjectType | + StaticObjectType | TerrainObjectType; +static const F32 sClearDistance = 1.4f; + +bool AIJetting::assureClear(AIConnection* ai, Player* ) +{ + // Get loc a little bit above the feet- + Point3F botLoc(ai->mLocation.x, ai->mLocation.y, ai->mLocation.z + 0.2); + + if (botLoc.z < mSeekDest.z - 2.0) + { + // This shouldn't go on very long, just need a nudge if anything... + if (++mCounter < 32) + { + Point3F vec = botLoc; + (vec -= mSeekDest).z = 0.0f; + + // Usual checks just in case.... + F32 len = vec.len(); + if (len > 0.1) + { + Loser loser(sLOSMask); + Point3F clearLoc(botLoc.x, botLoc.y, mSeekDest.z); + + // Compute vector to come in by- + vec *= (sClearDistance / len); + clearLoc -= vec; + + if (!loser.haveLOS(botLoc, clearLoc)) + { + // Seek away, our vec contains which way to go... + ai->setMoveLocation(botLoc += vec); + ai->setMoveSpeed(0.6); + return false; + } + } + } + } + + mWillBonk = willBonk(ai->mLocation, mSeekDest); + + newState(AwaitEnergy); + return false; +} + +//------------------------------------------------------------------------------------- + +// Wait for amount of energy we think we need. +bool AIJetting::awaitEnergy(AIConnection* ai, Player* player) +{ + ai->setMoveLocation(mSeekDest); + if (ai->mVelocity.lenSquared() < 0.2) + { + if (player->getEnergyValue() > 0.99) + { + newState(PrepareToJump); + } + else + { + // Run the energy calculation every frame. We need to use the same methods + // that the graph uses to decide that a given hop is makeable with a certain + // amount of energy ability configuration. + F32 ratings[2]; + JetManager::Ability ability; + +// Tribes player jetting was remove from the player class. +#if 0 + ability.dur = player->getJetAbility(ability.acc, ability.dur, ability.v0); +#else + ability.acc = 0; + ability.dur = 0; + ability.v0 = 0; +#endif + gNavGraph->jetManager().calcJetRatings(ratings, ability); + + F32 jetD = gNavGraph->jetManager().jetDistance(ai->mLocation, mSeekDest); + + if (jetD < ratings[!mWillBonk && player->canJump()]) + newState(PrepareToJump); + } + } + else + ai->setMoveSpeed(0); + return false; +} + +//------------------------------------------------------------------------------------- + +bool AIJetting::prepareToJump(AIConnection* ai, Player* player) +{ + ai->setMoveLocation(mSeekDest); + ai->setMoveSpeed(0); + if (++mCounter >= JumpWaitCount) + { + // Can't check if can jump so often right now - so just do it once... + bool jumpReady = (mCounter==JumpWaitCount) && (player->haveContact() || player->isMounted()); + + // HACK to remedy problem with never finding a contact surface sometimes. + if (!jumpReady && (mCounter > JumpWaitCount * 8)) + jumpReady = (ai->mVelocity.lenSquared() < 0.04); + + if (jumpReady) + { + mJumpPoint = ai->mLocation; + Point3F here = ai->mLocation; + here.z += 100; + ai->setMoveLocation(here); + if (!mWillBonk || player->isMounted()) + ai->pressJump(); + ai->pressJet(); + newState(InTheAir); + mCanInterupt = false; + } + } + return false; +} + +//------------------------------------------------------------------------------------- + +bool AIJetting::inTheAir(AIConnection* ai, Player* player) +{ + bool advanceState = false; + + if (player->haveContact()) { + if (++mCounter >= 2) { + mStatus = AIJetFail; + return true; + } + } + else + mCounter = 0; + + ai->setMoveLocation(mSeekDest); + + if (mUpChute) { + // Basically jet until bonk as long as we're going vertically + if (ai->mVelocity2D.lenSquared() > 0.04 || ai->mVelocity.z < -0.1) + mUpChute = false; + else { + ai->setMoveSpeed(0.0f); + ai->pressJet(); + } + } + + if (!mUpChute) { + if (ai->mLocation.z > mSeekDest.z) { + // Must monitor our Z velocity- + if (ai->mVelocity.z < 0) + ai->setMoveSpeed(0.0f); + else + ai->setMoveSpeed(1.0f); + ai->pressJet(); + } + else { + ai->setMoveSpeed(0.0f); + F32 zSpeed = ai->mVelocity.z; + F32 howHigh = zSpeed * (zSpeed / GravityConstant); + if ((zSpeed < 0.01) || (ai->mLocation.z + howHigh) < (mSeekDest.z + 1.2)) + ai->pressJet(); + } + } + + // get 2D distance to wall: + F32 wallD = distFromWall(ai->mLocation); + + if (wallD < mTotal2D * 0.5) + setAim(mSeekDest); + + if (wallD < 0.1) + advanceState = true; + + // We need a good check for failure. One simple measure for failure might be to + // look at component of lateral velocity along the vector to the destination. + // Also, if we run out of energy and are far from destination... + if (!advanceState) + { + F32 landD; + if (AIJetting::figureLandingDist(ai, landD)) + if( landD > (wallD - AmountInFront)) + advanceState = true; + } + + if (advanceState) + newState(SlowToLand); + + return false; +} + +//------------------------------------------------------------------------------------- + +bool AIJetting::slowToLand(AIConnection* ai, Player* player) +{ + if (player->haveContact()) + { + // First clause meant to handle case where we didn't get off the ledge- + if (ai->mLocation.z - mLandPoint.z > 1.3 && distFromWall(ai->mLocation) > 1.0) { + newState(PrepareToJump, JumpWaitCount-1); + } + //==> We need to use some volume information from the destination. + else { + if (!within(ai->mLocation, mLandPoint, 4.0)) + { + mStatus = AIJetFail; + return true; + } + else { + mCanInterupt = true; + newState(WalkToPoint); + } + } + } + else + { + Point2F vel2(ai->mVelocity.x, ai->mVelocity.y); + F32 speed2 = vel2.len(); + + setAim(mSeekDest); + + // Use velocity checks to finish it up- + F32 zvel = mFabs(ai->mVelocity.z); + if (speed2 < 0.1 && zvel < 0.1) + return true; + + F32 wallD = distFromWall(ai->mLocation), landD; + bool beyond = figureLandingDist(ai, landD) && (landD > wallD- AmountInFront); + bool pullIn = (speed2 * PullInPercent) > wallD && speed2 > 0.7; + + if (beyond && pullIn) // try to slow down + { + ai->setMoveLocation(mJumpPoint); + ai->setMoveSpeed(1.0f); + ai->pressJet(); + } + else + { + ai->setMoveLocation(ai->mLocation); + ai->setMoveSpeed(0.0f); + if(! beyond) + ai->pressJet(); + } + } + return false; +} + +//------------------------------------------------------------------------------------- + +bool AIJetting::walkToPoint(AIConnection* ai, Player*) +{ +#if 0 + mStatus = AIJetSuccess; + ai->setMoveSpeed(0.0f); + return true; +#else + ai->setMoveLocation(mLandPoint); + ai->setMoveTolerance(HitPointThresh * 0.6); + ai->setMoveSpeed(0.3f); + if (within_2D(ai->mLocation, mLandPoint, HitPointThresh) || ++mCounter > 10) + { + // wound up under or over the point... + if (mFabs(ai->mLocation.z - mLandPoint.z) > HitPointThresh) + mStatus = AIJetFail; + else + mStatus = AIJetSuccess; + ai->setMoveSpeed(0.0f); + return true; + } + return false; +#endif +} + +//------------------------------------------------------------------------------------- + +// Returns true when done. +bool AIJetting::process(AIConnection* ai, Player* player) +{ + if (!firstTimeStuff(ai, player)) + return true; + + switch(mState) + { + case AssureClear: return assureClear(ai, player); + case AwaitEnergy: return awaitEnergy(ai, player); + case PrepareToJump: return prepareToJump(ai, player); + case InTheAir: return inTheAir(ai, player); + case SlowToLand: return slowToLand(ai, player); + case WalkToPoint: return walkToPoint(ai, player); + } + return true; +} + diff --git a/ai/aiNavJetting.h b/ai/aiNavJetting.h new file mode 100644 index 0000000..a9d1564 --- /dev/null +++ b/ai/aiNavJetting.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AINAVJETTING_H_ +#define _AINAVJETTING_H_ + +enum AIJetStatus +{ + AIJetInactive, + AIJetWorking, + AIJetFail, + AIJetSuccess +}; + +class AIConnection; + +class AIJetting +{ + protected: + enum JettingStates{ + AssureClear, + AwaitEnergy, + PrepareToJump, + InTheAir, + SlowToLand, + WalkToPoint, + }; + + bool mFirstTime; + bool mUpChute; + bool mIntoMount; + bool mShouldAim; + bool mCanInterupt; + bool mWillBonk; + Point3F mSeekDest; + Point3F mLandPoint; + VectorF mWallNormal; + Point3F mJumpPoint; + Point3F mWallPoint; + Point3F mTopOfChute; + F32 mTotal2D; + F32 mLaunchSpeed; + F32 mSlope; + S32 mState; + S32 mCounter; + AIJetStatus mStatus; + NavJetting * mJetInfo; + + void newState(S16 state, S16 counter = 0); + F32 distFromWall(const Point3F& loc); + void setAim(const Point3F& at); + bool willBonk(Point3F src, Point3F dst); + bool figureLandingDist(AIConnection* ai, F32& dist); + bool firstTimeStuff(AIConnection* ai, Player* player); + + // States: + bool assureClear(AIConnection* ai, Player* player); + bool awaitEnergy(AIConnection* ai, Player* player); + bool prepareToJump(AIConnection* ai, Player* player); + bool inTheAir(AIConnection* ai, Player* player); + bool slowToLand(AIConnection* ai, Player* player); + bool walkToPoint(AIConnection* ai, Player* player); + + public: + AIJetting() {reset();} + AIJetStatus status() {return mStatus;} + void reset() {mStatus = AIJetInactive;} + + bool init(const Point3F& dest, bool intoMount = false, NavJetting * inf = 0); + bool process(AIConnection* ai, Player* player); + bool shouldAimAt(Point3F& atWhere); + bool badTimeToSearch() const {return true;} // {return !mCanInterupt;} +}; + +#endif diff --git a/ai/aiNavStep.cc b/ai/aiNavStep.cc new file mode 100644 index 0000000..ed61dfc --- /dev/null +++ b/ai/aiNavStep.cc @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/aiConnection.h" +#include "ai/aiNavStep.h" + + +AIStepJet::AIStepJet(const Point3F& dest) +{ + mJetting.init(dest); +} + +void AIStepJet::process(AIConnection* ai, Player* player) +{ + Parent::process(ai, player); + if (mStatus != InProgress) + return; + + ai->setMoveMode(AIConnection::ModeWalk); + + if( mJetting.process(ai, player) ) + mStatus = (mJetting.status() == AIJetSuccess ? Finished : Failed); +} + diff --git a/ai/aiNavStep.h b/ai/aiNavStep.h new file mode 100644 index 0000000..6e71b67 --- /dev/null +++ b/ai/aiNavStep.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AINAVSTEP_H_ +#define _AINAVSTEP_H_ + +#ifndef _AISTEP_H_ +#include "ai/aiStep.h" +#endif +#ifndef _GRAPH_H_ +#include "ai/graph.h" +#endif +#ifndef _AINAVJETTING_H_ +#include "ai/aiNavJetting.h" +#endif + +class AIStepJet : public AIStep +{ + protected: + typedef AIStep Parent; + + AIJetting mJetting; + public: + AIStepJet(const Point3F& dest); + void process(AIConnection* ai, Player* player); +}; + +#endif diff --git a/ai/aiObjective.cc b/ai/aiObjective.cc new file mode 100644 index 0000000..8aaf90c --- /dev/null +++ b/ai/aiObjective.cc @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "console/consoleInternal.h" +#include "Core/bitStream.h" +#include "ai/aiObjective.h" + +IMPLEMENT_CO_NETOBJECT_V1(AIObjective); + +Sphere AIObjective::smSphere(Sphere::Octahedron); + +//---------------------------------------------------------------------------// +//AIObjective Methods + +AIObjective::AIObjective() +{ + mTypeMask = StaticObjectType; + mDescription = StringTable->insert(""); + mMode = StringTable->insert(""); + mTargetClient = StringTable->insert(""); + mTargetObject = StringTable->insert(""); + mTargetClientId = -1; + mTargetObjectId = -1; + mLocation.set(0, 0, 0); + + mWeightLevel1 = 0; + mWeightLevel2 = 0; + mWeightLevel3 = 0; + mWeightLevel4 = 0; + mOffense = false; + mDefense = false; + + mRequiredEquipment = StringTable->insert(""); + mDesiredEquipment = StringTable->insert(""); + mBuyEquipmentSets = StringTable->insert(""); + mCannedChat = StringTable->insert(""); + + mIssuedByHuman = false; + mIssuedByClientId = -1; + mForceClientId = -1; + + mLocked = false; + + //this will allow us to access the persist fields from script + setModStaticFields(true); +} + +//---------------------------------------------------------------------------- + +bool AIObjective::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + //set the task namespace + const char *name = getName(); + if(name && name[0] && getClassRep()) + { + Namespace *parent = getClassRep()->getNameSpace(); + Con::linkNamespaces(parent->mName, name); + mNameSpace = Con::lookupNamespace(name); + } + + if(!isClientObject()) + setMaskBits(UpdateSphereMask); + return(true); +} + +//---------------------------------------------------------------------------- + +void AIObjective::inspectPostApply() +{ + Parent::inspectPostApply(); + setMaskBits(UpdateSphereMask); +} + +void AIObjective::consoleInit() +{ + Parent::consoleInit(); +} + +//---------------------------------------------------------------------------- + +void AIObjective::initPersistFields() +{ + Parent::initPersistFields(); + addField("description", TypeString, Offset(mDescription, AIObjective)); + addField("mode", TypeString, Offset(mMode, AIObjective)); + addField("targetClient", TypeString, Offset(mTargetClient, AIObjective)); + addField("targetObject", TypeString, Offset(mTargetObject, AIObjective)); + addField("targetClientId", TypeS32, Offset(mTargetClientId, AIObjective)); + addField("targetObjectId", TypeS32, Offset(mTargetObjectId, AIObjective)); + addField("location", TypePoint3F, Offset(mLocation, AIObjective)); + + addField("weightLevel1", TypeS32, Offset(mWeightLevel1, AIObjective)); + addField("weightLevel2", TypeS32, Offset(mWeightLevel2, AIObjective)); + addField("weightLevel3", TypeS32, Offset(mWeightLevel3, AIObjective)); + addField("weightLevel4", TypeS32, Offset(mWeightLevel4, AIObjective)); + addField("offense", TypeBool, Offset(mOffense, AIObjective)); + addField("defense", TypeBool, Offset(mDefense, AIObjective)); + + addField("equipment", TypeString, Offset(mRequiredEquipment, AIObjective)); + addField("desiredEquipment", TypeString, Offset(mDesiredEquipment, AIObjective)); + addField("buyEquipmentSet", TypeString, Offset(mBuyEquipmentSets, AIObjective)); + + addField("chat", TypeString, Offset(mCannedChat, AIObjective)); + + addField("issuedByHuman", TypeBool, Offset(mIssuedByHuman, AIObjective)); + addField("issuedByClientId", TypeS32, Offset(mIssuedByClientId, AIObjective)); + addField("forceClientId", TypeS32, Offset(mForceClientId, AIObjective)); + + addField("locked", TypeBool, Offset(mLocked, AIObjective)); + +} + +//---------------------------------------------------------------------------// + +U32 AIObjective::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // + if(stream->writeFlag(mask & UpdateSphereMask)) + { + } + return(retMask); +} + +void AIObjective::unpackUpdate(NetConnection * con, BitStream * stream) +{ + Parent::unpackUpdate(con, stream); + if(stream->readFlag()) + { + } +} + +//---------------------------------------------------------------------------// +//AIObjectiveQ Methods + +IMPLEMENT_CONOBJECT(AIObjectiveQ); + +static void cAIQSortByWeight(SimObject *obj, S32, const char **) +{ + AIObjectiveQ *objectiveQ = static_cast(obj); + objectiveQ->sortByWeight(); +} + +void AIObjectiveQ::consoleInit() +{ + Parent::consoleInit(); + + Con::addCommand("AIObjectiveQ", "sortByWeight", cAIQSortByWeight, "aiQ.sortByWeight()", 2, 2); +} + +void AIObjectiveQ::addObject(SimObject *obj) +{ + //only aioObjectives can be part of an objective Queue + AIObjective *objective = dynamic_cast(obj); + if (! objective) + { + Con::printf("Error AIObjective::addObject() - attempting to add something other than an AIObjective!"); + //print the error, but don't return - in case I need to revert the ai scripts... + //return; + } + Parent::addObject(obj); +} + +S32 QSORT_CALLBACK AIObjectiveQ::compareWeight(const void* a,const void* b) +{ + return (*reinterpret_cast(b))->getSortWeight() - + (*reinterpret_cast(a))->getSortWeight(); +} + +void AIObjectiveQ::sortByWeight() +{ + dQsort(objectList.address(), objectList.size(), sizeof(SimObject *), compareWeight); +} diff --git a/ai/aiObjective.h b/ai/aiObjective.h new file mode 100644 index 0000000..1453219 --- /dev/null +++ b/ai/aiObjective.h @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AIOBJECTIVE_H_ +#define _AIOBJECTIVE_H_ + +#ifndef _BITSTREAM_H_ +#include "core/bitStream.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif +#ifndef _MATHIO_H_ +#include "math/mathIO.h" +#endif +#ifndef _SPHERE_H_ +#include "game/sphere.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _MISSIONMARKER_H_ +#include "game/missionMarker.h" +#endif + + +class AIObjective : public MissionMarker +{ + private: + typedef MissionMarker Parent; + static Sphere smSphere; + + StringTableEntry mDescription; + StringTableEntry mMode; + StringTableEntry mTargetClient; + StringTableEntry mTargetObject; + S32 mTargetClientId; + S32 mTargetObjectId; + Point3F mLocation; + + S32 mWeightLevel1; + S32 mWeightLevel2; + S32 mWeightLevel3; + S32 mWeightLevel4; + bool mOffense; + bool mDefense; + + StringTableEntry mRequiredEquipment; + StringTableEntry mDesiredEquipment; + StringTableEntry mBuyEquipmentSets; + + StringTableEntry mCannedChat; + bool mLocked; + + bool mIssuedByHuman; + S32 mIssuedByClientId; + S32 mForceClientId; + + public: + AIObjective(); + S32 getSortWeight() const { return mWeightLevel1; } + + static void initPersistFields(); + static void consoleInit(); + + // SimObject + bool onAdd(); + void inspectPostApply(); + + // NetObject + enum SpawnSphereMasks { + UpdateSphereMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + // NetObject + U32 packUpdate(NetConnection *, U32, BitStream *); + void unpackUpdate(NetConnection *, BitStream *); + + DECLARE_CONOBJECT(AIObjective); +}; + + +class AIObjectiveQ : public SimSet +{ + private: + typedef SimSet Parent; + + public: + DECLARE_CONOBJECT(AIObjectiveQ); + static void consoleInit(); + void addObject(SimObject *obj); + + static S32 QSORT_CALLBACK compareWeight(const void* a,const void* b); + void sortByWeight(); +}; + +#endif diff --git a/ai/aiPlayer.cc b/ai/aiPlayer.cc new file mode 100644 index 0000000..0e14f6b --- /dev/null +++ b/ai/aiPlayer.cc @@ -0,0 +1,446 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- +#include "ai/aiPlayer.h" +#include "core/realcomp.h" +#include "math/mMatrix.h" +#include "game/moveManager.h" + +/** + * Constructor + */ +AIPlayer::AIPlayer() { + mMoveMode = ModeStop; + mMoveDestination.set( 0.0f, 0.0f, 0.0f ); + mAimLocation.set( 0.0f, 0.0f, 0.0f ); + mMoveSpeed = 0.0f; + mMoveTolerance = 0.25f; + + // Clear the triggers + for( int i = 0; i < MaxTriggerKeys; i++ ) + mTriggers[i] = false; + + mAimToDestination = true; + + mRotation.set( 0.0f, 0.0f, 0.0f ); + mLocation.set( 0.0f, 0.0f, 0.0f ); + mPlayer = NULL; +} + +/** + * Sets the object the bot is targeting + * + * @param targetObject The object to target + */ +void AIPlayer::setTargetObject( ShapeBase *targetObject ) { + + if ( !targetObject || !bool( mTargetObject ) || targetObject->getId() != mTargetObject->getId() ) + mTargetInSight = false; + + mTargetObject = targetObject; +} + +/** + * Returns the target object + * + * @return Object bot is targeting + */ +S32 AIPlayer::getTargetObject() { + 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 AIPlayer::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 AIPlayer::setMoveMode( S32 mode ) { + if( mode < 0 || mode >= ModeCount ) + mode = 0; + + mMoveMode = mode; +} + +/** + * Sets how far away from the move location is considered + * "on target" + * + * @param tolerance Movement tolerance for error + */ +void AIPlayer::setMoveTolerance( 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 ) { + // 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; + + // TEST CODE + RayInfo dummy; + + if( mPlayer ) { + if( !mPlayer->getContainer()->castRay( mLocation, location, InteriorObjectType | + StaticShapeObjectType | StaticObjectType | + TerrainObjectType, &dummy ) ) + Con::printf( "I can see the target." ); + else + Con::printf( "I can't see my target!! AAAAHHHHH!" ); + } +} + +/** + * Sets the location for the bot to aim at + * + * @param location Point to aim at + */ +void AIPlayer::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 AIPlayer::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 + */ +void AIPlayer::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; + + // If system is disabled, don't process + if ( !gAISystemEnabled ) + return; + + // 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, zDiff; + + + + // Check if we are dead, if so, throw the script callback -- PUT THIS IN AIPLAYER + //if( !dStricmp( mPlayer->getStateName(), "dead" ) ) { + // TO-DO: Script callback + // return; + //} + + F32 moveSpeed = mMoveSpeed; + + switch( mMoveMode ) { + + case ModeStop: + return; // Stop means no action, peroid + break; + + case ModeIdle: + // TO-DO: Insert callback for scripted idle stuff + break; + + case ModeWalk: + moveSpeed /= 2.0f; // Walking speed is half running speed + // Fall through to run + case ModeRun: + + // Get my location + MatrixF const& myTransform = mPlayer->getTransform(); + myTransform.getColumn( 3, &mLocation ); + + // Set rotation variables + mRotation = mPlayer->getRotation(); + Point3F headRotation = mPlayer->getHeadRotation(); + curYaw = mRotation.z; + curPitch = headRotation.x; + xDiff = mAimLocation.x - mLocation.x; + yDiff = mAimLocation.y - mLocation.y; + + // first do Yaw + if( !isZero( xDiff ) || !isZero( yDiff ) ) { + // use the cur yaw between -Pi and Pi + while( curYaw > M_2PI ) + curYaw -= M_2PI; + while( curYaw < -M_2PI ) + curYaw += M_2PI; + + // find the new yaw + newYaw = mAtan( xDiff, yDiff ); + + // find the yaw diff + F32 yawDiff = newYaw - curYaw; + + // make it between 0 and 2PI + if( yawDiff < 0.0f ) + yawDiff += M_2PI; + else if( yawDiff >= M_2PI ) + yawDiff -= M_2PI; + + // now make sure we take the short way around the circle + if( yawDiff > M_PI ) + yawDiff -= M_2PI; + else if( yawDiff < -M_PI ) + yawDiff += M_2PI; + + mMove.yaw = yawDiff; + + // set up the movement matrix + moveMatrix.set( EulerF( 0, 0, newYaw ) ); + } + else + moveMatrix.set( EulerF( 0, 0, curYaw ) ); + + // next do pitch + F32 horzDist = Point2F( mAimLocation.x, mAimLocation.y ).len(); + + if( !isZero( horzDist ) ) { + //we shoot from the gun, not the eye... + F32 vertDist = mAimLocation.z; + + newPitch = mAtan( horzDist, vertDist ) - ( M_PI / 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 ) ) && ( !isZero( mMoveSpeed ) ) ) + { + if( isZero( xDiff ) ) + mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed ); + else if( isZero( 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 ), &newMove ); + + //and sub the result back in the mMove structure + mMove.x = newMove.x; + mMove.y = newMove.y; + } + else { + // Ok, we are close enough + setMoveMode( ModeStop ); + Con::printf( "I'm close enough to my destination to stop." ); + } + break; + } + + // Copy over the trigger status + for( int i = 0; i < MaxTriggerKeys; i++ ) { + mMove.trigger[i] = mTriggers[i]; + mTriggers[i] = false; + } +} + +/** + * This method is just called to stop the bots from running amuck + * while the mission cycles + */ +void AIPlayer::missionCycleCleanup() { + setMoveMode( ModeStop ); +} + +/** + * Sets the move speed for an AI object, remember, walk is 1/2 run + */ +static void cAISetMoveSpeed( SimObject *obj, S32, const char** argv ) { + AIPlayer *ai = static_cast( obj ); + ai->setMoveSpeed( dAtoi( argv[2] ) ); +} + +/** + * Stops all AI movement, halt! + */ +static void cAIStop( SimObject *obj, S32, const char** ) { + AIPlayer *ai = static_cast( obj ); + ai->setMoveMode( AIPlayer::ModeStop ); +} + +/** + * Tells the AI to aim at the location provided + */ +static void cAIAimAtLocation( SimObject *obj, S32 argc, const char** argv ) { + AIPlayer *ai = static_cast( obj ); + Point3F v( 0.0f,0.0f,0.0f ); + dSscanf( argv[2], "%f %f %f", &v.x, &v.y, &v.z ); + + ai->setAimLocation( v ); +} + +/** + * Returns the point the AI is aiming at + */ +static const char *cAIGetAimLocation( SimObject *obj, S32, const char** ) { + AIPlayer *ai = static_cast( obj ); + Point3F aimPoint = ai->getAimLocation(); + + char* returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%f %f %f", aimPoint.x, aimPoint.y, aimPoint.z ); + + return returnBuffer; +} + +/** + * Sets the bots target object + */ +static void cAISetTargetObject( SimObject *obj, S32 argc, const char **argv ) { + AIPlayer *ai = static_cast( obj ); + + // 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 + */ +static const char *cAIGetTargetObject( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + S32 targetId = ai->getTargetObject(); + char* returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%d", targetId ); + + return returnBuffer; +} + +/** + * Checks to see if the target is in sight + */ +static bool cAITargetInSight( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + return ai->targetInSight(); +} + +/** + * Tells the bot the mission is cycling + */ +static void cAIMissionCycleCleanup( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + ai->missionCycleCleanup(); +} + +/** + * Tells the AI to move forward 100 units...TEST FXN + */ +static void cAIMoveForward( SimObject *obj, S32, const char ** ) { + + AIPlayer *ai = static_cast( obj ); + ShapeBase *player = ai->getControlObject(); + Point3F location; + MatrixF const &myTransform = player->getTransform(); + myTransform.getColumn( 3, &location ); + + location.y += 100.0f; + + ai->setMoveDestination( location ); +} // *** /TEST FXN + +/** + * Sets the AI to walk mode + */ +static void cAIWalk( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + ai->setMoveMode( AIPlayer::ModeWalk ); +} + +/** + * Sets the AI to run mode + */ +static void cAIRun( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + ai->setMoveMode( AIPlayer::ModeRun ); +} + + +/** + * Console init function + */ +void AIPlayer::consoleInit() { + // TEST FXN + Con::addCommand( "AIPlayer", "moveForward", cAIMoveForward, "ai.moveForward()", 2, 2 ); + + Con::addCommand( "AIPlayer", "walk", cAIWalk, "ai.walk()", 2, 2 ); + Con::addCommand( "AIPlayer", "run", cAIRun, "ai.run()", 2, 2 ); + Con::addCommand( "AIPlayer", "stop", cAIStop, "ai.stop()", 2, 2 ); + Con::addCommand( "AIPlayer", "setMoveSpeed", cAISetMoveSpeed, "ai.setMoveSpeed( float )", 3, 3 ); + + Con::addCommand( "AIPlayer", "setTargetObject", cAISetTargetObject, "ai.setTargetObject( object )", 3, 4 ); + Con::addCommand( "AIPlayer", "getTargetObject", cAIGetTargetObject, "ai.getTargetObject()", 2, 2 ); + Con::addCommand( "AIPlayer", "targetInSight", cAITargetInSight, "ai.targetInSight()", 2, 2 ); + Con::addCommand( "AIPlayer", "aimAt", cAIAimAtLocation, "ai.aimAt( point )", 3, 3 ); + Con::addCommand( "AIPlayer", "getAimLocation", cAIGetAimLocation, "ai.getAimLocation()", 2, 2 ); +} \ No newline at end of file diff --git a/ai/aiPlayer.h b/ai/aiPlayer.h new file mode 100644 index 0000000..ce91f03 --- /dev/null +++ b/ai/aiPlayer.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AIPLAYER_H_ +#define _AIPLAYER_H_ + +#include "ai/aiConnection.h" +#include "game/player.h" + +class AIPlayer : 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]; + + bool mTargetInSight; + Player *mPlayer; + + Point3F mMoveDestination; + Point3F mLocation; + Point3F mRotation; // Euler really + + bool mAimToDestination; // Why is this in here? + Point3F mAimLocation; // Because objects would have facing as well. + + + SimObjectPtr mTargetObject; + public: + + DECLARE_CONOBJECT( AIPlayer ); + + enum + { + ModeStop = 0, + ModeWalk, // Walk is runSpeed / 2 + ModeRun, + ModeIdle, + ModeCount // This is in there as a max index value + }; + + AIPlayer(); + + void getMoveList( Move **movePtr,U32 *numMoves ); + static void consoleInit(); + + // ---Targeting and aiming sets/gets + void setTargetObject( ShapeBase *targetObject ); + S32 getTargetObject(); + bool targetInSight() { return mTargetInSight; } + + // ---Movement sets/gets + void setMoveSpeed( F32 speed ); + F32 getMoveSpeed() { return mMoveSpeed; } + + void setMoveMode( S32 mode ); + S32 getMoveMode() { return mMoveMode; } + + void setMoveTolerance( F32 tolerance ); + F32 getMoveTolerance() { return mMoveTolerance; } + + void setMoveDestination( const Point3F &location ); + Point3F getMoveDestination() { return mMoveDestination; } + + // ---Facing(Aiming) sets/gets + void setAimLocation( const Point3F &location ); + Point3F getAimLocation() { return mAimLocation; } + void clearAim(); + + // ---Other + void missionCycleCleanup(); + + // ---Callbacks +}; + +#endif \ No newline at end of file diff --git a/ai/aiStep.cc b/ai/aiStep.cc new file mode 100644 index 0000000..9d6077d --- /dev/null +++ b/ai/aiStep.cc @@ -0,0 +1,805 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "core/realComp.h" +#include "console/simBase.h" +#include "ai/aiConnection.h" +#include "game/player.h" +#include "game/projectile.h" +#include "math/mRandom.h" +#include "ai/aiStep.h" + +AIStep::AIStep() +{ + mStatus = InProgress; +} + +void AIStep::process(AIConnection *client, Player *player) +{ + //only process if we're still "in progress" + if (mStatus != InProgress) + return; + + //make sure we have a client and a player + if (!client || !player) + mStatus = Failed; + + //make sure we're not dead + if (! dStricmp(player->getStateName(), "dead")) + mStatus = Failed; +} + +//----------------------------------------------------------------------------- +//ESCORT step +AIStepEscort::AIStepEscort(GameConnection *clientToEscort) +{ + mInitialized = false; + mClientToEscort = clientToEscort; + mResetDestinationCounter = 0; +} + +void AIStepEscort::process(AIConnection *client, Player *player) +{ + Parent::process(client, player); + if (mStatus != InProgress) + return; + + //make sure we have a valid target to escort + Player *targetPlayer = NULL; + if ((! bool(mClientToEscort)) || (! mClientToEscort->getControlObject())) + mStatus = Failed; + else + targetPlayer = dynamic_cast(mClientToEscort->getControlObject()); + if (! targetPlayer) + { + mStatus = Failed; + return; + } + + //set the energy levels + if (! mInitialized) + { + mInitialized = true; + if (targetPlayer->isMounted()) + client->setMoveMode(AIConnection::ModeMountVehicle); + else + client->setMoveMode(AIConnection::ModeExpress); + client->setMoveTolerance(4.0f); + client->setEnergyLevels(0.05f, 0.15f); + } + + //get the target's current location (global coords) + MatrixF const& targetTransform = targetPlayer->getTransform(); + Point3F targetLocation; + targetTransform.getColumn(3, &targetLocation); + + //go back to using the euclidean distance + F32 distToTarget = getMax(client->getPathDistRemaining(20), (client->mLocation - targetLocation).len()); + + //cut down on the number of path searches + if (--mResetDestinationCounter <= 0) + { + mResetDestinationCounter = 5; + client->setMoveDestination(targetLocation); + } + + //get the current time + S32 curTime = Sim::getCurrentTime(); + + //see if we're close enough to the target yet + if (distToTarget < 10.0f) + { + mProximityBuffer = true; + if (client->getMoveMode() != AIConnection::ModeStop) + { + client->setMoveMode(AIConnection::ModeStop); + mStoppedTime = curTime; + mIdleStarted = false; + } + } + else if (distToTarget < 16.0f) + { + if (mProximityBuffer) + { + if (client->getMoveMode() != AIConnection::ModeStop) + { + client->setMoveMode(AIConnection::ModeStop); + mStoppedTime = curTime; + mIdleStarted = false; + } + } + else + { + //make sure we aim at the player momentarily... + if (client->getMoveMode() == AIConnection::ModeStop) + client->setScriptAimLocation(Point3F(targetLocation.x, targetLocation.y, targetLocation.z + 1.6f), 500); + + if (targetPlayer->isMounted()) + client->setMoveMode(AIConnection::ModeMountVehicle); + else + client->setMoveMode(AIConnection::ModeExpress); + } + } + else + { + //make sure we aim at the player momentarily... + if (client->getMoveMode() == AIConnection::ModeStop) + client->setScriptAimLocation(Point3F(targetLocation.x, targetLocation.y, targetLocation.z + 1.6f), 500); + mProximityBuffer = false; + if (targetPlayer->isMounted()) + client->setMoveMode(AIConnection::ModeMountVehicle); + else + client->setMoveMode(AIConnection::ModeExpress); + } + + //now see if it's time to "idle" + if (client->getMoveMode() == AIConnection::ModeStop && curTime - mStoppedTime > 5000) + { + //init the idle vars + if (!mIdleStarted) + { + mIdleStarted = true; + mChokePoints.clear(); + NavigationGraph::getChokePoints(client->mLocation, mChokePoints, 10, 65); + mIdleNextTime = 0; + } + + //see if it's time to look around some more + if (curTime > mIdleNextTime) + { + mIdleNextTime = curTime + 2000 + gRandGen.randF() * 3000; + + //see if we should look or play an animation + if (gRandGen.randF() < 0.06f) + { + player->setActionThread("pda", false, true, false); + } + else + { + Point3F lookLocation; + if (mChokePoints.size() == 0) + { + if (gRandGen.randF() < 0.3f) + lookLocation = targetLocation; + else + lookLocation = client->mLocation; + } + else + { + S32 index = S32(gRandGen.randF() * (mChokePoints.size() + 0.9)); + if (index == mChokePoints.size()) + lookLocation = targetLocation; + else + lookLocation = mChokePoints[index]; + } + + //add some noise to the lookLocation + Point3F tempVector = lookLocation - client->mLocation; + tempVector.z = 0; + if (tempVector.len() < 0.1f) + tempVector.set(1, 0, 0); + tempVector.normalize(); + tempVector *= 10.0f; + F32 noise = 2.0f; + tempVector.x += -noise + gRandGen.randF() * 2 * noise; + tempVector.y += -noise + gRandGen.randF() * 2 * noise; + tempVector.z = 1.5f + gRandGen.randF() * 2 * noise; + Point3F newAimLocation = client->mLocation + tempVector; + + //now aim at that point + if (!client->scriptIsAiming()) + client->setScriptAimLocation(newAimLocation, 3000); + } + } + } +} + +//----------------------------------------------------------------------------- +//ENGAGE step +AIStepEngage::AIStepEngage(GameConnection *target) +{ + mInitialized = false; + mTarget = target; + mStraifeCounter = 0; + mPauseCounter = 0; + mPausing = false; + mCheckLOSCounter = 0; + mClearLOSToTarget = false; + + mSearching = false; + mSearchInitialized = false; + + mUsingEnergyWeapon = false; + mEnergyWeaponRecharge = 0.0f; +} + +Point3F AIStepEngage::findStraifeLocation(AIConnection *client) +{ + Point3F newLocation; + Point3F tempV = client->mLocation - client->mTargLocation; + Point3F newLocationVector; + if (tempV.len() < 0.001f) + tempV.set(0, 1, 0); + tempV.normalize(); + mCross(tempV, Point3F(0, 0, 1), &newLocationVector); + if (newLocationVector.len() < 0.001f) + newLocationVector.set(0, 1, 0); + newLocationVector.normalize(); + + F32 newLocationLength = newLocationVector.len(); + if (newLocationLength > 0.9f && newLocationLength < 1.1f) + { + //if we're not moving, choose randomly whether we straife right or left + if (client->mVelocity2D.len() < 1.0f) + newLocation = client->mTargLocation + (newLocationVector * client->mEngageMinDistance); + else + { + //otherwise, choose the point closest to the direction we're already moving... + F32 myAngle = AIConnection::get2DAngle(client->mLocation + client->mVelocity2D, client->mLocation); + F32 tempAngle1 = AIConnection::get2DAngle(client->mLocation, client->mTargLocation + newLocationVector); + F32 tempAngle2 = AIConnection::get2DAngle(client->mLocation, client->mTargLocation - newLocationVector); + + if (mFabs(tempAngle1 - myAngle) < mFabs(tempAngle2 - myAngle)) + newLocation = client->mTargLocation + (newLocationVector * client->mEngageMinDistance); + else + newLocation = client->mTargLocation - (newLocationVector * client->mEngageMinDistance); + } + } + else + { + Con::printf("DEBUG AIStepEngage::findStraifeLocation() - player and target are on top of each other..."); + newLocation = client->mTargLocation; + } + + //return the result + return newLocation; +} + +void AIStepEngage::process(AIConnection *client, Player *player) +{ + Parent::process(client, player); + if (mStatus != InProgress) + return; + + //make sure the target is set + if (! mInitialized) + { + mInitialized = true; + + //set the target and the energy levels + client->setMoveTolerance(2.0f); + client->setEngageTarget(mTarget); + client->setEnergyLevels(0.3f, 0.2f); + return; + } + + //make sure we still have a target + if (! client->mTargetPlayer) + { + mStatus = Finished; + client->setMoveMode(AIConnection::ModeStop); + return; + } + + //set the outdoor bools + bool targIsOutdoors = (client->getOutdoorRadius(client->mTargLocation) > 0); + bool playerIsOutdoors = (client->getOutdoorRadius(client->mLocation) > 0); + + //set the LOS variables + S32 losTime; + Point3F losLocation; + bool hasLOS = client->hasLOSToClient(client->getEngageTarget(), losTime, losLocation); + + //only gain height or straife if the target is outdoors + if (targIsOutdoors) + { + //decriment the straife counter + bool timeToStraife = false; + mStraifeCounter--; + + //see if we should try to gain height + S32 mode = client->getMoveMode(); + if (mode == AIConnection::ModeGainHeight) + { + //see if we need to cancel the gaining of height + if (client->mEnergy < 0.2f || client->mLocation.z - client->mTargLocation.z > 15.0f || + client->mEnergy < client->mWeaponEnergy - client->mEnergyReserve) + { + timeToStraife = true; + } + } + else + { + //see if we should try to gain height - only if we're outdoors + if (playerIsOutdoors && mPauseCounter <= 0 && (client->mEnergy > 0.5f || client->mEnergy - client->mTargEnergy > 0.3f) && + client->mEnergy > client->mWeaponEnergy + client->mEnergyFloat && + client->mDistToTarg2D < 45.0f && client->mVelocity2D.len() > 5.0f && + client->mLocation.z - client->mTargLocation.z < 15.0f && client->mSkillLevel >= 0.3f) + client->setMoveMode(AIConnection::ModeGainHeight); + + //see if we need to straife + else + { + //see if we're nearish our destination + Point3F straifeVector = client->mLocation - mStraifeLocation; + straifeVector.z = 0; + if (straifeVector.len() < getMin(8, client->mEngageMinDistance)) + { + client->setMoveMode(AIConnection::ModeStop); + if (!mPausing) + { + //if we have had LOS to the client within the last 5 seconds at this moment, then pause + //no need to pause if we're behind a hill + if (hasLOS || losTime < 5000) + { + F32 skillAdjust = (1.0f - client->mSkillLevel) * + (1.0f - client->mSkillLevel) * + (1.0f - client->mSkillLevel); + mPauseCounter = S32(gRandGen.randF() * 400.0f * skillAdjust); + mPausing = true; + } + } + mPauseCounter--; + } + + if (mStraifeCounter <= 0 || mPauseCounter <= 0) + timeToStraife = true; + } + } + + //see if we need to straife + if (timeToStraife) + { + F32 skillAdjust = (1.0f - client->mSkillLevel) * + (1.0f - client->mSkillLevel); + mStraifeCounter = 70 + S32(400.0f * skillAdjust); + mPauseCounter = 32767; + mPausing = false; + mStraifeLocation = findStraifeLocation(client); + client->setMoveMode(AIConnection::ModeExpress); + client->setMoveDestination(mStraifeLocation); + } + } + else + { + //if we've never even detected the client, technically, this is a script error - + //bots shouldn't be fighting anyone they've never seen. However... + if (losLocation == Point3F(0, 0, 0)) + losLocation = client->mTargLocation; + + //if the targ is Indoors, and we're outdoors, move to the client's position so that + //it's not so rediculously easy to lose the bots just by running into a doorway.. + if (playerIsOutdoors) + losLocation = client->mTargLocation; + + //see if we have LOS to the target (needed since we use a slightly different point than the LOS detection table + if (--mCheckLOSCounter <= 0) + { + player->disableCollision(); + mCheckLOSCounter = 10; + mClearLOSToTarget = false; + RayInfo rayInfo; + Point3F startPt = player->getBoxCenter(); + Point3F endPt = client->mTargLocation; + U32 mask = TerrainObjectType | InteriorObjectType | WaterObjectType | ForceFieldObjectType; + mClearLOSToTarget = (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo)); + player->enableCollision(); + } + + //see if we're already at our losLocation, is it time to start idling (searching)? + if (!mClearLOSToTarget && !mSearching && ((client->mLocation - losLocation).len() < 3.0f) && (client->getMoveMode() == AIConnection::ModeStop)) + { + mSearching = true; + mSearchInitialized = false; + } + else if (mSearching) + { + //see if we should abort the search (we found our target) + if (mClearLOSToTarget) + { + mSearching = false; + } + + //else initialize the search + else if (! mSearchInitialized) + { + mSearchInitialized = true; + mChokeLocation = losLocation; + mChokeIndex = -1; + NavigationGraph::getChokePoints(losLocation, mChokePoints, 10, 65); + mSearchTimer = Sim::getCurrentTime() + 3000; + } + + else + { + //see if we're at the choke location, and we've been there for a few seconds... + if (((client->mLocation - mChokeLocation).len() < 3.0f) && (client->getMoveMode() == AIConnection::ModeStop) && (Sim::getCurrentTime() > mSearchTimer)) + { + //remove the current choke index from the array + if (mChokeIndex >= 0) + mChokePoints.erase(mChokeIndex); + + //if we've run out of choke points to check, we've lost our target... + if (mChokePoints.size() <= 0) + { + mStatus = Failed; + return; + } + else + { + mChokeIndex = S32(gRandGen.randF() * (mChokePoints.size() - 0.1f)); + mChokeLocation = mChokePoints[mChokeIndex]; + mSearchTimer = Sim::getCurrentTime() + (gRandGen.randF() * 1000) + 2000; + client->setMoveMode(AIConnection::ModeExpress); + } + } + + //move to the choke point + client->setMoveDestination(mChokeLocation); + } + } + + //else we either have LOS, or we're moving to the target's last known location... + else + { + client->setMoveDestination(losLocation); + + //if we can see the target, and we're close enough, don't get any closer + if (mClearLOSToTarget && client->mDistToTarg2D < 15.0f) + client->setMoveMode(AIConnection::ModeStop); + else + client->setMoveMode(AIConnection::ModeExpress); + } + } +} + +//----------------------------------------------------------------------------- +//RangeObject step +AIStepRangeObject::AIStepRangeObject(GameBase *targetObject, const char *projectile, F32 *minDist, F32 *maxDist, Point3F *fromLocation) +{ + mInitialized = false; + mTargetObject = targetObject; + mCheckLOSCounter = 0; + + //set the range distances + F32 minDistance = 0; + F32 maxDistance = 1000; + if (*minDist) + minDistance = *minDist; + if (*maxDist) + maxDistance = *maxDist; + + mMinDistance = getMax(0.0f, getMin(minDistance, maxDistance)); + mMaxDistance = getMax(1.0f, getMax(minDistance, maxDistance)); + mFromLocation.set(-1, -1, -1); + if (fromLocation) + mFromLocation = *fromLocation; + mPrevLocation.set(0, 0, 0); + + //set the mask + mLOSMask = TerrainObjectType | InteriorObjectType | PlayerObjectType | ForceFieldObjectType; + if (bool(mTargetObject)) + { + Player *plyr = dynamic_cast(targetObject); + if (bool(plyr)) + mLOSMask = TerrainObjectType | InteriorObjectType | StaticShapeObjectType | ForceFieldObjectType; + } + + if ((! projectile) || (! projectile[0]) || (! dStricmp(projectile, "NoAmmo"))) + mProjectile = NULL; + else + { + if (! Sim::findObject(projectile, mProjectile)) + { + Con::printf("AIStepRangeObject() failed - unable to find projectile datablock: %s", projectile); + mProjectile = NULL; + } + } + + if (! bool(mTargetObject) || ! bool(mProjectile)) + mStatus = Failed; +} + +void AIStepRangeObject::process(AIConnection *client, Player *player) +{ + //make sure we have a client and a player + if (!client || !player) + { + mStatus = Failed; + return; + } + + //make sure we're not dead + if (! dStricmp(player->getStateName(), "Dead")) + { + mStatus = Failed; + return; + } + + //make sure we still have a target object + if (! bool(mTargetObject) || ! bool(mProjectile)) + { + mStatus = Failed; + return; + } + + //make sure the target is set + if (! mInitialized) + { + mInitialized = true; + + //set the target and the energy levels + client->setMoveTolerance(0.25f); + client->setEngageTarget(NULL); + + mTargetObject->getWorldBox().getCenter(&mTargetPoint); + if (mFromLocation != Point3F(-1, -1, -1)) + mGraphDestination = NavigationGraph::findLOSLocation(client->mLocation, mTargetPoint, mMinDistance, SphereF(mFromLocation, mMinDistance / 2.0f), mMaxDistance); + else + mGraphDestination = NavigationGraph::findLOSLocation(client->mLocation, mTargetPoint, mMinDistance, SphereF(client->mLocation, mMinDistance / 2.0f), mMaxDistance); + + //if the range is small, the graph may not have enough resolution... + if (mMaxDistance <= 10.0f) + { + if ((mTargetPoint - mGraphDestination).len() > 10.0f) + mGraphDestination = mTargetPoint; + } + + client->setMoveDestination(mGraphDestination); + client->setMoveMode(AIConnection::ModeExpress); + mStatus = InProgress; + } + + //only check every 6 frames + if (--mCheckLOSCounter <= 0) + { + mCheckLOSCounter = 6; + + //see if we're within range, or if we're where the graph told us to go... + F32 directionDot = AIConnection::get2DDot(mGraphDestination - client->mLocation, mTargetPoint - client->mLocation); + F32 distToTarg = (mTargetPoint - client->mLocation).len(); + if (((distToTarg <= mMaxDistance || directionDot <= 0.0f) && + (distToTarg > mMinDistance || directionDot > 0.0f)) || + ((client->mLocation - mGraphDestination).len() < 8.0f)) + { + Point3F aimVectorMin, aimVectorMax; + F32 timeMin, timeMax; + bool targetInRange = mProjectile->calculateAim(mTargetPoint, Point3F(0, 0, 0), client->mMuzzlePosition, client->mVelocity, &aimVectorMin, &timeMin, &aimVectorMax, &timeMax); + if (targetInRange) + { + //if we're shooting a ballistic projectile (outside), we don't need + bool playerIsOutdoors = (client->getOutdoorRadius(client->mLocation) > 0); + bool clearLOSToTarget = false; + if (mProjectile->isBallistic && playerIsOutdoors) + clearLOSToTarget = true; + else + { + //see if we have line of site + player->disableCollision(); + RayInfo rayInfo; + Point3F startPt = client->mMuzzlePosition; + Point3F endPt = mTargetPoint; + if (! gServerContainer.castRay(startPt, endPt, mLOSMask, &rayInfo)) + { + clearLOSToTarget = true; + } + else + { + //if we're here, castRay() hit something, and rayInfo should have been initialized... + if (bool(rayInfo.object) && rayInfo.object->getId() == mTargetObject->getId()) + clearLOSToTarget = true; + } + player->enableCollision(); + } + + //reset the var based on LOS + targetInRange = clearLOSToTarget; + } + + if (targetInRange) + { + //stop the client + client->setMoveMode(AIConnection::ModeStop); + mPrevLocation = client->mLocation; + if (mPrevLocation == client->mLocation) + mStatus = Finished; + else + mStatus = InProgress; + } + else + { + //if we're at our graph location, but still aren't within range... + if (((client->mLocation - mGraphDestination).len() < 8.0f) && (client->getMoveMode() == AIConnection::ModeStop)) + { + //try a shorter max distance and redo the graph query + mMaxDistance = getMax(mMaxDistance - 10.0f, mMinDistance); + mInitialized = false; + } + + //else keep moving + else + { + client->setMoveDestination(mGraphDestination); + client->setMoveMode(AIConnection::ModeExpress); + } + mStatus = InProgress; + } + } + else + { + //if we're at our graph location, but still aren't within range... + if (((client->mLocation - mGraphDestination).len() < 8.0f) && (client->getMoveMode() == AIConnection::ModeStop)) + { + //try a shorter max distance and redo the graph query + mMaxDistance = getMax(mMaxDistance - 10.0f, mMinDistance); + mInitialized = false; + } + + //else keep moving + else + { + client->setMoveDestination(mGraphDestination); + client->setMoveMode(AIConnection::ModeExpress); + } + mStatus = InProgress; + } + } +} + +//----------------------------------------------------------------------------- +//RangeObject step +AIStepIdlePatrol::AIStepIdlePatrol(Point3F *idleLocation) +{ + mInitialized = false; + mIdleLocation.set(0, 0, 0); + if (idleLocation) + mIdleLocation = *idleLocation; + mStatus = InProgress; +} + +void AIStepIdlePatrol::process(AIConnection *client, Player *player) +{ + Parent::process(client, player); + if (mStatus != InProgress) + return; + + //make sure the target is set + if (! mInitialized) + { + mInitialized = true; + + if (mIdleLocation == Point3F(0, 0, 0)) + mIdleLocation = client->mLocation; + + NavigationGraph::getChokePoints(mIdleLocation, mChokePoints, 25, 65); + mOutdoorRadius = client->getOutdoorRadius(mIdleLocation); + + mIdleState = MoveToLocation; + mStateInit = false; + mMoveLocation = mIdleLocation; + mHeadingHome = true; + return; + } + + switch (mIdleState) + { + case MoveToLocation: + if (! mStateInit) + { + mStateInit = true; + client->setMoveDestination(mMoveLocation); + client->setMoveMode(AIConnection::ModeExpress); + client->setMoveTolerance(4.0f); + } + else if (client->getPathDistRemaining(20.0f) < 8.0f) + { + client->setMoveMode(AIConnection::ModeStop); + mIdleState = LookAround; + mStateInit = false; + } + break; + + + case LookAround: + { + S32 curTime = Sim::getCurrentTime(); + if (! mStateInit) + { + mStateInit = true; + mIdleNextTime = curTime + 2000 + gRandGen.randF() * 3000; + mIdleEndTime = curTime + 8000 + gRandGen.randF() * 8000; + } + + //see if it's time to move somewhere + if (curTime > mIdleEndTime) + { + //choose a choke point + if (mHeadingHome) + { + mHeadingHome = false; + if (mChokePoints.size() == 0) + { + mMoveLocation = mIdleLocation; + if (mOutdoorRadius > 0.0f) + { + F32 noise = getMin(mOutdoorRadius, 30.0f); + mMoveLocation.x += -noise / 2 + gRandGen.randF() * noise; + mMoveLocation.y += -noise / 2 + gRandGen.randF() * noise; + } + } + else + { + mChokeIndex = S32(gRandGen.randF() * (mChokePoints.size() - 0.1f)); + mMoveLocation = mChokePoints[mChokeIndex]; + } + } + else + { + //40% chance of heading to the idle point + if (gRandGen.randF() < 0.4f || mChokePoints.size() <= 1) + { + mHeadingHome = true; + mMoveLocation = mIdleLocation; + } + else + { + mChokeIndex = S32(gRandGen.randF() * (mChokePoints.size() - 0.1f)); + mMoveLocation = mChokePoints[mChokeIndex]; + } + } + + //set the new state + mIdleState = MoveToLocation; + mStateInit = false; + } + + //alternate between choosing a new place to look, and pausing to look there + else if (curTime > mIdleNextTime) + { + mIdleNextTime = curTime + 2000 + gRandGen.randF() * 3000; + + //see if we should look or play an animation + if (gRandGen.randF() < 0.06f) + { + player->setActionThread("pda", false, true, false); + } + else + { + Point3F lookLocation; + if (mChokePoints.size() == 0) + lookLocation = mIdleLocation; + else + { + S32 index = S32(gRandGen.randF() * (mChokePoints.size() + 0.9)); + if (index == mChokePoints.size()) + lookLocation = mIdleLocation; + else + lookLocation = mChokePoints[index]; + } + + //add some noise to the lookLocation + Point3F tempVector = lookLocation - client->mLocation; + tempVector.z = 0; + if (tempVector.len() < 0.001f) + tempVector.set(0, 1, 0); + tempVector.normalize(); + tempVector *= 10.0f; + F32 noise = 2.0f; + tempVector.x += -noise + gRandGen.randF() * 2 * noise; + tempVector.y += -noise + gRandGen.randF() * 2 * noise; + tempVector.z = 1.5f + gRandGen.randF() * 2 * noise; + Point3F newAimLocation = client->mLocation + tempVector; + + //now aim at that point + if (!client->scriptIsAiming()) + client->setScriptAimLocation(newAimLocation, 3000); + } + } + } + break; + } +} diff --git a/ai/aiStep.h b/ai/aiStep.h new file mode 100644 index 0000000..3a802e3 --- /dev/null +++ b/ai/aiStep.h @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AISTEP_H_ +#define _AISTEP_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GAMECONNECTION_H_ +#include "game/gameConnection.h" +#endif +#ifndef _PLAYER_H_ +#include "game/player.h" +#endif + +class AIStep : public SimObject +{ + public: + enum StepResult + { + InProgress = 0, + Failed, + Finished + }; + int mStatus; + + protected: + float get2DAngle(const Point3F &endPt, const Point3F &basePt); + + public: + AIStep(); + int getStatus() { return mStatus; } + virtual void process(AIConnection *ai, Player *player); +}; + +class AIStepEscort : public AIStep +{ + typedef AIStep Parent; + + bool mInitialized; + bool mProximityBuffer; + S32 mResetDestinationCounter; + SimObjectPtr mClientToEscort; + + //add some idle vars + S32 mStoppedTime; + Vector mChokePoints; + bool mIdleStarted; + S32 mIdleNextTime; + + public: + AIStepEscort(GameConnection *clientToEscort = NULL); + void process(AIConnection *ai, Player *player); +}; + +class AIStepEngage : public AIStep +{ + typedef AIStep Parent; + + bool mInitialized; + SimObjectPtr mTarget; + + S32 mStraifeCounter; + S32 mPauseCounter; + S32 mCheckLOSCounter; + bool mClearLOSToTarget; + bool mPausing; + Point3F mStraifeLocation; + + bool mUsingEnergyWeapon; + float mEnergyWeaponRecharge; + + bool mSearching; + bool mSearchInitialized; + Point3F mChokeLocation; + Vector mChokePoints; + S32 mChokeIndex; + S32 mSearchTimer; + bool mSearchMove; + + public: + AIStepEngage(GameConnection *target = NULL); + void process(AIConnection *ai, Player *player); + + void initProcessVars(Player *player, Player *targPlayer); + Point3F findStraifeLocation(AIConnection *client); +}; + +class AIStepRangeObject : public AIStep +{ + typedef AIStep Parent; + + bool mInitialized; + SimObjectPtr mTargetObject; + ProjectileData *mProjectile; + U32 mLOSMask; + + F32 mMinDistance; + F32 mMaxDistance; + Point3F mTargetPoint; + Point3F mGraphDestination; + Point3F mFromLocation; + Point3F mPrevLocation; + S32 mCheckLOSCounter; + + public: + AIStepRangeObject(GameBase *targetObject = NULL, const char *projectile = NULL, F32 *minDist = NULL, F32 *maxDist = NULL, Point3F *fromLocation = NULL); + void process(AIConnection *ai, Player *player); +}; + +class AIStepIdlePatrol : public AIStep +{ + typedef AIStep Parent; + + bool mInitialized; + Point3F mIdleLocation; + Point3F mMoveLocation; + Vector mChokePoints; + S32 mChokeIndex; + F32 mOutdoorRadius; + + enum IdleStates + { + MoveToLocation, + LookAround, + }; + S32 mIdleState; + S32 mIdleNextTime; + S32 mIdleEndTime; + bool mStateInit; + bool mHeadingHome; + + public: + AIStepIdlePatrol(Point3F *idleLocation = NULL); + void process(AIConnection *ai, Player *player); +}; + +#endif diff --git a/ai/aiTask.cc b/ai/aiTask.cc new file mode 100644 index 0000000..30383d8 --- /dev/null +++ b/ai/aiTask.cc @@ -0,0 +1,235 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleInternal.h" +#include "ai/aiTask.h" + +IMPLEMENT_CONOBJECT(AITask); + +static void cAISetWeightFreq(SimObject *obj, S32, const char **argv) +{ + AITask *task = static_cast(obj); + // task->setWeightFreq(dAtoi(argv[2])); + // LH - did this until proper optimizations are performed since calcWeight() + // was 10% of some profiles. + task->setWeightFreq(dAtoi(argv[2]) * 3); +} + +static void cAISetWeight(SimObject *obj, S32, const char **argv) +{ + AITask *task = static_cast(obj); + task->setWeight(dAtoi(argv[2])); +} + +static const char* cAIGetWeight(SimObject *obj, S32, const char **) +{ + AITask *task = static_cast(obj); + S32 weight = task->getWeight(); + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d", weight); + return returnBuffer; +} + +static void cAIReWeight(SimObject *obj, S32, const char **) +{ + AITask *task = static_cast(obj); + task->reWeight(); +} + +static void cAISetMonitorFreq(SimObject *obj, S32, const char **argv) +{ + AITask *task = static_cast(obj); + task->setMonitorFreq(dAtoi(argv[2])); +} + +static void cAIReMonitor(SimObject *obj, S32, const char **) +{ + AITask *task = static_cast(obj); + task->reMonitor(); +} + +void AITask::consoleInit() +{ + Con::addCommand("AITask", "setWeightFreq", cAISetWeightFreq, "ai.setWeightFreq(freq)", 3, 3); + Con::addCommand("AITask", "setWeight", cAISetWeight, "ai.setWeight(weight)", 3, 3); + Con::addCommand("AITask", "getWeight", cAIGetWeight, "ai.getWeight()", 2, 2); + Con::addCommand("AITask", "reWeight", cAIReWeight, "ai.reWeight()", 2, 2); + + Con::addCommand("AITask", "setMonitorFreq", cAISetMonitorFreq, "ai.setMonitorFreq(freq)", 3, 3); + Con::addCommand("AITask", "reMonitor", cAIReMonitor, "ai.reMonitor()", 2, 2); +} + +AITask::AITask() +{ + mWeightFreq = 30; + mMonitorFreq = 30; + mWeightCounter = 0; + mMonitorCounter = 0; + mWeight = 0; +} + +bool AITask::onAdd() +{ + if (! Parent::onAdd()) + return false; + + //set the task 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; +} + +void AITask::calcWeight(AIConnection *ai) +{ + //make sure task is valid + if (! ai) + return; + + //see if it's time to re-weight the task + // if (--mWeightCounter <= 0) + if (gCalcWeightSlicer.ready(mWeightCounter, mWeightFreq)) + { + // mWeightCounter = mWeightFreq; + Con::executef(this, 2, "weight", avar("%d", ai->getId())); + } +} + +void AITask::monitor(AIConnection *ai) +{ + //make sure task is valid + if (! ai) + return; + + if (--mMonitorCounter <= 0) + { + mMonitorCounter = mMonitorFreq; + Con::executef(this, 2, "monitor", avar("%d", ai->getId())); + } +} + +void AITask::assume(AIConnection *ai) +{ + //make sure task is valid + if (! ai) + return; + + mMonitorCounter = 0; + Con::executef(this, 2, "assume", avar("%d", ai->getId())); +} + +void AITask::retire(AIConnection *ai) +{ + //make sure task is valid + if (! ai) + return; + + Con::executef(this, 2, "retire", avar("%d", ai->getId())); +} + +//------------------------------------------------------------------------------------- + +AISlicer::AISlicer() +{ + reset(); +} + +void AISlicer::init(U32 delay, S32 maxPerTic) +{ + // mRand.setSeed(0x88); + mDelay = delay; + mLastTime = Sim::getCurrentTime(); + // mCapPileUp = (getMax(maxPerTic - 1, 0) * mDelay); + maxPerTic; + mDebugTotal = 0; + mBudget = 2.0; + // mEnabled = true; +} + +void AISlicer::reset() +{ + init(); + // mEnabled = false; +} + +#if 0 +// Return true if this counter goes to zero AND sufficient time has elapsed. If +// the time hasn't elapsed - leave the counter. This should ensure that every +// body gets their shot without super-high frequency guys shutting out slow guys. +bool AISlicer::ready(S32& counter, S32 resetValue) +{ + U32 T = Sim::getCurrentTime(); + + if (T - mLastTime >= mDelay && --counter <= 0) + { + // 1/4 of time, add stagger noise to those that won't really notice it + if (resetValue > 3) + resetValue += !(mRand.randI() & 3); + counter = resetValue; + + // We want to allow multiple times per tic, but also to cap how much "reserve" + // readiness is allowed to pile up. + mLastTime = getMax(mLastTime + mDelay, T - mCapPileUp); + + // Verify it's handling more than one per tick Ok + mDebugTotal++; + + return true; + } + return false; +} +#else + +// The above is very mis-conceived, let's try something a little better- +// Note mDelay is a millisecond amount, while counter & reset value are game ticks. +bool AISlicer::ready(S32& counter, S32 resetValue) +{ + bool goodToGo = false; + + counter--; + + if (mBudget > 0) + { + if (counter < 0) + { + mBudget -= 1.0; + counter = resetValue; + goodToGo = true; + } + } + else + { + // Budget keeps track of how many we currently are allowed to let through. We'd + // like to keep it to no more than one per mDelay milliseconds. + U32 T = Sim::getCurrentTime(); + mBudget += (F32(T - mLastTime) / F32(mDelay)); + mBudget = getMin(mBudget, 40.0f); + mLastTime = T; + + // We have to let through tasks that have been waiting a while. Nothing will + // wait in vain for more than about 1.2 extra seconds- + if (counter < -37) + { + counter = resetValue; + goodToGo = true; + } + } + + // Track total to make sure we're getting about the percentage that we want. + mDebugTotal += goodToGo; + + return goodToGo; +} + +#endif diff --git a/ai/aiTask.h b/ai/aiTask.h new file mode 100644 index 0000000..a1a961d --- /dev/null +++ b/ai/aiTask.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AITASK_H_ +#define _AITASK_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _AICONNECTION_H_ +#include "ai/aiConnection.h" +#endif +#ifndef _PLAYER_H_ +#include "Test/player.h" +#endif + +class AITask : public SimObject +{ + typedef SimObject Parent; + + S32 mWeightFreq; + S32 mWeightCounter; + + S32 mMonitorFreq; + S32 mMonitorCounter; + + S32 mWeight; + + public: + DECLARE_CONOBJECT(AITask); + static void consoleInit(); + + AITask(); + bool onAdd(); + + void setWeightFreq(S32 freq) { mWeightFreq = getMax(1, freq); } + void setWeight(S32 weight) { mWeight = weight; } + void reWeight() { mWeightCounter = 0; } + S32 getWeight() { return mWeight; } + + void setMonitorFreq(S32 freq) { mMonitorFreq = getMax(1, freq); } + void reMonitor() { mMonitorCounter = 0; } + + //methods to call the script functions + void calcWeight(AIConnection *ai); + void monitor(AIConnection *ai); + void assume(AIConnection *ai); + void retire(AIConnection *ai); +}; + +#endif diff --git a/ai/graph.cc b/ai/graph.cc new file mode 100644 index 0000000..c0d19a1 --- /dev/null +++ b/ai/graph.cc @@ -0,0 +1,1528 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/fileStream.h" +#include "ai/graphFloorPlan.h" +#include "terrain/terrRender.h" +#include "game/player.h" +#include "game/gameConnection.h" +#include "ai/graphLOS.h" + +IMPLEMENT_CONOBJECT(NavigationGraph); + +//------------------------------------------------------------------------------------- + +NavigationGraph * gNavGraph = NULL; +NavGraphGlobals gNavGlobs; +S32 NavigationGraph::mIncarnation = 0; +bool NavigationGraph::sDrawOutdoorNodes = true; +bool NavigationGraph::sDrawIndoorNodes = true; +bool NavigationGraph::sDrawJetConnections = true; +bool NavigationGraph::sSeedDropOffs = false; +F32 NavigationGraph::sProcessPercent = 0.0f; +S32 NavigationGraph::sTotalEdgeCount = 0; +S32 NavigationGraph::sEdgeRenderMaxOutdoor = 300; +S32 NavigationGraph::sEdgeRenderMaxIndoor = 300; +U32 NavigationGraph::sLoadMemUsed = 0; +S32 NavigationGraph::sProfCtrl0 = 0; +S32 NavigationGraph::sProfCtrl1 = 0; +S32 NavigationGraph::sShowThreatened = -1; +static const char sSpawnPath[] = "terrains/%s.spn"; +static const char sRegularPath[] = "terrains/%s.nav"; + +//------------------------------------------------------------------------------------- + +// Version history. Only from ChangedToFloorPlan on is now supported. I'll leave the +// list here for aetiological reasons... :) +enum Updates { + TerrainOnly, + HandLaidInterior, + AddedBridges, + AddedPathTable, + AddedLOSTable, + ChangedToFloorPlan, + TrimmedBridges, + RevisedLOSToHash, + BetterSpawnMode + // AddedChuteHints +}; + +S32 NavigationGraph::sVersion = BetterSpawnMode; + +//------------------------------------------------------------------------------------- + +static const char * getGraphName(char * fileBuf, S32 bufSz, bool spawn) +{ + const char * name = Con::getVariable("CurrentMission"); + if (spawn) + dSprintf(fileBuf, bufSz, sSpawnPath, name); + else + dSprintf(fileBuf, bufSz, sRegularPath, name); + return fileBuf; +} + +//------------------------------------------------------------------------------------- + +NavigationGraph::NavigationGraph() + : mMaxTransients(AbsMaxBotCount * 2), + mCustomArea(0,0,0,0) +{ + AssertFatal(!gNavGraph, "May not create more than one NavigationGraph!"); + gNavGraph = this; + mNumOutdoor = 0; + mNumIndoor = 0; + mTransientStart = -1; + mCullDensity = 0.3f; + mLargestIsland = -1; + mPushedBridges = 0; + mTableBuilder = NULL; + mMainSearcher = NULL; + mLOSSearcher = NULL; + mDistSearcher = NULL; + mHaveVolumes = false; + mValidLOSTable = false; + mValidPathTable = false; + mIsSpawnGraph = false; + mCheckNode = 0; + mDeadlyLiquid = false; + mSubmergedScale = 1.0; + mShoreLineScale = 1.0; + mEdgePool = NULL; + mOutdoorNodes = NULL; + mLOSTable = NULL; + mVersion = -1; + sLoadMemUsed = 0; + setGenMagnify(0, 0, 0); +} + +NavigationGraph::~NavigationGraph() +{ + if (mTransientStart != -1) + for (S32 j = 0; j < mMaxTransients; j++) + if (GraphNode * transientNode = mNodeList[mTransientStart + j]) + delete transientNode; + + newIncarnation(); // (This deletes the searchers) + + delete [] mEdgePool; + delete [] mOutdoorNodes; + + gNavGraph = NULL; +} + +bool NavigationGraph::onAdd() +{ + U32 memUsedBefore = Memory::getMemoryUsed(); + + // Temp to avoid old small devs causing huge graph builds- + mConjoin.maxAngleDev = getMax(mConjoin.maxAngleDev, 45.0f); + + if (!Parent::onAdd()) + return false; + + mTerrainBlock = GroundPlan::getTerrainObj(); + + // DMMNOTPRESENT +// if (TerrainRender::mLiquidType >= 4) { +// mDeadlyLiquid = true; +// mSubmergedScale = 1000.0; +// mShoreLineScale = 2.0; +// } +// else { + mDeadlyLiquid = false; + mSubmergedScale = 3.7; + mShoreLineScale = 1.41; +// } + + bool forceNavLoad = Con::getBoolVariable("$GraphForceLoad"); + + if (forceNavLoad) + { + // Need to insure presence of NAV due to how code works below... + char txt[256]; + if (Stream * S = ResourceManager->openStream(getGraphName(txt, sizeof(txt), 0))) + ResourceManager->closeStream(S); + else + forceNavLoad = false; + } + + if (Con::getBoolVariable("$OFFLINE_NAV_BUILD")) + forceNavLoad = true; + + // If we're not in a single player game, we check HostGameBotCount to see + // if we want to skip NAV (if one is even there). If we are in SinglePlayer, + // a NAV file is required. + mIsSpawnGraph = false; + if (!forceNavLoad) + { + const char * missionType = Con::getVariable("CurrentMissionType"); + + // If not single player, then check the bot count. + // If it is single player, we need the graph. + if (dStricmp(missionType, "SinglePlayer")) + mIsSpawnGraph = !Con::getIntVariable("HostGameBotCount"); + } + + if (loadGraph()) + { + if (!mIsSpawnGraph) + { + makeGraph(true); + pushBridges(); + clearLoadData(); // remove load data not needed at run time. + } + } + + sLoadMemUsed = Memory::getMemoryUsed() - memUsedBefore; + Con::printf("Memory consumed = %d", sLoadMemUsed); + + return true; +} + +void NavigationGraph::onRemove() +{ + Parent::onRemove(); +} + +void NavigationGraph::clearLoadData() +{ + mEdgeInfoList.clear(); mEdgeInfoList.compact(); + mNodeInfoList.clear(); mNodeInfoList.compact(); + mBridgeList.clear(); mBridgeList.compact(); + mTerrainInfo.consolidated.clear(); mTerrainInfo.consolidated.compact(); + mTerrainInfo.shadowHeights.clear(); mTerrainInfo.shadowHeights.compact(); +} + +// Called before saving on spawn graphs- clean out all unneeded data. +void NavigationGraph::purgeForSpawn() +{ + mEdgeInfoList.clear(); mEdgeInfoList.compact(); + mBridgeList.clear(); mBridgeList.compact(); + // mNodeVolumes.clear(); mNodeVolumes.compact(); + if (mLOSTable) + mLOSTable->clear(); +} + +// Called when new graph is made. Incarnation # used so bots can (theoretically) +// run around which graph editing is taking place. +void NavigationGraph::newIncarnation() +{ + mIncarnation = mIncarnation + 1; + + // Searchers- + delete mTableBuilder; + delete mMainSearcher; + delete mLOSSearcher; + delete mDistSearcher; + mTableBuilder = NULL; + mMainSearcher = NULL; + mLOSSearcher = NULL; + mDistSearcher = NULL; + + mJetManager.clear(); +} + +//------------------------------------------------------------------------------------- + +// Print size of anything which supports memSize(), accumulate total. +#define PrintMemSize(V, msg) { \ + Con::printf("%s: usage = %d", msg, V.memSize()); \ + total += V.memSize(); } + +// Try to account for all contributors to the overall footprint- +// (This isn't complete yet)- +U32 NavigationGraph::reckonMemory() const +{ + // TerrainGraphInfo mTerrainInfo.memSize(); + // ChuteHints mChutes; + // OutdoorNode * mOutdoorNodes; + // GraphBSPTree mIndoorTree; + // GraphEdge * mEdgePool; + U32 total = 0; + PrintMemSize(mNodeList, "mNodeList"); // GraphNodeList + PrintMemSize(mNodeGrid, "mNodeGrid"); // GraphNodeList + PrintMemSize(mNonTransient, "mNonTransient"); // GraphNodeList + PrintMemSize(mIndoorPtrs, "mIndoorPtrs"); // GraphNodeList + PrintMemSize(mIndoorNodes, "mIndoorNodes"); // IndoorNodeList + PrintMemSize(mIslandPtrs, "mIslandPtrs"); // GraphNodeList + PrintMemSize(mIslandSizes, "mIslandSizes"); // Vector + PrintMemSize(mBoundaries, "mBoundaries"); // GraphBoundaries + PrintMemSize(mNodeVolumes, "mNodeVolumes"); // GraphVolumeList + + PrintMemSize(mRenderThese, "mRenderThese"); // Vector + PrintMemSize(mRenderBoxes, "mRenderBoxes"); // Vector + PrintMemSize(mTempNodeBuf, "mTempNodeBuf"); // Vector + PrintMemSize(mVisibleEdges, "mVisibleEdges"); // GraphEdgePtrs + // SearchThreats mThreats; + // MonitorForceFields mForceFields; + // JetManager mJetManager; + Con::printf("*** Total accounted for = %d", total); + return total; +} + +//------------------------------------------------------------------------------------- + +void NavigationGraph::checkHashTable(S32 nodeCount) const +{ + #if 0 + S32 accum[4] = {0, 0, 0, 0}; + S32 errCount = 0; + + for (S32 i = 0; i < nodeCount; i++) { + for (S32 j = 0; j < nodeCount; j++) { + U32 vOld = mLOSXRef.value(i, j); + U32 vNew = mLOSHashTable.value(i, j); + if (vOld != vNew) { + errCount++; + Con::printf("LOSHash Error at %d <-> %d", i, j); + } + else { + AssertFatal(vOld < 4, "Bad LOS hash table entry value"); + accum[vOld]++; + } + } + } + + if (errCount) + Con::printf("There were %d errors in conversion of LOS table", errCount); + for (S32 d = 0; d < 4; d++) + Con::printf("LOS distribution for %d = %d", d, accum[d]); + #else + nodeCount; + #endif +} + +// From original XREF data, convert to hash version. +U32 NavigationGraph::makeLOSHashTable() +{ + U32 memUsage = 0; + S32 nodeCount = numNodes(); + + memUsage = mLOSHashTable.convertTable(mLOSXRef, nodeCount); + checkHashTable(nodeCount); + mLOSXRef.clear(); + mLOSTable = & mLOSHashTable; + + return memUsage; +} + +//------------------------------------------------------------------------------------- + +GraphSearch * NavigationGraph::getMainSearcher() +{ + if (!mMainSearcher) + mMainSearcher = new GraphSearch(); + return mMainSearcher; +} + +GraphSearchLOS * NavigationGraph::getLOSSearcher() +{ + if (!mLOSSearcher) + mLOSSearcher = new GraphSearchLOS(); + return mLOSSearcher; +} + +GraphSearchDist * NavigationGraph::getDistSearcher() +{ + if (!mDistSearcher) + mDistSearcher = new GraphSearchDist(); + return mDistSearcher; +} + +//------------------------------------------------------------------------------------- + +bool NavigationGraph::gotOneWeCanUse() // i.e. for navigation (not spawning) +{ + if (gNavGraph) { + if (gNavGraph->mNumOutdoor || gNavGraph->mNumIndoor) + return true; + } + return false; +} + +bool NavigationGraph::hasSpawnLocs() +{ + return (gNavGraph && gNavGraph->mSpawnList.size()); +} + +bool NavigationGraph::customArea(GridArea& areaOut) const +{ + if (mCustomArea.extent.x > 0 && mCustomArea.extent.y > 0) { + areaOut = mCustomArea; + return true; + } + return false; +} + +// Practically speaking, warnings might be part of graph fine tuning until Beta. +void NavigationGraph::warning(const char* message) +{ + message; +#ifdef DEBUG + static S32 warnCount = 0; + Con::printf("GraphWarning #%d! %s", ++warnCount, message); +#endif +} + +void NavigationGraph::initPersistFields() +{ + Parent::initPersistFields(); + + // Slope deviation at which terrain squares are considered flat. + addField("conjoinAngleDev", TypeF32, Offset(mConjoin.maxAngleDev, NavigationGraph)); + + // ratio of largest island to total nodes: + addField("cullDensity", TypeF32, Offset(mCullDensity, NavigationGraph)); + + // If user doesn't want the mission area as default- + addField("customArea", TypeRectI, Offset(mCustomArea, NavigationGraph)); +} + +//------------------------------------------------------------------------------------- +// GRAPH LOAD & SAVE. + +bool NavigationGraph::loadGraph() +{ + bool Ok = false; + char fileBuf[256]; + + getGraphName(fileBuf, sizeof(fileBuf), mIsSpawnGraph); + + // Load if found- + if (Stream * stream = ResourceManager->openStream(fileBuf)) + { + if (! load(* stream, mIsSpawnGraph)) + Con::printf("loadGraph: Failed to load '%s'", fileBuf); + else + Ok = true; + ResourceManager->closeStream(stream); + + if (Ok && !mIsSpawnGraph) + { + // NAV graph needs the spawn data. I know, this isn't all that organized... + getGraphName(fileBuf, sizeof(fileBuf), true); + if (Stream * spawnStream = ResourceManager->openStream(fileBuf)) + { + if (!load(* spawnStream, true)) + Con::printf("Error loading spawn file into NAV graph"); + ResourceManager->closeStream(spawnStream); + } + else + Con::printf("loadGraph: NAV Ok, but SPN open failed (%s)", fileBuf); + } + } + else + Con::printf("loadGraph: Couldn't open '%s' for read", fileBuf); + + return Ok; +} + +bool NavigationGraph::saveGraph() +{ + FileStream fStream; + bool Ok = false; + char fileBuf[256]; + + getGraphName(fileBuf, sizeof(fileBuf), mIsSpawnGraph); + + if (ResourceManager->openFileForWrite(fStream, ResourceManager->getModPathOf(fileBuf), fileBuf)) + { + if (mIsSpawnGraph) + { + makeSpawnList(); + purgeForSpawn(); + } + if (!save(fStream)) + Con::printf("saveGraph: Failed to save '%s'", fileBuf); + else + Ok = true; + fStream.close(); + + // call scripts that were done. + Con::executef(this, 1, "navBuildComplete"); + } + else + Con::printf("saveGraph: Couldn't open '%s' for write", fileBuf); + + return Ok; +} + +//------------------------------------------------------------------------------------- +// GRAPH STREAM PERSISTENCE + +bool NavigationGraph::load(Stream & s, bool isSpawn) +{ + bool Ok = true; + + Ok &= s.read(&mVersion); + + // Some reserved- + S32 res; + Ok &= (s.read(&res) && s.read(&res) && s.read(&res) && s.read(&res)); + + // The reduced spawn list- + if (mVersion >= BetterSpawnMode) + { + Ok &= mSpawnList.read(s); + + // Before we're loaded, this condition signals that graph is just a spawn list. + // After loaded, non-empty mSpawnList is the condition.... + if (isSpawn) + return Ok; + else + mSpawnList.reset(); + } + + // Read edges & nodes/volumes. Volume list is separate from indoor node list only + // because of order things were developed in. Otherwise they are one-to-one. + Ok &= readVector1(s, mEdgeInfoList); + Ok &= readVector1(s, mNodeInfoList); + Ok &= mNodeVolumes.read(s); + + // Outside- + Ok &= mTerrainInfo.read(s); + + // Bridges- + AssertISV(mVersion >= TrimmedBridges, "Graph needs rebuilt for this mission"); + Ok &= mBridgeList.read(s); + // else + // Ok &= mBridgeList.readOld(s); + + // Tables- + if(mVersion >= AddedPathTable) + Ok &= mPathXRef.read(s); + + if(mVersion >= AddedLOSTable) + { + if (mVersion >= RevisedLOSToHash) + { + Ok &= mLOSHashTable.read(s); + mLOSTable = & mLOSHashTable; + } + else + { + Ok &= mLOSXRef.read(s); + if (sVersion >= RevisedLOSToHash) + { + // Temporary conversion code + S32 numNodes = mNodeInfoList.size() + mTerrainInfo.consolidated.size(); + if (mLOSXRef.valid(numNodes)) + { + mLOSHashTable.convertTable(mLOSXRef, numNodes); + checkHashTable(numNodes); + mLOSXRef.clear(); + mLOSTable = & mLOSHashTable; + } + } + else + mLOSTable = & mLOSXRef; + } + } + + // if(mVersion >= AddedChuteHints) + // Ok &= mChutes.read(s); + + return Ok; +} + +bool NavigationGraph::save(Stream & s) +{ + bool Ok = true; + + Ok &= s.write(sVersion); + + // Reserveds- + S32 res = 0; + Ok &= (s.write(res) && s.write(res) && s.write(res) && s.write(res)); + + if (sVersion >= BetterSpawnMode) + { + if (!mIsSpawnGraph) + mSpawnList.reset(); + Ok &= mSpawnList.write(s); + if (mIsSpawnGraph) + return Ok; + } + + // Indoors- + Ok &= writeVector1(s, mEdgeInfoList); + Ok &= writeVector1(s, mNodeInfoList); + Ok &= mNodeVolumes.write(s); + + // Outside- + Ok &= mTerrainInfo.write(s); + + // Bridges- + Ok &= mBridgeList.write(s); + + // Tables- + Ok &= mPathXRef.write(s); + + // Write out the table- + if (sVersion < RevisedLOSToHash) + Ok &= mLOSXRef.write(s); + else + Ok &= mLOSHashTable.write(s); + + // Chute hints- + // Ok &= mChutes.write(s); + + return Ok; +} + +//------------------------------------------------------------------------------------- +// Graph Console Functions +//------------------------------------------------------------------------------------- + +static bool cSaveGraph(SimObject *ptr, S32, const char **) +{ + NavigationGraph *navGraph = static_cast(ptr); + return navGraph->saveGraph(); +} + +//------------------------------------------------------------------------------------- + +static bool cSetGround(SimObject *ptr, S32, const char **argv) +{ + NavigationGraph * navGraph = static_cast(ptr); + GroundPlan * gp = dynamic_cast(Sim::findObject(argv[2])); + + if (!gp) + Con::printf("Couldn't find ground plan %s", argv[2]); + else + return navGraph->setGround(gp); + + return false; +} + +//------------------------------------------------------------------------------------- + +static bool cMakeGraph(SimObject *ptr, S32, const char **) +{ + NavigationGraph * navGraph = static_cast(ptr); + navGraph->makeGraph(false); + return true; +} + +//------------------------------------------------------------------------------------- + +static bool cLoadGraph(SimObject *ptr, S32, const char **) +{ + NavigationGraph *navGraph = static_cast(ptr); + return navGraph->loadGraph(); +} + +//------------------------------------------------------------------------------------- +// LOS XRef table construction. + +static bool cPrepLOS(SimObject * ptr, S32 argc, const char **argv) +{ + Point3F viewLoc(0,0,100); + if (argc == 3) + dSscanf(argv[2], "%f %f %f", &viewLoc.x, &viewLoc.y, &viewLoc.z); + NavigationGraph *navGraph = static_cast(ptr); + return navGraph->prepLOSTableWork(viewLoc); +} + +static bool cMakeLOS(SimObject * ptr, S32, const char **) +{ + NavigationGraph *navGraph = static_cast(ptr); + return navGraph->makeLOSTableEntries(); +} + +//------------------------------------------------------------------------------------- +// Bridge building and management + +// Processing routine - just builds the data +static bool cFindBridges(SimObject *ptr, S32, const char **) +{ + NavigationGraph *navGraph = static_cast(ptr); + if (const char * errorText = navGraph->findBridges()) + { + Con::printf(errorText); + return false; + } + return true; +} + +// Installs the edges onto the nodes of a made graph +static bool cPushBridges(SimObject *ptr, S32, const char **) +{ + NavigationGraph *navGraph = static_cast(ptr); + + if (S32 islandsBefore = navGraph->numIslands()) + { + if (const char * errorText = navGraph->pushBridges()) + Con::printf(errorText); + else + { + if (S32 islandsBridged = islandsBefore - navGraph->numIslands()) + Con::printf("%d islands have been bridged", islandsBridged); + return true; + } + } + else + Con::printf("Graph hasn't been made"); + + return false; +} + +//------------------------------------------------------------------------------------- + +// Try to cull out dense clusters of nodes. Pass in a param if you want to +// cull out everything except biggest island. +static bool cCullIslands(SimObject *ptr, S32, const char **) +{ + NavigationGraph *navGraph = static_cast(ptr); + navGraph->cullIslands(); + return true; +} + +//------------------------------------------------------------------------------------- + +static bool cMakeTables(SimObject * ptr, S32, const char* * ) +{ + NavigationGraph * navGraph = static_cast(ptr); + navGraph->makeTables(); + return true; +} + +//------------------------------------------------------------------------------------- + +static bool cAssemble(SimObject * ptr, S32, const char* *) +{ + NavigationGraph * navGraph = static_cast(ptr); + return navGraph->assemble(); +} + +//------------------------------------------------------------------------------------- + +static S32 cNumNodes(SimObject* ptr, S32, const char* []) +{ + NavigationGraph * navGraph = static_cast(ptr); + return navGraph->numNodes(); +} + +static bool cSetGenMode(SimObject* ptr, S32 argc, const char* argv[]) +{ + if (argc == 3) { + NavigationGraph * navGraph = static_cast(ptr); + navGraph->setGenMode(!dStricmp(argv[2], "spawn")); + } + else + Con::printf("Set graph generation mode to Nav (default) or Spawn"); + return true; +} + +//------------------------------------------------------------------------------------- + +static S32 cRandNode(SimObject* ptr, S32 argc, const char* argv[]) +{ + NavigationGraph * navGraph = static_cast(ptr); + + if (argc >= 4) + { + Point3F pt; + dSscanf(argv[2], "%f %f %f", &pt.x, &pt.y, &pt.z); + F32 radius = dAtoi(argv[3]); + bool inside = argc > 4 ? dAtob(argv[4]) : false; + bool outside = argc > 5 ? dAtob(argv[5]) : false; + + return navGraph->randNode(pt, radius, inside, outside); + } + else + { + Con::printf ("Given a point, radius, and flags for indoor or outdoor"); + Con::printf ("inclusion, %s returns a node index (-1 if not found).", argv[1]); + Con::printf ("Note use navGraph.nodeLoc() to get location from index"); + } + return -1; +} + +static const char bogusLocation[] = "0 0 500"; + +static const char * cNodeLoc(SimObject* ptr, S32 argc, const char* argv[]) +{ + NavigationGraph * navGraph = static_cast(ptr); + + if (argc == 3) + { + S32 nodeIndex =dAtoi(argv[2]); + + if (const Point3F * pt = navGraph->getSpawnLoc(nodeIndex)) + { + char * buff = Con::getReturnBuffer(100); + dSprintf(buff, 100, "%f %f %f", pt->x, pt->y, pt->z); + return buff; + } + else + Con::printf("Invalid index (%d) passed to nodeLoc()", nodeIndex); + } + else + Con::printf("Gets the location of the specified node or spawn index"); + + return bogusLocation; +} + +// Go up slightly and cast down to ground. +static void adjustSpawnLoc(Point3F & point) +{ + point.z += 1.3; + Loser cast(-1); + if (cast.hitBelow(point, 20.0)) + { + Point2F sideways(cast.mColl.normal.x, cast.mColl.normal.y); + point.z += sideways.len() / 2.0; + point.z += 0.2; // <<=== some shapes have problems... cf. ThinInce + } +} + +static const char * cRandNodeLoc(SimObject* ptr, S32 argc, const char* argv[]) +{ + NavigationGraph * navGraph = static_cast(ptr); + + if (argc == 3) + { + S32 nodeIndex =dAtoi(argv[2]); + + if (const Point3F * pt = navGraph->getRandSpawnLoc(nodeIndex)) + { + // if (NavigationGraph::sProfCtrl0 == 337) { + // // Testing steepness on Escalade- want spawns near high building- + // static U32 stagger = 0; + // static Point3F testLocs[5] = { + // Point3F(438, -42, 297), + // Point3F(434.4, -53.5, 270), + // Point3F(428, -45, 264), + // Point3F(463, -14, 264), + // Point3F(473, -44, 223) + // }; + // pt = &testLocs[stagger++ % 5]; + // } + + Point3F point = * pt; + adjustSpawnLoc(point); + char * buff = Con::getReturnBuffer(100); + dSprintf(buff, 100, "%f %f %f", point.x, point.y, point.z); + return buff; + } + else + Con::printf("Invalid index (%d) passed to %s()", nodeIndex, argv[1]); + } + else + Con::printf("Finds a random location within the space of the given node"); + + return bogusLocation; +} + +// Get a good direction to face, given a location. +static const char * cWhereToLook(SimObject* , S32 argc, const char* argv[]) +{ + if (argc >= 2) { + Point3F point; + dSscanf(argv[1], "%f %f %f", &point.x, &point.y, &point.z); + + // We get a Z rotation back- + F32 angle = - NavigationGraph::whereToLook(point); + + // Build our return string for use by setTransform(). + char * buff = Con::getReturnBuffer(120); + dSprintf(buff, 120, "%f %f %f 0 0 1 %f", point.x, point.y, point.z, angle); + return buff; + } + else { + Con::printf ("Given a point, return a transform in a 'nice' direction to look."); + return NULL; + } +} + +static bool cNavGraphExists(SimObject*, S32, const char **) +{ + return (NavigationGraph::gotOneWeCanUse() || NavigationGraph::hasSpawnLocs()); +} + +static void cDumpInfo2File(SimObject *ptr, S32, const char**) +{ + NavigationGraph * ng = static_cast(ptr); + + #define printDump(a) {const char * b = a; LogFile.write(dStrlen(b),b);} + + FileStream LogFile; + + LogFile.open("NavMetrics.log", FileStream::ReadWrite); + + if(LogFile.getStatus() == Stream::Ok) + { + LogFile.setPosition(LogFile.getStreamSize()); + + printDump("\r\n\r\n"); + // printDump(avar("Mission: %s", ng->mGraphFile)); + printDump("---------------------------\r\n"); + printDump(avar("Graph Stats: %d nodes (%d outdoor)\r\n", + ng->numNodes(), ng->numOutdoor())); + printDump(avar("--> %d bridges\r\n", ng->numBridges())); + printDump(avar("--> %d edges\r\n", NavigationGraph::sTotalEdgeCount)); + printDump(avar("Graph load memory used: %d", NavigationGraph::sLoadMemUsed)); + printDump("\r\n"); + } + + LogFile.close(); +} + +static bool cGraphInfo(SimObject* ptr, S32 argc, const char* argv[]) +{ + NavigationGraph * navGraph = static_cast(ptr); + + // Test it out + // U32 memUse = navGraph->makeLOSHashTable(); + // Con::printf("Hash table segment pool takes up %d bytes", memUse); + + S32 bridges = navGraph->numBridges(); + S32 edges = NavigationGraph::sTotalEdgeCount; + S32 totalNodes = navGraph->numNodes(); + S32 numOutdoor = navGraph->numOutdoor(); + + Con::printf("Graph Stats: %d nodes (%d outdoor)", totalNodes, numOutdoor); + Con::printf("--> %d islands", navGraph->numIslands()); + Con::printf("--> %d bridges", bridges); + Con::printf("--> %d edges", edges); + Con::printf("Graph load memory used: %d", NavigationGraph::sLoadMemUsed); + Con::printf("Edge alloc = (%d + %d + 2 x %d) x %d == %d", + bridges, edges, totalNodes, sizeof(GraphEdge), + (bridges + edges + 2 * totalNodes) * sizeof(GraphEdge) + ); + Con::printf("NavGraph structure = %d bytes", sizeof(NavigationGraph)); + + if (argc > 2) { + Point3F from; + dSscanf(argv[2], "%f %f %f", &from.x, &from.y, &from.z); + F32 inner = (argc > 3 ? dAtof(argv[3]) : 50); + F32 outer = (argc > 4 ? dAtof(argv[4]) : 1e9); + U32 cond = (argc > 5 ? dAtoi(argv[5]) : 3); + + navGraph->clearRenderSegs(); + navGraph->markNodesInSight(from, inner, outer, cond); + Con::printf(navGraph->drawNodeInfo(from)); + } + return true; +} + +//------------------------------------------------------------------------------------- + +static void cSpawnInfo(SimObject* ptr, S32, const char* []) +{ + NavigationGraph * navGraph = static_cast(ptr); + navGraph->printSpawnInfo(); +} + +//------------------------------------------------------------------------------------- + +// Args to installThreat and updateThreat are same, parse here. Also checks for +// presence of adequate graph. +static ShapeBase * fetchThreatInfo(S32 argc, const char* argv[], S32& team, F32& rad) +{ + if (NavigationGraph::gotOneWeCanUse()) { + ShapeBase * threatObject; + if (argc >= 4 && Sim::findObject(argv[2], threatObject)) { + team = dAtoi(argv[3]); + rad = argc > 4 ? dAtof(argv[4]) : 50.0f; + return threatObject; + } + } + return NULL; +} + +static bool cInstallThreat(SimObject* , S32 argc, const char* argv[]) +{ + S32 team; + F32 rad; + + if (ShapeBase * threat = fetchThreatInfo(argc, argv, team, rad)) + return gNavGraph->installThreat(threat, team, rad); + + Con::printf("%s(): register permanent (static object) threat on the graph", argv[1]); + return false; +} + +//------------------------------------------------------------------------------------- + +static bool cDetectForceFields(SimObject* , S32, const char**) +{ + if (NavigationGraph::gotOneWeCanUse()) { + gNavGraph->detectForceFields(); + } + return false; +} + +//------------------------------------------------------------------------------------- +// These are patches to use for suspected script function bottlenecks so they show up +// in the profiler. We pass down arguments. Do some timing, might give info. + +struct TrackPatch +{ + enum { MaxDepth = 12 }; + U32 recursed[MaxDepth + 1]; + U32 totalMS, numCalls, depth, maxDepth, lastMS; + F32 average; + const char * name; + TrackPatch() + { + totalMS = numCalls = depth = maxDepth = lastMS = 0; + dMemset(recursed, 0, sizeof(recursed)); + average = 0.0f; + } + const char * patch(S32, const char**); +}; + +const char * TrackPatch::patch(S32 argc, const char * * argv) +{ + name = argv[0]; + recursed[depth++]++; + if (depth > maxDepth) + { + AssertFatal(depth < MaxDepth, avar("Patched too deep: %s", name)); + maxDepth = depth; + } + // Call and time it- + U32 saveMS = Platform::getRealMilliseconds(); + const char * result = Con::execute(argc - 1, argv + 1); + lastMS = (Platform::getRealMilliseconds() - saveMS); + totalMS += lastMS; + average = F32(totalMS) / F32(++numCalls); + depth--; + return result; +} + +TrackPatch gTrackProfPatch1; +const char * cProfilePatch1(SimObject* , S32 argc, const char** argv) +{ + return gTrackProfPatch1.patch(argc, argv); +} + +TrackPatch gTrackProfPatch2; +const char * cProfilePatch2(SimObject* , S32 argc, const char** argv) +{ + return gTrackProfPatch2.patch(argc, argv); +} + +//------------------------------------------------------------------------------------- + + +// For checking out a couple of LOS bugs. Also mulitple casts for getting idea +// of LOS "budget". ==> Needs to go back into DEBUG +static const char * cGetLOSPoint(SimObject* , S32 argc, const char * * argv) +{ + if (argc >= 4) + { + Point3F from, losPt, to; + dSscanf(argv[2], "%f %f %f", &from.x, &from.y, &from.z); + dSscanf(argv[3], "%f %f %f", &to.x, &to.y, &to.z); + S32 iters = (argc > 4 ? dAtoi(argv[4]) : 1); + + RayInfo coll; + while (--iters >= 0) + if (gServerContainer.castRay(from, to, U32(-1), &coll)) + if (!iters) + { + Point3F S = coll.point; + Con::printf("Found solution = (%f, %f, %f)", S.x, S.y, S.z); + } + } + return 0; +} + +//------------------------------------------------------------------------------------- + +// Want to test the spawning: +static S32 cNumSpawns(SimObject * ptr, S32, const char **) +{ + return (static_cast(ptr))->numSpawns(); +} +static const char * cGetSpawn(SimObject * ptr, S32 argc, const char * * argv) +{ + if (argc == 3) + { + char * buff = Con::getReturnBuffer(100); + NavigationGraph * graph = static_cast(ptr); + S32 which = dAtoi(argv[2]); + + if (validArrayIndex(which, graph->numSpawns())) + { + Point3F point = graph->getSpawn(which); + adjustSpawnLoc(point); + dSprintf(buff, 100, "%f %f %f", point.x, point.y, point.z); + return buff; + } + else + Con::printf("Spawn index %d out of range!", which); + } + return 0; +} + +//------------------------------------------------------------------------------------- + +#ifdef DEBUG + +static const char * cHidingPlace(SimObject* , S32 argc, const char * * argv) +{ + if (argc >= 4) { + Point3F from, avoidPt; + dSscanf(argv[2], "%f %f %f", &from.x, &from.y, &from.z); + dSscanf(argv[3], "%f %f %f", &avoidPt.x, &avoidPt.y, &avoidPt.z); + F32 rad = (argc > 4 ? dAtof(argv[4]) : 13.0f); + F32 hideLen = (argc > 5 ? dAtof(argv[5]) : 22.0f); + Point3F seek; + + // Two hide queries- first seeks those with further hide length byeond, other + // seeks a slope away from LOS point (used for sniping) + if (hideLen > 0) + seek = NavigationGraph::hideOnDistance(from, avoidPt, rad, hideLen); + else + seek = NavigationGraph::hideOnSlope(from, avoidPt, rad, -hideLen); + + Con::printf("Seek point is (%f, %f, %f)", seek.x, seek.y, seek.z); + } + else { + Con::printf("From src, find hiding place from avoid at least rad away."); + Con::printf("Hides based on hidden distance beyond or slope angle."); + } + return 0; +} + +//------------------------------------------------------------------------------------- + +static const char * cChokePoints(SimObject* , S32 argc, const char * * argv) +{ + if (argc >= 4) { + Point3F from; + dSscanf(argv[2], "%f %f %f", &from.x, &from.y, &from.z); + F32 hideDist = dAtof(argv[3]); + F32 maxDist = (argc > 4 ? dAtof(argv[4]) : 1e9); + Vector points; + NavigationGraph::getChokePoints(from, points, hideDist, maxDist); + } + else { + Con::printf("Get list of choke points from source. HideDist tells how long of"); + Con::printf("obstructed length must exist out of LOS. maxDist truncs search"); + } + return 0; +} + +//------------------------------------------------------------------------------------- + +// Tracking has been removed, but we may put it back in a different for. Anyway, +// this has some other info. +static bool cTrackObject(SimObject* ptr, S32 argc, const char* argv[]) +{ + NavigationGraph * navGraph = static_cast(ptr); + GameConnection * target; + if (argc >= 3 && Sim::findObject(argv[2], target)) + { + if (Player * player = dynamic_cast(target->getControlObject())) + { + // We just sort of dump all our test stuff here.... + F32 thrust, dur, jumpSpeed; + player->getJetAbility(thrust, dur, jumpSpeed); + F32 rating = gNavGraph->jetManager().calcJetRating(thrust, dur); + Con::printf("Jet rating for player = %f", rating); + rating += (jumpSpeed * dur * TickSec); + Con::printf("With jump, rating is = %f", rating); + + return true; + } + } + return true; +} + +//------------------------------------------------------------------------------------- + +// See if payer can get from A to B. +static bool cPlayerCanGo(SimObject* ptr, S32 argc, const char* argv[]) +{ + if (argc == 5) + { + Point3F A, B; + Player * player; + if (Sim::findObject(argv[2], player)) + { + NavigationGraph * navGraph = static_cast(ptr); + dSscanf(argv[3], "%f %f %f", &A.x, &A.y, &A.z); + dSscanf(argv[4], "%f %f %f", &B.x, &B.y, &B.z); + + F32 ratings[2]; + JetManager::Ability ability; + player->getJetAbility(ability.acc, ability.dur, ability.v0); + gNavGraph->jetManager().calcJetRatings(ratings, ability); + Con::printf("Ratings = %d and %d", ratings[0], ratings[1]); + if (gNavGraph->testPlayerCanReach(A, B, ratings)) + Con::printf("Made it"); + } + else + Con::printf("Could not find Player %s", argv[2]); + } + return false; +} + + +// debug +#endif + +#ifdef INTERNAL_RELEASE +#define DO_MATH_TESTS 1 +#else +#define DO_MATH_TESTS 0 +#endif + +#if DO_MATH_TESTS +extern void Athlon_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *res); +extern void SSE_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *res); +extern void default_matF_x_matF_C(const F32 *matA, const F32 *matB, F32 *res); + +extern U32 gSSE_MatXMat_Calls; + +static Point3F& rangify(Point3F& cycle) // keep in range- +{ + if (cycle.x > 4.0) cycle.x -= (3.14159 * 2.0); + if (cycle.y > 5.0) cycle.y -= (3.14159 * 2.0); + if (cycle.z > 6.0) cycle.z -= (3.14159 * 2.0); + return cycle; +} + +F32 sWorstResultDiff = 0.0; // insure same results- +static bool verify(const F32 * m1, const F32 * m2, S32 N = 16) +{ + while(N--) { + F32 D = mFabs(*m1++ - *m2++); + sWorstResultDiff = getMax(D, sWorstResultDiff); + if (D > 0.01) + return false; + } + return true; +} + +// Test out the Athlon code- +static S32 cTestMath(SimObject*, S32 argc, const char* argv[]) +{ + // May just want this info (so pass in zero to add nothing to it) + Con::printf("Num calls to SSE func = %d", gSSE_MatXMat_Calls); + + // Default to a million calls- + S32 numTests = (argc > 1 ? dAtoi(argv[1]) : 10); + S32 numIters = (argc > 2 ? dAtoi(argv[2]) : 100000); + S32 whatTest = (argc > 3 ? dAtoi(argv[3]) : 0); + void (*mo_Betta_Math)(const F32*, const F32 *, F32*) = default_matF_x_matF_C; + S32 failures = 0; + + U32 properties = Platform::SystemInfo.processor.properties; + switch(whatTest) + { + case 0: + if (properties & CPU_PROP_SSE) { + Con::errorf("Testing SSE"); + mo_Betta_Math = SSE_MatrixF_x_MatrixF; + } else { + Con::errorf("SSE not detected!"); + return 0; + } + break; + case 1: + if (properties & CPU_PROP_3DNOW) { + Con::errorf("Trying to test 3DNow"); + mo_Betta_Math = Athlon_MatrixF_x_MatrixF; + } else { + Con::errorf("3DNow not detected!"); + return 0; + } + break; + } + + // Make random matrices. + for (S32 which = 0; which < 2; which++) + { + Point3F point1(-1e9, 1000.1, -2), point2(1, 2, 1e17); + EulerF cycle1(0,0,0), cycle2(M_PI,M_PI,M_PI); + EulerF add1(0.2, 0.3, 0.5), add2(0.7, 0.13, 0.37); + U32 ms = Platform::getRealMilliseconds(); + + for (S32 i = 0; i < numTests; i++) + { + MatrixF mat1(cycle1); + MatrixF mat2(cycle2); + mat1.setColumn(3, point1); + mat1.setColumn(3, point2); + MatrixF res1, res2; + + // Check we're getting same results- + default_matF_x_matF_C(mat1, mat2, res1); + mo_Betta_Math(mat1, mat2, res2); + if (!verify(res1, res2)) { + Con::printf("Matrix outputs differ on case %d!", i); + failures++; + } + + // Now do repeated calls to get timing. Use separate loops to avoid dilution. + if (which) for (S32 j = 0; j < numIters; j++) + default_matF_x_matF_C(mat1, mat2, res1); + else for (S32 j = 0; j < numIters; j++) + mo_Betta_Math(mat1, mat2, res1); + + rangify(cycle1 += add1); + rangify(cycle2 += add2); + point1 -= add2; + point2 += add1; + } + Con::printf("%d ms on pass %d", Platform::getRealMilliseconds() - ms, which); + } + + Con::printf("Worst result difference = %f", sWorstResultDiff); + + return failures; +} +#endif + + +//------------------------------------------------------------------------------------- + +// Test out the closestNode() function on the graph. Also other misc. test/debug stuff +static bool cCheckFunction(SimObject* ptr, S32 argc, const char* argv[]) +{ + if (argc == 3) + { + Point3F center; + F32 contain; + NavigationGraph * navGraph = static_cast(ptr); + dSscanf(argv[2], "%f %f %f", ¢er.x, ¢er.y, ¢er.z); + GraphNode * best = navGraph->closestNode(center, & contain); + if (best) + { + Con::printf("********** Containment = %f **********", contain); + Point3F P = best->location(); + Con::printf("Supplied loc = (%f, %f, %f)", center.x, center.y, center.z); + Con::printf("Node location = (%f, %f, %f)", P.x, P.y, P.z); + NodeProximity prox = best->containment(center); + Con::printf("Prox = (%f, %f, %f)", prox.mLateral, prox.mHeight, prox.mAboveC); + } + else + Con::printf("Nothing found"); + + FloorPlan::setBreakPoint(center); + } + return true; +} + +//------------------------------------------------------------------------------------- + +// +// This probably needs to remain ISV because it can be useful in determining if a graph +// is feasible / efficient. +// +static bool cTimeTest(SimObject * ptr, S32 argc, const char * * argv) +{ + if (argc >= 3) + { + S32 numSearches = dAtoi(argv[2]); + NavigationGraph * navGraph = static_cast(ptr); + bool doAStar = (argc > 3 ? dAtob(argv[3]) : false); + + U32 saveMS = Platform::getRealMilliseconds(); + F32 average = navGraph->performTests(numSearches, doAStar); + F32 elapsed = F32(Platform::getRealMilliseconds() - saveMS); + + Con::printf("Average number of Q extractions in search = %f", average); + Con::printf("Elapsed time = %f seconds", elapsed * 0.001); + } + return true; +} + +//------------------------------------------------------------------------------------- + +PreloadTextures::PreloadTextures() +{ + mNext = 0; +} + +PreloadTextures::~PreloadTextures() // just want to watch destruct in debugger... +{ + mNext = 0; +} + +void PreloadTextures::load(const char * name, bool clamp) +{ + AssertFatal(mNext < MaxHandles-1, "PreloadTextures::load()"); + mTextures[mNext++] = TextureHandle(name, MeshTexture, clamp); +} + +static bool cPreload(SimObject * ptr, S32, const char * * argv) +{ + NavigationGraph * navGraph = static_cast(ptr); + navGraph->mTextures.load(argv[2], dAtob(argv[3])); + return true; +} + +//------------------------------------------------------------------------------------- + +static bool cGenDebug(SimObject * ptr, S32 argc, const char * * argv) +{ + NavigationGraph * navGraph = static_cast(ptr); + + if (argc > 2) + { + Point3F magnifyLoc, dbg1, dbg2; + Point3F * p1 = NULL, * p2 = NULL; + F32 magnifyRad = 40; + F32 xyCheck = 0.5, zCheck = 2.0; + + dSscanf(argv[2], "%f %f %f", &magnifyLoc.x, &magnifyLoc.y, &magnifyLoc.z); + + if (argc > 3) + { + magnifyRad = dAtof(argv[3]); + + if (argc > 4) + { + // These aren't documented and are just for internal debugging. They're + // kept ISV since the build process is tryingly slow in DTEST, and they + // don't effect operation, or significant speed/space, in ship verion. + p1 = & dbg1; + dSscanf(argv[4], "%f %f %f", &p1->x, &p1->y, &p1->z); + if (argc > 5) + { + p2 = & dbg2; + dSscanf(argv[5], "%f %f %f", &p2->x, &p2->y, &p2->z); + if (argc > 6) + { + xyCheck = dAtof(argv[6]); + if (argc > 7) + zCheck = dAtof(argv[7]); + } + } + } + } + + SphereF magnifySphere(magnifyLoc, magnifyRad); + navGraph->setGenMagnify(&magnifySphere, p1, p2, xyCheck, zCheck); + return true; + } + + navGraph->setGenMagnify(NULL, NULL, NULL); + Con::printf("This function is for testing the graph generation by focusing"); + Con::printf("on a smaller area (magnify sphere) to speed things up."); + return false; +} + +//------------------------------------------------------------------------------------- + +static const char navGraphTxt[] = "NavigationGraph"; + +#define GraphCmd1(method, cfunc, usage, minparams, maxparams) \ + Con::addCommand(navGraphTxt, method, cfunc, usage, minparams, maxparams) +#define GraphCmd2(function, cfunc, usage, minparams, maxparams) \ + Con::addCommand(function, cfunc, usage, minparams, maxparams) +#define GraphVar1(name, type, svar) \ + Con::addVariable(name, type, &NavigationGraph::##svar) +#define GraphVar2(name, type, varname) \ + Con::addVariable(name, type, &varname) + + +static void addGraphVariables() +{ + GraphVar1("$pref::NavGraph::drawOutdoor", TypeBool, sDrawOutdoorNodes); + GraphVar1("$pref::NavGraph::drawIndoor", TypeBool, sDrawIndoorNodes); + GraphVar1("$pref::NavGraph::drawJetEdges", TypeBool, sDrawJetConnections); + GraphVar1("graphProcessPercent", TypeF32, sProcessPercent); + + // Average MS per call to patch functions- + GraphVar2("patch1Avg", TypeF32, gTrackProfPatch1.average); + GraphVar2("patch2Avg", TypeF32, gTrackProfPatch2.average); + GraphVar2("patch1Total", TypeS32, gTrackProfPatch1.totalMS); + GraphVar2("patch2Total", TypeS32, gTrackProfPatch2.totalMS); + GraphVar2("patch1Last", TypeS32, gTrackProfPatch1.lastMS); + GraphVar2("patch2Last", TypeS32, gTrackProfPatch2.lastMS); + GraphVar2("patch1Calls", TypeS32, gTrackProfPatch1.numCalls); + GraphVar2("patch2Calls", TypeS32, gTrackProfPatch2.numCalls); + + // Number of edges rendered in the AI editor- + GraphVar1("edgeRenderMaxOutdoor", TypeS32, sEdgeRenderMaxOutdoor); + GraphVar1("edgeRenderMaxIndoor", TypeS32, sEdgeRenderMaxIndoor); + + // Nodes in view of this threat should be highlighted- + GraphVar1("showNodeThreat", TypeS32, sShowThreatened); + + // Some control variables for channeling the profiles. eg. focusing on indoors, etc + GraphVar1("ProfileControl0", TypeS32, sProfCtrl0); + GraphVar1("ProfileControl1", TypeS32, sProfCtrl1); + + GraphVar1("Graph::SeedDropOffs", TypeBool, sSeedDropOffs); +} + + +void NavigationGraph::consoleInit() +{ + Parent::consoleInit(); + + addGraphVariables(); + + // Temp- + GraphCmd1("Preload", cPreload, "navGraph.preload(name,clamp);", 4, 4); + + GraphCmd1("makeGraph", cMakeGraph, "navGraph.makeGraph();", 2, 2); + GraphCmd1("setGenMode", cSetGenMode, "navGraph.setGenMode(nav|spawn);", 2, 3); + + GraphCmd1("saveGraph", cSaveGraph, "navGraph.saveGraph();", 2, 2); + GraphCmd1("loadGraph", cLoadGraph, "navGraph.loadGraph();", 2, 2); + + // Misc. graph building functions- + GraphCmd1("setGround", cSetGround, "navGraph.setGround(GroundPlan);", 3, 3); + GraphCmd1("prepLOS", cPrepLOS, "navGraph.prepLOS();", 2, 3); + GraphCmd1("makeLOS", cMakeLOS, "navGraph.makeLOS();", 2, 2); + GraphCmd1("findBridges", cFindBridges, "navGraph.findBridges();", 2, 2); + GraphCmd1("pushBridges", cPushBridges, "navGraph.pushBridges();", 2, 2); + GraphCmd1("cullIslands", cCullIslands, "navGraph.cullIslands();", 2, 2); + GraphCmd1("makeTables", cMakeTables, "navGraph.makeTables();", 2, 2); + GraphCmd1("assemble", cAssemble, "navGraph.assemble();", 2, 2); + + // Tests / diagnostics + GraphCmd1("timeTest", cTimeTest, "navGraph.timeTest(iterations[, doAStar])", 2, 4); + GraphCmd1("genDebug", cGenDebug, "navGraph.genDebug(magnifyLoc[, magnifyRad])", 2, 8); + GraphCmd1("check", cCheckFunction, "navGraph.check(loc);", 3, 3); + GraphCmd1("getLOSPoint", cGetLOSPoint, "navGraph.getLOSPoint(from, to [,N]);", 4, 5); + GraphCmd1("numSpawns", cNumSpawns, "navGraph.numSpawns()", 2, 2); + GraphCmd1("getSpawn", cGetSpawn, "navGraph.getSpawn(which)", 2, 3); + #ifdef DEBUG + GraphCmd1("track", cTrackObject, "navGraph.track(clientId);", 2, 3); + GraphCmd1("hidingPlace", cHidingPlace, "navGraph.hidingPlace(src,avoid,rad,len/ang);", 2, 6); + GraphCmd1("chokePoints", cChokePoints, "navGraph.chokePoints(here,hideD,maxD);", 2, 5); + GraphCmd1("playerCanGo", cPlayerCanGo, "navGraph.playerCanGo(fromA,toB,player);", 5, 5); + #endif + + // Queries + GraphCmd1("randNode", cRandNode, "navGraph.randNode(pt, rad, indoor, outdoor);", 2, 6); + GraphCmd1("numNodes", cNumNodes, "navGraph.numNodes();", 2, 2); + GraphCmd1("nodeLoc", cNodeLoc, "navGraph.nodeLoc(nodeIndex);", 2, 3); + GraphCmd1("randNodeLoc", cRandNodeLoc, "navGraph.randNodeLoc(nodeIndex);", 2, 3); + GraphCmd2("WhereToLook", cWhereToLook, "WhereToLook(playerLoc);", 2, 2); + GraphCmd2("navGraphExists", cNavGraphExists, "navGraphExists();", 1, 1); + GraphCmd1("info", cGraphInfo, "navGraph.info([loc], [rad]);", 2, 7); + GraphCmd1("dumpInfo2File", cDumpInfo2File, "navGraph.dumpInfo2File();", 2, 2); + GraphCmd1("spawnInfo", cSpawnInfo, "navGraph.spawnInfo();", 2, 2); + + // Script should register threats with these- + GraphCmd1("installThreat", cInstallThreat, "navGraph.installThreat(id, team [,R]);", 2, 5); + GraphCmd2("NavDetectForceFields", cDetectForceFields, "NavDetectForceFields();", 1, 1); + + // Utilities for profiling script functions- + GraphCmd2("ProfilePatch1", cProfilePatch1, "ProfilePatch1(func, args...);", 2, 20); + GraphCmd2("ProfilePatch2", cProfilePatch2, "ProfilePatch2(func, args...);", 2, 20); + + + #if DO_MATH_TESTS + GraphCmd2("TestMath", cTestMath, "TestMath(nTests, nIters);", 1, 4); + #endif +} diff --git a/ai/graph.h b/ai/graph.h new file mode 100644 index 0000000..bb73b2b --- /dev/null +++ b/ai/graph.h @@ -0,0 +1,341 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPH_H_ +#define _GRAPH_H_ + +#define _GRAPH_DEBUG_ +#define _GRAPH_WARNINGS_ 0 +#define _GRAPH_PART_ 1 + +#ifndef _GRAPHDATA_H_ +#include "ai/graphData.h" +#endif +#ifndef _GRAPHBASE_H_ +#include "ai/graphBase.h" +#endif +#ifndef _GRAPHPARTITION_H_ +#include "ai/graphPartition.h" +#endif +#ifndef _GRAPHNODES_H_ +#include "ai/graphNodes.h" +#endif +#ifndef _GRAPHTRANSIENT_H_ +#include "ai/graphTransient.h" +#endif +#ifndef _GRAPHLOCATE_H_ +#include "ai/graphLocate.h" +#endif +#ifndef _GRAPHTHREATS_H_ +#include "ai/graphThreats.h" +#endif +#ifndef _GRAPHFORCEFIELD_H_ +#include "ai/graphForceField.h" +#endif +#ifndef _GRAPHJETTING_H_ +#include "ai/graphJetting.h" +#endif +#ifndef _GRAPHSEARCHES_H_ +#include "ai/graphSearches.h" +#endif +#ifndef _GRAPHPATH_H_ +#include "ai/graphPath.h" +#endif +#ifndef _GRAPHGROUNDPLAN_H_ +#include "ai/graphGroundPlan.h" +#endif +#ifndef _TEXTUREPRELOAD_H_ +#include "ai/texturePreload.h" +#endif + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +class NavigationGraph : public SimObject +{ + typedef SimObject Parent; + static S32 sVersion; + friend class NavigationPath; + + public: + static bool sDrawOutdoorNodes, sDrawIndoorNodes, sDrawJetConnections; + static S32 sEdgeRenderMaxOutdoor, sEdgeRenderMaxIndoor; + static S32 sProfCtrl0, sProfCtrl1; + static S32 sTotalEdgeCount; + static F32 sProcessPercent; + static U32 sLoadMemUsed; + static S32 sShowThreatened; + static bool sSeedDropOffs; + + static void consoleInit(); + static void initPersistFields(); + static bool gotOneWeCanUse(); + static bool hasSpawnLocs(); + static void warning(const char* msg); + static F32 fastDistance(const Point3F& p1, const Point3F& p2); + static Point3F hideOnSlope(const Point3F& from, const Point3F& avoid, + F32 rad, F32 deg); + static Point3F hideOnDistance(const Point3F& from, const Point3F& avoid, + F32 rad, F32 hideLen = 10.0); + static Point3F findLOSLocation(const Point3F& from, const Point3F& wantToSee, + F32 minAway, const SphereF& getCloseTo, F32 capDist=1e11); + static S32 getChokePoints(const Point3F& srcPoint, Vector& points, + F32 minHideDist, F32 stopSearchDist=1e9); + static F32 whereToLook(Point3F P); + + protected: + // .NAV or .SPN file data: + SpawnLocations mSpawnList; + EdgeInfoList mEdgeInfoList; + NodeInfoList mNodeInfoList; + GraphVolumeList mNodeVolumes; + TerrainGraphInfo mTerrainInfo; + BridgeDataList mBridgeList; + PathXRefTable mPathXRef; + ChuteHints mChutes; + LOSHashTable mLOSHashTable; + + // Run time node / edge lists: + GraphNodeList mNodeList; + GraphNodeList mNodeGrid; + GraphNodeList mNonTransient; + GraphNodeList mIndoorPtrs; + IndoorNodeList mIndoorNodes; + OutdoorNode * mOutdoorNodes; + GraphNodeList mIslandPtrs; + Vector mIslandSizes; + GraphBSPTree mIndoorTree; + GraphEdge * mEdgePool; + LOSXRefTable mLOSXRef; + LOSTable * mLOSTable; + + S32 mVersion; + S32 mLargestIsland; + S32 mNumOutdoor; + S32 mNumIndoor; + S32 mTransientStart; + static S32 mIncarnation; + S32 mCheckNode; + const S32 mMaxTransients; + bool mValidLOSTable; + bool mValidPathTable; + bool mHaveVolumes; + bool mPushedBridges; + bool mIsSpawnGraph; + bool mDeadlyLiquid; + F32 mSubmergedScale; + F32 mShoreLineScale; + GraphSearch * mTableBuilder; + GraphSearch * mMainSearcher; + GraphSearchLOS * mLOSSearcher; + GraphSearchDist * mDistSearcher; + GraphBoundaries mBoundaries; + SimObjectPtr mTerrainBlock; + Vector mRenderThese; + Vector mRenderBoxes; + GraphEdge mEdgeBuffer[MaxOnDemandEdges]; + GraphEdgePtrs mVisibleEdges; + SearchThreats mThreats; + MonitorForceFields mForceFields; + JetManager mJetManager; + + // Temp buffers returned (as const T &) by misc fetch methods- + Vector mTempNodeBuf; + GraphNodeList mUtilityNodeList1; + GraphNodeList mUtilityNodeList2; + GraphVolume mTempVolumeBuf; + + // Inspect persist variables. + F32 mCullDensity; + ConjoinConfig mConjoin; + GridArea mCustomArea; + + public: + FindGraphNode mFoundNodes[FindGraphNode::HashTableSize]; + + protected: + bool onAdd(); + void onRemove(); + S32 markIslands(); + S32 doFinalFixups(); + void clearLoadData(); + void purgeForSpawn(); + bool initInteriorNodes(const EdgeInfoList&, const NodeInfoList&, S32); + S32 getNodesInArea(GraphNodeList& listOut, GridArea gridArea); + void makeRunTimeNodes(bool useEdgePool = false); + S32 setupOutdoorNodes(const GridArea& area,const Consolidated& cons, + GraphNodeList& grid,GraphNodeList& list); + F32 distancef(const Point3F& p1, const Point3F& p2); + void chokePoints(const Point3F& S, Vector& pts, F32 H, F32 M); + S32 crossingSegs(const GraphNode*, const GraphEdge*, LineSegment* const); + void getInSphere(const SphereF&, GraphNodeList& I, GraphNodeList& O); + void getOutdoorList(GraphNodeList& list); + void newIncarnation(); + S32 hookTransient(TransientNode&); + void unhookTransient(TransientNode&); + S32 floodPartition (U32 seed, bool needBoth, F32 jetRating); + S32 partitionOneArmor(PartitionList& list, F32 jetRating); + + public: + NavigationGraph(); + ~NavigationGraph(); + DECLARE_CONOBJECT(NavigationGraph); + + F32 performTests(S32 numTests, bool aStar = 0); + bool testPlayerCanReach(Point3F A, Point3F B, const F32* ratings); + F32 lineAcrossTerrain(const Point3F& from, Point3F& to); + bool inNodeVolume(const GraphNode* node, const Point3F& point); + NodeProximity getContainment(S32 indoorNodeIndex, const Point3F& point); + bool verticallyInside(S32 indoorIndex, const Point3F& point); + bool closestPointOnVol(GraphNode*, const Point3F& pt, Point3F& soln) const; + bool possibleToJet(const Point3F& from, const Point3F& to, U32 armor=0); + F32 heightAboveFloor(S32 indoorIndex, const Point3F& point); + S32 indoorIndex(const GraphNode* node); + PlaneF getFloorPlane(GraphNode* node); + + bool useVolumeTraverse(const GraphNode* from, const GraphEdge* to); + bool volumeTraverse(const GraphNode* from, const GraphEdge* to, NavigationPath::State&); + S32 checkIndoorSkip(NavigationPath::State & state); + + bool volumeTraverse(const GraphNode*, const GraphEdge*, const Point3F&, U8&, Point3F&); + bool volumeTraverse(const GraphNode*, const GraphEdge*, const NavigationPath::State&); + S32 checkIndoorSkip(const GraphEdgePtrs& edges, const Point3F& cur, S32 from, Vector& visit, Point3F& seek); + bool installThreat(ShapeBase * threat, S32 team, F32 rad); + bool updateThreat(ShapeBase * threat, S32 team, F32 rad); + void patrolForProblems(); + + // Three types of search machines get used- + GraphSearch* getMainSearcher(); + GraphSearchLOS* getLOSSearcher(); + GraphSearchDist* getDistSearcher(); + + // Transient management + void destroyPairOfHooks(S32 idx); + S32 createPairOfHooks(); + bool pushTransientPair(TransientNode& src, TransientNode& dst, + U32 team, const JetManager::ID& jetCaps); + bool canReachLoc(const FindGraphNode& src, const FindGraphNode& dst, + U32 team, const JetManager::ID& jetCaps); + void popTransientPair(TransientNode& src, TransientNode& dst); + + // Graph construction methods called from console, console queries, ... + bool load(Stream&, bool isSpawn = false); + bool save(Stream&); + bool loadGraph(); + bool saveGraph(); + bool assemble(); + bool makeGraph(bool usePool=false); + S32 makeTables(); + const char * findBridges(); + const char * pushBridges(); + S32 scatterSeeds(); + void installSeed(const Point3F& at); + S32 compactIndoorData(const Vector& itrList, S32 numOutdoor); + void setGenMagnify(const SphereF*,const Point3F*,const Point3F*,F32 xy=1,F32 z=2); + bool prepLOSTableWork(Point3F viewLoc); + bool makeLOSTableEntries(); + S32 cullIslands(); + bool setGround(GroundPlan * gp); + S32 randNode(const Point3F& P, F32 R, bool in, bool out); + void detectForceFields() {mForceFields.atMissionStart();} + void checkHashTable(S32 nodeCount) const; + bool sanctionSpawnLoc(GraphNode * node, const Point3F& pt) const; + const Point3F* getRandSpawnLoc(S32 nodeIndex); + const Point3F* getSpawnLoc(S32 nodeIndex); + U32 makeLOSHashTable(); + void makeSpawnList(); + U32 reckonMemory() const; + + // Queries for nodes and edges: for hand edited; and for algorithmic + S32 setNodesAndEdges(const EdgeInfoList&, const NodeInfoList&); + S32 getNodesAndEdges(EdgeInfoList& edges, NodeInfoList& nodes); + S32 setEdgesAndNodes(const EdgeInfoList&, const NodeInfoList&); + S32 setNodeVolumes(const GraphVolumeList& volumes); + S32 setAlgorithmic(const EdgeInfoList& E, const NodeInfoList& N); + S32 setChuteHints(const Vector& chutes); + S32 clearAlgorithmic(); + S32 findJumpableNodes(); + void expandShoreline(U32 wave = 0); + + // Node and edge finding - + const GraphEdgePtrs& getBlockedEdges(GameBase* byThis, U32 objTypeMask); + const GraphNodeList& getVisibleNodes(GraphNode * S, const Point3F& loc, F32 rad); + const GraphNodeList& getVisibleNodes(const Point3F& loc, F32 rad); + const GraphVolume& fetchVolume(const GraphNode* N, bool above); + S32 getNodesInBox(Box3F worldBox, GraphNodeList& listOut, bool justIndoor=false); + S32 crossNearbyVolumes(const Point3F& from, Vector& points); + GraphNode * terrainHookNode(const Point3F& pos, S32& index); + GraphNode * findTerrainNode(const Point3F& loc); + GraphNode * nearbyTerrainNode(const Point3F& loc); + GraphNode * closestNode(const Point3F& loc, F32 * containment = NULL); + bool haveMuzzleLOS(S32 nodeInd1, S32 nodeInd2); + bool terrainHeight(Point3F pos, F32* height, Point3F* normal=0); + F32 getRoamRadius(const Point3F &loc); + void worldToGrid(const Point3F& wPos, Point2I& gPos); + Point3F gridToWorld(const Point2I& gPos); + GridArea getGridRectangle(const Point3F& atPos, S32 gridRad); + bool customArea(GridArea& fetch) const; + GridArea getWorldRect(); + + // render methods + void drawNodeInfo(GraphNode * node); + const char* drawNodeInfo(const Point3F& loc); + void render(Point3F &camPos, bool drawClipped); + void drawNeighbors(GraphNode*, GraphEdgeArray, const Point3F&, F32 w=1e9); + void markNodesInSight(const Point3F& from, F32 in, F32 out, U32 cond); + void clearRenderBoxes() {mRenderBoxes.clear();} + void clearRenderSegs() {mRenderThese.clear();} + void getCrossingPoints(S32 from, Point3F* pts, GraphEdge* to); + void setRenderList(const Vector& segs) {mRenderThese=segs;} + void renderInteriorNodes(Point3F camPos); + void pushRenderSeg(const LineSegment& ls); + void pushRenderBox(Point3F boxLoc, F32 zadd=1.5); + void renderTransientNodes(); + GraphThreatSet showWhichThreats() const; + + // One-liners + S32 incarnation() const {return mIncarnation;} + GraphNode* lookupNode(S32 X) const {return mNodeList[X];} + S32 numIslands() const {return mIslandPtrs.size();} + S32 numOutdoor() const {return mNumOutdoor;} + S32 numNodesAll() const {return mNodeList.size();} + S32 numNodes() const {return mNonTransient.size();} + bool validLOSXref() const {return mValidLOSTable;} + bool validPathXRef() const {return mValidPathTable;} + bool haveVolumes() const {return mHaveVolumes;} + bool haveForceFields() const {return (mForceFields.count() != 0);} + F32 submergedScale() const {return mSubmergedScale;} + F32 shoreLineScale() const {return mShoreLineScale;} + bool deadlyLiquids() const {return mDeadlyLiquid;} + bool haveTerrain() const {return bool(mTerrainBlock);} + S32 numBridges() const {return mBridgeList.numPositiveBridges();} + S32 largestIsland() const {return mLargestIsland;} + S32 numSpawns() const {return mSpawnList.size();} + Point3F getSpawn(S32 i) const {return mSpawnList[i];} + void printSpawnInfo() const {mSpawnList.printInfo();} + void setGenMode(bool spawn) {mIsSpawnGraph = spawn;} + void monitorForceFields() {mForceFields.monitor();} + Vector& tempNodeBuff() {return mTempNodeBuf;} + const LOSTable* getLOSXref() const {return mValidLOSTable ? mLOSTable : NULL;} + const GraphBoundary& getBoundary(S32 i) {return mBoundaries[i];} + GraphThreatSet getThreatSet(S32 T) const {return mThreats.getThreatSet(T);} + void newPartition(GraphSearch* S, U32 T) {mForceFields.informSearchFailed(S, T);} + SearchThreats* threats() {return &mThreats;} + JetManager& jetManager() {return mJetManager;} + ChuteHints& getChutes() {return mChutes;} + + // We manage the data that is shared (for memory optimization) among searchers + GraphSearch::Globals mSharedSearchLists; + + // Temporary - store on graph until we have a special object for it- + PreloadTextures mTextures; +}; + +extern NavigationGraph * gNavGraph; + +#endif diff --git a/ai/graphBSP.h b/ai/graphBSP.h new file mode 100644 index 0000000..1db5371 --- /dev/null +++ b/ai/graphBSP.h @@ -0,0 +1,220 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHBSP_H_ +#define _GRAPHBSP_H_ + +template class AxisAlignedBSP +{ + public: + enum {Lower, Upper}; + + class BSPNode { + public: + U16 mAxis; + U16 mCount; + union { + U16 mBranch[2]; + T * mLeaf; + } X; + F32 mDivide; + BSPNode() + { + mAxis = 0; + mCount = 0; + mDivide = 0.0f; + X.mBranch[Lower] = X.mBranch[Upper] = 0; + X.mLeaf = NULL; + } + }; + + typedef Vector BSPPool; + + protected: + static S32 sSortAxis; + BSPPool mTree; + VectorPtr mList; + + protected: + static S32 QSORT_CALLBACK compareAxis(const void* , const void* ); + void sortOnAxis(VectorPtr& list, S32 axis); + void traverse(S32 ind, const Box3F& box, VectorPtr* result) const; + U16 partition(S32 start, S32 N); + + public: + void makeTree(const VectorPtr& listIn); + S32 getIntersecting(VectorPtr& listOut, Box3F box) const; + void clear(); +}; + +//------------------------------------------------------------------------------------- + +template S32 AxisAlignedBSP::sSortAxis; + +template +S32 QSORT_CALLBACK AxisAlignedBSP::compareAxis(const void* a,const void* b) +{ + const F32 * locA = (*(T**)a)->location(); + const F32 * locB = (*(T**)b)->location(); + F32 A = locA[sSortAxis], B = locB[sSortAxis]; + return (A < B ? -1 : (A > B ? 1 : 0)); +} + +template void AxisAlignedBSP::sortOnAxis(VectorPtr& list, S32 axis) +{ + sSortAxis = axis; + dQsort((void* )(list.address()), list.size(), sizeof(T* ), compareAxis); +} + +//------------------------------------------------------------------------------------- + +// Main tree building routine. Divides up the space at each level until done. +template U16 AxisAlignedBSP::partition(S32 start, S32 N) +{ + S32 place = mTree.size(); + BSPNode assembleNode; + + if (N < 2) + { + AssertFatal(N == 1, "Bad call to axis-aligned BSP partition"); + assembleNode.mCount = 1; + assembleNode.X.mLeaf = mList[start]; + mTree.push_back(assembleNode); + } + else + { + S32 mid1 = (N >> 1); + S32 mid0 = mid1 - 1; + F32 bestSeparation = -1; + VectorPtr divisions[3]; + + // Try all three divisions. We take the one whose halves are furthest apart + // at the divide. + for (S32 axis = 0; axis < 3; axis++) + { + divisions[axis].setSize(N); + dMemcpy(&(divisions[axis][0]), &mList[start], N * sizeof(T*)); + sortOnAxis(divisions[axis], axis); + + const F32 * endOfFirst = divisions[axis][mid0]->location(); + const F32 * startOf2nd = divisions[axis][mid1]->location(); + + F32 low = endOfFirst[axis]; + F32 high = startOf2nd[axis]; + F32 separation = (high - low); + + AssertFatal(separation >= 0, "Bad sort in axis-aligned BSP construction"); + + if (separation > bestSeparation) + { + bestSeparation = separation; + assembleNode.mCount = N; + assembleNode.mAxis = axis; + assembleNode.mDivide = low + (separation * 0.5f); + } + } + + // Copy over the sorted elements along the axis we're dividing- + dMemcpy(&mList[start], &(divisions[assembleNode.mAxis][0]), N * sizeof(T*)); + + // Install the node proper, and call down to further partition. + mTree.push_back(assembleNode); + + // NOTE!! This code doesn't work in release build if we haven't pre-reserved the + // !!!!!! tree. It looks like a compiler bug to me, not 100% sure though. + mTree[place].X.mBranch[Lower] = partition(start, mid1); + mTree[place].X.mBranch[Upper] = partition(start + mid1, N - mid1); + } + + // Return where we're at in the tree. + return place; +} + +//------------------------------------------------------------------------------------- +// Make a tree whose leaves are the nodes passed in. + +template void AxisAlignedBSP::makeTree(const VectorPtr& listIn) +{ + mList = listIn; + mTree.clear(); + + AssertFatal(mList.size() < 32000, "AxisAlignedBSP::makeTree() - too many nodes"); + + if (mList.size()) { + mTree.reserve(mList.size() * 3); + partition(0, mList.size()); + mTree.compact(); + } + + mList.clear(); + mList.compact(); +} + +//------------------------------------------------------------------------------------- + +// We want our check to be inclusive of both ends (Box3F method isn't above) +inline bool boxContainsPt(const Box3F& B, const Point3F& P) +{ + if (P.x >= B.min.x && P.x <= B.max.x) + if (P.y >= B.min.y && P.y <= B.max.y) + return (P.z >= B.min.z && P.z <= B.max.z); + return false; +} + +//------------------------------------------------------------------------------------- + + +template void AxisAlignedBSP::traverse(S32 ind, const Box3F& box, + VectorPtr* result) const +{ + const BSPNode & node = mTree[ind]; + + if (node.mCount == 1) { // At node- + if (boxContainsPt(box, node.X.mLeaf->location())) + result->push_back(node.X.mLeaf); + } + else { + AssertFatal(node.mCount > 1, "AxisAlignedBSP::traverse()"); + if (node.mDivide > ((const F32*)(box.max))[node.mAxis]) + traverse(node.X.mBranch[Lower], box, result); + else if (node.mDivide < ((const F32*)(box.min))[node.mAxis]) + traverse(node.X.mBranch[Upper], box, result); + else { + Box3F splitUpper(box); + Box3F splitLower(box); + ((F32*)(splitUpper.min))[node.mAxis] = node.mDivide; + ((F32*)(splitLower.max))[node.mAxis] = node.mDivide; + traverse(node.X.mBranch[Lower], splitLower, result); + traverse(node.X.mBranch[Upper], splitUpper, result); + } + } +} + +//------------------------------------------------------------------------------------- +// Push all nodes intersecting the box onto the user-supplied list. We don't clear +// list because we'll probably have two trees - for small nodes and large nodes. + +template +S32 AxisAlignedBSP::getIntersecting(VectorPtr& result, Box3F box) const +{ + if (mTree.size()) + traverse(0, box, &result); + return result.size(); +} + +//------------------------------------------------------------------------------------- + +template void AxisAlignedBSP::clear() +{ + mTree.clear(); mTree.compact(); + mList.clear(); mList.compact(); +} + + +//------------------------------------------------------------------------------------- + +#endif diff --git a/ai/graphBase.cc b/ai/graphBase.cc new file mode 100644 index 0000000..3f4225b --- /dev/null +++ b/ai/graphBase.cc @@ -0,0 +1,388 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- +// GraphEdge + +GraphEdge::GraphEdge() +{ + mDest = -1; // Destination node index + mBorder = -1; // Border segment on interiors + mDist = 1.0; // 3D dist, though jet edges are adjusted a little so ratings work + mJet = 0; // Jetting connection + mHopOver = 0; // For jetting, amount to hop up and over + + // Factors that influence edge scaling- + // + mTeam = 0; // An edge that only this team can pass - i.e. force fields. + mJump = 0; // On jetting edges - is it flat enough to jump? + mLateral = 0; // Crude lateral distance - needed to know if we can jet down. + mDown = 0; // Down side of a jet? + mSteep = 0; // Can't walk up + setInverse(1.0); +} + +const char * GraphEdge::problems() const +{ + if (mDist < 0) + return "Dijkstra() forbids edge with negative distance"; + if (getInverse() < 0) + return "Edge with negative scale"; + return NULL; +} + +// Given the jet rating, can this connection be traversed? If down, must satisfy +// lateral only. Used by searcher, and by partition builder. In the latter case it +// must (on 1st of its 2 passes) treat down same as up to find stranded partitions. +bool GraphEdge::canJet(const F32* ratings, bool both) const +{ + if (!mDown || both) + { + F32 rating = ratings[mJump]; + return (rating >= mDist); + } + else + { + F32 rating = ratings[mJump]; + return rating > F32(mLateral); + } +} + +// This table gives our inverse speed values, so we can keep it in a few bits. Should +// still give the desired behavior, though a little coarsely at times. Perfect paths +// in terms of travel times is not something that people seem to percieve that much... +const F32 GraphEdge::csInvSpdTab[InvTabSz + 1] = +{ + 0.0, 0.1, 0.2, 0.3, 0.4, + 0.6, 0.8, 1.0, 1.2, 1.4, + + 1.7, 2.0, 2.6, 3.5, 4.6, + 5.7, 6.8, 8.0, 9.2, 10.4, + + 12.0, 14.1, 17.0, 20.0, 24.0, + 30.0, 37.0, 45.0, 54.0, 64.0, + + 80.0, 100.0, 125.0, 156.0, 200.0, + 400.0, 800.0, 1600.0, 3200.0, 6400.0, + + 2.56e4, 1.02e5, 4.1e5, 1.6e6, 6.5e6, + 2.6e7, 1.05e8, 4.19e8, 1.68e9, 9.9e10, + + 1e11, 1e12, 1e13, 1e14, 1e15, + 1e16, 1e17, 1e18, 1e19, 1e20, + 1e21, 1e22, 1e23, 1e24, + + // Sentinal value - + 1e40, +}; + +// Find the 6-bit number using a binary search... +void GraphEdge::setInverse(F32 is) +{ + S32 lo = 0; + S32 hi = InvTabSz; + + AssertFatal(is < csInvSpdTab[hi] && is > csInvSpdTab[lo], "Bad edge inverse speed"); + + // Find the largest value we're greater than in the table- + for (S32 i = InvSpdBits; i > 0; i--) + { + S32 mid = (hi + lo >> 1); + if (is >= csInvSpdTab[mid]) + lo = mid; + else + hi = mid; + } + + mInverse = lo; +} + +//------------------------------------------------------------------------------------- +// GraphNode Methods, Default Virtuals + +GraphNode::GraphNode() +{ + mIsland = -1; + mOnPath = 0; + mAvoidUntil = 0; + mLoc.set(-1,-1,-1); + // mOwnedPtr = NULL; + // mOwnedCnt = 0; + mThreats = 0; +} + +// Derived classes must supply fetcher which takes a buffer to build in. This method +// patches to that, and should serve fine if pointers to these aren't kept around. +const Point3F& GraphNode::location() const +{ + static Point3F buff[8]; + static U8 ind = 0; + return fetchLoc(buff[ind++ & 7]); +} + +// Outdoor nodes use level (and they start at -1), others don't. Though this is also +// used by path randomization to determine if it's reasonable to avoid the node. +S32 GraphNode::getLevel() const +{ + return -2; +} + +Point3F GraphNode::getRenderPos() const +{ + Point3F buff, adjusted; + adjusted = fetchLoc(buff); + adjusted.z += 0.3; + return adjusted; +} + +// Set if not already set (shouldn't be set already, caller asserts) +void GraphNode::setIsland(S32 num) +{ + AssertFatal(!mFlags.test(GotIsland), "GraphNode::setIsland()"); + mFlags.set(GotIsland); + mIsland = num; +} + +// Ideally this ratio would accurately reflect travel speeds along the slopes. +// Here we use +// xy / total If we're going down. Hence XY is net path distance. +// total / xy If we're going up. +static F32 getBaseEdgeScale(const Point3F& src, Point3F dst) +{ + F32 totalDist = (dst -= src).len(); + bool goingUp = (dst.z > 0); + dst.z = 0; + F32 xyDist = dst.len(); + F32 ratio = 1.0; + + if (xyDist > 0.01 && totalDist > 0.01) + { + if (goingUp) + { + ratio = (totalDist / xyDist); + if (ratio < 1.2) + ratio = 1.0; + } + else + ratio = (xyDist / totalDist); + + // Want to do something between square root and straight value, so we'll + // raise it to the 3/4 power here, which will bring it to 1 from either side. + ratio = mSqrt(mSqrt(ratio) * ratio); + } + + return ratio; +} + +F32 GraphNode::edgeScale(const GraphNode * dest) const +{ + // F32 scale = 1.0; + F32 scale = getBaseEdgeScale(location(), dest->location()); + + if (submerged()) + scale *= gNavGraph->submergedScale(); + else if (shoreline()) + scale *= gNavGraph->shoreLineScale(); + + if (dest->submerged()) + scale *= gNavGraph->submergedScale(); + else if (dest->shoreline()) + scale *= gNavGraph->shoreLineScale(); + + // Searcher reserves REALLY large numbers (10^20th about) to signal search + // failure, so make sure we don't inadvertently interfere with any of that. + scale = getMin(scale, 1000000000.0f); + + return scale; +} + +static GraphEdge sEdgeBuff[MaxOnDemandEdges]; + +bool GraphNode::neighbors(const GraphNode* other) const +{ + GraphEdgeArray edgeList = other->getEdges(sEdgeBuff); + S32 indexOfThis = getIndex(); + + while (GraphEdge * edge = edgeList++) + if (edge->mDest == indexOfThis) + return true; + + return false; +} + +GraphEdge * GraphNode::getEdgeTo(S32 to) const +{ + GraphEdgeArray edgeList = getEdges(sEdgeBuff); + + while (GraphEdge * edge = edgeList++) + if (edge->mDest == to) + return edge; + + return NULL; +} + +// Virtual that is only used on non-grid nodes +const GraphEdge * GraphNode::getEdgePtr() const +{ + return 0; +} + +// This is only called on nodes known to be terrain. +F32 GraphNode::terrHeight() const +{ + return 1e17; +} + +// Initial use of this is for weighted random selection of nodes for spawn points. +F32 GraphNode::radius() const +{ + return 2.0; +} + +// Not in yet, but we'll use this instead of radius for weighted random spawn points +F32 GraphNode::area() const +{ + return 0.01; +} + +// Used to know thinness of node. Spawn system avoids, as do smoothing edges. +F32 GraphNode::minDim() const +{ + return 0.01; +} + +const Point3F& GraphNode::getNormal() const +{ + static const Point3F sStraightUp(0,0,1); + return sStraightUp; +} + +// Used for finding how much a node contains a point. More negative => point is +// more inside. +NodeProximity GraphNode::containment(const Point3F& point) const +{ + NodeProximity nodeProx; + nodeProx.mLateral = (point - location()).len() - radius(); + return nodeProx; +} + +Point3F GraphNode::randomLoc() const +{ + return location(); +} + +// Nodes which have volumes associated. Added to allow the transient nodes to +// respond with the proper volume that they may be associated with. +S32 GraphNode::volumeIndex() const +{ + return getIndex(); +} + +// Set the avoidance if it isn't already. The inmportant flag will override though. +void GraphNode::setAvoid(U32 duration, bool isImportant) +{ + U32 curTime = Sim::getCurrentTime(); + + if (curTime > mAvoidUntil || isImportant) + { + mAvoidUntil = curTime + duration; + mFlags.set(StuckAvoid, isImportant); + + // On avoidance, also mark neighbors- + if (isImportant) + { + GraphEdgeArray edgeList = getEdges(sEdgeBuff); + while (GraphEdge * edge = edgeList++) + { + GraphNode * node = gNavGraph->lookupNode(edge->mDest); + node->mAvoidUntil = mAvoidUntil; + node->mFlags.clear(StuckAvoid); // slight avoid here. + } + } + } +} + +//------------------------------------------------------------------------------------- +// Default RegularNode Virtuals + +RegularNode::RegularNode() +{ + mIndex = -1; + mNormal.set(0,0,1); +} + +GraphEdgeArray RegularNode::getEdges(GraphEdge*) const +{ + return GraphEdgeArray(mEdges.size(), mEdges.address()); +} + +GraphEdge & RegularNode::pushEdge(GraphNode * node) +{ + GraphEdge edge; + edge.mDest = node->getIndex(); + mEdges.push_back(edge); + return mEdges.last(); +} + +const Point3F& RegularNode::getNormal() const +{ + return mNormal; +} + +//------------------------------------------------------------------------------------- + +void GraphNodeList::setFlags(U32 bits) +{ + for(const_iterator it = begin(); it != end(); it++) + if(*it) + (*it)->mFlags.set(bits); +} + +void GraphNodeList::clearFlags(U32 bits) +{ + for(const_iterator it = begin(); it != end(); it++) + if(*it) + (*it)->mFlags.clear(bits); +} + +S32 GraphNodeList::searchSphere(const SphereF& sphere, const GraphNodeList& listIn) +{ + clear(); + F32 radiusSquared = (sphere.radius * sphere.radius); + for(const_iterator it = listIn.begin(); it != listIn.end(); it++) + if(*it) + if((sphere.center - (*it)->location()).lenSquared() < radiusSquared) + push_back(*it); + return size(); +} + +GraphNode * GraphNodeList::closest(const Point3F& loc, bool) +{ + GraphNode * bestNode = NULL; + F32 bestRadSquared = 1e12, dSqrd; + for (const_iterator it = begin(); it != end(); it++) + if (*it) + if ((dSqrd = (loc - (*it)->location()).lenSquared()) < bestRadSquared) + { + bestRadSquared = dSqrd; + bestNode = *it; + } + return bestNode; +} + +// Add the node pointer if it's not already in the list. +bool GraphNodeList::addUnique(GraphNode * node) +{ + for (iterator n = begin(); n != end(); n++) + if (* n == node) + return false; + push_back(node); + return true; +} + diff --git a/ai/graphBase.h b/ai/graphBase.h new file mode 100644 index 0000000..a8507e1 --- /dev/null +++ b/ai/graphBase.h @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHBASE_H_ +#define _GRAPHBASE_H_ + +#ifndef _OVECTOR_H_ +#include "ai/oVector.h" +#endif + +class GraphNodeList; +class NavigationGraph; +typedef S32 GraphQIndex; +typedef U64 GraphThreatSet; + +//------------------------------------------------------------------------------------- +// Graph Edges - See graphNodeBase.cc for methods, comments. + +class GraphEdge +{ + enum {InvSpdBits = 6, InvTabSz = 1 << InvSpdBits}; + static const F32 csInvSpdTab[InvTabSz + 1]; + + U8 mSteep : 1; + U8 mInverse : InvSpdBits; + U8 mOnPath : 1; + U8 mJet : 1; // These are only private since they're not + U8 mDown : 1; // straight regular types. Otherwise this + U8 mJump : 1; // class is kept pretty open for the sake of + U8 mTeam : 5; // speed in the debug build (since edges are + U8 mLateral; // used a lot inlines slow things down + U8 mHopOver; // by a noticeable amount). + + public: + F32 mDist; + S16 mDest; + S16 mBorder; + + GraphEdge(); + bool empty() const {return mDest < 0;} + bool isBorder() const {return mBorder >= 0;} + bool isJetting() const {return mJet;} + bool isDown() const {return mDown;} + bool isJump() const {return mJump;} + bool isSteep() const {return mSteep;} + U8 getTeam() const {return mTeam;} + U8 getLateral() const {return mLateral;} + F32 getHop() const {return mapU8ToJetHop(mHopOver);} + F32 getInverse() const {return csInvSpdTab[mInverse];} + bool hasHop() const {return mHopOver!=0;} + void setJetting() {mJet = 1;} + void setDown(bool b) {mDown = b;} + void setJump(bool b) {mJump = b;} + void setTeam(U8 team) {mTeam = team;} + void setLateral(U8 amt) {mLateral = amt;} + void setSteep(bool b) {mSteep = b;} + void setImpossible() {setInverse(1e37);} + void setHop(F32 amt) {mHopOver = mapJetHopToU8(amt);} + void setHop(U8 persistAmt) {mHopOver = persistAmt;} + F32 getTime() const {return (mDist * getInverse());} + void copyInverse(const GraphEdge* e) {mInverse = e->mInverse;} + bool canJet(const F32* ratings, bool both=false) const; + void setInverse(F32 inv); + const char* problems() const; +}; + +typedef OVector GraphEdgeList; +typedef Vector GraphEdgePtrs; + +class GraphEdgeArray +{ + S32 count; + GraphEdge * edges; + public: + GraphEdgeArray() {count = 0; edges = NULL;} + GraphEdgeArray(S32 c, GraphEdge* e) {count = c; edges = e;} + GraphEdge * operator++(int) {return (count ? (count--, edges++) : 0);} + S32 numEdges() const {return count;} + void incCount() {count++;} +}; + +struct NodeProximity +{ + F32 mLateral, mHeight, mAboveC; + void makeBad() {mLateral=1e13;} + void makeGood() {mAboveC = mLateral = -1e13;} + operator F32&() {return mLateral;} + NodeProximity() {makeBad();} + bool inside() const {return (mLateral <= 0 && mAboveC <= 0);} + bool insideZ() const {return (mAboveC <= 0);} + bool possible() const; +}; + +//------------------------------------------------------------------------------------- +// Virtual Base Class For Run Time Graph Nodes + +#define GraphMaxOnPath 31 + +class GraphNode // graphNodeBase.cc +{ + friend class NavigationGraph; + friend class GraphNodeList; + + protected: + enum{ + NodeSpecific = 0xFF, + Grid = BIT(8), + Outdoor = BIT(9), + Transient = BIT(10), + ExtendedGrid = BIT(11), + Indoor = BIT(12), + BelowPortal = BIT(13), + GotIsland = BIT(14), + Transient0 = BIT(15), + Transient1 = (Transient0<<1), + HaveTransient = (Transient0|Transient1), + PotentialSeed = BIT(19), + UsefulSeed = BIT(20), + Flat = BIT(21), + Algorithmic = BIT(22), + StuckAvoid = BIT(23), + Inventory = BIT(24), + Render0 = BIT(25), + Shadowed = BIT(26), + // Reserve three contiguous bits for shoreline, which is wider for lava. + LiquidPos = 27, + LiquidZone = (0x7 << LiquidPos), + Submerged = BIT(LiquidPos + 0), + ShoreLine = BIT(LiquidPos + 1), + LavaBuffer = BIT(LiquidPos + 2), + }; + BitSet32 mFlags; + S16 mIsland; + S16 mIndex; + S8 mOnPath; + S8 mLevel; + // U16 mOwnedCnt; + // GraphEdge * mOwnedPtr; + U32 mAvoidUntil; + GraphThreatSet mThreats; + GraphEdgeList mEdges; + Point3F mLoc; + + protected: + GraphEdge * pushTransientEdge(S32 dest); + void popTransientEdge(); + + public: + GraphNode(); + + // One-liners + void set(U32 bits) {mFlags.set(bits);} + void clear(U32 bits) {mFlags.clear(bits);} + bool test(U32 bits) const {return mFlags.test(bits);} + bool grid() const {return test(Grid);} + bool outdoor() const {return test(Outdoor);} + bool indoor() const {return test(Indoor);} + bool belowPortal() const {return test(BelowPortal);} + bool shoreline() const {return test(ShoreLine);} + bool shadowed() const {return test(Shadowed);} + bool submerged() const {return test(Submerged);} + bool stuckAvoid() const {return test(StuckAvoid);} + bool transient() const {return test(Transient);} + bool gotIsland() const {return test(GotIsland);} + bool inventory() const {return test(Inventory);} + bool render0() const {return test(Render0);} + bool algorithmic() const {return test(Algorithmic);} + bool flat() const {return test(Flat);} + bool canHaveBorders() const {return test(Indoor|Transient);} + bool liquidZone() const {return test(LiquidZone);} + bool potentialSeed() const {return test(PotentialSeed);} + bool usefulSeed() const {return test(UsefulSeed);} + bool uselessSeed() const {return potentialSeed() && !usefulSeed();} + S32 island() const {return mIsland;} + U32 onPath() const {return mOnPath;} + U32 avoidUntil() const {return mAvoidUntil;} + void setOnPath() {mOnPath = GraphMaxOnPath;} + void decOnPath() {if(mOnPath) mOnPath--;} + void setUsefulSeed() {set(UsefulSeed);} + GraphThreatSet& threats() {return mThreats;} + + // Pure virtuals. + virtual const Point3F& fetchLoc(Point3F& buff) const = 0; + virtual GraphEdgeArray getEdges(GraphEdge* buff) const = 0; + virtual S32 getIndex() const = 0; + + // Virtuals- + virtual const Point3F& location() const; + virtual const Point3F& getNormal() const; + virtual const GraphEdge* getEdgePtr() const; + virtual S32 getLevel() const; + virtual S32 volumeIndex() const; + virtual F32 edgeScale(const GraphNode* to) const; + virtual NodeProximity containment(const Point3F& loc) const; + virtual Point3F getRenderPos() const; + virtual Point3F randomLoc() const; + virtual F32 terrHeight() const; + virtual F32 radius() const; + virtual F32 minDim() const; + virtual F32 area() const; + + // Other: + void setIsland(S32 islandNum); + void setAvoid(U32 duration, bool important=false); + bool neighbors(const GraphNode*) const; + GraphEdge* getEdgeTo(S32 to) const; + GraphEdge* getEdgeTo(const GraphNode* n) const {return getEdgeTo(n->getIndex());} +}; + +//------------------------------------------------------------------------------------- + +class GraphNodeList : public VectorPtr +{ + public: + void setFlags(U32 bitset); + void clearFlags(U32 bitset); + S32 searchSphere(const SphereF& sphere, const GraphNodeList& listIn); + GraphNode* closest(const Point3F& loc, bool los=false); + bool addUnique(GraphNode * node); +}; + +typedef AxisAlignedBSP GraphBSPTree; + +#endif diff --git a/ai/graphBridge.cc b/ai/graphBridge.cc new file mode 100644 index 0000000..531e1f3 --- /dev/null +++ b/ai/graphBridge.cc @@ -0,0 +1,974 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" +#include "ai/graphLOS.h" +#include "ai/graphBridge.h" + +//------------------------------------------------------------------------------------- + +// Controls generation- +static bool sSpawnGraph = false; + +// Numbers that are different between the two types of graphs. Most jet connections +// on NAV generation use the smaller lateral value, but we special case for large +// terrain-to-terrain hops that are passed through. cf. FireStorm. +#define NAVLateralMax 60.0 +#define IslandHopLateralMax 120.0 +#define SPNLateralMax 100.0 +static const Point3F sBoxOffNAV(IslandHopLateralMax, IslandHopLateralMax, 10000.0); +static const Point3F sBoxOffSPN(SPNLateralMax, SPNLateralMax, 10000.0); + +//------------------------------------------------------------------------------------- + +static const U32 sLOSMask = InteriorObjectType + | StaticShapeObjectType + | StaticObjectType + | WaterObjectType + | TerrainObjectType; + +static Point3F * sBreakPt1 = 0, * sBreakPt2 = 0; +static SphereF * sMagSphere; +static F32 sBreakRangeXY = 0.5, sBreakRangeZ = 2.0; +static const S32 sGraphFanCount = 10; // ==> 1-11-1, reduced, should be fine. +static const F32 sGraphFanWidth = 1.0; +static const F32 sWideFanRaise = 1.3; + +// Clear distance assures they can get over a ledge, land Dist assures there's purchase. +// Second clear dist is for case when one node is a portal (i.e. chutes) +static const F32 sClearDists[2] = {1.3, 1.0}; +static const F32 sLandDists[2] = {0.4, 0.2}; +static const F32 sJumpAngDot = 0.707; + +// NOT SURE ABOUT THIS # Sometimes we want more - maybe allow other checks to insure +// that things work when it's small (clear dist, etc.) There are some small ledge +// hops which are needed - but hop over code will probably assure... +static const F32 sMinXY = 2.0; +static const F32 sMinXYSqrd = (sMinXY * sMinXY); + +static const Point3F sMinAboveVec(0,0,2.0); + +#define SlightlyOffFloor 0.157 +#define MaxHopOverXY 37.0 +// lhpatch- Make this # go on graph (was barely too small in one map at 17) +#define MaxWalkableXY 20.0 +#define MinLateralThresh 12.0 + +//------------------------------------------------------------------------------------- + +#define SquareOf(x) ((x)*(x)) + +static bool bridgeable(const Point3F& from, const Point3F& to, bool bothOutdoor) +{ + F32 lateralThresh; + + if (sSpawnGraph) + { + lateralThresh = SPNLateralMax; + } + else + { + if (bothOutdoor) + lateralThresh = IslandHopLateralMax; + else + lateralThresh = NAVLateralMax; + + // Pull down the lateral thresh for tall hops. Basically subtract off of it for + // every meter of Z, but keep a minimum amount, which is necessary to assure + // floating bases do get bridged (if only the downward hop is doable). + if ( (lateralThresh -= mFabs(from.z - to.z)) < MinLateralThresh ) + lateralThresh = MinLateralThresh; + } + + Point2F lateral(from.x - to.x, from.y - to.y); + return (lateral.lenSquared() < SquareOf(lateralThresh)); +} + +//------------------------------------------------------------------------------------- + +// Get the fan vector in passed ref, and return number of increments. horzVec +// is a horizontal vector guaranteed to have length. +static S32 calcFanVector(const VectorF& horzVec, VectorF& fanIncOut) +{ + fanIncOut.set(-horzVec.y, horzVec.x, 0); + fanIncOut.normalize(sGraphFanWidth / F32(sGraphFanCount)); + return sGraphFanCount; +} + +//------------------------------------------------------------------------------------- +// Some code for isolating cases in the debugger. Mainly for focusing on a couple of +// nodes to see why they're not getting bridged. See navGraph.genDebug() in graph.cc + +static bool doBreak() {return true;} + +static bool checkEnds(const Point3F& n1, const Point3F& n2) +{ + if (mFabs(n1.x - sBreakPt1->x) < sBreakRangeXY) + if (mFabs(n2.x - sBreakPt2->x) < sBreakRangeXY) + if (mFabs(n2.y - sBreakPt2->y) < sBreakRangeXY) + if (mFabs(n1.y - sBreakPt1->y) < sBreakRangeXY) + if (mFabs(n1.z - sBreakPt1->z) < sBreakRangeZ) + if (mFabs(n2.z - sBreakPt2->z) < sBreakRangeZ) + return doBreak(); + + return false; +} + +//------------------------------------------------------------------------------------- + +GraphBridge::GraphBridge(const GraphNodeList& mainList, S32 islands, + BridgeDataList& listOut) + : mMainList(mainList), + mIslands(islands), + mBridgesOut(listOut) +{ + heedSeeds(false); +} + +//------------------------------------------------------------------------------------- + +// This is a bit indirect... but here's where we control which brdiges to attempt on +// the two passes. On the first, all seeds are ignored. On the second we only bridge +// where at least one useful seed is present, and NO useless seed is involved. +bool GraphBridge::heedThese(const GraphNode* from, const GraphNode* to) +{ + if (mHeedSeeds) + if (from->usefulSeed() || to->usefulSeed()) + return !(from->uselessSeed() || to->uselessSeed()); + else + return false; + else + return !(from->potentialSeed() || to->potentialSeed()); +} + +void GraphBridge::heedSeeds(bool b) +{ + mHeedSeeds = b; +} + +//------------------------------------------------------------------------------------- + +// Try straight distance for the ratios. After we added good scale values on the +// edges, our bridger makes too many edges since the ratios are off. Until this is +// addressed (if indeed it really needs it), let's use straight distance on edges. +F32 GraphBridge::getEdgeTime(const GraphEdge * edge) +{ + return edge->mDist; +} + +//------------------------------------------------------------------------------------- + +// Here's where we do all the work - try to see if a bridge exists. +// +bool GraphBridge::tryToBridge(const GraphNode* from, const GraphNode* to, F32 heuristic) +{ + // This is actually where the separate passes (normal, then seeding) is controlled- + if (!heedThese(from, to)) + return false; + + // Check for in, or near, liquids. + if (from->liquidZone() || to->liquidZone()) + return false; + + S32 fromIndex = from->getIndex(); + S32 toIndex = to->getIndex(); + + Point3F fromLoc = from->location(); + Point3F toLoc = to->location(); + + bool oneIsPortal = (from->belowPortal() ^ to->belowPortal()); + + // Debug stopping points. + if (sBreakPt1 && sBreakPt2) + { + if (checkEnds(fromLoc, toLoc) || checkEnds(toLoc, fromLoc)) + if (oneIsPortal) + doBreak(); + } + + // NOTE: we can make the OffFloor amount smaller if outdoor node locations are + // raised up to always be above terrain. (U16 rounding I think) + fromLoc.z += SlightlyOffFloor; + toLoc.z += SlightlyOffFloor; + + bool bothOutside = (from->outdoor() && to->outdoor()); + + if (from->neighbors(to)) + return false; + else + { + // Prefer to bridge going UP to insure that the ratio check gets handled (larger + // travel times going up). + if (fromLoc.z > toLoc.z) + return false; + else if (fromLoc.z == toLoc.z && fromIndex > toIndex) + return false; + } + + // Create low, high, mid (point above low, but on level with high). + Point3F mid, low, high; + low = fromLoc; + high = toLoc; + (mid = low).z = high.z; + F32 zDist = (high.z - low.z); + + // Get 2D dist + Point2F xyVec(toLoc.x - fromLoc.x, toLoc.y - fromLoc.y); + F32 xyDistSq = xyVec.lenSquared(); + + // Handles all the LOS work + Loser los(sLOSMask, true); + + if ((xyDistSq > 0.0001) && bridgeable(fromLoc, toLoc, bothOutside)) + { + Point3F aboveM, aboveH, clearPt, landRoom, across; + bool isChute = false, chuteTop = false; + bool walkable = false, gapWalk = false; + bool canCheckJet = true, direct = false; + bool makeBridge = false; + F32 wall; + + // See if we shouldn't look for jetting. + F32 xyDist = mSqrt(xyDistSq); + if (from->inventory() || to->inventory() || xyDistSq < sMinXYSqrd) + canCheckJet = false; + else + { + if (from->getNormal().z < sJumpAngDot || to->getNormal().z < sJumpAngDot) + canCheckJet = false; + } + + bool checkLandRoom = (zDist > 1.0 && to->indoor()); + + Point3F aboveVec = sMinAboveVec; + + aboveM = mid + aboveVec; + aboveH = high + aboveVec; + across = (high - mid); + across.normalize(); + clearPt = mid + (sClearDists[oneIsPortal] * across); + landRoom = high - (sLandDists[oneIsPortal] * across); + + // Move back on the up checks so the guy has room behind him. We have to + // raise up the low a little bit to account for slope, but of course + // we can't raise more than height. + if (canCheckJet) + { + // There are two types of chute situations here, one for if we have this + // connection goes to the top of the chute, the other for middle ones. + // For the top we do a slope check off the collision up there and are lax + // about other LOS checks. For the middle we require a couple more. + if (ChuteHints::Info info = gNavGraph->getChutes().info(low, mid)) + { + isChute = true; + if (info == ChuteHints::ChuteTop) + chuteTop = true; + } + + // Might still want to do a little of this on chutes... ? + if (!isChute) + { + VectorF backUpVec = (across * 0.8); + low -= backUpVec; + low.z += getMin(2.0f, zDist); + mid -= backUpVec; + aboveM -= backUpVec; + } + } + + bool walkOff = los.haveLOS(mid, high); + bool hopOver = false; + if (!walkOff && xyDist < MaxHopOverXY) + { + hopOver = los.findHopLine(mid, high, 2, wall); + if (hopOver) + { + mid.z += wall; + high.z += wall; + aboveM.z += wall; + aboveH.z += wall; + } + } + + if (walkOff || hopOver) + if (los.haveLOS(high, aboveH)) + if (chuteTop || los.haveLOS(low, aboveM)) + if (chuteTop || los.haveLOS(aboveM, aboveH)) + { + // Don't do walk checks between two outdoor nodes- + if (!isChute && (!from->outdoor() || !to->outdoor())) + { + // do gap walk for special case of small zdiff or direct LOS + direct = los.haveLOS(low, high); + if (direct) + gapWalk = los.walkOverGaps(low, high, 0.8); + else if (zDist < gNavGlobs.mStepHeight) + gapWalk = los.walkOverGaps(mid, high, 0.8); + + // If there's a wall to hop over, don't do the walk code. Also, if both + // nodes are in the same island, we only consider walking if the + // heuristic (ratio of best-path-so-far to straight line dist) is high. + if (walkOff) + if ((from->island() != to->island()) || (heuristic > 3.7)) + walkable = los.walkOverBumps(aboveM, aboveH); + } + + if (walkable || canCheckJet) + { + // Do the fanned LOS - just replicate the above slew of checks. + VectorF fanInc; + S32 numFan = calcFanVector(across, fanInc); + + // Added extra checks 1-11-1: Must do a wider fanned check on + // low -> aboveM. Connections without room are slipping through + // the tests because they are diagonal. Also must from a point + // below the clear point, else they effectively can't clear on + // tall hops. + bool ignoreWide = (chuteTop || (zDist < sWideFanRaise + 0.1)); + Point3F aboveL, belowC; + if (!ignoreWide) + { + aboveL.set(low.x, low.y, low.z + sWideFanRaise); + belowC.set(clearPt.x, clearPt.y, low.z + sWideFanRaise); + } + + if (los.fannedLOS1(high, aboveH, fanInc, numFan)) + if (los.fannedLOS1(high, mid, fanInc, numFan)) + if (chuteTop || los.fannedLOS1(low, aboveM, fanInc, numFan)) + if (chuteTop || los.fannedLOS2(aboveM, aboveH, fanInc, numFan)) + if (ignoreWide || los.fannedLOS2(aboveL, aboveM, fanInc, numFan)) + { + if (!isChute) + { + if (gapWalk) + if (direct) // in the direct case we need one more fanned LOS + gapWalk = los.fannedLOS1(low, high, fanInc, numFan); + + if (gapWalk) + walkable = true; + else if (walkable) // (i.e. walkable so far in our checks) + walkable = los.fannedBumpWalk(aboveM, aboveH, fanInc, numFan); + } + + if (walkable) + { + // Well, it's too bad we had to do all this work on to find long + // connections that we throw out, but we have to figure out if it + // needs jet. + makeBridge = (xyDist < MaxWalkableXY); + } + else + { + if (canCheckJet && (xyDist > GraphJetBridgeXY)) + { + // The clear check is only needed for jetting connections, + // same with landing room check - which we should only use when + // going up to indoor nodes (and one LOS failure is adequate) + if (!checkLandRoom || !los.haveLOS(low, landRoom)) + if (isChute || los.haveLOS(low, clearPt)) + if (isChute || los.fannedLOS1(low, clearPt, fanInc, numFan)) + if (ignoreWide || los.fannedLOS2(belowC, clearPt, fanInc, numFan)) + { + // Check for bonk due to jumps, which add a couple meters. + // We want this to pass chutes and other indoor situations Ok, + // so do LOS up at each end and use that as endpoints. + + F32 jumpAdd = (oneIsPortal && xyDist < LiberalBonkXY ? 0.6 : gNavGlobs.mJumpAdd); + + aboveM.z += (los.heightUp(aboveM, jumpAdd) - 0.1); + aboveH.z += (los.heightUp(aboveH, jumpAdd) - 0.1); + + if (isChute || los.fannedLOS2(aboveM, aboveH, fanInc, numFan)) + makeBridge = true; + } + + // LOS keeps track of this at lowest level- don't allow + // jetting connections through force fields. + if (los.mHitForceField) + makeBridge = false; + } + } + } + } + } + + if (makeBridge) + { + GraphBridgeData bridge1(fromIndex, toIndex); + + if (walkable) + bridge1.setWalkable(); + + if (hopOver) + bridge1.setHop(wall); + + mBridgesOut.push_back(bridge1); + return true; + } + } + + return false; +} + +// We use a searcher to find those same-island nodes which we should try to bridge +// to - namely those who are far on the path relative to straight line distance. +// This will hopefully find for us all indoor jetting connections which are 'useful', +// as well as finding which terrain nodes should be bridged (like over water). +void GraphBridge::onQExtraction() +{ + GraphNode * mExtractNode = extractedNode(); + if (mExtractNode != mCurrent) + { + if (mSameIslandMark.test(mHead.mIndex)) + { + AssertFatal(mSameIslandCount > 0, "GraphBridge::onQExtraction()"); + + // done when we've visited all the possibilities in the same island + if (--mSameIslandCount == 0) + setEarlyOut(true); + + Point3F nodeLoc = mExtractNode->location(); + F32 straightLineDist = (mStartLoc - nodeLoc).len(); + if (straightLineDist > 0.01) + { + bool bothOutdoor = (mFromOutdoor && mExtractNode->outdoor()); + F32 ratio = distSoFar() / straightLineDist; + F32 thresh = mRatios[bothOutdoor]; + + if (ratio > thresh) + { + // Same-island bridges with good ratios are considered first... + Candidate candidate(mExtractNode, -ratio); + mCandidates.push_back(candidate); + } + } + + mSameIslandMark.clear(mHead.mIndex); + } + } +} + +// Do all the bridging. Has two modes- spawn and regular nav. Each iteration performs +// one loop of bridging- from one source node to all nodes that need to be bridged. +S32 GraphBridge::findAllBridges() +{ + S32 nodeCount = mMainList.size(); + Vector islandCross(mIslands); + GraphNodeList considerList; + + mSameIslandMark.setSize(nodeCount); + mSameIslandMark.clear(); + + mRatios[1] = 1.6; // Outdoor-to-outdoor- try a little smaller ratio than- + mRatios[0] = 2.2; // Other combinations of connections + + setSizeAndClear(islandCross, mIslands); + + for (S32 i = 0; i < nodeCount; i++) + { + if ((mCurrent = mMainList[i]) != NULL) + { + // Get list of all possibilities (bound box query) + mStartLoc = mCurrent->location(); + + if (sMagSphere) + { + Point3F pt = sMagSphere->center; + if ((pt -= mStartLoc).lenSquared() > SquareOf(sMagSphere->radius)) + continue; + } + + Point3F boxOff = (sSpawnGraph ? sBoxOffSPN : sBoxOffNAV); + Box3F area(mStartLoc - boxOff, mStartLoc + boxOff); + S32 curIsland = mCurrent->island(); + + gNavGraph->getNodesInBox(area, considerList); + + // Divy out those in separate island, and mark those in the same island for + // consideration by the special seacher. + mCandidates.clear(); + mSameIslandCount = 0; + for (S32 j = 0; j < considerList.size(); j++) { + GraphNode * consider = considerList[j]; + if (consider != mCurrent) { + if (consider->island() == curIsland) { + if (!sSpawnGraph) { + mSameIslandMark.set(consider->getIndex()); + mSameIslandCount++; + } + } + else { + // Bridges to different islands sorted by distance + F32 dist = (mStartLoc - consider->location()).lenSquared(); + Candidate candidate(consider, dist); + mCandidates.push_back(candidate); + } + } + } + + // Special search to find which same-island nodes need consideration. We want + // to bridge same-island nodes if their ground travel distance is far relative + // to their straight-line distance. + if (!sSpawnGraph) + { + setEarlyOut(false); + mFromOutdoor = mCurrent->outdoor(); + performSearch(mCurrent, NULL); + } + + // Candidates considered in order of best-ness heuristic + mCandidates.sort(); + + // Try to bridge to all candidates which remain. + setSizeAndClear(islandCross, mIslands); + for (CandidateList::iterator c = mCandidates.begin(); c != mCandidates.end(); c++) + { + if (sSpawnGraph) + { + // For spawn we only need to cross to an island once. + if (!islandCross[c->node->island()]) + if (tryToBridge(mCurrent, c->node)) + islandCross[c->node->island()] = true; + } + else + { + // For regular graph, allow a certain number of bridges to each island. + // Need to sort list, and vary base on island size. We pass in the + // heuristic # for avoiding too many walking connections. + if (islandCross[c->node->island()] < 3) + if (tryToBridge(mCurrent, c->node, -(c->heuristic))) + islandCross[c->node->island()]++; + } + } + } + } + + return mBridgesOut.size(); +} + +S32 gCountReplacementEdges, gCountUnreachable; + +// This checks to see if outdoor neighbor edges need to be modified or removed due +// to steepness. If so, a special 'replacement' bridge is created, which either +// removes the default one at startup, or converts it to a jetting connection. Note +// that this also needs to remove steep edges underwater as well. +void GraphBridge::checkOutdoor(const GraphNode* from, const GraphNode* to) +{ + static const F32 stepD = 0.3; + + Point3F fromLoc = from->location(); + Point3F toLoc = to->location(); + + // Vector for stepping sideways, and across. We're given that these are + // non-zero vectors since from and to are separate neighbors. + Point3F across = (toLoc - fromLoc); + Point3F sideVec(across.y, -across.x, 0); + sideVec.normalize(0.8); + + // Figure out step across vector, and number of iterations. + across.z = 0; + F32 dist2D = across.len(); + S32 numSteps = S32(dist2D / stepD + 1.0); + across /= F32(numSteps); + + // Get top and bottom points. Add amount should be enough given that the + // consolidator generally won't pass really large peaks between nodes. + F32 maxZ = getMax(fromLoc.z, toLoc.z); + Point3F stepVec(fromLoc.x, fromLoc.y, maxZ); + + // Step three separate lines across to account for player width. + VectorF normal; + F32 height, previousZ, highestZ = -1e12; + bool removeEdge = false; + bool walkable = true; + stepVec -= sideVec; + + for (F32 sideWise = 0.0; sideWise < 2.1 && !removeEdge; sideWise += 1.0) + { + Point3F point = (stepVec + sideVec * sideWise); + + if (gNavGraph->terrainHeight(point, &height)) + highestZ = getMax(previousZ = height, highestZ); + else + removeEdge = true; + + // Do loop plus one extra for last point. Look for too steep a slope if our Z + // is increasing. This means we can't walk. Also check to see if it's possible + // to jet - namely if the highest point isn't too far above higher node. + for (S32 N = 0; N <= numSteps && !removeEdge; N++, point += across) + { + if (gNavGraph->terrainHeight(point, &height, &normal)) + { + // Going up and too steep? + if (normal.z < gNavGlobs.mWalkableDot && height > previousZ) + walkable = false; + highestZ = getMax(previousZ = height, highestZ); + } + else + removeEdge = true; + } + } + + // Most of the time, this should happen- + if (walkable && !removeEdge) + return; + + GraphBridgeData bridge(from->getIndex(), to->getIndex()); + bridge.setReplacement(); + + // See if we can at least jet. + if (!removeEdge && + (from->getNormal().z >= sJumpAngDot && to->getNormal().z >= sJumpAngDot) && + (!from->liquidZone() && !to->liquidZone()) && + (highestZ - maxZ < 5.0) + ) + { + gCountReplacementEdges++; + bridge.setHop(highestZ - maxZ + 1.0); + } + else + { + gCountUnreachable++; + bridge.setUnreachable(); + } + + mBridgesOut.push_back(bridge); +} + +// Special pass to find those outdoor neighbors that are too steep to walk up. The +// collision checking in checkOutdoor() only masks with terrain since it is already +// established that there are no other obstructions along the ground. +void GraphBridge::trimOutdoorSteep() +{ + gCountReplacementEdges = 0; + gCountUnreachable = 0; + + S32 nodeCount = mMainList.size(); + for (S32 i = 0; i < nodeCount; i++) + if (GraphNode * from = mMainList[i]) + if (from->outdoor()) { + GraphEdgeArray edges = from->getEdges(NULL); + while (GraphEdge * e = edges++) + if (!e->isJetting()) + if (GraphNode * to = gNavGraph->lookupNode(e->mDest)) + if (to->outdoor()) + checkOutdoor(from, to); + } +} + +//------------------------------------------------------------------------------------- + +S32 QSORT_CALLBACK GraphBridge::CandidateList::cmpCandidates(const void * a,const void * b) +{ + F32 A = ((Candidate*)a)->heuristic; + F32 B = ((Candidate*)b)->heuristic; + return (A < B ? -1 : (A > B ? 1 : 0)); +} + +void GraphBridge::CandidateList::sort() +{ + dQsort((void* )(this->address()), this->size(), sizeof(Candidate), cmpCandidates); +} + +//------------------------------------------------------------------------------------- + +// Something to make it easy to quickly check out the graph generation in a particular +// area by only doing bridge building in that vicinity. +void NavigationGraph::setGenMagnify(const SphereF * sphere, + const Point3F * end1, const Point3F * end2, + F32 xyCheck, F32 zCheck) +{ + static SphereF sSphere; + static Point3F sPoint1, sPoint2; + + sMagSphere = NULL; + sBreakPt2 = sBreakPt1 = NULL; + + if (sphere) + *(sMagSphere = &sSphere) = *sphere; + if (end1) + *(sBreakPt1 = &sPoint1) = *end1; + if (end2) + *(sBreakPt2 = &sPoint2) = *end2; + + sBreakRangeXY = xyCheck; + sBreakRangeZ = zCheck; +} + +//------------------------------------------------------------------------------------- + +const char * NavigationGraph::findBridges() +{ + const char * errorText = NULL; + + if (!mIslandPtrs.size()) + errorText = "Error: islands haven't been marked in graph"; + else + { + BridgeDataList bridges; + + GraphBridge builder(mNodeList, numIslands(), bridges); + + sSpawnGraph = mIsSpawnGraph; + Loser::mCasts = 0; + + builder.findAllBridges(); + builder.trimOutdoorSteep(); + + // set the graph variable: + mBridgeList = bridges; + } + return errorText; +} + +const char * NavigationGraph::pushBridges() +{ + if (mPushedBridges) + return "Bridges already pushed - do MakeGraph() to remove them"; + + if (mBridgeList.size() == 0) + return "No bridges exist"; + + for (S32 i = 0; i < mBridgeList.size(); i++) + { + GraphBridgeData & B = mBridgeList[i]; + + if (!B.isReplacement()) + { + for (S32 k = 0; k < 2; k++) + { + RegularNode * src = dynamic_cast(mNodeList[B.nodes[k^0]]); + RegularNode * dst = dynamic_cast(mNodeList[B.nodes[k^1]]); + + GraphEdge& edge = src->pushEdge(dst); + if (B.mustJet()) { + edge.setJetting(); + if (B.jetClear) + edge.setHop(B.jetClear); + } + + mJetManager.initEdge(edge, src, dst); + } + } + else + { + mJetManager.replaceEdge(B); + } + } + + Con::printf("Connected %d bridges", mBridgeList.size()); + mPushedBridges = true; + markIslands(); + return NULL; +} + +//------------------------------------------------------------------------------------- + +GraphSeeds::GraphSeeds(NavigationGraph& graph) + : mGraph(graph), + mLoser(InteriorObjectType) +{ + // Save node count for later mapping into antecedent index table. + mOriginalCount = graph.numNodes(); +} + +// Add the seed. We have to remember our parent (antecedent) that spawned us thinking +// that we might be potentially useful. +void GraphSeeds::pushSeed(GraphNode* antecedent, const Point3F& pos, GraphSeed::Type type) +{ + S32 index = antecedent->getIndex(); + GraphSeed seed(index, pos, type); + mAntecedents.push_back(index); + push_back(seed); +} + +Point3F sgCheckSeedUpper(-149.0, -136.5, 166.42); + +// Look at drop off from each edge of node. If it lands inside of an interior volume +// with a modicum of containment, then we want to add a seed node there. +void GraphSeeds::seedDropOffs(GraphNode * antecedent) +{ + // Just for quick testing - look at our shape and see if we can get the behavior we + // want locally here- + // if (!within(antecedent->location(), sgCheckSeedUpper, 0.6)) + // return; + + mVolume = mGraph.fetchVolume(antecedent, false); + Vector &corners = mVolume.mCorners; + + if (S32 N = corners.size()) + { + // Simplify the loop- + corners.push_back(corners[0]); + + // Do drop offs on each edge. + for (S32 i = 0; i < N; i++) + { + Point3F p0 = corners[i + 0]; + Point3F p1 = corners[i + 1]; + Point3F drop = scaleBetween(p0, p1, 0.5); + VectorF normal(p0.y - p1.y, p1.x - p0.x, 0); + F32 len = normal.len(); + + if (len > 0.01) + { + // Go out at least by clear distance. Insure we're above our plane. + normal *= ((sClearDists[0] * 1.2) / len); + drop += normal; + F32 floorZ = solveForZ(mVolume.mFloor, drop); + drop.z = (floorZ + 0.5); + if (mLoser.hitBelow(drop, 50.0f)) + { + // Now see if we landed inside of a (different) node that's large, + // as defined by having good containment on this point. + drop.z += 0.2; + F32 containment; + // if (within(drop, sgCheckSeedLower, 3.0)) + if (GraphNode * closest = mGraph.closestNode(drop, &containment)) + { + if (closest == antecedent) + { + AssertFatal(containment > 0, "GraphSeeds::seedDropOffs()"); + } + else + { + if (containment < -1.3) + { + GraphSeed::Type seedType = GraphSeed::DropOff; + + // For those close to chute nodes, we make these regular nodes + // that go through full bridging process. + if (mGraph.getChutes().findNear(drop, 2.2, 1.0, mNearChutes)) + seedType = GraphSeed::Regular; + } + } + } + } + } + } + } +} + +// Procedure is then to cast across any of the walls we are outside of and see if +// the point that is cast across has decent containment within this selfsame node. +// Probably the containment we are looking for is a little less than the cast +// across distance. If Ok, then we create the node with our given antecedent. +// See crossNearbyVolumes() in graphFind.cc where most of this work happens. +void GraphSeeds::seedInventory(GraphNode * antecedent) +{ + Vector crossings; + const Point3F& nodeLoc = antecedent->location(); + if (S32 N = mGraph.crossNearbyVolumes(antecedent->location(), crossings)) + for (S32 i = 0; i < N; i++) + if (mLoser.haveLOS(nodeLoc, crossings[i])) + pushSeed(antecedent, crossings[i], GraphSeed::Inventory); +} + +S32 GraphSeeds::scatter() +{ + for (S32 i = 0; i < mGraph.numNodes(); i++) + if (GraphNode * node = mGraph.lookupNode(i)) + if (node->indoor()) + if (node->inventory()) + seedInventory(node); + else if (NavigationGraph::sSeedDropOffs) + seedDropOffs(node); + + return size(); +} + +// Find those seeds which have antecedents that aren't part of the largest island +// in the graph. These are the seeds we will want to use. For the drop off seeds +// we only attempt to bridge to their antecedent. +void GraphSeeds::markUsefulSeeds() +{ + // Make sure offsets are managed Ok - all seeds should now be nodes. + AssertFatal(mOriginalCount + size() == mGraph.numNodes(), "markUsefulSeeds()"); + + for (iterator it = begin(); it != end(); it++) + { + GraphNode * antecedent = mGraph.lookupNode(it->antecedent()); + + if (antecedent->island() != mGraph.largestIsland()) + { + S32 mapIndex = mOriginalCount + (it - begin()); + GraphNode * seed = mGraph.lookupNode(mapIndex); + + // Bridge builder uses this bit- + seed->setUsefulSeed(); + } + } +} + +//------------------------------------------------------------------------------------- + +#define SeedBoxW 0.3 +#define SeedBoxH 2.0 + +// Here's where we're adding the seed into the (persisted) data lists. They will +// take effect as run time nodes on the next call to makeGraph(). +void NavigationGraph::installSeed(const Point3F& pos) +{ + IndoorNodeInfo nodeInfo; + nodeInfo.pos = pos; + nodeInfo.setSeed(true); + mNodeInfoList.push_back(nodeInfo); + mNodeVolumes.addVolume(pos, SeedBoxW, SeedBoxH); +} + +// +// What was previously several calls from script (makeGraph, findBridges, pushBridges) +// has now been rolled altogether for the purposes of trying to fill the last holes +// that we are seeing in the graph. This assumes that script has already uploaded +// the groundPlan and floorPlan information. +// +bool NavigationGraph::assemble() +{ + sSpawnGraph = mIsSpawnGraph; + Loser::mCasts = 0; + + // First we need a made graph for the seeder to work. + makeGraph(); + + // Look for problem areas and scatter seeds. + GraphSeeds seeds(*this); + seeds.scatter(); + for (S32 i = 0; i < seeds.size(); i++) + installSeed(seeds[i].location()); + + // Remake to create the run time seed nodes. + makeGraph(); + + // Now build bridges WITHOUT using the seed nodes- + BridgeDataList bridges; + GraphBridge bridgeBuilder(mNodeList, numIslands(), bridges); + bridgeBuilder.heedSeeds(false); + bridgeBuilder.findAllBridges(); + + // Set the bridges and connect. This finds largest island, which puts us - + mBridgeList = bridges; + pushBridges(); + + // - in a position to know which seeds might matter (those with stranded parents) + seeds.markUsefulSeeds(); + + // Now bridge seed nodes- + bridges.clear(); + bridgeBuilder.heedSeeds(true); + bridgeBuilder.findAllBridges(); + + // Still need to trim outdoor steep connections (this adds to bridges) + bridgeBuilder.trimOutdoorSteep(); + + // Add the new bridges to the list and rebuild everything- + mBridgeList.merge(bridges); + makeGraph(); + pushBridges(); + + // Done + return 0; +} diff --git a/ai/graphBridge.h b/ai/graphBridge.h new file mode 100644 index 0000000..b3a9522 --- /dev/null +++ b/ai/graphBridge.h @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHBRIDGE_H_ +#define _GRAPHBRIDGE_H_ + +class GraphBridge : public GraphSearch +{ + protected: + typedef GraphSearch Parent; + struct Candidate { + GraphNode * node; + F32 heuristic; + Candidate(GraphNode* n=NULL, F32 m=0.0) {node = n; heuristic = m;} + }; + class CandidateList : public Vector { + static S32 QSORT_CALLBACK cmpCandidates(const void* , const void* ); + public: + void sort(); + }; + + protected: + // Parameters to this object- + const GraphNodeList& mMainList; + const S32 mIslands; + BridgeDataList& mBridgesOut; + + // Working variables- + GraphNode * mCurrent; + Point3F mStartLoc; + BitVector mSameIslandMark; + S32 mSameIslandCount; + bool mFromOutdoor; + bool mHeedSeeds; + F32 mRatios[2]; + CandidateList mCandidates; + ChutePtrList mChuteList; + + protected: + void onQExtraction(); + F32 getEdgeTime(const GraphEdge* e); + bool earlyOut() {return true;} + bool heedThese(const GraphNode* from, const GraphNode* to); + bool tryToBridge(const GraphNode* from, const GraphNode* to, F32 ratio=1e13); + void checkOutdoor(const GraphNode* from, const GraphNode* to); + + public: + GraphBridge(const GraphNodeList& mainList, S32 islands, BridgeDataList& listOut); + S32 findAllBridges(); + void trimOutdoorSteep(); + void heedSeeds(bool b); +}; + +class GraphSeed +{ + public: + enum Type {DropOff, Inventory, Regular}; + + protected: + S32 mAntecedent; + Point3F mLocation; + Type mType; + + public: + GraphSeed(S32 A, const Point3F& P, Type T) : mAntecedent(A), mLocation(P), mType(T) {} + bool isDropOff() const {return mType == DropOff;} + bool isInventory() const {return mType == Inventory;} + S32 antecedent() const {return mAntecedent;} + const Point3F& location() const {return mLocation;} +}; + +class GraphSeeds : public Vector +{ + protected: + NavigationGraph& mGraph; + GraphVolume mVolume; + Loser mLoser; + Vector mAntecedents; + S32 mOriginalCount; + ChutePtrList mNearChutes; + + void pushSeed(GraphNode* antecedent, const Point3F&, GraphSeed::Type); + void seedDropOffs(GraphNode* antecedent); + void seedInventory(GraphNode* antecedent); + + public: + GraphSeeds(NavigationGraph& G); + void markUsefulSeeds(); + S32 scatter(); +}; + +#endif diff --git a/ai/graphBuildLOS.cc b/ai/graphBuildLOS.cc new file mode 100644 index 0000000..7665485 --- /dev/null +++ b/ai/graphBuildLOS.cc @@ -0,0 +1,208 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +#define MuzzleHt 0.82f +#define HeadHt 2.2f + + +//------------------------------------------------------------------------------------- + +// Want to see stuff on screen, so we scamble node order (bunch of random swaps) +static void makeScrambler(Vector& vec, S32 size) +{ + S32 i, i1, i2, count = size * 10; + + for (i = 0, vec.setSize(size); i < size; i++) + vec[i] = i; + + while (--count >= 0) + { + S32 t = vec[i1 = (gRandGen.randI()&0xFFFFFF) % size]; + vec[i1] = vec[i2 = (gRandGen.randI()&0xFFFFFF) % size]; + vec[i2] = t; + } +} + +static RayInfo sColl; +static bool haveLOS(Point3F src, Point3F dst, F32 above) +{ + static const U32 sMask = InteriorObjectType|TerrainObjectType; + src.z += above; + dst.z += above; + return !gServerContainer.castRay(src, dst, sMask, &sColl); +} + +class MakeLOSEntries : public GraphSearch +{ + const GraphNodeList& mList; + LOSXRefTable& mLOSTable; + S32 mCurrent; + GraphNode * mFromNode; + Point3F mFromLoc; + F32 mThreshDist; + U32 mStartTime, mSaveMS; + S32 mLowButNotHigh; + S32 mFromIndex; + Vector mScramble; + S32 mLOSCalls; + + public: + Vector mRenderSegs; + Point3F mViewLoc; + + protected: + // virtuals- + void onQExtraction(); + F32 getEdgeTime(const GraphEdge*); + bool earlyOut(); + + public: + MakeLOSEntries(const GraphNodeList& list, LOSXRefTable& xRef, Point3F view); + + bool isDone() const {return mCurrent>=mList.size();} + U32 elapsedTime() const {return Platform::getRealMilliseconds()-mSaveMS;} + S32 lowNotHigh() const {return mLowButNotHigh;} + S32 numLOSCalls() const {return mLOSCalls;} + bool nextOneReady(); + void workAWhile(); +}; + +MakeLOSEntries::MakeLOSEntries(const GraphNodeList& list, LOSXRefTable& los, Point3F v) + : mList(list), mLOSTable(los), mViewLoc(v) +{ + S32 N = list.size(); + mLOSTable.setDims(N * (N + 1) >> 1, 2); // Alloc our 2-bit table + mViewLoc.set(-44,-31,90); + mThreshDist = 700.0f; + mCurrent = -1; + mSaveMS = Platform::getRealMilliseconds(); + mLOSCalls = mLowButNotHigh = 0; + makeScrambler(mScramble, list.size()); +} + +// Run LOS from base to this node. We truncate the search at a certain path distance, +// which should make sense in all but a few cases that we should be aware of. It +// will make the search quicker for most worlds though. +void MakeLOSEntries::onQExtraction() +{ + S32 toIndex = extractedNode()->getIndex(); + + if (mFromIndex < toIndex) { + //==> Path distance really not right- just need a better local query. + if (distSoFar() < mThreshDist) { + Point3F toLoc = extractedNode()->location(); + bool losLow = haveLOS(mFromLoc, toLoc, MuzzleHt); + bool losHigh = haveLOS(mFromLoc, toLoc, HeadHt); + U32 tabEntry = LOSXRefTable::Hidden; + + // Speed tracking + mLOSCalls += 2; + + // These are strange- add to list of warning log for graph maker + mLowButNotHigh += (losLow && !losHigh); + + // Crude calc of entry for now- + if(losLow) + tabEntry = LOSXRefTable::FullLOS; + else if (losHigh) + tabEntry = LOSXRefTable::MinorLOS; + + // Enter into table- + mLOSTable.setEntry(mFromIndex, toIndex, tabEntry); + + // Stuff to render- + if (mRenderSegs.size() < 600) { + LineSegment segment(mFromLoc, tabEntry ? toLoc : sColl.point); + if (segment.distance(mViewLoc) < 120.0f) + mRenderSegs.push_back(segment); + } + } + else // outside + setDone(); + } +} + +// Virtual - this will cause graph time to equal distance. +F32 MakeLOSEntries::getEdgeTime(const GraphEdge * edge) +{ + return edge->mDist; +} + +// Prepare a new search. Note mCurrent constructs at -1 for proper entry. +bool MakeLOSEntries::nextOneReady() +{ + if (++mCurrent < mList.size()) { + mFromNode = mList[mScramble[mCurrent]]; + mFromLoc = mFromNode->location(); + mFromIndex = mFromNode->getIndex(); + return true; + } + return false; +} + +#define MillisecondWorkShift 90 + +// Virtual - runSearch() can be stopped in the middle. +bool MakeLOSEntries::earlyOut() +{ + U32 timeElapsed = (Platform::getRealMilliseconds() - mStartTime); + return (timeElapsed > MillisecondWorkShift); +} + +void MakeLOSEntries::workAWhile() +{ + mStartTime = Platform::getRealMilliseconds(); + + while (!isDone() && !earlyOut()) + if (inProgress()) + runSearch(mFromNode); + else if (nextOneReady()) + runSearch(mFromNode); + + NavigationGraph::sProcessPercent = F32(mCurrent) / F32(mList.size()); +} + +bool NavigationGraph::prepLOSTableWork(Point3F viewLoc) +{ + if (mTableBuilder) { + delete mTableBuilder; + mTableBuilder = NULL; + } + + mTableBuilder = new MakeLOSEntries(mNonTransient, mLOSXRef, viewLoc); + return true; +} + +// This gets called repeatedly until the table is built. Idea here is to slice +// the process so that some rendering can occur during this lengthy process. +bool NavigationGraph::makeLOSTableEntries() +{ + MakeLOSEntries * makeEntries = dynamic_cast(mTableBuilder); + AssertFatal(makeEntries, "Graph preprocess: prepLOSTable() needed"); + + makeEntries->workAWhile(); + + mRenderThese = makeEntries->mRenderSegs; + makeEntries->mRenderSegs.clear(); + + if (makeEntries->isDone()) { + clearRenderSegs(); + Con::printf("Total table size = %d", mLOSXRef.numBytes()); + Con::printf("Performed %d LOS calls", makeEntries->numLOSCalls()); + Con::printf("Elapsed time = %d milliseconds", makeEntries->elapsedTime()); + if (makeEntries->lowNotHigh()) + Con::printf("%d Low-not-high entries found", makeEntries->lowNotHigh()); + delete mTableBuilder; + mTableBuilder = NULL; + // Convert data to better hasher- + makeLOSHashTable(); + return false; + } + return true; +} diff --git a/ai/graphConjoin.cc b/ai/graphConjoin.cc new file mode 100644 index 0000000..8ae39e9 --- /dev/null +++ b/ai/graphConjoin.cc @@ -0,0 +1,660 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graphData.h" +#include "Core/BitTables.h" +#include "ai/graphGroundPlan.h" +// Two pass consolidation: +// +// 1. Find what is maximum level that can be consolidated based on flatness (+ same- +// type-ness of the squares inside). +// 2. Actually assign the levels, limiting some based on condition II below. +// +// Here are the conditions for consolidating squares. +// +// I. A square can only be consolidated if there are no level zero squares inside +// and if all of the four subsquares have managed to be consolidated (plus +// being inside the graph region). ====> plus no empty squares. +// II. No terrain node can have a neighbor whose level differs by more than one. So +// we can't have a huge consolidated square with a bunch of little neighbors, +// for example. There are two motivations for this: We'd like to be able +// to cap the neighbor count; Second, it will help us insure that large +// nodes still make sense as distinct entries in the LOS xref table. +// III. A square can be consolidated if all sub-squares are of the same type, plus +// this square is of size 1< PI/6. +// +Point2F GridNormalInfo::normalToAngle(const VectorF& normal) +{ + Point2F angle; + angle.x = (M_PI / 2.0) - mAtan( normal.x, normal.z ); + angle.y = (M_PI / 2.0) - mAtan( normal.y, normal.z ); + return angle; +} +VectorF GridNormalInfo::angleToNormal(const Point2F& A) +{ + VectorF normal( mCos(A.x)/mSin(A.x), mCos(A.y)/mSin(A.y), 1.0); + normal.normalize(); + return normal; +} + +//------------------------------------------------------------------------------------- + +// +// The following 3 classes perform separate passes of the data consolidation. +// Note they only build the data (what gets persisted) - creating run time +// nodes from this data is done elsewhere. +// +class FindBestConsolidations : public GridVisitor +{ + protected: + Vector mGridNormals; + TrackLevels & mTrackLevels; + const ConjoinConfig & mConfigure; + F32 mDotThreshold; + void getGridNormals(); + bool allSameType(const GridArea& R, U8& nodeType); + bool areaIsFlat(const GridArea& R); + bool atLevelZero(const GridArea& R); // virtual + bool afterDivide(const GridArea& R, S32 level, bool success); // virtual + + public: + FindBestConsolidations(const GridArea& G, const ConjoinConfig& C, TrackLevels& T); +}; +class SmoothOutLevels : public GridVisitor +{ + protected: + const S32 mLevel; + TrackLevels & mTrackLevels; + bool allWater(const GridArea& R); + void capLevelAt(const GridArea& R, S32 L); + void doCurrentBottom(const GridArea& R); + bool atLevelZero(const GridArea& R); // virtual + bool beforeDivide(const GridArea& R, S32 level); // virtual + + public: + SmoothOutLevels(const GridArea& G, TrackLevels& T, S32 L); +}; +class BuildConsolidated : public GridVisitor +{ + protected: + const TrackLevels & mTrackLevels; + Consolidated & mConsolidated; + bool checkAddToList(const GridArea& R, S32 level); + bool beforeDivide(const GridArea& R, S32 level); // virtual + bool atLevelZero(const GridArea& R); // virtual + + public: + BuildConsolidated(const GridArea& G, const TrackLevels& T, Consolidated& C); +}; + + +//------------------------------------------------------------------------------------- + +ConjoinConfig::ConjoinConfig() +{ + maxAngleDev = 45; // (only one actually used right now...) + maxBowlDev = 70; + maxLevel = 6; +} + +//------------------------------------------------------------------------------------- + +// Just check that it's a valid rect for that level. +static bool checkArea(const GridArea & G, S32 L, const char * caller) +{ + U16 ext = (1 << L); + U16 mask = ext - 1; + const char * problem = NULL; + + if( G.point.x & mask ) + problem = "X point isn't aligned"; + else if ( G.point.y & mask ) + problem = "Y point isn't aligned"; + else if( G.extent.x != ext ) + problem = "X extent is bad"; + else if( G.extent.y != ext ) + problem = "Y extent is bad"; + + AssertFatal( caller && ! problem, avar("Problem= %s in %s", problem, caller) ); + + return ! problem && caller; +} + +TrackLevels::TrackLevels(S32 sz) +{ + init(sz); +} + +void TrackLevels::init(S32 size) +{ + setSizeAndClear(achievedLevels, size); + setSizeAndClear(nodeTypes, size); +} + +S32 TrackLevels::size() const +{ + return achievedLevels.size(); +} + +U16 TrackLevels::getNodeType(S32 idx) const +{ + return nodeTypes[idx]; +} + +void TrackLevels::setNodeType(S32 idx, U8 nodeType) +{ + nodeTypes[idx] = nodeType; +} + +// Achieved level is the highest set bit. +void TrackLevels::setAchievedLevel(S32 i, S32 level) +{ + AssertFatal(i < achievedLevels.size(), "TrackLevels::setAchievedLevel()"); + + if(level < 0) { + achievedLevels[i] = 0; + } + else{ + U16 mask = (1 << level); + AssertFatal(achievedLevels[i]==mask-1, "setAchievedLevel"); + achievedLevels[i] |= mask; + } +} + +// Note that TrackLevels maps the case of zero not being achieved into a -1 value. +S32 TrackLevels::getAchievedLevel(S32 idx) const +{ + U16 achieved = achievedLevels[idx]; + U16 L = BitTables::getPower16(achieved); + + if( L-- > 0 ) + return S32(L); + else + return -1; +} + +// There's a problem with -1 and 0 not carrying enough information. We have some +// nodes which need to be considered as -1 for the purposes of the consolidation +// since they don't really span a square, but we need to consider them zero below +// when we're assembling the list. Pretty klunky... +S32 TrackLevels::originalNodeLevel(S32 idx) const +{ + S32 level = getAchievedLevel(idx); + if(level == -1 && nodeTypes[idx]) + return 0; + else + return level; +} + +void TrackLevels::capLevelAt(S32 i, U16 lev) +{ + AssertFatal(validArrayIndex(i, achievedLevels.size()), "TrackLevels::capLevelAt()"); + U16 mask = (1 << lev + 1) - 1; + achievedLevels[i] &= mask; +} + +//------------------------------------------------------------------------------------- +// Visitor to find best consolidations + +FindBestConsolidations::FindBestConsolidations( const GridArea& gridArea, + const ConjoinConfig& thresholds, + TrackLevels& trackArrayOut + ) + : mTrackLevels(trackArrayOut), + mConfigure(thresholds), + GridVisitor(gridArea) +{ + // Pre-gather terrain normal information; convert angle values to what we need + getGridNormals(); + mDotThreshold = F32(mCos(mDegToRad(thresholds.maxAngleDev))); +} + +// Go through and fetch the triangle normals from the terrain, plus compute the our +// pseudo-Euler for each normal. +void FindBestConsolidations::getGridNormals() +{ + TerrainBlock * terr = GroundPlan::getTerrainObj(); + F32 sqrW = gNavGlobs.mSquareWidth; + F32 stepIn = (sqrW * 0.1); + + Point2I stepper; + mGridNormals.clear(); + + for (mArea.start(stepper); mArea.pointInRect(stepper); mArea.step(stepper)) + { + // this is empty by default + GridNormalInfo info; + + // find out, based on split, good points to use for normal check: + Point2F loc(stepper.x * sqrW, stepper.y * sqrW); + Point2F upperCheckPt = loc; + Point2F lowerCheckPt = loc; + if( (stepper.x + stepper.y) & 1 ){ + lowerCheckPt += Point2F(stepIn, stepIn); + upperCheckPt += Point2F(sqrW - stepIn, sqrW - stepIn); + } + else{ + lowerCheckPt += Point2F(sqrW - stepIn, stepIn); + upperCheckPt += Point2F(stepIn, sqrW - stepIn); + } + + // get slopes and convert to the pseudo-Euler + if (terr->getNormal(lowerCheckPt, &info.normals[0])) + if (terr->getNormal(upperCheckPt, &info.normals[1])) { + for (S32 both = 0; both < 2; both++) { + info.angles[both] = info.normalToAngle(info.normals[both]); + if (info.normals[both].z < gNavGlobs.mWalkableDot) + info.hasSteep = true; + } + info.notEmpty = true; + } + + mGridNormals.push_back( info ); + } +} + +bool FindBestConsolidations::allSameType(const GridArea& R, U8& nodeType) +{ + nodeType = mTrackLevels.getNodeType(mArea.getIndex(R.point)); + Point2I stepper; + for (R.start(stepper); R.pointInRect(stepper); R.step(stepper)) + if (mTrackLevels.getNodeType(mArea.getIndex(stepper)) != nodeType) + return false; + return true; +} + +// ==> +// ==> This routine is where all the consolidation work happens and will eventually +// ==> do a lot more work to do more robust checks. Example: +// ==> We want to allow more flatness for bowls than for hills. +// ==> We may want to consolidate more in places far from action. +// ==> +bool FindBestConsolidations::areaIsFlat(const GridArea& R) +{ + // Area must be of same type. If that type is submerged - then we consider + // it flat right away. + U8 nodeType; + if (!allSameType(R, nodeType)) + return false; + else if (nodeType == GraphNodeSubmerged) + return true; + + Point2F minAngle(M_PI, M_PI); + Point2F maxAngle(0,0); + Point2I stepper; + + // First accumulate angle bounds, + for (R.start(stepper); R.pointInRect(stepper); R.step(stepper)) + { + const GridNormalInfo & info = mGridNormals[ mArea.getIndex(stepper) ]; + + // Don't consolidate if there are non-walkable surfaces- + // Actually... + // Turns out this makes too many nodes on some maps - we'll do this differently + // if (info.hasSteep) + // return false; + + for (S32 triangle = 0; triangle < 2; triangle++) + { + minAngle.x = getMin( info.angles[triangle].x, minAngle.x ); + minAngle.y = getMin( info.angles[triangle].y, minAngle.y ); + maxAngle.x = getMax( info.angles[triangle].x, maxAngle.x ); + maxAngle.y = getMax( info.angles[triangle].y, maxAngle.y ); + } + } + + // Get the middle angle and the corresponding unit vector: + Point2F medianAngle = (minAngle + maxAngle) * 0.5; + VectorF medianNormal = GridNormalInfo::angleToNormal (medianAngle); + + // Find maximum deviation from median slope. + F32 minDot = 1.0; + for (R.start(stepper); R.pointInRect(stepper); R.step(stepper)) + { + const GridNormalInfo & info = mGridNormals[ mArea.getIndex(stepper) ]; + for (S32 triangle = 0; triangle < 2; triangle++) { + // == > We can check early out here, but want to watch behavior for now + minDot = getMin( mDot(info.normals[triangle], medianNormal), minDot ); + } + } + + // if all dot products are sufficiently close to 1, we're hopefully flat- + return minDot > mDotThreshold; +} + +// pass one of consolidation - finds maximum possible squares. +bool FindBestConsolidations::atLevelZero(const GridArea& R) +{ + S32 idx = mArea.getIndex(R.point); + S32 achieved = mTrackLevels.getAchievedLevel(idx); + + if( achieved < 0 ) + return false; + + AssertFatal( achieved == 0, "FindBest messed up at level zero." ); + + return true; +} + +bool FindBestConsolidations::afterDivide(const GridArea& R, S32 level, bool success) +{ + S32 idx = mArea.getIndex(R.point); + AssertFatal( validArrayIndex(idx, mTrackLevels.size()), "Conjoin weird idx"); + + // ==> Next: Look at node type as part of consolidation. Mainly Water. + // ==> Q: Will other volume types be important? (i.e. fog, ..?). + if( success && level <= MaxConsolidateLevel ) + { + if (areaIsFlat( R )) + { + Point2I stepper; // set achieved level in all sub-squares. + for (R.start(stepper); R.pointInRect(stepper); R.step(stepper)) + mTrackLevels.setAchievedLevel( mArea.getIndex(stepper), level ); + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------------- +// VISITOR TO SMOOTH OUT LEVELS. +// Some of the best ones may be eliminated to satisfy condition II above. + +static GridArea getParentArea(const GridArea & R, S32 L) +{ + checkArea( R, L, "getParentArea one" ); + L = L + 1; + Point2I roundDownPoint((R.point.x >> L) << L, (R.point.y >> L) << L); + Point2I doubleTheExtent( R.extent.x << 1, R.extent.y << 1 ); + GridArea parent(roundDownPoint, doubleTheExtent); + checkArea( parent, L, "getParentArea two" ); + return parent; +} + +SmoothOutLevels::SmoothOutLevels(const GridArea& G, TrackLevels& T, S32 L) + : mTrackLevels(T), mLevel(L), GridVisitor(G) { } + + +// Cap all within the given area at L. +void SmoothOutLevels::capLevelAt(const GridArea& R, S32 L) +{ + checkArea( R, L, "capping level in smoother" ); + + Point2I stepper; + for (R.start(stepper); R.pointInRect(stepper); R.step(stepper)) { + S32 index = mArea.getIndex(stepper); + if (index >= 0) + mTrackLevels.capLevelAt(index, L); + } +} + +bool SmoothOutLevels::allWater(const GridArea& R) +{ + Point2I stepper; + for (R.start(stepper); R.pointInRect(stepper); R.step(stepper)) + if (mTrackLevels.getNodeType(mArea.getIndex(stepper)) != GraphNodeSubmerged) + return false; + return true; +} + +// Each pass does a different bottom level to smooth it out. Within this routine we +// are guaranteed that we're at the bottom level for this pass. +void SmoothOutLevels::doCurrentBottom(const GridArea& R) +{ + bool needToCapSurrounding; + S32 idx = mArea.getIndex(R.point); + S32 achieved = mTrackLevels.getAchievedLevel(idx); + + // if (allWater(R)) + // needToCapSurrounding = false; + // else + { + // At level 0, we cap if either the achieved is 0, or -1 (an empty square). + if (mLevel == 0) + needToCapSurrounding = (achieved <= 0); + else + needToCapSurrounding = (achieved == mLevel); + } + + if (needToCapSurrounding) + { + // THIS IS THE TRICKY STEP IN SMOOTHING OUT THE LEVELS! We cap all eight of our + // same-sized neighbors- but we choose the cap level based on whether or not they + // fall inside OUR parent, or if they fall in a NEIGHBOR of our parent. If in our + // parent, then cap at our level. If in neighbor's parent, we cap at THAT level. + + GridArea ourParent = getParentArea(R, mLevel); + + // Loop on all 8 neighbors: + for(S32 y = -1; y <= 1; y++) for(S32 x = -1; x <= 1; x++) if (x || y) + { + Point2I neighborPoint = Point2I(x << mLevel, y << mLevel) + R.point; + S32 neighborIndex = mArea.getIndex( neighborPoint ); + if (neighborIndex >= 0) + { + GridArea neighborArea(neighborPoint, R.extent); + if (ourParent.contains(neighborArea)) + capLevelAt(neighborArea, mLevel); + else + { + GridArea neighborParent = getParentArea( neighborArea, mLevel ); + if(mArea.contains(neighborParent)) + capLevelAt(neighborParent, mLevel + 1); + } + } + } + } +} + +// If the current bottom level we are looking at is the best consolidation +// that can happen for this square, go around to all neighbors and cap +// their best-so-far levels. +bool SmoothOutLevels::atLevelZero(const GridArea& R) +{ + if(mLevel == 0) { + doCurrentBottom(R); + return true; + } + return false; +} + +bool SmoothOutLevels::beforeDivide(const GridArea& R, S32 level) +{ + checkArea( R, level, "smoother before divide" ); + // AssertFatal( level >= mLevel, "Smoothing making it too far down somehow" ); + + if( level > mLevel ) // still at high level - tell it to divide further. + return true; + else if(level == mLevel){ // + doCurrentBottom(R); + return false; + } + else + return false; +} + +//------------------------------------------------------------------------------------- +// This Visitor assembles the list into mConsolidated: + +BuildConsolidated::BuildConsolidated(const GridArea& G, const TrackLevels& T, Consolidated& C) + : GridVisitor(G), mTrackLevels(T), mConsolidated(C) +{ } + +bool BuildConsolidated::checkAddToList(const GridArea& R, S32 level) +{ + checkArea(R, level, "checkAddToList"); + + if (mTrackLevels.originalNodeLevel(mArea.getIndex(R.point)) == level) + { + OutdoorNodeInfo nodeInfo; + nodeInfo.level = level; + nodeInfo.x = R.point.x; + nodeInfo.y = R.point.y; + mConsolidated.push_back(nodeInfo); + return false; + } // (ret vals needed for beforeDivide(), false will stop the depth recurse) + return true; +} +bool BuildConsolidated::beforeDivide(const GridArea& R, S32 level) +{ + return checkAddToList( R, level ); +} +bool BuildConsolidated::atLevelZero(const GridArea& R) +{ + checkAddToList( R, 0 ); + return Whatever; +} + +//------------------------------------------------------------------------------------- + +// This routine is supplied with those grid squares that can't conjoin. +// The 'output' is to set up the consolidated list member variable, +// and return if successful. +bool TerrainGraphInfo::buildConsolidated(const TrackLevels & whichZero, + const ConjoinConfig & configInfo) +{ + TrackLevels trackData = whichZero; + GridArea gridArea(originGrid, gridDimensions); + + // pass one to find best possible consolidations + FindBestConsolidations findLargestPossible(gridArea,configInfo,trackData); + findLargestPossible.traverse(); + + // Next, enforce maximum difference of one on the levels. + for (S32 bottom = 0; bottom < MaxConsolidateLevel; bottom++) + { + // two passes needed to properly propagate the smoothing... + for (S32 pass = 0; pass < 2; pass++) + { + SmoothOutLevels doSmoothing(gridArea, trackData, bottom); + doSmoothing.traverse(); + } + } + + // Now build the node list. The caller is responsible for making it + // part of the graph. + BuildConsolidated buildIt(gridArea, trackData, consolidated); + consolidated.clear(); + buildIt.traverse(); + + return true; +} + +//------------------------------------------------------------------------------------- +// Mark bit on grid square signalling if it's near steep. Will cause roam radii to +// be capped so bots don't walk off slopes, and don't advance through a slope they must +// walk around. + +S32 TerrainGraphInfo::markNodesNearSteep() +{ + TerrainBlock * terr = GroundPlan::getTerrainObj(); + F32 startAng = M_PI * (15.0 / 8.0); + F32 angDec = M_PI / 4.0; + Point3F loc, normal; + S32 nSteep = 0; + + for (S32 i = 0; i < nodeCount; i++) + { + // Get position- terrain system is grid aligned... + indexToLoc(loc, i); + loc -= originWorld; + + // Could be smarter here, but it's a drop in the preprocessing bucket. Just + // check enough points to be sure to find if any triangle is steep. + for (F32 ang = startAng; ang > 0; ang -= angDec) + { + Point2F checkPos(mCos(ang) * 0.2 + loc.x, mSin(ang) * 0.2 + loc.y); + if (terr->getNormal(checkPos, &normal)) + if (normal.z < gNavGlobs.mWalkableDot) { + setSteep(i); + nSteep++; + break; + } + } + } + return nSteep; +} + +//------------------------------------------------------------------------------------- +// This was being done off of run time nodes- convert to just using the grid data. + +bool TerrainGraphInfo::consolidateData(const ConjoinConfig & config) +{ + if (nodeCount <= 0) + return false; + + // This is a good place to mark the steep bits on the navigableFlags. Note it's not + // used quite the same as the hasSteep in the consolidation, which shouldn't carry + // over to other grid squares. The bit does (used for roamRadii) + markNodesNearSteep(); + + // Initialize NULL track data (keeps track of conjoin levels achieved per square). + TrackLevels levelData(nodeCount); + const U32 checkOpenSquare = 0xff; + + // Assemble the data for the consolidator. + for (S32 i = 0; i < nodeCount; i++) + { + U8 nodeType = NavGraphNotANode; + S32 nodeLevel = -1; + + if (!obstructed(i)) + { + // Check for shadowed - we flag like indoor as well to make them all stay + // at the low level. We need them dense in shadow so there will be enough + // jetting connections going up. + if (shadowed(i)) + { + nodeType = NavGraphIndoorNode; + } + else if (submerged(i)) + { + nodeType = NavGraphSubmergedNode; + nodeLevel = 0; + } + else + { + // We have to use level -1 for things which don't have all their neighbors, + // with one exception being on the edge of the mission area. Here we just + // see if it has the neighbors it should have (returned by onSideOfArea()). + nodeType = NavGraphOutdoorNode; + if (const U32 neighbors = onSideOfArea(i)) { + if (neighborFlags[i] == neighbors) + nodeLevel = 0; + } + else if (neighborFlags[i] == checkOpenSquare) + nodeLevel = 0; + } + + levelData.setAchievedLevel(i, nodeLevel); + levelData.setNodeType(i, nodeType); + } + } + + bool Ok = buildConsolidated(levelData, config); + + return Ok; +} diff --git a/ai/graphData.cc b/ai/graphData.cc new file mode 100644 index 0000000..3380886 --- /dev/null +++ b/ai/graphData.cc @@ -0,0 +1,1283 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graphData.h" +#include "terrain/terrRender.h" + +// 4 6 7 Layout for how we translate the +// 2 N 5 numbers 0 to 7 into the eight +// 0 1 3 surrounding grid locations. +const Point2I TerrainGraphInfo::gridOffs[8] = { + Point2I( -1, -1 ), + Point2I( 0, -1 ), Point2I( -1, 0 ), + Point2I( 1, -1 ), Point2I( -1, 1 ), + Point2I( 1, 0 ), Point2I( 0, 1 ), + Point2I( 1, 1 ) }; + +S32 TerrainGraphInfo::smVersion = 0; + + +//------------------------------------------------------------------------------------- +// Indoor Graph Edges + +bool GraphEdgeInfo::isAlgorithmic() const { + return to[0].flags.test(Algorithmic); +} +void GraphEdgeInfo::setAlgorithmic(bool yesOrNo) { + to[0].flags.set(Algorithmic, yesOrNo); +} + +GraphEdgeInfo::GraphEdgeInfo() +{ + segNormal.set(0,0,1); + segPoints[0] = segPoints[1] = segNormal; +} + +bool GraphEdgeInfo::read(Stream & s) +{ + for( S32 i = 0; i < 2; i++ ){ + U32 mask; + if( s.read(&mask) ){ + to[i].flags = mask; + if( s.read(&to[i].res) && s.read(&to[i].dest) ) + continue; + return false; + } + } + + if (mathRead(s, &segPoints[0])) + if (mathRead(s, &segPoints[1])) + if (mathRead(s, &segNormal)) + return true; + + return false; +} +bool GraphEdgeInfo::write(Stream & s) const +{ + for( S32 i = 0; i < 2; i++ ){ + U32 mask = to[i].flags; + if( s.write(mask) && s.write(to[i].res) && s.write(to[i].dest) ) + continue; + return false; + } + + if (mathWrite(s, segPoints[0])) + if (mathWrite(s, segPoints[1])) + if (mathWrite(s, segNormal)) + return true; + + return false; +} + +//------------------------------------------------------------------------------------- +// Indoor Nodes + +IndoorNodeInfo::IndoorNodeInfo() +{ + pos.set(-1,-1,-1); + antecedent = -1; + unused = 0; +} + +// The boolean setters & getters. Avoiding inlines since these should check +// invariant relationships among the flags. +bool IndoorNodeInfo::isAlgorithmic() const +{ + return flags.test( Algorithmic ); +} + +void IndoorNodeInfo::setAlgorithmic(bool yesOrNo) +{ + flags.set( Algorithmic, yesOrNo ); +} + +bool IndoorNodeInfo::isInventory() const +{ + return flags.test(Inventory); +} + +void IndoorNodeInfo::setInventory(bool yesOrNo) +{ + flags.set(Inventory, yesOrNo); +} + +bool IndoorNodeInfo::isBelowPortal() const +{ + return flags.test(BelowPortal); +} + +void IndoorNodeInfo::setBelowPortal(bool yesOrNo) +{ + flags.set(BelowPortal, yesOrNo); +} + +bool IndoorNodeInfo::isSeed() const +{ + return flags.test(Seed); +} + +void IndoorNodeInfo::setSeed(bool yesOrNo) +{ + flags.set(Seed, yesOrNo); +} + +bool IndoorNodeInfo::read(Stream & s) +{ + U32 mask; + if (s.read(&mask)) + { + flags = mask; + return s.read(&unused) && s.read(&antecedent) && mathRead(s, &pos); + } + return false; +} +bool IndoorNodeInfo::write(Stream & s) const +{ + U32 mask = flags; + if (s.write(mask)) + return s.write(unused) && s.write(antecedent) && mathWrite(s, pos); + return false; +} + +//------------------------------------------------------------------------------------- +// Outdoor (consolidated) node information + +OutdoorNodeInfo::OutdoorNodeInfo() +{ + level = 0xff; + flags = 0; + height = 0; //==> Not used - can be taken out. + x = y = -1; +} + +bool OutdoorNodeInfo::read(Stream& s) +{ + return s.read(&level) && + s.read(&flags) && + s.read(&height) && + s.read(&x) && + s.read(&y) + ; +} + +bool OutdoorNodeInfo::write(Stream & s) const +{ + return s.write(level) && + s.write(flags) && + s.write(height) && + s.write(x) && + s.write(y) + ; +} + +//------------------------------------------------------------------------------------- + +SpawnLocations::SpawnLocations() +{ + reset(); +} + +void SpawnLocations::reset() +{ + clear(); + compact(); + mSpheres.clear(); + mSpheres.compact(); + mRes0 = 0; +} + +bool SpawnLocations::read(Stream& s) +{ + if (s.read(&mRes0)) + if (mathReadVector(*this, s)) + return readVector1(s, mSpheres); + return false; +} + +bool SpawnLocations::write(Stream& s) const +{ + if (s.write(mRes0)) + if (mathWriteVector(*this, s)) + return writeVector1(s, mSpheres); + + return false; +} + +SpawnLocations::Sphere::Sphere(const Point3F& center, F32 radius) + : mSphere(center, radius) +{ + mInside = false; + mCount = 0; + mOffset = 0; + mRes0 = 0; +} + +bool SpawnLocations::Sphere::read(Stream& s) +{ + if (s.read(&mRes0)) + if (mathRead(s, &mSphere)) + if (s.read(&mInside)) + if (s.read(&mCount)) + return s.read(&mOffset); + return false; +} + +bool SpawnLocations::Sphere::write(Stream& s) const +{ + if (s.write(mRes0)) + if (mathWrite(s, mSphere)) + if (s.write(mInside)) + if (s.write(mCount)) + return s.write(mOffset); + return false; +} + +// Lookup a random location from our pre-computed data. +S32 SpawnLocations::getRandom(const SphereF& sphere, bool inside, U32 rnd) +{ + // Find closest sphere matching inside/outside requirement: + const Sphere * closest = NULL; + F32 minDist = 1e22, d; + for (S32 i = 0; i < mSpheres.size(); i++) + { + const Sphere * S = & mSpheres[i]; + if (S->mInside == inside && S->mCount > 0) + if ((d = (S->mSphere.center - sphere.center).lenSquared()) < minDist) + { + closest = S; + minDist = d; + } + } + + // Get the random index in the list. + if (closest) + { + U32 count = closest->mCount; + return (closest->mOffset + ((rnd & 0x7FFFFF) % count)); + } + else + return -1; +} + +void SpawnLocations::printInfo() const +{ + Con::printf("--- %d Spheres were generated", mSpheres.size()); + for (S32 i = 0; i < mSpheres.size(); i++) + { + const Sphere * S = & mSpheres[i]; + const Point3F & P = S->mSphere.center; + + Con::printf("%d : %s at center (%f, %f, %f) - generated %d", + i + 1, + S->mInside ? "Inside" : "Outside", + P.x, P.y, P.z, + S->mCount + ); + } +} + +//------------------------------------------------------------------------------------- +// Potential bridges. + +void GraphBridgeInfo::init(S32 from, S32 to) +{ + srcNode = from; + dstNode = to; + jetClear = 0; + howTo = 0; + res1 = 0; + res2 = 0; + res3 = 0; +} + +bool GraphBridgeInfo::read(Stream& s) +{ + return + ( s.read(&dstNode) && + s.read(&srcNode) && + s.read(&jetClear) && + s.read(&howTo) && + s.read(&res1) && + s.read(&res2) && + s.read(&res3) + ); +} + +bool GraphBridgeInfo::write(Stream & s) const +{ + return + ( s.write(dstNode) && + s.write(srcNode) && + s.write(jetClear) && + s.write(howTo) && + s.write(res1) && + s.write(res2) && + s.write(res3) + ); +} + +//------------------------------------------------------------------------------------- +// Graph Bridge Data + +void GraphBridgeData::init(U16 n0, U16 n1) +{ + nodes[0] = n1; // for some reason, these are backwards, which wound up causing + nodes[1] = n0; // a problem for the replacement edges. The quick remedy is to + jetClear = 0; // change the edge replacement ON LOAD (graphJetting.cc) rather + howTo = 0; // than rebuild just to change a non-intuitive ordering here... +} + +bool GraphBridgeData::read(Stream& s) +{ + return( s.read(&nodes[0]) && + s.read(&nodes[1]) && + s.read(&jetClear) && + s.read(&howTo) + ); +} + +bool GraphBridgeData::write(Stream & s) const +{ + return( s.write(nodes[0]) && + s.write(nodes[1]) && + s.write(jetClear) && + s.write(howTo) + ); +} + +BridgeDataList::BridgeDataList() +{ + mReplaced[0] = mReplaced[1] = 0; + mSaveTotal = 0; +} + +bool BridgeDataList::read(Stream& s) +{ + mSaveTotal = 0; + if (readVector1(s, *this)) + { + mSaveTotal = size(); + return true; + } + return false; +} + +bool BridgeDataList::write(Stream& s) const +{ + return writeVector1(s, *this); +} + +// Given array of accumulation numbers for how many edges each node has, add +// in the bridges as well. +S32 BridgeDataList::accumEdgeCounts(U16 * edgeCounts) +{ + S32 total = 0; + mReplaced[0] = mReplaced[1] = 0; + + for (iterator it = begin(); it != end(); it++) + { + if (!it->isReplacement()) + { + total += 2; + for (S32 i = 0; i < 2; i++) + edgeCounts[it->nodes[i]]++; + } + else + { + mReplaced[it->isUnreachable() != 0]++; + } + } + + return total; +} + +// Number of non-replacement bridges- basically how many edges it adds +// to the world (this is just used for memory tracking). +S32 BridgeDataList::numPositiveBridges() const +{ + return (mSaveTotal << 1) - replaced(); +} + +S32 BridgeDataList::replaced() const +{ + return mReplaced[0] + mReplaced[1]; +} + +// Read the old style and convert +#if 0 +bool BridgeDataList::readOld(Stream& s) +{ + BridgeInfoList oldList; + + if (oldList.read(s)) + { + S32 oldSz = oldList.size(); + AssertFatal((oldSz % 2) == 0, "Old sizes were always even"); + setSize(oldSz >>= 1); + + // Old format very bloated and stores both ways, which turned out not to have any + // differences right now (later systems, maybe...). + while (oldSz--) + { + GraphBridgeInfo& bridgeOld = oldList[oldSz << 1]; + GraphBridgeData& bridgeNew = (*this)[oldSz]; + + bridgeNew.nodes[0] = U16(bridgeOld.srcNode); + bridgeNew.nodes[1] = U16(bridgeOld.dstNode); + bridgeNew.howTo = bridgeOld.howTo; + bridgeNew.jetClear = mapJetHopToU8(bridgeOld.jetClear); + } + return true; + } + return false; +} +#endif + +//------------------------------------------------------------------------------------- + +bool GraphVolumeList::read(Stream& s) +{ + return readVector1(s, *this) && mathReadVector(mPlanes, s); +} + +bool GraphVolumeList::write(Stream& s) const +{ + return writeVector1(s, *this) && mathWriteVector(mPlanes, s); +} + +//------------------------------------------------------------------------------------- + +S32 ChuteHints::findNear(const Box3F& box, ChutePtrList& list) const +{ + list.clear(); + return mBSP.getIntersecting(list, box); +} + +// Patch for the query we usually do- +S32 ChuteHints::findNear(const Point3F& P, S32 xy, S32 z, ChutePtrList& list) const +{ + Point3F boxMin(P.x - xy, P.y - xy, P.z - z); + Point3F boxMax(P.x + xy, P.y + xy, P.z + z); + Box3F theBox(boxMin, boxMax, true); + return findNear(theBox, list); +} + +// Create the BSP. makeTree() needs a pointer list, so we must build one. +void ChuteHints::makeBSP() +{ + VectorPtr pointers; + pointers.setSize(size()); + iterator c = begin(); + for (S32 i = size() - 1; i >= 0; i--) + pointers[i] = c++; + mBSP.makeTree(pointers); +} + +void ChuteHints::init(const Vector& list) +{ + setSize(list.size()); + Vector::const_iterator p = list.begin(); + iterator c = begin(); + for (S32 i = list.size() - 1; i >= 0; i--, p++, c++) { + c->x = p->x; + c->y = p->y; + c->z = p->z; + } + makeBSP(); +} + +bool ChuteHints::read(Stream& s) +{ + if (readVector1(s, *this)) { + makeBSP(); + return true; + } + return false; +} + +bool ChuteHints::write(Stream& s) const +{ + return writeVector1(s, *this); +} + +// Given the verticle line of a (potential) jetting hop, see if it is one of the +// chute types. Build a box up as far as LOS goes to do this. We're at the +// top if the highest found chute hint is level with the given top point. +ChuteHints::Info ChuteHints::info(Point3F bottom, const Point3F& top) +{ + const F32 boxR = 1.4; + + if (mFabs(top.z - bottom.z) > 4.0 * /*Take out for moment-*/1e9) { + // Do these queries as a first pass to avoid LOS calls- + if (findNear(bottom, boxR, 2.0, mQuery) > 0) { + if (findNear(top, boxR, 2.0, mQuery) > 0) { + + bottom.z += 1.0; + + const U32 mask = (InteriorObjectType|TerrainObjectType); + const F32 maxH = 500.0; + Point3F upper(bottom.x, bottom.y, bottom.z + maxH); + RayInfo coll; + + if (gServerContainer.castRay(bottom, upper, mask, &coll)) + upper.z = coll.point.z; + + Point3F boxMin(bottom.x - boxR, bottom.y - boxR, bottom.z - 2.0); + Point3F boxMax(upper.x + boxR, upper.y + boxR, upper.z); + Box3F theBox(boxMin, boxMax, true); + + // Well, this should always return something at this point.. + if (S32 numHints = findNear(theBox, mQuery)) { + // Find max- + F32 maxZ = -1e12, z; + for (S32 i = 0; i < numHints; i++) + if ((z = mQuery[i]->z) > maxZ) + maxZ = z; + + if (mFabs(maxZ - top.z) < 2.0) + return ChuteTop; + else + return ChuteMid; + } + } + } + } + return NotAChute; +} + +//------------------------------------------------------------------------------------- + +bool PathXRefEntry::read(Stream& s) +{ + U32 numEntries, bitWidth; + if (s.read(&numEntries) && s.read(&bitWidth)) { + setDims(numEntries, bitWidth); + return s.read(numBytes(), dataPtr()); + } + return false; +} + +bool PathXRefEntry::write(Stream& s) const +{ + if (s.write(numEntries()) && s.write(bitWidth())) + return s.write(numBytes(), dataPtr()); + return false; +} + +PathXRefTable::~PathXRefTable() +{ + clear(); +} + +bool PathXRefTable::read(Stream& s) +{ + return constructVector(s, *this); +} + +bool PathXRefTable::write(Stream& s) const +{ + return writeVector1(s, *this); +} + +void PathXRefTable::setSize(S32 size) +{ + clear(); + setSizeAndConstruct(*this, size); +} + +void PathXRefTable::clear() +{ + destructAndClear(*this); +} + +//------------------------------------------------------------------------------------- + +bool LOSXRefTable::read(Stream& s) +{ + U32 numEntries, bitWidth; + if (s.read(&numEntries) && s.read(&bitWidth)) { + setDims(numEntries, bitWidth); + return s.read(numBytes(), dataPtr()); + } + return false; +} + +bool LOSXRefTable::write(Stream& s) const +{ + if (s.write(numEntries()) && s.write(bitWidth())) + return s.write(numBytes(), dataPtr()); + return false; +} + +U32 LOSXRefTable::value(S32 i1, S32 i2) const +{ + if (i1 == i2) + return FullLOS; + else + { + U32 lookup = triangularTableIndex(i1, i2); + AssertFatal(lookup < numEntries(), "LOSXRefTable::value() read beyond end"); + return getU17(lookup); + } +} + +void LOSXRefTable::setEntry(S32 i1, S32 i2, U32 val) +{ + setU17(triangularTableIndex(i1, i2), val); +} + +bool LOSXRefTable::valid(S32 numNodes) const +{ + S32 expectedTableSize = triangularTableIndex(numNodes + 1, 0); + return (numEntries() == expectedTableSize); +} + +// Reset, free up memory. +void LOSXRefTable::clear() +{ + setDims(0, 0); +} + +//------------------------------------------------------------------------------------- +// LOS Hash Table + +LOSHashTable::LOSHashTable() +{ + mTable = NULL; + mNumNodes = 0; + mRes0 = mRes1 = 0; +} + +LOSHashTable::~LOSHashTable() +{ + delete [] mTable; +} + +bool LOSHashTable::valid(S32 numNodes) const +{ + return (numNodes == mNumNodes); +} + +// Do the node-to-node lookup. +U32 LOSHashTable::value(S32 i1, S32 i2) const +{ + if (i1 == i2) + return FullLOS; + else + { + U16 from, to, seg; + + if (i1 < i2) { // Lookup goes UP + from = i1; + seg = ((to = i2) >> SegShift); + } + else { + from = i2; + seg = ((to = i1) >> SegShift); + } + + Key key(from, seg); + U32 hash = calcHash(key); + AssertFatal(hash < mTabSz, "LOSHashTable: bad hash value in lookup"); + + // Do the bucket "walk" - + if (S32 bucketSize = mTable[hash + 1] - mTable[hash]) + { + const Segment * seg = &mSegments[mTable[hash]]; + while (--bucketSize >= 0) + { + if (seg->mKey.mCompare == key.mCompare) + return seg->value(to & SegMask); + seg++; + } + } + + // Nothing found, so it's hidden + return Hidden; + } +} + +U32 LOSHashTable::mTabSz = 0; + +// This needs to be improved (hence we sort after loading as well...) +U32 LOSHashTable::calcHash(Key key) +{ + U32 n = key.mKey.mNode; + U32 s = key.mKey.mSeg; + U32 val = (n << 8 + (s & 7)) ^ (n + (n >> 1)) ^ ((s << 2) | (s & 1)); + return val % mTabSz; +} + +// Look for size that is relatively prime to first few primes (uh, can't hurt...) +U32 LOSHashTable::calcTabSize() +{ + static const U32 nPrimes = 12; + static U32 primes[nPrimes] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}; + + for (U32 nSegs = mSegments.size(); /* until we find one */ ; nSegs++ ) + { + bool done = true; + + for (U32 i = 0; i < nPrimes; i++) + if (!(nSegs % primes[i])) + done = false; + + if (done) { + mTabSz = nSegs; + break; + } + } + return mTabSz; +} + +S32 QSORT_CALLBACK LOSHashTable::cmpHashes(const void * a,const void * b) +{ + U32 A = calcHash( ((Segment*)a)->mKey ); + U32 B = calcHash( ((Segment*)b)->mKey ); + AssertFatal(A < mTabSz && B < mTabSz, "LOSHashTable: hash has bad result"); + return (A < B ? -1 : (A > B ? 1 : 0)); +} + +// This assumes the table size has been calculated- +void LOSHashTable::sortByHashVal() +{ + dQsort((void* )(mSegments.address()), mSegments.size(), sizeof(Segment), cmpHashes); +} + +LOSHashTable::Segment::Segment(Key key, const U32 losInfo[SegSize]) +{ + mKey = key; + dMemset(mLOS, 0, sizeof(mLOS)); + for (U32 i = 0; i < SegAlloc; i++) + for (U32 j = 0; j < 4; j++) + mLOS[i] |= (losInfo[i * 4 + j] << (j * 2)); +} + +// Build the new better table from the old style. Return memory usage (for measuring, +// but it's also conceivable that we could try different seg sizes dynamically). +U32 LOSHashTable::convertTable(const LOSXRefTable& XRefTable, S32 numNodes) +{ + S32 numUniform = 0; + S32 numSameSeg = 0; + + mNumNodes = numNodes; + mSegments.clear(); + + for (S32 srcNode = 0; srcNode < numNodes - 1; srcNode++) + { + // Check all other segments (including ours if we're not the last in it). Note + // inclusive compare to catch last segment! + for (S32 seg = (srcNode + 1 >> SegShift); seg <= (numNodes >> SegShift); seg++) + { + S32 begin = (seg << SegShift); + S32 end = begin + SegSize; + + if (end > numNodes) + end = numNodes; + + // If in our own segment, only check node numbers higher... + bool sameSeg = (seg == (srcNode >> SegShift)); + if (sameSeg) + { + begin = srcNode + 1; + end = ((srcNode + SegSize) & ~SegMask); + if (end > numNodes) + end = numNodes; + } + + // Examine all the nodes in the segment for non-hidden + S32 losCount = 0; + U32 losInfo[SegSize]; + dMemset(losInfo, 0, sizeof(losInfo)); + for (U32 dstNode = begin; dstNode < end; dstNode++) + { + U32 info = XRefTable.value(U16(srcNode), U16(dstNode)); + losInfo[dstNode & SegMask] = info; + losCount += (info != 0); + } + + // Add the segment if something was seen. + if (losCount) + { + Key key(srcNode, seg); + Segment segment(key, losInfo); + mSegments.push_back(segment); + + // See if they're all the same + S32 a; + for (a = 1; a < SegSize; a++) + if (losInfo[a] != losInfo[0]) + break; + numUniform += (a == SegSize); + numSameSeg += sameSeg; + } + } + } + + Con::printf("Found %d Uniform entries", numUniform); + Con::printf("Found %d SameSeg entries", numSameSeg); + + // Sorts segment vector- + calcTabSize(); + sortByHashVal(); + + // Set up mTable + U32 tabMemUse = makeTheTable(); + + return (mSegments.memSize() + tabMemUse); +} + +// Given that we've got a vector of segments sorted by hash value, make mTable. +// Assumes table size has already been calculated. +// Return memory size of table. +U32 LOSHashTable::makeTheTable() +{ + U32 deepest = 0, depth; + + // Alloc and flag as uninitialized. Extra 1 needed for bucket size math at end. + delete [] mTable; + mTable = new IndexType[ mTabSz + 1 ]; + U32 memSize = (mTabSz + 1) * sizeof(IndexType); + dMemset(mTable, 0xFF, memSize); + + // Set up hash pointers- + for (U32 seg = 0; seg < mSegments.size(); seg++) + { + U32 hash = calcHash(mSegments[seg].mKey); + if (mTable[hash] == IndexType(-1)) + mTable[hash] = seg; + else if ((depth = seg - mTable[hash]) > deepest) + deepest = depth; + } + + // Fill in empty elements so bucket size math works + AssertFatal(mTable[mTabSz] == IndexType(-1), "LOS Hash table overwritten at end"); + U32 fillValue = mSegments.size(); + U32 numEmpty = 0, numFilled = 0; + for (U32 H = mTabSz; H; H--) + { + if (mTable[H] == IndexType(-1)) { + mTable[H] = fillValue; + numEmpty++; + } + else { + fillValue = mTable[H]; + numFilled++; + } + } + + Con::printf("Table size = %d, Segments = %d, Filled = %d, Deepest = %d", + mTabSz, mSegments.size(), numFilled, deepest ); + + return memSize; +} + +bool LOSHashTable::Segment::read(Stream& s) +{ + if (s.read(&mKey.mKey.mNode) && s.read(&mKey.mKey.mSeg)) + return s.read(SegAlloc, mLOS); + return false; +} + +bool LOSHashTable::Segment::write(Stream& s) const +{ + if (s.write(mKey.mKey.mNode) && s.write(mKey.mKey.mSeg)) + return s.write(SegAlloc, mLOS); + return false; +} + +void LOSHashTable::clear() +{ + mNumNodes = 0; + delete [] mTable; + mTable = NULL; + mSegments.clear(); + mSegments.compact(); +} + +bool LOSHashTable::read(Stream& s) +{ + if (s.read(&mNumNodes)) + if (readVector1(s, mSegments)) + if (s.read(&mRes0) && s.read(&mRes1)) + { + calcTabSize(); + sortByHashVal(); // For now we sort Segments on load since we're still + makeTheTable(); // tweaking the hash function + return true; + } + return false; +} + +bool LOSHashTable::write(Stream& s) const +{ + if (s.write(mNumNodes)) + if (writeVector1(s, mSegments)) + if (s.write(mRes0) && s.write(mRes1)) + return true; + return false; +} + +//------------------------------------------------------------------------------------- +// Header info for the Terrain Data. + +TerrainGraphInfo::TerrainGraphInfo() +{ + haveGraph = false; + dMemset(indOffs, 0xff, sizeof(indOffs)); + nodeCount = numShadows = -1; + originWorld.set(-1,-1,-1); + originGrid.set(-1,-1); + gridDimensions = gridTopRight = originGrid; +} + +bool TerrainGraphInfo::read(Stream & s) +{ + S32 version; + + haveGraph = false; + + if( !s.read(&version) || version != smVersion ) + return false; + + bool Ok = s.read( & nodeCount) && + mathRead(s, & originWorld) && + mathRead(s, & originGrid) && + mathRead(s, & gridDimensions) && + mathRead(s, & gridTopRight) && + readVector2(s, navigableFlags) && + readVector2(s, neighborFlags) && + readVector2(s, shadowHeights) && + readVector2(s, roamRadii) && + consolidated.read(s) + ; + + if( Ok ) + doFinalDataSetup(); + + return( haveGraph = Ok ); +} + +bool TerrainGraphInfo::write(Stream & s) const +{ + bool Ok = s.write( smVersion ) && + s.write( nodeCount) && + mathWrite(s, originWorld) && + mathWrite(s, originGrid) && + mathWrite(s, gridDimensions) && + mathWrite(s, gridTopRight) && + writeVector2(s, navigableFlags) && + writeVector2(s, neighborFlags) && + writeVector2(s, shadowHeights) && + writeVector2(s, roamRadii) && + consolidated.write(s) + ; + + return Ok; +} + +//------------------------------------------------------------------------------ +// Terrain graph info utility functions. + + +// Get the index of the given grid location if it's in our region, else -1. +// This doesn't check for obstruction though. +S32 TerrainGraphInfo::posToIndex(Point2I p) const +{ + S32 idx = -1; + + if( haveGraph ) + { + p -= originGrid; + + if( validArrayIndex(p.x, gridDimensions.x) ) + if( validArrayIndex(p.y, gridDimensions.y) ) + idx = p.y * gridDimensions.x + p.x; + + AssertFatal( idx >= -1 && idx < nodeCount, "TerrainGraphInfo::posToIndex()" ); + } + + return idx; +} + +// Fetch the location at the given grid location - a straight mapping +// to world space, whether it's a valid node or not. But then return +// if it's a valid node. +bool TerrainGraphInfo::posToLoc(Point3F& loc, const Point2I& p) +{ + S32 index = posToIndex(p); + bool isValid = (index >= 0 && !obstructed(index)); + + loc.x = F32(p.x << gNavGlobs.mSquareShift); + loc.y = F32(p.y << gNavGlobs.mSquareShift); + loc.z = 0.0f; // need actual position at some point. + loc += originWorld; + + return isValid; +} + + + +// Map the given world coordinate into our graph grid. +// Sort of a worldToGrid() routine. +S32 TerrainGraphInfo::locToIndex(Point3F loc) const +{ + loc += gNavGlobs.mHalfSquare; + loc -= originWorld; + loc *= gNavGlobs.mInverseWidth; + return posToIndex( Point2I( mFloor(loc.x), mFloor(loc.y)) ); +} + +// Get the grid location. Returning a pointer since we may +// handle bad indices at some point with a NULL return value. +Point3F * TerrainGraphInfo::indexToLoc(Point3F & loc, S32 index) +{ + Point2I & pos = indexToPos(index); + + loc.x = F32(pos.x << gNavGlobs.mSquareShift); + loc.y = F32(pos.y << gNavGlobs.mSquareShift); + loc.z = 0.0f; + + loc += originWorld; + + return & loc; +} + +bool TerrainGraphInfo::obstructed(const Point3F& loc) const +{ + S32 index = locToIndex(loc); + return (index < 0) || obstructed(index); +} + +Point2I & TerrainGraphInfo::indexToPos(S32 index) const +{ + static Point2I sPoint; + sPoint.x = originGrid.x + index % gridDimensions.x; + sPoint.y = originGrid.y + index / gridDimensions.x; + return sPoint; +} + + +S32 TerrainGraphInfo::locToIndexAndSphere(SphereF & sphereOut, const Point3F & loc) +{ + if( roamRadii.size() ) + { + S32 idx = locToIndex(loc); + if( validArrayIndex(idx, roamRadii.size()) && !obstructed(idx) ) + { + indexToLoc(sphereOut.center, idx); + sphereOut.radius = fixedToFloat(roamRadii[ idx ]); + if( sphereOut.isContained(loc) ) + return idx; + } + } + return -1; +} + +// This function assumes that the grid and navigable flags have been set up. +// +void TerrainGraphInfo::computeRoamRadii() +{ + Vector spiral; + Vector dists; + + S32 radius = getMin( getMax(gridDimensions.x, gridDimensions.y), 20); + S32 scount = getGridSpiral(spiral, dists, radius); + S32 n, s; + + // DMMNOTPRESENT + bool deadlyLiquids = false; +// bool deadlyLiquids = (TerrainRender::mLiquidType >= 4); + + roamRadii.setSize(nodeCount); + + for (n = 0; n < nodeCount; n++) + { + F32 roamDist = 0.0; + F32 extraAvoid = 0.0; + + if (!obstructed(n)) + { + Point2I basePos = indexToPos(n); + U8 saveType = squareType(n); + + for (s = 0; s < scount; s++) + { + Point2I roamPos = basePos + spiral[s]; + S32 roamInd = posToIndex( roamPos ); + + if (!validArrayIndex(roamInd, nodeCount) || obstructed(roamInd)) + break; + + if (steep(roamInd)) + { + extraAvoid = 0.5; + break; + } + + if (saveType != squareType(roamInd)) + { + // If we run into deadly liquids, then cut our roam distance more- + if (deadlyLiquids && submerged(roamInd)) + extraAvoid = 2.0; + break; + } + } + + if( s == scount ) // no hit - can probably just use largest dist + roamDist = dists[s - 1]; + else + roamDist = (dists[s] - 1.4); + } + + F32 roamGridDist = getMax(roamDist - extraAvoid, 0.0f); + + roamRadii[n] = floatToFixed(roamGridDist * gNavGlobs.mSquareWidth); + } +} + +// The following are used to find where to 'inbounds' when out of bounds. That is, +// a location on graph border to (find a closest node to) go to. +void TerrainGraphInfo::setSideSegs() +{ + Point2I corners[4] = { + Point2I(originGrid.x + 0, originGrid.y + 0 ), + Point2I(originGrid.x + gridDimensions.x - 1, originGrid.y + 0 ), + Point2I(originGrid.x + gridDimensions.x - 1, originGrid.y + gridDimensions.y - 1 ), + Point2I(originGrid.x + 0, originGrid.y + gridDimensions.y - 1 ) + }; + + bool Ok[4]; + S32 numOk, p1, p2; + Point3F loc1, loc2; + + for( numOk = p1 = 0; p1 < 4; p1++ ) + { + p2 = (p1 + 1) % 4; + Ok[p1] = posToLoc(loc1, corners[p1] ); + Ok[p2] = posToLoc(loc2, corners[p2] ); + numOk += Ok[p1]; + boundarySegs[p1] = LineSegment(loc1, loc2); + } + + // We need to have warnings in the system if the boundaries don't have nodes. + if( numOk < 4 ) + Con::printf( "Graph Warning! Only %d corners of grid are navigable!", numOk ); +} + +void TerrainGraphInfo::doFinalDataSetup() +{ + // Set up grid area. + gridArea.point = originGrid; + gridArea.extent = gridDimensions; + + setSideSegs(); + + // Offsets for finding neighboring squares when working with indices. + for (S32 dir = 0; dir < 8; dir++ ) { + Point2I off = gridOffs[dir]; + indOffs[dir] = off.y * gridDimensions.x + off.x; + } +} + +Point3F TerrainGraphInfo::whereToInbound(const Point3F & loc) +{ + // Assuming that passed parameter has already filtered with inGraphArea(). + AssertFatal(!inGraphArea(loc), "whereToInbound() only meant for out of bound locs"); + AssertFatal(haveGraph, "whereToInbound() called without a graph set up"); + + S32 solution = -1; + F32 bestDist = 1e9; + + for( S32 i = 0; i < 4; i++ ) + { + F32 d = boundarySegs[i].distance(loc); + + if( d < bestDist ) + bestDist = d, solution = i; + } + + return boundarySegs[solution].solution(); +} + +// +// See if you can go straight across terrain between the given points. Return +// what percentage of the way the system believes can be achieved. +// +// ==> Need to handle out of bounds checks properly. Basically clip to mission +// area and then enter this loop. If not intersecting, we probably return +// 100%, or maybe the check for outside should happen earlier and +// the bot would already know they are free to roam. +// +F32 TerrainGraphInfo::checkOpenTerrain(const Point3F & from, Point3F & to) +{ + SphereF sphere; + S32 prevSphere = locToIndexAndSphere(sphere, from); + + if( prevSphere < 0 ) { + to = from; + return 0.0f; + } + + LineStepper linearPath(from, to); + + while(true) + { + // Find outbound intersection on sphere. (Note WE tell linearPath when to step) + F32 dist = linearPath.getOutboundIntersection(sphere); + + // make it all the way? + if( dist > linearPath.remainingDist() ) + return 1.0; + + // no intersection. only can happen theoretically due to rounding dullness. + if( dist < 0.0f ) + break; + + // See if we're in a new circle (after advancing the stepper). + S32 nextSphere = locToIndexAndSphere(sphere, linearPath.advanceToSolution()); + + // Keep going while we're inside a sphere (and it's not the same one!) + if( nextSphere >= 0 && nextSphere != prevSphere ) + prevSphere = nextSphere; + else + break; + } + + to = linearPath.getSolution(); + return linearPath.distSoFar() / linearPath.totalDist(); +} + +//------------------------------------------------------------------------------------- + +// Return if the given grid position is on the side of the area, and if so we return a +// bitset of all the valid neighbors it _could_ have. See consolidateData() for use. +U32 TerrainGraphInfo::onSideOfArea(S32 index) +{ + Point2I p = indexToPos(index); + U32 retVal = 0xff; + + // is it (well) inside? + if(p.x>originGrid.x && p.xoriginGrid.y && p.y NodeInfoList; +typedef Vector EdgeInfoList; + +class Consolidated : public Vector +{ + public: + bool read(Stream& s) {return readVector1(s, *this);} + bool write(Stream& s) const {return writeVector1(s, *this);} +}; + +//------------------------------------------------------------------------------------- + +struct SpawnLocations : Vector +{ + struct Sphere + { + SphereF mSphere; + bool mInside; + S32 mCount; + S32 mOffset; + S32 mRes0; + + Sphere(const Point3F& center, F32 radius); + bool read(Stream& s); + bool write(Stream& s) const; + }; + typedef Vector Spheres; + + S32 mRes0; + Spheres mSpheres; + + SpawnLocations(); + + S32 getRandom(const SphereF&, bool inside, U32 rnd); + void printInfo() const; + void reset(); + bool read(Stream& s); + bool write(Stream& s) const; +}; + +//------------------------------------------------------------------------------------- + +// Learn information for potential bridges. +// This is old format now- See *BridgeData* below +struct GraphBridgeInfo +{ + enum How {CanWalk=1, MustJet=2}; // ==> Would like to do this: CanFall=4}; ! + S32 dstNode; + S32 srcNode; + F32 jetClear; + U8 howTo; + U8 res1, res2, res3; + + GraphBridgeInfo(S32 src, S32 dst) {init(src, dst);} + GraphBridgeInfo() {init(-1, -1);} + + void init(S32 src, S32 dst); + bool read(Stream& s); + bool write(Stream& s) const; + + void setWalkable() {howTo=CanWalk;} + bool isWalkable() {return (howTo & CanWalk);} + bool mustJet() {return !isWalkable();} +}; + +class BridgeInfoList : public Vector +{ + public: + bool read(Stream& s) {return readVector1(s, *this);} + bool write(Stream& s) const {return writeVector1(s, *this);} +}; + +//------------------------------------------------------------------------------------- + +inline U8 mapJetHopToU8(F32 hop) { + return (U8(mFloor((hop + 0.124) * 8.0))); +} + +inline F32 mapU8ToJetHop(U8 amt) { + return (F32(amt) * 0.125); +} + +struct GraphBridgeData +{ + // ==> Would like to have a CanFall field too.... + enum How {CanWalk=1, MustJet=2, Replacement=4, Unreachable=8}; + U16 nodes[2]; + U8 jetClear; + U8 howTo; + + GraphBridgeData(U16 src, U16 dst) {init(src, dst);} + GraphBridgeData() {init(-1, -1);} + + void init(U16 n0, U16 n1); + bool read(Stream& s); + bool write(Stream& s) const; + + void setWalkable() {howTo |= CanWalk;} + void setReplacement() {howTo |= Replacement;} + void setUnreachable() {howTo |= Unreachable;} + void setHop(F32 dist) {jetClear = mapJetHopToU8(dist);} + bool isWalkable() {return (howTo & CanWalk);} + bool isReplacement() {return (howTo & Replacement);} + bool isUnreachable() {return (howTo & Unreachable);} + bool mustJet() {return !isWalkable();} +}; + +class BridgeDataList : public Vector +{ + S32 mReplaced[2]; + S32 mSaveTotal; + public: + BridgeDataList(); + S32 replaced() const; + S32 numPositiveBridges() const; + S32 accumEdgeCounts(U16 * edgeCounts); + bool readOld(Stream& s); + bool read(Stream& s); + bool write(Stream& s) const; +}; + +//------------------------------------------------------------------------------------- +// See graphVolume.cc + +struct GraphVolume +{ + PlaneF mFloor; + PlaneF mCeiling; + const PlaneF * mPlanes; + S32 mCount; + Vector mCorners; +}; + +struct GraphVolInfo +{ + S32 mPlaneCount; + S32 mPlaneIndex; + bool read(Stream& s) {return s.read(&mPlaneCount) && s.read(&mPlaneIndex);} + bool write(Stream& s) const {return s.write(mPlaneCount) && s.write(mPlaneIndex);} +}; + +class GraphVolumeList : public Vector +{ + bool intersectWalls(S32 i, const PlaneF& with, Vector& list) const; + PlaneF* getPlaneList(S32 i) {return &mPlanes[(*this)[i].mPlaneIndex];} + + public: + Vector mPlanes; + + const PlaneF* planeArray(S32 i) const {return &mPlanes[(*this)[i].mPlaneIndex];} + S32 planeCount(S32 i) const {return this->operator[](i).mPlaneCount;} + PlaneF floorPlane(S32 i) const {return planeArray(i)[planeCount(i)-2];} + PlaneF abovePlane(S32 i) const {return planeArray(i)[planeCount(i)-1];} + S32 memSize() const {return (Vector::memSize() + mPlanes.memSize());} + F32 getMinExt(S32 i, Vector& ptBuffer); + bool closestPoint(S32 ind, const Point3F& point, Point3F& soln) const; + S32 getCorners(S32 i, Vector& pts, bool noTop=false) const; + void addVolume(Point3F pos, F32 boxw, F32 boxh); + void cullUnusedPlanes(); + void nudgeVolumesOut(); + + bool read(Stream& s); + bool write(Stream& s) const; +}; + +//------------------------------------------------------------------------------------- + +struct ChuteHint : public Point3F +{ + bool read(Stream& s) {return s.read(&x) && s.read(&y) && s.read(&z);} + bool write(Stream& s) const {return s.write(x) && s.write(y) && s.write(z);} + const Point3F& location() {return *this;} // for BSP-er +}; + +typedef AxisAlignedBSP ChuteBSP; +typedef VectorPtr ChutePtrList; + +class ChuteHints : public Vector +{ + ChuteBSP mBSP; + ChutePtrList mQuery; + void makeBSP(); + public: + enum Info {NotAChute, ChuteTop, ChuteMid}; + S32 findNear(const Point3F& P, S32 xy, S32 z, ChutePtrList& list) const; + S32 findNear(const Box3F& box, ChutePtrList& list) const; + void init(const Vector& list); + Info info(Point3F bot, const Point3F& top); + bool read(Stream& s); + bool write(Stream& s) const; +}; + +//------------------------------------------------------------------------------------- + +struct PathXRefEntry : public BitVectorW +{ + bool read(Stream& s); + bool write(Stream& s) const; +}; + +class PathXRefTable : public Vector +{ + public: + ~PathXRefTable(); + bool read(Stream& s); + bool write(Stream& s) const; + void setSize(S32 size); + void clear(); +}; + +//------------------------------------------------------------------------------------- + +class LOSTable +{ + public: + enum TwoBitCodes {Hidden, MinorLOS, MuzzleLOS, FullLOS}; + virtual U32 value(S32 ind1, S32 ind2) const = 0; + virtual bool valid(S32 numNodes) const = 0; + virtual bool write(Stream& s) const = 0; + virtual bool read(Stream& s) = 0; + virtual void clear() = 0; + + bool hidden(S32 i1, S32 i2) const { return (value(i1, i2) == Hidden); } + bool fullLOS(S32 i1, S32 i2) const { return (value(i1, i2) == FullLOS); } + bool muzzleLOS(S32 i1, S32 i2) const { return (value(i1, i2) >= MuzzleLOS); } +}; + +//------------------------------------------------------------------------------------- + +class LOSXRefTable : public BitVectorW, public LOSTable +{ + public: + void setEntry(S32 ind1, S32 ind2, U32 val); + U32 value(S32 ind1, S32 ind2) const; + bool valid(S32 numNodes) const; + void clear(); + bool read(Stream& s); + bool write(Stream& s) const; +}; + +//------------------------------------------------------------------------------------- + +class LOSHashTable : public LOSTable +{ + public: + enum{ SegShift = 6, // seems to be the magic # + SegSize = (1 << SegShift), + SegMask = (SegSize - 1), + SegAlloc = (1 << SegShift - 2) + }; + + protected: + union Key { + struct { + U16 mNode, mSeg; + } mKey; + U32 mCompare; + Key(U16 node = 0, U16 seg = 0) {mKey.mNode = node; mKey.mSeg = seg;} + }; + + struct Segment { + U8 mLOS[SegAlloc]; + Key mKey; + Segment(Key key, const U32 losInfo[SegSize]); + U32 value(U32 i) const {return 3 & mLOS[i >> 2] >> ((i & 3) << 1);} + bool read(Stream& s); + bool write(Stream& s) const; + }; + + typedef Vector SegmentList; + typedef U16 IndexType; + + protected: + static U32 mTabSz; + IndexType * mTable; + SegmentList mSegments; + U32 mNumNodes; + U32 mRes0, mRes1; + + protected: + static U32 calcHash(Key key); + static S32 QSORT_CALLBACK cmpHashes(const void* ,const void* ); + U32 calcTabSize(); + void sortByHashVal(); + U32 makeTheTable(); + + public: + LOSHashTable(); + ~LOSHashTable(); + U32 convertTable(const LOSXRefTable& from, S32 numNodes); + U32 value(S32 ind1, S32 ind2) const; + bool valid(S32 numNodes) const; + bool write(Stream& s) const; + bool read(Stream& s); + void clear(); +}; + +//------------------------------------------------------------------------------------- +// Data saved on graph object in MIS file- + +// Configures how nodes are consolidated. +struct ConjoinConfig +{ + ConjoinConfig(); + F32 maxAngleDev; + F32 maxBowlDev; + S32 maxLevel; +}; + +//------------------------------------------------------------------------------------- +// Consolidation data structures. Used in computing, not persisted. + +struct GridNormalInfo +{ + bool notEmpty; + bool hasSteep; + VectorF normals[2]; + Point2F angles[2]; + GridNormalInfo(); + static Point2F normalToAngle(const VectorF& N); + static VectorF angleToNormal(const Point2F& A); +}; + +#define MaxConsolidateLevel 6 + +class TrackLevels +{ + Vector achievedLevels; + Vector nodeTypes; + public: + TrackLevels(S32 sz); + void setAchievedLevel(S32 index, S32 level); + S32 getAchievedLevel(S32 index) const; + S32 originalNodeLevel(S32 idx) const; + U16 getNodeType(S32 index) const; + void setNodeType(S32 index, U8 nodeType); + S32 size() const; + void capLevelAt(S32 idx, U16 lev); + void init(S32 size); +}; + +//------------------------------------------------------------------------------------- + +struct TerrainGraphInfo +{ + static const Point2I gridOffs[8]; + static S32 smVersion; + + // false until data is set or loaded: + bool haveGraph; + + // persisted data: + S32 nodeCount; + S32 numShadows; + Point3F originWorld; + Point2I originGrid; + Point2I gridDimensions; + Point2I gridTopRight; + Vector navigableFlags; + Vector neighborFlags; + Vector shadowHeights; + Vector roamRadii; + Consolidated consolidated; + + // Data computed from the above at load/initialize: + LineSegment boundarySegs[4]; + S32 indOffs[8]; + GridArea gridArea; + + TerrainGraphInfo(); + + // small utility functions. + Point2I& indexToPos(S32 index) const; + Point3F* indexToLoc(Point3F& locOut, S32 index); + bool posToLoc(Point3F& locOut, const Point2I& p); + S32 posToIndex(Point2I pos) const; + S32 locToIndex(Point3F loc) const; + S32 locToIndexAndSphere(SphereF& s, const Point3F& L); + bool obstructed(const Point3F& L) const; + U8 squareType(S32 n) const {return (navigableFlags[n] & 7);} + bool obstructed(S32 n) const {return squareType(n)==GraphNodeObstructed;} + bool shadowed(S32 n) const {return squareType(n)==GraphNodeShadowed;} + bool submerged(S32 n) const {return squareType(n)==GraphNodeSubmerged;} + bool haveConsData() const {return consolidated.size() > 0;} + F32 shadowHeight(S32 n) const {return shadowHeights[n];} + bool inGraphArea(const Point3F& loc) const {return locToIndex(loc) >= 0;} + bool steep(S32 n) const {return navigableFlags[n] & GroundNodeSteep;} + void setSteep(S32 n) {navigableFlags[n] |= GroundNodeSteep;} + + // not-as-small utility functions + // S32 needFloorPlanMojo(Vector& listOut, TerrainBlock* terr); + Point3F whereToInbound(const Point3F& loc); + F32 checkOpenTerrain(const Point3F& from, Point3F& to); + U32 onSideOfArea(S32 index); + + // persist + bool read(Stream & s); + bool write(Stream & s) const; + + // Calculation stuff (consolidation), precomputes, setup.. + bool buildConsolidated(const TrackLevels & trackInf, const ConjoinConfig & config); + bool consolidateData(const ConjoinConfig & config); + S32 markNodesNearSteep(); + void computeRoamRadii(); + void getGridNormals(Vector&); + void setSideSegs(); + + // After new data is created or loaded, this doest last needed calcs, fixups, etc. + void doFinalDataSetup(); +}; + +#endif diff --git a/ai/graphDebug.cc b/ai/graphDebug.cc new file mode 100644 index 0000000..401cbce --- /dev/null +++ b/ai/graphDebug.cc @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +static void cantFind(const Point3F& P) +{ + Con::printf("Can't find node near (%f %f %f)", P.x, P.y, P.z); +} + +// See if the given player can get from point A to point B. +bool NavigationGraph::testPlayerCanReach(Point3F A, Point3F B, const F32* ratings) +{ + Vector indexList; + bool success = false; + GraphSearch * searcher = getMainSearcher(); + + if (GraphNode * src = closestNode(A)) + { + if (GraphNode * dst = closestNode(B)) + { + searcher->setAStar(true); + searcher->performSearch(src, dst); + searcher->setRatings(ratings); + success = searcher->getPathIndices(indexList); + } + else + cantFind(B); + } + else + cantFind(A); + + return success; +} + +//------------------------------------------------------------------------------------- + +F32 NavigationGraph::performTests(S32 count, bool enableAStar) +{ + // Seed off count - so test happens same for same count. + MRandomR250 rand(count); + S64 dijkstraIters = 0; + S64 relaxCount = 0; + U32 numSearchesDone = 0; + Vector indexList; + + // Get our searcher- + GraphSearch * searcher = getMainSearcher(); + + // Perform random path searches. numNodes() ignores transients. + while (--count >= 0) + { + if (GraphNode * src = mNodeList[(rand.randI()&0xFFFFFFF) % numNodes()]) + { + if (GraphNode * dst = mNodeList[(rand.randI()&0xFFFFFFF) % numNodes()]) + { + numSearchesDone++; + searcher->setAStar(enableAStar); + dijkstraIters += searcher->performSearch(src, dst); + relaxCount += searcher->relaxCount(); + searcher->getPathIndices(indexList); + } + } + } + + if (numSearchesDone) + { + if (relaxCount) + { + F64 relaxAverage = F64(relaxCount) / F64(numSearchesDone); + Con::printf("Average edge relaxations = %f", F32(relaxAverage)); + } + return F32(F64(dijkstraIters) / F64(numSearchesDone)); + } + + return 0; +} + +//------------------------------------------------------------------------------------- + +// Flag (for render) those nodes having LOS from the given closest point. We check +// in and out radii, and the condition is what we compare to. Used for testing. +void NavigationGraph::markNodesInSight(const Point3F& from, F32 in, F32 out, U32 cond) +{ + if (!validLOSXref()) + { + warning("markNodesInSight() called without valid XRef table"); + return; + } + + if (GraphNode * src = closestNode(from)) + { + S32 srcInd = src->getIndex(); + for (S32 i = 0; i < mNonTransient.size(); i++) + { + GraphNode * cur = mNonTransient[i]; + F32 dist = (from - cur->location()).len(); + S32 curInd = cur->getIndex(); + bool markIt = false; + + if (dist > in && dist < out && curInd != srcInd) + markIt = (mLOSTable->value(srcInd, curInd) == cond); + + if (markIt) + cur->set(GraphNode::Render0); + else + cur->clear(GraphNode::Render0); + } + } + else + warning("markNodesInSight() couldn't find closest node"); +} + diff --git a/ai/graphDefines.h b/ai/graphDefines.h new file mode 100644 index 0000000..baaf3ce --- /dev/null +++ b/ai/graphDefines.h @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHDEFINES_H_ +#define _GRAPHDEFINES_H_ + +enum GridOffsets { + GridBottomLeft, + GridBottom, GridLeft, + GridBottomRight, GridTopLeft, + GridRight, GridTop, + GridTopRight, + NumGridOffsets +}; + +enum NavGraphDefinitions { + GraphNodeClear = 0, // Navigable flags on the terrain grid information. + GraphNodeShadowed, // We're using 4, allow up to 8 types. + GraphNodeObstructed, + GraphNodeSubmerged, + GroundNodeSteep = (1 << 3), // Signals next to unwalkable slope. + + GraphThreatLimit = 64, + GraphMaxTeams = 32, + AbsMaxBotCount = 64, +}; + +// 2.5 minutes- +#define GraphMaxNodeAvoidMS 150000 + +// Pull-in amount used to avoid rounding errors in indoor node volume obstuction checks +#define NodeVolumeShrink 0.014f +#define UncappedNodeVolumeHt 1000.0f + +// Bridge builder assures jet connections at least a certain amount. Jet code uses a little +// smaller threshold (for not attempting) since bots might not be right on point- +#define GraphJetBridgeXY 1.7f +#define GraphJetFailXY 1.3f + +#define MaxGraphNodeVolRad 25.0f + +#define LiberalBonkXY 21.0f + + +// Repository for all graph dimension numbers. +struct NavGraphGlobals +{ + // Terrain: + F32 mSquareWidth; + F32 mInverseWidth; + F32 mSquareRadius; + S32 mSquareShift; + Point3F mHalfSquare; + + void setTerrNumbers(S32 shift) + { + mSquareShift = shift; + mSquareWidth = F32(1 << shift); + mInverseWidth = 1.0 / mSquareWidth; + mSquareRadius = F32(1 << shift-1); + mHalfSquare.set(mSquareRadius - 0.01, mSquareRadius - 0.01, 0.0); + } + + // Walk / jet / jump configuration numbers + F32 mWalkableDot; + F32 mWalkableAngle; + F32 mJumpAdd; + F32 mTallestPlayer; + F32 mStepHeight; + F32 mPrettySteepAngle; + F32 mPrettySteepDot; + + void setWalkData(F32 angle, F32 stepHeight) + { + mWalkableAngle = mDegToRad(angle); + mWalkableDot = mCos(mWalkableAngle); + mJumpAdd = 2.0; + mTallestPlayer = 2.3; + mStepHeight = stepHeight; + + // Angle slightly less than max used to decide to make a transient connection + // jettable between two points outdoors. cf. graphLocate.cc + mPrettySteepAngle = mWalkableAngle * (6.0 / 7.0); + mPrettySteepDot = mCos(mPrettySteepAngle); + } + + // Build a default version - + NavGraphGlobals() + { + setTerrNumbers(3); // Default shift value + setWalkData(70, 0.75); // Walk angle (min of all maxes), step height + } +}; + +// Defined in NavigationGraph.cc- +extern NavGraphGlobals gNavGlobs; + +#endif diff --git a/ai/graphDijkstra.cc b/ai/graphDijkstra.cc new file mode 100644 index 0000000..66e5204 --- /dev/null +++ b/ai/graphDijkstra.cc @@ -0,0 +1,377 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +// Two special back-index entries are defined for if element has been extracted or +// has not been in the queue yet. DefNotInQueue uses -1 so we can memset() array. +#define DefExtracted (1<<30) +#define DefNotInQueue GraphQIndex(-1) +#define MaskExtracted (DefExtracted-1) +#define NodeExtracted(x) (((x) & DefExtracted) && (x) > 0) +#define FlagExtracted(x) ((x)|= DefExtracted) +#define NotInQueue(x) ((x)==DefNotInQueue) +#define DoDiagnostic(x) {x;} + +// The Q and Q indices are shared among all searchers. They're kept relatively private +// by using a GraphSearch subclass which is only friends with GraphSearch. +// +GraphSearch::GraphSearch() + : mHead(-1, F32(-1), F32(-1)), + mQueue(gNavGraph->mSharedSearchLists.searchQ), + mQIndices(gNavGraph->mSharedSearchLists.qIndices), + mHeuristicsVec(gNavGraph->mSharedSearchLists.heuristics), + mPartition(gNavGraph->mSharedSearchLists.partition) +{ + mHead.mTime = -1; + mExtractNode = NULL; + mVisitOnExtract = true; + mVisitOnRelax = true; + mEarlyOut = true; + mSearchDist = 0.0f; + mTargetNode = -1; + mSourceNode = -1; + mIterations = 0; + mRelaxCount = 0; + mThreatSet = 0; + mInformThreats = 0; + mInformTeam = 0; + mInformRatings = NULL; + mInProgress = false; + mAStar = false; + mTargetLoc.set(0,0,0); + initializeIndices(); + mHeuristicsPtr = NULL; + mRandomize = false; +} + +bool GraphSearch::initializeIndices() +{ + if (NavigationGraph::gotOneWeCanUse()) + { + if (gNavGraph->numNodesAll() != mQIndices.size()) + { + S32 totalNodes = gNavGraph->numNodesAll(); + AssertFatal(totalNodes < (1 << 15), "Graph size can't exceed 32K"); + mHeuristicsVec.setSize(totalNodes); + mQIndices.setSize(totalNodes); + mPartition.setSize(totalNodes); + + // Largest search includes just two transients + mQueue.reserve(gNavGraph->numNodes() + 2); + + doLargeReset(); + return true; + } + } + return false; +} + +void GraphSearch::doSmallReset() +{ + mPartition.clear(); +#if 1 + register S32 i = mQueue.size(); + register SearchRef * searchRef = &mQueue[0]; + while (--i >= 0) { + mQIndices[searchRef->mIndex] = DefNotInQueue; + mHeuristicsVec[ (searchRef++)->mIndex ] = 0; + } +#else + for (S32 i = mQueue.size() - 1; i >= 0; i--) { + S32 whichNode = mQueue[i].mIndex; + mQIndices[whichNode] = DefNotInQueue; + mHeuristicsVec[whichNode] = 0; + } +#endif +} + +void GraphSearch::doLargeReset() +{ + dMemset(mQIndices.address(), 0xff, mQIndices.memSize()); + dMemset(mHeuristicsVec.address(), 0, mHeuristicsVec.memSize()); + mPartition.clear(); +} + +void GraphSearch::resetIndices() +{ + if (! initializeIndices()) { + if (mQueue.size()) { // Compare size of search queue to total node count. + S32 ratio = mQIndices.size() / mQueue.size(); + if (ratio > 7) { + doSmallReset(); + return; + } + } + //==> Note could queue size sometimes be clear and the following would happen? + doLargeReset(); + } +} + +// Mainly for access to accumulated userdata on the nodes- +SearchRef* GraphSearch::getSearchRef(S32 nodeIndex) +{ + S32 queueInd = mQIndices[nodeIndex]; + AssertFatal(!NotInQueue(queueInd), "getSearchRef() called with non-queued node"); + return &mQueue[queueInd & MaskExtracted]; +} + +// This version is just a straight lookup using a Q index. +SearchRef* GraphSearch::lookupSearchRef(S32 queueIndex) +{ + return &mQueue[queueIndex & MaskExtracted]; +} + +inline SearchRef* GraphSearch::insertSearchRef(S32 nodeIndex, F32 dist, F32 sort) +{ + S32 vecIndex = mQueue.size(); + mQIndices[nodeIndex] = vecIndex; + SearchRef assemble(nodeIndex, dist, sort); + mQueue.insert(assemble); + return &mQueue[vecIndex]; +} + +// Called when randomizing to get edge scale factor. Turn off randomizing after a point +// to limit the slowdown caused by the (sometimes much) larger search expansion. +S32 GraphSearch::getAvoidFactor() +{ + if (mIterations > 80) + mRandomize = false; + else { + // Note unsigned compare important - + U32 timeDiff = (mExtractNode->avoidUntil() - mCurrentSimTime); + if (timeDiff < GraphMaxNodeAvoidMS) + if (mExtractNode->stuckAvoid()) + return 400; + else + return 25; + } + return 0; +} + +F32 GraphSearch::calcHeuristic(const GraphEdge* edge) +{ + F32 dist = mHeuristicsPtr[edge->mDest]; + if (dist == 0) + { + #if 0 + GraphNode * destNode = gNavGraph->lookupNode(edge->mDest); + dist = (destNode->location() - mTargetLoc).len(); + #else + // ==> Want to try a crude distance function here to avoid square root. + RegularNode * destNode = static_cast(gNavGraph->lookupNode(edge->mDest)); + dist = (destNode->mLoc - mTargetLoc).len(); + #endif + mHeuristicsPtr[edge->mDest] = dist; + } + return dist; +} + +// Compute new time along the edge. This is virtual since special searches +// want to play with it (cf. dist-only based searches, LOS avoid searches). +F32 GraphSearch::getEdgeTime(const GraphEdge* edge) +{ + F32 edgeTime = (edge->mDist * edge->getInverse()); + + AssertFatal(!edge->problems(), edge->problems()); + + // Weight for jetting capabilities- edges that can't be traversed. Don't do it with + // transient connections (partition predictions must work out true). + if (mJetRatings && edge->isJetting() && edge->mDest < mTransientStart) + if (!edge->canJet(mJetRatings)) + return SearchFailureAssure; + + // Edges that only one team can go through, namely (working) force fields- + if (mTeam && edge->getTeam()) + if (edge->getTeam() != mTeam) + return SearchFailureAssure; + else + edgeTime *= 1.3; + + return edgeTime; +} + +bool GraphSearch::runDijkstra() +{ + mCurrentSimTime = Sim::getCurrentTime(); + + while (SearchRef * head = mQueue.head()) + { + // Make COPY of head since list can move! Also, 'visitors' make use of the info. + mHead = * head; + mQueue.removeHead(); + + // This means search failed + if (mHead.mTime > SearchFailureThresh) + break; + + // Mark visited- + mPartition.set(mHead.mIndex); + + // Check if done- + if (mTargetNode == mHead.mIndex) { + mSearchDist = mHead.mDist; + break; + } + + // set the extracted node. visitor may use it. + mExtractNode = gNavGraph->lookupNode(mHead.mIndex); + + // callback visit - subclass uses extractedNode() to access current + if (mVisitOnExtract) + onQExtraction(); + + // Avoidance of nodes for randomize or for stuckness. + S32 avoidThisNode = (mRandomize ? getAvoidFactor() : false); + + // Check for threat avoidance. + bool nodeThreatened = (mThreatSet && (mExtractNode->threats() & mThreatSet)); + + // Mark this node as extracted, remove from Queue. Do after onQExtraction(). + GraphQIndex headIndex = mQIndices[mHead.mIndex]; + FlagExtracted(mQIndices[mHead.mIndex]); + + // Relax all neighbors (or add to queue in first place) + GraphEdgeArray edgeList = mExtractNode->getEdges(mEdgeBuffer); + while (GraphEdge * edge = edgeList++) + { + GraphQIndex queueInd = mQIndices[edge->mDest]; + register SearchRef * searchRef; + + if (! NodeExtracted(queueInd)) + { + F32 newDist = mHead.mDist + edge->mDist; + F32 edgeTime = getEdgeTime(edge); + if (nodeThreatened) + edgeTime *= 10; + if (avoidThisNode) + edgeTime += avoidThisNode; + F32 newTime = mHead.mTime + edgeTime; + F32 sortOnThis = (mAStar ? newTime + calcHeuristic(edge) : newTime); + + // Relax dist for neighbor (1st time is a "relax" down from infinity) + if (NotInQueue(queueInd)) { + searchRef = insertSearchRef(edge->mDest, newDist, sortOnThis); + searchRef->mPrev = headIndex; + searchRef->mTime = newTime; + } + else if (sortOnThis < (searchRef = &mQueue[queueInd])->mSort) { + searchRef->mTime = newTime; + searchRef->mSort = sortOnThis; + searchRef->mDist = newDist; + searchRef->mPrev = headIndex; + mQueue.changeKey(queueInd); + } + else + continue; // didn't relax - must skip relax callback + + DoDiagnostic(mRelaxCount++); + if (mVisitOnRelax) + onRelaxEdge(edge); + } + } + + mIterations++; + + if (mEarlyOut && earlyOut()) + return (mInProgress = true); + } + + return (mInProgress = false); +} + +void GraphSearch::initSearch(GraphNode * S, GraphNode * D) +{ + mIterations = 0; + mRelaxCount = 0; + mInProgress = true; + mSearchDist = 0.0f; + mTransientStart = gNavGraph->numNodes(); + mSourceNode = S->getIndex(); + + // Set up target if present. A* requires target. + if (D) + mTargetNode = D->getIndex(), mTargetLoc = D->location(); + else + mTargetNode = -1, mInformAStar = false; + + // These four search-modifying variables hold their value for only one search- + mAStar = mInformAStar; /*---------*/ mInformAStar = false; + mThreatSet = mInformThreats; /*---------*/ mInformThreats = 0; + mTeam = mInformTeam; /*---------*/ mInformTeam = 0; + mJetRatings = mInformRatings; /*---------*/ mInformRatings = NULL; + + // Avoid bad inlining in debug build... + mHeuristicsPtr = mHeuristicsVec.address(); + + // Cleanup from last search- + resetIndices(); + + // Set source as first element in Q- + mQueue.clear(); + insertSearchRef(S->getIndex(), 0, 0)->mTime = 0; + mQueue.buildHeap(); +} + +//------------------------------------------------------------------------------------- + +// Find list of node indices on search that just happened. If search had target start +// back from there, else use parameter (custom searches). Return if target reached. +bool GraphSearch::getPathIndices(Vector & indices, S32 target) +{ + indices.clear(); + + if(target < 0) + target = mTargetNode; + + AssertFatal(target >= 0, "Bad use of getPathIndices"); + + if (mPartition.test(target)) + { + // Get the list of node indices going back from target to source. Since these are + // queue indices, 0 is start (source node is first in Q), hence the while (prev). + S32 prev = (mQIndices[target] & MaskExtracted); + if (prev < mQIndices.size()) + { + while (prev) + { + indices.push_back(mQueue[prev].mIndex); + prev = mQueue[prev].mPrev; + } + indices.push_back(mSourceNode); + reverseVec(indices); + return true; + } + } + return false; +} + +// D can be NULL (used for visitation expansions). +S32 GraphSearch::performSearch(GraphNode * S, GraphNode * D) +{ + if( D && D->island() != S->island() ) + return -1; + + initSearch(S, D); + runDijkstra(); + + return mIterations; +} + +// Search that can be interupted. Returns true while in progress. User is responsible +// for not calling it again if the search has finished. cf. earlyOut() virtual. +bool GraphSearch::runSearch(GraphNode * S, GraphNode * D) +{ + if (D && D->island() != S->island()) + return false; + + if (!mInProgress) + initSearch(S, D); + + return runDijkstra(); +} + diff --git a/ai/graphFind.cc b/ai/graphFind.cc new file mode 100644 index 0000000..fc4c89b --- /dev/null +++ b/ai/graphFind.cc @@ -0,0 +1,562 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +U32 gCallsToClosestNode = 0; + +//------------------------------------------------------------------------------------- + +bool NavigationGraph::possibleToJet(const Point3F& from, const Point3F& to, U32) +{ + // To be refined- + return (from - to).len() < 60; +} + +//------------------------------------------------------------------------------------- +// This routine is used by the seed scattering approach - generate points that are +// inside nearby volumes, reflecting across one of the walls. + +S32 NavigationGraph::crossNearbyVolumes(const Point3F& from, Vector& points) +{ + Point3F size(20, 20, 20); + Box3F checkBox(from, from); + checkBox.min -= size; + checkBox.max += size; + GraphNodeList nodeList; + + points.clear(); + if (S32 N = getNodesInBox(checkBox, nodeList, true)) + { + // Find ones we're outside of a little ways - i.e. positive containment, but not + // too low a number. The caller will do LOS checks... Also, we verify that we're + // below the cieling as well. + for (S32 i = 0; i < N; i++) + { + GraphNode * node = nodeList[i]; + NodeProximity prox = node->containment(from); + + // Make sure we're somewhat near. Note Z check needs to come last here... + if ((F32(prox) > 0.1) && (F32(prox) < 6.0) && prox.insideZ()) + { + // It's a candidate. Now find closest point on volume.... + Point3F soln; + if (closestPointOnVol(node, from, soln)) + { + // ...and reflect across point.. + VectorF inVec(soln.x - from.x, soln.y - from.y, 0.0); + inVec.normalize(0.8); + soln += inVec; + soln.z = solveForZ(getFloorPlane(node), soln) + 0.2; + + // See if that point is reasonably well inside the volume... + if (node->containment(soln) < -0.3) + points.push_back(soln); + } + } + } + } + return points.size(); +} + +//------------------------------------------------------------------------------------- + +S32 NavigationGraph::getNodesInBox(Box3F worldBox, GraphNodeList& listOut, bool justIndoor) +{ + // Box is assumed to be square in XY, get "radius" + Point3F center; + worldBox.getCenter(¢er); + + listOut.clear(); + + if (!justIndoor && haveTerrain()) + { + // Fetch terrain nodes- + F32 rad = worldBox.len_x() * 0.5; + S32 gridRadius = S32(rad / gNavGlobs.mSquareWidth) + 1; + GridArea gridArea = getGridRectangle(center, gridRadius); + getNodesInArea(listOut, gridArea); + } + + // Fetch from the indoor node BSP (this one doesn't clear the list) + Point3F radExt(MaxGraphNodeVolRad, MaxGraphNodeVolRad, MaxGraphNodeVolRad); + worldBox.min -= radExt; + worldBox.max += radExt; + mIndoorTree.getIntersecting(listOut, worldBox); + + return listOut.size(); +} + +bool NavigationGraph::haveMuzzleLOS(S32 nodeInd1, S32 nodeInd2) +{ + if (nodeInd1 != nodeInd2 && mValidLOSTable) + return mLOSTable->muzzleLOS(nodeInd1, nodeInd2); + else + return true; +} + +//------------------------------------------------------------------------------------- + +const GraphNodeList& NavigationGraph::getVisibleNodes(GraphNode * from, const Point3F& loc, F32 rad) +{ + mUtilityNodeList1.clear(); + mUtilityNodeList2.clear(); + + if (from) + { + SphereF sphere(loc, rad); + + mUtilityNodeList1.clear(); + + if (haveTerrain()) + { + // Fetch terrain. + S32 gridRadius = S32(rad / gNavGlobs.mSquareWidth) + 1; + GridArea gridArea = getGridRectangle(loc, gridRadius); + getNodesInArea(mUtilityNodeList1, gridArea); + } + + // Fetch from the indoor node BSP (this one doesn't clear the list) + Box3F checkBox(loc, loc, true); + rad += MaxGraphNodeVolRad; + Point3F radExt(rad, rad, rad); + checkBox.min -= radExt; + checkBox.max += radExt; + mIndoorTree.getIntersecting(mUtilityNodeList1, checkBox); + + S32 fromInd = from->getIndex(); + + for (S32 i = 0; i < mUtilityNodeList1.size(); i++) + if (haveMuzzleLOS(fromInd, mUtilityNodeList1[i]->getIndex())) + mUtilityNodeList2.push_back(mUtilityNodeList1[i]); + } + return mUtilityNodeList2; +} + +const GraphNodeList& NavigationGraph::getVisibleNodes(const Point3F& loc, F32 rad) +{ + FindGraphNode finder(loc); + return getVisibleNodes(finder.closest(), loc, rad); +} + +//------------------------------------------------------------------------------------- + +#define CrossingSegAdjustUp 0.3 + +// Figure out what constitutes a crossing path. +S32 NavigationGraph::crossingSegs(const GraphNode * node, const GraphEdge * edge, + LineSegment * const segBuffer) +{ + LineSegment * segs = segBuffer; + Point3F nodeLoc = node->location(); + Point3F destLoc = lookupNode(edge->mDest)->location(); + + nodeLoc.z += CrossingSegAdjustUp; + destLoc.z += CrossingSegAdjustUp; + + if (edge->isBorder()) { + Point3F crossingPt = getBoundary(edge->mBorder).midpoint(); + crossingPt.z += CrossingSegAdjustUp; + (segs++)->set(nodeLoc, crossingPt); + (segs++)->set(crossingPt, destLoc); + } + else { + // Do non-jetting like jetting as well - otherwise we miss collisions. + // if (edge->isJetting()) { + Point3F arcCorner; + if (nodeLoc.z > destLoc.z) + (arcCorner = destLoc).z = nodeLoc.z; + else + (arcCorner = nodeLoc).z = destLoc.z; + (segs++)->set(nodeLoc, arcCorner); + (segs++)->set(arcCorner, destLoc); + } + + return (segs - segBuffer); +} + +// Find all edges that are blocked by the given object. This is used by the force field +// monitor code, called at mission start to find affected edges. +const GraphEdgePtrs& NavigationGraph::getBlockedEdges(GameBase* object, U32 typeMask) +{ + Box3F objBox = object->getWorldBox(); + GraphEdge edgeBuffer[MaxOnDemandEdges]; + LineSegment segments[4]; + RayInfo collision; + GraphNodeList consider; + + mVisibleEdges.clear(); + if (S32 numNodes = getNodesInBox(objBox, consider)) { + for (S32 i = 0; i < numNodes; i++) { + GraphNode * node = consider[i]; + GraphEdgeArray edges = node->getEdges(edgeBuffer); + while (GraphEdge * edge = edges++) + for (S32 j = crossingSegs(node, edge, segments) - 1; j >= 0; j--) { + Point3F A = segments[j].getEnd(0); + Point3F B = segments[j].getEnd(1); + // Quick crude test- + if (objBox.collideLine(A, B)) + // Now do LOS, must hit object in question. We must be careful + // that force fields don't overlap in navigable area. + if (gServerContainer.castRay(A, B, typeMask, &collision)) + if (collision.object == object) { + mVisibleEdges.push_back(edge); + break; + } + } + } + } + + // mVisibleEdges is a generic utility buffer for several queries, user copies off + return mVisibleEdges; +} + +//------------------------------------------------------------------------------------- + +// For an outdoor node location, fetch the roaming radius off the terrain graph +// information. Return 0.0 if nothing found. +F32 NavigationGraph::getRoamRadius(const Point3F &loc) +{ + SphereF sphere; + Point3F loc2D(loc.x, loc.y, 0.0f); + + if (mTerrainInfo.inGraphArea(loc2D)) + if (mTerrainInfo.locToIndexAndSphere(sphere, loc2D) >= 0) + return sphere.radius; + + return 0.0f; +} + +//------------------------------------------------------------------------------------- + +// Patch function to translate into block space. 10/27/00- added optional normal +bool NavigationGraph::terrainHeight(Point3F pos, F32 * height, Point3F * normal) +{ + pos -= mTerrainInfo.originWorld; + if (mTerrainBlock) { + Point2F point2F(pos.x, pos.y); + if (mTerrainBlock->getHeight(point2F, height)) { + if (normal != NULL) + mTerrainBlock->getNormal(point2F, normal, true); + return true; + } + } + return false; +} + +// Look for terrain node here using grid lookup. +GraphNode * NavigationGraph::findTerrainNode(const Point3F & atLocation) +{ + Point3F loc; + bool inArea; + + if (haveTerrain()) { + // Bots can enter outside of area- we don't do height check below for such locs. + if (mTerrainInfo.inGraphArea(atLocation)) { + inArea = true; + loc = atLocation; + } + else { + inArea = false; + loc = mTerrainInfo.whereToInbound(atLocation); + } + + GraphNode * node = NULL; + S32 gridIndex = mTerrainInfo.locToIndex(loc); + + if (gridIndex >= 0) { + if (GraphNode * node = mNodeGrid[gridIndex]) { + if (inArea) { + F32 height; + if (terrainHeight(loc, &height)) + { + if (height < (loc.z + 0.04)) + { + // Point3F terrLoc = node->location(); + // F32 nodeZ = terrLoc.z; + F32 terrHt = node->terrHeight(); + if (loc.z < (height + terrHt)) + return node; + } + } + } + else { + return node; + } + } + } + } + + return NULL; +} + +// If the findTerrainNode() fails, we see if we're on terrain with a +// node that is within one grid location. +GraphNode * NavigationGraph::nearbyTerrainNode(const Point3F& loc) +{ + GraphNode * node = NULL; + + if (haveTerrain()) + { + F32 H; + S32 gridIndex = mTerrainInfo.locToIndex(loc); + + if( (gridIndex >= 0) && terrainHeight(loc, &H) && (H < loc.z + 0.04) ) + { + // look for an immediately neighboring node - find the nearest one + S32 *offs = mTerrainInfo.indOffs, i, n; + F32 bestDist = 1e9, dSq; + for (i = 0; i < 8; i++) + if (validArrayIndex(n = gridIndex + offs[i], mTerrainInfo.nodeCount)) + if (GraphNode* N = mNodeGrid[n]) + if ( (dSq = (loc - N->location()).lenSquared()) < bestDist ) + bestDist = dSq, node = N; + } + } + + return node; +} + +GraphNode * NavigationGraph::closestNode(const Point3F& loc, F32 * containment) +{ + GraphNode * terrNode = findTerrainNode(loc); + + gCallsToClosestNode++; + + if (terrNode) + return terrNode; + else + { + terrNode = nearbyTerrainNode(loc); + + Box3F box(loc, loc, true); + Point3F boundUp(0, 0, 20); + Point3F boundDown(0, 0, 80); + Point3F boundOut(35, 35, 0); + + box.max += boundUp; + box.max += boundOut; + box.min -= boundDown; + box.min -= boundOut; + + GraphNodeList indoor; + + // We really need to use two trees - one for big nodes, and one for smaller. + // This will keep the searches much more intelligent- oversize only a little + // for the tree with small nodes - and so we'll not get so many. Oversize + // a lot for the big nodes - but there aren't nearly as many of them anyway. + mIndoorTree.getIntersecting(indoor, box); + + // Find the node with the best containment metric + GraphNode * bestNode = NULL; + F32 bestMetric = 1e13; + F32 metric; + for (GraphNodeList::iterator i = indoor.begin(); i != indoor.end(); i++) { + metric = F32((*i)->containment(loc)); + if (metric < bestMetric) { + bestMetric = metric; + bestNode = *i; + } + } + + if (! bestNode) + return terrNode; + else { + if (terrNode) { + //==> Move this to a terrain containment function - + F32 terrMetric = (terrNode->location() - loc).len() - terrNode->radius(); + if (terrMetric < bestMetric) + return terrNode; + } + if (containment) + *containment = bestMetric; + return bestNode; + } + } +} + +//------------------------------------------------------------------------------------- +// Structure which remembers results of searches, and which are also kept in a hash +// table as well. Shows up in profiles now that we use canReachLoc() a lot in the +// objective weighting. + +FindGraphNode::FindGraphNode() +{ + init(); +} + +// Manage loc-to-node searches since many are the same. +FindGraphNode::FindGraphNode(const Point3F& pt, GraphNode* hint) +{ + init(); + setPoint(pt, hint); +} + +void FindGraphNode::init() +{ + mClosest = NULL; + // Should be safe value that no-one will use: + mPoint.set(-3.14159e14, 3.1e13, -2.22e22); +} + +U32 FindGraphNode::calcHash(const Point3F& point) +{ + register const U32 * hashNums = (const U32 *)(&point); + U32 val0 = (hashNums[0] >> 7) ^ (hashNums[0] << 5); + U32 val1 = (hashNums[1] + 77773) & 0xFFFFFFF; + U32 val2 = (hashNums[2] * 37) & 0xFFFFFFF; + return (val0 + val1 + val2) % HashTableSize; +} + +void FindGraphNode::setPoint(const Point3F& point, GraphNode * hint/*=0*/) +{ + // If point has not changed- we're done. + if (point == mPoint) + return; + + // Hash point into our cache (which NavigationGraph holds for us)- + mPoint = point; + U32 hash = calcHash(mPoint); + FindGraphNode & cacheEntry = gNavGraph->mFoundNodes[hash]; + + // See if hash table contains it. + if (cacheEntry.mPoint == point) + { + mClosest = cacheEntry.mClosest; + } + else + { + // If there's a hint - see if we're inside it - that is then the closest. Don't + // put these in the hash table though since the hints usually refer to locations + // that are moving, and we don't want them walking over our cache. + if (hint && hint->containment(point) < 0) + { + mClosest = hint; + } + else + { + // Otherwise do the search- + mClosest = gNavGraph->closestNode(point); + + // Put into cache. We do this even for NULL results. + cacheEntry = * this; + } + } +} + +//------------------------------------------------------------------------------------- +// Grid <-> World Translation Methods + +GridArea NavigationGraph::getGridRectangle(const Point3F& atPos, S32 gridRadius) +{ + GridArea atRect; + Point2I atGridLoc; + + worldToGrid(atPos, atGridLoc); + atRect.point.x = atGridLoc.x - gridRadius; + atRect.point.y = atGridLoc.y - gridRadius; + atRect.extent.x = atRect.extent.y = (gridRadius << 1); + return atRect; +} + +void NavigationGraph::worldToGrid(const Point3F &wPos, Point2I &gPos) +{ + TerrainBlock *terrain = GroundPlan::getTerrainObj(); + Point3F origin; + terrain->getTransform().getColumn(3, &origin); + + float x = (wPos.x - origin.x) / (float)terrain->getSquareSize(); + float y = (wPos.y - origin.y) / (float)terrain->getSquareSize(); + + gPos.x = (S32)mFloor(x); + gPos.y = (S32)mFloor(y); +} + +Point3F NavigationGraph::gridToWorld(const Point2I& gPos) +{ + Point3F retVal; + if (!mTerrainInfo.posToLoc(retVal, gPos)) + retVal.set(-1, -1, -1); + return retVal; +} + +GridArea NavigationGraph::getWorldRect() +{ + return GridArea(mTerrainInfo.originGrid, mTerrainInfo.gridDimensions); +} + +//------------------------------------------------------------------------------------- +// Visitor to find terrain nodes in rectangular area. + +class VisitNodeGrid : public GridVisitor +{ + protected: + const GridArea& mWorld; // invariant is that the mNodeGrid + const GraphNodeList& mGrid; // corresponds to mWorld area. + GraphNodeList& mListOut; + + GraphNode * getNodeAt(const GridArea& R); + + public: + VisitNodeGrid( const GridArea& toVisit, const GridArea& worldArea, + const GraphNodeList& gridList, GraphNodeList& listOut ) + : GridVisitor(toVisit), mWorld(worldArea), + mGrid(gridList), mListOut(listOut) { } + + bool beforeDivide(const GridArea& R, S32 level); + bool atLevelZero(const GridArea& R); +}; + +GraphNode * VisitNodeGrid::getNodeAt(const GridArea& R) +{ + S32 index = mWorld.getIndex(R.point); + AssertFatal( validArrayIndex(index, mGrid.size()), "VisitNodeGrid- bad index" ); + return mGrid[index]; +} + +bool VisitNodeGrid::beforeDivide(const GridArea& R, S32 level) +{ + if (GraphNode * node = getNodeAt(R)) { + if(node->getLevel() == level) { + mListOut.push_back(node); + return false; + } + } + return true; +} + +bool VisitNodeGrid::atLevelZero(const GridArea& R) +{ + if (GraphNode * node = getNodeAt(R)) + mListOut.push_back( node ); + return true; //N/A +} + +// Query list for list of nodes within a certain area. +S32 NavigationGraph::getNodesInArea(GraphNodeList& listOut, GridArea toVisit) +{ + listOut.clear(); + if (haveTerrain()) + { + GridArea worldRect = getWorldRect(); + if (mNodeGrid.size() == (worldRect.extent.x * worldRect.extent.y)) + { + if (toVisit.intersect(worldRect)) + { + VisitNodeGrid visitor(toVisit, worldRect, mNodeGrid, listOut); + visitor.traverse(); + } + } + } + return listOut.size(); +} + +void NavigationGraph::getOutdoorList(GraphNodeList& to) +{ + to.setSize(mNumOutdoor); + dMemcpy(to.address(), mNodeList.address(), mNumOutdoor * sizeof(GraphNode*)); +} diff --git a/ai/graphFloorPlan.cc b/ai/graphFloorPlan.cc new file mode 100644 index 0000000..602e31d --- /dev/null +++ b/ai/graphFloorPlan.cc @@ -0,0 +1,2392 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graphFloorPlan.h" +#include "console/consoleTypes.h" +#include "interior/interiorResObjects.h" +#include "game/gameBase.h" +#include "ai/graphData.h" +#include "core/fileStream.h" + + +//---------------------------------------------------------------- +// +// FloorPlan Implementation +// +//---------------------------------------------------------------- + +FloorPlan *gFloorPlan = NULL; + +IMPLEMENT_CONOBJECT(FloorPlan); +static F32 sCollinearDist = 0.01; +static F32 sParallelDot = 0.992; +Point3F FloorPlan::sDebugBreakPoint(-11.31f, -132.0f, 131.4f); +F32 FloorPlan::sFloorThresh = 70; +F32 FloorPlan::sMinDivRadius = 0.8; +F32 FloorPlan::sMaxDivRadius = 20; +bool FloorPlan::sSubDivide = true; +bool FloorPlan::sDrawConnections = true; +bool FloorPlan::sDrawVolumeHts = false; +F32 FloorPlan::sHeightDiff = 10; +F32 FloorPlan::sSpecialMaxRadius = 1; +bool FloorPlan::sUseSpecial = false; +S32 FloorPlan::amountToDivide = 3; +bool FloorPlan::sDisableAsserts = false; +bool FloorPlan::LogMode = false; + +FloorPlan::FloorPlan() +{ + mGraphDetails = NULL; + currInterior = NULL; + currInstance = NULL; + mNumInteriors = 0; + mExtraInteriors = 0; + mDataCollected = false; + newLog = true; + numZones = 0; +} + +//------------------------------------------------------------------------------------- + +#define _FP_DEBUG_ 1 + +#if _FP_DEBUG_ +static void debugStopPt() +{ +} +#endif + +//---------------------------------------------------------------------------- + +FloorPlan::~FloorPlan() +{ + if(mGraphDetails) + delete [] mGraphDetails; +} + +//---------------------------------------------------------------------------- + +bool FloorPlan::onAdd() +{ + if(!Parent::onAdd()) + return false; + + gFloorPlan = this; + return true; +} + +//---------------------------------------------------------------------------- + +void FloorPlan::onRemove() +{ + Parent::onRemove(); + gFloorPlan = NULL; +} + +//---------------------------------------------------------------------------- + +static void findObjectsCallback(SceneObject *obj, S32 val) +{ + Vector *list = (Vector *)val; + list->push_back(obj); +} + +//---------------------------------------------------------------------------- +// Sift though all the objects that are in the Server container. +// Extract the data if its an interior. +//---------------------------------------------------------------------------- +void FloorPlan::snoopInteriors() +{ + U32 mask = InteriorObjectType; + Vector objects; + gServerContainer.findObjects(mask, findObjectsCallback, (S32)&objects); + + mNumInteriors = objects.size(); + mGraphDetails = new GraphDetails[mNumInteriors+1024]; + mSlopeThresh = mCos(mDegToRad(sFloorThresh)); + + for(S32 i = 0; i < mNumInteriors; i++) + { + InteriorInstance *intInstant = dynamic_cast(objects[i]); + MatrixF const &transform = intInstant->getTransform(); + Point3F const &scale = intInstant->getScale(); + Interior *interior = intInstant->getDetailLevel(0); + currInterior = interior; + currInstance = intInstant; + numZones = interior->mZones.size(); + +#if !DEBUGMODE + + InteriorDetail detail; + +#endif + + detail.mIndex = i; + + /* + char buffer[256]; + dSprintf(buffer, sizeof(buffer), "interiors/%s", intInstant->mInteriorFileName); + Resource interiorRes; + interiorRes = ResourceManager->load(buffer); + */ + + // Special nodes (basically just the chute hints) + for(S32 g = 0; g < intInstant->mInteriorRes->getNumSpecialNodes(); g++) + { + AISpecialNode * node = intInstant->mInteriorRes->getSpecialNode(g); + + // Scale and transform to world space + Point3F loc = node->mPos; + loc.x *= scale.x; + loc.y *= scale.y; + loc.z *= scale.z; + transform.mulP(loc); + mGraphDetails[i].chutes.push_back(loc); + + // DSpecNode sNode; + // sNode.pos = node->mPos; + // dStricmp(node->mName, sNode.name); + // sNode.pos.x *= scale.x; + // sNode.pos.y *= scale.y; + // sNode.pos.z *= scale.z; + // transform.mulP(sNode.pos); + // detail.mSpecialNodes.push_back(sNode); + } + + detail.mPoints.setSize(interior->mPoints.size()); + for(S32 j = 0; j < detail.mPoints.size(); j++) + { + detail.mPoints[j].e = NULL; + detail.mPoints[j].x = interior->mPoints[j].point.x; + detail.mPoints[j].y = interior->mPoints[j].point.y; + detail.mPoints[j].z = interior->mPoints[j].point.z; + detail.mPoints[j].x *= scale.x; + detail.mPoints[j].y *= scale.y; + detail.mPoints[j].z *= scale.z; + transform.mulP(detail.mPoints[j]); + detail.mPoints[j].flags.clear(); + } + + detail.mNumUnObstructed = 0; + + // extract all plane information + extractData(interior, &detail, transform); + + +#if ITERATEMODE + + for(S32 divideCount = 0; divideCount < amountToDivide; divideCount++) + { + Con::printf(""); + Con::printf(""); + Con::printf(""); + Con::printf("interation: %d", divideCount); + subdivideSurfaces(&detail); + } +#else + + // subdivide until we have a workable graph for this interior + while(subdivideSurfaces(&detail) > 0) + mDivLevel++; +#endif + // sort surface list based on zone + sortSurfaceList(&detail); + + // connect all volumes + buildConnections(&detail); + + // extract the graph + graphExtraction(&detail, &(mGraphDetails[i])); + + buildNodesFromPortals(&detail); + + log(avar("Interior %d Volumes: %d", i, detail.mVolumes.size())); + +#if !DEBUGMODE + + // clean up memory... YAY!! + detail.mEdgeTable.clear(); + for(S32 x = 0; x < detail.mSurfaces.size(); x++) + delete [] detail.mSurfaces[x].mEdges; + +#endif + + } + +#if !DEBUGMODE + + // external static shape hook + buildStaticShapeCenterNodes(); + buildStaticShapeGeometryNodes(); + +#endif +} + +//---------------------------------------------------------------------------- + +void FloorPlan::log(const char *string) +{ + if(!LogMode) + return; + + FileStream LogFile; + + LogFile.open("GenMetrics.log", FileStream::ReadWrite); + + if(LogFile.getStatus() == Stream::Ok) + { + LogFile.setPosition(LogFile.getStreamSize()); + + LogFile.write(dStrlen(string), string); + LogFile.write(2, "\r\n"); + } + + LogFile.close(); +} + +//---------------------------------------------------------------------------- +// The whole idea here is too extract all data from the passed interior. +// Once all data has been extracted, surfaces that won't be needed +// ex: surfaces that a bot can't traverse. are thrown out. +// What we should end up with are floor surfaces, which will then be +// flooded with nodes(subdivision) and finally connected together to +// form the graph for this interior +//---------------------------------------------------------------------------- +void FloorPlan::extractData(Interior *interior, + InteriorDetail *detail, + const MatrixF &transform) +{ + detail->mSurfaces.setSize(interior->mSurfaces.size()); + Interior::Surface surf; + + // extract out the portals we need from this interior + extractPortals(interior, detail, transform); + + U32 i; + for(i = 0; i < detail->mSurfaces.size(); i++) + { + // grab a surface.... + surf = interior->mSurfaces[i]; + DSurf *currSurf = &(detail->mSurfaces[i]); + + currSurf->mFlags.clear(); + currSurf->mFlipStates.clear(); + + S32 vertCount = surf.windingCount; + currSurf->mEdges = new DEdge *[vertCount]; + currSurf->mDivLevel = 0; + currSurf->parent = NULL; + currSurf->mSubSurfCount = 0; + + // go ahead and extract + interior->collisionFanFromSurface(surf, currSurf->fullWinding, &(currSurf->mNumEdges)); + + // cull out all unwanted surfaces. Ceilings...ect + PlaneF plane = interior->getPlane(surf.planeIndex); + if(Interior::planeIsFlipped(surf.planeIndex)) + plane.neg(); + + VectorF normal = plane; + transform.mulV(normal); + currSurf->mNormal = normal; + if(normal.z <= 0) + { + currSurf->mFlags.set(DSurf::isWall); + currSurf->mFlags.set(DSurf::shouldBeCulled); + } + + // what kind of slope does this surface have? + if(!currSurf->mFlags.test(DSurf::shouldBeCulled)) + { + F32 dot = mDot(normal, VectorF(0, 0, 1)); + if(dot < mSlopeThresh) + currSurf->mFlags.set(DSurf::shouldBeCulled); + } + + // keep a handle to this surface for quick traversable + // surface only operations + if(!currSurf->mFlags.test(DSurf::shouldBeCulled)) + detail->mTraversable.push_back(i); + } + + // build all edges for this interior + buildEdgeTable(detail); + buildZoneInfo(detail); +} + +//-------------------------------------------------------------------------- + +void FloorPlan::sortSurfaceList(InteriorDetail *d) +{ + d->sortedSurfList.clear(); + d->sortedSurfList = d->mUnobstructed; + d->mUnobstructed.clear(); + + for(S32 zone = -1; zone < numZones; zone++) + { + for(S32 surfCount = 0; surfCount < d->sortedSurfList.size(); surfCount++) + { + U32 key = d->sortedSurfList[surfCount]; + DSurf &surf = d->mSurfaces[key]; + if(surf.mZone == zone) + d->mUnobstructed.push_back(key); + } + } +} + +//-------------------------------------------------------------------------- + +void FloorPlan::buildZoneInfo(InteriorDetail *d) +{ + S32 surfCount = d->mTraversable.size(); + DPoint center; + MatrixF transform = currInstance->mWorldToObj; + Point3F scale = currInstance->getScale(); + + for(S32 j = 0; j < surfCount; j++) + { + S32 whichSurface = d->mTraversable[j]; + DSurf &surf = d->mSurfaces[whichSurface]; + createCenterNode(d, &surf, ¢er); + center.z += 1; + transform.mulP(center); + center.x /= scale.x; + center.y /= scale.y; + center.z /= scale.z; + + surf.mZone = currInterior->getZoneForPoint(center); + } +} + +//-------------------------------------------------------------------------- + +static const F32 sPortalThresh = mCos(mDegToRad(20.0f)); + +void FloorPlan::extractPortals(Interior *i, InteriorDetail *d, const MatrixF &transform) +{ + S32 count = i->mPortals.size(); + for(S32 pIdx = 0; pIdx < count; pIdx++) + { + Interior::Portal *sourcePortal = &(i->mPortals[pIdx]); + U32 index = sourcePortal->planeIndex; + PlaneF plane = i->getPlane(index); + if(Interior::planeIsFlipped(index)) + plane.neg(); + + VectorF normal = plane; + transform.mulV(normal); + + if(mFabs(normal.z) > sPortalThresh) + { + DPortal portal; + if(buildPortalCenter(d, i, sourcePortal, &portal)) + d->mPortals.push_back(portal); + } + else + continue; + } +} + +//-------------------------------------------------------------------------- + +bool FloorPlan::buildPortalCenter(InteriorDetail *d, Interior *interior, Interior::Portal *sourcePortal, DPortal *portal) +{ + // extract the trifans and find center + Point3F center(0, 0, 0); + //Point3F start(interior->mPoints[interior->mWindings[interior->mWindingIndices[sourcePortal->triFanStart]]].point); + //transform.mulP(start); + + for(U32 j = 0; j < sourcePortal->triFanCount; j++) + { + const Interior::TriFan &fan = interior->mWindingIndices[sourcePortal->triFanStart + j]; + U32 numPoints = fan.windingCount; + + if(!numPoints) + continue; + + for(U32 k = 0; k < numPoints; k++) + { + const Point3F &pt = d->mPoints[interior->mWindings[fan.windingStart + k]]; + center += pt; + } + + center /= numPoints; + portal->center = center; + } + + // only use portals that are big enough for players to fit thru + if(1)//(center - start).len() >= 1.0) + return true; + else + return false; +} + +//-------------------------------------------------------------------------- + +void FloorPlan::buildEdgeTable(InteriorDetail *detail) +{ + U32 i, k; + for(i = 0; i < detail->mSurfaces.size(); i++) + { + DSurf *currSurf = &(detail->mSurfaces[i]); + + for(k = 0; k < currSurf->mNumEdges; k++) + { + DEdge edge; + edge.left = NULL; + edge.right = NULL; + edge.start = currSurf->fullWinding[k]; + edge.flags.clear(); + // edge.totalBelongsTo = 0; + if(!currSurf->mFlags.test(DSurf::shouldBeCulled)) + edge.flags.set(DEdge::traversable); + + if(k == currSurf->mNumEdges - 1) + edge.end = currSurf->fullWinding[0]; + else + edge.end = currSurf->fullWinding[k+1]; + + if(!detail->mEdgeTable.contains(edge)) + { + edge.length = (detail->mPoints[edge.end] - detail->mPoints[edge.start]).len(); + detail->mEdgeTable.insert(edge); + currSurf->mEdges[k] = detail->mEdgeTable.find(edge); + detail->mPoints[edge.start].e = currSurf->mEdges[k]; + } + else + { + currSurf->mEdges[k] = detail->mEdgeTable.find(edge); + if(!detail->mPoints[edge.start].e) + detail->mPoints[edge.start].e = currSurf->mEdges[k]; + + if(edge.start != currSurf->mEdges[k]->start) + currSurf->mFlipStates.set(BIT(k)); + } + } + + // done! compute some more. + findLongestEdge(currSurf); + computeSurfaceRadius(detail, currSurf); + + if(currSurf->mFlags.test(DSurf::isWall)) + setToWallNodes(detail, *currSurf); + } +} + +//------------------------------------------------------------------------- + +// Handle thin nodes differently. +bool FloorPlan::shouldDivideSurf(DSurf &surf) +{ + if (surf.mMinRadius < 0.4) { + if (surf.mMinRadius > 0.2) + { + bool canDiv = surf.mMaxRadius >= 2.5; + if(canDiv) + return true; + else + { + surf.mFlags.set(DSurf::tooSmall); + return false; + } + } + else + { + surf.mFlags.set(DSurf::tooSmall); + return false; + } + } + else + return surf.mMaxRadius >= sMinDivRadius; +} + +//-------------------------------------------------------------------------- + +void FloorPlan::setToWallNodes(InteriorDetail *d, DSurf &surf) +{ + U32 i; + for(i = 0; i < surf.mNumEdges; i++) + { + d->mPoints[surf.mEdges[i]->start].flags.set(DPoint::wall); + d->mPoints[surf.mEdges[i]->end].flags.set(DPoint::wall); + } +} + +//-------------------------------------------------------------------------- + +U32 FloorPlan::subdivideSurfaces(InteriorDetail *d) +{ + U32 totalDivided = 0; + + // Traversable array grows during below loop - we only want to do the ones that + // are ready at this iteration of the divide. + S32 surfCount = d->mTraversable.size(); + + for(S32 j = 0; j < surfCount; j++) + { + S32 whichSurface = d->mTraversable[j]; + + DSurf &surf = d->mSurfaces[whichSurface]; + + if(surf.mNumEdges >= 32) + continue; + + if (shouldDivideSurf(surf)) + { + if(!surf.mFlags.test(DSurf::unObstructed | DSurf::divided)) + { + // check for obstructions + DVolume vol; + surfCollisionInfo surfColInfo; + + if((obstructedSurf(d, surf, &vol, surfColInfo)) || (surf.mMaxRadius > sMaxDivRadius)) + { + // something is obstructing this surf, so divided it down + // so as to isolate the surfaces that are obstructed + if(!surfColInfo.specialCase) + { + //Con::printf("subdividing surface %d", whichSurface); + subdivideSurface(d, surf, whichSurface); + } + else + { + //Con::printf("split surface %d", whichSurface); + //if(whichSurface == 26) + //subdivideSurface(d, surf, whichSurface); + //else + splitSurface(d, surf, whichSurface, surfColInfo); + } + totalDivided++; + } + /* even though this surface is traversable we need + to check if there are some little holes in the ceiling + that players can jet out of. If there are, just divide more. + + else if(offHeightDistribution(d, surf)) + { + subdivideSurface(d, surf, whichSurface); + totalDivided++; + } + + this surface is unobstructed!*/ + else + { + surf.mFlags.set(DSurf::unObstructed); + createCenterNode(d, &surf, &(surf.mCntr)); + vol.capPt.x = surf.mCntr.x; + vol.capPt.y = surf.mCntr.y; + vol.capPt.z = (surf.mCntr.z + vol.ht); + vol.surfIdx = whichSurface; + surf.volIdx = d->mVolumes.size(); + d->mVolumes.push_back(vol); + d->mUnobstructed.push_back(whichSurface); + } + } + } + } + return totalDivided; +} + +//---------------------------------------------------------------------------- + +void FloorPlan::subdivideSurface(InteriorDetail *d, DSurf &surf, U32 surfIdx) +{ + BitSet32 edgeTracker; + U32 newSurfCount = 0; + DEdge *edgePtr; + + U32 i; + for(i = 0; i < surf.mNumEdges; i++) + { + edgePtr = surf.mEdges[i]; + if(shouldDivideEdge(surf, edgePtr)) + { + edgeTracker.set(BIT(i)); + newSurfCount++; + if(!edgePtr->flags.test(DEdge::divided)) + subdivideEdge(d, edgePtr, 0.5f); + } + } + + // we have a special case here + if(newSurfCount < 2) + { + edgeTracker.clear(); + newSurfCount = 0; + for(i = 0; i < surf.mNumEdges; i++) + { + edgePtr = surf.mEdges[i]; + if(!shouldDivideEdge(surf, edgePtr) && edgePtr->length >= 2) + { + edgeTracker.set(BIT(i)); + subdivideEdge(d, edgePtr, 0.5f); + newSurfCount++; + } + } + if(newSurfCount < 2) + { + surf.mFlags.set(DSurf::divided); + return; + } + } + + // create all the new surfaces + surf.mSubSurfCount = newSurfCount; + DEdge **edges = new DEdge *[32]; + + if(newSurfCount > 2) + { + DPoint center; + createCenterNode(d, &surf, ¢er); + center.flags.clear(); + center.flags.set(DPoint::divGenerated | DPoint::center); + d->mPoints.push_back(center); + U32 centIdx = (d->mPoints.size() - 1); + generateNewEdges(d, centIdx, surf, edges, edgeTracker); + createNewSurfaces(d, surfIdx, edges, edgeTracker); + } + else + { + newSurfacesSpecialCase(d, surfIdx, edgeTracker); + } + delete [] edges; +} + +//---------------------------------------------------------------------------- + +void FloorPlan::splitSurface(InteriorDetail *d, DSurf &surf, U32 surfIdx, surfCollisionInfo &info) +{ + BitSet32 edgeTracker; + surf.mSubSurfCount = 2; + F32 newFactor = info.factor - (0.1 / surf.mEdges[info.divideEdge1]->length); + + edgeTracker.set(BIT(info.divideEdge1)); + edgeTracker.set(BIT(info.divideEdge2)); + + if(!surf.mFlipStates.test(BIT(info.divideEdge1))) + subdivideEdge(d, surf.mEdges[info.divideEdge1], newFactor); + else + subdivideEdge(d, surf.mEdges[info.divideEdge1], 1 - newFactor); + + if(!surf.mFlipStates.test(BIT(info.divideEdge2))) + subdivideEdge(d, surf.mEdges[info.divideEdge2], 1 - newFactor); + else + subdivideEdge(d, surf.mEdges[info.divideEdge2], newFactor); + + newSurfacesSpecialCase(d, surfIdx, edgeTracker); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::newSurfacesSpecialCase(InteriorDetail *d, U32 surfIdx, BitSet32 &edgeTracker) +{ + U32 edgeIdx[2]; + U32 count = 0; + + d->mSurfaces[surfIdx].mFlags.set(DSurf::split); + d->mSurfaces[surfIdx].mFlags.set(DSurf::divided); + + U32 i; + for(i = 0; i < d->mSurfaces[surfIdx].mNumEdges; i++) + if(edgeTracker.test(BIT(i))) + edgeIdx[count++] = i; + + DEdge *edge1 = d->mSurfaces[surfIdx].mEdges[edgeIdx[0]]; + DEdge *edge2 = d->mSurfaces[surfIdx].mEdges[edgeIdx[1]]; + + DEdge midEdge; + midEdge.start = edge1->midPoint; + midEdge.end = edge2->midPoint; + midEdge.flags.set(DEdge::divGenerated); + midEdge.length = (d->mPoints[midEdge.end] - d->mPoints[midEdge.start]).len(); + d->mEdgeTable.insert(midEdge); + DEdge *edge = d->mEdgeTable.find(midEdge); + + // start with a divided edge + U32 edgeCount = 0; // keeps track of the master surf's edge idx + while(!edgeTracker.test(BIT(edgeCount))) + edgeCount++; + + for(i = 0; i < 2; i++) + { + DSurf newSurf; + DSurf *currSurf = &newSurf; + currSurf->mEdges = new DEdge *[32]; + currSurf->mDivLevel = 0; + currSurf->mNormal = d->mSurfaces[surfIdx].mNormal; + currSurf->mFlags.clear(); + currSurf->mFlipStates.clear(); + currSurf->parent = &(d->mSurfaces[surfIdx]); + currSurf->mSubSurfCount = 0; + currSurf->mZone = d->mSurfaces[surfIdx].mZone; + + U32 edgeIdx = 0; // keeps track of new surf edge idx's + + // add the first edge to the new surf's edge list + if(!d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) + { + currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->right; + } + else + { + currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->left; + currSurf->mFlipStates.set(BIT(edgeIdx - 1)); + } + + // wrap around + if(edgeCount == (d->mSurfaces[surfIdx].mNumEdges - 1)) + edgeCount = 0; + else + edgeCount++; + + // keep adding edges if they are not divided + while(!edgeTracker.test(BIT(edgeCount))) + { + if(d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) + currSurf->mFlipStates.set(BIT(edgeIdx)); + currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]; + + // wrap around + if(edgeCount == d->mSurfaces[surfIdx].mNumEdges - 1) + edgeCount = 0; + else + edgeCount++; + } + + // add the last generated edges to complete this surf + if(!d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) + { + currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->left; + } + else + { + currSurf->mEdges[edgeIdx] = d->mSurfaces[surfIdx].mEdges[edgeCount]->right; + currSurf->mFlipStates.set(BIT(edgeIdx)); + edgeIdx++; + } + + currSurf->mEdges[edgeIdx] = edge; + edgeIdx++; + + if(!i) + currSurf->mFlipStates.set(BIT(edgeIdx - 1)); + + // update total edges for this surf + currSurf->mNumEdges = edgeIdx; + + // this surf is created, update other members. + findLongestEdge(currSurf); + computeSurfaceRadius(d, currSurf); + + d->mSurfaces[surfIdx].mSubSurfs[i] = d->mSurfaces.size(); + d->mSurfaces.push_back(newSurf); + d->mTraversable.push_back(d->mSurfaces.size() - 1); + } +} + +//---------------------------------------------------------------------------- + +void FloorPlan::createNewSurfaces(InteriorDetail *d, U32 surfIdx, DEdge **edges, BitSet32 &edgeTracker) +{ + // start with a divided edge + U32 edgeCount = 0; // keeps track of the outer surf's edge idx + while(!edgeTracker.test(BIT(edgeCount))) + edgeCount++; + + U32 i; + for(i = 0; i < d->mSurfaces[surfIdx].mSubSurfCount; i++) + { + DSurf newSurf; + DSurf *currSurf = &newSurf; + currSurf->mEdges = new DEdge *[32]; + currSurf->mDivLevel = 0; + currSurf->mNormal = d->mSurfaces[surfIdx].mNormal; // all new surfs are co-planar + currSurf->mFlags.clear(); + currSurf->mFlipStates.clear(); + currSurf->parent = &(d->mSurfaces[surfIdx]); + currSurf->mSubSurfCount = 0; + currSurf->mZone = d->mSurfaces[surfIdx].mZone; + + U32 edgeIdx = 0; // keeps track of new surf edge idx's + U32 startEdge = edgeCount; // keeps track of which edge we started at + + // add the first edge to the new surf's edge list + if(!d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) + { + currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->right; + } + else + { + currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->left; + currSurf->mFlipStates.set(BIT(edgeIdx - 1)); + } + + // wrap around + if(edgeCount == (d->mSurfaces[surfIdx].mNumEdges - 1)) + edgeCount = 0; + else + edgeCount++; + + // keep adding edges if they are not divided + while(!edgeTracker.test(BIT(edgeCount))) + { + if(d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) + currSurf->mFlipStates.set(BIT(edgeIdx)); + currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]; + + // wrap around + if(edgeCount == d->mSurfaces[surfIdx].mNumEdges - 1) + edgeCount = 0; + else + edgeCount++; + } + + // add the last generated edges to complete this surf + if(!d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) + { + currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->left; + } + else + { + currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->right; + currSurf->mFlipStates.set(BIT(edgeIdx - 1)); + } + + currSurf->mEdges[edgeIdx++] = edges[edgeCount]; + currSurf->mEdges[edgeIdx++] = edges[startEdge]; // this will be flipped for sure + currSurf->mFlipStates.set(BIT(edgeIdx - 1)); + currSurf->mNumEdges = edgeIdx; + + // this surf is created, update other members. + findLongestEdge(currSurf); + computeSurfaceRadius(d, currSurf); + + d->mSurfaces[surfIdx].mSubSurfs[i] = d->mSurfaces.size(); + d->mSurfaces.push_back(newSurf); + d->mTraversable.push_back(d->mSurfaces.size() - 1); + } + + d->mSurfaces[surfIdx].mFlags.set(DSurf::divided); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::findLongestEdge(DSurf *surf) +{ + F32 longest = 0; + DEdge *longPtr; + DEdge *edgePtr; + + U32 i; + for(i = 0; i < surf->mNumEdges; i++) + { + edgePtr = surf->mEdges[i]; + if(edgePtr->length > longest) + { + longPtr = edgePtr; + longest = edgePtr->length; + } + } + surf->mLongest = longPtr; +} + +//---------------------------------------------------------------------------- + +void FloorPlan::subdivideEdge(InteriorDetail *d, DEdge *edge, F32 factor) +{ + DPoint &p1 = d->mPoints[edge->start]; + DPoint &p2 = d->mPoints[edge->end]; + + // subdivide the edge + DPoint p3; + + p3.x = p1.x + ((p2.x - p1.x) * factor); + p3.y = p1.y + ((p2.y - p1.y) * factor); + p3.z = p1.z + ((p2.z - p1.z) * factor); + + p3.flags.set(DPoint::divGenerated); + p3.e = edge; + + // if both are wall nodes, so is this one + if(p1.flags.test(DPoint::wall) && p2.flags.test(DPoint::wall)) + p3.flags.set(DPoint::wall); + + d->mPoints.push_back(p3); + U32 idx = (d->mPoints.size() - 1); + edge->midPoint = idx; + DEdge e1, e2; + e1.flags.clear(); + e2.flags.clear(); + e1.flags.set(DEdge::divGenerated | DEdge::traversable); + e2.flags.set(DEdge::divGenerated | DEdge::traversable); + + // construct the new edges and add them to the edgeTable + e1.start = edge->start; + e1.end = idx; + e2.start = idx; + e2.end = edge->end; + + e1.right = e1.left = e1.next = NULL; + + F32 length = edge->length - (edge->length * factor); + e2.length = length; + e2.right = e2.left = e2.next = NULL; + e1.length = (edge->length * factor); + d->mEdgeTable.insert(e1); + d->mEdgeTable.insert(e2); + edge->right = d->mEdgeTable.find(e2); + edge->left = d->mEdgeTable.find(e1); + edge->flags.set(DEdge::divided); +} + +//---------------------------------------------------------------------------- + +// only call this after subdividing all edges of this surface and after +// creating its center node +void FloorPlan::generateNewEdges(InteriorDetail *d, U32 cenIdx, DSurf &surf, DEdge **edges, BitSet32 &edgeTracker) +{ + DEdge *edgePtr; + U32 count = 0; + + U32 i; + for(i = 0; i < surf.mNumEdges; i++) + { + edgePtr = surf.mEdges[i]; + if(edgeTracker.test(BIT(i))) + { + DEdge edge; + edge.start = edgePtr->midPoint; + edge.end = cenIdx; + edge.length = (d->mPoints[edge.end] - d->mPoints[edge.start]).len(); + edge.flags.clear(); + edge.flags.set(DEdge::divGenerated | DEdge::traversable); + edge.right = edge.left = edge.next = NULL; + d->mEdgeTable.insert(edge); + edges[i] = d->mEdgeTable.find(edge); + } + else + edges[i] = NULL; + } +} + +//---------------------------------------------------------------------------- +// use the center of mass of a poly as the center of this surf. +void FloorPlan::createCenterNode(InteriorDetail *d, DSurf *surf, DPoint *CM) +{ + Point2F* points = new Point2F[surf->mNumEdges]; + F32 heightSum = 0; + + for(S32 i = 0; i < surf->mNumEdges; i++) + { + S32 ind = (surf->mFlipStates.test(BIT(i)) ? surf->mEdges[i]->end : surf->mEdges[i]->start); + points[i].x = d->mPoints[ind].x; + points[i].y = d->mPoints[ind].y; + heightSum += d->mPoints[ind].z; + } + + Point2F c; + bool ok = polygonCM(surf->mNumEdges, points, &c); + + CM->x = c.x; + CM->y = c.y; + CM->z = heightSum/surf->mNumEdges; + CM->e = surf->mEdges[0]; + + // trying to track a bug here. + bool nodeOk = validatePoint(*CM); + + if(!nodeOk) + { + CM = NULL; + return; + } + + delete [] points; +} + +//---------------------------------------------------------------------------- + +void FloorPlan::graphExtraction(InteriorDetail *d, GraphDetails *g) +{ + EdgeInfoList edges; + NodeInfoList nodes; + EdgeInfoList segs; + GraphVolumeList volumes; + U32 indexCount = 0; + BitVector tracker; + U32 *reMap; + GraphEdgeInfo *edgePtr; + IndoorNodeInfo *nodePtr; + GraphVolInfo *volPtr; + U32 planeCount = 0; + + // Take each edge from each traversable surface and first remap + // its verts into the new vertex array, and then finely add the + // edge to the edge list. I can't possibly see how a stray node + // that isn't part of any edge could be passed to the navGraph. + U32 j, k; + reMap = new U32[d->mConPoints.size()]; + tracker.setSize(d->mConPoints.size()); + tracker.clear(); + + for(j = 0; j < d->mConnections.size(); j++) + { + DConnection &con = d->mConnections[j]; + U32 p1Idx = con.start; + U32 p2Idx = con.end; + + // get each node index of this edge. remap it then add it + // to the point list + if(!tracker.test(p1Idx)) + { + tracker.set(p1Idx); + reMap[p1Idx] = indexCount; + nodes.increment(); + nodePtr = &(nodes[indexCount++]); + nodePtr->pos = Point3F(d->mConPoints[p1Idx].x, d->mConPoints[p1Idx].y, d->mConPoints[p1Idx].z); + nodePtr->flags.clear(); + + // add volume info + DVolume *vol = &(d->mVolumes[d->mSurfaces[d->mConPoints[p1Idx].surfIdx].volIdx]); + volumes.increment(); + volPtr = &(volumes[volumes.size() - 1]); + volPtr->mPlaneCount = vol->mNumPlanes; + volPtr->mPlaneIndex = planeCount; + for(k = 0; k < vol->mNumPlanes; k++) + { + volumes.mPlanes.increment(); + volumes.mPlanes[planeCount++] = vol->mPlanes[k]; + } + } + if(!tracker.test(p2Idx)) + { + tracker.set(p2Idx); + reMap[p2Idx] = indexCount; + nodes.increment(); + nodePtr = &(nodes[indexCount++]); + nodePtr->pos = Point3F(d->mConPoints[p2Idx].x, d->mConPoints[p2Idx].y, d->mConPoints[p2Idx].z); + nodePtr->flags.clear(); + + // add volume info + DVolume *vol = &(d->mVolumes[d->mSurfaces[d->mConPoints[p2Idx].surfIdx].volIdx]); + volumes.increment(); + volPtr = &(volumes[volumes.size() - 1]); + volPtr->mPlaneCount = vol->mNumPlanes; + volPtr->mPlaneIndex = planeCount; + for(k = 0; k < vol->mNumPlanes; k++) + { + volumes.mPlanes.increment(); + volumes.mPlanes[planeCount++] = vol->mPlanes[k]; + } + } + + // now pack the edge into the edge list + edges.increment(); + edgePtr = &(edges[edges.size() - 1]); + edgePtr->to[0].dest = reMap[con.start]; + edgePtr->to[1].dest = reMap[con.end]; + + // now pack the interface data + GraphEdgeInfo edgeSeg; + edgeSeg.segPoints[0] = con.segNodes[0]; + edgeSeg.segPoints[1] = con.segNodes[1]; + edgeSeg.to[0].dest = reMap[con.start]; + edgeSeg.to[1].dest = reMap[con.end]; + segs.push_back(edgeSeg); + } + + // find all islands and add them to the node list + U32 idx, pIdx; + for(idx = 0; idx < tracker.getSize(); idx++) + { + if (!tracker.test(idx)) + { + tracker.set(idx); + reMap[idx] = indexCount; + nodes.increment(); + nodePtr = &(nodes[indexCount++]); + bool invNode = d->mConPoints[idx].flags.test(DPoint::inventory); + nodePtr->pos = Point3F(d->mConPoints[idx].x, d->mConPoints[idx].y, d->mConPoints[idx].z); + nodePtr->flags.clear(); + + if(invNode) + nodePtr->setInventory(); + + // volume info + if(!invNode) + { + DVolume &vol = d->mVolumes[d->mSurfaces[d->mConPoints[idx].surfIdx].volIdx]; + volumes.increment(); + volPtr = &(volumes[volumes.size() - 1]); + volPtr->mPlaneCount = vol.mNumPlanes; + volPtr->mPlaneIndex = planeCount; + for(pIdx = 0; pIdx < vol.mNumPlanes; pIdx++) + { + volumes.mPlanes.increment(); + volumes.mPlanes[planeCount++] = vol.mPlanes[pIdx]; + } + } + else + { + DVolume vol; + createInventoryVol(d->mConPoints[idx], vol); + volumes.increment(); + volPtr = &(volumes[volumes.size() - 1]); + volPtr->mPlaneCount = vol.mNumPlanes; + volPtr->mPlaneIndex = planeCount; + for(pIdx = 0; pIdx < vol.mNumPlanes; pIdx++) + { + volumes.mPlanes.increment(); + volumes.mPlanes[planeCount++] = vol.mPlanes[pIdx]; + } + } + } + } + + delete [] reMap; + g->volumes = volumes; + g->edges = edges; + g->nodes = nodes; + g->segs = segs; +} + +//---------------------------------------------------------------------------- + +void FloorPlan::upload2NavGraph(NavigationGraph *navGraph) +{ + AssertFatal(navGraph, "Upload2NavGraph(): Bad pointer to navGraph passed."); + + EdgeInfoList edges; + NodeInfoList nodes; + EdgeInfoList segs; + GraphVolumeList volumes; + Vector planes; + Vector chutes; + + for(S32 i = 0; i < (mNumInteriors + mExtraInteriors); i++) + { + // The graph data for this interior + GraphDetails &g = mGraphDetails[i]; + + // Offset the node indices on all the edges- + for (S32 D = 0; D < 2; D++) { + for(S32 e = 0; e < g.edges.size(); e++) g.edges[e].to[D].dest += nodes.size(); + for(S32 s = 0; s < g.segs.size(); s++) g.segs[s].to[D].dest += nodes.size(); + } + + // Offset plane indices on the volumes- + for(S32 p = 0; p < g.volumes.size(); p++) + g.volumes[p].mPlaneIndex += planes.size(); + + // Accumulate the master lists of edges, nodes, volumes, etc. + edges.merge(g.edges); + segs.merge(g.segs); + nodes.merge(g.nodes); + volumes.merge(g.volumes); + planes.merge(g.volumes.mPlanes); + chutes.merge(g.chutes); + } + + // set the plane list to the volume plane list + volumes.mPlanes = planes; + + // pass all this data back to navGraph for build + navGraph->setAlgorithmic(edges, nodes); + navGraph->setEdgesAndNodes(segs, nodes); + navGraph->setNodeVolumes(volumes); + navGraph->setChuteHints(chutes); +} + +//---------------------------------------------------------------------------- + +#define defaultVolHt 3 + +void FloorPlan::createInventoryVol(DPoint p, DVolume &vol) +{ + vol.mNumPlanes = 6; + vol.ht = defaultVolHt; + vol.capPt.set(p.x, p.y, (p.z + defaultVolHt)); + + Point3F origPt(p.x, p.y, p.z); + Point3F pt; + F32 interfaceWidth = 0.8f; + + // define the volume's planes + pt.set(origPt.x - interfaceWidth, origPt.y, origPt.z); + vol.mPlanes[0].set(pt, VectorF(-1, 0, 0)); + pt.set(origPt.x, origPt.y + interfaceWidth, origPt.z); + vol.mPlanes[1].set(pt, VectorF(0, 1, 0)); + pt.set(origPt.x + interfaceWidth, origPt.y, origPt.z); + vol.mPlanes[2].set(pt, VectorF(1, 0, 0)); + pt.set(origPt.x, origPt.y - interfaceWidth, origPt.z); + vol.mPlanes[3].set(pt, VectorF(0, -1, 0)); + pt.set(origPt.x, origPt.y, origPt.z - 1); + vol.mPlanes[4].set(pt, VectorF(0, 0, -1)); + pt.set(origPt.x, origPt.y, origPt.z + 2.0); + vol.mPlanes[5].set(pt, VectorF(0, 0, 1)); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::buildNodesFromPortals(InteriorDetail *d) +{ + for(S32 portalIdx = 0; portalIdx < d->mPortals.size(); portalIdx++) + { + bool usePnt = true; + + // get the point to create a node from + Point3F pos; + Point3F start = d->mPortals[portalIdx].center; + Point3F end = start; + end.z -= 1000; + + RayInfo info; + if(gServerContainer.castRay(start, end, (InteriorObjectType | TerrainObjectType), &info)) + { + pos = info.point; + pos.z += 0.1; + } + else + continue; + + if (d->haveSurfaceNear(pos, 0.15)) + continue; + + for(S32 ptsCount = 0; ptsCount < portalPoints.size(); ptsCount++) + if(pointsAreEqual(pos, portalPoints[ptsCount])) { + usePnt = false; + break; + } + + if(usePnt) + { + portalPoints.push_back(pos); + + // create the node + IndoorNodeInfo node; + node.pos.set(pos.x, pos.y, pos.z); + node.setBelowPortal(); + + // create the volume + DVolume invVol; + GraphVolInfo vol; + DPoint p; + p.set(pos.x, pos.y, pos.z); + createInventoryVol(p, invVol); + vol.mPlaneIndex = 0; + vol.mPlaneCount = invVol.mNumPlanes; + + // now make the simple graph object & add all the data + GraphDetails d; + d.nodes.push_back(node); + d.volumes.push_back(vol); + + // add the planes that will define this node + for(U32 k = 0; k < 6; k++) + d.volumes.mPlanes.push_back(invVol.mPlanes[k]); + + // no segs or edges needed, woohoo! + + // now push this onto the graphDetails list + mGraphDetails[mNumInteriors + mExtraInteriors++] = d; + } + } +} + +//---------------------------------------------------------------------------- + +bool FloorPlan::pointsAreEqual(Point3F &a, Point3F &b) +{ + if((b - a).len() > 2) + return false; + else + return true; +} + +//---------------------------------------------------------------------------- + +void FloorPlan::buildStaticShapeCenterNodes() +{ + Con::executef(this, 1, "staticShapeListConstruct"); + + Vector::iterator i; + for(i = mStaticShapeCenterList.begin(); i != mStaticShapeCenterList.end(); i++) + processShape(i, true); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::buildStaticShapeGeometryNodes() +{ + Con::executef(this, 1, "staticShapeListConstruct"); + + Vector::iterator i; + for(i = mStaticShapeGeometryList.begin(); i != mStaticShapeGeometryList.end(); i++) + processShape(i, false); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::processShape(ShapeBase **s, bool center) +{ + // for now this only includes inventory shapes + if(center) + { + // get the point to create a node from + Point3F pos; + (*s)->getTransform().getColumn(3, &pos); + pos.z++; // raise it up a bit + + // create the node + IndoorNodeInfo node; + node.pos.set(pos.x, pos.y, pos.z); + node.flags.clear(); + node.setInventory(); + + // create the volume + DVolume invVol; + GraphVolInfo vol; + DPoint p; + p.set(pos.x, pos.y, pos.z); + createInventoryVol(p, invVol); + vol.mPlaneIndex = 0; + vol.mPlaneCount = invVol.mNumPlanes; + + // now make the simple graph object & add all the data + GraphDetails d; + d.nodes.push_back(node); + d.volumes.push_back(vol); + + // add the planes that will define this node + U32 k; + for(k = 0; k < 6; k++) + d.volumes.mPlanes.push_back(invVol.mPlanes[k]); + + // no segs or edges needed, woohoo! + + // now push this onto the graphDetails list + mGraphDetails[mNumInteriors + mExtraInteriors++] = d; + } + + // this graph will be based off of this shape's geometry + else + { + U32 i, j, k; + Box3F box = (*s)->getWorldBox(); + + // simple way to get our collision surfs + ClippedPolyList polyList; + polyList.mPlaneList.clear(); + polyList.mNormal.set(0, 0, 0); + + // this is all we will need from the polylist + Vector polys; + Vector verts; + + // these will be needed for reMap of verts + U32 reMapSize = 0; + U32 vCount = 0; + U32 *reMap = NULL; + + // get our collision surfs from the polylist polys + U32 mask = StaticShapeObjectType; + if(gServerContainer.buildPolyList(box, mask, &polyList)) + { + for(i = 0; i < polyList.mPolyList.size(); i++) + { + ClippedPolyList::Poly *poly = &(polyList.mPolyList[i]); + if(poly->object->getId() == (*s)->getId()) // make sure its the same obj + { + VectorF normal; + normal.set(poly->plane.x, poly->plane.y, poly->plane.z); + mSlopeThresh = mCos(mDegToRad(45.0f)); + + // what kind of slope does this surface have? + F32 dot = mDot(normal, VectorF(0, 0, 1)); + if(dot >= mSlopeThresh) + polys.push_back(polyList.mPolyList[i]); + } + } + } + else + return; // didn't get any polys back from the polylist + + InteriorDetail d; + + // copy over the point list + for(i = 0; i < polyList.mVertexList.size(); i++) + { + Point3F pt = polyList.mVertexList[i].point; + DPoint dpt; + dpt.set(pt.x, pt.y, pt.z); + dpt.flags.clear(); + dpt.e = NULL; + d.mPoints.push_back(dpt); + } + + // now create the surfs + for(i = 0; i < polys.size(); i++) + { + DSurf currSurf; + + currSurf.mFlags.clear(); + currSurf.mFlipStates.clear(); + + S32 vertCount = polys[i].vertexCount; + currSurf.mEdges = new DEdge *[vertCount]; + currSurf.mDivLevel = 0; + currSurf.parent = NULL; + currSurf.mSubSurfCount = 0; + currSurf.mNormal = polys[i].plane; + + for(j = 0, k = polys[i].vertexStart; k < (polys[i].vertexStart + polys[i].vertexCount); j++, k++) + currSurf.fullWinding[j] = polyList.mIndexList[k]; + + currSurf.mNumEdges = polys[i].vertexCount; + + d.mSurfaces.push_back(currSurf); + d.mTraversable.push_back(i); + } + + // build the edge table for this shape + buildEdgeTable(&d); + + // subdivide until we have a workable graph for this shape + U32 dummy = 0; + while(subdivideSurfaces(&d) > 0) + dummy++; + + // connect all volumes + buildConnections(&d); + + // extract the graph + graphExtraction(&d, &(mGraphDetails[mNumInteriors + mExtraInteriors++])); + + // clean up memory... YAY!! + d.mEdgeTable.clear(); + U32 x; + for(x = 0; x < d.mSurfaces.size(); x++) + delete [] d.mSurfaces[x].mEdges; + } +} + +//---------------------------------------------------------------------------- + +Point3F FloorPlan::getMinBoxPoint(InteriorDetail *d, U8 n_edges, DEdge **edges) +{ + Point3F minPt(1e9, 1e9, 1e9); + for(S32 i = 0; i < n_edges; i++) + minPt.setMin(d->mPoints[edges[i]->start]); + return minPt; +} + +//---------------------------------------------------------------------------- + +Point3F FloorPlan::getMaxBoxPoint(InteriorDetail *d, U8 n_edges, DEdge **edges) +{ + Point3F maxPt(-1e9, -1e9, -1e9); + for(S32 i = 0; i < n_edges; i++) + maxPt.setMax(d->mPoints[edges[i]->start]); + return maxPt; +} + +//---------------------------------------------------------------------------- + +void FloorPlan::buildEdgeNormals(InteriorDetail *d, DSurf &surf, VectorF *normalList) +{ + Point3F start, end; + F32 temp; + + U32 i; + for(i = 0; i < surf.mNumEdges; i++) + { + start = d->mPoints[surf.mEdges[i]->start]; + end = d->mPoints[surf.mEdges[i]->end]; + normalList[i] = end - start; + temp = normalList[i].x; + + if(!surf.mFlipStates.test(BIT(i))) + { + normalList[i].x = (normalList[i].y * -1); + normalList[i].y = temp; + } + else + { + normalList[i].x = normalList[i].y; + normalList[i].y = (temp * -1); + } + normalList[i].z = 0; + normalList[i].normalize(); + } +} + +//---------------------------------------------------------------------------- + +bool FloorPlan::obstructedSurf(InteriorDetail *d, DSurf &surf, DVolume *vol, surfCollisionInfo &info) +{ + F32 ht; + bool obstruction = setupPolyList(d, surf, &ht, vol, info); + + if(obstruction) + if(info.specialCase) + return true; + else + return (ht < 2.4); + else + return false; +} + +//------------------------------------------------------------------------------------- + +// We lower the top when collisions are found so that later extrusion processing +// doesn't get a collision. +#define LowerTheTop 0.05f + +bool FloorPlan::setupPolyList(InteriorDetail *d, DSurf &surf, F32 *height, DVolume *vol, surfCollisionInfo &info) +{ + info; + Box3F bBox; + bBox.min = getMinBoxPoint(d, surf.mNumEdges, surf.mEdges); + bBox.max = getMaxBoxPoint(d, surf.mNumEdges, surf.mEdges); + bBox.max.z += (UncappedNodeVolumeHt + 10); + F32 len; + + createCenterNode(d, &surf, &(surf.mCntr)); + + if(surf.mLongest->length == surf.mEdges[0]->length) + len = surf.mEdges[1]->length; + else + len = surf.mEdges[0]->length; + + F32 threshold = 0.3 * len; + bool useSpecial = false; + + struct collisionInfo { + enum CollisionType { + normal = 0, + ground = 1, + }; + + CollisionType type; + F32 ht; + + collisionInfo() {ht = 10000000; type = normal;} + }; + + ClippedPolyList polyList; + ClippedPolyList testList; + + // build a volume for this surface + vol->mNumPlanes = surf.mNumEdges+2; + + polyList.mPlaneList.clear(); + polyList.mPlaneList.setSize(surf.mNumEdges+1); + polyList.mNormal.set(0, 0, 0); + + testList.mPlaneList.clear(); + testList.mPlaneList.setSize(surf.mNumEdges+1); + testList.mNormal.set(0, 0, 0); + + VectorF *normals; + normals = new VectorF[surf.mNumEdges]; + buildEdgeNormals(d, surf, normals); + + #if _FP_DEBUG_ + VectorF saveNormals[40]; + Point3F savePoints[40]; + S32 saveIndex = 0; + #endif + + for(S32 i = 0; i < surf.mNumEdges; i++) + { + // check flipstate for edge + S32 ind = (surf.mFlipStates.test(BIT(i)) ? surf.mEdges[i]->end : surf.mEdges[i]->start); + Point3F pnt = d->mPoints[ind]; + Point3F tPnt = d->mPoints[ind]; + + // Note that we output a shrunk volume. It is adjusted back out later after + // some further processing is done on volumes. + pnt -= (normals[i] * NodeVolumeShrink); + tPnt -= (normals[i] * threshold); + + polyList.mPlaneList[i].set(pnt, normals[i]); + testList.mPlaneList[i].set(tPnt, normals[i]); + vol->mPlanes[i].set(pnt, normals[i]); + + #if _FP_DEBUG_ + saveNormals[saveIndex] = normals[i]; + savePoints[saveIndex++] = pnt; + #endif + + } + delete [] normals; + + + // cap the bottom for surfaces that have some slope + Point3F p = d->mPoints[surf.mEdges[0]->start]; + + VectorF vec(surf.mNormal); + p += (vec * NodeVolumeShrink); + vec.neg(); + + polyList.mPlaneList[surf.mNumEdges].set(p, vec); + testList.mPlaneList[surf.mNumEdges].set(p, vec); + + // cap the top of the test volume + Point3F aP = p; + aP.z += 2.5; + VectorF pU(0,0,1); + PlaneF tP(aP, pU); + testList.mPlaneList.push_back(tP); + + + vol->mPlanes[surf.mNumEdges].set(p, vec); + *height = 0.0f; + + #if _FP_DEBUG_ + saveNormals[saveIndex] = vec; + savePoints[saveIndex++] = p; + // Debug check to stop on a surface containing sDebugBreakPoint + if (mFabs(vol->mPlanes[surf.mNumEdges].distToPlane(sDebugBreakPoint)) < 0.6) { + // volume doesn't have top plane right now, hence dec & inc: + vol->mNumPlanes--; + if (vol->checkInside(sDebugBreakPoint)) + debugStopPt(); + vol->mNumPlanes++; + } + #endif + +#if TESTSPECIAL + + // determine if we can split this surface in a way that will better our floorPlan + useSpecial = isQuadSurface(d, surf) && specSurfTest(surf, testList, bBox); + +#endif + + // build the poly list + U32 mask = InteriorObjectType | StaticShapeObjectType | TerrainObjectType | StaticObjectType; + if(gServerContainer.buildPolyList(bBox, mask, &polyList)) + { + if (polyList.mPolyList.size()) + { + F32 maxHt = maxZforSurf(d, surf); + Point3F lowPt; + F32 ht; + collisionInfo lowCollision; + DSide sides[4]; + + for(S32 j = 0; j < polyList.mPolyList.size(); j++) + { + ClippedPolyList::Poly *p = &(polyList.mPolyList[j]); + for(S32 k = p->vertexStart; k < (p->vertexStart + p->vertexCount); k++) + { + d->polyTestPoints.push_back(polyList.mVertexList[polyList.mIndexList[k]].point); + ht = polyList.mVertexList[polyList.mIndexList[k]].point.z - maxHt; + if(ht < lowCollision.ht) + { + lowPt = polyList.mVertexList[polyList.mIndexList[k]].point; + lowPt.z -= LowerTheTop; + lowCollision.ht = (ht - LowerTheTop); + + // determine collision type + if(p->object->getTypeMask() & TerrainObjectType) + lowCollision.type = collisionInfo::ground; + else + lowCollision.type = collisionInfo::normal; + } + +#if TESTSPECIAL + + if(useSpecial && ht < 2.4) // ************optimize the division **************** + { + Point3F colPt = polyList.mVertexList[polyList.mIndexList[k]].point; + F32 distToPlane; + bool found = false; + + for(U32 side = 0; side < 4; side++) + { + distToPlane = mFabs(polyList.mPlaneList[side].distToPlane(colPt)); + if(distToPlane <= threshold) + { + if(distToPlane >= 0.05) + { + sides[side].closePts++; + if(distToPlane > sides[side].bestDist) + sides[side].bestDist = distToPlane; + + } + found = true; + } + } + + if(!found) + useSpecial = false; + } +#endif + } + } + +#if TESTSPECIAL + + if(useSpecial && haveGoodData(sides)) + { + setCollisionInfoData(info, sides, surf); + return true; + } +#endif + + // if the lowest height was terrain, this space must be obstructed + if(lowCollision.type == collisionInfo::ground) + *height = 0; + else + { + *height = lowCollision.ht; + vol->ht = *height; + vol->mPlanes[surf.mNumEdges+1].set(lowPt, VectorF(0, 0, 1)); + } + return true; + } + } + + //if(!inSolid(surf.mCntr)) + //{ + *height = UncappedNodeVolumeHt; + Point3F pt = d->mPoints[surf.mEdges[0]->start]; + pt.z += UncappedNodeVolumeHt; + vol->mPlanes[surf.mNumEdges+1].set(pt, VectorF(0, 0, 1)); + vol->ht = UncappedNodeVolumeHt; + return false; + //} + //else + //{ + //*height = 0; + //return true; + //} +} + +//------------------------------------------------------------------------------------- + +bool FloorPlan::inSolid(DPoint ¢er) +{ + Point3F start, end; + start = center; + start.z += 0.1; + end = start; + end.z += 1; + + RayInfo info; + if(gServerContainer.castRay(start, end, InteriorObjectType, &info)) + return true; + else + return false; +} + +//------------------------------------------------------------------------------------- + +bool FloorPlan::specSurfTest(DSurf &surf, ClippedPolyList &list, Box3F &b) +{ + surf; + U32 mask = InteriorObjectType | StaticShapeObjectType | TerrainObjectType | StaticObjectType; + if(gServerContainer.buildPolyList(b, mask, &list))// || inSolid(surf.mCntr)) + return false; + else + return true; +} + +//------------------------------------------------------------------------------------- + +bool FloorPlan::haveGoodData(DSide *sides) +{ + S32 sideToUse = -1; + S32 highSideCount = 0; + + for(S32 side = 0; side < 4; side++) + { + if(sides[side].closePts > highSideCount) + { + sideToUse = side; + highSideCount = sides[side].closePts; + } + } + + return sideToUse != -1; +} + +//------------------------------------------------------------------------------------- + +bool FloorPlan::isQuadSurface(InteriorDetail *d, DSurf &surf) +{ + if(surf.mNumEdges == 4) + { + DEdge *edge0 = surf.mEdges[0]; + DEdge *edge1 = surf.mEdges[1]; + DEdge *edge2 = surf.mEdges[2]; + DEdge *edge3 = surf.mEdges[3]; + + VectorF e0, e1, e2, e3; + + if(!surf.mFlipStates.test(BIT(0))) + e0 = d->mPoints[edge0->end] - d->mPoints[edge0->start]; + else + e0 = d->mPoints[edge0->start] - d->mPoints[edge0->end]; + + if(!surf.mFlipStates.test(BIT(1))) + e1 = d->mPoints[edge1->end] - d->mPoints[edge1->start]; + else + e1 = d->mPoints[edge1->start] - d->mPoints[edge1->end]; + + if(!surf.mFlipStates.test(BIT(2))) + e2 = d->mPoints[edge2->end] - d->mPoints[edge2->start]; + else + e2 = d->mPoints[edge2->start] - d->mPoints[edge2->end]; + + if(!surf.mFlipStates.test(BIT(3))) + e3 = d->mPoints[edge3->end] - d->mPoints[edge3->start]; + else + e3 = d->mPoints[edge3->start] - d->mPoints[edge3->end]; + + e0.normalize(); + e1.normalize(); + e2.normalize(); + e3.normalize(); + + bool E0parE2 = (mFabs(mDot(e0, e2)) > 0.992); + bool E1parE3 = (mFabs(mDot(e1, e3)) > 0.992); + + bool useSpecial = E0parE2 && E1parE3; + return useSpecial; + } + else + return false; +} + +//------------------------------------------------------------------------------------- + +void FloorPlan::setCollisionInfoData(surfCollisionInfo &info, DSide *sides, DSurf &surf) +{ + info.specialCase = true; + S32 sideToUse = -1; + S32 highSideCount = 0; + + for(S32 side = 0; side < 4; side++) + { + if(sides[side].closePts > highSideCount) + { + sideToUse = side; + highSideCount = sides[side].closePts; + } + } + + AssertFatal(sideToUse != -1, "no side chosen for collision info data"); + + switch(sideToUse) + { + case 0: + info.divideEdge1 = 3; + info.divideEdge2 = 1; + break; + case 1: + info.divideEdge1 = 0; + info.divideEdge2 = 2; + break; + case 2: + info.divideEdge1 = 1; + info.divideEdge2 = 3; + break; + case 3: + info.divideEdge1 = 2; + info.divideEdge2 = 0; + break; + } + + S32 sideA = info.divideEdge1; + S32 sideB = info.divideEdge2; + + F32 factorA = 1 - (sides[sideToUse].bestDist / surf.mEdges[sideA]->length); + info.factor = factorA; +} + +//------------------------------------------------------------------------------------- + +void FloorPlan::computeSurfaceRadius(InteriorDetail *d, DSurf *surf) +{ + if (surf->mFlags.test(DSurf::shouldBeCulled)) + return; // else we get NaN values below (center of vertical surfaces) + + DPoint center; + createCenterNode(d, surf, ¢er); + + F32 longest = -1e9f; + F32 shortest = 1e9f; + DEdge * * edges = surf->mEdges; + for(S32 i = 0; i < surf->mNumEdges; i++) + { + LineSegment seg(d->mPoints[edges[i]->start], d->mPoints[edges[i]->end]); + F32 D = seg.distance(center); + longest = getMax(longest, D); + shortest = getMin(shortest, D); + } + + // longest from center to a vert = radius + surf->mMaxRadius = longest; + surf->mMinRadius = shortest; +} + +//---------------------------------------------------------------------------- + +F32 FloorPlan::maxZforSurf(InteriorDetail *d, DSurf &surf) +{ + F32 maxZ = -1e12; + for(S32 i = 0; i < surf.mNumEdges; i++) + if(d->mPoints[surf.mEdges[i]->start].z > maxZ) + maxZ = d->mPoints[surf.mEdges[i]->start].z; + + return maxZ; +} + +//---------------------------------------------------------------------------- + +void FloorPlan::newConnection(InteriorDetail *d, U32 start, U32 end, DConnection &con) +{ + DConnection connect; + connect.start = start; + connect.end = end; + connect.segNodes[0] = con.segNodes[0]; + connect.segNodes[1] = con.segNodes[1]; + + d->mConnections.push_back(connect); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::buildConnections(InteriorDetail *d) +{ + U32 j, k, c, g; + d->mConPoints.setSize(d->mUnobstructed.size()); + + for(c = 0; c < d->mUnobstructed.size(); c++) + { + DSurf &s = d->mSurfaces[d->mUnobstructed[c]]; + d->mConPoints[c] = s.mCntr; + d->mConPoints[c].surfIdx = d->mUnobstructed[c]; + d->mConPoints[c].flags.clear(); + s.mCtrIdx = c; + log(avar("surface %d zone: %d", c, s.mZone)); + } + + d->mConnections.clear(); + + for(j = 0; j < d->mUnobstructed.size(); j++) + { + DSurf &surf_1 = d->mSurfaces[d->mUnobstructed[j]]; + for(k = (j+1); k < d->mUnobstructed.size(); k++) + { + DSurf &surf_2 = d->mSurfaces[d->mUnobstructed[k]]; + + // early out based on distance + if(!passDistanceCheck(surf_1, surf_2)) + continue; + + for(c = 0; c < surf_1.mNumEdges; c++) + { + DEdge *edge_1 = surf_1.mEdges[c]; + for(g = 0; g < surf_2.mNumEdges; g++) + { + DEdge *edge_2 = surf_2.mEdges[g]; + DConnection con; + if(isSharedEdge(d, edge_1, edge_2, con)) + { + newConnection(d, surf_1.mCtrIdx, surf_2.mCtrIdx, con); + break; + } + } + } + } + } + //findDuplicateCons(d); +} + +//---------------------------------------------------------------------------- + +bool FloorPlan::offHeightDistribution(InteriorDetail *d, DSurf &surf) +{ + // only perform this on flat surfaces + if(surf.mNormal.z != 1) + return false; + + // grab all the data + Vector points; + points.setSize(surf.mNumEdges+1); + createCenterNode(d, &surf, &points[0]); + points[0].z += 0.1; + + U32 i; + for(i = 1; i <= surf.mNumEdges; i++) + { + if(!surf.mFlipStates.test(BIT(i-1))) + points[i] = d->mPoints[surf.mEdges[i-1]->start]; + else + points[i] = d->mPoints[surf.mEdges[i-1]->end]; + points[i].z += 0.1; + } + + // adjust our points + VectorF *vecs = new VectorF[surf.mNumEdges]; + for(i = 1; i <= surf.mNumEdges; i++) + { + vecs[i-1] = points[0] - points[i]; + vecs[i-1].normalize(); + points[i] += (1.75 * vecs[i-1]); + } + + // now do the line of sights + bool contact = false; + F32 *hts = new F32[surf.mNumEdges+1]; + for(i = 0; i <= surf.mNumEdges; i++) + { + RayInfo info; + Point3F end(points[i]); + end.z += 100; + if(gServerContainer.castRay(points[i], end, InteriorObjectType, &info)) + { + contact = true; + hts[i] = info.point.z - points[i].z; + } + else + hts[i] = 100; + } + + // don't bother if we have a completly unobstructed volume + bool differs = false; + if(contact) + if(surf.mMaxRadius > sSpecialMaxRadius) + differs = htsDifferAlot(hts, surf.mNumEdges+1); + + // clean up + delete [] vecs; + delete [] hts; + return differs; +} + +//---------------------------------------------------------------------------- + +bool FloorPlan::htsDifferAlot(F32 *hts, U8 n) +{ + // sort these first + U32 i, j; + for(i = 0; i < n; i++) + { + for(j = i; j < n; j++) + { + if(hts[i] > hts[j]) + { + F32 temp = hts[i]; + hts[i] = hts[j]; + hts[j] = temp; + } + } + } + return (hts[n-1] - hts[0] > sHeightDiff); +} + +//---------------------------------------------------------------------------- + +bool FloorPlan::passDistanceCheck(DSurf &surf_1, DSurf &surf_2) +{ + bool distance = (surf_2.mCntr - surf_1.mCntr).len() < (sMaxDivRadius*3); + +// VectorF floor(0, 0, 1); +// if((surf_1.mNormal == floor) && (surf_2.mNormal == floor)) +// { +// bool sameZ = mFabs(surf_2.mCntr.z) == mFabs(surf_1.mCntr.z); +// return distance && sameZ; +// } +// else + return distance; +} + +//---------------------------------------------------------------------------- + +bool FloorPlan::isSharedEdge(InteriorDetail *d, DEdge *edge_1, DEdge *edge_2, DConnection &con) +{ + DEdge *small; + DEdge *big; + DEdge *sourceEdge = edge_1; + + if(edge_1->length > edge_2->length) + { + big = edge_1; + small = edge_2; + } + else + { + big = edge_2; + small = edge_1; + } + + // are any points equal? + bool ss_bs = small->start == big->start; + bool ss_be = small->start == big->end; + bool se_bs = small->end == big->start; + bool se_be = small->end == big->end; + + // easy case, hook up and leave + if(((ss_bs) || (ss_be)) && ((se_bs) || (se_be))) + { + con.segNodes[0] = d->mPoints[small->start]; + con.segNodes[1] = d->mPoints[small->end]; + return isUsableInterface(con); + } + + VectorF sourceVec = (d->mPoints[sourceEdge->end] - d->mPoints[sourceEdge->start]); + VectorF vec_1 = (d->mPoints[big->end] - d->mPoints[big->start]); + VectorF vec_2 = (d->mPoints[small->end] - d->mPoints[small->start]); + vec_1.normalize(); + vec_2.normalize(); + sourceVec.normalize(); + + Point3F p1 = d->mPoints[small->start]; + Point3F p2 = d->mPoints[small->end]; + Point3F p3 = d->mPoints[big->start]; + Point3F p4 = d->mPoints[big->end]; + + // check if parallel + bool parallel = (mFabs(mDot(vec_1, vec_2)) > sParallelDot); + + if(parallel) + { + LineSegment line(d->mPoints[big->start], d->mPoints[big->end]); + + F32 dist_1 = line.distance(d->mPoints[small->start]); + F32 dist_2 = line.distance(d->mPoints[small->end]); + + // check if collinear + if((dist_1 < sCollinearDist) || (dist_2 < sCollinearDist) || isSteppable(d, big, small)) + { + // project Points of small line onto larger line + VectorF vec_3 = (d->mPoints[small->start] - d->mPoints[big->start]); + VectorF vec_4 = (d->mPoints[small->end] - d->mPoints[big->start]); + + F32 proj_1 = mDot(vec_1, vec_3); + F32 proj_2 = mDot(vec_1, vec_4); + + // get our t values based on the projection. + F32 t1 = proj_1/big->length; + F32 t2 = proj_2/big->length; + + // sorting makes things alittle easier + bool flipped = false; + if(t1 > t2) + { + F32 temp = t1; t1 = t2; t2 = temp; + flipped = true; + } + + // check if lines are overlapping + if(t2 <= 0 || t1 >= 1) + return false; + + // we know the small line is overlapping the bigger line + // we just need to determine which case of overlap + + // case 1 + // containment or equal + if((t1 >= 0) && (t2 <= 1)) + { + con.segNodes[0] = d->mPoints[small->start]; + con.segNodes[1] = d->mPoints[small->end]; + } + else + { + // case 2 + // the edges overlap on the start end + if(t1 < 0) + { + if (!flipped) + { + con.segNodes[0] = d->mPoints[big->start]; + con.segNodes[1] = d->mPoints[small->end]; + } + else + { + con.segNodes[0] = d->mPoints[big->start]; + con.segNodes[1] = d->mPoints[small->start]; + } + } + // case 3 + // the edges overlap with t1 >= 0 and t2 extending past big->end + else + { + if (!flipped) + { + con.segNodes[0] = d->mPoints[small->start]; + con.segNodes[1] = d->mPoints[big->end]; + } + else + { + con.segNodes[0] = d->mPoints[small->end]; + con.segNodes[1] = d->mPoints[big->end]; + } + } + } + + // Project both nodes onto the proper segment + VectorF conVec1 = (con.segNodes[0] - d->mPoints[sourceEdge->start]); + VectorF conVec2 = (con.segNodes[1] - d->mPoints[sourceEdge->start]); + F32 conPro1 = mDot(sourceVec, conVec1); + F32 conPro2 = mDot(sourceVec, conVec2); + F32 tval1 = conPro1 / sourceEdge->length; + F32 tval2 = conPro2 / sourceEdge->length; + + con.segNodes[0] = d->mPoints[sourceEdge->start] + ((d->mPoints[sourceEdge->end] - d->mPoints[sourceEdge->start]) * tval1); + con.segNodes[1] = d->mPoints[sourceEdge->start] + ((d->mPoints[sourceEdge->end] - d->mPoints[sourceEdge->start]) * tval2); + + return isUsableInterface(con); + } + } + return false; +} + +//---------------------------------------------------------------------------- + +bool FloorPlan::isSteppable(InteriorDetail *d, DEdge *big, DEdge *small) +{ + F32 smallZ = d->mPoints[small->start].z; + F32 bigZ = d->mPoints[big->start].z; + + if(mFabs(smallZ - bigZ) > 0.75) + return false; + + Point3F ss = d->mPoints[small->start]; + Point3F se = d->mPoints[small->end]; + Point3F bs = d->mPoints[big->start]; + Point3F be = d->mPoints[big->end]; + se.z = ss.z = bs.z = be.z = 0.0f; + + LineSegment line(bs, be); + + return (line.distance(ss) < sCollinearDist) || (line.distance(se) < sCollinearDist); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::initPersistFields() +{ + Parent::initPersistFields(); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::generate() +{ + mDivLevel = 0; + snoopInteriors(); + + Con::printf("Total portal nodes created: %d\n", portalPoints.size()); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::findDuplicateCons(InteriorDetail *d) +{ + U32 theSame = 0; + + U32 i, j; + for(i = 0; i < d->mConnections.size(); i++) + { + DConnection &con_1 = d->mConnections[i]; + for(j = (i+1); j < d->mConnections.size(); j++) + { + DConnection &con_2 = d->mConnections[j]; + if(con_1 == con_2) + { + theSame++; + d->mConnections.erase(j--); + } + } + } + Con::printf("duplicate connections removed from %d: %d\n", d->mIndex, theSame); +} + + +// console functions +//---------------------------------------------------------------------------- + +static void cGenerate(SimObject *ptr, S32, const char **) +{ + FloorPlan *obj = static_cast(ptr); + + if(obj) + obj->generate(); +} + +//---------------------------------------------------------------------------- + +static void cUpload(SimObject *ptr, S32, const char **) +{ + FloorPlan *obj = static_cast(ptr); + NavigationGraph *ng = dynamic_cast(Sim::findObject("NavGraph")); + + if(!ng) + Con::printf("no navigationGraph exists!"); + + if(obj) + obj->upload2NavGraph(ng); +} + +//---------------------------------------------------------------------------- + +static void cAddStaticCenter(SimObject *ptr, S32, const char **argv) +{ + FloorPlan *fp = static_cast(ptr); + + S32 id = dAtoi(argv[2]); + ShapeBase *shape = NULL; + Sim::findObject(id, shape); + + fp->mStaticShapeCenterList.push_back(shape); +} + +//---------------------------------------------------------------------------- + +static void cAddStaticGeom(SimObject *ptr, S32, const char **argv) +{ + FloorPlan *fp = static_cast(ptr); + + S32 id = dAtoi(argv[2]); + ShapeBase *shape = NULL; + Sim::findObject(id, shape); + + fp->mStaticShapeGeometryList.push_back(shape); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::consoleInit() +{ + Parent::consoleInit(); + + Con::addVariable("FloorPlan::CollinearDist", TypeF32, &sCollinearDist); + Con::addVariable("FloorPlan::ParallelDot", TypeF32, &sParallelDot); + Con::addVariable("FloorPlan::FloorAngleThresh", TypeS32, &sFloorThresh); + Con::addVariable("FloorPlan::HeightDiff", TypeF32, &sHeightDiff); + Con::addVariable("FloorPlan::SpecialMaxRadius", TypeF32, &sSpecialMaxRadius); + Con::addVariable("FloorPlan::MaxRadius", TypeF32, &sMaxDivRadius); + Con::addVariable("FloorPlan::MinRadius", TypeF32, &sMinDivRadius); + Con::addVariable("FloorPlan::subdivide", TypeBool, &sSubDivide); + Con::addVariable("FloorPlan::drawConnections", TypeBool, &sDrawConnections); + Con::addVariable("FloorPlan::drawVols", TypeBool, &sDrawVolumeHts); + Con::addVariable("FP::special", TypeBool, &sUseSpecial); + Con::addVariable("FP::DisableAsserts", TypeBool, &sDisableAsserts); + Con::addVariable("FP::divCount", TypeS32, &amountToDivide); + Con::addCommand("FloorPlan", "generate", cGenerate, "obj.generate()", 2, 2); + Con::addCommand("FloorPlan", "upload", cUpload, "obj.upload()", 2, 2); + Con::addCommand("FloorPlan", "addStaticCenter", cAddStaticCenter, "obj.addStaticCenter( shape )", 3, 3); + Con::addCommand("FloorPlan", "addStaticGeom", cAddStaticGeom, "obj.addStaticGeom( shape )", 3, 3); + Con::linkNamespaces("SimObject", "FloorPlan"); +} + diff --git a/ai/graphFloorPlan.h b/ai/graphFloorPlan.h new file mode 100644 index 0000000..6a11355 --- /dev/null +++ b/ai/graphFloorPlan.h @@ -0,0 +1,231 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHFLOORPLAN_H_ +#define _GRAPHFLOORPLAN_H_ + +#define TESTSPECIAL 1 +#define ITERATEMODE 0 + +// ************************************************************************************ +#define DEBUGMODE 0 // Only use this if you want to debug ONE interior. More than + // one interior in a mission will crash with this defined to 1 + // Also, this doesn't support graph builds. Use the build function + // called fpStart() and fpend() to generate nodes for the interior + // you want to debug. These functions are defined in navGraph.cs +// ************************************************************************************ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _INTERIOR_H_ +#include "interior/interior.h" +#endif +#ifndef _INTERIORINSTANCE_H_ +#include "interior/interiorInstance.h" +#endif +#ifndef _GRAPH_H_ +#include "ai/graph.h" +#endif +#ifndef _GRAPHMATH_H_ +#include "ai/graphMath.h" +#endif +#ifndef _BITSET_H_ +#include "core/bitSet.h" +#endif +#ifndef _GRAPHGENUTILS_H_ +#include "ai/graphGenUtils.h" +#endif +#ifndef _TSSHAPEINSTANCE_H_ +#include "ts/tsShapeInstance.h" +#endif +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif + +//---------------------------------------------------------------- + +class FloorPlan: public SimObject +{ + private: + + // members + typedef SimObject Parent; + GraphDetails *mGraphDetails; + Interior *currInterior; + InteriorInstance *currInstance; + F32 mSlopeThresh; + U32 mNumInteriors; + U32 mExtraInteriors; + bool mDataCollected; + U8 mDivLevel; + bool newLog; + S32 numZones; + + struct surfCollisionInfo + { + bool specialCase; + S32 divideEdge1; + S32 divideEdge2; + F32 factor; + + surfCollisionInfo() { specialCase = false; divideEdge1 = divideEdge2 = -1; factor = 0.f; } + }; + +#if DEBUGMODE + + InteriorDetail detail; + +#endif + + // static members + static Point3F sDebugBreakPoint; + static F32 sFloorThresh; + static F32 sMinDivRadius; + static F32 sMaxDivRadius; + static bool sSubDivide; + static bool sDrawConnections; + static bool sDrawVolumeHts; + static F32 sHeightDiff; + static F32 sSpecialMaxRadius; + static bool sUseSpecial; + static S32 amountToDivide; + static bool sDisableAsserts; + static bool LogMode; + + public: + Vector mStaticShapeCenterList; + Vector mStaticShapeGeometryList; + Vector portalPoints; + + // constructors/destructors + public: + FloorPlan(); + ~FloorPlan(); + DECLARE_CONOBJECT(FloorPlan); + static void consoleInit(); + static void initPersistFields(); + static void setBreakPoint(const Point3F& pt) {sDebugBreakPoint = pt;} + + // public interface + public: + void render(); + void generate(); + void upload2NavGraph(NavigationGraph *ng); + + // simbase methods + protected: + bool onAdd(); + void onRemove(); + + // interior extraction methods + private: + void snoopInteriors(); + void extractData(Interior *interior, InteriorDetail *detail, const MatrixF &transform); + void buildEdgeTable(InteriorDetail *detail); + void buildStaticShapeGeometryNodes(); + void buildStaticShapeCenterNodes(); + void buildNodesFromPortals(InteriorDetail *d); + void processShape(ShapeBase **s, bool center); + void graphExtraction(InteriorDetail *d, GraphDetails *g); + + // subdivision methods + private: + U32 subdivideSurfaces(InteriorDetail *d); + void splitSurface(InteriorDetail *d, DSurf &surf, U32 surfIndex, surfCollisionInfo &info); + void subdivideSurface(InteriorDetail *d, DSurf &surf, U32 surfIdx); + void subdivideEdge(InteriorDetail *d, DEdge *edge, F32 factor); + void createNewSurfaces(InteriorDetail *d, U32 surfIdx, DEdge **edges, BitSet32 &edgeTracker); + void newSurfacesSpecialCase(InteriorDetail *d, U32 surfIdx, BitSet32 &edgeTracker); + bool shouldDivideEdge(DSurf &surf, DEdge *edge); + bool shouldDivideSurf(DSurf &surf); + void createCenterNode(InteriorDetail *d, DSurf *surf, DPoint *CM); + void generateNewEdges(InteriorDetail *d, U32 cenIdx, DSurf &surf, DEdge **edges, BitSet32 &edgeTracker); + + // polylist collision methods + private: + bool setupPolyList(InteriorDetail *d, DSurf &surf, F32 *ht, DVolume *vol, surfCollisionInfo &info); + Point3F getMinBoxPoint(InteriorDetail *d, U8 n_edges, DEdge **edges); + Point3F getMaxBoxPoint(InteriorDetail *d, U8 n_edges, DEdge **edges); + void buildEdgeNormals(InteriorDetail *d, DSurf &surf, VectorF *normalList); + bool obstructedSurf(InteriorDetail *d, DSurf &surf, DVolume *vol, surfCollisionInfo &info); + + // internal helpers + private: + void setToWallNodes(InteriorDetail *d, DSurf &surf); + void findLongestEdge(DSurf *surf); + bool validatePoint(DPoint &p); + void computeSurfaceRadius(InteriorDetail *d, DSurf *surf); + F32 maxZforSurf(InteriorDetail *d, DSurf &surf); + void newConnection(InteriorDetail *d, U32 start, U32 end, DConnection &con); + bool isSharedEdge(InteriorDetail *d, DEdge *edge_1, DEdge *edge_2, DConnection &con); + bool passDistanceCheck(DSurf &surf_1, DSurf &surf_2); + void buildConnections(InteriorDetail *d); + void findDuplicateCons(InteriorDetail *d); + bool isUsableInterface(DConnection &con); + bool offHeightDistribution(InteriorDetail *d, DSurf &surf); + bool htsDifferAlot(F32 *hts, U8 n); + void createInventoryVol(DPoint p, DVolume &vol); + void setCollisionInfoData(surfCollisionInfo &info, DSide *side, DSurf &surf); + bool isQuadSurface(InteriorDetail *d, DSurf &surf); + bool haveGoodData(DSide *sides); + bool isSteppable(InteriorDetail *d, DEdge *big, DEdge *small); + bool specSurfTest(DSurf &surf, ClippedPolyList &list, Box3F &b); + void log(const char *string); + void extractPortals(Interior *i, InteriorDetail *d, const MatrixF &transform); + bool buildPortalCenter(InteriorDetail *d, Interior *interior, Interior::Portal *sourcePortal, DPortal *portal); + bool pointsAreEqual(Point3F &a, Point3F &b); + bool inSolid(DPoint ¢er); + void buildZoneInfo(InteriorDetail *d); + void sortSurfaceList(InteriorDetail *d); + + // rendering methods + private: + void drawEdge(Point3F, Point3F); + void drawSurface(InteriorDetail *d, DSurf &surf); + void drawNode(DPoint pos); + void renderEdgeTable(InteriorDetail *d); + void drawPoly(U8 n, Point3F *pts, bool unObstruct, bool special, S32 zone); + void drawConnections(InteriorDetail *d); + void drawVolumes(InteriorDetail *d); + void renderPolyPoints(InteriorDetail *d); + void renderSpecialNodes(InteriorDetail *d); + void drawInterfaces(InteriorDetail *d); +}; + +extern FloorPlan *gFloorPlan; // just for testing + +// inlines +//------------------------------------------------------------------------- + +inline bool FloorPlan::shouldDivideEdge(DSurf &surf, DEdge *edge) +{ + return ((edge->length / surf.mLongest->length) > 0.4); +} + +//------------------------------------------------------------------------- + +inline bool FloorPlan::validatePoint(DPoint &p) +{ + return (p.x == p.x) && (p.y == p.y) && (p.z == p.z); +} + +//------------------------------------------------------------------------- + +inline bool FloorPlan::isUsableInterface(DConnection &con) +{ + F32 length = (con.segNodes[1] - con.segNodes[0]).len(); + // return (length > 0.8); + return (length > 1.2); +} + +//------------------------------------------------------------------------- + +#endif + + + diff --git a/ai/graphFloorRender.cc b/ai/graphFloorRender.cc new file mode 100644 index 0000000..a0ebb0a --- /dev/null +++ b/ai/graphFloorRender.cc @@ -0,0 +1,761 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "ai/graphFloorPlan.h" +#include "ai/graphGroundPlan.h" + +extern void wireCube(F32 size,Point3F pos); + +#define zone1 0.1 +#define zone2 0.2 +#define zone3 0.3 +#define zone4 0.4 +#define zone5 0.5 +#define zone6 0.6 +#define zone7 0.7 +#define zone8 0.8 +#define zone9 0.9 +#define zone10 1.0 +#define zone11 0.1 +#define zone12 0.2 +#define zone13 0.3 +#define zone14 0.4 +#define zone15 0.5 +#define zone16 0.6 +#define zone17 0.7 +#define zone18 0.8 +#define zone19 0.9 + +//---------------------------------------------------------------------------- +// render methods - gives visual rep of generated graph +// * must be called during engine rendering phase +//---------------------------------------------------------------------------- +void FloorPlan::render() +{ +#if DEBUGMODE + + U32 i; + for(i = 0; i < detail.mTraversable.size(); i++) + { + // only draw this surface if it has no children + if(detail.mSurfaces[detail.mTraversable[i]].mSubSurfCount == 0) + drawSurface(&detail, detail.mSurfaces[detail.mTraversable[i]]); + } + //renderPolyPoints(&detail); + //drawConnections(&detail); + //drawInterfaces(&detail); + //renderPolyPoints(&detail); + +#endif +} + +void FloorPlan::drawInterfaces(InteriorDetail *d) +{ + for(S32 i = 0; i < d->mConnections.size(); i++) + { + drawEdge(d->mConnections[i].segNodes[0], d->mConnections[i].segNodes[1]); + } +} + +void FloorPlan::renderSpecialNodes(InteriorDetail *) +{ + U32 sCount = portalPoints.size(), j; + for(j = 0; j < sCount; j++) + wireCube(0.2f, portalPoints[j]); +} + +void FloorPlan::renderPolyPoints(InteriorDetail *d) +{ + U32 sCount = d->polyTestPoints.size(), j; + for(j = 0; j < sCount; j++) + wireCube(0.1, d->polyTestPoints[j]); + +} + +//---------------------------------------------------------------------------- + +void FloorPlan::renderEdgeTable(InteriorDetail *d) +{ + DEdge *walk; + DPoint p1, p2; + + U32 i; + for(i = 0; i < d->mEdgeTable.tableSize(); i++) + { + walk = d->mEdgeTable.getBucket(i); + while(walk) + { + if(walk->flags.test(DEdge::divGenerated)) + { + p1 = d->mPoints[walk->start]; + p2 = d->mPoints[walk->end]; + + bool p1ok = validatePoint(p1); + bool p2ok = validatePoint(p2); + AssertFatal(p1ok, "invalid Point3F"); + AssertFatal(p2ok, "invalid Point3F"); + + drawEdge(p1, p2); + } + walk = walk->next; + } + } +} + +//---------------------------------------------------------------------------- + +void FloorPlan::drawSurface(InteriorDetail *d, DSurf &surf) +{ + Point3F pts[32]; + + U32 i; + for(i = 0; i < surf.mNumEdges; i++) + { + if(!surf.mFlipStates.test(BIT(i))) + pts[i] = d->mPoints[surf.mEdges[i]->start]; + else + pts[i] = d->mPoints[surf.mEdges[i]->end]; + } + + drawPoly(i, pts, surf.mFlags.test(DSurf::unObstructed), surf.mFlags.test(DSurf::tooSmall), surf.mZone); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::drawPoly(U8 n, Point3F *pts, bool, bool, S32 zone) +{ + switch(zone) + { + case -1: + case 0: + glColor3f(1, 1, 1); + break; + case 1: + glColor3f(zone10, zone1, zone1); + break; + case 2: + glColor3f(zone2, zone10, zone2); + break; + case 3: + glColor3f(zone3, zone3, zone10); + break; + case 4: + glColor3f(zone10, zone4, zone4); + break; + case 5: + glColor3f(zone5, zone10, zone5); + break; + case 6: + glColor3f(zone6, zone6, zone10); + break; + case 7: + glColor3f(zone10, zone7, zone7); + break; + case 8: + glColor3f(zone8, zone10, zone8); + break; + case 9: + glColor3f(zone9, zone9, zone10); + break; + case 10: + glColor3f(zone1, zone10, zone10); + break; + case 11: + glColor3f(zone10, zone11, zone11); + break; + case 12: + glColor3f(zone12, zone10, zone12); + break; + case 13: + glColor3f(zone13, zone13, zone10); + break; + case 14: + glColor3f(zone10, zone14, zone14); + break; + case 15: + glColor3f(zone15, zone10, zone15); + break; + case 16: + glColor3f(zone16, zone16, zone10); + break; + case 17: + glColor3f(zone10, zone17, zone17); + break; + case 18: + glColor3f(zone18, zone10, zone18); + break; + case 19: + glColor3f(zone19, zone19, zone10); + break; + } + + U32 i; + glBegin(GL_POLYGON); + for(i = 0; i < n; i++) + glVertex3f(pts[i].x, pts[i].y, pts[i].z+.01); + glEnd(); + + // outline + glBegin(GL_LINE_LOOP); + glColor3f(0, 0, 0); + for(i = 0; i < n; i++) + glVertex3f(pts[i].x, pts[i].y, pts[i].z+.02); + glEnd(); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::drawEdge(Point3F p1, Point3F p2) +{ + glBegin(GL_LINES); + glColor3f(0, 1, 0); + glVertex3f(p1.x, p1.y, (p1.z+.2)); + glVertex3f(p2.x, p2.y, (p2.z+.2)); + glEnd(); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::drawNode(DPoint pos) +{ + glPointSize(10); + glBegin(GL_POINTS); + glColor3f(0, 0, 1); + glVertex3f(pos.x, pos.y, pos.z+.2); + glEnd(); +} + +//---------------------------------------------------------------------------- + +void FloorPlan::drawConnections(InteriorDetail *d) +{ + if(!sDrawConnections) + return; + + DPoint p1, p2; + + U32 i; + for(i = 0; i < d->mConnections.size(); i++) + { + p1 = d->mConPoints[d->mConnections[i].start]; + p1.z += .1; + p2 = d->mConPoints[d->mConnections[i].end]; + p2.z += .1; + drawNode(p1); + drawNode(p2); + drawEdge(p1, p2); + } +} + +//---------------------------------------------------------------------------- + +void FloorPlan::drawVolumes(InteriorDetail *d) +{ + U32 i; + for(i = 0; i < d->mVolumes.size(); i++) + { + //drawEdge(d->mVolumes[i].surfacePtr->mCntr, d->mVolumes[i].capPt); + } +} + +//---------------------------------------------------------------------------- + +void GroundPlan::render(Point3F &camPos, bool drawClipped) +{ + U32 i; + S32 dataBaseSize = mGridDatabase.size(); + if( ! dataBaseSize ) + return; + + // 2 methods for render + if(drawClipped) + { + Point2I gPos; + S32 index; + + worldToGrid(camPos, gPos); + if(gPos.x < mGridOrigin.x || gPos.x > (mGridOrigin.x + mGridDims.x)) + return; + + index = gridToIndex(gPos); + + S32 start, end; + S32 xspan, yspan; + getClippedRegion(index, start, end, xspan, yspan); + + // begin rendering + S32 x, y; + for(y = 0; y < yspan; y++) + { + for(x = start + (y * mGridDims.x); x < ((start + (y * mGridDims.x)) + xspan); x++) + { + index = x; + + // don't draw invalid node indexes + if(index > dataBaseSize || index < 0) + continue; + + GridDetail &grid = mGridDatabase[index]; + + // clear node + if(grid.mNavFlag == GridDetail::clear) + { + Point3F pos1 = grid.mWorld[0][0]; + pos1.z += 1; + wireCube(1, pos1); + } + else if(grid.mNavFlag == GridDetail::shadowed) + { + Point3F pos2 = grid.mWorld[0][0]; + pos2.z += 1; + wireCube(1, pos2); + drawShadow(grid, index); + } + drawObstruct(grid, index); + + // draw a network of connections for this node + drawNeighbor(grid, index); + } + } + } + + // draw the whole graph + else + { + for(i = 0; i < dataBaseSize; i++) + { + GridDetail &grid = mGridDatabase[i]; + + // clear node + /*if(grid.mNavFlag == GridDetail::clear) + { + Point3F pos1 = grid.mWorld[0][0]; + pos1.z += 1; + wireCube(1, pos1); + } + else if(grid.mNavFlag == GridDetail::shadowed) + { + Point3F pos2 = grid.mWorld[0][0]; + pos2.z += 1; + wireCube(1, pos2); + drawShadow(grid, i); + }*/ + drawObstruct(grid, i); + //drawNeighbor(grid, i); + } + } +} + +//---------------------------------------------------------------------------- +// these were more for testing +void GroundPlan::drawShadow(GridDetail &grid, S32 index) +{ + Point2I *gPos; + Point2I cPos; + Point3F wMin; + Point3F wMax; + gPos = getPosition(index); + + if(!gPos) + return; + + cPos = *gPos; + gridToWorld(cPos, wMin); + gridToWorld(cPos + gridOffset[7], wMax); + + glBegin(GL_LINES); + glColor3f(1.0, 0.0, 0.0); + glVertex3f(grid.mWorld[0][0].x, grid.mWorld[0][0].y, grid.mWorld[0][0].z); + glVertex3f(grid.mWorld[0][0].x, grid.mWorld[0][0].y, grid.mWorld[0][0].z+grid.mShadowHt); + glEnd(); +} + +//---------------------------------------------------------------------------- + +void GroundPlan::drawObstruct(GridDetail &grid, S32 index) +{ + Point2I *gPos; + Point2I cPos; + Point3F wMin; + Point3F wMax; + gPos = getPosition(index); + + if(!gPos) + return; + + cPos = *gPos; + gridToWorld(cPos, wMin); + gridToWorld(cPos + gridOffset[7], wMax); + + for(U16 i = 0; i < 2; i++) + { + if(grid.mNavFlags[i] == GridDetail::obstructed) + { + glBegin(GL_TRIANGLES); + glColor3f(1, 0, 0); + glVertex3f(grid.mWorld[i][0].x, grid.mWorld[i][0].y, grid.mWorld[i][0].z+.5); + glVertex3f(grid.mWorld[i][1].x, grid.mWorld[i][1].y, grid.mWorld[i][1].z+.5); + glVertex3f(grid.mWorld[i][2].x, grid.mWorld[i][2].y, grid.mWorld[i][2].z+.5); + glEnd(); + } + } +} + +//---------------------------------------------------------------------------- + +void GroundPlan::drawNeighbor(GridDetail &grid, S32 i) +{ + Point3F to, nodeP; + nodeP = grid.mWorld[0][0]; + nodeP.z += 1; + GridDetail *g; + + bool isBottomRow = false; + bool isLeftEdge = false; + bool isRightEdge = false; + bool isTopRow = false; + + if(i % mGridDims.x == 0) + isLeftEdge = true; + if(i < mGridDims.x) + isBottomRow = true; + if(((i+1) % mGridDims.x) == 0) + isRightEdge = true; + if(i >= ((mGridDims.x * mGridDims.y) - mGridDims.x)) + isTopRow = true; + + if(!isLeftEdge && !isBottomRow) + { + if(!isTopRow && !isRightEdge) + { + if(grid.mNeighbors.test(BIT(0))) + { + g = &(mGridDatabase[getNeighborDetails(i, 0)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(1))) + { + g = &(mGridDatabase[getNeighborDetails(i, 1)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(2))) + { + g = &(mGridDatabase[getNeighborDetails(i, 2)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(3))) + { + g = &(mGridDatabase[getNeighborDetails(i, 3)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(4))) + { + g = &(mGridDatabase[getNeighborDetails(i, 4)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(5))) + { + g = &(mGridDatabase[getNeighborDetails(i, 5)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(6))) + { + g = &(mGridDatabase[getNeighborDetails(i, 6)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(7))) + { + g = &(mGridDatabase[getNeighborDetails(i, 7)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + } + else if(!isTopRow) + { + if(grid.mNeighbors.test(BIT(0))) + { + g = &(mGridDatabase[getNeighborDetails(i, 0)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(1))) + { + g = &(mGridDatabase[getNeighborDetails(i, 1)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(2))) + { + g = &(mGridDatabase[getNeighborDetails(i, 2)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(4))) + { + g = &(mGridDatabase[getNeighborDetails(i, 4)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(6))) + { + g = &(mGridDatabase[getNeighborDetails(i, 6)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + } + else if(!isRightEdge) + { + if(grid.mNeighbors.test(BIT(0))) + { + g = &(mGridDatabase[getNeighborDetails(i, 0)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(1))) + { + g = &(mGridDatabase[getNeighborDetails(i, 1)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(2))) + { + g = &(mGridDatabase[getNeighborDetails(i, 2)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(3))) + { + g = &(mGridDatabase[getNeighborDetails(i, 3)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(5))) + { + g = &(mGridDatabase[getNeighborDetails(i, 5)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + } + else + { + if(grid.mNeighbors.test(BIT(0))) + { + g = &(mGridDatabase[getNeighborDetails(i, 0)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(1))) + { + g = &(mGridDatabase[getNeighborDetails(i, 1)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(2))) + { + g = &(mGridDatabase[getNeighborDetails(i, 2)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + } + } + else if(!isBottomRow) + { + if(!isTopRow) + { + if(grid.mNeighbors.test(BIT(1))) + { + g = &(mGridDatabase[getNeighborDetails(i, 1)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(3))) + { + g = &(mGridDatabase[getNeighborDetails(i, 3)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(5))) + { + g = &(mGridDatabase[getNeighborDetails(i, 5)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(6))) + { + g = &(mGridDatabase[getNeighborDetails(i, 6)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(7))) + { + g = &(mGridDatabase[getNeighborDetails(i, 7)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + } + else + { + if(grid.mNeighbors.test(BIT(3))) + { + g = &(mGridDatabase[getNeighborDetails(i, 3)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(6))) + { + g = &(mGridDatabase[getNeighborDetails(i, 6)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(7))) + { + g = &(mGridDatabase[getNeighborDetails(i, 7)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + } + } + else if(!isLeftEdge) + { + if(!isRightEdge) + { + if(grid.mNeighbors.test(BIT(2))) + { + g = &(mGridDatabase[getNeighborDetails(i, 2)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(4))) + { + g = &(mGridDatabase[getNeighborDetails(i, 4)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(5))) + { + g = &(mGridDatabase[getNeighborDetails(i, 5)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(6))) + { + g = &(mGridDatabase[getNeighborDetails(i, 6)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(7))) + { + g = &(mGridDatabase[getNeighborDetails(i, 7)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + } + else + { + if(grid.mNeighbors.test(BIT(2))) + { + g = &(mGridDatabase[getNeighborDetails(i, 2)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(4))) + { + g = &(mGridDatabase[getNeighborDetails(i, 4)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(6))) + { + g = &(mGridDatabase[getNeighborDetails(i, 6)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + } + } + else + { + if(grid.mNeighbors.test(BIT(5))) + { + g = &(mGridDatabase[getNeighborDetails(i, 5)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(6))) + { + g = &(mGridDatabase[getNeighborDetails(i, 6)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + if(grid.mNeighbors.test(BIT(7))) + { + g = &(mGridDatabase[getNeighborDetails(i, 7)]); + to = g->mWorld[0][0]; + to.z += 1; + drawConnection(nodeP, to); + } + } +} + +//---------------------------------------------------------------------------- + +void GroundPlan::drawConnection(Point3F &st, Point3F &ed) +{ + glBegin(GL_LINES); + glColor3f(0.0, 0.0, 1.0); + glVertex3f(st.x, st.y, st.z); + glVertex3f(ed.x, ed.y, ed.z); + glEnd(); +} + +//---------------------------------------------------------------------------- diff --git a/ai/graphForceField.cc b/ai/graphForceField.cc new file mode 100644 index 0000000..e1668ff --- /dev/null +++ b/ai/graphForceField.cc @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +#define ForceFieldCheckFrequency 2.0f +#define ForceFieldCheckDelay U32(1000.0f / ForceFieldCheckFrequency) + +//------------------------------------------------------------------------------------- + +MonitorForceFields::MonitorForceFields() +{ + mFFList = NULL; + mCount = 0; + mSaveTimeMS = 0; + for (S32 i = 0; i < GraphMaxTeams; i++) + mTeamPartitions[i].setType(GraphPartition::ForceField); +} + +MonitorForceFields::~MonitorForceFields() +{ + delete [] mFFList; +} + +//------------------------------------------------------------------------------------- + +static S32 forceFieldTeam(ForceFieldBare * ff) +{ + if (ff) + return (ff->isTeamControlled() ? ff->getSensorGroup() : 0); + return 0; +} + +static bool forceFieldUp(ForceFieldBare * ff) +{ + if (ff) + return dAtob(Con::executef(ff, 1, "isPowered")); + return false; +} + +//------------------------------------------------------------------------------------- + +MonitorForceFields::ForceField::ForceField() +{ + mActive = false; + mTeam = 0; +} + +void MonitorForceFields::ForceField::updateEdges() +{ + // Want this quick, avoid functions calls in loop in debug exe. + register GraphEdgePtrs::iterator e = mEffects.begin(); + register S32 count = mEffects.size(); + U32 team = (mActive ? mTeam : 0); + while( --count >= 0 ) + (* e++)->setTeam(team); +} + +void MonitorForceFields::atMissionStart() +{ + SimpleQueryList sql; + gServerContainer.findObjects(ForceFieldObjectType, + SimpleQueryList::insertionCallback, S32(&sql)); + + delete [] mFFList; + mFFList = new ForceField [ mCount = sql.mList.size() ]; + + // Initialze array, and disable LOS intersections on all the force fields. + S32 i; + for (i = 0; i < mCount; i++) { + ForceFieldBare * ffPtr = dynamic_cast(sql.mList[i]); + AssertFatal(ffPtr, "MonitorForceFields query didn't work"); + mFFList[i].mFF = ffPtr; + mFFList[i].mTeam = forceFieldTeam(ffPtr); + mFFList[i].mActive = forceFieldUp(ffPtr); + ffPtr->disableCollision(); + } + + // Do the collision work, only enabling the current object- + for (i = 0; i < mCount; i++) { + ForceFieldBare * ffPtr = mFFList[i].mFF; + ffPtr->enableCollision(); + mFFList[i].mEffects = gNavGraph->getBlockedEdges(ffPtr, ForceFieldObjectType); + ffPtr->disableCollision(); + } + + // Restore collision enablement state- perform initial update- + for (i = 0; i < mCount; i++) { + mFFList[i].mFF->enableCollision(); + mFFList[i].updateEdges(); + } +} + +// Clear partitions on all teams except one supplied. +void MonitorForceFields::clearPartitions(U32 exceptTeam) +{ + for (S32 i = 0; i < GraphMaxTeams; i++) + if (i != exceptTeam) + mTeamPartitions[i].clear(); +} + +// This team had a search fail (which we assure is only due to force fields), and so we +// update their FF partition list- we mark all nodes visited on search. +void MonitorForceFields::informSearchFailed(GraphSearch * searcher, U32 team) +{ + AssertFatal(team >= 0 && team < GraphMaxTeams, "Bad team index in FF monitor"); + mTeamPartitions[team].pushPartition(searcher->getPartition()); +} + +GraphPartition::Answer MonitorForceFields::reachable(U32 team, S32 from, S32 to) +{ + return mTeamPartitions[team].reachable(from, to); +} + +// Check them periodically. Just call this often. +void MonitorForceFields::monitor() +{ + U32 T = Sim::getCurrentTime(); + + if ((T - mSaveTimeMS) > ForceFieldCheckDelay) + { + mSaveTimeMS = T; + // Check for changes to team or active status. + for (S32 i = 0; i < mCount; i++) + { + ForceField & ff = mFFList[i]; + + // if (ForceFieldBare * forceFieldObject = (ForceFieldBare*)ff.mFF) + { + U32 team = forceFieldTeam(ff.mFF); + bool active = forceFieldUp((ForceFieldBare*)ff.mFF); + + if (team == ff.mTeam && active == ff.mActive) + continue; + + // Here we only invalidate the old team and the new team, unless either + // of them is team zero (which means everybody's partition gets changed). + if (team != ff.mTeam) { + if (team && ff.mTeam) { + mTeamPartitions[team].clear(); + mTeamPartitions[ff.mTeam].clear(); + } + else { + clearPartitions(0); + } + ff.mTeam = team; + } + + if (active != ff.mActive) { + clearPartitions(ff.mTeam); + ff.mActive = active; + } + + mFFList[i].updateEdges(); + } + } + } +} diff --git a/ai/graphForceField.h b/ai/graphForceField.h new file mode 100644 index 0000000..efc6e27 --- /dev/null +++ b/ai/graphForceField.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHFORCEFIELD_H_ +#define _GRAPHFORCEFIELD_H_ + +#ifndef _FORCEFIELDBARE_H_ +#include "game/forceFieldBare.h" +#endif + +class MonitorForceFields +{ + public: + struct ForceField + { + ForceField(); + void updateEdges(); + SimObjectPtr mFF; + GraphEdgePtrs mEffects; + bool mActive; + U32 mTeam; + }; + + protected: + ForceField * mFFList; + S32 mCount; + U32 mSaveTimeMS; + PartitionList mTeamPartitions[GraphMaxTeams]; + + public: + MonitorForceFields(); + ~MonitorForceFields(); + GraphPartition::Answer reachable(U32 team, S32 from, S32 to); + void clearPartitions(U32 allBut); + void informSearchFailed(GraphSearch * searcher, U32 team); + S32 count() const {return mCount;} + void atMissionStart(); + void monitor(); +}; + +#endif diff --git a/ai/graphGenUtils.cc b/ai/graphGenUtils.cc new file mode 100644 index 0000000..defa05f --- /dev/null +++ b/ai/graphGenUtils.cc @@ -0,0 +1,212 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graphGenUtils.h" + +//---------------------------------------------------------------- +// +// EdgeTable Implementation +// +//---------------------------------------------------------------- + +EdgeTable::EdgeTable() +{ + hashTable = NULL; + init(); +} + +//---------------------------------------------------------------- + +EdgeTable::~EdgeTable() +{ +} + +//---------------------------------------------------------------- + +S32 EdgeTable::hash(DEdge edge) +{ + S32 value = ((edge.start + edge.end) % 1009);// <- prime # + return value; +} + +//---------------------------------------------------------------- + +void EdgeTable::init() +{ + hashTable = new DEdge *[DefaultTableSize]; + hashTableSize = DefaultTableSize; + hashEntryCount = 0; + + S32 i; + for(i = 0; i < hashTableSize; i++) + hashTable[i] = NULL; +} + +//---------------------------------------------------------------- + +void EdgeTable::clear() +{ + DEdge *walk, *temp; + U32 i; + for(i = 0; i < hashTableSize; i++) + { + walk = hashTable[i]; + while(walk) + { + temp = walk->next; + delete walk; + walk = temp; + } + hashTable[i] = NULL; + } + delete [] hashTable; +} + +//---------------------------------------------------------------- + +void EdgeTable::insert(DEdge edge) +{ + DEdge *entry = new DEdge(); + entry->start = edge.start; + entry->end = edge.end; + entry->midPoint = edge.midPoint; + entry->next = NULL; + entry->right = NULL; + entry->left = NULL; + entry->length = edge.length; + entry->flags = edge.flags; + // entry->totalBelongsTo = edge.totalBelongsTo; + + S32 i; + // for(i = 0; i < edge.totalBelongsTo; i++) + // entry->shared[i] = edge.shared[i]; + + S32 idx = (hash(*entry) % hashTableSize); + entry->next = hashTable[idx]; + hashTable[idx] = entry; + hashEntryCount++; + if(hashEntryCount > hashTableSize) + { + // resize the hash table + DEdge *head = NULL, *walk, *temp; + for(i = 0; i < hashTableSize; i++) + { + walk = hashTable[i]; + while(walk) + { + temp = walk->next; + walk->next = head; + head = walk; + walk = temp; + } + } + delete [] hashTable; + hashTableSize = hashTableSize * 2 + 1; + hashTable = new DEdge *[hashTableSize]; + + for(i = 0; i < hashTableSize; i++) + hashTable[i] = NULL; + + while(head) + { + temp = head->next; + idx = (hash(*head) % hashTableSize); + head->next = hashTable[idx]; + hashTable[idx] = head; + head = temp; + } + } +} + +//---------------------------------------------------------------- + +bool EdgeTable::contains(DEdge edge) +{ + DEdge **walk = &hashTable[hash(edge) % hashTableSize]; + while(*walk) + { + if(**walk == edge) + return true; + walk = &((*walk)->next); + } + return false; +} + +//---------------------------------------------------------------- + +DEdge *EdgeTable::find(DEdge edge) +{ + DEdge **walk = &hashTable[hash(edge) % hashTableSize]; + while(*walk) + { + if(**walk == edge) + return *walk; + walk = &((*walk)->next); + } + return NULL; +} + + +//------------------------------------------------------------------------------------- + +DSurf::DSurf() +{ + mEdges = NULL; + mLongest = NULL; + mNumEdges = 0; + mSubSurfCount = 0; + mDivLevel = 0; + mMaxRadius = 0.0; + mMinRadius = 0.0; + parent = NULL; + dMemset(mSubSurfs, 0, sizeof(mSubSurfs)); + dMemset(fullWinding, 0, sizeof(fullWinding)); + mNormal.set(0,0,0); + mCtrIdx = 0; + volIdx = 0; + mZone = -1; +} + +//------------------------------------------------------------------------------------- + +DVolume::DVolume() +{ + mNumPlanes = 0; + dMemset(mPlanes, 0, sizeof(mPlanes)); + surfIdx = 0; + ht = 0.0; + capPt.set(0,0,0); +} + +// See if point is inside the planes- +bool DVolume::checkInside(const Point3F& point) +{ + for (S32 i = 0; i < mNumPlanes; i++) + if (mPlanes[i].distToPlane(point) >= 0.1) + return false; + return true; +} + + +//------------------------------------------------------------------------------------- +// Interior Detail + +InteriorDetail::InteriorDetail() +{ + mIndex = -1; + mNumUnObstructed = 0; + numPolyLists = 0; +} + +bool InteriorDetail::haveSurfaceNear(const Point3F& pos, F32 rad) +{ + rad *= rad; // use square of length + for (S32 i = 0; i < mSurfaces.size(); i++) + if ((pos - mSurfaces[i].mCntr).lenSquared() < rad) + return true; + return false; +} diff --git a/ai/graphGenUtils.h b/ai/graphGenUtils.h new file mode 100644 index 0000000..c1b079f --- /dev/null +++ b/ai/graphGenUtils.h @@ -0,0 +1,304 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHGENUTILS_H_ +#define _GRAPHGENUTILS_H_ + +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _BITSET_H_ +#include "Core/bitSet.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MPLANE_H_ +#include "Math/mPlane.h" +#endif +#ifndef _MBOX_H_ +#include "Math/mBox.h" +#endif +#ifndef _GRAPHDATA_H_ +#include "ai/graphData.h" +#endif + +//---------------------------------------------------------------- + +class DEdge +{ + public: + enum + { + divGenerated = BIT(0), + wall = BIT(1), + divided = BIT(2), + drawn = BIT(3), + traversable = BIT(4), + }; + + public: + U32 start, end, midPoint; + DEdge *right, *left; + DEdge *next; + BitSet32 flags; + F32 length; + + public: + bool operator==(const DEdge &edge); +}; + +inline bool DEdge::operator==(const DEdge &edge) +{ + return (((start == edge.start) && (end == edge.end)) || + ((end == edge.start) && (start == edge.end))); +} + +//---------------------------------------------------------------- +// hashtable to make graph edge building very quick. +class EdgeTable +{ + public: + enum + { + DefaultTableSize = 32, + }; + + private: + DEdge **hashTable; + U32 hashTableSize; + U32 hashEntryCount; + + public: + EdgeTable(); + ~EdgeTable(); + + void insert(DEdge edge); + bool contains(DEdge edge); + DEdge *find(DEdge edge); + DEdge *getBucket(U32 bucket); + U32 size() const; + U32 tableSize() const; + void clear(); + + private: + void init(); + S32 hash(DEdge edge); +}; + +//---------------------------------------------------------------- + +inline DEdge *EdgeTable::getBucket(U32 bucket) +{ + return hashTable[bucket]; +} + +//---------------------------------------------------------------- + +inline U32 EdgeTable::size() const +{ + return hashEntryCount; +} + +//---------------------------------------------------------------- + +inline U32 EdgeTable::tableSize() const +{ + return hashTableSize; +} + +//---------------------------------------------------------------- + +class DSpecNode +{ + public: + enum + { + chuteNode = BIT(0), + }; + + public: + BitSet32 flags; + Point3F pos; + StringTableEntry name; +}; + +//---------------------------------------------------------------- + +class DPoint : public Point3F +{ + public: + enum + { + divGenerated = BIT(0), + wall = BIT(1), + center = BIT(2), + inventory = BIT(3), + }; + + public: + BitSet32 flags; + DEdge *e; + U32 surfIdx; + + public: + //operator Point3F &(){ return *this; } +}; + +//---------------------------------------------------------------- + +class DSurf +{ + public: + enum + { + isWall = BIT(0), + shouldBeCulled = BIT(1), + divided = BIT(2), + split = BIT(3), + unObstructed = BIT(4), + specialSplit = BIT(5), + tooSmall = BIT(6), + }; + + DEdge **mEdges; + DEdge *mLongest; + BitSet32 mFlags; + BitSet32 mFlipStates; + U32 mNumEdges; + U16 mSubSurfs[32]; + U16 mSubSurfCount; + VectorF mNormal; + S16 mDivLevel; + F32 mMaxRadius; + F32 mMinRadius; + U32 fullWinding[32]; + DSurf *parent; + DPoint mCntr; + U32 mCtrIdx; + U32 volIdx; + S32 mZone; + + DSurf(); +}; + +//---------------------------------------------------------------- + +class DConnection +{ + public: + U32 start; + U32 end; + + // interface points + Point3F segNodes[2]; + + public: + bool operator==(const DConnection &con); +}; + +//---------------------------------------------------------------- + +inline bool DConnection::operator==(const DConnection &con) +{ + return (((start == con.start) && (end == con.end)) || + ((end == con.start) && (start == con.end))); +} + +//---------------------------------------------------------------- + +class DVolume +{ + public: + U32 mNumPlanes; + PlaneF mPlanes[32]; + U32 surfIdx; + F32 ht; + Point3F capPt; + DVolume(); + + bool checkInside(const Point3F& pt); +}; + +class DPortal +{ + public: + Point3F center; +}; + +//---------------------------------------------------------------- + +class InteriorDetail +{ + public: + Vector mSurfaces; + Vector mPoints; + Vector mConnections; + Vector mConPoints; + Vector mVolumes; + Vector mTraversable; + Vector mUnobstructed; + Vector mSpecialNodes; + Vector polyTestPoints; + Vector mPortals; + Vector sortedSurfList; + + //ClippedPolyList *polyLists; + EdgeTable mEdgeTable; + S16 mIndex; + S32 mNumUnObstructed; + Box3F mBbox; + S32 numPolyLists; + + InteriorDetail(); + bool haveSurfaceNear(const Point3F& pos, F32 rad); +}; + +class GraphDetails +{ + public: + EdgeInfoList edges; + NodeInfoList nodes; + EdgeInfoList segs; + GraphVolumeList volumes; + Vector chutes; + + GraphDetails() { } +}; + +//---------------------------------------------------------------- + +class GridDetail +{ + public: + enum { + clear = 0, + shadowed = 1, + obstructed = 2, + submerged = 3, + }; + + // these are at the node level + U8 mNavFlag; + BitSet32 mNeighbors; + F32 mShadowHt; + + // these are at the triangle level + U8 mNavFlags[2]; + F32 mShadowHts[2]; + Point3F mWorld[2][3]; +}; + +struct DSide +{ + S32 closePts; + F32 bestDist; + + DSide() { closePts = 0; bestDist = 0.f; } +}; + +#endif diff --git a/ai/graphGroundPlan.cc b/ai/graphGroundPlan.cc new file mode 100644 index 0000000..04b4fb4 --- /dev/null +++ b/ai/graphGroundPlan.cc @@ -0,0 +1,1067 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" +#include "terrain/terrData.h" +#include "Editor/terrainActions.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "Collision/clippedPolyList.h" +#include "ai/graphGroundVisit.h" +#include "terrain/waterBlock.h" + +GroundPlan *gGroundPlanTest = NULL; +IMPLEMENT_CONOBJECT(GroundPlan); + +// 4 6 7 +// 2 N 5 +// 0 1 3 +Point2I GroundPlan::gridOffset[8] = +{ + Point2I(-1, -1),// 0 + Point2I(0, -1), Point2I(-1, 0), // 1 2 + Point2I(1, -1), Point2I(-1, 1), // 3 4 + Point2I(1, 0), Point2I(0, 1), // 5 6 + Point2I(1, 1) // 7 +}; + + +GroundPlan::GroundPlan() +{ + mTerrainBlock = 0; + mTotalVisited = 0; +} + +//---------------------------------------------------------------------------- + +GroundPlan::~GroundPlan() +{ +} + +//---------------------------------------------------------------------------- + +GridDetail GroundPlan::defaultDetail() +{ + GridDetail detail; + detail.mNavFlags[0] = GridDetail::clear; + detail.mNavFlags[1] = GridDetail::clear; + detail.mShadowHts[0] = -1; + detail.mShadowHts[1] = -1; + return detail; +} + +//---------------------------------------------------------------------------- + +void GroundPlan::initPersistFields() +{ + Parent::initPersistFields(); +} + +//---------------------------------------------------------------------------- + +bool GroundPlan::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mTerrainBlock = getTerrainObj(); + gGroundPlanTest = this; + return true; +} + +//---------------------------------------------------------------------------- + +void GroundPlan::onRemove() +{ + Parent::onRemove(); + gGroundPlanTest = NULL; +} + +//---------------------------------------------------------------------------- + +void GroundPlan::getClippedRegion(S32 index, S32 &st, S32 &ed, S32 &xspan, S32 &yspan) +{ + xspan = 16; + yspan = 16; + st = (index - 8) - (8 * mGridDims.x); + ed = (index + 8) + (8 * mGridDims.x); +} + +//---------------------------------------------------------------------------- + +bool GroundPlan::isOnBoundary(S32 i) +{ + if (i % mGridDims.x == 0) + return true; + if (i < mGridDims.x) + return true; + if (((i+1) % mGridDims.x) == 0) + return true; + if (i >= ((mGridDims.x * mGridDims.y) - mGridDims.x)) + return true; + + return false; +} + +//---------------------------------------------------------------------------- + +S32 GroundPlan::gridToIndex(const Point2I &gPos) +{ + S32 index; + index = gPos.x - mGridOrigin.x; + index += ((gPos.y - mGridOrigin.y) * mGridDims.x); + return index; +} + +//---------------------------------------------------------------------------- + +void GroundPlan::gridToWorld(const Point2I &gPos, Point3F &wPos) +{ + const MatrixF &mat = mTerrainBlock->getTransform(); + Point3F origin; + mat.getColumn(3, &origin); + + wPos.x = gPos.x * (float)mTerrainBlock->getSquareSize() + origin.x; + wPos.y = gPos.y * (float)mTerrainBlock->getSquareSize() + origin.y; + wPos.z = getGridHeight(gPos); +} + +//---------------------------------------------------------------------------- + +void GroundPlan::worldToGrid(const Point3F &wPos, Point2I &gPos) +{ + const MatrixF & mat = mTerrainBlock->getTransform(); + Point3F origin; + mat.getColumn(3, &origin); + + float x = (wPos.x - origin.x) / (float)mTerrainBlock->getSquareSize(); + float y = (wPos.y - origin.y) / (float)mTerrainBlock->getSquareSize(); + + gPos.x = (S32)mFloor(x); + gPos.y = (S32)mFloor(y); +} + +//---------------------------------------------------------------------------- + +void GroundPlan::gridToCenter(const Point2I &gPos, Point2I &cPos) +{ + cPos.x = gPos.x & TerrainBlock::BlockMask; + cPos.y = gPos.y & TerrainBlock::BlockMask; +} + +//---------------------------------------------------------------------------- + +F32 GroundPlan::getGridHeight(const Point2I &gPos) +{ + Point2I cPos; + gridToCenter(gPos, cPos); + return(fixedToFloat(mTerrainBlock->getHeight(cPos.x, cPos.y))); +} + +//---------------------------------------------------------------------------- + +TerrainBlock *GroundPlan::getTerrainObj() +{ + SimObject *obj = Sim::findObject("Terrain"); + if(!obj) + return(0); + + TerrainBlock *terrain = static_cast(obj); + return(terrain); +} + +//---------------------------------------------------------------------------- + +bool GroundPlan::inspect(GridArea &area) +{ + if(!mTerrainBlock) + { + mTerrainBlock = getTerrainObj(); + if(!mTerrainBlock) + { + Con::printf("no Terrain object"); + return false; + } + } + + AssertFatal(area.extent.x > 0 && area.extent.y > 0, "no extents to mission area"); + + GridArea inspectArea; + Point3F wPos(area.point.x, area.point.y, 0); + worldToGrid(wPos, inspectArea.point); + + mGridOrigin = inspectArea.point; + mGridDims.x = (area.extent.x >> 3); + mGridDims.y = (area.extent.y >> 3); + mGridDatabase.clear(); + + // build the initial database with default details + for(S32 y = mGridOrigin.y; y < (mGridOrigin.y + mGridDims.y); y++) + { + for(S32 x = mGridOrigin.x; x < (mGridOrigin.x + mGridDims.x); x++) + { + GridDetail detail; + detail = defaultDetail(); + mGridDatabase.push_back(detail); + } + } + + // perform mission area inspection + GridArea inspectionArea(mGridOrigin, mGridDims); + InspectionVisitor inspector(inspectionArea, *this); + inspector.traverse(); + computeNavFlags(); + findNeighbors(); + + Con::printf("total zero squares inspected: %d\n", mTotalVisited); + + // were done! + return true; +} + +//------------------------------------------------------------------------------------- + +// static void doTempBreak() {} +// static Point3F minBreakPt(936, 1184, 0); +// static Point3F maxBreakPt(944, 1192, 0); + +//------------------------------------------------------------------------------------- + +void GroundPlan::inspectSquare(const GridArea &gridArea, GridDetail &square) +{ + S32 sqSize = mTerrainBlock->getSquareSize(); + Point2I cPos; + gridToCenter(gridArea.point, cPos); + bool is45 = isSplit45(cPos); + bool isEmpty = false; + Point3F verts[2][3]; + Point3F mid1; + Point3F mid2; + Point3F wPos; + + gridToWorld(gridArea.point+gridOffset[6], mid1); // 0,1 + gridToWorld(gridArea.point+gridOffset[5], mid2); // 1,0 + gridToWorld(gridArea.point, wPos); + + Box3F area; + area.min = wPos; + area.max.x = area.min.x + sqSize; + area.max.y = area.min.y + sqSize; + area.max.z = getGridHeight(gridArea.point + gridOffset[7]); + + // if (within_2D(area.min, minBreakPt, 0.1)) + // if (within_2D(area.max, maxBreakPt, 0.1)) + // doTempBreak(); + + if(is45) + { + verts[0][0] = area.min; + verts[0][1] = mid1; + verts[0][2] = area.max; + verts[1][0] = area.min; + verts[1][1] = area.max; + verts[1][2] = mid2; + } + else + { + verts[0][0] = area.min; + verts[0][1] = mid1; + verts[0][2] = mid2; + verts[1][0] = mid1; + verts[1][1] = area.max; + verts[1][2] = mid2; + } + + square.mWorld[0][0] = verts[0][0]; + square.mWorld[0][1] = verts[0][1]; + square.mWorld[0][2] = verts[0][2]; + square.mWorld[1][0] = verts[1][0]; + square.mWorld[1][1] = verts[1][1]; + square.mWorld[1][2] = verts[1][2]; + + // check for empty square + GridSquare *gs = mTerrainBlock->findSquare(0, gridArea.point); + if(gs->flags & GridSquare::Empty) + isEmpty = true; + + if(!isEmpty) + { + U32 mask = InteriorObjectType | TurretObjectType; + + RayInfo collision; + Point3F endPt1, endPt2, endPt3, endPt4; + + endPt1 = verts[0][1]; // mid1 (0,1) + endPt2 = verts[1][1]; // area max + endPt3 = verts[1][2]; // mid2 (1,0) + endPt4 = verts[0][0]; // area min + + for (S32 i = 0; i < 2; i++) + { + bool done = false; + if (is45) { + if (i == 0) { + if(gServerContainer.castRay(endPt4, endPt1, mask, &collision) || + gServerContainer.castRay(endPt1, endPt2, mask, &collision) || + gServerContainer.castRay(endPt2, endPt4, mask, &collision)) + { + square.mNavFlags[i] = GridDetail::obstructed; + square.mShadowHt = -1; + done = true; + } + } + else + { + if(gServerContainer.castRay(endPt4, endPt2, mask, &collision) || + gServerContainer.castRay(endPt2, endPt3, mask, &collision) || + gServerContainer.castRay(endPt3, endPt4, mask, &collision)) + { + square.mNavFlags[i] = GridDetail::obstructed; + square.mShadowHt = -1; + done = true; + } + } + } + else + { + if(i == 0) + { + if(gServerContainer.castRay(endPt4, endPt1, mask, &collision) || + gServerContainer.castRay(endPt1, endPt3, mask, &collision) || + gServerContainer.castRay(endPt3, endPt4, mask, &collision)) + { + square.mNavFlags[i] = GridDetail::obstructed; + square.mShadowHt = -1; + done = true; + } + } + else + { + if(gServerContainer.castRay(endPt1, endPt2, mask, &collision) || + gServerContainer.castRay(endPt2, endPt3, mask, &collision) || + gServerContainer.castRay(endPt3, endPt1, mask, &collision)) + { + square.mNavFlags[i] = GridDetail::obstructed; + square.mShadowHt = -1; + done = true; + } + } + } + + if(!done) + { + Point3F min, mid, max; + VectorF norm1, norm2, norm3; + Box3F bBox(endPt4, endPt2); + Point3F extra(1, 1, 0); + bBox.min -= extra; + bBox.max += extra; + bBox.max.z += 1000; + + if(is45) { + if(i == 0) { + min = endPt4; + mid = endPt1; + max = endPt2; + norm1.set(-1, 0, 0); + norm2.set(0, 1, 0); + norm3.set(0.7071, -0.7071, 0); + } + else { + min = endPt4; + mid = endPt2; + max = endPt3; + norm1.set(-0.7071, 0.7071, 0); + norm2.set(1, 0, 0); + norm3.set(0, -1, 0); + } + } + else { + if(i == 0) { + min = endPt4; + mid = endPt1; + max = endPt3; + norm1.set(-1, 0, 0); + norm2.set(0.7071, 0.7071, 0); + norm3.set(0, -1, 0); + } + else { + min = endPt1; + mid = endPt2; + max = endPt3; + norm1.set(0, 1, 0); + norm2.set(1, 0, 0); + norm3.set(-0.7071, -0.7071, 0); + } + } + + F32 threshold = 2.2, heightDiff; + + if(setupPolyList(bBox, min, mid, max, norm1, norm2, norm3, &heightDiff)) + { + if(heightDiff > threshold) + { + square.mNavFlags[i] = GridDetail::shadowed; + square.mShadowHts[i] = heightDiff; + } + else + { + square.mNavFlags[i] = GridDetail::obstructed; + square.mShadowHts[i] = -1; + } + } + else + { + square.mNavFlags[i] = GridDetail::clear; + square.mShadowHts[i] = -1; + } + } + } + } + else + { + square.mNavFlags[0] = GridDetail::obstructed; + square.mNavFlags[1] = GridDetail::obstructed; + square.mShadowHts[0] = -1; + square.mShadowHts[1] = -1; + } +} + +//------------------------------------------------------------------------------------- + +// Changed to account for actual worst case height in the triangle. As it was, it +// would yeild negative heights on some obstructed areas on slopes, which would cause +// the inspect to mark it clear. + +bool GroundPlan::setupPolyList(const Box3F &bBox, + const Point3F &min, + const Point3F &mid, + const Point3F &max, + const VectorF &norm1, + const VectorF &norm2, + const VectorF &norm3, + F32 *height ) +{ + ClippedPolyList polyList; + + polyList.mPlaneList.clear(); + polyList.mPlaneList.setSize(5); + + // Build a bottom plane using the three points. + Point3F vec1 = (mid - max); + Point3F vec2 = (mid - min); + VectorF bottomNormal; + mCross(vec1, vec2, &bottomNormal); + if (bottomNormal.z > 0) + bottomNormal.z = -bottomNormal.z; + + // Need to find maximum Z value as basis for determining height. + F32 highestZ = min.z; + if (max.z > highestZ) + highestZ = max.z; + if (mid.z > highestZ) + highestZ = mid.z; + + // Adjust out the triangle to ensure more "breathing room" around obstacles + Point3F adjusted1 = (min + (norm1 * 0.8f)); + Point3F adjusted2 = (mid + (norm2 * 0.8f)); + Point3F adjusted3 = (max + (norm3 * 0.8f)); + + polyList.mNormal.set(0, 0, 0); + polyList.mPlaneList[0].set(adjusted1, norm1); + polyList.mPlaneList[1].set(adjusted2, norm2); + polyList.mPlaneList[2].set(adjusted3, norm3); + // polyList.mPlaneList[3].set(min, VectorF(0, 0, -1)); + polyList.mPlaneList[3].set(min, bottomNormal); + polyList.mPlaneList[4].set(bBox.max, VectorF(0, 0, 1)); + + U32 mask = InteriorObjectType | TurretObjectType; + + // build the poly list + if(gServerContainer.buildPolyList(bBox, mask, &polyList)) + { + F32 lowestZ = 10000000, lowestDeltaZ; + bool found = false; + + for (S32 j = 0; j < polyList.mPolyList.size(); j++) { + ClippedPolyList::Poly *p = &(polyList.mPolyList[j]); + for (S32 k = p->vertexStart; k < (p->vertexStart + p->vertexCount); k++) { + F32 ht = polyList.mVertexList[polyList.mIndexList[k]].point.z; + if(ht < lowestZ) { + lowestZ = ht; + lowestDeltaZ = (ht - highestZ); + found = true; + } + } + } + + if (found) { + *height = lowestDeltaZ; + return true; + } + } + return false; +} + +//---------------------------------------------------------------------------- + +void GroundPlan::computeNavFlags() +{ + bool isBottomRow = false; + bool isLeftEdge = false; + bool is45 = false; + bool hasWater = missionHasWater(); + BitSet32 obsBits; + S32 totalNodes = mGridDatabase.size(); + + // detection of water or liquids + Vector blocks; + if(hasWater) + getAllWaterBlocks(blocks); + + U32 i; + for(i = 0; i < totalNodes; i++) + { + is45 = isSplit45(*(getPosition(i))); + isBottomRow = false; + isLeftEdge = false; + + if(i % mGridDims.x == 0) + isLeftEdge = true; + if(i < mGridDims.x) + isBottomRow = true; + + GridDetail &g = mGridDatabase[i]; + packTriangleBits(g, obsBits, GridDetail::obstructed, i, isLeftEdge, isBottomRow); + + if(is45) + { + if(obsBits.testStrict(0xff)) + g.mNavFlag = GridDetail::obstructed; + else + { + // check for shadowed node based on surrounding triangles + BitSet32 shBits; + packTriangleBits(g, shBits, GridDetail::shadowed, i, isLeftEdge, isBottomRow); + if(shBits > 0) + { + g.mNavFlag = GridDetail::shadowed; + //g.mShadowHt = 20; + g.mShadowHt = lowestShadowHt(g, shBits, i); + } + else + g.mNavFlag = GridDetail::clear; + } + } + else + { + if(obsBits.test(BIT(1)) && obsBits.test(BIT(2)) && obsBits.test(BIT(5)) && obsBits.test(BIT(6))) + g.mNavFlag = GridDetail::obstructed; + else + { + // check for shadowed node based on surrounding triangles + BitSet32 shBits; + packTriangleBits(g, shBits, GridDetail::shadowed, i, isLeftEdge, isBottomRow); + if(shBits > 0) + { + g.mNavFlag = GridDetail::shadowed; + g.mShadowHt = 20; + g.mShadowHt = lowestShadowHt(g, shBits, i); + } + else + g.mNavFlag = GridDetail::clear; + } + } + + // set water bit if needed + if(hasWater) + { + U32 blockCount; + for(blockCount = 0; blockCount < blocks.size(); blockCount++) + { + Point2I *gPos = getPosition(i); + Point3F wPos; + gridToWorld((*gPos), wPos); + if((wPos.x < -292 && wPos.x > -298) && (wPos.y < -130 && wPos.y > -135)) + bool test = true; + + if(blocks[blockCount]->isPointSubmergedSimple(wPos)) + { + g.mNavFlag = GridDetail::submerged; + break; // once the bit is set, it can't change. + } + } + } + } +} + +//---------------------------------------------------------------------------- + +void GroundPlan::findNeighbors() +{ + bool isBottomRow = false; + bool isLeftEdge = false; + bool is45; + BitSet32 obsBits; + + S32 totalNodes = mGridDatabase.size(); + + U32 i; + for(i = 0; i < totalNodes; i++) + { + isBottomRow = false; + isLeftEdge = false; + + if(i % mGridDims.x == 0) + isLeftEdge = true; + if(i < mGridDims.x) + isBottomRow = true; + + is45 = isSplit45(*(getPosition(i))); + GridDetail &g = mGridDatabase[i]; + packTriangleBits(g, obsBits, GridDetail::obstructed, i, isLeftEdge, isBottomRow); + + // we have the info, now find the neighbors + g.mNeighbors.clear(); + + if(is45) + { + if(!obsBits.test(BIT(0)) || !obsBits.test(BIT(1))) + g.mNeighbors.set(BIT(0)); + if(!obsBits.test(BIT(1)) || !obsBits.test(BIT(2))) + g.mNeighbors.set(BIT(1)); + if(!obsBits.test(BIT(4)) || !obsBits.test(BIT(0))) + g.mNeighbors.set(BIT(2)); + if(!obsBits.test(BIT(2)) || !obsBits.test(BIT(3))) + g.mNeighbors.set(BIT(3)); + if(!obsBits.test(BIT(4)) || !obsBits.test(BIT(5))) + g.mNeighbors.set(BIT(4)); + if(!obsBits.test(BIT(3)) || !obsBits.test(BIT(7))) + g.mNeighbors.set(BIT(5)); + if(!obsBits.test(BIT(5)) || !obsBits.test(BIT(6))) + g.mNeighbors.set(BIT(6)); + if(!obsBits.test(BIT(6)) || !obsBits.test(BIT(7))) + g.mNeighbors.set(BIT(7)); + } + else + { + // todo: + // check for slope when crossing triangle boundry. + if(!obsBits.test(BIT(0)) && !obsBits.test(BIT(1))) + g.mNeighbors.set(BIT(0)); + if(!obsBits.test(BIT(1)) || !obsBits.test(BIT(2))) + g.mNeighbors.set(BIT(1)); + if(!obsBits.test(BIT(1)) || !obsBits.test(BIT(5))) + g.mNeighbors.set(BIT(2)); + if(!obsBits.test(BIT(2)) && !obsBits.test(BIT(3))) + g.mNeighbors.set(BIT(3)); + if(!obsBits.test(BIT(4)) && !obsBits.test(BIT(5))) + g.mNeighbors.set(BIT(4)); + if(!obsBits.test(BIT(6)) || !obsBits.test(BIT(2))) + g.mNeighbors.set(BIT(5)); + if(!obsBits.test(BIT(5)) || !obsBits.test(BIT(6))) + g.mNeighbors.set(BIT(6)); + if(!obsBits.test(BIT(6)) && !obsBits.test(BIT(7))) + g.mNeighbors.set(BIT(7)); + } + } +} + +//---------------------------------------------------------------------------- + +S32 GroundPlan::getNavigable(Vector &navFlags, Vector &shadowHts) +{ + U32 i; + S32 shadowed = 0; + S32 totalNodes = mGridDatabase.size(); + navFlags.setSize( totalNodes ); + shadowHts.setSize( totalNodes ); + + for(i = 0; i < totalNodes; i++) + { + navFlags[i] = mGridDatabase[i].mNavFlag; + shadowHts[i] = mGridDatabase[i].mShadowHt; + if(mGridDatabase[i].mNavFlag == GridDetail::shadowed) + shadowed++; + } + return shadowed; +} + +//---------------------------------------------------------------------------- + +Vector &GroundPlan::getNeighbors(Vector &neighbors) +{ + U32 i; + S32 totalNodes = mGridDatabase.size(); + neighbors.setSize( totalNodes ); + for(i = 0; i < totalNodes; i++) + neighbors[i] = mGridDatabase[i].mNeighbors; + return neighbors; +} + +//---------------------------------------------------------------------------- + +void GroundPlan::packTriangleBits(const GridDetail &g, BitSet32 &bits, S32 bitType, S32 index, bool isLeftEdge, bool isBottomRow) +{ + GridDetail *grid; + + bool isRightEdge = false; + bool isTopRow = false; + + if(((index+1) % mGridDims.x) == 0) + isRightEdge = true; + if(index >= ((mGridDims.x * mGridDims.y) - mGridDims.x)) + isTopRow = true; + + bits.clear(); + if(isBottomRow && isRightEdge) + { + // these are considered obstructed triangles + // since we are sitting at the origin + if(bitType == GridDetail::obstructed) + bits.set(BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(6)|BIT(7)); + + grid = &(mGridDatabase[getNeighborDetails(index, 2)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(4)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(5)); + } + else if(isLeftEdge && isTopRow) + { + // these are considered obstructed triangles + // since we are sitting at the origin + if(bitType == GridDetail::obstructed) + bits.set(BIT(0)|BIT(1)|BIT(4)|BIT(5)|BIT(6)|BIT(7)); + + grid = &(mGridDatabase[getNeighborDetails(index, 1)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(2)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(3)); + } + else if(!isBottomRow && !isLeftEdge) + { + // checks for right and top edges + if(isTopRow && !isRightEdge) + { + if(bitType == GridDetail::obstructed) + bits.set(BIT(6)|BIT(7)|BIT(4)|BIT(5)); + + grid = &(mGridDatabase[getNeighborDetails(index, 0)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(0)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(1)); + + grid = &(mGridDatabase[getNeighborDetails(index, 1)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(2)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(3)); + } + else if(isRightEdge && !isTopRow) + { + if(bitType == GridDetail::obstructed) + bits.set(BIT(2)|BIT(3)|BIT(6)|BIT(7)); + + grid = &(mGridDatabase[getNeighborDetails(index, 0)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(0)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(1)); + + grid = &(mGridDatabase[getNeighborDetails(index, 2)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(4)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(5)); + } + else if(index != mGridDatabase.size()-1) + { + // need to test all triangles + grid = &(mGridDatabase[getNeighborDetails(index, 0)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(0)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(1)); + + grid = &(mGridDatabase[getNeighborDetails(index, 1)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(2)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(3)); + + grid = &(mGridDatabase[getNeighborDetails(index, 2)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(4)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(5)); + + if(g.mNavFlags[0] == bitType) + bits.set(BIT(6)); + if(g.mNavFlags[1] == bitType) + bits.set(BIT(7)); + } + else + { + if(bitType == GridDetail::obstructed) + bits.set(BIT(6)|BIT(7)|BIT(2)|BIT(3)|BIT(4)|BIT(5)); + + grid = &(mGridDatabase[getNeighborDetails(index, 0)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(0)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(1)); + } + } + else if(!isBottomRow) + { + // these are considered obstructed triangles + // since we are sitting on the left edge + if(bitType == GridDetail::obstructed) + bits.set(BIT(0)|BIT(1)|BIT(5)|BIT(4)); + grid = &(mGridDatabase[getNeighborDetails(index, 1)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(2)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(3)); + + if(g.mNavFlags[0] == bitType) + bits.set(BIT(6)); + if(g.mNavFlags[1] == bitType) + bits.set(BIT(7)); + + } + else if(!isLeftEdge) + { + // these are considered obstructed triangles + // since we are sitting on the bottom row + if(bitType == GridDetail::obstructed) + bits.set(BIT(0)|BIT(1)|BIT(2)|BIT(3)); + if(g.mNavFlags[0] == bitType) + bits.set(BIT(6)); + if(g.mNavFlags[1] == bitType) + bits.set(BIT(7)); + + grid = &(mGridDatabase[getNeighborDetails(index, 2)]); + if(grid->mNavFlags[0] == bitType) + bits.set(BIT(4)); + if(grid->mNavFlags[1] == bitType) + bits.set(BIT(5)); + } + else + { + // these are considered obstructed triangles + // since we are sitting at the origin + if (bitType == GridDetail::obstructed) + bits.set(BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)|BIT(5)); + + // we are at the origin + if(g.mNavFlags[0] == bitType) + bits.set(BIT(6)); + if(g.mNavFlags[1] == bitType) + bits.set(BIT(7)); + } +} + +//---------------------------------------------------------------------------- + +// static Point3F breakPt(160, -272, 0); +static void doBreak() {} + +//---------------------------------------------------------------------------- + +F32 GroundPlan::lowestShadowHt(const GridDetail &g, BitSet32 &bits, S32 index) +{ + F32 minList[8]; + S32 N = 0; + + // if (within_2D(g.mWorld[0][0], breakPt, 1.0)) + // doBreak(); + + GridDetail * grid = &(mGridDatabase[getNeighborDetails(index, 0)]); + if(bits.test(BIT(0))) + minList[N++] = grid->mShadowHts[0]; + if(bits.test(BIT(1))) + minList[N++] = grid->mShadowHts[1]; + + grid = &(mGridDatabase[getNeighborDetails(index, 1)]); + if(bits.test(BIT(2))) + minList[N++] = grid->mShadowHts[0]; + if(bits.test(BIT(3))) + minList[N++] = grid->mShadowHts[1]; + + grid = &(mGridDatabase[getNeighborDetails(index, 2)]); + if(bits.test(BIT(4))) + minList[N++] = grid->mShadowHts[0]; + if(bits.test(BIT(5))) + minList[N++] = grid->mShadowHts[1]; + + if(bits.test(BIT(6))) + minList[N++] = g.mShadowHts[0]; + if(bits.test(BIT(7))) + minList[N++] = g.mShadowHts[1]; + + // Not sure if this is the perfect fix - only assume there is any height to this if + // any of the shadow heights were checked. Is small value for N==0 small enough? + F32 lowHt; + if (N) + { + lowHt = 10000; + while(N--) + lowHt = getMin(lowHt, minList[N]); + } + else + { + doBreak(); + lowHt = 2.0; + } + + return lowHt; +} + +//---------------------------------------------------------------------------- + +static void findWaterCallback(SceneObject *obj, S32 val) +{ + Vector *list = (Vector *)val; + list->push_back(obj); +} + +//---------------------------------------------------------------------------- + +bool GroundPlan::missionHasWater() +{ + Vector objects; + gServerContainer.findObjects(WaterObjectType, findWaterCallback, (S32)&objects); + return objects.size(); +} + +//---------------------------------------------------------------------------- + +void GroundPlan::getAllWaterBlocks(Vector &blocks) +{ + Vector objects; + gServerContainer.findObjects(WaterObjectType, findWaterCallback, (S32)&objects); + blocks.setSize(objects.size()); + + U32 i; + for(i = 0; i < objects.size(); i++) + blocks[i] = dynamic_cast(objects[i]); +} + +//---------------------------------------------------------------- + +bool GroundPlan::setTerrainGraphInfo(TerrainGraphInfo* info) +{ + Point2I dims(getGridDimWidth(), getGridDimHeight()); + + info->originGrid = getOrigin(); + info->gridTopRight = info->originGrid; + info->gridDimensions = dims; + info->gridTopRight += info->gridDimensions; + info->gridTopRight += TerrainGraphInfo::gridOffs[GridBottomLeft]; + + // Query the ground plan database for neighbors and such: + info->numShadows = getNavigable(info->navigableFlags, info->shadowHeights); + info->nodeCount = getNeighbors(info->neighborFlags).size(); + + AssertFatal(info->nodeCount == dims.x * dims.y, "Weird Ground Plan Information"); + + // This gets set up with a separate call (since it takes a while to compute). + setSizeAndClear(info->roamRadii, info->nodeCount); + + // Set up the world origin for translations. + TerrainBlock * block = getTerrainObj(); + AssertFatal(block, "NavGraph setGround() can't find a terrain block...."); + block->getTransform().getColumn(3, &info->originWorld); + AssertFatal(info->originWorld.z == 0.0f, "Graph expects terrain origin z == 0"); + + return true; +} + +//---------------------------------------------------------------------------- + +#define AlignmentMask 31 + +// Round out the area to power of two boundary (AlignmentMask + 1), taking +// into the account the terrain block origin. +GridArea GroundPlan::alignTheArea(const GridArea& areaIn) +{ + Point3F offsetF; + TerrainBlock * block = getTerrainObj(); + block->getTransform().getColumn(3, &offsetF); + Point2I offset(mFloor(offsetF.x), mFloor(offsetF.y)); + + // Figure how much needs to be added adjusted area to get it aligned nice, + // then we'll add that to the original one - i.e. unadjust. + Point2I adjPointBy(areaIn.point); + adjPointBy -= offset; + adjPointBy.x &= AlignmentMask; + adjPointBy.y &= AlignmentMask; + + // Add to extent to account for moving point back, then round up. + Point2I newExtent(areaIn.extent); + newExtent += adjPointBy; + newExtent.x += AlignmentMask; + newExtent.y += AlignmentMask; + newExtent.x &= (~AlignmentMask); + newExtent.y &= (~AlignmentMask); + + // Assemble the area we want- + GridArea newArea(areaIn); + newArea.point -= adjPointBy; + newArea.extent = newExtent; + + return newArea; +} + +//---------------------------------------------------------------------------- + +void GroundPlan::genExterior(Point2I &min, Point2I &max, NavigationGraph *nav) +{ + GridArea area; + + // Persist fields can specify custom area, else use passed in mission area. + if (!nav->customArea(area)) + { + area.point = min; + area.extent = max; + } + area = alignTheArea(area); + inspect(area); +} + +//---------------------------------------------------------------------------- + +// Inspect the terrain if it is present, return bool indicating if so. +static bool cInspect(SimObject *obj, S32, const char **argv) +{ + GroundPlan *plan = static_cast(obj); + NavigationGraph *nav = static_cast(Sim::findObject("NavGraph")); + if(plan && nav && nav->haveTerrain()) + { + Point2I point, extent; + dSscanf(argv[2], "%d %d", &point.x, &point.y); + dSscanf(argv[3], "%d %d", &extent.x, &extent.y); + plan->genExterior(point, extent, nav); + nav->setGround(plan); + return true; + } + return false; +} + +//---------------------------------------------------------------------------- + +void GroundPlan::consoleInit() +{ + Parent::consoleInit(); + Con::linkNamespaces("SimObject", "GroundPlan"); + Con::addCommand("GroundPlan", "inspect", cInspect, "gp.inspect(min, max);", 4, 4); +} + diff --git a/ai/graphGroundPlan.h b/ai/graphGroundPlan.h new file mode 100644 index 0000000..df4bdc5 --- /dev/null +++ b/ai/graphGroundPlan.h @@ -0,0 +1,185 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHGROUNDPLAN_H_ +#define _GRAPHGROUNDPLAN_H_ + +#ifndef _TERRDATA_H_ +#include "terrain/terrData.h" +#endif +#ifndef _CLIPPEDPOLYLIST_H_ +#include "Collision/clippedPolyList.h" +#endif +#ifndef _GRAPHGENUTILS_H_ +#include "ai/graphGenUtils.h" +#endif + +class WaterBlock; +class NavigationGraph; + +//--------------------------------------------------------------- + +class GroundPlan: public SimObject +{ + friend class InspectionVisitor; + + // members + typedef SimObject Parent; + TerrainBlock *mTerrainBlock; + Vector mGridDatabase; + Point2I mGridOrigin; + Point2I mGridDims; + S32 mTotalVisited; + + // static members + static Point2I gridOffset[8]; + static bool renderMe; + static bool drawclipped; + + // constructors/destructors + public: + GroundPlan(); + ~GroundPlan(); + DECLARE_CONOBJECT(GroundPlan); + static void consoleInit(); + static void initPersistFields(); + + // specific to SceneObject + protected: + bool onAdd(); + void onRemove(); + void drawObstruct(GridDetail &grid, S32 index); + void drawShadow(GridDetail &grid, S32 index); + void drawNeighbor(GridDetail &grid, S32 i); + void drawConnection(Point3F &st, Point3F &ed); + + // terrain block details + protected: + void gridToWorld(const Point2I &gPos, Point3F &wPos); + void worldToGrid(const Point3F &wPos, Point2I &gPos); + void gridToCenter(const Point2I &gPos, Point2I &cPos); + F32 getGridHeight(const Point2I &gPos); + S32 gridToIndex(const Point2I &gPos); + void getClippedRegion(S32 index, S32 &st, S32 &ed, S32 &xspan, S32 &yspan); + bool isSplit45(const Point2I &gPos); + bool isOnBoundary(S32 i); + bool missionHasWater(); + void getAllWaterBlocks(Vector &blocks); + + // inspection details + private: + void inspectSquare(const GridArea &gridArea, GridDetail &square); + S32 getNeighborDetails(S32 index, S32 key); + void packTriangleBits(const GridDetail &g, BitSet32 &bits, S32 bitType, S32 index, bool isLeftEdge, bool isBottomRow); + F32 lowestShadowHt(const GridDetail &g, BitSet32 &bits, S32 index); + GridArea alignTheArea(const GridArea& areaIn); + void findNeighbors(); + void computeNavFlags(); + bool setupPolyList(const Box3F& bBox, const Point3F& min, const Point3F& mid, + const Point3F& max, const VectorF& norm1, const VectorF& norm2, + const VectorF& norm3, F32* height); + GridDetail defaultDetail(); + + // interface + public: + Point2I getOrigin() const; + Point2I *getPosition(S32 index); + bool inspect(GridArea &area); + S32 getNavigable(Vector &navFlags, Vector &shadowHts); + Vector &getNeighbors(Vector &neighbors); + static TerrainBlock *getTerrainObj(); + S32 getGridDimWidth() const; + S32 getGridDimHeight() const; + void render(Point3F &camPos, bool drawClipped); + bool setTerrainGraphInfo(TerrainGraphInfo* info); + void genExterior(Point2I &min, Point2I &max, NavigationGraph *nav); +}; + +extern GroundPlan *gGroundPlanTest; + +//---------------------------------------------------------------------------- + +inline S32 GroundPlan::getGridDimWidth() const +{ + return mGridDims.x; +} + +//---------------------------------------------------------------------------- + +inline S32 GroundPlan::getGridDimHeight() const +{ + return mGridDims.y; +} + +//---------------------------------------------------------------------------- + +inline Point2I GroundPlan::getOrigin() const +{ + return mGridOrigin; +} + +//---------------------------------------------------------------------------- + +inline Point2I *GroundPlan::getPosition(S32 index) +{ + static Point2I sPoint; + sPoint.x = mGridOrigin.x + index % mGridDims.x; + sPoint.y = mGridOrigin.y + index / mGridDims.x; + return &sPoint; +} + +//---------------------------------------------------------------------------- + +inline bool GroundPlan::isSplit45(const Point2I &gPos) +{ + GridSquare *gs = mTerrainBlock->findSquare(0, gPos); + if(gs->flags & GridSquare::Split45) + return true; + else + return false; +} + +//---------------------------------------------------------------------------- + +inline S32 GroundPlan::getNeighborDetails(S32 index, S32 key) +{ + if(key < 0 || key > 7) + return index; + + switch(key) + { + case 0: + index = (index - mGridDims.x) - 1; + break; + case 1: + index -= mGridDims.x; + break; + case 2: + index--; + break; + case 3: + index = (index - mGridDims.x) + 1; + break; + case 4: + index = (index + mGridDims.x) - 1; + break; + case 5: + index++; + break; + case 6: + index = index + mGridDims.x; + break; + case 7: + index = (index + mGridDims.x) + 1; + break; + } + + AssertFatal((index <= mGridDatabase.size() - 1) || (index > 0), "GroundPlan::getNeighborDetails() - index out of bounds!"); + return index; +} + +#endif diff --git a/ai/graphGroundVisit.h b/ai/graphGroundVisit.h new file mode 100644 index 0000000..4280668 --- /dev/null +++ b/ai/graphGroundVisit.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHGROUNDVISIT_H_ +#define _GRAPHGROUNDVISIT_H_ + +#ifndef _GRAPHMATH_H_ +#include "ai/graphMath.h" +#endif + +class GroundPlan; +class GridDetail; + +class InspectionVisitor : public GridVisitor +{ + protected: + GroundPlan& mGPlan; + Vector mObjects; + const U32 mMask; + + public: + //==> Might want to pass in mask later- + InspectionVisitor(const GridArea &toVisit, GroundPlan &p) : + mMask(InteriorObjectType|GameBaseObjectType|TurretObjectType), + GridVisitor(toVisit), mGPlan(p) { } + + bool beforeDivide(const GridArea& R, S32 level); + bool atLevelZero(const GridArea& R); +}; + +//-------------------------------------------------------------------------- + +static void findObjectsCallback(SceneObject *obj, S32 val) +{ + Vector *list = (Vector *)val; + list->push_back(obj); +} + +//-------------------------------------------------------------------------- + +bool InspectionVisitor::beforeDivide(const GridArea& R, S32) +{ + Box3F wBox; + + // construct a world box based on these grid squares + mGPlan.gridToWorld(R.point, wBox.min); + mGPlan.gridToWorld((R.point+R.extent), wBox.max); + wBox.min.z = -(wBox.max.z = 5000); + + // Clear the list and fetch objects + mObjects.clear(); + gServerContainer.findObjects(-1, findObjectsCallback, S32(&mObjects)); + + // Scan list- + for (Vector::iterator i = mObjects.begin(); i != mObjects.end(); i++) + { + SceneObject * obj = (* i); + if (obj->getTypeMask() & mMask) + { + const Box3F & objBox = obj->getWorldBox(); + if (wBox.isOverlapped(objBox)) + { + if (obj->getName() && dStricmp("SmallRock", obj->getName()) == 0) + continue; + + return true; + } + } + } + + return false; +} + +//-------------------------------------------------------------------------- + +bool InspectionVisitor::atLevelZero(const GridArea& R) +{ + AssertFatal(R.extent.x == 1 && R.extent.y == 1, "inspection visitor error!"); + mGPlan.mTotalVisited++; + S32 index = mGPlan.gridToIndex(R.point); + GridDetail &detail = mGPlan.mGridDatabase[index]; + mGPlan.inspectSquare(R, detail); + return true; +} + +//-------------------------------------------------------------------------- + +#endif + diff --git a/ai/graphIndoors.cc b/ai/graphIndoors.cc new file mode 100644 index 0000000..bbaed27 --- /dev/null +++ b/ai/graphIndoors.cc @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- +// Interior Node Methods + + +NodeProximity InteriorNode::containment(const Point3F& loc) const +{ + if (gNavGraph->haveVolumes()) + return gNavGraph->getContainment(mIndex, loc); + else + return Parent::containment(loc); +} + +void InteriorNode::init(const IndoorNodeInfo & g, S32 index, const Point3F& normal) +{ + mIndex = index; + mLoc = g.pos; + mNormal = normal; + mFlags.set(Inventory, g.isInventory()); + mFlags.set(Algorithmic, g.isAlgorithmic()); + mFlags.set(BelowPortal, g.isBelowPortal()); + mFlags.set(PotentialSeed, g.isSeed()); +} + +GraphEdge& InteriorNode::pushOneWay(const GraphEdgeInfo::OneWay & edgeIn) +{ + GraphEdge edgeOn; + edgeOn.mDest = edgeIn.dest; + if(edgeIn.isJetting()) + edgeOn.setJetting(); + mEdges.push_back(edgeOn); + return mEdges.last(); +} + +InteriorNode::InteriorNode() +{ + mFlags.set(Indoor); + mMinDim = 0.5; + mArea = (mMinDim * mMinDim); +} + +//------------------------------------------------------------------------------------- + +// Must destruct the nodes. +void IndoorNodeList::clear() +{ + for (iterator it = begin(); it != end(); it++) + it->~InteriorNode(); + Parent::clear(); +} + +void IndoorNodeList::init(S32 size) +{ + clear(); + setSizeAndConstruct(*this, size); +} + +// Must destruct elements. +IndoorNodeList::~IndoorNodeList() +{ + clear(); +} + +//------------------------------------------------------------------------------------- + +GraphBoundary::GraphBoundary(const GraphEdgeInfo& edgeInfo) +{ + seg[0] = edgeInfo.segPoints[0]; + seg[1] = edgeInfo.segPoints[1]; + + normal.x = seg[0].y - seg[1].y; + normal.y = seg[1].x - seg[0].x; + normal.z = 0; + F32 len = normal.len(); + AssertFatal(len > 0.001, "GraphBoundry has small border"); + normal *= (1.0f / len); +} + +//==> Note - we may want to just have one boundary segment per edge - if so +// we can still compute both and then merge them. I think we may need both though +// since we will want to define a complete segment that they can cross over +// at some point. + +// Some computations needed to set it up to know how to navigate through there. +void GraphBoundary::setItUp(S32 nodeIndex, const NodeInfoList& nodes, + const GraphVolumeList& volumes) +{ + Point3F midpoint = scaleBetween(seg[0], seg[1], 0.5f); + + // Set normal to point in the direction (outbound) we need- + Point3F nodeCenter = nodes[nodeIndex].pos; + Point3F outbound = (midpoint - nodeCenter); + if (mDot(outbound, normal) < 0) + normal *= -1.0; + + // Find minimum of the midpoint from all planes, skipping current one. + //==> SKIP?? + //==> This all needs to be better smoothed. + F32 D, minDist = 1e9; + S32 numWalls = volumes.planeCount(nodeIndex) - 2; + const PlaneF* planes = volumes.planeArray(nodeIndex); + AssertFatal(numWalls > 2, "Graph node has no meaningful volume"); + while (numWalls--) + if ((D = -planes[numWalls].distToPlane(midpoint)) > 0.01) + if (D < minDist) + minDist = D; + + distIn = getMin(minDist, 1.2f); + + // Now set up the point we need to go through. + seekPt = midpoint - (distIn * normal); +} + +//------------------------------------------------------------------------------------- + +bool NavigationGraph::initInteriorNodes(const EdgeInfoList & edges, + const NodeInfoList & nodes, S32 startIndex) +{ + // This allocation must happen before (in caller) for edge pool purposes- + mBoundaries.clear(); + + mHaveVolumes = (mNodeVolumes.size() == nodes.size()); + + // Build node list + Vector utilityBuffer; + for (S32 i = 0; i < nodes.size(); i++) { + Point3F normal(0,0,1); + F32 minDim = 1.0; + F32 area = 1.0; + if (mHaveVolumes) { + Point3F floorVec = mNodeVolumes.floorPlane(i); + if (floorVec.z != 0) + normal = -floorVec; + else + warning("Graph needs regeneration (missing floor plane)"); + + minDim = mNodeVolumes.getMinExt(i, utilityBuffer); + } + mIndoorNodes[i].init(nodes[i], startIndex + i, normal); + mIndoorNodes[i].setDims(minDim, area); + } + + for (S32 j = 0; j < edges.size(); j++) + { + const GraphEdgeInfo& E = edges[j]; + + for (U32 k = 0; k < 2; k++) + { + GraphEdgeInfo::OneWay edgeOneWay = E.to[k^1]; + S32 dstIndex = edgeOneWay.dest; + S32 srcIndex = E.to[k].dest; + edgeOneWay.dest += startIndex; + GraphEdge& edge = mIndoorNodes[srcIndex].pushOneWay(edgeOneWay); + + // set up the boundary information + if (mHaveVolumes) + { + edge.mBorder = mBoundaries.size(); + GraphBoundary boundary(E); + boundary.setItUp(srcIndex, nodes, mNodeVolumes); + mBoundaries.push_back(boundary); + AssertFatal(mBoundaries.size() < (1 << 15), "Maximum Boundaries = 32K"); + } + } + } + + return nodes.size(); +} + +//------------------------------------------------------------------------------------- + +// Given a list of interior node indices to get rid of, compact those nodes (and any +// edges left hanging) out of the lists (mEdgeInfoList & mNodeInfoList). Also +// compacts out of the volume list if such is present. +S32 NavigationGraph::compactIndoorData(const Vector& cullList, S32 numOutdoor) +{ + // On balance, it's more convenient to map into the indoor node list 'space' here... + S32 i; + Vector axeList(cullList.size()); + for (i = 0; i < cullList.size(); i++) + axeList.push_back(cullList[i] - numOutdoor); + + // Minimize fragmentation by pre-reserving. + NodeInfoList culledNodes(mNodeInfoList.size()); + EdgeInfoList culledEdges(mEdgeInfoList.size()); + BridgeDataList culledBridges; + GraphVolumeList culledVolumes; + culledVolumes.reserve(mNodeVolumes.size()); + culledBridges.reserve(mBridgeList.size()); + + // ==> This looks wasteful, these need to be culled + if (mHaveVolumes) + culledVolumes.mPlanes = mNodeVolumes.mPlanes; + + // Mark nodes-to-axe with -1, the rest at zero. + Vector remap; + for (i = 0, setSizeAndClear(remap,mNodeInfoList.size()); i < axeList.size(); i++) + { + S32 removedNode = axeList[i]; + AssertFatal(removedNode >= 0, "NavigationGraph::compactIndoorData()"); + remap[removedNode] = -1; + } + + // Fetch nodes to keep into new list and build remap indices for remaining + for (i = 0; i < mNodeInfoList.size(); i++) if (remap[i] == 0) + { + remap[i] = culledNodes.size(); + culledNodes.push_back(mNodeInfoList[i]); + if (mHaveVolumes) + culledVolumes.push_back(mNodeVolumes[i]); + } + + // Copy down culled edge list and remap the "to" pointers. + for (i = 0; i < mEdgeInfoList.size(); i++) + { + GraphEdgeInfo edge = mEdgeInfoList[i]; + if ((edge.to[0].dest = remap[edge.to[0].dest]) >= 0) + { + edge.to[1].dest = remap[edge.to[1].dest]; + culledEdges.push_back(edge); + } + } + + // Remap bridge indices, and remove any invalid. Note that outdoor nodes don't get + // culled, even if they have a stranded pocket. + for (i = 0; i < mBridgeList.size(); i++) + { + GraphBridgeData bridge = mBridgeList[i]; + bool stillGood = true; + + for (S32 j = 0; stillGood && (j < 2); j++) + { + // Remap indoor nodes, or see if it's been culled- + S32 nodeInd = bridge.nodes[j]; + if (nodeInd >= numOutdoor) + { + S32 mappedNode = remap[nodeInd - numOutdoor]; + if (mappedNode >= 0) + bridge.nodes[j] = (mappedNode + numOutdoor); + else + stillGood = false; + } + } + + if (stillGood) + culledBridges.push_back(bridge); + } + + // install the new lists: + mEdgeInfoList = culledEdges; + mNodeInfoList = culledNodes; + mNodeVolumes = culledVolumes; + mNodeVolumes.cullUnusedPlanes(); + mBridgeList = culledBridges; + + // whatever- + return culledNodes.size(); +} + diff --git a/ai/graphIsland.cc b/ai/graphIsland.cc new file mode 100644 index 0000000..394283c --- /dev/null +++ b/ai/graphIsland.cc @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- +// Mark islands using the visitation callback of GraphSearch, set up mIslandPtrs +// list. Happens on a freshly made graph (island mark bit on nodes is clear). + +class IslandMarker : public GraphSearch +{ + public: + S32 mCurIsland; + void onQExtraction(); + F32 getEdgeTime(const GraphEdge*); +}; + +void IslandMarker::onQExtraction() +{ + extractedNode()->setIsland(mCurIsland); +} + +// We now have worlds where the island filler won't flood up, but will flood +// down, which results in an error. The default searcher stops when an +// "infinite" distance is encountered, so we need to override it here. +F32 IslandMarker::getEdgeTime(const GraphEdge*) +{ + return 1.0; +} + +S32 NavigationGraph::markIslands() +{ + mIslandPtrs.clear(); + mIslandSizes.clear(); + mNonTransient.clearFlags(GraphNode::GotIsland); + mLargestIsland = -1; + + S32 i, N, maxCount = -1; + + // Do island-mark expansions until we have no more unmarked nodes + IslandMarker islandMarker; + islandMarker.mCurIsland = 0; + for (i = 0; i < mNonTransient.size(); i++) + { + if (GraphNode * node = mNonTransient[i]) + { + if (!node->gotIsland()) + { + N = islandMarker.performSearch(node, NULL); + if (N > maxCount) + { + maxCount = N; + mLargestIsland = islandMarker.mCurIsland; + } + mIslandSizes.push_back(N); + mIslandPtrs.push_back(node); + islandMarker.mCurIsland++; + } + + if (RegularNode * regular = dynamic_cast(node)) + regular->transientReserve(); + } + } + + // return total island count + return mIslandPtrs.size(); +} + +//------------------------------------------------------------------------------------- + +// +// Culls all indoor nodes that are not part of the largest island. +// +S32 NavigationGraph::cullIslands() +{ + S32 originalCount = mNonTransient.size(); + Vector cullList(originalCount); + + for(S32 i = 0; i < mNonTransient.size(); i++) + if (mNonTransient[i]->indoor() && mNonTransient[i]->island() != mLargestIsland) + cullList.push_back(i); + + // remap indices and do whatever culling was found- + if (cullList.size()) + compactIndoorData(cullList, mNumOutdoor); + + Con::printf("Original count was %d nodes", originalCount); + Con::printf("%d nodes were culled", cullList.size()); + + return cullList.size(); +} diff --git a/ai/graphJetting.cc b/ai/graphJetting.cc new file mode 100644 index 0000000..7aae2ea --- /dev/null +++ b/ai/graphJetting.cc @@ -0,0 +1,485 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +#define RatingsCloseEnough 0.1 + +JetManager::JetManager() +{ + mFloodInds[0] = NULL; + mFloodInds[1] = NULL; + mArmorPart = NULL; +} + +JetManager::~JetManager() +{ + clear(); +} + +void JetManager::clear() +{ + for (S32 i = 0; i < AbsMaxBotCount; i++) + mPartitions[i].doConstruct(); + + delete mArmorPart; + delete [] mFloodInds[0]; + delete [] mFloodInds[1]; + mArmorPart = NULL; + mFloodInds[0] = NULL; + mFloodInds[1] = NULL; +} + +// Used for both passes of the armor partitioning. Basically do a flood fill expansion +// with the armor partition telling us what has been "extracted". We use two flipping +// lists for this, each containing the last round of extractions. This routine is then +// used twice - reused for finding downhill. +S32 JetManager::floodPartition(U32 seed, bool needBoth, F32 ratings[2]) +{ + // Set up the loop- + S32 numFound = 0; + S32 floodSizes[2] = {1, 0}; + S32 curExtract = 0, curDeposit; + mArmorPart->set(mFloodInds[0][0] = seed); + + // Keep extracting until nothing gets put into next time list + while (floodSizes[curExtract]) + { + U16 * extractFrom = mFloodInds[curExtract]; + U16 * depositInto = mFloodInds[curDeposit = (curExtract ^ 1)]; + + for (S32 i = floodSizes[curExtract] - 1; i >= 0; i--) + { + GraphNode * node = gNavGraph->lookupNode(extractFrom[i]); + GraphEdgeArray edges = node->getEdges(NULL); + + while (GraphEdge * edge = edges++) { + if (!mArmorPart->test(edge->mDest)) { + if (!edge->isJetting() || edge->canJet(ratings, needBoth)) { + mArmorPart->set(edge->mDest); + depositInto[floodSizes[curDeposit]++] = edge->mDest; + } + } + } + } + numFound += floodSizes[curExtract]; + floodSizes[curExtract] = 0; + // Here's the flip. Note the previous line clears deposit size for next loop. + curExtract = curDeposit; + } + + return numFound; +} + +// Code to find a partition for one armor / energy configuration. +S32 JetManager::partitionOneArmor(JetPartition& list) +{ + U32 memUsedBefore = Memory::getMemoryUsed(); + S32 nodeCount = gNavGraph->numNodes(); + + // First time allocations. Also, newIncarnation() will clear them. Partition size + // spends a couple bytes to include transients so search code doesn't have to check. + if (! mArmorPart) { + mArmorPart = new GraphPartition(); + mArmorPart->setSize(gNavGraph->numNodesAll()); + mFloodInds[0] = new U16 [nodeCount]; + mFloodInds[1] = new U16 [nodeCount]; + } + + S32 seedNode = 0; + S32 total = 0; + Vector saveSeedNodes; + + // PASS 1 + // + // First pass finds the 'stranded' component of each partition- this floods along + // only those edges that can be traversed BOTH WAYS. + // + list.clear(); + mArmorPart->clear(); + while (seedNode < nodeCount) + { + if (!mArmorPart->test(seedNode)) + { + total += floodPartition (seedNode, true, list.ratings); + + // Add the partition to the list, and remember the seed for next pass. + list.pushPartition(* mArmorPart); + saveSeedNodes.push_back(seedNode); + + if (total == nodeCount) + break; + } + seedNode++; + } + + // PASS 2 + // + // This pass finds the downhill component of each partition, if different. Note that + // setDownhill() only adds a separate partition if the downhill one is different. + // + for (S32 i = 0; i < list.size(); i++) + { + mArmorPart->clear(); + floodPartition (saveSeedNodes[i], false, list.ratings); + list[i].setDownhill(* mArmorPart); + } + + U32 memUsedAfter = Memory::getMemoryUsed(); + if (memUsedAfter > memUsedBefore) + NavigationGraph::sLoadMemUsed += (memUsedAfter - memUsedBefore); + + return list.size(); +} + +//------------------------------------------------------------------------------------- + +JetManager::Ability::Ability() +{ + acc = dur = v0 = -111; +} + +// Check for close enough abilities. So far the only thing I've seen change is the +// duration number. +bool JetManager::Ability::operator==(const Ability& ability) +{ + return( mFabs(acc - ability.acc) < 0.01 && + mFabs(dur - ability.dur) < 0.5 && + mFabs(v0 - ability.v0) < 0.1 + ); +} + +JetManager::ID::ID() +{ + id = -1; + incarnation = -1; +} + +void JetManager::ID::reset() +{ + if (NavigationGraph::gotOneWeCanUse()) + gNavGraph->jetManager().decRefCount(* this); + id = -1; +} + +JetManager::ID::~ID() +{ + // Could probably just use reset() here... + if (NavigationGraph::gotOneWeCanUse()) + gNavGraph->jetManager().decRefCount(* this); +} + +void JetManager::JetPartition::doConstruct() +{ + ratings[0] = 0.0; + ratings[1] = 0.0; + users = 0; + used = false; + time = 0; +} + +JetManager::JetPartition::JetPartition() +{ + doConstruct(); +} + +//------------------------------------------------------------------------------------- + +void JetManager::decRefCount(const ID& id) +{ + if (id.id >= 0) { + JetPartition & jetPartition = mPartitions[id.id]; + AssertFatal(jetPartition.users > 0, "JetManager ID management is not in sync"); + // This is when it starts to grow old- + if (--jetPartition.users == 0) + jetPartition.time = Sim::getCurrentTime(); + } +} + +// Users update this with an id and a rating. Return true if a new one is created, +// telling consumer (nav path) to try to put off slow operations a little bit. +bool JetManager::update(ID& id, const Ability& ability) +{ + // If missions cycles, or graph is rebuilt, id is out of date and should be reset- + if (id.incarnation != gNavGraph->incarnation()) + { + id.incarnation = gNavGraph->incarnation(); + id.id = -1; + } + + // If new entry or no longer valid, find match or create new. + if (id.id < 0 || !(mPartitions[id.id].ability == ability)) + { + decRefCount(id); + + // Look for existing partition that matches this ability. We keep unused ones- + // If all have had abilities set on them, then take oldest one. + U32 oldAge = U32(-1); + JetPartition * slot = NULL; + JetPartition * oldest = NULL; + JetPartition * j = mPartitions; + for (S32 i = 0; i < AbsMaxBotCount; i++, j++) + { + if (j->ability == ability) { + j->users++; + id.id = i; + return false; + } + else { + // Look for free entry in case it's needed. First choice are those not yet + // in use. Second choice is the oldest one (smallest creation timestamp). + if (!slot && !j->users) { + if (!j->used) + slot = j; + else if (j->time < oldAge) + oldAge = j->time, oldest = j; + } + } + } + + // No match found- create a new one with the slot found. + if (!slot) + slot = oldest; + AssertFatal(slot != NULL, "JetManager couldn't find empty slot"); + id.id = (slot - mPartitions); + slot->ability = ability; + slot->users = 1; + slot->used = true; + calcJetRatings(slot->ratings, ability); + slot->setType(GraphPartition::Armor); + + // Do the work- + partitionOneArmor(* slot); + + // Inform caller that we did some slow work- + return true; + } + return false; +} + +//------------------------------------------------------------------------------------- +// Here's the math. We've done the phsyics math accurately for the vertical distance, +// but we're just tweaking it for the lateral travel distance, which looks in practice +// to be about the same. That is, a bot can jet a flat XY distance that is close to +// how high straight up it can jet. But we scale down the lateral (increase what it +// _thinks_ it has to do) since this is just a heuristic. I'm not sure what the exact +// math is for estimating how far a bot can jet in any arc... + +#define LateralExtra 2.00f +#define LateralScale 1.28f + +F32 JetManager::modifiedLateral(F32 lateralDist) +{ + return (lateralDist * LateralScale + LateralExtra); +} + +F32 JetManager::modifiedDistance(F32 lateralDist, F32 verticalDist) +{ + return verticalDist + modifiedLateral(lateralDist); +} + +// In one case the public needs the actual XY from the lateral field, so this function +// is the inverse of modifiedLateral(). +F32 JetManager::invertLateralMod(F32 lateralField) +{ + F32 result = (lateralField - LateralExtra) * (1.0 / LateralScale); + return getMax(0.0f, result); +} + +//------------------------------------------------------------------------------------- +// Public function that the aiNavJetting module uses for knowing when it has enough +// energy to launch. That code must use the same adjusted distance that +// the jetting edges use. + +F32 JetManager::jetDistance(const Point3F& from, const Point3F& to) const +{ + F32 vertical = (to.z - from.z); + Point2F lateral(to.x - from.x, to.y - from.y); + + if (vertical > 0) + return modifiedDistance(lateral.len(), vertical); + else + return modifiedLateral(lateral.len()); +} + +//------------------------------------------------------------------------------------- + +// +// All edge initialization comes through here, especially required since the +// partitioning needs at least one invariants holding on the edges, namely that +// the mDist field of a pair of edges between two nodes must be exactly the same. +// +void JetManager::initEdge(GraphEdge& edge, const GraphNode* src, const GraphNode* dst) +{ + // The jet scale probably needs a little bit of work (and it seems like it really + // depends on the bot's energy levels. Any hop that uses less than half the + // bot's energy would probably be a good one to take, all else being equal. + if (!edge.isJetting()) + edge.setInverse(src->edgeScale(dst)); + else + edge.setInverse(calcJetScale(src, dst)); + + // Must assure that distances are the same both ways, so just to be sure we + // sort the locations. Partitioning flooding relies on this. + const Point3F& srcLoc = src->location(); + const Point3F& dstLoc = dst->location(); + Point3F high; + Point3F low; + if (srcLoc.z > dstLoc.z) + { + high = srcLoc; + low = dstLoc; + edge.setDown(edge.isJetting()); + } + else + { + high = dstLoc; + low = srcLoc; + } + + // Distance on edge is sum of horizontal + some extra + vertical. The extra + // is a buffer so they can get the lateral velicity going. + F32 absHeight = (high.z - low.z); + (high -= low).z = 0; + F32 lateral = high.len(); + // edge.mDist = (lateral + absHeight + 2.0); + edge.mDist = modifiedDistance(lateral, absHeight); + + // Get lateral distance for hops downward, little extra for buffer. + if (edge.isDown()) + edge.setLateral(U8(modifiedLateral(lateral))); + + // Set flatness. Must be conservative with it so that edges evaluate the + // same both ways for partioning purposes. + if (src->flat() && dst->flat()) + edge.setJump(true); +} + +// Ok - the indices are backwards from what's intuitive, but it's not a bug +// per se. We'll change to the intuitive if there's a convenient rebuild time... +// cf. The GraphBridgeData constructor. +void JetManager::replaceEdge(GraphBridgeData & B) +{ + GraphNode * src = gNavGraph->lookupNode(B.nodes[1]); + GraphNode * dst = gNavGraph->lookupNode(B.nodes[0]); + GraphEdge * edge = src->getEdgeTo(dst); + + AssertFatal(src && dst && edge, "JetManager::replaceEdge()"); + + if (B.isUnreachable()) + { + edge->setSteep(true); + edge->setJetting(); + edge->setImpossible(); + } + else + { + edge->setJetting(); + edge->setHop(B.jetClear); + initEdge(* edge, src, dst); + } +} + +// Calculates scale factor on jetting edges for deciding when to do them. +F32 JetManager::calcJetScale(const GraphNode* from, const GraphNode* to) const +{ + F32 scale; + + F32 zdiff = (to->location().z - from->location().z); + + if (zdiff > 0) { + // Jetting up adds to factor. Base amount is 3.0, then we + // also add 1 for every 10 meters going up beyond 20. + F32 per10 = mapValueLinear(zdiff, 20.0f, 120.0f, 0.0f, 10.0f); + scale = 3.0 + per10; + } + else { + // We'll make this number better once we have detection of + // which are walkable (walk-off-able) and which aren't. + scale = 1.4; + } + + return scale; +} + +#define GravityNum 0.625 + +// Given thrust and duration, see how far we can go. +F32 JetManager::calcJetRating(F32 thrust, F32 duration) +{ + F32 tSqrd = duration * duration; + thrust -= GravityNum; + + // How far we travel while applying the continuous force- + F32 pureJetDist = (0.5 * thrust * tSqrd * TickSec); + + // Once jet is gone- final velocity carries up. More jet remains, but that's our + // reserve so we can be sure to cover the distance. + F32 finalVel = (thrust * duration); + F32 driftUp = (0.5 * finalVel / GravityNum) * finalVel; + + return (pureJetDist + driftUp * TickSec); +} + + +// Calculate both ratings: +// 0 - without jumping +// 1 - can jump +// +// The ratings are a little bit conservative, but we need that buffer since the math +// isn't perfect for long hops. Mainly, we assume duration lasts as long as +// energy, whereas you actually get a little bit more from recharge- that's our buffer. +// +void JetManager::calcJetRatings(F32 * ratings, const Ability& ability) +{ + ratings[0] = calcJetRating(ability.acc, ability.dur); + ratings[1] = ratings[0] + (ability.v0 * ability.dur * TickSec); +} + +const F32 * JetManager::getRatings(const ID& jetCaps) +{ + JetPartition & J = mPartitions[jetCaps.id]; + AssertFatal(J.users > 0, "Bad call to JetManager::getRatings()"); + return J.ratings; +} + +GraphPartition::Answer JetManager::reachable(const ID& jetCaps, S32 from, S32 to) +{ + AssertFatal(jetCaps.id >= 0, "Uninitialized ID given to JetManager::reachable()"); + return mPartitions[jetCaps.id].reachable(from, to); +} + +//------------------------------------------------------------------------------------- +// Estimate what percentage of energy is needed to make the given hop. Actually going +// from distance/ability to energy required is not performed anywhere, and instead of +// trying to invert the math, we'll just do a quick binary search. This only happens +// once when an edge is encountered in a path. And we save square roots! Ok I'm lazy. +F32 JetManager::estimateEnergy(const ID& jetCaps, const GraphEdge * edge) +{ + JetPartition & J = mPartitions[jetCaps.id]; + AssertFatal(J.users > 0, "JetManager::estimateEnergy()"); + + Ability interpAbility = J.ability; + F32 lower = 0.0; + F32 upper = 1.0; + F32 ratings[2]; + // Eight iterations will get us within 1% + for (S32 i = 0; i < 8; i++) + { + F32 interpPercent = ((lower + upper) * 0.5); + interpAbility.dur = (interpPercent * J.ability.dur); + calcJetRatings(ratings, interpAbility); + if (edge->canJet(ratings)) + upper = interpPercent; + else + lower = interpPercent; + } + + // This is the lowest percentage we found which still works (or it's 1.0). + return upper; +} + diff --git a/ai/graphJetting.h b/ai/graphJetting.h new file mode 100644 index 0000000..28b9c3f --- /dev/null +++ b/ai/graphJetting.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHJETTING_H_ +#define _GRAPHJETTING_H_ + +class JetManager +{ + public: + class ID + { + friend class JetManager; + S32 id; + S32 incarnation; + public: + ID(); + ~ID(); + void reset(); + + }; + struct Ability{ F32 acc; F32 dur; F32 v0; Ability(); bool operator==(const Ability&); }; + friend class ID; + + static F32 modifiedLateral(F32 xyDist); + static F32 modifiedDistance(F32 xyDist, F32 zDist); + static F32 invertLateralMod(F32 lateralField); + + protected: + struct JetPartition : public PartitionList + { + F32 ratings[2]; + Ability ability; + S32 users; + bool used; + U32 time; + JetPartition(); + void doConstruct(); + }; + + U16 * mFloodInds[2]; + GraphPartition * mArmorPart; + JetPartition mPartitions[AbsMaxBotCount]; + + protected: + S32 floodPartition(U32 seed, bool needBoth, F32 ratings[2]); + void decRefCount(const ID& id); + + public: + JetManager(); + ~JetManager(); + S32 partitionOneArmor(JetPartition& list); + GraphPartition::Answer reachable(const ID& jetCaps, S32 from, S32 to); + const F32* getRatings(const ID& jetCaps); + F32 jetDistance(const Point3F& from, const Point3F& to) const; + F32 calcJetScale(const GraphNode* from, const GraphNode* to) const; + void initEdge(GraphEdge& edge, const GraphNode* src, const GraphNode* dst); + void replaceEdge(GraphBridgeData& replaceInfo); + F32 estimateEnergy(const ID& jetCaps, const GraphEdge * edge); + F32 calcJetRating(F32 thrust, F32 duration); + void calcJetRatings(F32 * ratings, const Ability& ability); + bool update(ID& id, const Ability& ability); + void clear(); +}; + +#endif diff --git a/ai/graphLOS.cc b/ai/graphLOS.cc new file mode 100644 index 0000000..6b4f64d --- /dev/null +++ b/ai/graphLOS.cc @@ -0,0 +1,225 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" +#include "ai/graphLOS.h" + +U32 Loser::mCasts = 0; // Just for informal profiling, counting casts. +static const F32 scGraphStepCheck = 0.75; + +Loser::Loser(U32 mask, bool checkFF) + : mCheckingFF(checkFF), + mMask(mask) +{ + mHitForceField = false; +} + +bool Loser::haveLOS(const Point3F& src, const Point3F& dst) +{ + if (mCheckingFF) + { + // Keep track if a force field is encountered. Walkable connections allow, + // jettable connections can't go through them. + U32 maskWithFF = (mMask | ForceFieldObjectType); + mCasts++; + mColl.object = NULL; // (Not sure if system clears) + if (gServerContainer.castRay(src, dst, maskWithFF, &mColl)) + { + if (mColl.object->getTypeMask() & ForceFieldObjectType) + mHitForceField = true; // and fall through do normal LOS. + else + return false; + } + } + mCasts++; + mColl.object = NULL; + return !gServerContainer.castRay(src, dst, mMask, &mColl); +} + +// Drop the point down to collision (if any) below. If no collision, we don't +// change the drop point, and return false. +bool Loser::hitBelow(Point3F& drop, F32 down) +{ + Point3F below(drop.x, drop.y, drop.z - down); + if (gServerContainer.castRay(drop, below, mMask, &mColl)) + { + drop = mColl.point; + return true; + } + return false; +} + +// Height above, capped at UpHeightMax. +F32 Loser::heightUp(const Point3F& from, F32 maxUp) +{ + Point3F up(from.x, from.y, from.z + maxUp); + if (haveLOS(from, up)) + return maxUp; + else + return (mColl.point.z - from.z); +} + +// Do LOS fanning one of the points. Just do a bunch of lines. We pre-increment +// and pre-decrement since the S-to-D los has already been checked. +bool Loser::fannedLOS1(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N) +{ + Point3F D0 = D, D1 = D; + while(N--) + if(!haveLOS(S, D0 += inc) || !haveLOS(S, D1 -= inc)) + return false; + return true; +} + +// Do LOS fanning both points. +bool Loser::fannedLOS2(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N) +{ + Point3F D0 = D, D1 = D, S0 = S, S1 = S; + while(N--) + if(!haveLOS(S0 += inc, D0 += inc) || !haveLOS(S1 -= inc, D1 -= inc)) + return false; + return true; +} + +// Do LOS checks downward to look for changes greater than step height. +bool Loser::walkOverBumps(Point3F S, Point3F D, F32 inc) +{ + VectorF step = (D - S); + S32 N = getMax(S32(step.len() / inc), 2); + + // Note D becomes our stepper directly under S. + F32 downDist = 140; + (D = S).z -= downDist; + + if (haveLOS(S, D)) + return false; + + // Set up initial Z down. + F32 zdown = mColl.point.z; + step *= (1.0f / F32(N)); + + while (N--) + { + if (haveLOS(S += step, D += step)) + return false; + + if (mColl.object->getTypeMask() & WaterObjectType) + return false; + + F32 getZ = mColl.point.z; + + if (mFabs(getZ - zdown) > scGraphStepCheck) + return false; + + // Save the Z, unless we're not walkable, in which case accumulate. + //==> Use proper slope values from PlayerData. + if (mColl.normal.z > gNavGlobs.mWalkableDot) + zdown = getZ; + } + + return true; +} + +// Fan the walk - this is SLOW - we'll use less resolution. Note we could try: +// ==> Walk from outside in (on all fanned checks), which will probably +// ==> result in earlier exits whenever false is the result. +// ==> Note we have to profile this whole process. +bool Loser::fannedBumpWalk(const Point3F& S, const Point3F& D, VectorF inc, S32 N) +{ + Point3F D0 = D, D1 = D, S0 = S, S1 = S; + inc *= 2.0f; + while ((N -= 2) >= 0) + if (!walkOverBumps(S0 += inc, D0 += inc) || !walkOverBumps(S1 -= inc, D1 -= inc)) + return false; + return true; +} + +// (If both are outdoor, we'll look for a larger hop amount). +static const F32 sHopLookInc = 0.2; + +// Move lines up, looking for a free line. We also want to assure that while there's +// not a free line, we at least have a minimum amount of breathing room - about 1.5m. +bool Loser::findHopLine(Point3F S, Point3F D, F32 maxUp, F32& freeHt) +{ + F32 tThresh = (1.5f / (S - D).len()); + + F32 total = 0; + while (total < maxUp) + { + S.z += sHopLookInc; + D.z += sHopLookInc; + total += sHopLookInc; + if (haveLOS(S, D)) + { + freeHt = total; + return true; + } + else + { + if (mColl.t < tThresh) // look for minimum amount in + break; + haveLOS(D, S); // ... from both directions + if (mColl.t < tThresh) + break; + } + } + return false; +} + +#define GapWalkInc 0.15f + +bool Loser::walkOverGaps(Point3F S, Point3F D, F32 allowedGap) +{ + VectorF incVec = (D - S); + VectorF step2D(incVec.x, incVec.y, 0); + F32 inc2D = step2D.len(); + S32 N = getMax(S32(inc2D / GapWalkInc) + 1, 2); + F32 bandWid = gNavGlobs.mStepHeight; + F32 saveL = 0, total2D = 0; + bool wasBelow = false; + + // D serves as our stepper to do LOS down to + (D = S).z -= 100; + incVec *= (1.0f / F32(N)); + inc2D *= (1.0f / F32(N)); + + while (N--) + { + bool los = !gServerContainer.castRay(S, D, mMask, &mColl); + + // Must handle water- + if (!los) + if (mColl.object->getTypeMask() & WaterObjectType) + return false; + + // check if we're below the allowed band- + if (los || mColl.point.z < (S.z - bandWid)) + { + if (wasBelow) + { + if (total2D - saveL > allowedGap) + return false; + } + else + { + wasBelow = true; + saveL = total2D; + } + } + else + { + wasBelow = false; + saveL = total2D; + } + + S += incVec; + D += incVec; + total2D += inc2D; + } + + return true; +} + diff --git a/ai/graphLOS.h b/ai/graphLOS.h new file mode 100644 index 0000000..efe6718 --- /dev/null +++ b/ai/graphLOS.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHLOS_H_ +#define _GRAPHLOS_H_ + +struct Loser +{ + static U32 mCasts; + const bool mCheckingFF; + const U32 mMask; + RayInfo mColl; + bool mHitForceField; + + Loser(U32 mask, bool checkFF = false); + + bool haveLOS(const Point3F& src, const Point3F& dst); + bool fannedLOS1(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N); + bool fannedLOS2(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N); + bool walkOverBumps(Point3F S, Point3F D, F32 inc = 0.2); + bool fannedBumpWalk(const Point3F& S, const Point3F& D, VectorF inc, S32 N); + bool findHopLine(Point3F S, Point3F D, F32 maxUp, F32& freeHt); + bool walkOverGaps(Point3F S, Point3F D, F32 allowedGap); + bool hitBelow(Point3F& dropPoint, F32 castDown); + F32 heightUp(const Point3F& from, F32 max); +}; + +#endif diff --git a/ai/graphLocate.cc b/ai/graphLocate.cc new file mode 100644 index 0000000..8cf9274 --- /dev/null +++ b/ai/graphLocate.cc @@ -0,0 +1,529 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +#define WaitAfterNearbyFound 11 +#define WaitAfterNothingFound 16 + +// Just want to construct a really good proximity for forcing sorting first below- +static struct BestProx: public NodeProximity{ BestProx(){makeGood();} } sBestProximity; + +//------------------------------------------------------------------------------------- + +bool NodeProximity::possible() const +{ + return (mLateral < 4.0); +} + +ProximateNode::ProximateNode(const NodeProximity& p, GraphNode* n) +{ + mProximity = p; + mNode = n; +} + +static S32 QSORT_CALLBACK varCompare(const void* a,const void* b) +{ + ProximateNode * proxA = (ProximateNode*)a; + ProximateNode * proxB = (ProximateNode*)b; + if (proxA->mProximity < proxB->mProximity) + return -1; + else if (proxA->mProximity > proxB->mProximity) + return 1; + else + return 0; +} + +void ProximateList::sort() +{ + dQsort((void *)address(), size(), sizeof(ProximateNode), varCompare); +} + +//------------------------------------------------------------------------------------- + +// Ok - "dist" searcher is now a misnomer. It's really a depth searcher, and it's +// only used for the locator. +GraphSearchDist::GraphSearchDist() +{ + mBestMatch = NULL; + mPoint.set(0,0,0); + mMaxSearchDist = 100; + setOnDepth(false); +} + +void GraphSearchDist::setDistCap(F32 d) +{ + mMaxSearchDist = d; +} + +// For the graph locator, we actually just want to search out to a certain DEPTH, +// rather than on distance (go a few connections removed). +F32 GraphSearchDist::getEdgeTime(const GraphEdge* edge) +{ + if (mSearchOnDepth) + return 1.001f; + else + return edge->mDist; +} + +// The tracker needs to know travel distance if the location has left it's last node. +void GraphSearchDist::onQExtraction() +{ + GraphNode * extractNode = extractedNode(); + if (extractNode->indoor()) + { + NodeProximity metric = extractNode->containment(mPoint); + + if (metric.inside()) + { + mBestMatch = extractNode; + mBestMetric = metric; + mProximate.clear(); + setEarlyOut(true); + } + else + { + // We need to track if we've had any really close matches, and then + // disregard the high ones. Note that the definition of "really close" + // must probably account for whether the destination has a bounding box + // or not. + if (metric.possible()) + if (metric < mCurThreshold) + { + // Add to the list of possibilities. + ProximateNode candidate(metric, extractNode); + mProximate.push_back(candidate); + + if (metric < mBestMetric) + { + mBestMetric = metric; + mCurThreshold = getMax(F32(metric), 1.0f); + } + } + } + F32 thusFar = (mSearchOnDepth ? timeSoFar() : distSoFar()); + if (thusFar > mMaxSearchDist) + setEarlyOut(true); + } +} + +// Find the best indoor node to connect to, return metric for how good. +// The lower the number the better (using distance from planes). +NodeProximity GraphSearchDist::findBestMatch(GraphNode * current, const Point3F& loc) +{ + // Set threshold based on how good the initial match is. + mBestMatch = NULL; + mBestMetric.makeBad(); + mProximate.clear(); + + if (!current) + current = gNavGraph->closestNode(loc); + + if (current) + { + setEarlyOut(false); + mPoint = loc; + mCurThreshold = 1e13; + if (current->indoor()) + setDistCap(3.3); + else + setDistCap(2.2); + setOnDepth(true); + performSearch(current); + setOnDepth(false); + } + + return mBestMetric; +} + +//------------------------------------------------------------------------------------- + +GraphLocate::GraphLocate() +{ + reset(); + mMounted = false; +} + +void GraphLocate::reset() +{ + mLocation.set(1e13,1e13,1e13); + mLoc2D = mLocation; + mTraveled = 1e13; + mClosest = NULL; + mCounter = 0; + mTerrain = false; + mUpInAir = false; +} + +void GraphLocate::forceCheck() +{ + mCounter = 0; + mTraveled = 1e13; +} + +//------------------------------------------------------------------------------------- + +static const U32 sMask = InteriorObjectType; + +static bool haveLOS(Point3F src, Point3F dst, bool indoor) +{ + static RayInfo coll; + src.z += 0.13; + dst.z += 0.13; + + // Probably don't usually need collide with terrain at all, but there are some cases + // on proximity check to indoor nodes. + U32 mask = indoor ? (sMask|TerrainObjectType) : sMask; + + if (!gServerContainer.castRay(src, dst, mask, &coll)) + return true; + else { + // check vehicle collision- ? + return false; + } +} + +// We allow a slight increase on the lower height, up to step height (see Generators +// down in Masada bunker - top of steps, fail LOS). +static bool checkLOS(const Point3F& src, const Point3F& dst, bool indoor) +{ + if (!haveLOS(src, dst, indoor)) + { + // Do more liberal check, raising up the lower point by up to 0.7 (~step height). + Point3F high, low; + if (src.z < dst.z) + low = src, high = dst; + else + low = dst, high = src; + low.z += getMin(0.7f, high.z - low.z); + return haveLOS(low, high, indoor); + } + return true; +} + +//------------------------------------------------------------------------------------- + + +bool GraphLocate::canHookTo(const GraphLocate& other, bool& mustJet) const +{ + bool canHook = false; + + if (mClosest && other.mClosest) + { + if (onTerrain() && other.onTerrain()) + { + canHook = (mClosest == other.mClosest || mClosest->neighbors(other.mClosest)); + } + else + { + // might want more checking here (which is why the clause is separate...) + canHook = (mClosest == other.mClosest); + } + } + + if (canHook) + mustJet = (mUpInAir || other.mUpInAir); + + return canHook; +} + +void GraphLocate::setMounted(bool b) +{ + if (mMounted != b) + { + mMounted = b; + mClosest = NULL; + forceCheck(); + } +} + +// I think we can get away with doing the 2D check here since it's established that +// we're Ok in 3D and we're not looking at jet connections. (The reason it's needed +// is because guys (especially heavies) are actually far off the edge in Z on slopes!). +bool GraphLocate::checkRegularEdge(Point3F fromLoc, GraphEdge* edge) +{ + GraphNode * destNode = gNavGraph->lookupNode(edge->mDest); + Point3F destLoc = destNode->location(); + destLoc.z = fromLoc.z = 0.0f; + LineSegment edgeSeg(fromLoc, destLoc); + + //==> If we can preprocess a walking width onto these edges, that could be good. + //==> For now the value is hard coded. + F32 dist = edgeSeg.distance(mLoc2D); + return (dist <= 0.8); +} + +ProximateNode * GraphLocate::examineCandidates(ProximateList& proximate) +{ + ProximateNode * bestIndoor = NULL; + ProximateNode * bestOutdoor = NULL; + + mConnections.clear(); + if (proximate.size()) + { + proximate.sort(); + for (ProximateList::iterator i = proximate.begin(); i != proximate.end(); i++) + { + bool isIndoor = i->mNode->indoor(); + if (checkLOS(i->mNode->location(), mLocation, isIndoor)) + { + pushEdge(i->mNode); + if (bestIndoor == NULL) // check for return value + { + if (isIndoor) + bestIndoor = i; + else + bestOutdoor = i; + } + } + if (mConnections.size() > 3) + break; + } + } + else + { + #if _GRAPH_WARNINGS_ + NavigationGraph::warning("No proximate nodes found in graph locator"); + #endif + } + + // See if we want final jetting connection, and set that only for the best match. + if (mMounted && mConnections.size()) + { + mConnections[0].mBorder = -1; + mConnections[0].setJetting(); + } + + if (bestIndoor) + return bestIndoor; + else + return bestOutdoor; +} + +// Fetch neighbors onto list. Be careful with all the different edge types (jet, +// border edges, walkable straight connections). Routine is only called when we're +// inside the node, so we can connect to all except certain straight line edges. +void GraphLocate::getNeighbors(GraphNode* ofNode, bool outdoor, bool makeJet) +{ + static GraphEdge edgeBuffer[MaxOnDemandEdges]; + GraphEdgeArray edgeList = ofNode->getEdges(edgeBuffer); + S32 outdoorNumber = gNavGraph->numOutdoor(); + Point3F fromLoc = ofNode->location(); + + if (outdoor) { + while(GraphEdge * edge = edgeList++) { + if(edge->mDest < outdoorNumber) { + GraphEdge connection = * edge; + if (!connection.isSteep()) + { + if (makeJet || connection.isJetting()) + connection.setJetting(); + mConnections.push_back(connection); + } + } + else { + // Check if we're near the edge. Note we know this isn't a 'border' edge + // because terrains nodes don't have border edges going out. + if (!edge->isJetting() && checkRegularEdge(fromLoc, edge)) + mConnections.push_back(* edge); + } + } + } + else { + // From indoor node we're inside. + while (GraphEdge * edge = edgeList++) { + if (edge->mDest >= outdoorNumber) { // (indoor dest) + if (makeJet) { + // If we're inside the neighbor's top and bottom planes, we can make + // a jet connection. + if (gNavGraph->verticallyInside(edge->mDest, mLocation)) { + GraphNode * destNode = gNavGraph->lookupNode(edge->mDest); + if (gNavGraph->possibleToJet(mLocation, destNode->location())) { + GraphEdge jetEdge; + jetEdge.mDest = edge->mDest; + jetEdge.setJetting(); + jetEdge.mDist = edge->mDist; + jetEdge.copyInverse(edge); + mConnections.push_back(jetEdge); + } + } + } + else { + if (!edge->isJetting()) + if (edge->isBorder() || checkRegularEdge(fromLoc, edge)) + mConnections.push_back(* edge); + } + } + else { // Neighbor is outdoor- + if (makeJet) { + // Since this indoor node has a terrain neighbor, we know the location + // is above the terain. However the loc could be under top of node + // in the case of some shaded nodes. + GraphNode * destNode = gNavGraph->lookupNode(edge->mDest); + Point3F destLoc = destNode->location(); + F32 topZ = (destLoc.z + destNode->terrHeight()); + if (mLocation.z < topZ) { + if (gNavGraph->possibleToJet(mLocation, destLoc)) { + mConnections.push_back(* edge); + mConnections.last().setJetting(); + } + } + } + else { + // Can check near edge here on other types of connections. + if (!edge->isJetting() && checkRegularEdge(fromLoc, edge)) + mConnections.push_back(* edge); + } + } + } + } +} + +void GraphLocate::pushEdge(GraphNode * to, F32 * getCosine) +{ + GraphEdge edge; + edge.mDest = to->getIndex(); + Point3F toLoc = to->location(); + edge.mDist = (toLoc -= mLocation).len(); + + // Added for steepness detection (LH 11/12/00). + if (getCosine) + { + if (edge.mDist < 0.01) + * getCosine = 1.0; + else + { + toLoc.z = 0; + * getCosine = (toLoc.len() / edge.mDist); + } + } + + mConnections.push_back(edge); +} + +// We're inside this one, so we can hook to it and all neighbors. +void GraphLocate::beLikeNode(GraphNode* ourNode) +{ + mConnections.clear(); + bool tryJet = false; + bool outdoor = !ourNode->indoor(); + if (mMounted) { + if (outdoor) { + F32 height; + if (gNavGraph->terrainHeight(mLocation, &height)) + tryJet = (mLocation.z - height) > 2.0; + } + else { + // Handle indoor with volume checks. Since we know we're inside, just + // check the height above the floor. + if (gNavGraph->heightAboveFloor(ourNode->getIndex(), mLocation) > 2.0) + tryJet = true; + } + } + + // Get all neighbors of the node we're inside of + getNeighbors(mClosest = ourNode, outdoor, tryJet); + + F32 cosAngle = 1.0; + pushEdge(mClosest, outdoor ? &cosAngle : NULL); + if (tryJet || cosAngle < gNavGlobs.mPrettySteepDot) + mConnections.last().setJetting(); + mTraveled = 0.0f; + mCounter = 0; + mUpInAir = tryJet; +} + +void GraphLocate::update(const Point3F& newLoc) +{ + bool searchIsReady = ((--mCounter < 0) && (mTraveled > 0)); + F32 moveDist = (mLocation - newLoc).len(); + + // Update the traveled total + mTraveled += moveDist; + + // Check for big jumps - redo search. + // if (moveDist > 30.0) + if (moveDist > 5.0) + mClosest = NULL; + + if ((moveDist > 0.1) || searchIsReady) + { + mLocation = newLoc; + mLoc2D.set(mLocation.x, mLocation.y, 0.0); + mUpInAir = false; + + if (GraphNode * onTerr = gNavGraph->findTerrainNode(mLocation)) + { + mTerrain = true; + mMetric.makeBad(); + beLikeNode(onTerr); + } + else + { + + mTerrain = false; + NodeProximity newMetric; + + // If the node match is good, it means we can afford to search as soon as + // the location leaves that node. Note we can move inside a node that + // we were just near to before, so the connections should be remade. + if (mClosest && (newMetric = mClosest->containment(mLocation)).inside()) + { + if (!mMetric.inside()) + beLikeNode(mClosest); + else if (mCounter < 0) + { + // Want to re-evaluate 'straight-line' edge connections every so often. + if (mCounter < -4) + beLikeNode(mClosest); + } + else + mCounter = 0; + + mMetric = newMetric; + } + else if (searchIsReady) + { + GraphSearchDist * searcher = gNavGraph->getDistSearcher(); + mMetric = searcher->findBestMatch(mClosest, mLocation); + if (searcher->bestMatch()) + { + beLikeNode(searcher->bestMatch()); + } + else + { + // Check proximate list. + ProximateList& nearby = searcher->getProximate(); + + if (GraphNode * terr = gNavGraph->nearbyTerrainNode(mLocation)) + { + ProximateNode willSortFirst(sBestProximity, terr); + nearby.push_back(willSortFirst); + } + + if (ProximateNode * bestBet = examineCandidates(nearby)) + { + mClosest = bestBet->mNode; + mCounter = WaitAfterNearbyFound; + mTraveled = getMax( static_cast( bestBet->mProximity ), 0.0f ); + } + else + { + // When nothing found, we don't reset the travel distance - this + // will increase the search distance gradually in the case where + // we somehow miss a match, get out of system. Uh, should work.. + mClosest = NULL; + mCounter = WaitAfterNothingFound; + } + + mCounter += (gRandGen.randI() & 3); // Little bit of stagger + } + } + } + } +} + diff --git a/ai/graphLocate.h b/ai/graphLocate.h new file mode 100644 index 0000000..d57e1a1 --- /dev/null +++ b/ai/graphLocate.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHLOCATE_H_ +#define _GRAPHLOCATE_H_ + +struct ProximateNode +{ + GraphNode * mNode; + NodeProximity mProximity; + ProximateNode(const NodeProximity& p, GraphNode* n); +}; + +class ProximateList : public Vector +{ + public: + void sort(); +}; + +class GraphLocate : public GraphNodeList +{ + protected: + Point3F mLocation; + Point3F mLoc2D; + F32 mTraveled; + S32 mCounter; + bool mUpInAir; + bool mMounted; + GraphNode * mClosest; + bool mTerrain; + NodeProximity mMetric; + GraphEdgeList mConnections; + + protected: + void getNeighbors(GraphNode* of, bool outdoor, bool makeJet=false); + bool checkRegularEdge(Point3F from, GraphEdge* to); + ProximateNode * examineCandidates(ProximateList& proximate); + void beLikeNode(GraphNode* ourNode); + void pushEdge(GraphNode* to, F32* getCos=NULL); + + public: + GraphLocate(); + void update(const Point3F& loc); + bool canHookTo(const GraphLocate&, bool& jet) const; + void reset(); + void setMounted(bool b); + void forceCheck(); + + const GraphEdgeList& getEdges() const {return mConnections;} + GraphNode * bestMatch() const {return mClosest;} + NodeProximity bestMetric() const {return mMetric;} + bool onTerrain() const {return mTerrain;} + bool isMounted() const {return mMounted;} + void cleanup() {reset(); mMounted=false;} +}; + +class FindGraphNode // graphFind.cc +{ + private: + GraphNode * mClosest; + Point3F mPoint; + U32 calcHash(const Point3F& point); + public: + enum {HashTableSize = 307}; + FindGraphNode(); + FindGraphNode(const Point3F& pt, GraphNode* hint = 0); + void setPoint(const Point3F& pt, GraphNode * hint = 0); + GraphNode * closest() const {return mClosest;} + void init(); +}; + +#endif diff --git a/ai/graphMake.cc b/ai/graphMake.cc new file mode 100644 index 0000000..c8db519 --- /dev/null +++ b/ai/graphMake.cc @@ -0,0 +1,363 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "terrain/terrRender.h" + +//------------------------------------------------------------------------------------- + +#ifdef DEBUG +#define DebugCheckHanging 1 +#else +#define DebugCheckHanging 0 +#endif + +//------------------------------------------------------------------------------------- + +static bool cullOneEdgeDup(GraphEdgeList& edges, BitVector& mark) +{ + GraphEdgeList::iterator it, cull = NULL; + + for (it = edges.begin(); it != edges.end(); it++) + if (mark.test(it->mDest)) { + cull = it; + break; + } + else + mark.set(it->mDest); + + for (it = edges.begin(); it != edges.end(); it++) + mark.clear(it->mDest); + + if (cull) + edges.erase_fast(cull); + + return (cull != NULL); +} + +//------------------------------------------------------------------------------------- +// This is where distances are calculated. Also used to verify that all edges have a +// counterpart coming back. Other final stuff has found its way here as well... +S32 NavigationGraph::doFinalFixups() +{ + S32 numberHanging = 0, i; + GraphEdge edgeBuffOuter[MaxOnDemandEdges]; + GraphEdgeArray edgeListOuter; + + sTotalEdgeCount = 0; + + for (i = 0; i < mNodeList.size(); i++) + { + if (GraphNode * node = mNodeList[i]) + { + edgeListOuter = node->getEdges(edgeBuffOuter); + sTotalEdgeCount += edgeListOuter.numEdges(); + while (GraphEdge * edgePtrOuter = edgeListOuter++) + { + GraphNode * neighbor = mNodeList[edgePtrOuter->mDest]; + AssertFatal(neighbor, "graph has bad neighbor indices"); + + // Main edge setup method- + mJetManager.initEdge(*edgePtrOuter, node, neighbor); + + #if DebugCheckHanging + + if (edgePtrOuter->mDest == i) + Con::errorf("Node (%d) has edge pointing to self!", i); + + // This debugging check only needed when we're modifying generation tools. + // Otherwise it's slow, and the generation is pretty solid anyway. + static GraphEdge sEdgeBuffInner[MaxOnDemandEdges]; + bool found = false; + GraphEdgeArray edgeListInner = neighbor->getEdges(sEdgeBuffInner); + while (GraphEdge * edgePtrInner = edgeListInner++) + if (edgePtrInner->mDest == i){ + found = true; + break; + } + if (!found) + { + numberHanging++; + Point3F P = node->location(); + Point3F N = neighbor->location(); + S32 D = edgePtrOuter->mDest; + Con::errorf("Hanging edge from %d (%f, %f, %f) to %d (%f, %f, %f)", + i, P.x, P.y, P.z, D, N.x, N.y, N.z); + } + #endif + } + } + } + + // Keep separate list of the non-transients. + mTransientStart = mNodeList.size(); + mNonTransient.reserve(mNodeList.size() + mMaxTransients); + mNonTransient = mNodeList; + for (i = 0; i < mMaxTransients; i++ ) + mNodeList.push_back(NULL); + + // Trim duplicate edges. This is rare (interior generator can get a strange + // boundary with parallel edges), and causes problems. Not the best fix for + // this problem (see graphTransient.cc attempt), but reasonable. + BitVector marker(mNodeList.size()); + S32 numDups = 0; + marker.clear(); + for (i = 0; i < mNodeList.size(); i++) + if (GraphNode * node = mNodeList[i]) + while (cullOneEdgeDup(node->mEdges, marker)) + numDups++; + if (numDups) + Con::printf("%d duplicate edges found on nodes.", numDups); + + // Good idea to mark islands AFTER checking for hanging. + markIslands(); + + // See navGraph.cc- + newIncarnation(); + + mValidLOSTable = (mLOSTable && mLOSTable->valid(numNodes())); + + mValidPathTable = false; // this has been dropped... + + return numberHanging; +} + +//------------------------------------------------------------------------------------- +// +// Assemble the graph from data that has been stored on it, computed, etc. +// + +bool NavigationGraph::makeGraph(bool needEdgePool) +{ + mPushedBridges = 0; + + makeRunTimeNodes(needEdgePool); // (graphOutdoors.cc) + Con::printf("MakeGraph: %d indoor, %d outdoor", mNumIndoor, mNumOutdoor); + + mIndoorPtrs.clear(); // convenience list + mIndoorTree.clear(); // BSP for it + + if (mNumIndoor) + { + mIndoorPtrs.reserve(mNumIndoor); + for (S32 i = 0; i < mNumIndoor; i++) + mIndoorPtrs.push_back(mNodeList[i + mNumOutdoor]); + + // Set up tree for quickly finding indoor nodes- + mIndoorTree.makeTree(mIndoorPtrs); + } + + return true; +} + +//------------------------------------------------------------------------------------- +// For now this just makes the roaming distance table. +// We compute these data separately since they take a while. + +S32 NavigationGraph::makeTables() +{ + // Get a table of ordered offsets. + mTerrainInfo.computeRoamRadii(); + return 0; +} + +//------------------------------------------------------------------------------------- +// Initialize the terrain info data from the ground plan. + +bool NavigationGraph::setGround(GroundPlan * gp) +{ + mTerrainInfo.haveGraph = true; + if (gp->setTerrainGraphInfo(&mTerrainInfo)) + { + mTerrainInfo.doFinalDataSetup(); + mTerrainInfo.consolidateData(mConjoin); + } + else + { + mTerrainInfo.haveGraph = false; + } + return mTerrainInfo.haveGraph; +} + +//------------------------------------------------------------------------------------- + +#define LOOK_AT_SPECIFIC_NODE 0 + +#if LOOK_AT_SPECIFIC_NODE +Point3F gSpecificNodeLoc(-149, -131.828, 163); +F32 gSpecificNodeRad = 9.0; +S32 findSpecificNode(const NodeInfoList& nodes) +{ + S32 found = -1; + for (S32 i = 0; i < nodes.size(); i++) + { + Point3F point = nodes[i].pos; + if ((point - gSpecificNodeLoc).len() < gSpecificNodeRad) + Con::printf("Node %d is where it's at", found = i); + } + return found; +} +#endif + +//------------------------------------------------------------------------------------- +// Get / Set interior node data. This doesn't affect the graph (building +// the graph from indoor and outdoor databases is a step that must be +// called explicitly). + +S32 NavigationGraph::getNodesAndEdges(EdgeInfoList& edges, NodeInfoList& nodes) +{ + edges = mEdgeInfoList; + nodes = mNodeInfoList; + return nodes.size(); +} + +// Set the system up with the given list of edges and nodes. Resets / hooks up +// nodes indoors. +S32 NavigationGraph::setNodesAndEdges(const EdgeInfoList& edges, const NodeInfoList& nodes) +{ + mEdgeInfoList = edges; + mNodeInfoList = nodes; + return nodes.size(); +} + +//------------------------------------------------------------------------------------- + +S32 NavigationGraph::setEdgesAndNodes(const EdgeInfoList& edges, const NodeInfoList& nodes) +{ + return setAlgorithmic(edges, nodes); +} + +//------------------------------------------------------------------------------------- + +// Interior node generator calls this to add data to graph, plus these lists +// then become hand-editable. We always clean out existing algorithmic first. +S32 NavigationGraph::setAlgorithmic(const EdgeInfoList& edges, const NodeInfoList& nodes) +{ + #if LOOK_AT_SPECIFIC_NODE + findSpecificNode(nodes); + #endif + clearAlgorithmic(); + // Add the edges with remapped dest index, and marked as algorithmic. + S32 indexAdd = mNodeInfoList.size(); + for (EdgeInfoList::const_iterator e = edges.begin(); e != edges.end(); e++) { + GraphEdgeInfo edge = * e; + edge.setAlgorithmic(); + edge.to[0].dest += indexAdd; + edge.to[1].dest += indexAdd; + mEdgeInfoList.push_back(edge); + } + for (NodeInfoList::const_iterator n = nodes.begin(); n != nodes.end(); n++) { + IndoorNodeInfo node = * n; + node.setAlgorithmic(); + mNodeInfoList.push_back(node); + } + return 0; +} + +// Clear algorithmic nodes out of main list, plus edges that connect to any. +S32 NavigationGraph::clearAlgorithmic() +{ + Vector mapIndices; + NodeInfoList keepNodes; + EdgeInfoList keepEdges; + + setSizeAndClear(mapIndices, mNodeInfoList.size(), 0xff); + + // get which nodes we're keeping and set up remap + for (S32 i = 0; i < mNodeInfoList.size(); i++) + if (mNodeInfoList[i].isAlgorithmic() == false) { + mapIndices[i] = keepNodes.size(); + keepNodes.push_back(mNodeInfoList[i]); + } + + // get the edges we're keeping and remap their destination indices + EdgeInfoList::iterator edge; + for (edge = mEdgeInfoList.begin(); edge != mEdgeInfoList.end(); edge++) + if (edge->isAlgorithmic() == false) + if (mNodeInfoList[edge->to[0].dest].isAlgorithmic() == false) + if (mNodeInfoList[edge->to[1].dest].isAlgorithmic() == false) { + for (S32 j = 0; j < 2; j++) + edge->to[j].dest = mapIndices[edge->to[j].dest]; + keepEdges.push_back(*edge); + } + + // copy back the culled lists + mNodeInfoList = keepNodes; + mEdgeInfoList = keepEdges; + + return mNodeInfoList.size(); +} + +//------------------------------------------------------------------------------------- + +S32 NavigationGraph::setNodeVolumes(const GraphVolumeList& volumes) +{ + mNodeVolumes = volumes; + mNodeVolumes.nudgeVolumesOut(); + return mNodeVolumes.size(); +} + +//------------------------------------------------------------------------------------- + +S32 NavigationGraph::setChuteHints(const Vector& hints) +{ + S32 numHints = hints.size(); + mChutes.init(hints); + return numHints; +} + +//------------------------------------------------------------------------------------- + +static const U32 sMask = InteriorObjectType|StaticShapeObjectType|TerrainObjectType; + +// The path logic as it pertains to jetting connections (often) needs to know for sure +// that the player can jump (makes a big difference in achievable height). This method +// must figure this out. It's Ok if we miss a few where it's possible to jump, but we +// don't want to err the other way (SOMETIMES can't jump, but think we can). +S32 NavigationGraph::findJumpableNodes() +{ + S32 total = 0; + + if (!mIsSpawnGraph) + { + F32 check45 = mSqrt(0.5); + RayInfo coll; + S32 i, j; + Point3F down(0, 0, -6); + + // ==> As an optimization, we should only do this with nodes that you jet out of. + + // Um, because of where this is called, we can't use numNodes()- + S32 numNodes = (mNumIndoor + mNumOutdoor); + + for (i = 0; i < numNodes; i++) { + if (GraphNode * node = lookupNode(i)) { + if (node->getNormal().z > check45) { + // Cast several rays down and check their normals. + Point3F loc = node->location(); + loc += Point3F(-0.8, -0.9, 2.0); + for (j = 0; j < 4; j++) { + Point3F corner((j & 1 != 0) * 1.6, (j & 2 != 0) * 1.7, 0.0); + corner += loc; + if (gServerContainer.castRay(corner, corner + down, sMask, &coll)) + if (coll.normal.z < check45) + break; + } + if (j == 4) { + node->set(GraphNode::Flat); + total++; + } + } + } + } + } + + return total; +} + diff --git a/ai/graphMath.cc b/ai/graphMath.cc new file mode 100644 index 0000000..fa2ad47 --- /dev/null +++ b/ai/graphMath.cc @@ -0,0 +1,341 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graphMath.h" +#include "Core/realComp.h" +#include "ai/tBinHeap.h" + +//------------------------------------------------------------------------------------- + +F32 solveForZ(const PlaneF& plane, const Point3F& point) +{ + AssertFatal(mFabs(plane.z) > 0.0001, "solveForZ() expects non-vertical plane"); + return (-plane.d - plane.x * point.x - plane.y * point.y) / plane.z; +} + +//------------------------------------------------------------------------------------- + +// Simple "iterator", used as in- +// for (mArea.start(step); mArea.pointInRect(step); mArea.step(step)) +// ... +bool GridArea::start(Point2I& steppingPoint) const +{ + if (isValidRect()) + { + steppingPoint = point; + return true; + } + return false; +} +bool GridArea::step(Point2I& steppingPoint) const +{ + if (++steppingPoint.x >= (point.x + extent.x)) + { + steppingPoint.x = point.x; + if (++steppingPoint.y >= (point.y + extent.y)) + return false; + } + return true; +} + +//------------------------------------------------------------------------------------- + +GridVisitor::GridVisitor(const GridArea & area) + : mArea(area.point, area.extent) +{ + mPostCheck = mPreCheck = true; +} + +// Default versions of virtuals. The Before and After callbacks just remove themselves: +bool GridVisitor::beforeDivide(const GridArea&,S32) {return !(mPreCheck = false);} +bool GridVisitor::atLevelZero(const GridArea&) {return true; } +bool GridVisitor::afterDivide(const GridArea&,S32,bool) {return !(mPostCheck= false);} + +// Recursive region traversal. +// R is a box of width 2^L, and aligned on like boundary (L low bits all zero) +bool GridVisitor::recurse(GridArea R, S32 L) +{ + bool success = true; + + if (! R.overlaps(mArea)) { + success = false; + } + else if (L == 0) { + success = atLevelZero(R); + } + else if (mPreCheck && mArea.contains(R) && !beforeDivide(R, L)) { // early out? + success = false; + } + else { + // Made it to sub-division! + S32 half = 1 << (L - 1); + for (S32 y = half; y >= 0; y -= half) + for (S32 x = half; x >= 0; x -= half) + if (!recurse(GridArea(R.point.x + x, R.point.y + y, half, half), L - 1)) + success = false; + + // Post order stuff- + if (mPostCheck && mArea.contains(R) && !afterDivide(R, L, success)) + success = false; + } + return success; +} + +bool GridVisitor::traverse() +{ + S32 level = 1; + GridArea powerTwoRect (-1, -1, 2, 2); + + // Find the power-of-two-sized rect that encloses user-supplied grid. + while (!powerTwoRect.contains(mArea)) + if (++level < 31) { + powerTwoRect.point *= 2; + powerTwoRect.extent *= 2; + } + else + return false; + + // We have our rect and starting level, perform the recursive traversal: + return recurse(powerTwoRect, level); +} + + +//------------------------------------------------------------------------------------- +// Find distance of point from the segment. If the point's projection onto +// line isn't within the segment, then take distance from the endpoints. +// +// This also leaves the closest point set in soln. +// +F32 LineSegment::distance(const Point3F & p) +{ + VectorF vec1 = b - a, vec2 = p - a; + F32 len = vec1.len(), dist = vec2.len(); + + soln = a; // good starting default. + + // First check for case where A and B are basically same (avoid overflow) - + // can return distance from either point. + if( len > 0.001 ) + { + // normalize vector from A to B and get projection of AP along it. + F32 dot = mDot( vec1 *= (1/len), vec2 ); + if(dot >= 0) + { + if(dot <= len) + { + F32 sideSquared = (dist * dist) - (dot * dot); + if (sideSquared <= 0) // slight imprecision led to NaN + dist = sideSquared = 0; + else + dist = mSqrt(sideSquared); + soln += (vec1 * dot); + } + else + { + soln = b; + dist = (b - p).len(); + } + } + } + + return dist; +} + +// Special check which does different math in 3D and 2D. If the test is passed +// in 3d, then do a 2d check. +bool LineSegment::botDistCheck(const Point3F& p, F32 dist3, F32 dist2) +{ + F32 d3 = distance(p); + if (d3 < dist3) + { + Point2F proj2d(soln.x - p.x, soln.y - p.y); + F32 d2 = proj2d.lenSquared(); + dist2 *= dist2; + return (d2 < dist2); + } + return false; +} + + +//------------------------------------------------------------------------------------- +// Get a list of grid offsets that "spirals" out in order of closeness. +// This is used by the navigation (preprocess) code to find nearest +// obstructed grid location, and it uses this for its search order. + +S32 getGridSpiral(Vector & spiral, Vector & dists, S32 radius) +{ + S32 x, y, i; + Vector pool; + BinHeap heap; + + // Build all the points that we'll want to consider. We're going to + // leave off those points that are outside of circle. + for( y = -radius; y <= radius; y++ ) + for( x = -radius; x <= radius; x++ ) + { + F32 dist = Point2F( F32(x), F32(y) ).len(); + if( dist <= F32(radius) + 0.00001 ) + { + pool.push_back( Point2I(x,y) ); + heap.insert( -dist ); // negate for sort order + } + } + + // Get the elements out in order. + heap.buildHeap(); + while( (i = heap.headIndex()) >= 0 ) + { + spiral.push_back( pool[i] ); + dists.push_back( -heap[i] ); // get the (sign-restored) distance + heap.removeHead(); + } + + return spiral.size(); +} + + +//----------------------------------------------------------------- +// Line Stepper class. + +void LineStepper::init(const Point3F & a, const Point3F & b) +{ + solution = A = a, B = b; + total = (dir1 = B - A).len(); + soFar = advance = 0.0f; + + if( total > 0.00001 ) + { + dir1 *= (1 / total); + } + else + { + total = 0.0f; + dir1.set(0,0,0); + } +} + +// +// Finds the intersection of the line with the sphere on the way out. If there +// are two intersections, and we're starting outside, this should find the +// second. +// +F32 LineStepper::getOutboundIntersection(const SphereF & S) +{ + // get projection of sphere center along our line + F32 project = mDot( S.center - A, dir1 ); + + // find point nearest to center of sphere + Point3F nearPoint = A + (dir1 * project); + + // if this isn't in the sphere, then there's no intersection. negative return flags this. + if( ! S.isContained(nearPoint) ) + return -1.0f; + + // there is an intersection, figure how far from nearest point it is + F32 length = mSqrt( S.radius*S.radius - (nearPoint-S.center).lenSquared() ); + + // find the solution point and advance amount (which is return value) + solution = nearPoint + dir1 * length; + return( advance = length + project ); +} + +const Point3F& LineStepper::advanceToSolution() +{ + if( advance != 0.0f ) + { + soFar += advance; + A = solution; + advance = 0.0f; + } + return solution; +} + +//--------------------------------------------------------------- +// Finds the center of Mass of a CONVEX polygon. +// Verts should contain an array of Point2F's, in order, +// and of size n, that will represent the polygon. +//--------------------------------------------------------------- +bool polygonCM(S32 n, Point2F *verts, Point2F *CM) +{ + if(!verts || n < 3) + return false; + + F32 area, area2 = 0.0; + Point2F cent; + CM->x = 0; + CM->y = 0; + + U32 i; + for(i = 1; i < (n-1); i++) + { + triCenter(verts[0], verts[i], verts[i+1], cent); + area = triArea(verts[0], verts[i], verts[i+1]); + CM->x += area * cent.x; + CM->y += area * cent.y; + area2 += area; + } + + CM->x /= 3 * area2; + CM->y /= 3 * area2; + return true; +} + +//------------------------------------------------------------------------------------- + +// Lifted from tools/morian/CSGBrush.cc. This converts to doubles for insurance, which +// shouldn't pose a speed problem since this is mainly used in graph preprocess. +bool intersectPlanes(const PlaneF& p, const PlaneF& q, const PlaneF& r, Point3F* pOut) +{ + Point3D p1(p.x, p.y, p.z); + Point3D p2(q.x, q.y, q.z); + Point3D p3(r.x, r.y, r.z); + F64 d1 = p.d, d2 = q.d, d3 = r.d; + + F64 bc = (p2.y * p3.z) - (p3.y * p2.z); + F64 ac = (p2.x * p3.z) - (p3.x * p2.z); + F64 ab = (p2.x * p3.y) - (p3.x * p2.y); + F64 det = (p1.x * bc) - (p1.y * ac) + (p1.z * ab); + + // Parallel planes + if (mFabsD(det) < 1e-5) + return false; + + F64 dc = (d2 * p3.z) - (d3 * p2.z); + F64 db = (d2 * p3.y) - (d3 * p2.y); + F64 ad = (d3 * p2.x) - (d2 * p3.x); + F64 detInv = 1.0 / det; + + pOut->x = ((p1.y * dc) - (d1 * bc) - (p1.z * db)) * detInv; + pOut->y = ((d1 * ac) - (p1.x * dc) - (p1.z * ad)) * detInv; + pOut->z = ((p1.y * ad) + (p1.x * db) - (d1 * ab)) * detInv; + + return true; +} + +bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result) +{ + if (v1.isZero() || v2.isZero() || (result == NULL)) + return false; + + Point3F v1Norm = v1, v2Norm = v2; + v1Norm.normalize(); + v2Norm.normalize(); + + if ((! isZero(1.0f - v1Norm.len())) || (! isZero(1.0f - v2Norm.len()))) + return false; + + //make sure the vectors are non-colinear + F32 dot = mDot(v1Norm, v2Norm); + if (dot > 0.999f || dot < -0.999f) + return false; + + //find the cross product, and normalize the result + mCross(v1Norm, v2Norm, result); + result->normalize(); + + return true; +} diff --git a/ai/graphMath.h b/ai/graphMath.h new file mode 100644 index 0000000..171c854 --- /dev/null +++ b/ai/graphMath.h @@ -0,0 +1,349 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHMATH_H_ +#define _GRAPHMATH_H_ + +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _MSPHERE_H_ +#include "Math/mSphere.h" +#endif +#ifndef _MATHIO_H_ +#include "Math/mathIO.h" +#endif + +//------------------------------------------------------------------------------------- +// One-line convenience functions: + +inline bool validArrayIndex(S32 index, S32 arrayLength) +{ + return U32(index) < U32(arrayLength); // (Unsigned compare handles < 0 case too) +} + +inline F32 triArea(const Point2F& a, const Point2F& b, const Point2F& c) +{ + return mFabs((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y)); +} + +inline void triCenter(const Point2F& p1, const Point2F& p2, const Point2F& p3, Point2F &c) +{ + c.x = p1.x + p2.x + p3.x; + c.y = p1.y + p2.y + p3.y; +} + +// prototype for finding the CM(center of mass) of a convex poly +bool polygonCM(S32 n, Point2F *verts, Point2F *CM); + +// Find point at intersection of three planes if such exists (returns true if so). +bool intersectPlanes(const PlaneF& p, const PlaneF& q, const PlaneF& r, Point3F* pOut); + +// Lookup into NxN table in which one entry is stored for each pair of unique indices. +inline S32 triangularTableIndex(S32 i1, S32 i2) { + if (i1 > i2) + return (((i1 - 1) * i1) >> 1) + i2; + else + return (((i2 - 1) * i2) >> 1) + i1; +} + +//------------------------------------------------------------------------------------- + +// Where vertical line through point hits plane (whose normal.z != 0): +F32 solveForZ(const PlaneF& plane, const Point3F& point); + +// Cross product, but with a check for bad input (parallel vectors): +bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result); + +//------------------------------------------------------------------------------------- +// Encapsulate some common operations on the grid rectangles +class GridArea : public RectI +{ + public: + GridArea() { } + GridArea(const Point2I& pt,const Point2I& ext) : RectI(pt,ext) {} + GridArea(S32 L, S32 T, S32 W, S32 H) : RectI(L,T,W,H) {} + GridArea(const GridArea & g) : RectI(g.point,g.extent) {} + + // grid to index, and vice versa: + S32 getIndex(Point2I pos) const; + Point2I getPos(S32 index) const; + + // step through grid in index order (Y outer loop): + bool start(Point2I &p) const; + bool step(Point2I &p) const; +}; + +inline S32 GridArea::getIndex(Point2I p) const +{ + p -= point; + if( validArrayIndex(p.x, extent.x) ) + if( validArrayIndex(p.y, extent.y) ) + return p.y * extent.x + p.x; + return -1; +} + +inline Point2I GridArea::getPos(S32 index) const +{ + Point2I P; + P.x = point.x + index % extent.x; + P.y = point.y + index / extent.x; + return P; +} + +//------------------------------------------------------------------------------------- +// Linear range mapping. + +// Get value that is pct way between first and last. Note this is probably a better +// way of getting midpoints than (A+B)*.5 due to rounding. +template T scaleBetween(T first, T last, F32 pct) +{ + return first + (pct * (last - first)); +} + +// Get percent of way of value between min and max, clamping return to range [0,1] +template F32 getPercentBetween(T value, T min, T max) +{ + if(mFabs((max - min)) < 0.0001) + return 0.0f; + else if(min < max){ + if (value <= min) return 0.0f; + else if (value >= max) return 1.0f; + } + else if (min > max){ + if (value >= min) return 0.0f; + else if (value <= max) return 1.0f; + } + return F32((value - min) / (max - min)); +} + +// Map value from domain into the given range, capping on ends- +template T mapValueLinear(T value, T dmin, T dmax, T rmin, T rmax) +{ + return scaleBetween(rmin, rmax, getPercentBetween(value, dmin, dmax)); +} + +template T mapValueQuadratic(T value, T dmin, T dmax, T rmin, T rmax) +{ + F32 pct = getPercentBetween(value, dmin, dmax); + return scaleBetween(rmin, rmax, pct * pct); +} + +template T mapValueSqrt(T value, T dmin, T dmax, T rmin, T rmax) +{ + F32 pct = getPercentBetween(value, dmin, dmax); + return scaleBetween(rmin, rmax, mSqrt(pct)); +} + +//------------------------------------------------------------------------------------- +// An object to do a quad-tree traversal of a grid region. + +class GridVisitor +{ + protected: + bool mPreCheck; + bool mPostCheck; + + bool recurse(GridArea rect, S32 level); + + public: + const GridArea mArea; + GridVisitor(const GridArea & area); + + // virtuals - how you get visited: + virtual bool beforeDivide(const GridArea& R, S32 level); + virtual bool atLevelZero(const GridArea& R); + virtual bool afterDivide(const GridArea& R, S32 level, bool success); + + // call the traversal: + bool traverse(); +}; + +//------------------------------------------------------------------------------------- +// Return if the two points are within the given threshold distance. + +template +bool within(PointType p1, const PointType & p2, DistType thresh) +{ + return (p1 -= p2).lenSquared() <= (thresh * thresh); +} + +inline bool within_2D(const Point3F & A, const Point3F & B, F32 D) +{ + Point2F A2d( A.x, A.y ); + Point2F B2d( B.x, B.y ); + return within( A2d, B2d, D ); +} + +//------------------------------------------------------------------------------------- +// Get a list of grid offsets that "spirals" out in order of closeness. + +S32 getGridSpiral(Vector & spiral, Vector & dists, S32 radius); + +//------------------------------------------------------------------------------------- +// Given a point, used to find closest distance/point on segment. + +class LineSegment +{ + Point3F a, b; + Point3F soln; + public: + LineSegment(const Point3F& _a, const Point3F& _b) {soln=a =_a;b =_b;} + LineSegment() {soln=a=b=Point3F(0,0,0);} + public: + void set(const Point3F& _a, const Point3F& _b) {soln=a =_a;b =_b;} + F32 distance(const Point3F& p); + bool botDistCheck(const Point3F& p, F32 d3, F32 d2); + Point3F solution() {return soln;} + Point3F getEnd(bool which) {return which ? b : a;} +}; + +//------------------------------------------------------------------------------------- + +class LineStepper +{ + Point3F A, B; + Point3F dir1; // unit direction vector from A to B. + F32 total; // total dist from A to B. + F32 soFar; // how far we've come. + + F32 advance; // solutions to queries are kept until the user + Point3F solution; // tells us to "officially" advance. + + public: + LineStepper(const Point3F & a, const Point3F & b) { init(a,b); } + + const Point3F & getSolution() { return solution; } + F32 distSoFar() { return soFar; } + F32 totalDist() { return total; } + F32 remainingDist() { return total-soFar; } + + // Methods that actually do stuff: + void init(const Point3F & a, const Point3F & b); + F32 getOutboundIntersection(const SphereF & s); + const Point3F & advanceToSolution(); +}; + +//------------------------------------------------------------------------------------- +// reverse elements in place + +template Vector & reverseVec( Vector & vector ) +{ + for(S32 halfWay = (vector.size() >> 1); halfWay; /* dec'd in loop */ ) + { + T & farOne = * (vector.end() - halfWay--); + T & nearOne = * (vector.begin() + halfWay); + T tmp=farOne; farOne=nearOne; nearOne=tmp; // swap + } + return vector; +} + +//-------------------------------------------------------------------------- +// Read/Write a vector of items, using T.read(stream), T.write(stream). +// Skip function reads same amount as readVector1(), bypassing data. + +template bool readVector1(Stream & stream, Vector & vec) +{ + S32 num, i; + bool Ok = stream.read( & num ); + for( i = 0, vec.setSize( num ); i < num && Ok; i++ ) + Ok = vec[i].read( stream ); + return Ok; +} +template bool writeVector1(Stream & stream, const Vector & vec) +{ + bool Ok = stream.write( vec.size() ); + for( U32 i = 0; i < vec.size() && Ok; i++ ) + Ok = vec[i].write( stream ); + return Ok; +} + +//------------------------------------------------------------------------------------- +// Read/Write a vector of items, using stream.read(T), stream.write(T). + +template bool readVector2(Stream & stream, Vector & vec) +{ + S32 num, i; + bool Ok = stream.read( & num ); + for( i = 0, vec.setSize( num ); i < num && Ok; i++ ) + Ok = stream.read(&vec[i]); + return Ok; +} +template bool writeVector2(Stream & stream, const Vector & vec) +{ + bool Ok = stream.write( vec.size() ); + for( S32 i = 0; i < vec.size() && Ok; i++ ) + Ok = stream.write(vec[i]); + return Ok; +} + +//------------------------------------------------------------------------------------- +// Functions to read / write a vector of items that define mathRead() / mathWrite(). + +template bool mathReadVector(Vector& vec, Stream& stream) +{ + S32 num, i; + bool Ok = stream.read(&num); + for (i = 0, vec.setSize(num); i < num && Ok; i++ ) + Ok = mathRead(stream, &vec[i]); + return Ok; +} + +template bool mathWriteVector(const Vector& vec, Stream& stream) +{ + bool Ok = stream.write(vec.size()); + for (S32 i = 0; i < vec.size() && Ok; i++) + Ok = mathWrite(stream, vec[i]); + return Ok; +} + +//------------------------------------------------------------------------------------- +// Perform setSize() and force construction of all the elements. Done to address some +// difficulty experienced in getting virtual function table pointer constructed. + +template +void setSizeAndConstruct(Vector & vector, S32 size) +{ + vector.setSize(size); + T * tList = new T [size]; + S32 memSize = size * sizeof(T); + dMemcpy(vector.address(), tList, memSize); + delete [] tList; +} + +template +void destructAndClear(Vector & vector) +{ + for (S32 i = vector.size() - 1; i >= 0; i--) + vector[i].~T(); + vector.clear(); +} + +template +void setSizeAndClear(Vector & vector, S32 size, S32 value=0) +{ + vector.setSize( size ); + dMemset(vector.address(), value, vector.memSize()); +} + +//-------------------------------------------------------------------------- +// Read a vector with construction + +template bool constructVector(Stream & stream, Vector & vec) +{ + S32 num, i; + bool Ok = stream.read(&num); + if(num && Ok) + { + setSizeAndConstruct(vec, num); + for (i = 0; i < num && Ok; i++) + Ok = vec[i].read(stream); + } + return Ok; +} + +#endif diff --git a/ai/graphNodes.h b/ai/graphNodes.h new file mode 100644 index 0000000..ef30598 --- /dev/null +++ b/ai/graphNodes.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHNODES_H_ +#define _GRAPHNODES_H_ + +class GraphSearch; +class NavigationGraph; + +class RegularNode : public GraphNode // graphNodeBase.cc +{ + friend class GraphSearch; + protected: + Point3F mNormal; + + GraphEdge * pushTransientEdge(S32); + void popTransientEdge(); + + public: + RegularNode(); + GraphEdge& pushEdge(GraphNode * node); + GraphEdgeArray getEdges(GraphEdge*) const; + const Point3F& getNormal() const; + + // one-liners: + void transientReserve() {mEdges.reserve(mEdges.size()+2);} + const Point3F& location() const {return mLoc;} + const Point3F& fetchLoc(Point3F&) const {return mLoc;} + const GraphEdge* getEdgePtr() const {return mEdges.address();} + GraphEdgeArray getEdges() const {return getEdges(NULL);} + S32 getIndex() const {return mIndex;} + S32 edgeUsage() const {return mEdges.memSize();} + F32 radius() const {return 1.0;} +}; + +struct GraphBoundary // graphIndoors.cc +{ + Point3F seg[2]; + Point3F normal; + Point3F seekPt; + F32 distIn; + + GraphBoundary(const GraphEdgeInfo&); + void setItUp(S32 N, const NodeInfoList&, const GraphVolumeList&); + Point3F midpoint() const {return (seg[0] + seg[1]) * 0.5;} +}; + +typedef Vector GraphBoundaries; + +class InteriorNode : public RegularNode // graphIndoors.cc +{ + typedef RegularNode Parent; + F32 mMinDim, mArea; + + public: + InteriorNode(); + void init(const IndoorNodeInfo& data, S32 index, const Point3F& floor); + void setDims(F32 minDim, F32 area) {mMinDim=minDim; mArea=area;} + F32 minDim() const {return mMinDim;} + F32 area() const {return mArea;} + GraphEdge& pushOneWay(const GraphEdgeInfo::OneWay&); + NodeProximity containment(const Point3F& loc) const; +}; + +class OutdoorNode : public RegularNode // graphOutdoors.cc +{ + friend class NavigationGraph; + protected: + F32 mHeight; + public: + OutdoorNode(); + S32 getLevel() const; + Point3F getRenderPos() const; + Point3F randomLoc() const; + F32 terrHeight() const {return mHeight;} + F32 radius() const {return F32(1< +{ + typedef Vector Parent; + public: + ~IndoorNodeList(); + void clear(); + void init(S32 sz); +}; + +#endif diff --git a/ai/graphOutdoors.cc b/ai/graphOutdoors.cc new file mode 100644 index 0000000..43ed2f1 --- /dev/null +++ b/ai/graphOutdoors.cc @@ -0,0 +1,530 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- +// Visitor for making run-time node list. + +class UnrollOutdoorList : public GridVisitor +{ + protected: + const GraphNodeList& mGrid; + GraphNodeList& mListOut; + + S32 getIndex(const GridArea& area); + + public: + UnrollOutdoorList(const GridArea& world, const GraphNodeList& grid, GraphNodeList& list) + : GridVisitor(world), + mGrid(grid), + mListOut(list) + { + } + + bool beforeDivide(const GridArea& R, S32 level); + bool atLevelZero(const GridArea& R); +}; + +S32 UnrollOutdoorList::getIndex(const GridArea& R) +{ + S32 index = mArea.getIndex( R.point ); + AssertFatal( validArrayIndex(index, mGrid.size()), "Node unroll bad index" ); + return index; +} + +bool UnrollOutdoorList::beforeDivide(const GridArea& R, S32 level) +{ + S32 index = getIndex(R); + if (GraphNode * node = mGrid[index]) + { + if (node->getLevel() == level) + { + mListOut.push_back(node); + return false; // stop the sub-divide + } + } + return true; // recurse further +} + +bool UnrollOutdoorList::atLevelZero(const GridArea& R) +{ + if (GraphNode * node = mGrid[getIndex(R)]) + mListOut.push_back(node); + return true; // (N/A) +} + +//------------------------------------------------------------------------------------- + +// Make the grid of node pointers in the given area. Makes the grid of +// empty nodes, and fills in the list, and sets indices. +S32 NavigationGraph::setupOutdoorNodes(const GridArea& area_in, const Consolidated& cons, + GraphNodeList& grid_out, GraphNodeList& list_out) +{ + S32 i; + + mOutdoorNodes = new OutdoorNode [cons.size()]; + + // Set up grid with pointers to outdoor nodes. Consolidated areas have the whole + // square within the grid set to point at them. + setSizeAndClear(grid_out, area_in.len_x() * area_in.len_y()); + for (i = 0; i < cons.size(); i++) + { + const OutdoorNodeInfo & nodeInfo = cons[i]; + OutdoorNode * outdoorNode = (mOutdoorNodes + i); + + Point2I gridPoint = nodeInfo.getPoint(); + mTerrainInfo.posToLoc(outdoorNode->mLoc, gridPoint); + S32 level = nodeInfo.getLevel(); + outdoorNode->mLevel = level; + + S32 ind = mTerrainInfo.posToIndex(gridPoint); + AssertFatal(ind >= 0, "setupOutdoorNodes: bad pos"); + if (mTerrainInfo.shadowed(ind)) { + outdoorNode->mHeight = mTerrainInfo.shadowHeight(ind); + outdoorNode->set(GraphNode::Shadowed); + } + else { + outdoorNode->mHeight = 1e17; + if (mTerrainInfo.submerged(ind)) + outdoorNode->set(GraphNode::Submerged); + } + + //==> Make this get the average normal. + S32 gridShift = gNavGlobs.mSquareShift; + Point2F terrGridLoc(gridPoint.x << gridShift, gridPoint.y << gridShift); + if (!mTerrainBlock->getNormal(terrGridLoc, &outdoorNode->mNormal)) + outdoorNode->mNormal.set(0,0,1); + + AssertFatal(level > -2, "Need level > -2 for outdoor nodes"); + + if (level < 0) + level = 0; + + // fill in the grid with pointers to this node + S32 gridWidth = 1 << level; + for (S32 y = 0; y < gridWidth; y++) + for (S32 x = 0; x < gridWidth; x++) + { + Point2I P = Point2I(x,y) + gridPoint; + grid_out[ area_in.getIndex( P ) ] = outdoorNode; + } + + // Construct the outdoor node type with position and level information. + // Fill in the grid here. + // if (level > 0) + { + Point3F middleOff (gridWidth << gridShift-1, gridWidth << gridShift-1, 0); + + if (level == 0) //==> Need to handle -1, 0 differently. + middleOff *= 0.0; + + outdoorNode->mLoc += middleOff; + if (!terrainHeight(outdoorNode->mLoc, & outdoorNode->mLoc.z)) + { + // This was put here to make obvious a bug a while ago that seems + // to have been gone for quite a while. + outdoorNode->mLoc.z = 120; + warning("graphOutdoors.cc: No terrain found in middle of grid node"); + } + } + } + + // Do the unroll loop to put into the node list proper. + UnrollOutdoorList listUnroller(area_in, grid_out, list_out); + listUnroller.traverse(); + + // Make sure it matches our data: + AssertFatal(list_out.size() == cons.size(), "setupOutdoorNodes: data doesn't jibe"); + + // Set indices. Our approach is desgined to have larger squares first in the list. + // Uh, there was a potential reason for that at some point, not utilized I think... + for (i = 0; i < list_out.size(); i++) + (dynamic_cast(list_out[i]))->mIndex = i; + + return list_out.size(); +} + +//------------------------------------------------------------------------------------- +// Visitor to hook up the nodes. + +class HookOutdoorNodes : public GridVisitor +{ + protected: + const GraphNodeList& mGrid; + const Vector& mNeighbors; + U16 * const mCounting; + + bool hookBothWays(GraphNode* node, const Point2I& off); + S32 getIndex(const GridArea& area); + + public: + HookOutdoorNodes(const GridArea& world, const GraphNodeList& grid, + const Vector& neighbors, U16 * const counts = NULL) + : GridVisitor(world), mGrid(grid), mNeighbors(neighbors), mCounting(counts) {} + + bool beforeDivide(const GridArea& R, S32 level); + bool atLevelZero(const GridArea& R); +}; + + +// Points for hooking to 12 neighbors. Offsets are in multiples of half the +// square width- further going across. See cornerStep & sideStep variables +// in loop below for order of these. Basically Left->Right, and then Up. +static const Point2I hookCorners[4] = { + Point2I( 0, 0 ), + Point2I( 2, 0 ), + Point2I( 0, 2 ), + Point2I( 2, 2 ) +}; +static const Point2I hookSides[8] = { + Point2I( 0, 0 ), Point2I( 1, 0 ), + Point2I( 0, 0 ), Point2I( 0, 1 ), + Point2I( 2, 0 ), Point2I( 2, 1 ), + Point2I( 0, 2 ), Point2I( 1, 2 ) +}; + +S32 HookOutdoorNodes::getIndex(const GridArea& R) +{ + S32 index = mArea.getIndex( R.point ); + AssertFatal( validArrayIndex(index, mGrid.size()), "Node unroll bad index" ); + return index; +} + + +// Hook the node, and return true if it was successful and it was a DOWNWARD hook. +// (i.e. down by ONE level). For downward hooks we also hook self into their +// list. Also do single hook ACROSS, and return false. +bool HookOutdoorNodes::hookBothWays(GraphNode* node, const Point2I& where) +{ + S32 index = mArea.getIndex( where ); + + if (index >= 0) + { + if (GraphNode * neighbor = mGrid[ index ]) + { + S32 srcLevel = node->getLevel(); + S32 dstLevel = neighbor->getLevel(); + + OutdoorNode * from = dynamic_cast(node); + OutdoorNode * to = dynamic_cast(neighbor); + + // check our assumptions- + AssertFatal (from && to && srcLevel>-2 && dstLevel>-2, "hookBothWays- ASKEW"); + AssertFatal (mAbs(srcLevel - dstLevel) <= 1, "hookBothWays- UNSMOOTH BORDER"); + + // consider -1 and 0 to be same level for purposes of this routine- + if (srcLevel < 0) srcLevel = 0; + if (dstLevel < 0) dstLevel = 0; + + // Hook to down-by-1 level, or across. Former case hooks both & returns true. + // 10/12/2000: Added counting mode which doesn't push (for pool allocation). + if( srcLevel == dstLevel + 1 ) + { + if (mCounting) { + mCounting[from->getIndex()]++; + mCounting[to->getIndex()]++; + } + else { + from->pushEdge(to); + to->pushEdge(from); + } + return true; + } + else if( srcLevel == dstLevel ) + { + if (mCounting) + mCounting[from->getIndex()]++; + else + from->pushEdge(to); + // fall through return false + } + } + } + return false; +} + + +// Hook to all neighbors of lower or equal level. When the level is equal, then +// we don't do the hook back (they'll do it themselves). +// +// Assert that level difference is preserved. +// +bool HookOutdoorNodes::beforeDivide(const GridArea& R, S32 level) +{ + GraphNode * node = mGrid[ getIndex (R) ]; + + if( node && node->getLevel() == level ) + { + // Look at all 12 neighbors - two on each side, and the four corners. + S32 cornerStep = 0, sideStep = 0; + S32 halfWidth = (1 << level-1); + S32 x, y; + + // Loop through sides and corners, skipping middle- + for (y = -1; y <= 1; y++) for (x = -1; x <= 1; x++) if (x || y) + { + // Get offset from the boundaries computed above. We only offset for + // negative components, hence the strange math: + Point2I gridOffset((x < 0) * x, (y < 0) * y); + + if (x && y) + { + Point2I cornerOff = hookCorners[cornerStep++]; + (cornerOff *= halfWidth) += gridOffset; + hookBothWays(node, cornerOff += R.point); + } + else // (x XOR y) + { + for (S32 adjacent = 0; adjacent < 2; adjacent++) + { + Point2I sideOff = hookSides[sideStep * 2 + adjacent]; + sideOff *= halfWidth; + sideOff += gridOffset; + sideOff += R.point; + if( ! hookBothWays(node, sideOff) ) + break; + } + sideStep++; + } + } + + // Stop the Visitor subdivision- + return false; + } + + // Tell Visitor to recurse further- + return true; +} + +bool HookOutdoorNodes::atLevelZero(const GridArea& R) +{ + // Hook to all neighbors. At this level we don't hook back (each hooks itself). + S32 index = getIndex(R); + if (GraphNode * node = mGrid[index]) + for (S32 dir = 0; dir < 8; dir++) + if (mNeighbors[index] & (1 << dir)) + { + S32 x = TerrainGraphInfo::gridOffs[dir].x; + S32 y = TerrainGraphInfo::gridOffs[dir].y; + + bool didBoth = true; + + if(x && y) { + // diagonal hooks must gaurantee that there are valid nodes on either + // side of the line we're proposing to connect. + Point2I acrossDiagP1(x,0); + Point2I acrossDiagP2(0,y); + S32 ind1 = mArea.getIndex(R.point + acrossDiagP1); + S32 ind2 = mArea.getIndex(R.point + acrossDiagP2); + + if (ind1 >= 0 && ind2 >= 0 && mGrid[ind1] && mGrid[ind2]) + didBoth = hookBothWays(node, Point2I(x,y) + R.point); + else + didBoth = false; + } + else + didBoth = hookBothWays(node, Point2I(x,y) + R.point); + + AssertFatal(!didBoth, "HookOutdoor: only lateral hook at level zero"); + } + + return true; +} + +//------------------------------------------------------------------------------------- + +// Create our run time consolidated nodes from the data that has been generated. +void NavigationGraph::makeRunTimeNodes(bool useEdgePool) +{ + GridArea worldArea(mTerrainInfo.originGrid, mTerrainInfo.gridDimensions); + HookOutdoorNodes hookingVisitor(worldArea, mNodeGrid, mTerrainInfo.neighborFlags); + U16 * edgeCounts = NULL; + + S32 totalNodeCount = mTerrainInfo.consolidated.size() + mNodeInfoList.size(); + + delete [] mOutdoorNodes; + mOutdoorNodes = NULL; + mNumOutdoor = 0; + mNodeList.clear(); + mNodeGrid.clear(); + mNodeList.reserve(mTerrainInfo.consolidated.size() + mNodeInfoList.size()); + + if (haveTerrain()) + { + mNodeGrid.reserve(worldArea.extent.x * worldArea.extent.y); + // Makes outdoor grid and starts the node list- + mNumOutdoor = setupOutdoorNodes(worldArea, mTerrainInfo.consolidated, mNodeGrid, mNodeList); + } + + // Need to allocate these here for edge pool- + mIndoorNodes.init(mNodeInfoList.size()); + + // Here is where we precompute edges if we're using a pool. To know outdoor counts, + // we run the traverser in a different mode. Then roll in stored edges and bridges. + delete [] mEdgePool; + mEdgePool = NULL; + + if (useEdgePool) + { + edgeCounts = new U16 [totalNodeCount]; + + // For now it reserves space for Transient push, will change (since that is + // a wasteful (though simple) approach). + for (S32 i = 0; i < totalNodeCount; i++) + edgeCounts[i] = 2; + + // Count outdoor edges- + if (haveTerrain()) + { + HookOutdoorNodes counter(worldArea, mNodeGrid, mTerrainInfo.neighborFlags, edgeCounts); + counter.traverse(); + } + + // Count indoor- + for (S32 e = 0; e < mEdgeInfoList.size(); e++) + for (S32 dir = 0; dir < 2; dir++) + edgeCounts[mNumOutdoor + mEdgeInfoList[e].to[dir].dest]++; + + // Count bridge edges- + mBridgeList.accumEdgeCounts(edgeCounts); + + // Get the total size needed- + U32 totalEdgeCount = 0; + for (S32 t = 0; t < totalNodeCount; t++) + totalEdgeCount += edgeCounts[t]; + + // Allocate the pool (a NavigationGraph member) + mEdgePool = new GraphEdge[totalEdgeCount]; + Con::printf("Allocated edge pool of size %d", totalEdgeCount); + + // Set the outdoor pointers into the pool- + U32 offset = 0; + for (S32 o = 0; o < mNumOutdoor; o++) + { + mNodeList[o]->mEdges.setOwned(&mEdgePool[offset], edgeCounts[o]); + offset += edgeCounts[o]; + } + + // Set the indoor edge pool pointers. + for (S32 x = 0; x < mIndoorNodes.size(); x++) + { + U16 count = edgeCounts[x + mNumOutdoor]; + mIndoorNodes[x].mEdges.setOwned(&mEdgePool[offset], count); + offset += count; + } + + AssertFatal(offset == totalEdgeCount, "makeRunTimeNodes()"); + } + + // hooks up the edges. + hookingVisitor.traverse(); + + // Set up the interior nodes. Last param gives starting index- + initInteriorNodes(mEdgeInfoList, mNodeInfoList, mNodeList.size()); + mNumIndoor = mIndoorNodes.size(); + + // Put the indoor nodes on our pointer list. + IndoorNodeList::iterator in; + for (in = mIndoorNodes.begin(); in != mIndoorNodes.end(); in++) + mNodeList.push_back(in); + + findJumpableNodes(); + + // Set up the shoreline. Push it back more for Lava so bots give it wider berth. + expandShoreline(0); + if (mDeadlyLiquid) + expandShoreline(1); + + S32 numHanging = doFinalFixups(); + + delete [] edgeCounts; + + // AssertFatal(numHanging < 1, "Run time consolidate hookup making hanging nodes"); +} + +//------------------------------------------------------------------------------------- + +// This sets the shoreline bit, which will affect edge scaling. Note that this method +// can be called multiple times to expand what is considered shoreline. This only +// operates on outdoor nodes and their outdoor neighbors. +void NavigationGraph::expandShoreline(U32 wave) +{ + AssertFatal(wave < 3, "Only three shoreline bits available"); + + U32 extendFromMask = (GraphNode::ShoreLine << wave); + U32 extendToMask = (extendFromMask << 1); + + for (S32 i = 0; i < mNumOutdoor; i++) { + if (GraphNode * node = lookupNode(i)) { + if (node->test(extendFromMask)) { + GraphEdgeArray edges = node->getEdges(mEdgeBuffer); + while (GraphEdge * edge = edges++) { + if (!edge->isJetting() && (edge->mDest < mNumOutdoor)) { + GraphNode * neighbor = lookupNode(edge->mDest); + if (!neighbor->test(extendFromMask)) + neighbor->set(extendToMask); + } + } + } + } + } +} + +//------------------------------------------------------------------------------------- +// Outdoor Node Methods + + +S32 OutdoorNode::getLevel() const +{ + return S32(mLevel); +} + +OutdoorNode::OutdoorNode() +{ + mIndex = -1; + mLoc.set(-1,-1,-1); + mFlags.set(Outdoor); +} + +Point3F OutdoorNode::getRenderPos() const +{ + Point3F adjustPos = location(); + + if( mLevel > 0 ){ + F32 up = F32(mLevel); + adjustPos.z += (up * 0.7); + } + else + adjustPos.z += 0.2; + + return adjustPos; +} + +// Randomize a location within an outdoor node. Choose random offsets that are at least 1 +// unit away from grid axes, and at least 0.5 units in from sides- +Point3F OutdoorNode::randomLoc() const +{ + Point3F loc = location(); + F32 *xy = loc; + F32 R = radius(); + + for (S32 i = 0; i < 2; i++) + { + F32 off = gRandGen.randF() * (R - 1.5) + 1.0; + if (gRandGen.randI() & 1) + xy[i] += off; + else + xy[i] -= off; + } + + gNavGraph->terrainHeight(loc, &loc.z); + loc.z += 1.0; // ===> Extra should depend on slope (if issue now w/ preprocess)? + + return loc; +} + diff --git a/ai/graphPartition.cc b/ai/graphPartition.cc new file mode 100644 index 0000000..7716ef2 --- /dev/null +++ b/ai/graphPartition.cc @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- + +GraphPartition::GraphPartition() +{ + mType = ForceField; + mCanJetDown = false; + // mPartition.setSize(gNavGraph->numNodesAll()); + // mPartition.clear(); +} + +void GraphPartition::install(const GraphPartition& p) +{ + mPartition.copy(p.mPartition); +} + +// Allocate the bit vector and clear it. +void GraphPartition::setSize(S32 N) +{ + mPartition.setSize(N); +} + +// Consumer uses a regular partition to build the downhill partition. We check here to +// see if it's any different from the regular partition. +void GraphPartition::setDownhill(const GraphPartition& downhill) +{ + U32 bytes = mPartition.getByteSize(); + + AssertFatal(mType == Armor, "Only armor types can have a downhill component"); + AssertFatal(bytes == downhill.mPartition.getByteSize(), "Unequal partition sizes"); + + if (dMemcmp(mPartition.getBits(), downhill.mPartition.getBits(), bytes)) { + mCanJetDown = true; + mDownhill.copy(downhill.mPartition); + } + else { + mCanJetDown = false; + } +} + +// See if partition can answer question of whether or not we can get from A to B. +GraphPartition::Answer GraphPartition::reachable(S32 A, S32 B) +{ + if (mPartition.test(A)) + { + if (mPartition.test(B) || (mCanJetDown && mDownhill.test(B))) + return CanReach; + else + return CannotReach; + } + else if (mPartition.test(B)) + return CannotReach; + else + return Ambiguous; +} + +//------------------------------------------------------------------------------------- + +PartitionList::PartitionList() +{ + mType = GraphPartition::ForceField; +} + +PartitionList::~PartitionList() +{ + clear(); +} + +//------------------------------------------------------------------------------------- + +void PartitionList::clear() +{ + while (size()) { + last().~GraphPartition(); + pop_back(); + } +} + +// This is called after searches that have failed to reach their target to register the +// new partition that has been discovered. ie. when a force field has changed states, +// then partition lists are invalid and get rebuilt as searches happen. +void PartitionList::pushPartition(const GraphPartition& partition) +{ + GraphPartition empty; + + // I'd like to construct the entry in the vector, but can't get it to go... + // i.e increment(); last().GraphPartition(); + + // But this should work, given that BitVector doesn't override assignment... ugh. + push_back(empty); + last().install(partition); + last().setType(mType); +} + +// Our partition lists will probably never have the complete answer to this. Need an +// ambiguous return value. +GraphPartition::Answer PartitionList::reachable(S32 A, S32 B) +{ + for (iterator partition = begin(); partition != end(); partition++) + { + GraphPartition::Answer answer = partition->reachable(A, B); + if (answer != GraphPartition::Ambiguous) + return answer; + } + AssertFatal(mType != GraphPartition::Armor, "Armor partitions are never ambiguous"); + return GraphPartition::Ambiguous; +} + +// When partitions are built at mission start, it has to check and see which nodes have +// entries after finishing each pass. It goes until graph is filled. +S32 PartitionList::haveEntry(S32 forNode) +{ + for (iterator partition = begin(); partition != end(); partition++) + if (partition->test(forNode)) + return true; + + return false; +} + diff --git a/ai/graphPartition.h b/ai/graphPartition.h new file mode 100644 index 0000000..f02a041 --- /dev/null +++ b/ai/graphPartition.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHPARTITION_H_ +#define _GRAPHPARTITION_H_ + +class GraphSearch; + +class GraphPartition +{ + public: + enum Types {Armor, ForceField}; + enum Answer {CannotReach, CanReach, Ambiguous}; + + protected: + Types mType; + bool mCanJetDown; + BitVector mPartition; + BitVector mDownhill; + + public: + GraphPartition(); + + void install (const GraphPartition& p); + void setDownhill(const GraphPartition& downhill); + void setPartition(GraphSearch * searcher); + Answer reachable(S32 from, S32 to); + void setSize(S32 N); + + void setType(Types t) {mType = t;} + bool test(S32 i) {return mPartition.test(i);} + void set(S32 i) {mPartition.set(i);} + void clear() {mPartition.clear();} +}; + +class PartitionList : public Vector +{ + protected: + GraphPartition::Types mType; + + public: + PartitionList(); + ~PartitionList(); + + void pushPartition(const GraphPartition& partition); + void setType(GraphPartition::Types t) {mType = t;} + GraphPartition::Answer reachable(S32 from, S32 to); + S32 haveEntry(S32 forNode); + void clear(); +}; + +#endif diff --git a/ai/graphPath.cc b/ai/graphPath.cc new file mode 100644 index 0000000..fe177f8 --- /dev/null +++ b/ai/graphPath.cc @@ -0,0 +1,831 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" +#include "platform/profiler.h" + +//------------------------------------------------------------------------------------- + +// Go through the construct function since we want to reconstruct on mission cycle. +NavigationPath::NavigationPath() +{ + constructThis(); +} + +void NavigationPath::constructThis() +{ + mState.constructThis(); + mCurEdge = NULL; + mTimeSlice = 0; + mSaveSeekNode = -1; + mSearchDist = 0.0f; + mUserStuck = true; + mForceSearch = true; + mRepathCounter = 10000; + mSaveDest.set(1e7, 1e7, 1e7); + setRedoDist(); + mPctThreshSqrd = 1.0; + mPathIndoors = mAreIndoors = false; + mAwaitingSearch = false; + mTeam = 0; + mBusyJetting = false; + mSearchWasValid = true; + mAdjustSeek.set(0,0,0); + dMemset(mSavedEndpoints, 0, sizeof(mSavedEndpoints)); + GraphEdge newEdge; + mSaveLastEdge = newEdge; + mStopCounter = 0; + mCastZ = mCastAng = 0.0; + mCastAverage.set(0,0,0); + mEstimatedEdge = NULL; + mEstimatedEnergy = 0.0; + mFindHere.init(); +} + +// Since some of the code is in elsewhere (graphSmooth), we should package up the +// relevant state for it... Doesn't seem like the best organization, but the idea is +// that the volume code "knows" more about smoothing paths indoor than we do here, +// and so that code should be separated... +void NavigationPath::State::constructThis() +{ + thisEdgeJetting = false; + nextEdgeJetting = false; + nextEdgePrecise = false; + curSeekNode = 0; + seekLoc.set(0,0,0); + path.clear(); + visit.clear(); + edges.clear(); + hereLoc.set(0,0,0); + destLoc.set(0,0,0); +} + +// Fetch the node along the path, undoing any bit-flipped Transient indices. +GraphNode * NavigationPath::getNode(S32 idx) +{ + S32 node = mState.path[idx]; + S32 toggle = -S32(node < 0); + return gNavGraph->lookupNode(node ^ toggle); +} + +// Get location in path, handling case where endpoints were transients. +Point3F NavigationPath::getLoc(S32 idx) +{ + S32 node = mState.path[idx]; + if(node >= 0) + return gNavGraph->lookupNode(node)->location(); + else + return mSavedEndpoints[idx > 0]; +} + +// Save endpoints of search (flagged with negative indices). A NULL dstNode +// is passed for searches where destination is a node in graph. +void NavigationPath::saveEndpoints(TransientNode* srcNode, TransientNode* dstNode) +{ + mSavedEndpoints[0] = srcNode->location(); + mState.path.first() ^= S32(-1); + if (dstNode) + { + mSavedEndpoints[1] = dstNode->location(); + mState.path.last() ^= S32(-1); + } + + // These edges are used for the path advancing. Must take care with edges + // coming off of the transients - the last one must be saved since it is popped + // after the search (source connection INTO graph remains though) + setSizeAndClear(mState.edges, getMax(mState.path.size()-1, 0)); + for (S32 i = mState.edges.size(); i > 0; i--) + { + GraphEdge * edgePtr = getNode(i-1)->getEdgeTo(getNode(i)); + AssertFatal(edgePtr, "All edges should exist in path"); + mState.edges[i - 1] = edgePtr; + } + if (mState.edges.size()) + { + mSaveLastEdge = * mState.edges.last(); + mState.edges.last() = & mSaveLastEdge; + } +} + +// This is for renderer - though we'll use a similar node marking scheme for +// implementing avoidance / randomization of paths. +void NavigationPath::markRenderPath() +{ + for(S32 i = getMax(mState.curSeekNode - 1, 0); i < mState.path.size(); i++) + getNode(i)->setOnPath(); +} + +// Revised path search to go off of the transient nodes. +void NavigationPath::computePath(TransientNode& srcNode, TransientNode& dstNode) +{ + mSearchDist = 0.0f; + mSearchWasValid = false; + + // Perform the search if the push indicates these two can (probably) reach. + if (gNavGraph->pushTransientPair(srcNode, dstNode, mTeam, mJetCaps)) + { + PROFILE_START(PathComputation); + + mState.path.clear(); + mState.curSeekNode = 0; + GraphSearch * searcher = gNavGraph->getMainSearcher(); + searcher->setAStar(true); + searcher->setTeam(mTeam); + searcher->setThreats(gNavGraph->getThreatSet(mTeam)); + searcher->setRandomize(true); + #if _GRAPH_PART_ + searcher->setRatings(gNavGraph->jetManager().getRatings(mJetCaps)); + #endif + + searcher->performSearch(&srcNode, &dstNode); + + // Path fetcher tells us if search failed. + if (searcher->getPathIndices(mState.path)) + { + + + + mSearchDist = searcher->searchDist(); + setSizeAndClear(mState.visit, mState.path.size()); + saveEndpoints(&srcNode, &dstNode); + mState.curSeekNode = 1; + setEdgeConstraints(); + mAwaitingSearch = false; + mSearchWasValid = true; + } + else + { + // Note - we need to know if the transient connections into the graph were + // all make-able by the bot (they should all go through the same evaluation + // to check the connections). I think this assert arose because a bot had + // transient connection that couldn't be made. + // // A search should only fail due to force fields. Armor capabilities should be + // // adequately predicted in advance. + // AssertFatal(gNavGraph->haveForceFields(), "Failed to predict failure"); + // + // // Force field management will update this team's partition accordingly. + // gNavGraph->newPartition(searcher, mTeam); + + if (gNavGraph->haveForceFields()) + { + // Force field management will update this team's partition accordingly. + gNavGraph->newPartition(searcher, mTeam); + } + } + PROFILE_END(); // PathComputation + } + else { + #if _GRAPH_WARNINGS_ + NavigationGraph::warning("Search tried across islands or partitions"); + #endif + } + + // Unconnects from graph. + gNavGraph->popTransientPair(srcNode, dstNode); +} + +// Just want all the post-pathCompute stuff separated out: +void NavigationPath::afterCompute() +{ + mSaveDest = mState.destLoc; + mForceSearch = false; + mRepathCounter = 0; + if ( mRedoMode == OnPercent ) { + F32 pctThresh = (mRedoPercent * mSearchDist); + mPctThreshSqrd = (pctThresh * pctThresh); + } + mUserStuck = false; +} + +bool NavigationPath::updateTransients(TransientNode& hereNode, + TransientNode& destNode, bool forceRedo) +{ + // Nodes could be invalid- + if (mHook.iGrowOld()) + mLocateHere.reset(), mLocateDest.reset(); + + if (forceRedo) + mLocateHere.forceCheck(), mLocateDest.forceCheck(); + + // This does all the work of figuring out how to connect- + mLocateHere.update(mState.hereLoc); + mLocateDest.update(mState.destLoc); + + // Install connections found, and update locs- + hereNode.setEdges(mLocateHere.getEdges()); + destNode.setEdges(mLocateDest.getEdges()); + hereNode.setClosest(mLocateHere.bestMatch()); + destNode.setClosest(mLocateDest.bestMatch()); + hereNode.setLoc(mState.hereLoc); + destNode.setLoc(mState.destLoc); + + // NOTE! Only hook one way due to how Push-Search-Pop works (computePath() above). + bool needToJet; + if (mLocateHere.canHookTo(mLocateDest, needToJet)) { + GraphEdge& edge = hereNode.pushEdge(&destNode); + if (needToJet) + edge.setJetting(); + gNavGraph->jetManager().initEdge(edge, &hereNode, &destNode); + } + + return forceRedo; // not used +} + +//------------------------------------------------------------------------------------- +// Periodic path updates. Return true if there are nodes to traverse. + +#define SearchWaitTicks 20 +#define AllowVisitTicks 21 + +bool NavigationPath::checkPathUpdate(TransientNode& hereNode, TransientNode& destNode) +{ + bool recompute = mForceSearch; + // bool recompute = false; + + bool changedSeekNode = (mSaveSeekNode != mState.curSeekNode); + mSaveSeekNode = mState.curSeekNode; + + if (!recompute) { + F32 threshold = (mRedoMode == OnDist ? mRedoDistSqrd : mPctThreshSqrd); + bool canSearch = (++mRepathCounter > SearchWaitTicks) && !userMustJet(); + bool needSearch = ((mState.destLoc - mSaveDest).lenSquared() > threshold); + + if (mUserStuck) + needSearch = true; + + if (needSearch) { + // We need to make the canSearch check a little more strict and wait a little + // extra time, or search when the node has been updated. + if (canSearch && (changedSeekNode || mRepathCounter > AllowVisitTicks)) + recompute = true; + else + mAwaitingSearch = true; + } + } + if (recompute) + mAwaitingSearch = true; + + updateTransients(hereNode, destNode, recompute); + + if (recompute) { + computePath(hereNode, destNode); + afterCompute(); + } + + if (weAreIndoors()) + mAreIndoors = true; + else { + if(checkOutsideAdvance()) + advanceByOne(); + } + + // Update path + if (mState.curSeekNode < mState.path.size()) + { + GraphNode * fromNode = (mState.curSeekNode > 0 ? getNode(mState.curSeekNode-1) : NULL); + + if (fromNode && gNavGraph->useVolumeTraverse(fromNode, mCurEdge)) + { + S32 adv = gNavGraph->checkIndoorSkip(mState); + + if (adv) + { + // Con::printf("Advanced through %d indoor nodes", adv); + while (adv--) + { + AssertFatal(canAdvance(), "Nav path tried illegal advance"); + advanceByOne(); + } + } + else + { + if (gNavGraph->volumeTraverse(fromNode, mCurEdge, mState)) + { + if (canAdvance()) //==> Shouldn't it always be possible to advance? + advanceByOne(); + } + } + } + else + { + mState.seekLoc = getLoc(mState.curSeekNode); + F32 threshold = visitRadius() + 0.22; + if(within_2D(mState.hereLoc, mState.seekLoc, threshold) && canAdvance()) { + advanceByOne(); + if (mState.curSeekNode < mState.path.size()) + mState.seekLoc = getLoc(mState.curSeekNode); // re-fetch! + } + } + } + + return (mState.curSeekNode < mState.path.size()); +} + +//------------------------------------------------------------------------------------- + +// The consumer of the NavigationPath must call this every frame. +bool NavigationPath::updateLocations(const Point3F& here, const Point3F& there) +{ + // This addition to the Z is important because we don't match to indoor nodes that + // we are below. Not pretty, but the alternative is most extra LOS calls, or other + // potential ambiguities in the node matching process. Also see the wall scanning + // code below, which is 'cognizant' of these numbers. + (mState.hereLoc = here).z += 0.4; + (mState.destLoc = there).z += 0.4; + + mAreIndoors = false; // (default - get's changed if indoors) + + if( !NavigationGraph::gotOneWeCanUse()) { + mState.seekLoc = mState.destLoc; + mSearchWasValid = false; + } + else { + // Threat manager needs to be called frequently- + gNavGraph->threats()->monitorThreats(); + gNavGraph->monitorForceFields(); + + // Note hook nodes must be fetched every frame (in case of graph revisions). + if (!checkPathUpdate(mHook.getHook1(), mHook.getHook2())) + mState.seekLoc = mState.destLoc; + + // Debug info- + markRenderPath(); + + // This just walks the graph looking for malfeasance + // gNavGraph->patrolForProblems(); + } + + // Movement code sets while jetting, each set lasts for one frame + mBusyJetting = false; + + // Busy-dec the stop counter, and check wall avoidance when non-zero. + if (--mStopCounter <= 0) + mStopCounter = 0; + else + checkWallAvoid(); + + // Return status of the last search performed- + return mSearchWasValid; +} + +//------------------------------------------------------------------------------------- +// Management of path advancing. + + +// This assumes we have a graph and are seeking a node within the list. +S32 NavigationPath::checkOutsideAdvance() +{ + if(canAdvance()) + { + S32 seekNode = mState.curSeekNode + 1; + if (seekNode < mState.path.size() && !mState.nextEdgeJetting) + { // See if we can get to the next one. We're doing this in 2D, which may be Ok + // in the long run too, though we might track maximum heights along the way. + // First we check for registered threats along this segment. + Point3F srcLoc(mState.hereLoc); + Point3F dstLoc(getLoc(seekNode)); + if (gNavGraph->threats()->sanction(srcLoc, dstLoc, mTeam)) + { + srcLoc.z = dstLoc.z = 0.0f; + F32 pct = gNavGraph->mTerrainInfo.checkOpenTerrain(srcLoc, dstLoc); + if (pct == 1.0) + return 1; + } + } + } + return 0; +} + +bool NavigationPath::canAdvance() +{ + if (mState.curSeekNode < mState.path.size()) + if (!mState.thisEdgeJetting) + return true; + return false; +} + +F32 NavigationPath::visitRadius() +{ + // if (mState.nextEdgeJetting || mAreIndoors || mPathIndoors) + if (mState.nextEdgePrecise || mAreIndoors || mPathIndoors) + return 0.4; + else + return 2.6; +} + +// See if the next destination is a Jetting edge. An assumption of this routine +// is that it is only called when a new path is computed, or when the path is +// advanced. Else problems - such as transient source having new connections +// which don't relate to the current path, etc. +void NavigationPath::setEdgeConstraints() +{ + mState.thisEdgeJetting = false; + mState.nextEdgeJetting = false; + mState.nextEdgePrecise = false; + mPathIndoors = false; + mCurEdge = NULL; + + if (mState.curSeekNode > 0 && mState.curSeekNode < mState.path.size()) + { + GraphNode * curSeekNode = getNode(mState.curSeekNode); + GraphNode * prevSeekNode = getNode(mState.curSeekNode-1); + + mCurEdge = mState.edges[mState.curSeekNode - 1]; + + if (curSeekNode->indoor() || prevSeekNode->indoor()) + mPathIndoors = true; + + mState.thisEdgeJetting = mCurEdge->isJetting(); + + // Configure the edge. Hop over is a U8 with 3 bits of precision. + if (mState.thisEdgeJetting) + { + mNavJetting.init(); + if (mCurEdge->hasHop()) + mNavJetting.mHopOver = mCurEdge->getHop(); + } + + if (mState.thisEdgeJetting) + mState.seekLoc = getLoc(mState.curSeekNode); + + if (mState.curSeekNode+1 < mState.path.size()) + { + GraphEdge * nextEdge = mState.edges[mState.curSeekNode]; + mState.nextEdgeJetting = nextEdge->isJetting(); + + // This flags that we need to go to the node location. + mState.nextEdgePrecise = (mState.nextEdgeJetting || !nextEdge->isBorder()); + } + } +} + +// Called when a node has been visited. Mark it for later avoidance (path randomizing) +// if such is enabled and reasonable. Don't mark large terrain nodes or transients. +void NavigationPath::randomization() +{ + GraphNode * node = getNode(mState.curSeekNode); + if (!node->transient() && node->getLevel() <= 2) + node->setAvoid(60000); +} + +// The path is advanced through only two interface functions, this one and the next. +void NavigationPath::advanceByOne() +{ + AssertFatal(canAdvance(), "advanceByOne() called incorrectly"); + randomization(); + mState.curSeekNode++; + setEdgeConstraints(); +} + +// When user is done - they inform the system so it can advance. +void NavigationPath::informJetDone() +{ + AssertFatal(mState.thisEdgeJetting, "informJetDone() only called when jetting"); + randomization(); + mState.curSeekNode++; + setEdgeConstraints(); +} + +// User queries to see if they now must jet. +bool NavigationPath::userMustJet() const +{ + return (mState.path.size() > 0 && mState.thisEdgeJetting); +} + +void NavigationPath::forceSearch() +{ + if (userMustJet() && _GRAPH_WARNINGS_) + NavigationGraph::warning("Searched forced while user is jetting"); + mForceSearch = true; +} + +// Here we use the path randomization to hopefully get the guy off the path. We back up +// through the list of any that were skipped - since we may not have even come to those +// yet. And then we go forward by one. +void NavigationPath::informStuck(const Point3F& stuckLoc, const Point3F& stuckDest) +{ + stuckLoc; stuckDest; + S32 start = getMax(mState.curSeekNode - 1, 1); + S32 end = getMin(mState.curSeekNode + 1, mState.path.size() - 1); + + while (start > 1 && mState.visit[start].test(State::Skipped)) + start--; + + while (start < end) { + GraphNode * node = getNode(start++); + AssertFatal(!node->transient(), "informStuck() didn't skip transients"); + node->setAvoid(15 * 1000, true); + } + + mUserStuck = true; +} + +//------------------------------------------------------------------------------------- + +U32 gCountStoppedTicks = 0; +// F32 gVelSquaredThresh = 0.1; +F32 gVelSquaredThresh = -1.0; // Take out for moment - needs more testing. + +// When not moving but should be, this gets called - when in Express/Walk mode. +void NavigationPath::informProgress(const VectorF& vel) +{ + F32 velSquared = vel.lenSquared(); + if (velSquared < gVelSquaredThresh) + { + if ((mStopCounter += 2) > 10) + gCountStoppedTicks++; + } + else + { + // Once not stopped, then limit how much we have to count down. But we do + // want that time as well for fading the running mCastAverage below. + mStopCounter = getMin(mStopCounter, 7); + } +} + +// A nice angle for scanning around with good coverage and consistent change. These +// are relative prime numbers a little over 3/8 of the cycle points (360.0, 1.90000) +static const F64 sCycleAngle = mDegToRad(F64(137.3)); +static const F64 sCycleHeight = 0.37747; +static const U32 sCycleMask = (InteriorObjectType|TerrainObjectType); +static const F32 sAverageTheNew = (1.0 / 8.0); +static const F32 sAverageTheOld = (1.0 - sAverageTheOld); + +// Monitor the stop counter. When it persists, look for walls and such to move +// away from. We do this with rotating LOS checks, which also have to move up and +// down to have the best chance of finding obstacles. Cycling on 1.9 height covers +// range from 0.4 to 2.3 (see above addition to supplied here loc z). +void NavigationPath::checkWallAvoid() +{ + if (mStopCounter > 3) + { + if ((mCastAng += sCycleAngle) > M_2PI) + mCastAng -= M_2PI; + if ((mCastZ += sCycleHeight) > 1.9) + mCastAng -= 1.9; + + F32 angle32 = F32(mCastAng); + Point3F startPoint( mState.hereLoc.x, mState.hereLoc.y, mState.hereLoc.z + F32(mCastZ) ); + Point3F lineOut( mCos(angle32), mSin(angle32), 0); + + // Get the endpoint out a little ways. Find intersection and convert back + // to an offset which we'll roll into running average of offsets. + (lineOut *= 7.0) += startPoint; + RayInfo coll; + gServerContainer.castRay(startPoint, lineOut, sCycleMask, &coll); + coll.point -= startPoint; + + // Get the running average- + coll.point *= sAverageTheNew; + mCastAverage *= sAverageTheOld; + mCastAverage += coll.point; + } + else + { + // In first few frames free, or first frames stuck, fade down the average. + mCastAverage *= (2.0 / 3.0); + } +} + +static Point3F reignIn(const Point3F& S, Point3F D, F32 cap) +{ + D -= S; + if (D.lenSquared() > (cap * cap)) + D.normalize(cap); + return D += S; +} + +// This is where user fetches location to seek. We now do additional checks to +// reign in this distance when near steep things. +const Point3F& NavigationPath::getSeekLoc(const VectorF&) +{ + // if (NavigationGraph::gotOneWeCanUse()) + // mAdjustSeek = reignIn(mState.hereLoc, mState.seekLoc, 10.0); + // else + if (mStopCounter) + { + // Seek away from collision point if that exists- + mAdjustSeek = mCastAverage; + mAdjustSeek *= F32(mStopCounter); + mAdjustSeek += mState.hereLoc; + } + else + mAdjustSeek = mState.seekLoc; + + return mAdjustSeek; +} + +//------------------------------------------------------------------------------------- + +// Convey the jetting ability to the jet manager. We let the aiConnection go ahead +// and retrieve this data from Player since we've so far kept from #including Player.h. +void NavigationPath::setJetAbility(const JetManager::Ability & ability) +{ + if (gNavGraph) { + if (_GRAPH_PART_ && gNavGraph->jetManager().update(mJetCaps, ability)) { + // A bunch of work just happened, let's forestall searches a little- + mRepathCounter = getMin(SearchWaitTicks >> 1, mRepathCounter); + } + } +} + +//------------------------------------------------------------------------------------- + +// How far across open terrain user can go from src to dst. dstLoc parameter is +// updated accordingly, and a percentage from 0 to 1 is returned. +F32 NavigationPath::checkOpenTerrain(const Point3F& srcLoc, Point3F& dstLoc) +{ + if (NavigationGraph::gotOneWeCanUse()) + return gNavGraph->mTerrainInfo.checkOpenTerrain(srcLoc, dstLoc); + dstLoc = srcLoc; + return 0.0; +} + +//------------------------------------------------------------------------------------- +// A couple of path lookahead functions- + +// Get a point that is dist along the path. +Point3F NavigationPath::getLocOnPath(F32 dist) +{ + Point3F currLoc(mState.hereLoc); + + for (S32 i = mState.curSeekNode; i < mState.path.size() && dist > 0.01; i++) + { + Point3F nextLoc = getLoc(i); + VectorF diffVec = nextLoc; + F32 len = (diffVec -= currLoc).len(); + + if (len < dist) + { + currLoc = nextLoc; + dist -= len; + } + else + { + currLoc += (diffVec *= (dist / len)); + break; + } + } + return currLoc; +} + +// Find distance remaining on path by stepping through it - stop if user has given a +// threshold dist outside of which they don't care (defaults to huge). +F32 NavigationPath::distRemaining(F32 maxCare) +{ + Point3F currLoc(mState.hereLoc); + F32 dist = 0.0f; + + for (S32 i = mState.curSeekNode; i < mState.path.size(); i++) + { + Point3F nextLoc = getLoc(i); + + if ((dist += (nextLoc - currLoc).len()) > maxCare) + return maxCare; + + currLoc = nextLoc; + } + return dist; +} + +//------------------------------------------------------------------------------------- + +// Find next edge that requires jetting if it's within maxDist. If found, then we +// estimate the needed energy to complete the hop. Since the estimation is a little +// lengthy - we remember which edge we computed for and only do it once. +F32 NavigationPath::jetWillNeedEnergy(F32 maxDist) +{ + if (!mState.thisEdgeJetting) + { + F32 accumDist = 0.0f; + for (S32 i = mState.curSeekNode; i < mState.edges.size(); i++) + { + // Let's only do a len() calculation to the first node, otherwise use distance + // on the edge. (First len() required because the bot's moving). + F32 legDist; + if (i == mState.curSeekNode) + legDist = (getLoc(i) - mState.hereLoc).len(); + else + legDist = mState.edges[i - 1]->mDist; + + if ((accumDist += legDist) < maxDist) + { + GraphEdge * nextEdge = mState.edges[i]; + if (nextEdge->isJetting()) + return estimateEnergy(nextEdge); + } + } + } + return 0.0; +} + +// Get the jet manager's estimation, and remember that we did this work for this +// edge since JetManager::estimateEnergy() does a bit o' math. +F32 NavigationPath::estimateEnergy(const GraphEdge * edge) +{ + if (mEstimatedEdge != edge) + { + mEstimatedEnergy = gNavGraph->jetManager().estimateEnergy(mJetCaps, edge); + mEstimatedEdge = edge; + } + return mEstimatedEnergy; +} + +//------------------------------------------------------------------------------------- + +// Return true if we are outside, and set the roam radius (in param) if so. +bool NavigationPath::locationIsOutdoors(const Point3F &location, F32* roamRadPtr) +{ + F32 roamRadius = 1e12; // Default is outdoors, with unlimited roam room. + + if (NavigationGraph::gotOneWeCanUse()) + { + if (!gNavGraph->haveTerrain()) + return false; + + Point3F tempLocation(location.x, location.y, 0.0f); + if (gNavGraph->mTerrainInfo.inGraphArea(tempLocation)) + { + SphereF sphere; + if (gNavGraph->mTerrainInfo.locToIndexAndSphere(sphere, tempLocation) >= 0) + roamRadius = sphere.radius; // fall through to true + else + return false; + } + } + + if (roamRadPtr) + *roamRadPtr = roamRadius; + + return true; +} + +// By default (with no graph), this is false. +bool NavigationPath::weAreIndoors() const +{ + if (NavigationGraph::gotOneWeCanUse()) + if (!gNavGraph->haveTerrain()) + return true; + else if (gNavGraph->mTerrainInfo.inGraphArea(mState.hereLoc)) + return !gNavGraph->findTerrainNode(mState.hereLoc); + + return false; +} + +bool NavigationPath::getPathNodeLoc(S32 ahead, Point3F& loc) +{ + S32 dest = mState.curSeekNode + ahead; + + if (dest < mState.path.size() && dest > 0) + { + GraphEdge * beforeEdge = mState.edges[dest - 1]; + if (!beforeEdge->isJetting()) + { + if (beforeEdge->isBorder()) + loc = gNavGraph->getBoundary(beforeEdge->mBorder).seekPt; + else + loc = getNode(dest)->location(); + return true; + } + } + return false; +} + +// Does the bot have to jet into a vehicle? +bool NavigationPath::intoMount() const +{ + if (mLocateDest.isMounted()) + if (mState.curSeekNode == mState.path.size() - 1) + return true; + return false; +} + +bool NavigationPath::canReachLoc(const Point3F& dst) +{ + if (NavigationGraph::gotOneWeCanUse()) + { + // mFindHere is a FindGraphNode which remembers results of closest node searches. + mFindHere.setPoint(mState.hereLoc, mFindHere.closest()); + FindGraphNode findDest(dst); + return gNavGraph->canReachLoc(mFindHere, findDest, mTeam, mJetCaps); + } + else + return true; +} + + +void NavigationPath::missionCycleCleanup() +{ + constructThis(); + mJetCaps.reset(); + mNavJetting.init(); + mHook.reset(); + mFindHere.init(); + mLocateHere.cleanup(); + mLocateDest.cleanup(); +} + diff --git a/ai/graphPath.h b/ai/graphPath.h new file mode 100644 index 0000000..59ea3bf --- /dev/null +++ b/ai/graphPath.h @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHPATH_H_ +#define _GRAPHPATH_H_ + +struct NavJetting +{ + NavJetting() {init();} + void init() {mHopOver = 0.0f; mChuteUp = mChuteDown = false;} + F32 mHopOver; + bool mChuteUp; + bool mChuteDown; +}; + +class NavigationPath +{ + public: + typedef Vector VisitState; + struct State + { + enum {Visited = (1 << 0), + Outbound = (1 << 1), + Skipped = (1 << 2)}; + void constructThis(); + Vector path; + VisitState visit; + GraphEdgePtrs edges; + Point3F seekLoc; + Point3F hereLoc; + Point3F destLoc; + bool thisEdgeJetting; + bool nextEdgeJetting; + bool nextEdgePrecise; + S32 curSeekNode; + }; + + private: + enum HowToRedo {OnDist, OnPercent}; + void constructThis(); + bool checkPathUpdate(TransientNode& here, TransientNode& there); + bool updateTransients(TransientNode& src, TransientNode& dst, bool redo); + void saveEndpoints(TransientNode* from, TransientNode* to); + void computePath(TransientNode& from, TransientNode& to); + F32 estimateEnergy(const GraphEdge * edge); + void checkWallAvoid(); + void randomization(); + void markRenderPath(); + void afterCompute(); + bool canAdvance(); + void setEdgeConstraints(); + void advanceByOne(); + + S32 checkOutsideAdvance(); + GraphNode * getNode(S32 idx); + Point3F getLoc(S32 index); + + State mState; + Point3F mHereLoc; + Point3F mDestLoc; + Point3F mAdjustSeek; + HowToRedo mRedoMode; + bool mForceSearch; + bool mAreIndoors; + bool mPathIndoors; + bool mAwaitingSearch; + bool mBusyJetting; + F32 mPctThreshSqrd; + F32 mRedoDistSqrd; + F32 mRedoPercent; + F32 mSearchDist; + U32 mTeam; + Point3F mSaveDest; + S32 mRepathCounter, mTimeSlice; + S32 mStopCounter; + S32 mSaveSeekNode; + bool mUserStuck; + bool mSearchWasValid; + GraphEdge * mCurEdge; + GraphLocate mLocateHere; + GraphLocate mLocateDest; + GraphHookRequest mHook; + Point3F mSavedEndpoints[2]; + GraphEdge mSaveLastEdge; + JetManager::ID mJetCaps; + NavJetting mNavJetting; + FindGraphNode mFindHere; + F64 mCastAng, mCastZ; + Point3F mCastAverage; + const GraphEdge * mEstimatedEdge; + F32 mEstimatedEnergy; + + public: + NavigationPath(); + + bool updateLocations(const Point3F& src, const Point3F& dst); + F32 checkOpenTerrain(const Point3F& from, Point3F& to); + bool locationIsOutdoors(const Point3F &loc, F32* roamDist=0); + bool weAreIndoors() const; + bool userMustJet() const; + bool intoMount() const; + void informJetDone(); + void setJetAbility(const JetManager::Ability& ability); + void informStuck(const Point3F& stuckLoc, const Point3F& stuckDest); + void informProgress(const VectorF& vel); + F32 jetWillNeedEnergy(F32 maxDist); + Point3F getLocOnPath(F32 dist); + F32 distRemaining(F32 maxCare=1e9); + bool getPathNodeLoc(S32 ahead, Point3F& loc); + const Point3F& getSeekLoc(const VectorF& vel); + bool canReachLoc(const Point3F& dst); + void missionCycleCleanup(); + F32 visitRadius(); + void forceSearch(); + + // One-liners- + NavJetting* getJetInfo() {return &mNavJetting;} + F32 searchDist() const {return mSearchDist;} + bool isPathCurrent() const {return !mAwaitingSearch;} + void getLastPath(Vector& L) {L=mState.path;} + void setTeam(U32 whichTeam) {mTeam = whichTeam;} + void setRedoDist(F32 D=0.8f) {mRedoDistSqrd=D*D; mRedoMode=OnDist;} + void setRedoPercent(F32 P=0.15f) {mRedoPercent=P; mRedoMode=OnPercent;} + void setDestMounted(bool b) {mLocateDest.setMounted(b);} + void setSourceMounted(bool b) {mLocateHere.setMounted(b);} + void informJetBusy() {mBusyJetting = true;} +}; + +#endif diff --git a/ai/graphQueries.cc b/ai/graphQueries.cc new file mode 100644 index 0000000..992ec5d --- /dev/null +++ b/ai/graphQueries.cc @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- +// Get fast travel distance if we have a graph with a path XRef table. First method is +// static and checks for presence of graph, second does the path table walk. + +F32 NavigationGraph::fastDistance(const Point3F& p1, const Point3F& p2) +{ + if(gotOneWeCanUse()) + return gNavGraph->distancef(p1, p2); + else + { + warning("fastDistance() called without usable graph present"); + return (p1 - p2).len(); + } +} + +F32 NavigationGraph::distancef(const Point3F& p1, const Point3F& p2) +{ + // REMOVED TABLE SEARCHES + // if (mValidPathTable) { + // GraphNode * src = closestNode(p1); + // GraphNode * dst = closestNode(p2); + // Vector nodes; + // + // if (src && dst) + // if (src->island() == dst->island()) + // return walkPathTable(src, dst, nodes); + // else + // warning("distancef() attempted across separate islands"); + // else + // warning("distancef() failed to find closest node(s)"); + // } + + return (p1 - p2).len(); +} + +//------------------------------------------------------------------------------------- + +void NavigationGraph::chokePoints(const Point3F& srcPoint, Vector& points, + F32 minHideD, F32 stopSearchD) +{ + if (GraphNode * srcNode = closestNode(srcPoint)) + getLOSSearcher()->findChokePoints(srcNode, points, minHideD, stopSearchD); + else + warning("getChokePoints() failed to find a closest node"); +} + +// Static function: +S32 NavigationGraph::getChokePoints(const Point3F& srcPoint, Vector& points, + F32 minHideDist, F32 stopSearchDist) +{ + points.clear(); + + if (gotOneWeCanUse()) + if (gNavGraph->validLOSXref()) + gNavGraph->chokePoints(srcPoint, points, minHideDist, stopSearchDist); + else + warning("getChokePoints() called without valid LOS XRef table"); + else + warning("getChokePoints() called without (usable) graph in place"); + + return points.size(); +} + +//------------------------------------------------------------------------------------- + +Point3F NavigationGraph::hideOnSlope(const Point3F& from, const Point3F& avoid, F32 rad, F32 deg) +{ + const char * warnMsg = NULL; + + if (gotOneWeCanUse()) + { + if (gNavGraph->validLOSXref()) + { + GraphSearchLOS * searcher = gNavGraph->getLOSSearcher(); + return searcher->hidingPlace(from, avoid, rad, mDegToRad(90-deg), true); + } + else + warnMsg = "hideOnSlope() called without valid LOS XRef table"; + } + else + warnMsg = "hideOnSlope() called without (usable) graph in place"; + + if (warnMsg && _GRAPH_WARNINGS_) + warning(warnMsg); + + return from; +} + +//------------------------------------------------------------------------------------- + +Point3F NavigationGraph::hideOnDistance(const Point3F& from, const Point3F& avoid, F32 rad, F32 hideLen) +{ + const char * warnMsg = NULL; + + if (gotOneWeCanUse()) + { + if (gNavGraph->validLOSXref()) + { + GraphSearchLOS * searcher = gNavGraph->getLOSSearcher(); + return searcher->hidingPlace(from, avoid, rad, hideLen, false); + } + else + warnMsg = "hideOnDistance() called without valid LOS XRef table"; + } + else + warnMsg = "hideOnDistance() called without (usable) graph in place"; + + if (warnMsg && _GRAPH_WARNINGS_) + warning(warnMsg); + + return from; +} + +//------------------------------------------------------------------------------------- + +Point3F NavigationGraph::findLOSLocation(const Point3F& from, const Point3F& wantToSee, + F32 minDist, const SphereF& getCloseTo, F32 capDist) +{ + const char * warnMsg = NULL; + + if (gotOneWeCanUse()) + { + if (gNavGraph->validLOSXref()) + { + GraphSearchLOS * searcher = gNavGraph->getLOSSearcher(); + return searcher->findLOSLoc(from, wantToSee, minDist, getCloseTo, capDist); + } + else + warnMsg = "findLOSLocation() called without valid LOS XRef table"; + } + else + warnMsg = "findLOSLocation() called without (usable) graph in place"; + + if (warnMsg && _GRAPH_WARNINGS_) + warning(warnMsg); + + return from; +} + diff --git a/ai/graphRender.cc b/ai/graphRender.cc new file mode 100644 index 0000000..40cbb6d --- /dev/null +++ b/ai/graphRender.cc @@ -0,0 +1,420 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" +#include "dgl/dgl.h" +#include "Core/color.h" + +//------------------------------------------------------------------------------------- + +static bool sShowFieldEdges = false; +static bool sFilterDownEdges = false; +static S32 sShowFFTeam = 0; +static S32 sEdgesDrawn = 0; + +//------------------------------------------------------------------------------------- + +struct SortedNode +{ + GraphNode * node; + F32 dist; + SortedNode(GraphNode* n=NULL, F32 d=0.0) {node = n; dist = d;} +}; + +class SortedList : public Vector +{ + static S32 QSORT_CALLBACK cmpSortedNodes(const void* , const void* ); + public: + void init(const GraphNodeList& list, const Point3F& camLoc); + void sort(); +}; + +S32 QSORT_CALLBACK SortedList::cmpSortedNodes(const void * a,const void * b) +{ + F32 A = ((SortedNode*)a)->dist; + F32 B = ((SortedNode*)b)->dist; + return (A < B ? -1 : (A > B ? 1 : 0)); +} + +void SortedList::sort() +{ + dQsort((void* )(this->address()), this->size(), sizeof(SortedNode), cmpSortedNodes); +} + +// Set up the list with distances (squared (faster)), and sort it. +void SortedList::init(const GraphNodeList& list, const Point3F& camLoc) +{ + clear(); + for (S32 i = 0; i < list.size(); i++) + { + GraphNode * node = list[i]; + SortedNode sortNode(node, (node->location() - camLoc).lenSquared()); + push_back(sortNode); + } + sort(); +} + +//------------------------------------------------------------------------------------- + +extern void wireCube(F32 size, Point3F pos); + +static void drawNode(const Point3F& pos, const ColorF& color, U32 whichType = 0) +{ + glPointSize(whichType ? 9 : 4); + glBegin(GL_POINTS); + if (whichType) + glColor3f(1, 1, 1); + else + glColor3f(color.red, color.green, color.blue); + glVertex3f(pos.x, pos.y, pos.z); + glEnd(); +} + +static void drawNode(const Point3F& pos, U32 whichType = 0) +{ + glPointSize(whichType ? 9 : 4); + glBegin(GL_POINTS); + if (whichType) + glColor3f(1, 1, 1); + else + glColor3f(1, 0, 0); + glVertex3f(pos.x, pos.y, pos.z); + glEnd(); +} + +// Draw node size to indicate roam radius amount. Try square root relationship. +static void drawOutdoorNode(const Point3F& pos, F32 roamRad, bool highlight) +{ + F32 mapRoamRad = mSqrt(roamRad * 2.0); + S32 numPoints = mFloor(mapRoamRad) + 1; + glPointSize(numPoints); + glBegin(GL_POINTS); + if (highlight) + glColor3f(1, 1, 1); + else + glColor3f(1, 0, 0); + glVertex3f(pos.x, pos.y, pos.z); + glEnd(); +} + +static void renderEdge(const Point3F& src, const Point3F& dst, + const ColorF* col = 0, bool wide=false, bool flip = false) +{ + const Point3F& from = (flip ? dst : src); + const Point3F& to = (flip ? src : dst); + Point3F extra = (to - from); + F32 len = extra.len(); + if (len > 0.01) + { + extra *= (0.04 / len); + extra += to; + glLineWidth(wide ? 3 : 1); + glBegin(GL_LINES); + if(col) + glColor3f(col->red, col->green, col->blue); + else + glColor3f(0, 0.3, 1); + glVertex3f(from.x, from.y, from.z); + glVertex3f(extra.x, extra.y, extra.z); + glEnd(); + glLineWidth(1); + sEdgesDrawn++; + } +} + +//------------------------------------------------------------------------------------- + +static void renderSegments(Vector& segments) +{ + static Point3F morph(0.0007,0.0037,0.0013); + static Point3F a(0,0,0); + + for (S32 i = 0; i < segments.size(); i++) + { + ColorF color(mFabs(mCos(a.x)), mFabs(mCos(a.y)), mFabs(mCos(a.z))); + a += morph; + if (a.x > M_2PI) a.x -= M_2PI; + if (a.y > M_2PI) a.y -= M_2PI; + if (a.z > M_2PI) a.z -= M_2PI; + + LineSegment& seg = segments[i]; + renderEdge(seg.getEnd(0), seg.getEnd(1), &color); + } +} + +static void renderBoxes(Vector& boxLocs) +{ + for (S32 i = 0; i < boxLocs.size(); i++) { + Point3F loc = boxLocs[i]; + wireCube(0.22f, loc); + } +} + +void NavigationGraph::pushRenderSeg(const LineSegment& lineSeg) +{ + if (mRenderThese.size() < 800) + mRenderThese.push_back(lineSeg); +} + +void NavigationGraph::pushRenderBox(Point3F boxLoc, F32 zadd) +{ + if (mRenderBoxes.size() < 100) { + boxLoc.z += zadd; + mRenderBoxes.push_back(boxLoc); + } +} + +//------------------------------------------------------------------------------------- +// RENDERING + +void NavigationGraph::render(Point3F &camPos, bool) +{ + GridArea w = getWorldRect(); + + if (mNodeList.size() && gotOneWeCanUse()) + { + U32 filters = Con::getIntVariable("$GraphRenderFilter"); + sFilterDownEdges = (filters & 1); + + sShowFFTeam = Con::getIntVariable("$GraphShowFFTeam"); + + // Signal fields with flashing- + sShowFieldEdges = !(Sim::getCurrentTime() & 0x600); + + // Render the last path query, avoid transients on ends. Note we need to switch + // to something which uses a bit on the edge instead of counter on node. + for (S32 k = mTempNodeBuf.size() - 1; k >= 0; k--) + if (GraphNode * tempNode = lookupNode(mTempNodeBuf[k])) + if (!tempNode->transient()) + tempNode->setOnPath(); + + if (haveTerrain() && sDrawOutdoorNodes) + { + GridArea c = getGridRectangle(camPos, 13); + if (w.intersect(c)) + { + GraphEdge edgeBuffer[MaxOnDemandEdges]; + GraphNodeList nodesInArea; + SortedList sortedList; + + S32 numNodes = getNodesInArea(nodesInArea, w); + sortedList.init(nodesInArea, camPos); + sEdgesDrawn = 0; + + for (S32 i = 0; i < numNodes && sEdgesDrawn < sEdgeRenderMaxOutdoor; i++) + { + bool highlight; + GraphNode * node = sortedList[i].node; + if (sShowThreatened < 0) + highlight = (node->render0() || node->submerged()); + else + highlight = (node->threats() & showWhichThreats()); + Point3F nodeLoc = node->getRenderPos(); + F32 roamRad = getRoamRadius(nodeLoc); + drawOutdoorNode(nodeLoc, roamRad, highlight); + drawNeighbors(node, node->getEdges(edgeBuffer), camPos); + } + } + } + + // if (sDrawTransients) + if (sDrawOutdoorNodes || sDrawIndoorNodes) + renderTransientNodes(); + + if (sDrawIndoorNodes) + renderInteriorNodes(camPos); + + if (mRenderThese.size() > 0) { + renderSegments(mRenderThese); + if (mRenderThese.size() > 9) { + for (S32 i = mRenderThese.size() / 8; i >= 0; i--) + mRenderThese.erase_fast((gRandGen.randI()&0xFFFFFF) % mRenderThese.size()); + } + } + + if (mRenderBoxes.size() > 0) { + renderBoxes(mRenderBoxes); + if (mRenderBoxes.size() > 3) { + for (S32 i = mRenderBoxes.size() / 3; i >= 0; i--) + mRenderBoxes.erase_fast((gRandGen.randI()&0xFFFFFF) % mRenderBoxes.size()); + } + } + } +} + +#define IndoorRenderThresh 49.0 +#define MinIndoorBoxWidth 7.0 +#define MaxIndoorBoxWidth 57.0 +#define MaxIndoorRender 100 + +void NavigationGraph::renderInteriorNodes(Point3F camPos) +{ + static F32 boxW = 24; + GraphEdge edgeBuffer[MaxOnDemandEdges]; + ColorF color(0, 1, 0); + GraphNodeList renderList; + + //==> Would of course be better to clip to box in FRONT of camera... + Point3F boxOff(boxW, boxW, 111); + Box3F clipBox(camPos - boxOff, camPos + boxOff); + mIndoorTree.getIntersecting(renderList, clipBox); + S32 numNodes = renderList.size(); + + SortedList sortedList; + sortedList.init(renderList, camPos); + sEdgesDrawn = 0; + + // Resize box based on density. Note we always want to go tall though. + // if (numNodes <= MaxIndoorRender) + // boxW = getMin(MaxIndoorBoxWidth, boxW + 0.5); + // else + // boxW = getMax(MinIndoorBoxWidth, boxW - 0.5); + + for (S32 i = 0; i < numNodes && sEdgesDrawn < sEdgeRenderMaxIndoor; i++) + { + bool highlight; + GraphNode * node = sortedList[i].node; + Point3F pos = node->getRenderPos(); + GraphEdgeArray edges = node->getEdges(edgeBuffer); + if (sShowThreatened < 0) + highlight = node->render0(); + else + highlight = (node->threats() & showWhichThreats()); + drawNode(pos, color, highlight); + drawNeighbors(node, edges, camPos, getMin(boxW + 40.0, 80.0)); + } +} + +void NavigationGraph::renderTransientNodes() +{ + GraphEdge edgeBuffer[MaxOnDemandEdges]; + + for (S32 i = 0; i < mMaxTransients; i++) + if (GraphNode * node = mNodeList[mTransientStart + i]) { + Point3F pos = node->getRenderPos(); + drawNode(pos); + drawNeighbors(node, node->getEdges(edgeBuffer), pos); + } +} + +// Find the two intermediate points to connect to between node volumes. We are given +// that the edge has a border definition. +void NavigationGraph::getCrossingPoints(S32 from, Point3F* twoPoints, GraphEdge* edge) +{ + GraphBoundary & B = mBoundaries[edge->mBorder]; + twoPoints[0] = B.seekPt; + + if (GraphEdge * edgeBack = lookupNode(edge->mDest)->getEdgeTo(from)) + twoPoints[1] = mBoundaries[edgeBack->mBorder].seekPt; + else + twoPoints[1] = B.seekPt + (B.normal * (B.distIn * 2)); + + twoPoints[0].z += 0.22; + twoPoints[1].z += 0.22; +} + +static const ColorF borderColor(0.0, 0.9, 0.11); // green +static const ColorF steepColor(0.5, 0.5, 0.5); // grey +static const ColorF jettingColor(0.9, 0.9, 0.0); // yellow +static const ColorF pathColor(1.0, 1.0, 1.0); // white + +// Note that index is only used for comparison to eliminate drawing both ways. +// The transient draw routine above forces the draw by passing zero. The others +// use their proper index. +void NavigationGraph::drawNeighbors(GraphNode* fromNode, GraphEdgeArray edges, + const Point3F& camPos, F32 within) +{ + Point3F fromLoc = fromNode->getRenderPos(); + S32 fromIndex = fromNode->getIndex(); + bool isTransient = fromNode->transient(); + + within *= within; + + while (GraphEdge * edge = edges++) { + // if (edge->mDest > fromIndex || isTransient) { + if (1) { // Think we need to render both ways... + // Flash field edges- + if (edge->getTeam() && !sShowFieldEdges) + { + // Console variable $graphShowFFTeam will make it only flash those edges + // owned by the specified team- + if (!sShowFFTeam || edge->getTeam() == sShowFFTeam) + continue; + } + GraphNode * toNode = mNodeList[edge->mDest]; + Point3F toLoc = toNode->getRenderPos(), mid, low, high; + ColorF color(0.07, 0.07, 0.98); + F32 shadePct = F32(toNode->onPath()) * (1.0 / F32(GraphMaxOnPath)); + bool whitenPath = (shadePct > 0.0 && fromNode->onPath()); + bool isJetting = edge->isJetting(); + bool isSteep = edge->isSteep(); + bool isBorder = edge->isBorder(); + + if (sFilterDownEdges && (toLoc.z < fromLoc.z)) + continue; + + if (isSteep) color = steepColor; + else if (isJetting) color = jettingColor; + else if (isBorder) color = borderColor; + + if (whitenPath) { + color.red = scaleBetween(color.red, 1.0f, shadePct); + color.green = scaleBetween(color.green, 1.0f, shadePct); + color.blue = scaleBetween(color.blue, 1.0f, shadePct); + toNode->decOnPath(); + fromNode->decOnPath(); + } + + if (isJetting) { + if (sDrawJetConnections && (toLoc-camPos).lenSquared() < within) { + bool flip; + if(fromLoc.z > toLoc.z) { + flip = true; + (mid = low = toLoc).z = (high = fromLoc).z; + } + else { + flip = false; + (mid = low = fromLoc).z = (high = toLoc).z; + } + + if (edge->hasHop()) { + Point3F aboveH(high.x, high.y, high.z + edge->getHop()); + mid.z += edge->getHop(); + renderEdge(low, mid, &color, whitenPath, flip); + renderEdge(mid, aboveH, &color, whitenPath, flip); + renderEdge(aboveH, high, &color, whitenPath, flip); + } + else { + renderEdge(fromLoc, mid, &color, whitenPath); + renderEdge(mid, toLoc, &color, whitenPath); + } + } + } + else if (isBorder && mHaveVolumes) { + Point3F connectPts[3+1]; + connectPts[0] = fromLoc; + connectPts[3] = toLoc; + getCrossingPoints(fromIndex, &connectPts[1], edge); + for (S32 c = 0; c < 3;) { + Point3F end = connectPts[c++]; + Point3F start = connectPts[c]; + if (isTransient && !whitenPath) { + end.z += 0.28, start.z += 0.28; + ColorF invertColor(1-color.red, 1-color.green, 1-color.blue); + renderEdge(start, end, &invertColor, whitenPath); + } + else { + renderEdge(start, end, &color, whitenPath); + } + } + } + else { + renderEdge(fromLoc, toLoc, &color, whitenPath); + } + } + }//edge loop +} diff --git a/ai/graphSearchLOS.cc b/ai/graphSearchLOS.cc new file mode 100644 index 0000000..6f5cebb --- /dev/null +++ b/ai/graphSearchLOS.cc @@ -0,0 +1,331 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- +// Searcher + +// This "visitor" callback does much of the work for three different types of LOS-based +// queries: hiding (two types), acquiring LOS, and a "choke point" finder. +void GraphSearchLOS::onQExtraction() +{ + GraphNode * extNode = extractedNode(); + mExtractLoc = extNode->location(); + mExtractInd = extNode->getIndex(); + setOnRelax(false); + + if (mLOSTable && !extNode->transient()) { + if (mSeekingLOS) { + if (!mNeedInside || (mLOSLoc - mExtractLoc).lenSquared() < mFarDist) { + // Check for LOS if we're looking for it. Note table doesn't store LOS + // with same entry. We save the best entry relative to the search + // sphere if the user wants that. + if (mExtractInd != mLOSKey) { + if (mLOSTable->muzzleLOS(mLOSKey, mExtractInd)) { + if (mNearSphere) { + // If we're in band outside of this sphere, take it, else + // keep looking but record best one found. + if (mOuterSphere.isContained(mExtractLoc)) { + setEarlyOut(true); + setTarget(mExtractInd); + } + else { + F32 dSqrd = (mExtractLoc - mNearSphere->center).lenSquared(); + if (dSqrd < mBestDistSqrd) { + setTarget(mExtractInd); + mBestDistSqrd = dSqrd; + } + } + } + else { + // No sphere, so we take first LOS point found + setEarlyOut(true); + setTarget(mExtractInd); + } + } + } + } + } + else { // LOS Avoid (find hidden / choke point query)- + if (mExtractInd != mLOSKey) { + if (mAvoidingLOS) { + if (mLOSTable->hidden(mLOSKey, mExtractInd)) { + F32 distHidden = getSearchRef(mExtractInd)->mUser.f; + if (mDistanceBased) { + if (distHidden > mMinHidden) { + setEarlyOut(true); + setTarget(mExtractInd); + } + else { + // For hidden nodes that aren't the final solution, propogate + // distance - which happens in the relaxation virtual below... + setOnRelax(true); + if (distHidden > mBestHidden) { + mBestHidden = distHidden; + setTarget(mExtractInd); + } + } + } + else { + // Hide based on slope. Look for better than supplied minimum + // but track the best we've found. Note that small 2d lengths + // can happen indoors- we ignore as being indeterminate. + Point3F V(mExtractLoc.x-mLOSLoc.x, mExtractLoc.y-mLOSLoc.y, 0); + F32 L = V.len(); + F32 dot = (L > 0.001 ? mDot(extNode->getNormal(), V /= L) : -1); + + #ifdef _GRAPH_DEBUG_ + Point3F nodeNormal = extNode->getNormal(); + nodeNormal *= 10; + LineSegment lineSeg(mExtractLoc, mExtractLoc + nodeNormal); + mDebugSegs.push_back(lineSeg); + #endif + + if (dot > mMinSlopeDot) { + setEarlyOut(true); + setTarget(mExtractInd); + } + else if (dot > mBestSlopeDot) { + mBestSlopeDot = dot; + setTarget(mExtractInd); + } + } + } + } + else {// Choke point query + if (distSoFar() < mMaxChokeDist) { + // Choke point query. We're interested in first time obstructed nodes + // which have lots of other obstructed nodes that descend from them. + // When we extract a descendant, we see about a new best distance. + if (mLOSTable->hidden(mLOSKey, mExtractInd)) { + setOnRelax(true); + if (mHead.mPrev >= 0) { + mChokePoints.push_back(mExtractInd); + } + else { + mExtractInd = ~mHead.mPrev; // Ones compliment has its uses! + SearchRef * ancestor = getSearchRef(mExtractInd); + if (mHead.mUser.f > ancestor->mUser.f) + ancestor->mUser.f = mHead.mUser.f; + } + } + } + else { + setEarlyOut(true); + } + } + } + }//(LOS Avoid Queries) + } +} + +// Here we propogate hidden distance to neighbors that are also hidden. +void GraphSearchLOS::onRelaxEdge(const GraphEdge* edge) +{ + if (edge->mDest != mLOSKey) + { + if (mLOSTable->hidden(mLOSKey, edge->mDest)) + { + SearchRef * sref = getSearchRef(edge->mDest); + F32 hiddenDistSoFar = mHead.mUser.f; + sref->mUser.f = (hiddenDistSoFar + edge->mDist); + + // For choke point query, use mPrev field for ancestor propogation- + if (mChokePtQuery) + sref->mPrev = ~mExtractInd; + } + } +} + +U32 gGraphSearchLOSCalls = 0; + +// The choke point query requires that we filter out edges going down. We only +// do this on those down edges which have LOS to the key point since we want the +// ones that don't have LOS. +F32 GraphSearchLOS::getEdgeTime(const GraphEdge* edge) +{ + if (mChokePtQuery && edge->isJetting() && edge->isDown()) + if (edge->mDest != mLOSKey) + if (!mLOSTable->hidden(mLOSKey, edge->mDest)) + { + // Probably won't happen often, but there could be a hop down that doesn't + // wind up as a choke point (maybe partial LOS), which will result in some + // point further along the path (that goes through this down hop) being + // a choke point, and the hop down won't get filtered. + return SearchFailureAssure; + } + else + { + // We want short hops from edges to take priority over longer lateral + // hops downward. So we scale up any lateral over a few. + gGraphSearchLOSCalls++; + F32 lateral = JetManager::invertLateralMod(F32(edge->getLateral())); + F32 truncLat = getMax(lateral - 4.0f, 0.0f); + return (truncLat * truncLat); + } + + return Parent::getEdgeTime(edge); +} + +//------------------------------------------------------------------------------------- + +void GraphSearchLOS::setNullDefaults() +{ + setEarlyOut(false); + mLOSTable = NULL; + mLOSLoc.set(-1,-1,-1); + mAvoidingLOS = mSeekingLOS = mNeedInside = mChokePtQuery = false; + mDistanceBased = true; + mMinSlopeDot = -1.0; + mBestSlopeDot = -1.0; + mFarDist = 1e22; + mThreatCount = 0; + mMaxChokeDist = 500; + mBestHidden = 0.0f; + mMinHidden = 22.0f; + mNearSphere = NULL; + mDebugSegs.clear(); + setOnRelax(false); +} + +//------------------------------------------------------------------------------------- + +Point3F GraphSearchLOS::findLOSLoc(const Point3F& from, const Point3F& wantToSee, + F32 minDist, const SphereF& getCloseTo, F32 capDist) +{ + setNullDefaults(); + + if (GraphNode * losNode = gNavGraph->closestNode(wantToSee)) { + if (GraphNode * sourceNode = gNavGraph->closestNode(from)) { + + // Configure the search for LOS seeking + mLOSKey = losNode->getIndex(); + mLOSLoc = losNode->location(); + mLOSTable = gNavGraph->getLOSXref(); + mNeedInside = (mFarDist = capDist) < 1e6; + mFarDist *= mFarDist; + (mOuterSphere = * (mNearSphere = & getCloseTo)).radius += (mNearWidth = 10); + mBestDistSqrd = 1e13; + mSeekingLOS = true; + + gNavGraph->threats()->pushTempThreat(losNode, wantToSee, minDist); + S32 iters = GraphSearch::performSearch(sourceNode, NULL); + gNavGraph->threats()->popTempThreat(); + + if (getTarget() >= 0) { + getPathIndices(gNavGraph->tempNodeBuff()); + return gNavGraph->lookupNode(getTarget())->location(); + } + } + } + return from; +} + +//------------------------------------------------------------------------------------- + +Point3F GraphSearchLOS::hidingPlace(const Point3F& from, const Point3F& avoid, + F32 avoidRad, F32 basisForAvoidance, bool avoidOnSlope) +{ + if (GraphNode * losNode = gNavGraph->closestNode(avoid)) { + if (GraphNode * sourceNode = gNavGraph->closestNode(from)) { + + // Clear out search machinery and set up what we want to do + setNullDefaults(); + + mLOSKey = losNode->getIndex(); + mLOSLoc = losNode->location(); + mLOSTable = gNavGraph->getLOSXref(); + mAvoidingLOS = true; + + // Can avoid on distance (beyond first hidden point), or on slope. + if (avoidOnSlope) { + mDistanceBased = false; + mMinSlopeDot = mCos(basisForAvoidance); + } + else { + mDistanceBased = true; + mMinHidden = basisForAvoidance; + } + + // Run the search with avoid center temporarily affected. + gNavGraph->threats()->pushTempThreat(losNode, avoid, avoidRad); + S32 iters = GraphSearch::performSearch(sourceNode, NULL); + gNavGraph->threats()->popTempThreat(); + + // Return if something found- + if (getTarget() >= 0) { + getPathIndices(gNavGraph->tempNodeBuff()); // (debug rendering) + return gNavGraph->lookupNode(getTarget())->location(); + } + } + } + else { + #if _GRAPH_WARNINGS_ + NavigationGraph::warning("hidingPlace() didn't find what it wanted"); + #endif + } + return from; +} + +//------------------------------------------------------------------------------------- + +// Special purpose search which makes use of mPrev field in SearchRef to +// accomplish what it wants- relies on that field not being needed because we're +// not finding a path. A choke point is a point just barely out of LOS from a given +// source, but beyond which there exists a long unbroken hidden string of nodes. This +// condition keeps the bots from finding choke points around poles or into alcoves. +S32 GraphSearchLOS::findChokePoints(GraphNode* S, Vector& points, + F32 minHideDist, F32 maxSearchDist) +{ + setNullDefaults(); + points.clear(); + if ((mLOSTable = gNavGraph->getLOSXref()) != NULL) + { + // Configure the machinery- + mLOSKey = S->getIndex(); + mLOSLoc = S->location(); + mMaxChokeDist = (maxSearchDist + minHideDist); + mMinHidden = minHideDist; + mChokePoints.clear(); + mChokePtQuery = true; + + // Do the work- + GraphSearch::performSearch(S, NULL); + + GraphNodeList downList; + for (S32 i = 0; i < mChokePoints.size(); i++) + { + S32 nodeInd = mChokePoints[i]; + SearchRef * searchRef = getSearchRef(nodeInd); + if (searchRef->mUser.f > minHideDist) + { + // We filter out those nodes which are down a jet connection EXCEPT that if + // very few choke points have been found, then we may use some of the nodes + // that are the top of such jet connections, so these are saved in downList + S32 beforeIndex = lookupSearchRef(searchRef->mPrev)->mIndex; + GraphNode * beforeNode = gNavGraph->lookupNode(beforeIndex); + GraphEdge * edgeTo = beforeNode->getEdgeTo(nodeInd); + + if (edgeTo->isJetting() && edgeTo->isDown()) + downList.addUnique(beforeNode); + else + points.push_back(gNavGraph->lookupNode(nodeInd)->location()); + } + } + + // If not very many points were found, then use some of the downList of locations + // that are just prior to a downward jet connection (to a hidden choke point). + // ===> Might want to select a random element of downList per iteration here... + while (downList.size() && points.size() < 4) + { + points.push_back(downList.last()->location()); + downList.pop_back(); + } + } + return points.size(); +} diff --git a/ai/graphSearches.h b/ai/graphSearches.h new file mode 100644 index 0000000..27f424e --- /dev/null +++ b/ai/graphSearches.h @@ -0,0 +1,170 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHSEARCHES_H_ +#define _GRAPHSEARCHES_H_ + +#ifndef _TBINHEAP_H_ +#include "ai/tBinHeap.h" +#endif + +#define SearchFailureThresh 1e17 +#define SearchFailureAssure 1e19 + +struct SearchRef +{ + SearchRef(S32 x, F32 d, F32 t) {mIndex=x; mDist=d; mSort=t; mPrev=-1;} + union UserData {F32 f; U32 u; S32 s; UserData() {f=0;} }; + S32 operator<(const SearchRef& sr2) {return mSort > sr2.mSort;} + S16 mIndex; + S16 mPrev; + F32 mDist; + UserData mUser; + F32 mTime; + F32 mSort; +}; + +typedef Vector QIndexList; +typedef BinHeap GraphQueue; + +class GraphSearch // graphDijkstra.cc +{ + GraphQueue & mQueue; + QIndexList & mQIndices; + Vector & mHeuristicsVec; + GraphPartition & mPartition; + // ^^^^ Shared lists ^^^^ + + Point3F mTargetLoc; + F32 * mHeuristicsPtr; + S32 mTargetNode, mSourceNode; + F32 mSearchDist; + bool mInProgress; + S32 mIterations; + S32 mRelaxCount; + S32 mTransientStart; + U32 mCurrentSimTime; + U32 mTeam, mInformTeam; + const F32 * mInformRatings, * mJetRatings; + GraphThreatSet mThreatSet, mInformThreats; + GraphEdge mEdgeBuffer[MaxOnDemandEdges]; + bool mVisitOnExtract, mVisitOnRelax; + bool mEarlyOut, mAStar, mInformAStar, mRandomize; + + bool initializeIndices(); + void doSmallReset(); + void doLargeReset(); + void resetIndices(); + SearchRef * insertSearchRef(S32 idx, F32 dist, F32 time); + S32 getAvoidFactor(); + F32 calcHeuristic(const GraphEdge* to); + void initSearch(GraphNode * S, GraphNode * D = NULL); + bool runDijkstra(); + + protected: + GraphNode * mExtractNode; + SearchRef mHead; + void setDone() {mInProgress=false;} + F32 distSoFar() const {return mHead.mDist;} + F32 timeSoFar() const {return mHead.mTime;} + GraphNode * extractedNode() const {return mExtractNode;} + SearchRef * getSearchRef(S32 nodeInd); + SearchRef * lookupSearchRef(S32 qInd); + virtual void onQExtraction() {mVisitOnExtract = false;} + virtual void onRelaxEdge(const GraphEdge*) {mVisitOnRelax = false;} + virtual bool earlyOut() {return (mEarlyOut=false);} + virtual F32 getEdgeTime(const GraphEdge* e); + + public: + GraphSearch(); + F32 doTableSearch(GraphNode* S, GraphNode* D, Vector& nodes); + S32 performSearch(GraphNode* S, GraphNode* D = 0); + bool getPathIndices(Vector& nodeIndexList, S32 target = -1); + bool runSearch(GraphNode* S, GraphNode* D = 0); + void markVisited(BitVector& emptyVec); + void setThreats(GraphThreatSet T) {mInformThreats = T;} + void setRatings(const F32* r) {mInformRatings = r;} + void setTeam(U32 team) {mInformTeam = team;} + const GraphPartition& getPartition() const {return mPartition;} + bool inProgress() const {return mInProgress;} + F32 searchDist() const {return mSearchDist;} + S32 getTarget() const {return mTargetNode;} + S32 relaxCount() const {return mRelaxCount;} + void setRandomize(bool b) {mRandomize = b;} + void setTarget(S32 T) {mTargetNode = T;} + void setEarlyOut(bool b) {mEarlyOut = b;} + void setOnRelax(bool b) {mVisitOnRelax = b;} + void setAStar(bool b = 1) {mInformAStar = b;} + class Globals { + friend class GraphSearch; + GraphQueue searchQ; + QIndexList qIndices; + Vector heuristics; + GraphPartition partition; + }; +}; + +class GraphSearchLOS : public GraphSearch // graphSearchLOS.cc +{ + typedef GraphSearch Parent; + const SearchThreat * * mConsider; + const LOSTable * mLOSTable; + const SphereF* mNearSphere; + Vector mDebugSegs; + S32 mThreatCount; + S32 mLOSKey, mExtractInd; + Point3F mExtractLoc, mLOSLoc; + SphereF mOuterSphere; + Vector mChokePoints; + F32 mNearWidth, mFarDist, mMaxChokeDist; + F32 mBestDistSqrd, mBestHidden, mMinHidden; + F32 mMinSlopeDot, mBestSlopeDot; + bool mSeekingLOS, mAvoidingLOS; + bool mDistanceBased; + bool mChokePtQuery; + bool mNeedInside; + void setNullDefaults(); + protected: + void onQExtraction(); + void onRelaxEdge(const GraphEdge*); + F32 getEdgeTime(const GraphEdge* e); + bool earlyOut() {return true;} + + public: + S32 performSearch(GraphNode* S, GraphNode* D, const SearchThreats& T); + S32 findChokePoints(GraphNode* S, Vector& pts, F32 hideD, F32 maxD); + Point3F hidingPlace(const Point3F& from, const Point3F& avoid, + F32 avoidRad, F32 avoidBasis, bool avoidOnSlope); + Point3F findLOSLoc(const Point3F& from, const Point3F& wantToSee, F32 minDist, + const SphereF& getCloseTo, F32 capDist); +}; + +class GraphSearchDist : public GraphSearch // graphLocate.cc +{ + GraphNode * mBestMatch; + NodeProximity mBestMetric; + Point3F mPoint; + ProximateList mProximate; + F32 mCurThreshold; + F32 mMaxSearchDist; + bool mSearchOnDepth; + + protected: + F32 getEdgeTime(const GraphEdge* edge); + void onQExtraction(); + + public: + GraphSearchDist(); + NodeProximity findBestMatch(GraphNode* cur, const Point3F& loc); + GraphNode * bestMatch() const {return mBestMatch;} + void setOnDepth(bool b) {mSearchOnDepth = b;} + bool earlyOut() {return true;} + ProximateList& getProximate() {return mProximate;} + void setDistCap(F32 d); +}; + +#endif diff --git a/ai/graphSmooth.cc b/ai/graphSmooth.cc new file mode 100644 index 0000000..25e24d3 --- /dev/null +++ b/ai/graphSmooth.cc @@ -0,0 +1,270 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- + +#ifdef _GRAPH_DEBUG_ +static void debugDrawBorderSeg(const GraphBoundary& border); +static void debugDrawParaNormal(const Point3F& midpoint, VectorF paraNormal); +static void debugDrawSeekVector(Point3F curLoc, Point3F seekLoc); +#else +#define debugDrawBorderSeg(border) +#define debugDrawParaNormal(midpoint,paranormal) +#define debugDrawSeekVector(curloc,seekloc) +#endif + +//------------------------------------------------------------------------------------- + +// define SegCheck3D (2.0f) +#define SegCheck3D (4.0f) +#define SegCheck2D (0.5f) + +#define RoomNeeded (0.5f) +#define InsideThreshSquared (RoomNeeded * RoomNeeded) + +// User has to call this this first- +bool NavigationGraph::useVolumeTraverse(const GraphNode* from, const GraphEdge* to) +{ + if (mHaveVolumes) + if (from->canHaveBorders()) // i.e (indoor | transient) + if (from->volumeIndex() >= 0) + if (to && !to->isJetting() && to->isBorder()) + return true; + return false; +} + +// +// See if line from currLoc to seekLoc can pass through the border. +// +// Ok, I'm beginning to see the wisdom of pursuing a BSP-based approach to the +// NAV graphs. We're doing a lot of work here and still not getting nearly enough +// free-form movement as could/should be had. +// +//==> This should probably account for different bot sizes. +// +static bool canFitThrough(const Point3F& currLoc, const Point3F& seekLoc, + const GraphBoundary& border) +{ + // We rely on fact that boundary normal is parallel to ground, and just do 2D math + // here. The source and destination locations should be on opposite sides. These + // dot products then also give us an interpolation value to find the crossing point. + VectorF seek2D = seekLoc, curr2D = currLoc; + (seek2D -= border.seg[0]).z = 0; + (curr2D -= border.seg[0]).z = 0; + + F32 seekDot = mDot(seek2D, border.normal); + F32 currDot = mDot(curr2D, border.normal); + + if (seekDot > 0.03 && currDot < -0.03) + { + F32 interp = -currDot / (seekDot - currDot); + Point3F crossPt = scaleBetween(currLoc, seekLoc, interp); + + // Want to make sure the crossing point is well inside. We've done plenty of + // math already, let's avoid square roots and just check 2 sufficient conditions: + // Dist from endpoints is enough, vectors to endpoints point away from each other + VectorF from0 = crossPt, from1 = crossPt; + (from0 -= border.seg[0]).z = 0; + (from1 -= border.seg[1]).z = 0; + + if (from0.lenSquared() > InsideThreshSquared) + if (from1.lenSquared() > InsideThreshSquared) + if (mDot(from0, from1) < -0.9) + return true; + } + + return false; +} + +//------------------------------------------------------------------------------------- + +bool NavigationGraph::volumeTraverse(const GraphNode* from, const GraphEdge* to, + NavigationPath::State & S) +{ + bool canAdvance = false; + const GraphBoundary & border = mBoundaries[to->mBorder]; + Point3F midpoint = (border.seg[0] + border.seg[1]) * 0.5f; + BitSet32 & visit = S.visit[S.curSeekNode-1]; + + debugDrawBorderSeg(border); + + // See if the node advancer below skipped this fromNode. + if (visit.test(NavigationPath::State::Skipped)) + { + // If so then we just want to get on the other side of it. + F32 dot = mDot(S.hereLoc - border.seg[0], border.normal); + if (dot > 0.05) + canAdvance = true; + } + else + { + if (inNodeVolume(from, S.hereLoc)) + { + visit.set(NavigationPath::State::Visited); + + // Get "line" to check proximity to (just use a really large segment) + // When near, head across, else seek the proper point. + Point3F paraNormal = (border.normal * 33.0); + LineSegment crossingLine(midpoint - paraNormal, midpoint + paraNormal); + + debugDrawParaNormal(midpoint, paraNormal); + + // This state was flipping on some small nodes near walls - should work to + // just only head out once in this case. + if (!visit.test(NavigationPath::State::Outbound)) + { + if (crossingLine.botDistCheck(S.hereLoc, SegCheck3D, SegCheck2D)) + visit.set(NavigationPath::State::Outbound); + else + S.seekLoc = border.seekPt; + } + + if (visit.test(NavigationPath::State::Outbound)) + S.seekLoc = midpoint + (border.normal * 2.0); + } + else + { + // The source node has been visited, but we're in the next node and want to + // decide if we can begin tracking next segment (if that's what's next). + if (visit.test(NavigationPath::State::Visited)) + { + LineSegment borderSeg(border.seg[0], border.seg[1]); + + //==> THIS CHECK NEEDS TO LOOK AT SEGMENT WHEN LARGE (use projected pulled in loc on seg) + // We could modifiy this pull-in a little based on player distance, and it would + // then traverse a little differently. + + // This has a problem in a certain case- they might not be able to achieve getting + // inside the volume for narrow ones. Need to handle being here for a couple of + // frames, plus we should make the approach to the border be better - based + // on where you want to go. + if (!borderSeg.botDistCheck(S.hereLoc, SegCheck3D, 0.2)) + { + // When the next edge is either jetting or laid walk connection, then + // we need to actually go to the node location before advancing. + if (S.nextEdgePrecise) + { + S.seekLoc = lookupNode(to->mDest)->location(); + if (within_2D(S.hereLoc, S.seekLoc, 0.3)) + canAdvance = true; + } + else + { + mRenderThese.clear(); + canAdvance = true; + } + } + else + { + S.seekLoc = (midpoint + (border.normal * 2.0)); + } + } + else + { + Point3F inVec = (border.seg[1] - border.seg[0]); + F32 len = inVec.len(); + + AssertFatal(len > 0.01, "There is a way-too-short border segment"); + + inVec.normalize(len > 1.2 ? 0.4 : len / 3); // get 'pulled in' segment + + LineSegment borderSeg(border.seg[0]+inVec, border.seg[1]-inVec); + + if (borderSeg.botDistCheck(S.hereLoc, SegCheck3D, SegCheck2D)) + { + mRenderThese.clear(); + canAdvance = true; + } + else + { + S.seekLoc = from->location(); + } + } + } + } + + debugDrawSeekVector(S.hereLoc, S.seekLoc); + + return canAdvance; +} + +// This just tries to do further skips from the current location. The above code +// performs advancement in the case of skipped nodes. Like the outdoor advance, +// we only attempt one at a time, but unlike it the advancement doesn't happen +// right away. +S32 NavigationGraph::checkIndoorSkip(NavigationPath::State & S) +{ + S32 startingFrom = (S.curSeekNode - 1); + S32 numberSkipped = 0; + + if (!S.visit[startingFrom].test(NavigationPath::State::Skipped)) + { + // See if we can skip ahead through outbound segments. + for (S32 from = startingFrom; from < S.edges.size() - 1; from++) + { + GraphEdge * fromOut = S.edges[from]; + GraphEdge * toOut = S.edges[from+1]; + + if (fromOut->isBorder() && toOut->isBorder()) + { + AssertFatal(!fromOut->isJetting() && !toOut->isJetting(), "Indoor skip"); + + const GraphBoundary & toBorder = mBoundaries[toOut->mBorder]; + const GraphBoundary & fromBorder = mBoundaries[fromOut->mBorder]; + + // See if segment to the TO outbound place passes Ok through FROM outbound + if (canFitThrough(S.hereLoc, toBorder.seekPt, fromBorder)) + { + // The first from has already been visited.... + if (from > startingFrom) + { + S.seekLoc = toBorder.seekPt; + S.visit[from].set(NavigationPath::State::Skipped); + numberSkipped++; + continue; + } + } + } + break; + } + } + return numberSkipped; +} + +//------------------------------------------------------------------------------------- + +#ifdef _GRAPH_DEBUG_ +static void debugDrawBorderSeg(const GraphBoundary& border) +{ + Point3F bseg1(border.seg[0]); + Point3F bseg2(border.seg[1]); + bseg1.z += 1.0f; + bseg2.z += 1.0f; + LineSegment borderSeg(bseg1, bseg2); + gNavGraph->pushRenderSeg(borderSeg); +} +static void debugDrawParaNormal(const Point3F& midpoint, VectorF paraNormal) +{ + paraNormal.normalize(4.0); + Point3F p1(midpoint - paraNormal); + Point3F p2(midpoint + paraNormal); + p1.z += 1.4f; + p2.z += 1.4f; + LineSegment paraNormalSeg(p1, p2); + gNavGraph->pushRenderSeg(paraNormalSeg); +} +static void debugDrawSeekVector(Point3F curLoc, Point3F seekLoc) +{ + curLoc.z += 1.8f; + seekLoc.z += 1.8f; + LineSegment seekVecSeg(curLoc, seekLoc); + gNavGraph->pushRenderSeg(seekVecSeg); +} +#endif + diff --git a/ai/graphSpawn.cc b/ai/graphSpawn.cc new file mode 100644 index 0000000..607902d --- /dev/null +++ b/ai/graphSpawn.cc @@ -0,0 +1,372 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" +#include "game/missionMarker.h" + +//------------------------------------------------------------------------------------- + +// Utility routine for below- Get nodes in sphere, separated into indoor/outdoor +void NavigationGraph::getInSphere(const SphereF& S, GraphNodeList& I, GraphNodeList& O) +{ + // Find all the candidate nodes. + for (S32 i = 0; i < numNodes(); i++) + if (GraphNode * node = lookupNode(i)) + if (S.isContained(node->location())) + (node->outdoor() ? O : I).push_back(node); +} + +static void findSpawnSpheres(Vector & list) +{ + for (SimSetIterator itr(Sim::getRootGroup()); *itr; ++itr) + if (SpawnSphere * ss = dynamic_cast(*itr)) + if (!ss->isClientObject()) + for (S32 i = list.size() - 1; i > -2; i--) // Check if it's already in our + if (i < 0) // list since the spawn spheres + list.push_back(ss); // are kept in a couple of sets + else if (ss == list[i]) + break; +} + +// Build the spawn data out from the node list. We're basically making a pre- +// randomized list per spawn sphere so the spawn lookups happen fast, plus +// we control the amount of data we're using for this system. +void NavigationGraph::makeSpawnList() +{ + mSpawnList.reset(); + Vector spawnSpheres; + findSpawnSpheres(spawnSpheres); + if (spawnSpheres.size() == 0) + { + Con::printf("Warning! No spawn sphere markers found in mission file!"); + return; + } + + // For holding lists for each sphere- + GraphNodeList indoor; + GraphNodeList outdoor; + indoor.reserve(mNumIndoor); + outdoor.reserve(mNumOutdoor); + + // List to build- + SpawnLocations spawnList; + + // Build our list of spheres for indoor / outdoor. + for (S32 s = 0; s < spawnSpheres.size(); s++) + { + outdoor.clear(); + indoor.clear(); + + // Get the lists- + SpawnSphere * ss = spawnSpheres[s]; + SpawnLocations::Sphere sphereList(ss->getPosition(), ss->mRadius); + getInSphere(sphereList.mSphere, indoor, outdoor); + + // Add spheres for outdoor and indoor if wanted and available. + if (ss->mIndoorWeight) { + if (indoor.size()) { + sphereList.mInside = true; + spawnList.mSpheres.push_back(sphereList); + } + else + Con::printf("Warning- Spawn sphere with no INDOOR nodes found!"); + } + if (ss->mOutdoorWeight) { + if (outdoor.size()) { + sphereList.mInside = false; + spawnList.mSpheres.push_back(sphereList); + } + else + Con::printf("Warning- Spawn sphere with no OUTDOOR nodes found!"); + } + } + + // Figure out max budget per spawn sphere. Try to keep it to 300 total, but each one + // should have at least 20. + S32 maxPer = 50; + if (spawnList.mSpheres.size() > 1) + maxPer = getMin(maxPer, 300 / spawnList.mSpheres.size()); + maxPer = getMax(20, maxPer); + + // Create pre-randomized location list for each sphere. + for (S32 i = 0; i < spawnList.mSpheres.size(); i++) + { + SpawnLocations::Sphere & sphereList = spawnList.mSpheres[i]; + outdoor.clear(); + indoor.clear(); + getInSphere(sphereList.mSphere, indoor, outdoor); + + S32 maxPointsHere = maxPer; + + // Get the list of points. If it's indoor and the # of nodes is smaller + // than the budget, then just scan all of them. Otherwise ... randomize. + sphereList.mOffset = spawnList.size(); + if (sphereList.mInside && indoor.size() < maxPointsHere) + { + for (S32 j = 0; j < indoor.size(); j++) + { + GraphNode * node = indoor[j]; + Point3F point = node->location(); + if (sanctionSpawnLoc(node, point)) + spawnList.push_back(point); + } + } + else + { + GraphNodeList & nodeList = (sphereList.mInside ? indoor : outdoor); + U32 nodeListSize = nodeList.size(); + S32 numPushed = 0; + + // Go for a while longer in case a lot are filtered- + for (S32 j = 0; j < (maxPointsHere * 10) && numPushed < maxPointsHere; j++) + { + U32 randIndex = (gRandGen.randI() % nodeListSize); + GraphNode * node = nodeList[randIndex]; + Point3F point = node->randomLoc(); + + if (sanctionSpawnLoc(node, point)) + { + spawnList.push_back(point); + numPushed++; + } + } + } + + // Set how many actually got added- + sphereList.mCount = (spawnList.size() - sphereList.mOffset); + + // Hopefully some got added- + if (sphereList.mCount == 0) + Con::printf("Warning: Spawn sphere didn't have any valid nodes within it."); + } + + // List is made, copy it over- + mSpawnList = spawnList; +} + +//------------------------------------------------------------------------------------- +// Spawn node functions. Old system operates if there's a NAV, else the new one +// if it's just a spawn graph. + +const Point3F * NavigationGraph::getSpawnLoc(S32 nodeIndex) +{ + if (mSpawnList.empty()) + { + if (validArrayIndex(nodeIndex, numNodes())) + if (GraphNode * node = lookupNode(nodeIndex)) + return & node->location(); + } + else + { + if (validArrayIndex(nodeIndex, mSpawnList.size())) + return & mSpawnList[nodeIndex]; + } + return NULL; +} + +const Point3F * NavigationGraph::getRandSpawnLoc(S32 nodeIndex) +{ + if (mSpawnList.empty()) + { + if (validArrayIndex(nodeIndex, numNodes())) + if (GraphNode * node = lookupNode(nodeIndex)) + { + static Point3F sPoint; + sPoint = node->randomLoc(); + return & sPoint; + } + } + else + { + if (validArrayIndex(nodeIndex, mSpawnList.size())) + return & mSpawnList[nodeIndex]; + } + return NULL; +} + +//------------------------------------------------------------------------------------- + +#define CastShift 6 +#define NumCasts (1 << CastShift) +#define MaskOff (NumCasts - 1) +#define Periphery (NumCasts / 4 + 1) +#define AngleInc ((M_PI * 2.0) / F32(NumCasts)) +#define CapDistance 60.0 + +// Do NumCasts LOS casts and save the distances (squared) and angles. Return minimum +// of the dists array. Used by the spawn function to find where to look, and by the +// code that pre-generates the random spawn locations. +static F32 lookAround(Point3F P, F32 dists[NumCasts], F32 qAngles[NumCasts]) +{ + F32 minDist = (CapDistance * CapDistance); + RayInfo coll; + + // Maybe get eye-height here, though midsection level would work better for + // guaranteeing a nicer view, in most cases. + P.z += 1.0; + + // Gather distances. + for (S32 i = 0; i < NumCasts; i++) + { + // Get quat angle, and do 90 degree conversion + F32 ang = F32(i) * AngleInc; + qAngles[i] = ang; + ang += (M_PI / 2.0); + + // Get Direction vector- convert to Dest vector. + VectorF D(mCos(ang), mSin(ang), 0); + D *= CapDistance; + D += P; + + // Find distances squared, and the min- + // const U32 mask = (InteriorObjectType|StaticShapeObjectType|TerrainObjectType); + if (gServerContainer.castRay(P, D, U32(-1), &coll)) + minDist = getMin( dists[i] = (coll.point -= P).lenSquared(), minDist ); + else + dists[i] = (CapDistance * CapDistance); + } + + return minDist; +} + +// This really isn't a graph function per se, but since this is affiliated with the +// spawn graph stuff, might as well go in the graph code. +F32 NavigationGraph::whereToLook(Point3F P) +{ + F32 dists[NumCasts]; + F32 qAngles[NumCasts]; + + // Do circle of LOS casts- + lookAround(P, dists, qAngles); + + // Now find out what looks the best. Maximize difference of squares form forward + // to side. Note that side angle is a little more than 90 deg so that we angle + // away from wall a little bit. + S32 bestIndex = 0; + F32 bestMetric = -1e13, sideDist, metric; + const U32 wrap = (NumCasts - 1); + for (U32 j = 0; j < NumCasts; j++) + { + // Take smaller off two off to the side distances- + sideDist = getMin(dists[(j - Periphery) & wrap], dists[(j + Periphery) & wrap]); + + // Metric is (forward dist squared) - ((smaller) side distance squared) + if ((metric = (dists[j] - sideDist)) > bestMetric) + bestMetric = metric, bestIndex = j; + } + + // Return the angle need for get/set transform. + return qAngles[bestIndex]; +} + +//------------------------------------------------------------------------------------- + +#define SpawnSlopeLimit 0.866 // cos 30 + +static bool checkSlopeBelow(const Point3F& point) +{ + RayInfo coll; + Point3F below(point.x, point.y, point.z - 10.0); + + if (gServerContainer.castRay(point, below, U32(-1), &coll)) + if (coll.normal.z > SpawnSlopeLimit) + return true; + + return false; +} + +// A spawn location is only generated if, looking from waist out in a circle, no +// collisions found within these thresholds. +#define IndoorSpawnExtraDist 1.8 +#define OutdoorSpawnExtraDist 7.2 + +bool NavigationGraph::sanctionSpawnLoc(GraphNode * node, const Point3F& point) const +{ + if (!node->liquidZone() && !node->inventory() && checkSlopeBelow(point)) + { + F32 dists[NumCasts]; + F32 angles[NumCasts]; + F32 thresh = (node->indoor() ? IndoorSpawnExtraDist : OutdoorSpawnExtraDist); + F32 minDistSqrd = lookAround(point, dists, angles); + return minDistSqrd > (thresh * thresh); + } + return false; +} + +//------------------------------------------------------------------------------------- + +// The parameters are from randI(), make a 64 bit return value in range [0,1] +static F64 getRandom64(U64 r1, U64 r2) +{ + r1 <<= 31; + r1 |= r2; + F64 frand = F64(r1); + F64 fnorm = frand * (1.0 / 4611686018427387904.0); + return fnorm; +} + +S32 NavigationGraph::randNode(const Point3F& P, F32 R, bool indoor, bool outdoor) +{ + SphereF sphere(P, R); + + // If there's a valid spawn list, that's what we use. This will occcur when + // it's a SPN file, or a NAV file from which only the spawn data was loaded. + if (!mSpawnList.empty()) + { + return mSpawnList.getRandom(sphere, indoor, gRandGen.randI()); + } + + // Use graph utility lists to save on realloc calls. + mUtilityNodeList1.clear(); + mUtilityNodeList2.clear(); + + // Note terrain should be done first since getNodesInArea() clears list... + if (outdoor && haveTerrain()) + { + S32 gridRad = S32(R / gNavGlobs.mSquareWidth) + 1; + GridArea gridArea = getGridRectangle(P, gridRad); + getNodesInArea(mUtilityNodeList1, gridArea); + } + + // ... and indoor second since getIntersecting() appends to list. + if (indoor) + { + Box3F wBox(P, P, true); + Point3F ext(R, R, R); + wBox.min -= ext; + wBox.max += ext; + mIndoorTree.getIntersecting(mUtilityNodeList1, wBox); + } + + S32 count = mUtilityNodeList2.searchSphere(sphere, mUtilityNodeList1); + + #if 1 + if (count > 0) + return mUtilityNodeList2[gRandGen.randI() % count]->getIndex(); + #else + // Stuff to do weighted average- + // Get total area, along with a cutout point that should give us a + // weighted random of the nodes based on their estimated area- + F64 cutoffArea = 0.0f; + for (S32 pass = 1; pass <= 2; pass++) + { + F64 totalArea = 0.0f; + for (S32 i = 0; i < count; i++) + { + F64 R = insideSphere[i]->radius(); + F64 area = (R * R); + totalArea += area; + if (pass == 2 && totalArea >= cutoffArea) + return insideSphere[i]->getIndex(); + } + if (pass == 1) + cutoffArea = totalArea * getRandom64(gRandGen.randI(), gRandGen.randI()); + } + #endif + + return -1; +} diff --git a/ai/graphThreats.cc b/ai/graphThreats.cc new file mode 100644 index 0000000..696d582 --- /dev/null +++ b/ai/graphThreats.cc @@ -0,0 +1,245 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- + +#define ThreatCheckFrequency 1.0f +#define ThreatCheckDelay U32( 1000.0f / ThreatCheckFrequency ) + +//------------------------------------------------------------------------------------- +// Search Threat Object + +SearchThreat::SearchThreat() +{ + mActive = false; + mTeam = 0; +} + +SearchThreat::SearchThreat(ShapeBase * threat, F32 R, S32 team) +{ + mThreat = threat; + MatrixF const& threatTransform = threat->getTransform(); + threatTransform.getColumn(3, ¢er); + radius = R; + mActive = (threat->getDamageState() == ShapeBase::Enabled); + mTeam = team; +} + +// Update the threat bit set on the nodes that this threat effects. Want it to +// be fast, so the two loops are separate. +void SearchThreat::updateNodes() +{ + GraphNodeList::iterator n = mEffects.begin(); + GraphThreatSet threatSetOn = 1; + GraphThreatSet threatSetOff = ~(threatSetOn <<= mSlot); + S32 count = mEffects.size(); + + if (mActive) + while( --count >= 0 ) + (* n++)->threats() |= threatSetOn; + else + while( --count >= 0 ) + (* n++)->threats() &= threatSetOff; +} + +// Check for changes to team or enabled status. If the enabled status changes, then +// bunch of edges change - let caller know there was a big change. +bool SearchThreat::checkEnableChange() +{ + bool enabled = (mThreat->getDamageState() == ShapeBase::Enabled); + if (enabled != mActive) { + mActive = enabled; + updateNodes(); + return true; + } + return false; +} + +//------------------------------------------------------------------------------------- +// Threat List Management / Configuration + +SearchThreats::SearchThreats() +{ + dMemset(mTeamCaresAbout, 0, sizeof(mTeamCaresAbout)); + + // Team zero is reserved for threats temporarily installed for the purpose of the + // LOS queries. (These queries temporarily flag edges, and then unflags them). + // All teams "worry" about threat zero. + mCheck = mCount = 1; + informOtherTeams(-1, 0, true); + mThreats[0].mActive = false; + + mSaveTimeMS = Sim::getCurrentTime(); +} + +// Inform all other teams of this threat's active status (update their care-about set). +void SearchThreats::informOtherTeams(S32 thisTeam, S32 thisThreat, bool isOn) +{ + GraphThreatSet threatSet = 1; + threatSet <<= thisThreat; + for (S32 i = 0; i < GraphMaxTeams; i++) + if (i != thisTeam && isOn) + mTeamCaresAbout[i] |= threatSet; + else + mTeamCaresAbout[i] &= ~threatSet; +} + +GraphThreatSet SearchThreats::getThreatSet(S32 team) const +{ + AssertFatal(validArrayIndex(team, GraphMaxTeams), "Bad team # to getThreatSet()"); + return mTeamCaresAbout[team]; +} + +void SearchThreats::pushTempThreat(GraphNode* losNode, const Point3F& avoidPt, F32 avoidRad) +{ + AssertFatal(mThreats[0].mEffects.size() == 0, "Temp threat push not balanced"); + mThreats[0].mEffects = gNavGraph->getVisibleNodes(losNode, avoidPt, avoidRad); + mThreats[0].mActive = true; + mThreats[0].updateNodes(); +} + +void SearchThreats::popTempThreat() +{ + mThreats[0].mActive = false; + mThreats[0].updateNodes(); + mThreats[0].mEffects.clear(); +} + +// Add the threat. This is called once per MIS file threat at mission startup. +bool SearchThreats::add(const SearchThreat& threat) +{ + if (mCount < MaxThreats) { + S32 X = mCount++; + mThreats[X] = threat; + informOtherTeams(threat.mTeam, mThreats[X].mSlot = X, true); + mThreats[X].mEffects = gNavGraph->getVisibleNodes(threat.center, threat.radius); + mThreats[X].updateNodes(); + return true; + } + return false; +} + +// Fetch into autoclass buffer, meant for immediate use. +S32 SearchThreats::getThreatPtrs(const SearchThreat* list[MaxThreats], GraphThreatSet mask) const +{ + S32 numActive = 0; + GraphThreatSet bit = 1; + + for (S32 i = 0; i < mCount; i++, bit <<= 1) + if ((mask & bit) && mThreats[i].mActive) + list[numActive++] = &mThreats[i]; + + return numActive; +} + +// This is called every tick to efficiently keep the list clean of threats +// that have gone away, or to monitor status of the threat. +// +// We return true if we can afford to do some more stuff and we're not at list's +// end. We can't afford to do more stuff if some threat had to be rescanned. +bool SearchThreats::checkOne() +{ + bool canCheckMore = true; + + if (mCheck < mCount) { + if (!bool(mThreats[mCheck].mThreat)) { + // AssertFatal(0, "Can static threats go away?"); + // if (mCheck < --mCount) // fast erase + // mThreats[mCheck] = mThreats[mCount]; + // mThreats[mCount].mThreat = NULL; + // mThreats[mCount].mActive = false; + } + else { + // getSensorGroup() has problem - for now just leave it at assigned team in order + // to get it tested. + // S32 team = mThreats[mCheck].mThreat->getSensorGroup(); + // if (mThreats[mCheck].mTeam != team) { + // informOtherTeams(mThreats[mCheck].mTeam, mCheck, false); + // informOtherTeams(team, mCheck, mThreats[mCheck].mActive); + // mThreats[mCheck].mTeam = team; + // } + + if (mThreats[mCheck].checkEnableChange()) { + canCheckMore = false; + informOtherTeams(mThreats[mCheck].mTeam, mCheck, mThreats[mCheck].mActive); + } + } + mCheck++; + } + else { + // Skip the reserved first threat- + mCheck = 1; + canCheckMore = false; + } + return canCheckMore; +} + +// Called frequently - but only do at most ThreatCheckFrequency times a second. +void SearchThreats::monitorThreats() +{ + U32 T = Sim::getCurrentTime(); + + if ( (T - mSaveTimeMS) > ThreatCheckDelay ) + { + mSaveTimeMS = T; + while (checkOne()) + ; + } +} + +// The path advancer uses this to see if a leg it wants to advance along +// goes through any registered threats. +bool SearchThreats::sanction(const Point3F& from, const Point3F& to, S32 team) const +{ + if (mCount) + { + const SearchThreat * threatList[MaxThreats]; + if (S32 N = getThreatPtrs(threatList, getThreatSet(team))) + { + LineSegment travelLine(from, to); + while (--N >= 0) + { + const SphereF * sphere = threatList[N]; + if (sphere->isContained(from) || sphere->isContained(to)) + return false; + if (travelLine.distance(sphere->center) < sphere->radius) + return false; + } + } + } + return true; +} + +// Just used for some debug rendering of those nodes in view of threats specified +// by console debug vars. +GraphThreatSet NavigationGraph::showWhichThreats() const +{ + if (sShowThreatened >= 0) + if (S32 N = mThreats.numThreats()) + return GraphThreatSet(1) << (sShowThreatened % N); + return 0; +} + +bool NavigationGraph::installThreat(ShapeBase * threat, S32 team, F32 rad) +{ + if (mIsSpawnGraph) + { + // Just a spawn graph... + return true; + } + else + { + U32 memUsedBefore = Memory::getMemoryUsed(); + SearchThreat newThreat(threat, rad, team); + bool success = mThreats.add(newThreat); + sLoadMemUsed += (Memory::getMemoryUsed() - memUsedBefore); + return success; + } +} + diff --git a/ai/graphThreats.h b/ai/graphThreats.h new file mode 100644 index 0000000..9540032 --- /dev/null +++ b/ai/graphThreats.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHTHREATS_H_ +#define _GRAPHTHREATS_H_ + +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif + +struct SearchThreat : public SphereF +{ + SearchThreat(); + SearchThreat(ShapeBase * threat, F32 R, S32 team); + void updateNodes(); + bool checkEnableChange(); + SimObjectPtr mThreat; + GraphNodeList mEffects; + bool mActive; + S16 mTeam; + S16 mSlot; +}; + +class SearchThreats +{ + public: + enum{ MaxThreats = (sizeof(GraphThreatSet) << 3) }; + + protected: + void informOtherTeams(S32 onTeam, S32 thisThreat, bool isOn); + S32 getThreatPtrs(const SearchThreat* L[MaxThreats], GraphThreatSet S) const; + + protected: + SearchThreat mThreats[MaxThreats]; + GraphThreatSet mTeamCaresAbout[GraphMaxTeams]; + S32 mCheck, mCount; + U32 mSaveTimeMS; + + public: + SearchThreats(); + void pushTempThreat(GraphNode* node, const Point3F& loc, F32 rad); + void popTempThreat(); + + bool checkOne(); + bool add(const SearchThreat&); + GraphThreatSet getThreatSet(S32 forTeam) const; + bool sanction(const Point3F& from, const Point3F& to, S32 team) const; + void monitorThreats(); + S32 numThreats() const {return mCount;} +}; + +#endif diff --git a/ai/graphTransient.cc b/ai/graphTransient.cc new file mode 100644 index 0000000..18cca3a --- /dev/null +++ b/ai/graphTransient.cc @@ -0,0 +1,318 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +//------------------------------------------------------------------------------------- + +GraphEdge * GraphNode::pushTransientEdge(S32 dest) +{ + // if (mEdges.isOwned()) + // { + // AssertFatal(!mOwnedPtr, "GraphNode::pushTransientEdge()"); + // mOwnedPtr = mEdges.address(); + // mOwnedCnt = mEdges.size(); + // mEdges.clearOwned(); + // mEdges.reserve(mOwnedCnt + 4); + // mEdges.setSize(mOwnedCnt); + // dMemcpy(mEdges.address(), mOwnedPtr, mOwnedCnt * sizeof(GraphEdge)); + // } + + // Hook back to the supplied destination- + GraphEdge edge; + edge.mDest = dest; + edge.mDist = (mLoc - gNavGraph->lookupNode(dest)->location()).len(); + mEdges.push_back(edge); + return & mEdges.last(); +} + +void GraphNode::popTransientEdge() +{ + // if (mOwnedPtr) + // { + // AssertFatal(!mEdges.isOwned(), "GraphNode::popTransientEdge()"); + // mEdges.setOwned(mOwnedPtr, mOwnedCnt, true); + // mOwnedPtr = NULL; + // mOwnedCnt = 0; + // } + // if (!mEdges.isOwned()) + // mEdges.pop_back(); + + mEdges.pop_back(); +} + +//------------------------------------------------------------------------------------- + + +TransientNode::TransientNode(S32 index) +{ + mGroundStart = -1; + mFlags.clear(); + mFlags.set(Transient); + mIndex = index; + mClosest = NULL; + mSaveClosest = NULL; +} + +Point3F TransientNode::getRenderPos() const +{ + Point3F buff, adjusted; + adjusted = fetchLoc(buff); + adjusted.z += 0.2; + return adjusted; +} + +// When asked for its list of edges, the transient node must return the list +// it used in the last search, not whatever it might currently think is it's +// best connection (it maintains these separately). This is a little confusing, +// might want to re-think if it's the best way to handle it. +GraphEdgeArray TransientNode::getEdges(GraphEdge*) const +{ + return GraphEdgeArray(mSearchEdges.size(), mSearchEdges.address()); +} + +// This method is used below to get the current best hooks. +GraphEdgeArray TransientNode::getHookedEdges() const +{ + return GraphEdgeArray(mEdges.size(), mEdges.address()); +} + +// This is weird, but I'm avoiding casts in the middle of the processing. Allows +// node traverser to handle transients Ok. +S32 TransientNode::volumeIndex() const +{ + if (mSaveClosest && mSaveClosest->indoor()) + return mSaveClosest->volumeIndex(); + return -1; +} + +//------------------------------------------------------------------------------------- +// Interface function for pairs of dynamic nodes + +// Remove the two hook nodes referenced by the index. Should always be nodes there +// if proper version management (using 'incarnation' variables) is happening. +void NavigationGraph::destroyPairOfHooks(S32 pairLookup) +{ + AssertFatal(validArrayIndex(pairLookup,mNodeList.size()-1) && + mNodeList[pairLookup] && mNodeList[pairLookup + 1], + "Poor GraphHookRequest communication" ); + + for(S32 i = 0; i < 2; i++) { + delete mNodeList[pairLookup + i]; + mNodeList[pairLookup + i] = NULL; + } +} + +// Note that NULL entries are fleshed out at the end of graphMake (in doFinalFixups()). +S32 NavigationGraph::createPairOfHooks() +{ + S32 findPair = mMaxTransients; + while( (findPair -= 2) >= 0 ) + if(!mNodeList[ mTransientStart + findPair]) + break; + + AssertISV(findPair >= 0, "Out of dynamic graph connections (bot max exceeded?)"); + findPair += mTransientStart; + AssertFatal(!mNodeList[findPair+1], "Free graph hooks must come in pairs"); + + // allocate 'em + for (S32 i = 0; i < 2; i++) + mNodeList[findPair + i] = new TransientNode(findPair + i); + + return findPair; +} + +//------------------------------------------------------------------------------------- + +void mayBeFineButTrapIt() +{ +} + +// Transient nodes point INTO the graph, but the graph doesn't need or maintain hooks +// back, except when a search is performed - see navPath.cc. +// +// We also set the search edges here - we copy over those nodes that had LOS. If +// none have LOS, we keep at least the closest one. +// +S32 NavigationGraph::hookTransient(TransientNode& transient) +{ + S32 retIsland = -7; + S32 thisIndex = transient.getIndex(); + GraphEdgeArray edgeList = transient.getHookedEdges(); + Point3F thisLoc = transient.mLoc; + + transient.mSearchEdges.clear(); + transient.mSaveClosest = transient.mClosest; + transient.mFirstHook = NULL; + + while (GraphEdge * edge = edgeList++) + { + GraphNode * hookTo = lookupNode(edge->mDest); + S32 hookIsland = hookTo->mIsland; + Point3F destLoc = hookTo->location(); + + if((retIsland != -7) && (hookIsland != retIsland)) + mayBeFineButTrapIt(); + else + retIsland = hookIsland; + + // The first hook is used as a representative node to determine reachability + // across partitions. + if (!transient.mFirstHook && !hookTo->transient()) + transient.mFirstHook = hookTo; + + if (GraphEdge * edgeBack = hookTo->pushTransientEdge(thisIndex)) + { + // Last edge of path may contain a border. Note we have to reflect the + // border, which is easy since they come in pairs :) + if (edge->isBorder()) + edgeBack->mBorder = (edge->mBorder ^ 1); + + if (edge->isJetting()) + edgeBack->setJetting(); + + if (edge->getTeam()) + edgeBack->setTeam(edge->getTeam()); + } + transient.mSearchEdges.push_back(*edge); + } + + // restore the regular list. + transient.mEdges = transient.mSearchEdges; + + return (transient.mIsland = retIsland); +} + +void NavigationGraph::unhookTransient(TransientNode & transient) +{ + GraphEdgeArray edgeList = transient.getEdges(NULL); + while (GraphEdge * edge = edgeList++) + lookupNode(edge->mDest)->popTransientEdge(); +} + + +//------------------------------------------------------------------------------------- +// Balanced calls for hooking in pair of transients. Hook returns if the two locations +// can find each other (or if it's ambiguous). Determined by islands and partitions. + +bool NavigationGraph::pushTransientPair(TransientNode& srcNode, TransientNode& dstNode, + U32 team, const JetManager::ID& jetCaps) +{ + S32 island0 = hookTransient(dstNode); + S32 island1 = hookTransient(srcNode); + + if (island0 == island1 && island0 >= 0) + { + // Islands Ok- check partitions- + S32 srcInd = srcNode.mFirstHook->getIndex(); + S32 dstInd = dstNode.mFirstHook->getIndex(); + + GraphPartition::Answer answer; + jetCaps; + + // First check armor partition- it never gives ambiguous answers (since they are + // computed in full when a new energy/armor configuration occurs). + #if _GRAPH_PART_ + answer = mJetManager.reachable(jetCaps, srcInd, dstInd); + if (answer == GraphPartition::CanReach) + #endif + { + // Then check force fields. Answer can be ambiguous (in which case the search + // will try, and add to the partition list if it fails). + answer = mForceFields.reachable(team, srcInd, dstInd); + if (answer == GraphPartition::CanReach || answer == GraphPartition::Ambiguous) + return true; + } + } + return false; +} + +void NavigationGraph::popTransientPair(TransientNode& srcNode, TransientNode& dstNode) +{ + unhookTransient(srcNode); + unhookTransient(dstNode); +} + +//------------------------------------------------------------------------------------- + +bool NavigationGraph::canReachLoc(const FindGraphNode& src, const FindGraphNode& dst, + U32 team, const JetManager::ID& jetCaps) +{ + GraphNode * srcNode = src.closest(); + GraphNode * dstNode = dst.closest(); + + if (srcNode && dstNode) { + if (srcNode->island() == dstNode->island()) { + // Islands Ok- check partitions- + S32 srcInd = srcNode->getIndex(); + S32 dstInd = dstNode->getIndex(); + + GraphPartition::Answer ans; + + // First check armor partition- it never gives ambiguous answers- + #if _GRAPH_PART_ + ans = mJetManager.reachable(jetCaps, srcInd, dstInd); + if (ans == GraphPartition::CanReach) + #endif + { + // Then check force fields. Answer can be ambiguous- + ans = mForceFields.reachable(team, srcInd, dstInd); + if (ans == GraphPartition::CanReach || ans == GraphPartition::Ambiguous) + return true; + } + } + } + return false; +} + +//------------------------------------------------------------------------------------- +// A path searcher goes through this interface to get a handle to a transient node. + +GraphHookRequest::GraphHookRequest() +{ + mIncarnation = -1; + mPairLookup = -1; +} + +GraphHookRequest::~GraphHookRequest() +{ + if (NavigationGraph::gotOneWeCanUse()) + if (gNavGraph->incarnation() == mIncarnation) + gNavGraph->destroyPairOfHooks(mPairLookup); +} + +// Called on mission cycle - basically a re-construct. +void GraphHookRequest::reset() +{ + mIncarnation = -1; + mPairLookup = -1; +} + +// There CAN be GraphHookRequest objects constructed without there being a graph, but NOT +// actual requests for nodes. This is why we don't assert above, but do in the following. +TransientNode & GraphHookRequest::getHook(S32 firstOrSecond) +{ + AssertFatal(NavigationGraph::gotOneWeCanUse(), "Called GraphHookRequest w/o graph"); + if(mIncarnation != gNavGraph->incarnation()) { + mPairLookup = gNavGraph->createPairOfHooks(); + mIncarnation = gNavGraph->incarnation(); + } + GraphNode * theNode = gNavGraph->lookupNode(mPairLookup + firstOrSecond); + AssertFatal(mPairLookup >= 0 && theNode != NULL, "Couldn not alloc transient node"); + AssertFatal(theNode->transient(), "Node must be transient!"); + + TransientNode * t = static_cast(theNode); + return *t; +} + +bool GraphHookRequest::iGrowOld() const +{ + AssertFatal( NavigationGraph::gotOneWeCanUse(), + "I hear the mermaids singing, each to each... " + "I do not think they sing to me... " ); + return gNavGraph->incarnation() != mIncarnation; +} diff --git a/ai/graphTransient.h b/ai/graphTransient.h new file mode 100644 index 0000000..846961c --- /dev/null +++ b/ai/graphTransient.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRAPHTRANSIENT_H_ +#define _GRAPHTRANSIENT_H_ + +class TransientNode : public RegularNode +{ + friend class NavigationGraph; + GraphEdgeList mSearchEdges; + S32 mGroundStart; + const GraphNode * mClosest, * mSaveClosest; + const GraphNode * mFirstHook; + public: + TransientNode(S32 index); + + GraphEdgeArray getEdges(GraphEdge*) const; + GraphEdgeArray getHookedEdges() const; + void informPushed(); + void makeEdgeList(const Point3F& loc, const GraphNodeList& list); + void connectOutdoor(GraphNode* outdoorNode, bool all=true); + void setLoc(const Point3F& loc) {mLoc = loc;} + void setEdges(const GraphEdgeList& edges) {mEdges=edges;} + void setClosest(const GraphNode* closest) {mClosest=closest;} + Point3F getRenderPos() const; + S32 volumeIndex() const; +}; + +class GraphHookRequest +{ + private: + TransientNode& getHook(S32 N); + S32 mIncarnation; + S32 mPairLookup; + + public: + GraphHookRequest(); + ~GraphHookRequest(); + TransientNode& getHook1() { return getHook(0); } + TransientNode& getHook2() { return getHook(1); } + bool iGrowOld() const; + void reset(); +}; + +#endif diff --git a/ai/graphVolume.cc b/ai/graphVolume.cc new file mode 100644 index 0000000..22b4a5b --- /dev/null +++ b/ai/graphVolume.cc @@ -0,0 +1,387 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ai/graph.h" + +#define PlaneAdjErr 0.014f +#define MapIndoorIndex(i) ((i)-mNumOutdoor) + +//------------------------------------------------------------------------------------- + +// General query for volume info - package up everything user might want, including +// list of corners. Bool parameter tells if ceiling corners should be fetched too. +const GraphVolume& NavigationGraph::fetchVolume(const GraphNode* node, bool above) +{ + S32 Index = MapIndoorIndex(node->getIndex()); + mTempVolumeBuf.mFloor = mNodeVolumes.floorPlane(Index); + mTempVolumeBuf.mCeiling = mNodeVolumes.abovePlane(Index); + mTempVolumeBuf.mPlanes = mNodeVolumes.planeArray(Index); + mTempVolumeBuf.mCount = mNodeVolumes.planeCount(Index); + mNodeVolumes.getCorners(Index, mTempVolumeBuf.mCorners, !above); + return mTempVolumeBuf; +} + +//------------------------------------------------------------------------------------- + +static const NodeProximity sFarProximity; + +// Points inside the volume return a negative distance from planes, so we take the +// maximum of these, and then "good" containment means that the maximum of these +// numbers is still pretty small. The check with floor is either/or though. We start +// with ceiling distance as the beginning metric. +NodeProximity NavigationGraph::getContainment(S32 indoorIndex, const Point3F& point) +{ + NodeProximity metric; + + PlaneF floor = mNodeVolumes.floorPlane(indoorIndex = MapIndoorIndex(indoorIndex)); + + // Below floor -> not a candidate, return low containment (far) + metric.mHeight = floor.distToPlane(point); + if (metric.mHeight > PlaneAdjErr) + return sFarProximity; + + F32 ceilingMetric = mNodeVolumes.abovePlane(indoorIndex).distToPlane(point); + metric.mAboveC = ceilingMetric; + + if (ceilingMetric > 0.0) { + // Tricky number here - probably don't want to make huge because it's sometimes + // important that we're near the top of volumes - other times not. Really + // need more info about the volume... This fixes problem in Beggar's Run. + ceilingMetric = getMax(ceilingMetric, 7.0f); + } + + // Find the closest wall, use ceiling as start (if we're below, else larger #). + metric.mLateral = ceilingMetric; + const PlaneF * planes = mNodeVolumes.planeArray(indoorIndex); + S32 numWalls = mNodeVolumes.planeCount(indoorIndex) - 2; + + while (--numWalls >= 0) + { + F32 D = (planes++)->distToPlane(point); + if (D > metric.mLateral) + metric.mLateral = D; + } + + return metric; +} + +//------------------------------------------------------------------------------------- +// Find closest point and store result in soln if it exists. + +bool NavigationGraph::closestPointOnVol(GraphNode * node, const Point3F& point, Point3F& soln) const +{ + S32 indoorIndex = MapIndoorIndex(node->getIndex()); + return mNodeVolumes.closestPoint(indoorIndex, point, soln); +} + +bool GraphVolumeList::closestPoint(S32 ind, const Point3F& point, Point3F& soln) const +{ + Vector corners; + bool foundIt = false; + PlaneF floor = floorPlane(ind); + + if (intersectWalls(ind, floor, corners)) + { + S32 numCorners = corners.size(); + F32 minDist = 1e13; + corners.push_back(corners[0]); + + for (S32 i = 0; i < numCorners; i++) + { + LineSegment segment(corners[i], corners[i+1]); + F32 D = segment.distance(point); + if (D < minDist) + { + foundIt = true; + minDist = D; + soln = segment.solution(); + soln.z = point.z; + } + } + } + return foundIt; +} + +//------------------------------------------------------------------------------------- + +PlaneF NavigationGraph::getFloorPlane(GraphNode * node) +{ + return mNodeVolumes.floorPlane(MapIndoorIndex(node->getIndex())); +} + +//------------------------------------------------------------------------------------- +// Are we between floor and ceiling of this node? + +bool NavigationGraph::verticallyInside(S32 indoorIndex, const Point3F& point) +{ + PlaneF floor = mNodeVolumes.floorPlane(indoorIndex = MapIndoorIndex(indoorIndex)); + if (floor.distToPlane(point) <= PlaneAdjErr) + if (mNodeVolumes.abovePlane(indoorIndex).distToPlane(point) <= PlaneAdjErr) + return true; + return false; +} + +F32 NavigationGraph::heightAboveFloor(S32 indoorIndex, const Point3F& point) +{ + PlaneF floor = mNodeVolumes.floorPlane(MapIndoorIndex(indoorIndex)); + return -(floor.distToPlane(point) + PlaneAdjErr); +} + +//------------------------------------------------------------------------------------- + +bool GraphVolumeList::intersectWalls(S32 i, const PlaneF& with, Vector& list) const +{ + bool noneWereParallel = true; + const PlaneF* planes = planeArray(i); + S32 N = planeCount(i) - 2; + + for (S32 w = 0; w < N; w++) { + Point3F point; + if (intersectPlanes(with, planes[w], planes[(w+1)%N], &point)) + list.push_back(point); + else { + // Parallel planes occasionally happen - since intersection points are + // not vital to system (just used to find bounding box) - we just + // continue, but return false; + // + noneWereParallel = false; + } + } + return noneWereParallel; +} + +//------------------------------------------------------------------------------------- +// Get intersections of the volume. + +S32 GraphVolumeList::getCorners(S32 ind, Vector& points, bool justFloor) const +{ + points.clear(); + PlaneF floor = floorPlane(ind); + intersectWalls(ind, floor, points); + if (!justFloor) + { + PlaneF ceiling = abovePlane(ind); + intersectWalls(ind, ceiling, points); + } + return points.size(); +} + +//------------------------------------------------------------------------------------- +// Get minimum extent of the volume. Want minimum of max distance taken from +// each wall plane. +// The point buffer is just passed in to avoid vector reallocs since this method is +// called a lot at startup, and Point3F vecs are showing up on memory profiles. + +F32 GraphVolumeList::getMinExt(S32 ind, Vector& ptBuffer) +{ + ptBuffer.clear(); + if (intersectWalls(ind, floorPlane(ind), ptBuffer)) + { + const PlaneF* planes = planeArray(ind); + S32 N = planeCount(ind) - 2; + F32 minD = 1e9; + for (S32 w = 0; w < N; w++) + { + F32 maxD = -10, d; + for (S32 p = 0; p < ptBuffer.size(); p++) + if ((d = (-planes[w].distToPlane(ptBuffer[p]))) > maxD) + maxD = d; + if (maxD < minD) + minD = maxD; + } + return minD; + } + else { + return 0.5; + } +} + +//------------------------------------------------------------------------------------- + +// Push a box of given height and width at P. +void GraphVolumeList::addVolume(Point3F pos, F32 boxw, F32 boxh) +{ + GraphVolInfo entry; + entry.mPlaneCount = 6; + entry.mPlaneIndex = mPlanes.size(); + + static const VectorF walls[4]= + {VectorF(-1,0,0), VectorF(0,1,0), VectorF(1,0,0), VectorF(0,-1,0)}; + + // Add the walls- + for (S32 i = 0; i < 4; i++) + { + Point3F point = pos + walls[i] * boxw; + PlaneF wall(point, walls[i]); + mPlanes.push_back(wall); + } + PlaneF floor(pos, VectorF(0, 0, -1)); + mPlanes.push_back(floor); + + pos.z += boxh; + PlaneF ceiling(pos, VectorF(0, 0, 1)); + mPlanes.push_back(ceiling); + + push_back(entry); +} + +//------------------------------------------------------------------------------------- + +// When islands are culled, lists are compacted, and this service is needed. It's +// called after the GraphVolInfo list is culled down. +void GraphVolumeList::cullUnusedPlanes() +{ + // Copy down the planes and remap the indices into the plane list. + Vector keepers(mPlanes.size()); + for (iterator it = begin(); it != end(); it++) + { + S32 newIndex = keepers.size(); + for (S32 p = 0; p < it->mPlaneCount; p++) + keepers.push_back(mPlanes[it->mPlaneIndex + p]); + it->mPlaneIndex = newIndex; + } + + // Replace the plane list with the keepers- + mPlanes = keepers; +} + +//------------------------------------------------------------------------------------- + +// Ideally always go through this for remapping to the indoor list: +S32 NavigationGraph::indoorIndex(const GraphNode* node) +{ + return MapIndoorIndex(node->getIndex()); +} + +//------------------------------------------------------------------------------------- + +bool NavigationGraph::inNodeVolume(const GraphNode* node, const Point3F& point) +{ + S32 nodeIndex = MapIndoorIndex(node->volumeIndex()); + AssertFatal(validArrayIndex(nodeIndex, mNodeVolumes.size()), "nonmatching volume"); + S32 numWalls = mNodeVolumes.planeCount(nodeIndex) - 2; + const PlaneF* planes = mNodeVolumes.planeArray(nodeIndex); + + while (numWalls--) + // if (planes[numWalls].whichSide(point) != PlaneF::Back) + if (planes[numWalls].distToPlane(point) > PlaneAdjErr) + return false; + + return true; +} + +//------------------------------------------------------------------------------------- + +void NavigationGraph::drawNodeInfo(GraphNode * node) +{ + if (mHaveVolumes) { + clearRenderBoxes(); + Vector corners; + mNodeVolumes.getCorners(indoorIndex(node), corners, false); + for (S32 c = 0; c < corners.size(); c++) + pushRenderBox(corners[c], 0); + } +} + +// This is a debugging method- see how well we match nodes and such. +const char * NavigationGraph::drawNodeInfo(const Point3F& loc) +{ + const char * info = NULL; + + if (GraphNode * node = closestNode(loc)) { + if (node->indoor()) { + // Put boxes around the node + drawNodeInfo(node); + info = "Found indoor node"; + } + else + info = "Found outdoor node"; + } + else + info = "Couldn't find a closest node to this location"; + + return info; +} + + +//------------------------------------------------------------------------------------- + +static const U32 sMask = InteriorObjectType|StaticShapeObjectType|StaticObjectType; + +static bool intersectsAnything(const Box3F& bounds, ClippedPolyList& polyList) +{ + if (gServerContainer.buildPolyList(bounds, sMask, &polyList)) + return (polyList.mPolyList.size() != 0); + return false; +} + +//------------------------------------------------------------------------------------- + +#define JustUnshrinkIt (NodeVolumeShrink + 0.002) +#define ExtrudeALittle (NodeVolumeShrink + 0.22) + +// +// We want to slightly oversize node volumes which can. This accomplishes two things: +// 1. The containment metrics are better. Example is when you're staning on a ledge +// but the node from below (which is along a wall) matches you better. +// 2. It should help with the indoor lookahead smoothing. It makes sure we are +// always inside nodes. +// +// This ALSO moves the volumes out by the amount that Greg's system pulls it in. +// (We do this here because of the extra checks which want them still shrunk). +// +void GraphVolumeList::nudgeVolumesOut() +{ + Point3F outSize(1.0, 1.0, 1.0); + Vector nudgeAmounts; + Vector corners; + + for (S32 i = 0; i < size(); i++) + { + // Calculate a bounding box of the node volume- + getCorners(i, corners, false); + Box3F bounds(Point3F(1e9, 1e9, 1e9), Point3F(-1e9, -1e9, -1e9), true); + for (Vector::iterator c = corners.begin(); c != corners.end(); c++) + bounds.min.setMin(*c), bounds.max.setMax(*c); + + // Perform our extrusion checks on all the walls (_separately_, hence bool array) + S32 numPlanes = planeCount(i); + PlaneF* planeList = getPlaneList(i); + nudgeAmounts.setSize(numPlanes); + bounds.min -= outSize; + bounds.max += outSize; + for (S32 n = 0; n < numPlanes; n++) + { + if (n == numPlanes - 2) { + // Floor- don't extrude this one + nudgeAmounts[n] = JustUnshrinkIt; + } + else { + // Build poly list extruded in one direction- + ClippedPolyList polyList; + polyList.mPlaneList.clear(); + polyList.mNormal.set(0, 0, 0); + for (S32 p = 0; p < numPlanes; p++) { + PlaneF plane = planeList[p]; + if (p == n) + plane.d -= ExtrudeALittle; + polyList.mPlaneList.push_back(plane); + } + + // See if we nudged without hitting anything. + if (intersectsAnything(bounds, polyList)) + nudgeAmounts[n] = JustUnshrinkIt; + else + nudgeAmounts[n] = ExtrudeALittle; + } + } + + // Now move the planes + for (S32 j = 0; j < numPlanes; j++) + planeList[j].d -= nudgeAmounts[j]; + } +} diff --git a/ai/oVector.h b/ai/oVector.h new file mode 100644 index 0000000..52fb2c2 --- /dev/null +++ b/ai/oVector.h @@ -0,0 +1,195 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _OVECTOR_H_ +#define _OVECTOR_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +#ifdef 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 + +template class OVector +{ + protected: + U16 mElementCount; + U16 mArraySize; + T * mArray; + + // Could probably use the low bit as flag and double our max size here... + U16 userOwned() const {return (mArraySize & 0x8000);} + U16 arraySize() const {return (mArraySize & 0x7fff);} + + bool resize(U32 ecount) { + bool Ok = true; + AssertFatal(ecount < (1<<15), "OVector: 32K maximum exceeded"); + if (!userOwned()) { + U32 size = arraySize(); // Want to use existing VectorResize(), + U32 count = mElementCount; // so convert to U32s for it... +#ifdef DEBUG_GUARD + Ok = VectorResize(&size, &count, (void**) &mArray, ecount, sizeof(T), __FILE__, __LINE__); +#else + Ok = VectorResize(&size, &count, (void**) &mArray, ecount, sizeof(T)); +#endif + mArraySize = size | userOwned(); + mElementCount = count; + } + else { + AssertISV(ecount <= arraySize(), "OVector: overgrown owned vector"); + mElementCount = ecount; + } + return Ok; + } + + public: + OVector() { + mArray = NULL; + mElementCount = mArraySize = 0; + } + + ~OVector() { + if (!userOwned()) + dFree(mArray); + } + + typedef T* iterator; + typedef const T* const_iterator; + + // One-liners- + iterator begin() {return mArray;} + iterator end() {return mArray + mElementCount;} + T& front() {return * begin();} + T& back() {return * end();} + T& first() {return mArray[0];} + T& last() {return mArray[mElementCount - 1];} + T& operator[](S32 i) {return operator[](U32(i));} + T& operator[](U32 i) {return mArray[i];} + const T& first() const {return mArray[0];} + const T& last() const {return mArray[mElementCount - 1];} + const_iterator begin() const {return mArray;} + const_iterator end() const {return mArray + mElementCount;} + const T& front() const {return * begin();} + const T& back() const {return * end();} + const T& operator[](U32 i) const {return mArray[i];} + const T& operator[](S32 i) const {return operator[](U32(i));} + void clear() {mElementCount = 0;} + void compact() {resize(mElementCount);} + bool empty() const {return (mElementCount == 0);} + bool isOwned() const {return (userOwned() != 0);} + S32 memSize() const {return capacity() * sizeof(T);} + U32 capacity() const {return arraySize();} + T * address() const {return mArray;} + S32 size() const {return S32(mElementCount);} + + // This is where user sets their own data. We then allow all other operations to + // go on as normal - errors will be caught in resize(), which everything uses. + void setOwned(T * data, U16 available, bool setSize = false) { + if (!userOwned()) + dFree(mArray); + AssertFatal(available < (1<<15), "OVector: can only hold 32K"); + mElementCount = (setSize ? available : 0); + mArraySize = (available | 0x8000); + mArray = data; + } + + void clearOwned() { + if (userOwned()) { + mElementCount = 0; + mArraySize = 0; + mArray = 0; + } + } + + S32 setSize(U32 size) { + if (size > arraySize()) + resize(size); + else + mElementCount = size; + return mElementCount; + } + + void increment(U32 delta=1) { + if ((mElementCount += delta) > arraySize()) + resize(mElementCount); + } + + void decrement(U32 delta = 1) { + if (mElementCount > delta) + mElementCount -= delta; + else + mElementCount = 0; + } + + void insert(U32 i) { + increment(); + dMemmove(&mArray[i + 1], &mArray[i], (mElementCount - i - 1) * sizeof(T)); + } + + void erase(U32 i) { + dMemmove(&mArray[i], &mArray[i + 1], (mElementCount - i - 1) * sizeof(T)); + decrement(); + } + + void erase_fast(U32 i) { // CAUTION: this does not maintain list order + if (i < (mElementCount - 1)) // Copies the last element into the deleted hole + dMemmove(&mArray[i], &mArray[mElementCount - 1], sizeof(T)); + decrement(); + } + + void erase_fast(iterator q) { + erase_fast(U32(q - mArray)); + } + + void push_back(const T& x) { + increment(); + mArray[mElementCount - 1] = x; + } + + void push_front(const T & x) { + insert(0); + mArray[0] = x; + } + + void pop_front() { + erase(U32(0)); + } + + void pop_back() { + decrement(); + } + + void reserve(U32 size) { + if (size > arraySize()) { + S32 ec = S32(mElementCount); + if (resize(size)) + mElementCount = U32(ec); + } + } + + void operator=(const OVector& p) { + resize(p.mElementCount); + if (p.mElementCount) + dMemcpy(mArray,p.mArray,mElementCount * sizeof(T)); + } + + void merge(const OVector& p) { + if (p.size()) { + S32 oldsize = size(); + resize(oldsize + p.size()); + dMemcpy( &mArray[oldsize], p.address(), p.size() * sizeof(T) ); + } + } +}; + +#endif //_OVECTOR_H_ diff --git a/ai/tBinHeap.h b/ai/tBinHeap.h new file mode 100644 index 0000000..5059b19 --- /dev/null +++ b/ai/tBinHeap.h @@ -0,0 +1,415 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/* + + BinHeap.h + jan 17, 2000 + +*/ +#ifndef BINHEAP_H +#define BINHEAP_H + +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + +#define BinHeapInline inline + +#define BinHeapParent(child) (((child) - 1) >> 1) +#define BinHeapRight(parent) (((parent) + 1) << 1) +#define BinHeapLeft(parent) (((parent) << 1) + 1) + +// Much perfomed operation for shifting in the heap +#define BinHeapMove(src, dst) (mBack[ mHeap[dst] = mHeap[src] ] = dst) + + +// +// BinaryHeap Class +// +template +class BinHeap +{ + protected: + T * mPool; + S16 * mHeap; + S16 * mBack; + + Vector mPoolVec; + Vector mHeapVec; + Vector mBackVec; + bool mIsHeapified; + S32 mHeapCount; + + protected: + void setPointers(); + void keyChange(S32 heapIndex); + void keyImprove(S32 heapIndex); + void shiftDown(S32 parent, S32 child); + void shiftUp(S32 parent, S32 child); + + public: + BinHeap(); + ~BinHeap(); + + void changeKey(S32 indexInArray); + void improveKey(S32 indexInArray); + void clear(); + void removeHead(); + void insert(const T &elem); + T *head(); + S32 headIndex(); + S32 count(); + S32 size(); + void buildHeap(); + void heapify(S32 heapIndex); + T &operator[](U32 index); + void reserve(S32 amount); + bool validateBack(); + bool validateHeap(); +}; + + +// inlines +//-------------------------------------------------------------------------------- + +template +BinHeapInline BinHeap::BinHeap() +{ + mHeapCount = 0; + mIsHeapified = false; + setPointers(); +} + +//-------------------------------------------------------------------------------- + +template +BinHeapInline S32 BinHeap::count() +{ + return mHeapCount; +} + +//-------------------------------------------------------------------------------- + +template +BinHeapInline S32 BinHeap::size() +{ + return mPoolVec.size(); +} + +//-------------------------------------------------------------------------------- + +template +BinHeapInline S32 BinHeap::headIndex() +{ + return (mHeapCount > 0 ? mHeap[0] : -1); +} + +//-------------------------------------------------------------------------------- + +template +BinHeapInline T * BinHeap::head() +{ + if(mHeapCount > 0) + return & mPool[mHeap[0]] ; + else + return NULL; +} + +//-------------------------------------------------------------------------------- + +template +BinHeapInline void BinHeap::setPointers() +{ + mPool = mPoolVec.address(); + mHeap = mHeapVec.address(); + mBack = mBackVec.address(); +} + +//-------------------------------------------------------------------------------- + +template +BinHeapInline void BinHeap::shiftDown(S32 parent, S32 child) +{ + mHeap[child] = mHeap[parent]; + mBack[mHeap[child]] = child; +} + +//-------------------------------------------------------------------------------- + +template +BinHeapInline void BinHeap::shiftUp(S32 parent, S32 child) +{ + mHeap[parent] = mHeap[child]; + mBack[mHeap[parent]] = parent; +} + +// implementation +//-------------------------------------------------------------------------------- + +template +BinHeap::~BinHeap() +{ +} + +//-------------------------------------------------------------------------------- + +template +BinHeapInline void BinHeap::changeKey(S32 indexInArray) +{ + S32 indexInHeap = mBack[indexInArray]; + keyChange(indexInHeap); +} + +//-------------------------------------------------------------------------------- + +template +BinHeapInline void BinHeap::improveKey(S32 index) +{ + keyImprove(mBack[index]); +} + +//-------------------------------------------------------------------------------- + +template +void BinHeap::keyChange(S32 heapIndex) +{ + S32 i = heapIndex; + S32 tempHeap2Vec = mHeap[heapIndex]; + mIsHeapified = false; + + while(i > 0) + { + if(mPool[mHeap[BinHeapParent(i)]] < mPool[tempHeap2Vec]) + { + mIsHeapified = true; + shiftDown(BinHeapParent(i), i); + i = BinHeapParent(i); + } + else + break; + } + mHeap[i] = tempHeap2Vec; + mBack[mHeap[i]] = i; + + if(!mIsHeapified) + heapify(heapIndex); +} + +//-------------------------------------------------------------------------------- + +// This version of keyChange() is for values known only to improve - and thus move +// towards head of queue. Dijkstra() knows this, so we remove many wasted calls +// to heapify() for case where the key didn't percolate up. +template +void BinHeap::keyImprove(S32 heapIndex) +{ + S32 i = heapIndex; + S32 tempHeap2Vec = mHeap[heapIndex]; + + while(i > 0) + { + S32 parent = BinHeapParent(i); + if(mPool[mHeap[parent]] < mPool[tempHeap2Vec]) + { + // shiftDown(parent, i); + BinHeapMove(parent, i); + i = parent; + } + else + { + // BinHeapMove(tempHeap2Vec, i); + mHeap[i] = tempHeap2Vec; + mBack[mHeap[i]] = i; + return; + } + } +} + +//-------------------------------------------------------------------------------- + +template +void BinHeap::clear() +{ + mPoolVec.clear(); + mHeapVec.clear(); + mBackVec.clear(); + setPointers(); + mIsHeapified = false; + mHeapCount = 0; +} + +//-------------------------------------------------------------------------------- + +template +void BinHeap::insert(const T &elem) +{ + S32 indexInArray = mPoolVec.size(); + mPoolVec.push_back(elem); + mHeapVec.increment(1); + mBackVec.push_back(mHeapCount); + setPointers(); + mHeap[mHeapCount++] = indexInArray; + + if(mIsHeapified) + { + register S32 i = mHeapCount - 1; + register S32 tempHeap2Vec = mHeap[i]; + + while(i > 0) + { + if(mPool[mHeap[BinHeapParent(i)]] < elem) + { + shiftDown(BinHeapParent(i), i); + i = BinHeapParent(i); + } + else + break; + } + mHeap[i] = tempHeap2Vec; + mBack[tempHeap2Vec] = i; + } +} + +//--------------------------------------------------------------------------------- + +template +void BinHeap::heapify(S32 parent) +{ + S32 l, r; + S32 largest = parent; + S32 tempHeap2Vec = mHeap[parent]; + + while(1) + { + if( (l = BinHeapLeft(parent)) < mHeapCount) // only carry further if left exists. + { + if(mPool[tempHeap2Vec] < mPool[mHeap[l]]) + largest = l; + + if( (r = BinHeapRight(parent)) < mHeapCount ) // don't do below work if no right + { + if(largest == parent && mHeap[parent] != tempHeap2Vec) + { + if( mPool[tempHeap2Vec] < mPool[mHeap[r]] ) + largest = r; + } + else + { + if( mPool[mHeap[largest]] < mPool[mHeap[r]] ) + largest = r; + } + } + } + + if(largest != parent) + { + shiftUp(parent, largest); + parent = largest; + } + else + { + mHeap[parent] = tempHeap2Vec; + mBack[tempHeap2Vec] = parent; + break; + } + } + mIsHeapified = true; +} + +//-------------------------------------------------------------------------------- + +template +bool BinHeap::validateBack() +{ + bool valid = true; + for(S32 i = 0; i < mHeapCount; i++) + { + if(mBack[i] == -1) + continue; + if(mHeap[mBack[i]] != i || mBack[mHeap[i]] != i) + valid = false; + } + return valid; +} + +//-------------------------------------------------------------------------------- + +template void BinHeap::reserve(S32 amount) +{ + mPoolVec.reserve(amount); + mHeapVec.reserve(amount); + mBackVec.reserve(amount); +} + +//-------------------------------------------------------------------------------- + +template +bool BinHeap::validateHeap() +{ + if(!mIsHeapified) + buildHeap(); + + bool valid = true; + S32 parents = (mHeapCount-1) >> 1; + + for(S32 i = parents; i >= 0; i--) + { + S32 l = BinHeapLeft(i); + S32 r = BinHeapRight(i); + + if(l < mHeapCount && mPool[mHeap[i]] < mPool[mHeap[l]]) + { + printf("error: (%d < l)parent with lower key than child!\n", i); + valid = false; + } + if(r < mHeapCount && mPool[mHeap[i]] < mPool[mHeap[r]]) + { + printf("Error: (%d < r)parent with lower key than child!\n", i); + valid = false; + } + } + return valid; +} + +//--------------------------------------------------------------------------------- + +template +void BinHeap::buildHeap() +{ + mIsHeapified = true; + for(S32 j = (mHeapCount >> 1) - 1; j >= 0; j--) + heapify(j); +} + +//--------------------------------------------------------------------------------- + +template +void BinHeap::removeHead() +{ + if(mHeapCount < 1) + return; + if(!mIsHeapified) + buildHeap(); + + mBack[mHeap[0]] = -1; + mBack[mHeap[0]=mHeap[--mHeapCount]] = 0; + if(mHeapCount) + heapify(0); +} + +//----------------------------------------------------------------------------------- + +template +BinHeapInline T & BinHeap::operator[](U32 index) +{ + return mPool[index]; +} + +//----------------------------------------------------------------------------------- + +#endif diff --git a/ai/texturePreload.h b/ai/texturePreload.h new file mode 100644 index 0000000..93fe191 --- /dev/null +++ b/ai/texturePreload.h @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TEXTUREPRELOAD_H_ +#define _TEXTUREPRELOAD_H_ + +class PreloadTextures +{ + enum {MaxHandles = 512}; + TextureHandle mTextures[MaxHandles]; + S32 mNext; + + public: + PreloadTextures(); + ~PreloadTextures(); + void load(const char * name, bool clamp); +}; + +#endif diff --git a/audio/audio.cc b/audio/audio.cc new file mode 100644 index 0000000..3ad0544 --- /dev/null +++ b/audio/audio.cc @@ -0,0 +1,3372 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "audio/audio.h" +#include "audio/audioNet.h" +#include "core/tVector.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "game/gameConnection.h" +#include "audio/audioCodec.h" +#include "core/fileStream.h" +#include "audio/audioThread.h" + +// Comment the following line to enable the audio thread again +#ifdef __linux +#define DISABLE_AUDIO_THREAD +#endif + +// miles openAL implementation does not handle 3dsource volumes through listener for all providers, +// so wrapper takes care of all this (undef if not needed) +// - scores for sources are attenuated by channel gain but not by listener gain +#define HANDLE_LISTENER_GAIN + +//------------------------------------------------------------------------- +namespace { + +#define RECORD_SPEED 8000 // default rate for capturing +#define RECORD_FORMAT AL_FORMAT_MONO16 // capture format +#define RECORD_BUFFERSIZE 1024 // 1k capture buffer size +#define RECORD_LEN (4 * 1000) // 4seconds max capture time +#define MAX_AUDIOSOURCES 16 // maximum number of concurrent sources +#define MIN_GAIN 0.05f // anything with lower gain will not be started +#define MIN_CAPTURE_SCALE 0.1f // for scaling captured buffers +#define MAX_CAPTURE_SCALE 5.f +#define MIN_UNCULL_PERIOD 500 // time before buffer is checked to be unculled +#define MIN_UNCULL_GAIN 0.1f // min gain of source to be unculled + +#define ALX_DEF_SAMPLE_RATE 44100 // default values for mixer +#define ALX_DEF_SAMPLE_BITS 16 +#define ALX_DEF_CHANNELS 2 + +#define FORCED_OUTER_FALLOFF 10000.f // forced falloff distance +static bool mDisableOuterFalloffs = false; // forced max falloff? +static F32 mInnerFalloffScale = 1.f; // amount to scale inner falloffs + +static Vector mDriverInfoList(__FILE__, __LINE__); +static bool mInitialized = false; +static Audio::DriverInfo *mActiveDriver = NULL; // the current audio driver (miles, none, ...) +static F32 mCaptureGainScale = 1.f; // amount to scale captured buffers before sending to server + +static F32 mAudioTypeVolume[Audio::NumAudioTypes]; // the attenuation for each of the channel types + +//------------------------------------------------------------------------- +struct LoopingImage +{ + AUDIOHANDLE mHandle; + Resource mBuffer; + Audio::Description mDescription; + AudioSampleEnvironment * mEnvironment; + + Point3F mPosition; + Point3F mDirection; + F32 mPan; + F32 mPitch; + F32 mScore; + S32 mCullTime; + + LoopingImage() { clear(); } + + void clear() + { + mHandle = NULL_AUDIOHANDLE; + mBuffer = NULL; + dMemset(&mDescription, 0, sizeof(Audio::Description)); + mEnvironment = 0; + mPosition.set(0.f,0.f,0.f); + mDirection.set(0.f,1.f,0.f); + mPan = 0.f; + mPitch = 1.f; + mScore = 0.f; + mCullTime = 0; + } +}; + +//------------------------------------------------------------------------- +struct VoiceStream +{ + U32 mClientId; + U8 mCodecId; + U8 mStreamId; + VoiceDecoderStream mDecoderStream; + ALuint mBuffer; + AUDIOHANDLE mHandle; +}; + +static F32 mMasterVolume = 1.f; // traped from AL_LISTENER gain (miles has difficulties with 3d sources) + +static ALuint mSource[MAX_AUDIOSOURCES]; // ALSources +static AUDIOHANDLE mHandle[MAX_AUDIOSOURCES]; // unique handles +static Resource mBuffer[MAX_AUDIOSOURCES]; // each of the playing buffers (needed for AudioThread) +static F32 mScore[MAX_AUDIOSOURCES]; // for figuring out which sources to cull/uncull +static F32 mSourceVolume[MAX_AUDIOSOURCES]; // the samples current un-attenuated gain (not scaled by master/channel gains) +static U32 mType[MAX_AUDIOSOURCES]; // the channel which this source belongs +static bool mRecording = false; // currently recording? +static bool mLocalCapture = false; // recording to local buffer? +static ALboolean mCaptureInitialized = AL_FALSE; // capture initialized? + +static ALuint mStreamBuffer; // the music strean buffer (only 1 music stream currently supported) +static AUDIOHANDLE mStreamHandle; // handle to the music stream +static bool mFinishedMusicStream = false; // set in callback (al thread) to process end of music stream +static bool mFinishedMusicStreamStopState = false; // set in callback (passed along to console) + +static AudioSampleEnvironment * mSampleEnvironment[MAX_AUDIOSOURCES]; // currently playing sample environments +static bool mEnvironmentEnabled = false; // environment enabled? +static SimObjectPtr mCurrentEnvironment; // the last environment set + +#define LOCAL_CAPTURE_SIZE (RECORD_LEN * RECORD_SPEED) +static U8 * mLocalCaptureBuffer = 0; // buffer to contain captured data +static U32 mLocalCaptureBufferPos = 0; // current capture buffer pos (circular) +static bool mLocalCaptureFinished = false; // finished capturing? (set in callback) +static ALuint mALCaptureBuffer = 0; // the buffer used to playback local capture data +static ALuint mEnvironment = 0; // al environment handle + +struct LoopingList : VectorPtr +{ + LoopingList() : VectorPtr(__FILE__, __LINE__) { } + + LoopingList::iterator findImage(AUDIOHANDLE handle); + void sort(); +}; + +// LoopingList and LoopingFreeList own the images +static LoopingList mLoopingList; // all the looping sources +static LoopingList mLoopingFreeList; // free store +static LoopingList mLoopingInactiveList; // sources which have not been played yet +static LoopingList mLoopingCulledList; // sources which have been culled (alxPlay called) + +static VectorPtr sVoiceStreams(__FILE__, __LINE__); +static VectorPtr sVoiceStreams_free(__FILE__, __LINE__); + +static VoiceEncoderStream mVoiceEncoderStream; + +#define AUDIOHANDLE_LOOPING_BIT (0x80000000) +#define AUDIOHANDLE_VOICE_BIT (0x40000000) +#define AUDIOHANDLE_INACTIVE_BIT (0x20000000) +#define AUDIOHANDLE_LOADING_BIT (0x10000000) +#define HANDLE_MASK ~(AUDIOHANDLE_LOOPING_BIT | AUDIOHANDLE_VOICE_BIT | AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT) + +// keep the 'AUDIOHANDLE_LOOPING_BIT' on the handle returned to the caller so that +// the handle can quickly be rejected from looping list queries +#define RETURN_MASK ~(AUDIOHANDLE_VOICE_BIT | AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT) +static AUDIOHANDLE mLastHandle = NULL_AUDIOHANDLE; + +static bool mForceMaxDistanceUpdate = false; // force gain setting for 3d distances +static U32 mNumSources = 0; // total number of sources to work with +static U32 mRequestSources = MAX_AUDIOSOURCES; // number of sources to request from openAL + +#define INVALID_SOURCE 0xffffffff +#define CAPTURE_BUFFER_SIZE (300) + +static U32 sCaptureTimeout = 0; + +inline bool areEqualHandles(AUDIOHANDLE a, AUDIOHANDLE b) +{ + return((a & HANDLE_MASK) == (b & HANDLE_MASK)); +} + +//------------------------------------------------------------------------- +// Looping image +//------------------------------------------------------------------------- +inline LoopingList::iterator LoopingList::findImage(AUDIOHANDLE handle) +{ + if(handle & AUDIOHANDLE_LOOPING_BIT) + { + LoopingList::iterator itr = begin(); + while(itr != end()) + { + if(areEqualHandles((*itr)->mHandle, handle)) + return(itr); + itr++; + } + } + return(0); +} + +inline int QSORT_CALLBACK loopingImageSort(const void * p1, const void * p2) +{ + const LoopingImage * ip1 = *(const LoopingImage**)p1; + const LoopingImage * ip2 = *(const LoopingImage**)p2; + + // min->max + return ip2->mScore - ip1->mScore; +} + +void LoopingList::sort() +{ + dQsort(address(), size(), sizeof(LoopingImage*), loopingImageSort); +} + +//------------------------------------------------------------------------- +LoopingImage * createLoopingImage() +{ + LoopingImage *image; + if (mLoopingFreeList.size()) + { + image = mLoopingFreeList.last(); + mLoopingFreeList.pop_back(); + } + else + image = new LoopingImage; + return(image); +} + +//------------------------------------------------------------------------- +static AUDIOHANDLE getNewHandle() +{ + mLastHandle++; + mLastHandle &= HANDLE_MASK; + if (mLastHandle == NULL_AUDIOHANDLE) + mLastHandle++; + return mLastHandle; +} +} // namespace {} + +//------------------------------------------------------------------------- +// function declarations +void alxLoopingUpdate(); +void alxUpdateScores(bool); + +//------------------------------------------------------------------------- +static void alxFreeVoiceStream(VectorPtr::iterator itr) +{ + VoiceStream *vs = *itr; + sVoiceStreams.erase_fast(itr); + sVoiceStreams_free.push_back(vs); + + vs->mHandle = NULL_AUDIOHANDLE; + vs->mDecoderStream.close(); + + Con::evaluatef("clientCmdPlayerStoppedTalking(%d, 1);", vs->mClientId); +} + +static void alxFreeVoiceStream(AUDIOHANDLE handle) +{ + VectorPtr::iterator itr = sVoiceStreams.begin(); + for (; itr != sVoiceStreams.end(); itr++) + if ((*itr)->mHandle == handle) + { + alxFreeVoiceStream(itr); + return; + } +} + +inline VectorPtr::iterator alxFindVoiceStream(AUDIOHANDLE handle) +{ + for(VectorPtr::iterator itr = sVoiceStreams.begin(); itr != sVoiceStreams.end(); itr++) + if(areEqualHandles((*itr)->mHandle, handle)) + return(itr); + return(0); +} + +static bool findFreeSource(U32 *index) +{ + for(U32 i = 0; i < mNumSources; i++) + if(mHandle[i] == NULL_AUDIOHANDLE) + { + *index = i; + return(true); + } + return(false); +} + +//-------------------------------------------------------------------------- +// - cull out the min source that is below volume +// - streams/voice/loading streams are all scored > 2 +// - volumes are attenuated by channel only +static bool cullSource(U32 *index, F32 volume) +{ + alGetError(); + + F32 minVolume = volume; + S32 best = -1; + for(S32 i = 0; i < mNumSources; i++) + { + if(mScore[i] < minVolume) + { + minVolume = mScore[i]; + best = i; + } + } + + if(best == -1) + return(false); + + // check if culling a looper + LoopingList::iterator itr = mLoopingList.findImage(mHandle[best]); + if(itr) + { + // check if culling an inactive looper + if(mHandle[best] & AUDIOHANDLE_INACTIVE_BIT) + { + AssertFatal(!mLoopingInactiveList.findImage(mHandle[best]), "cullSource: image already in inactive list"); + AssertFatal(!mLoopingCulledList.findImage(mHandle[best]), "cullSource: image should not be in culled list"); + mLoopingInactiveList.push_back(*itr); + } + else + { + (*itr)->mHandle |= AUDIOHANDLE_INACTIVE_BIT; + AssertFatal(!mLoopingCulledList.findImage(mHandle[best]), "cullSource: image already in culled list"); + AssertFatal(!mLoopingInactiveList.findImage(mHandle[best]), "cullSource: image should no be in inactive list"); + (*itr)->mCullTime = Platform::getRealMilliseconds(); + mLoopingCulledList.push_back(*itr); + } + } + + alSourceStop(mSource[best]); + mHandle[best] = NULL_AUDIOHANDLE; + mBuffer[best] = 0; + *index = best; + + return(true); +} + +//-------------------------------------------------------------------------- +// we want to compute approximate max volume at a particular distance +// ignore cone volume influnces +static F32 approximate3DVolume(const Audio::Description *desc, const Point3F &position) +{ + Point3F p1; + alxGetListener3f(AL_POSITION, &p1.x, &p1.y, &p1.z); + + p1 -= position; + F32 distance = p1.magnitudeSafe(); + + if(distance >= desc->mMaxDistance) + return(0.f); + else if(distance > desc->mMinDistance) + return(desc->mMinDistance / distance); + else + return 1.0f; +} + +//-------------------------------------------------------------------------- +inline U32 alxFindIndex(AUDIOHANDLE handle) +{ + for (U32 i=0; imDirect); + alSourcei(source, AL_ENV_SAMPLE_DIRECT_HF_EXT, env->mDirectHF); + alSourcei(source, AL_ENV_SAMPLE_ROOM_EXT, env->mRoom); + alSourcei(source, AL_ENV_SAMPLE_ROOM_HF_EXT, env->mRoomHF); + alSourcei(source, AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, env->mOutsideVolumeHF); + alSourcei(source, AL_ENV_SAMPLE_FLAGS_EXT, env->mFlags); + + alSourcef(source, AL_ENV_SAMPLE_OBSTRUCTION_EXT, env->mObstruction); + alSourcef(source, AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, env->mObstructionLFRatio); + alSourcef(source, AL_ENV_SAMPLE_OCCLUSION_EXT, env->mOcclusion); + alSourcef(source, AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, env->mOcclusionLFRatio); + alSourcef(source, AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, env->mOcclusionRoomRatio); + alSourcef(source, AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, env->mRoomRolloff); + alSourcef(source, AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, env->mAirAbsorption); +} + +static void alxSourceEnvironment(ALuint source, LoopingImage * image) +{ + AssertFatal(image, "alxSourceEnvironment: invalid looping image"); + if(image->mDescription.mIs3D) + alxSourceEnvironment(source, image->mDescription.mEnvironmentLevel, image->mEnvironment); +} + +//-------------------------------------------------------------------------- +// setup a source to play... loopers have pitch/pan cached +// - by default, pitch is 1x and pan is center (settings not defined in description) +// - all the settings are cached by openAL (miles version), so no worries setting them here +static void alxSourcePlay(ALuint source, Resource buffer, const Audio::Description *desc, const MatrixF *transform) +{ + alSourcei(source, AL_BUFFER, buffer->getALBuffer()); + alSourcef(source, AL_GAIN_LINEAR, desc->mVolume * mAudioTypeVolume[desc->mType] * mMasterVolume); + alSourcei(source, AL_SOURCE_LOOPING, desc->mIsLooping ? AL_TRUE : AL_FALSE); + alSourcef(source, AL_PITCH, 1.f); + + // stream sources are only setup for music streams (below...) + alSourcei(source, AL_STREAMING, AL_FALSE); + + // 3d? + if(transform != NULL) + { + alSourcei(source, AL_SOURCE_AMBIENT, AL_FALSE); + alSourcei(source, AL_CONE_INNER_ANGLE, desc->mConeInsideAngle); + alSourcei(source, AL_CONE_OUTER_ANGLE, desc->mConeOutsideAngle); + alSourcef(source, AL_CONE_OUTER_GAIN, desc->mConeOutsideVolume); + + Point3F p; + transform->getColumn(3, &p); + alSource3f(source, AL_POSITION, p.x, p.y, p.z); + + transform->getRow(1, &p); + alSource3f(source, AL_DIRECTION, p.x, p.y, p.z); + + // forcing different falloffs? + if(mDisableOuterFalloffs) + { + alSourcef(source, AL_MIN_DISTANCE, mInnerFalloffScale * desc->mMinDistance); + alSourcef(source, AL_MAX_DISTANCE, FORCED_OUTER_FALLOFF); + } + else + { + alSourcef(source, AL_MIN_DISTANCE, desc->mMinDistance); + alSourcef(source, AL_MAX_DISTANCE, desc->mMaxDistance); + } + + // environmental audio stuff: + alSourcef(source, AL_ENV_SAMPLE_REVERB_MIX_EXT, desc->mEnvironmentLevel); + if(desc->mEnvironmentLevel != 0.f) + alSourceResetEnvironment_EXT(source); + } + else // 2d source (default the pan) + { + alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE); + alSourcef(source, AL_PAN, 0.f); + } +} + +// helper for looping images +static void alxSourcePlay(ALuint source, LoopingImage * image) +{ + AssertFatal(image, "alxSourcePlay: invalid looping image"); + + // 3d source? need position/direction + if(image->mDescription.mIs3D) + { + MatrixF transform(true); + + transform.setColumn(3, image->mPosition); + transform.setRow(1, image->mDirection); + + alxSourcePlay(source, image->mBuffer, &image->mDescription, &transform); + } + else // 2d source? + { + alxSourcePlay(source, image->mBuffer, &image->mDescription, 0); + + // set the pan/pitch... + alxSourcef(source, AL_PITCH, image->mPitch); + alxSourcef(source, AL_PAN, image->mPan); + } +} + +//-------------------------------------------------------------------------- +AUDIOHANDLE alxCreateSource(const Audio::Description *desc, + const char *filename, + const MatrixF *transform, + AudioSampleEnvironment *sampleEnvironment) +{ + if(desc == NULL || filename == NULL || *filename == '\0') + return NULL_AUDIOHANDLE; + + F32 volume = desc->mVolume; + + // calculate an approximate attenuation for 3d sounds + if(transform && desc->mIs3D) + { + Point3F position; + transform->getColumn(3, &position); + volume *= approximate3DVolume(desc, position); + } + + // check the type specific volume + AssertFatal(desc->mType < Audio::NumAudioTypes, "alxCreateSource: invalid type for source"); + if(desc->mType >= Audio::NumAudioTypes) + return(NULL_AUDIOHANDLE); + + // done if channel is muted (and not a looper) + if(!desc->mIsLooping && (mAudioTypeVolume[desc->mType] == 0.f)) + return(NULL_AUDIOHANDLE); + + // scale volume by channel attenuation + volume *= mAudioTypeVolume[desc->mType]; + + // non-loopers don't add if < minvolume + if(!desc->mIsLooping && (volume <= MIN_GAIN)) + return(NULL_AUDIOHANDLE); + + U32 index = MAX_AUDIOSOURCES; + + // try and find an available source: 0 volume loopers get added to inactive list + if(volume > MIN_GAIN) + { + if(!findFreeSource(&index)) + { + alxUpdateScores(true); + + // scores do not include master volume + if(!cullSource(&index, volume)) + index = MAX_AUDIOSOURCES; + } + } + + // make sure that loopers are added + if(index == MAX_AUDIOSOURCES) + { + if(desc->mIsLooping) + { + Resource buffer = AudioBuffer::find(filename); + if(!(bool)buffer) + return(NULL_AUDIOHANDLE); + + // create the inactive looping image + LoopingImage * image = createLoopingImage(); + + image->mHandle = getNewHandle() | AUDIOHANDLE_LOOPING_BIT | AUDIOHANDLE_INACTIVE_BIT; + image->mBuffer = buffer; + image->mDescription = *desc; + image->mScore = volume; + image->mEnvironment = sampleEnvironment; + + // grab position/direction if 3d source + if(transform) + { + transform->getColumn(3, &image->mPosition); + transform->getRow(1, &image->mDirection); + } + + AssertFatal(!mLoopingInactiveList.findImage(image->mHandle), "alxCreateSource: handle in inactive list"); + AssertFatal(!mLoopingCulledList.findImage(image->mHandle), "alxCreateSource: handle in culled list"); + + // add to the looping and inactive lists + mLoopingList.push_back(image); + mLoopingInactiveList.push_back(image); + return(image->mHandle & RETURN_MASK); + } + else + return(NULL_AUDIOHANDLE); + } + + // clear the error state + alGetError(); + + // grab the buffer + Resource buffer = AudioBuffer::find(filename); + if((bool)buffer == false) + return NULL_AUDIOHANDLE; + + // init the source (created inactive) and store needed values + mHandle[index] = getNewHandle() | AUDIOHANDLE_INACTIVE_BIT; + mType[index] = desc->mType; + mBuffer[index] = buffer; + mScore[index] = volume; + mSourceVolume[index] = desc->mVolume; + mSampleEnvironment[index] = sampleEnvironment; + + ALuint source = mSource[index]; + + // setup play info + alxSourcePlay(source, buffer, desc, desc->mIs3D ? transform : 0); + if(mEnvironmentEnabled) + alxSourceEnvironment(source, desc->mEnvironmentLevel, sampleEnvironment); + + // setup a LoopingImage ONLY if the sound is a looper: + if(desc->mIsLooping) + { + mHandle[index] |= AUDIOHANDLE_LOOPING_BIT; + + LoopingImage * image = createLoopingImage(); + image->mHandle = mHandle[index]; + image->mBuffer = buffer; + image->mDescription = *desc; + image->mScore = volume; + image->mEnvironment = sampleEnvironment; + + // grab position/direction + if(transform) + { + transform->getColumn(3, &image->mPosition); + transform->getRow(1, &image->mDirection); + } + + AssertFatal(!mLoopingInactiveList.findImage(image->mHandle), "alxCreateSource: handle in inactive list"); + AssertFatal(!mLoopingCulledList.findImage(image->mHandle), "alxCreateSource: handle in culled list"); + + // add to the looping list + mLoopingList.push_back(image); + } + + // clear off all but looping bit + return(mHandle[index] & RETURN_MASK); +} + +//------------------------------------------------------------------------------ +AUDIOHANDLE alxCreateSource(AudioDescription *descObject, + const char *filename, + const MatrixF *transform, + AudioSampleEnvironment * sampleEnvironment ) +{ + if(!descObject || !descObject->getDescription()) + return(NULL_AUDIOHANDLE); + return (alxCreateSource(descObject->getDescription(), filename, transform, sampleEnvironment)); +} + +AUDIOHANDLE alxCreateSource(const AudioProfile *profile, const MatrixF *transform) +{ + if (profile == NULL) + return NULL_AUDIOHANDLE; + + return alxCreateSource(profile->mDescriptionObject, profile->mFilename, transform, profile->mSampleEnvironment); +} + +//-------------------------------------------------------------------------- +extern void threadPlay(AudioBuffer * buffer, AUDIOHANDLE handle); + +AUDIOHANDLE alxPlay(AUDIOHANDLE handle) +{ + U32 index = alxFindIndex(handle); + + if(index != MAX_AUDIOSOURCES) + { + // play if not already playing + if(mHandle[index] & AUDIOHANDLE_INACTIVE_BIT) + { + // jff: thread stuff +// // have the thread play this once the buffer is loaded +// if(bool(mBuffer[index]) && mBuffer[index]->isLoading()) +// { +// // move loopers into the inactive list +// LoopingList::iterator itr = mLoopingList.findImage(handle); +// if(itr) +// { +// mHandle[index] = NULL_AUDIOHANDLE; +// mBuffer[index] = 0; +// +// (*itr)->mHandle |= AUDIOHANDLE_LOADING_BIT; +// mLoopingInactiveList.push_back(*itr); +// } +// +// mHandle[index] |= AUDIOHANDLE_LOADING_BIT; +// +// if(gAudioThread) +// gAudioThread->setBufferPlayHandle(mBuffer[index], handle); +// return(handle); +// } + + mHandle[index] &= ~(AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT); + + // make sure the looping image also clears it's inactive bit + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + (*itr)->mHandle &= ~(AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT); + + alSourcePlay(mSource[index]); + + return(handle); + } + } + else + { + // move inactive loopers to the culled list, try to start the sound + LoopingList::iterator itr = mLoopingInactiveList.findImage(handle); + if(itr) + { + AssertFatal(!mLoopingCulledList.findImage(handle), "alxPlay: image already in culled list"); + mLoopingCulledList.push_back(*itr); + mLoopingInactiveList.erase_fast(itr); + alxLoopingUpdate(); + } + else if(mLoopingCulledList.findImage(handle)) + { + alxLoopingUpdate(); + } + else + return(NULL_AUDIOHANDLE); + } + + return(handle); +} + +//-------------------------------------------------------------------------- +// helper function.. create a source and play it +AUDIOHANDLE alxPlay(const AudioProfile *profile, const MatrixF *transform, const Point3F* /*velocity*/) +{ + if(profile == NULL) + return NULL_AUDIOHANDLE; + + AUDIOHANDLE handle = alxCreateSource(profile->mDescriptionObject, profile->mFilename, transform, profile->mSampleEnvironment); + if(handle != NULL_AUDIOHANDLE) + return(alxPlay(handle)); + return(handle); +} + +//-------------------------------------------------------------------------- +void alxStop(AUDIOHANDLE handle) +{ + U32 index = alxFindIndex(handle); + + // stop it + if(index != MAX_AUDIOSOURCES) + { +// jff: will have problems if buffer still loading + if(!(mHandle[index] & AUDIOHANDLE_INACTIVE_BIT)) + { + alSourceStop(mSource[index]); + } + + mSampleEnvironment[index] = 0; + mHandle[index] = NULL_AUDIOHANDLE; + mBuffer[index] = 0; + } + + // remove loopingImage and add it to the free list + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + // remove from inactive/culled list + if((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT) + { + LoopingList::iterator tmp = mLoopingInactiveList.findImage(handle); + + // inactive? + if(tmp) + mLoopingInactiveList.erase_fast(tmp); + else + { + //culled? + tmp = mLoopingCulledList.findImage(handle); + AssertFatal(tmp, "alxStop: failed to find inactive looping source"); + mLoopingCulledList.erase_fast(tmp); + } + } + + AssertFatal(!mLoopingInactiveList.findImage((*itr)->mHandle), "alxStop: handle in inactive list"); + AssertFatal(!mLoopingCulledList.findImage((*itr)->mHandle), "alxStop: handle in culled list"); + + // remove it + (*itr)->clear(); + mLoopingFreeList.push_back(*itr); + mLoopingList.erase_fast(itr); + } +} + +//-------------------------------------------------------------------------- +void alxStopAll() +{ + // stop all open sources + for(S32 i = mNumSources - 1; i >= 0; i--) + if(mHandle[i] != NULL_AUDIOHANDLE) + alxStop(mHandle[i]); + + // stop all looping sources + while(mLoopingList.size()) + alxStop(mLoopingList.last()->mHandle); +} + +void alxLoopSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_PAN: + (*itr)->mPan = value; + break; + case AL_GAIN: + (*itr)->mDescription.mVolume = Audio::DBToLinear(value); + break; + case AL_GAIN_LINEAR: + (*itr)->mDescription.mVolume = value; + break; + case AL_PITCH: + (*itr)->mPitch = value; + break; + case AL_MIN_DISTANCE: + (*itr)->mDescription.mMinDistance = value; + break; + case AL_CONE_OUTER_GAIN: + (*itr)->mDescription.mMaxDistance = value; + break; + } + } +} + +void alxLoopSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_POSITION: + (*itr)->mPosition.x = value1; + (*itr)->mPosition.y = value2; + (*itr)->mPosition.z = value3; + break; + + case AL_DIRECTION: + (*itr)->mDirection.x = value1; + (*itr)->mDirection.y = value2; + (*itr)->mDirection.z = value3; + break; + } + } +} + +void alxLoopSourcei(AUDIOHANDLE handle, ALenum pname, ALint value) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_SOURCE_AMBIENT: + (*itr)->mDescription.mIs3D = value; + break; + case AL_CONE_INNER_ANGLE: + (*itr)->mDescription.mConeInsideAngle = value; + break; + case AL_CONE_OUTER_ANGLE: + (*itr)->mDescription.mConeOutsideAngle = value; + break; + } + } +} + +void alxLoopGetSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat *value) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_PAN: + *value = (*itr)->mPan; + break; + case AL_GAIN: + *value = Audio::linearToDB((*itr)->mDescription.mVolume); + break; + case AL_GAIN_LINEAR: + *value = (*itr)->mDescription.mVolume; + break; + case AL_PITCH: + *value = (*itr)->mPitch; + break; + case AL_MIN_DISTANCE: + *value = (*itr)->mDescription.mMinDistance; + break; + case AL_CONE_OUTER_GAIN: + *value = (*itr)->mDescription.mMaxDistance; + break; + } + } +} + +void alxLoopGetSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_POSITION: + *value1 = (*itr)->mPosition.x; + *value2 = (*itr)->mPosition.y; + *value3 = (*itr)->mPosition.z; + break; + + case AL_DIRECTION: + *value1 = (*itr)->mDirection.x; + *value2 = (*itr)->mDirection.y; + *value3 = (*itr)->mDirection.z; + break; + } + } +} + +void alxLoopGetSourcei(AUDIOHANDLE handle, ALenum pname, ALint *value) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_SOURCE_AMBIENT: + *value = (*itr)->mDescription.mIs3D; + break; + case AL_SOURCE_LOOPING: + *value = true; + break; + case AL_CONE_INNER_ANGLE: + *value = (*itr)->mDescription.mConeInsideAngle; + break; + case AL_CONE_OUTER_ANGLE: + *value = (*itr)->mDescription.mConeOutsideAngle; + break; + } + } +} + +//-------------------------------------------------------------------------- +// AL get/set methods: Source +//-------------------------------------------------------------------------- +// - only need to worry about playing sources.. proper volume gets set on +// create source (so, could get out of sync if someone changes volume between +// a createSource and playSource call...) +void alxUpdateTypeGain(U32 typeMask) +{ + for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) + { + if(mHandle[i] == NULL_AUDIOHANDLE) + continue; + + if(!(typeMask & (1 << mType[i]))) + continue; + + ALint state = AL_STOPPED; + alGetSourcei(mSource[i], AL_SOURCE_STATE, &state); + + if(state == AL_PLAYING) + { + // volume = SourceVolume * ChannelVolume * MasterVolume + F32 vol = mSourceVolume[i] * mAudioTypeVolume[mType[i]] * mMasterVolume; + alSourcef(mSource[i], AL_GAIN_LINEAR, mClampF(vol, 0.f, 1.f)); + } + } +} + +void alxSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value) +{ + ALuint source = alxFindSource(handle); + + if(source != INVALID_SOURCE) + { + // ensure gain_linear + if(pname == AL_GAIN) + { + value = Audio::DBToLinear(value); + pname = AL_GAIN_LINEAR; + } + + // need to process gain settings (so source can be affected by channel/master gains) + if(pname == AL_GAIN_LINEAR) + { + U32 idx = alxFindIndex(handle); + AssertFatal(idx != MAX_AUDIOSOURCES, "alxSourcef: handle not located for found source"); + if(idx == MAX_AUDIOSOURCES) + return; + + // update the stored value + mSourceVolume[idx] = value; + + // volume = SourceVolume * ChannelVolume * MasterVolume + F32 vol = mSourceVolume[idx] * mAudioTypeVolume[mType[idx]] * mMasterVolume; + alSourcef(source, AL_GAIN_LINEAR, mClampF(vol, 0.f, 1.f)); + } + else + alSourcef(source, pname, value); + } + alxLoopSourcef(handle, pname, value); +} + +void alxSourcefv(AUDIOHANDLE handle, ALenum pname, ALfloat *values) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + alSourcefv(source, pname, values); + + if((pname == AL_POSITION) || (pname == AL_DIRECTION) || (pname == AL_VELOCITY)) + alxLoopSource3f(handle, pname, values[0], values[1], values[2]); +} + +void alxSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + { + ALfloat values[3]; + values[0] = value1; + values[1] = value2; + values[2] = value3; + alSourcefv(source, pname, values); + } + alxLoopSource3f(handle, pname, value1, value2, value3); +} + +void alxSourcei(AUDIOHANDLE handle, ALenum pname, ALint value) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + alSourcei(source, pname, value); + alxLoopSourcei(handle, pname, value); +} + +// sets the position and direction of the source +void alxSourceMatrixF(AUDIOHANDLE handle, const MatrixF *transform) +{ + ALuint source = alxFindSource(handle); + + Point3F pos; + transform->getColumn(3, &pos); + + Point3F dir; + transform->getRow(1, &dir); + + if(source != INVALID_SOURCE) + { + alSource3f(source, AL_POSITION, pos.x, pos.y, pos.z); + alSource3f(source, AL_DIRECTION, dir.x, dir.y, dir.z); + } + + alxLoopSource3f(handle, AL_POSITION, pos.x, pos.y, pos.z); + alxLoopSource3f(handle, AL_DIRECTION, dir.x, dir.y, dir.z); +} + +//-------------------------------------------------------------------------- +void alxGetSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat *value) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + { + // gain queries return unattenuated values + if((pname == AL_GAIN) || (pname == AL_GAIN_LINEAR)) + { + U32 idx = alxFindIndex(handle); + AssertFatal(idx != MAX_AUDIOSOURCES, "alxGetSourcef: found source but handle is invalid"); + if(idx == MAX_AUDIOSOURCES) + { + *value = 0.f; + return; + } + + if(pname == AL_GAIN) + *value = Audio::linearToDB(mSourceVolume[idx]); + else + *value = mSourceVolume[idx]; + } + else + alGetSourcef(source, pname, value); + } + else + alxLoopGetSourcef(handle, pname, value); +} + +void alxGetSourcefv(AUDIOHANDLE handle, ALenum pname, ALfloat *values) +{ + if((pname == AL_POSITION) || (pname == AL_DIRECTION) || (pname == AL_VELOCITY)) + alxGetSource3f(handle, pname, &values[0], &values[1], &values[2]); +} + +void alxGetSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + { + ALfloat values[3]; + alGetSourcefv(source, pname, values); + *value1 = values[0]; + *value2 = values[1]; + *value3 = values[2]; + } + else + alxLoopGetSource3f(handle, pname, value1, value2, value3); +} + +void alxGetSourcei(AUDIOHANDLE handle, ALenum pname, ALint *value) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + alGetSourcei(source, pname, value); + else + alxLoopGetSourcei(handle, pname, value); +} + +//-------------------------------------------------------------------------- +// AL get/set methods: Listener +//-------------------------------------------------------------------------- +void alxListenerf(ALenum pname, ALfloat value) +{ +#ifdef HANDLE_LISTENER_GAIN + // listener gain is handled through wrapper + if((pname == AL_GAIN) || (pname == AL_GAIN_LINEAR)) + { + // just handles al_gain_linear + if(pname == AL_GAIN) + value = Audio::DBToLinear(value); + + mMasterVolume = mClampF(value, 0.f, 1.f); + + // update all the sources + alxUpdateTypeGain(0xffffffff); + } + else + alListenerf(pname, value); +#else + alListenerf(pname, value); +#endif +} + +void alxListenerfv(ALenum pname, ALfloat * values) +{ + alListenerfv(pname, values); +} + +void alxListener3f(ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3) +{ + alListener3f(pname, value1, value2, value3); +} + +//-------------------------------------------------------------------------- +// set the listener's position and orientation +void alxListenerMatrixF(const MatrixF *transform) +{ + Point3F p1, p2; + transform->getColumn(3, &p1); + alListener3f(AL_POSITION, p1.x, p1.y, p1.z); + + transform->getColumn(2, &p1); // Up Vector + transform->getColumn(1, &p2); // Forward Vector + F32 orientation[6]; + orientation[0] = p1.x; + orientation[1] = p1.y; + orientation[2] = p1.z; + orientation[3] = p2.x; + orientation[4] = p2.y; + orientation[5] = p2.z; + alListenerfv(AL_ORIENTATION, orientation); +} + +//-------------------------------------------------------------------------- +void alxGetListenerf(ALenum pname, ALfloat *value) +{ +#ifdef HANDLE_LISTENER_GAIN + // listener gain is handled through wrapper + if(pname == AL_GAIN) + *value = Audio::linearToDB(mMasterVolume); + else if(pname == AL_GAIN_LINEAR) + *value = mMasterVolume; + else + alGetListenerf(pname, value); +#else + alGetListenerf(pname, value); +#endif +} + +void alxGetListenerfv(ALenum pname, ALfloat *values) +{ + alGetListenerfv(pname, values); +} + +void alxGetListener3f(ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3) +{ + ALfloat val[3]; + alGetListenerfv(pname, val); + *value1 = val[0]; + *value2 = val[1]; + *value3 = val[2]; +} + +void alxGetListeneri(ALenum pname, ALint *value) +{ + alGetListeneri(pname, value); +} + +//-------------------------------------------------------------------------- +// Capture methods +//-------------------------------------------------------------------------- +void alxCaptureDestroy() +{ + if(mCaptureInitialized) + alCaptureDestroy_EXT(); + + mVoiceEncoderStream.setCodec(AUDIO_CODEC_NONE); + AudioCodecManager::destroy(); + + mCaptureInitialized = false; +} + +bool alxCaptureInit() +{ + alxCaptureDestroy(); + + mCaptureInitialized = alCaptureInit_EXT(RECORD_FORMAT, RECORD_SPEED, RECORD_BUFFERSIZE); + if(!mCaptureInitialized) + return(false); + + // 0: <.v12> 1: <.v24> 2: <.v29> 3: + #ifdef USE_GSM_CODEC + S32 encodingId = Con::getIntVariable("$pref::Audio::encodingLevel", AUDIO_CODEC_GSM); + S32 decodingMask = Con::getIntVariable("$pref::Audio::decodingMask", 1 << AUDIO_CODEC_GSM) + #else + S32 encodingId = Con::getIntVariable("$pref::Audio::encodingLevel", AUDIO_CODEC_V12); + S32 decodingMask = Con::getIntVariable("$pref::Audio::decodingMask", 1 << AUDIO_CODEC_V12); + #endif + + // bring up the encoder (decoders instantiated when needed..) + if(!mVoiceEncoderStream.setCodec(encodingId) || !mVoiceEncoderStream.open()) + return(false); + + return(true); +} + +//-------------------------------------------------------------------------- +void alxCaptureStart(bool local) +{ + if(!mCaptureInitialized || mRecording) + return; + + mLocalCapture = local; + if(mLocalCapture) + { + mLocalCaptureBuffer = (U8 *)dMalloc(LOCAL_CAPTURE_SIZE); + mLocalCaptureBufferPos = 0; + Con::executef(2, "localCaptureStart", "record"); + } + else + { + GameConnection* connection = GameConnection::getServerConnection(); + if(!connection) + { + Con::errorf(ConsoleLogEntry::General, "alxCaptureStart: no server connection"); + return; + } + mVoiceEncoderStream.setConnection(connection); + } + sCaptureTimeout = Platform::getRealMilliseconds(); + + alCaptureStart_EXT(); + mRecording = true; +} + +void scaleSamples(void * data, U32 size, U32 format, F32 scale) +{ + if((format != AL_FORMAT_MONO16) || (scale == 1.f)) + return; + + S16 * pData = (S16 *)data; + U32 samples = size >> 1; + + for(U32 i = 0; i < samples; i++) + { + S32 samp = pData[i]; + samp *= scale; + pData[i] = mClamp(samp, S16_MIN, S16_MAX); + } +} + +static void alxCaptureSend(bool flush = false) +{ + U32 size; + U8 buffer[CAPTURE_BUFFER_SIZE]; + do { + size = alCaptureGetData_EXT(buffer, CAPTURE_BUFFER_SIZE, RECORD_FORMAT, RECORD_SPEED); + scaleSamples(buffer, size, RECORD_FORMAT, mCaptureGainScale); + mVoiceEncoderStream.setBuffer(buffer, size); + mVoiceEncoderStream.process(); + } while ((flush && size) || (size == CAPTURE_BUFFER_SIZE)); +} + +void alxBufferCapture() +{ + S32 len = LOCAL_CAPTURE_SIZE - mLocalCaptureBufferPos; + if(len > 0) + { + U32 size = alCaptureGetData_EXT(mLocalCaptureBuffer + mLocalCaptureBufferPos, len, RECORD_FORMAT, RECORD_SPEED); + scaleSamples(mLocalCaptureBuffer + mLocalCaptureBufferPos, size, RECORD_FORMAT, mCaptureGainScale); + mLocalCaptureBufferPos += size; + } +} + +// called from Miles thread... must process in main thread +void localCaptureFinishedCallback(U32, bool) +{ + mLocalCaptureFinished = true; +} + +void alxCaptureStop() +{ + if(!mCaptureInitialized || !mRecording) + return; + + mRecording = false; + sCaptureTimeout = 0; + alCaptureStop_EXT(); + + if(mLocalCapture && mLocalCaptureBuffer) + { + Con::executef(2, "localCaptureStop", "record"); + + alGenBuffers(1, &mALCaptureBuffer); + if(alGetError() != AL_NO_ERROR) + return; + + alBufferData(mALCaptureBuffer, RECORD_FORMAT, mLocalCaptureBuffer, mLocalCaptureBufferPos, RECORD_SPEED); + dFree(mLocalCaptureBuffer); + mLocalCaptureBuffer = 0; + + U32 index; + if(findFreeSource(&index)) + { + mHandle[index] = getNewHandle() | AUDIOHANDLE_INACTIVE_BIT; + mType[index] = Audio::DefaultAudioType; + ALuint source = mSource[index]; + + alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE); + alSourcei(source, AL_SOURCE_LOOPING, AL_FALSE); + alSourcei(source, AL_BUFFER, mALCaptureBuffer); + alSourcef(source, AL_GAIN_LINEAR, mAudioTypeVolume[Audio::DefaultAudioType]); + + alSourceCallback_EXT(source, localCaptureFinishedCallback); + alxPlay(mHandle[index]); + Con::executef(2, "localCaptureStart", "play"); + } + else + alDeleteBuffers(1, &mALCaptureBuffer); + } + else + { + alxCaptureSend(true); + mVoiceEncoderStream.flush(); + } +} + +bool alxIsCapturing() +{ + return(mRecording); +} + +void alxCaptureUpdate() +{ + // check if should destroy local capture buffer + if(mLocalCaptureFinished) + { + alDeleteBuffers(1, &mALCaptureBuffer); + Con::executef(2, "localCaptureStop", "play"); + mLocalCaptureFinished = false; + } + + // check if need to stop capturing + if(mCaptureInitialized && sCaptureTimeout != 0) + { + if((Platform::getRealMilliseconds()-sCaptureTimeout) > RECORD_LEN) + alxCaptureStop(); + else + { + if(mLocalCapture) + alxBufferCapture(); + else + alxCaptureSend(); + } + } +} + +//-------------------------------------------------------------------------- +// Voice streams +//-------------------------------------------------------------------------- +Vector::iterator alxFindStream(U8 codecId, U32 clientId, U8 streamId) +{ + Vector::iterator itr = sVoiceStreams.begin(); + for (; itr != sVoiceStreams.end(); itr++) + { + VoiceStream *vs = *itr; + if (vs->mCodecId == codecId && vs->mClientId == clientId && vs->mStreamId == streamId) + return itr; + } + return NULL; +} + +Vector::iterator alxNewStream(U8 codecId, U32 clientId, U8 streamId) +{ + if(codecId >= AUDIO_NUM_CODECS) + return(0); + + // need to get a new stream + VoiceStream *vs; + if (sVoiceStreams_free.size()) + { + vs = sVoiceStreams_free.last(); + sVoiceStreams_free.pop_back(); + } + else + vs = new VoiceStream; + + vs->mCodecId = codecId; + vs->mClientId = clientId; + vs->mStreamId = streamId; + + // open the codec + if(!vs->mDecoderStream.setCodec(codecId) || !vs->mDecoderStream.open()) + { + delete vs; + return(0); + } + + sVoiceStreams.push_back(vs); + return &sVoiceStreams.last(); +} + +void alxPlayStream(U8 codecId, U32 clientId, U8 streamId) +{ + Vector::iterator itr = alxFindStream(codecId, clientId, streamId); + if (itr == NULL) + return; + + Con::evaluatef("clientCmdPlayerStartTalking(%d, 1);", clientId); + VoiceStream *vs = *itr; + + alGenBuffers(1, &vs->mBuffer); + vs->mDecoderStream.process(); + U8 *data; + U32 size; + vs->mDecoderStream.getBuffer(&data, &size); + if(size) + { + alBufferData(vs->mBuffer, RECORD_FORMAT, data, size, RECORD_SPEED); + + // find an available source, if none then (hopefully) force a cull + U32 index; + if(!findFreeSource(&index)) + if(!cullSource(&index, 2.0f)) + return; + + // clear the error state + alGetError(); + + // init and play the source + mHandle[index] = getNewHandle() | AUDIOHANDLE_VOICE_BIT; + mSourceVolume[index] = mAudioTypeVolume[Audio::VoiceAudioType]; + vs->mHandle = mHandle[index]; + ALuint source = mSource[index]; + + alSourcei(source, AL_BUFFER, vs->mBuffer); + alSourcei(source, AL_SOURCE_LOOPING, AL_FALSE); + alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE); + alSourcef(source, AL_GAIN_LINEAR, mSourceVolume[index] * mMasterVolume); + alSourcePlay(source); + } + else + Con::printf("Decode size is ZERO! client %d", clientId); + + vs->mDecoderStream.close(); +} + +//-------------------------------------------------------------------------- +void alxReceiveVoiceStream(SimVoiceStreamEvent *event) +{ + Vector::iterator itr; + if (event->mSequence == 0) + itr = alxNewStream(event->mCodecId, event->mClientId, event->mStreamId); + else + itr = alxFindStream(event->mCodecId, event->mClientId, event->mStreamId); + + if (itr) + { + if (event->getSize()) + (*itr)->mDecoderStream.setBuffer(event->getData(), event->getSize()); + + if (event->getSize() < SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE) + alxPlayStream(event->mCodecId, event->mClientId, event->mStreamId); + } +} + +// Music stream: ----------------------------------------------------------- +// called from Miles thread... must process in main thread +void streamCallback(U32, bool stopped) +{ + mFinishedMusicStream = true; + mFinishedMusicStreamStopState = stopped; +} + +void alxPlayMusicStream(const char * filename) +{ + if(!alIsBuffer(mStreamBuffer)) + { + alGenBuffers(1, &mStreamBuffer); + if(alGetError() != AL_NO_ERROR) + return; + + // streamed buffers should not be managed + alBufferi_EXT(mStreamBuffer, AL_BUFFER_KEEP_RESIDENT, AL_TRUE); + } + + alxStop(mStreamHandle); + + if(!alBufferStreamFile_EXT(mStreamBuffer, (const ALubyte *)filename)) + { + const char * error = (const char *)alGetString(alGetError()); + Con::errorf(ConsoleLogEntry::General, "alxStartStream: %s", error); + } + + U32 index; + if(findFreeSource(&index)) + { + mHandle[index] = getNewHandle() | AUDIOHANDLE_INACTIVE_BIT; + mType[index] = Audio::MusicAudioType; + mSourceVolume[index] = mAudioTypeVolume[Audio::MusicAudioType]; + + ALuint source = mSource[index]; + alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE); + alSourcei(source, AL_STREAMING, AL_TRUE); + alSourcei(source, AL_BUFFER, mStreamBuffer); + alSourcef(source, AL_GAIN_LINEAR, mSourceVolume[index] * mMasterVolume); + + alSourceCallback_EXT(source, streamCallback); + alxPlay(mHandle[index]); + mStreamHandle = mHandle[index]; + } + else + { + Con::errorf(ConsoleLogEntry::General, "alxStartStream: no free sources"); + alDeleteBuffers(1, &mStreamBuffer); + } +} + +void alxStopMusicStream() +{ + alxStop(mStreamHandle); + if(alIsBuffer(mStreamBuffer)) + alDeleteBuffers(1, &mStreamBuffer); +} + +// checks if the music stream is done and calls into console.. +void alxStreamUpdate() +{ + if(mFinishedMusicStream) + { + Con::executef(2, "finishedMusicStream", mFinishedMusicStreamStopState ? "true" : "false"); + mFinishedMusicStream = false; + } +} + +//-------------------------------------------------------------------------- +// This is a somewhat crappy way of doing this, but under win32 the outer falloff +// is not always the distance at which the gain is clamped to 0 (some drivers +// ignore this setting on the dsound 3dbuffer). So, if this is enabled then +// the outer falloffs of all sources is pushed out to MAX_FALLOFF and the inner ones +// are scaled by the inner falloff scale (some hardware drivers attenuate differently) +// - sources are scaled on the play call as well +// - will trash any non-default falloffs +void alxDisableOuterFalloffs(bool disable) +{ + if(disable == mDisableOuterFalloffs) + return; + + // update all the playing sources (non-loopers cannot be reset after this) + for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) + { + if(alxIsValidHandle(mHandle[i])) + { + // only care about 3d sources... + ALint ambient = AL_FALSE; + alxGetSourcei(mHandle[i], AL_SOURCE_AMBIENT, &ambient); + if(ambient == AL_FALSE) + continue; + + if(disable) + { + ALfloat min = 1.f; + alGetSourcef(mSource[i], AL_MIN_DISTANCE, &min); + alSourcef(mSource[i], AL_MIN_DISTANCE, min * mInnerFalloffScale); + alSourcef(mSource[i], AL_MAX_DISTANCE, FORCED_OUTER_FALLOFF); + } + else + { + LoopingList::iterator itr = mLoopingList.findImage(mHandle[i]); + if(itr) + { + alSourcef(mSource[i], AL_MIN_DISTANCE, (*itr)->mDescription.mMinDistance); + alSourcef(mSource[i], AL_MAX_DISTANCE, (*itr)->mDescription.mMaxDistance); + } + } + } + } + + mDisableOuterFalloffs = disable; +} + +void alxSetInnerFalloffScale(F32 scale) +{ + mInnerFalloffScale = mClampF(scale, 0.1f, 10.f); +} + +F32 alxGetInnerFalloffScale() +{ + return(mInnerFalloffScale); +} + +//-------------------------------------------------------------------------- +// Simple metrics +//-------------------------------------------------------------------------- + +#ifdef GATHER_METRICS +static void alxGatherMetrics() +{ + S32 mNumOpenHandles = 0; + S32 mNumOpenLoopingHandles = 0; + S32 mNumOpenVoiceHandles = 0; + + S32 mNumActiveStreams = 0; + S32 mNumNullActiveStreams = 0; + S32 mNumActiveLoopingStreams = 0; + S32 mNumActiveVoiceStreams = 0; + + S32 mNumLoopingStreams = 0; + S32 mNumInactiveLoopingStreams = 0; + S32 mNumCulledLoopingStreams = 0; + + S32 mDynamicMemUsage = 0; + S32 mDynamicMemSize = 0; + S32 mMemUsage = 0; + S32 mBufferCount = 0; + S32 mDynamicBufferCount = 0; + + // count installed streams and open handles + for(U32 i = 0; i < mNumSources; i++) + { + if(mHandle[i] != NULL_AUDIOHANDLE) + { + mNumOpenHandles++; + if(mHandle[i] & AUDIOHANDLE_LOOPING_BIT) + mNumOpenLoopingHandles++; + if(mHandle[i] & AUDIOHANDLE_VOICE_BIT) + mNumOpenVoiceHandles++; + } + + ALint state = AL_STOPPED; + alGetSourcei(mSource[i], AL_SOURCE_STATE, &state); + if(state == AL_PLAYING) + { + mNumActiveStreams++; + if(mHandle[i] == NULL_AUDIOHANDLE) + mNumNullActiveStreams++; + if(mHandle[i] & AUDIOHANDLE_LOOPING_BIT) + mNumActiveLoopingStreams++; + if(mHandle[i] & AUDIOHANDLE_VOICE_BIT) + mNumActiveVoiceStreams++; + } + } + + for(LoopingList::iterator itr = mLoopingList.begin(); itr != mLoopingList.end(); itr++) + mNumLoopingStreams++; + for(LoopingList::iterator itr = mLoopingInactiveList.begin(); itr != mLoopingInactiveList.end(); itr++) + mNumInactiveLoopingStreams++; + for(LoopingList::iterator itr = mLoopingCulledList.begin(); itr != mLoopingCulledList.end(); itr++) + mNumCulledLoopingStreams++; + + alGetContexti_EXT(ALC_BUFFER_MEMORY_USAGE, &mMemUsage); + alGetContexti_EXT(ALC_BUFFER_DYNAMIC_MEMORY_SIZE, &mDynamicMemSize); + alGetContexti_EXT(ALC_BUFFER_DYNAMIC_MEMORY_USAGE, &mDynamicMemUsage); + alGetContexti_EXT(ALC_BUFFER_COUNT, &mBufferCount); + alGetContexti_EXT(ALC_BUFFER_DYNAMIC_COUNT, &mDynamicBufferCount); + + Con::setIntVariable("Audio::numOpenHandles", mNumOpenHandles); + Con::setIntVariable("Audio::numOpenLoopingHandles", mNumOpenLoopingHandles); + Con::setIntVariable("Audio::numOpenVoiceHandles", mNumOpenVoiceHandles); + + Con::setIntVariable("Audio::numActiveStreams", mNumActiveStreams); + Con::setIntVariable("Audio::numNullActiveStreams", mNumNullActiveStreams); + Con::setIntVariable("Audio::numActiveLoopingStreams", mNumActiveLoopingStreams); + Con::setIntVariable("Audio::numActiveVoiceStreams", mNumActiveVoiceStreams); + + Con::setIntVariable("Audio::numLoopingStreams", mNumLoopingStreams); + Con::setIntVariable("Audio::numInactiveLoopingStreams", mNumInactiveLoopingStreams); + Con::setIntVariable("Audio::numCulledLoopingStreams", mNumCulledLoopingStreams); + + Con::setIntVariable("Audio::memUsage", mMemUsage >> 10); + Con::setIntVariable("Audio::dynamicMemSize", mDynamicMemSize >> 10); + Con::setIntVariable("Audio::dynamicMemUsage", mDynamicMemUsage >> 10); + Con::setIntVariable("Audio::bufferCount", mBufferCount); + Con::setIntVariable("Audio::dynamicBufferCount", mDynamicBufferCount); +} +#endif + +//-------------------------------------------------------------------------- +// Audio Update... +//-------------------------------------------------------------------------- +void alxLoopingUpdate() +{ + static LoopingList culledList; + + S32 updateTime = Platform::getRealMilliseconds(); + + // check if can wakeup the inactive loopers + if(mLoopingCulledList.size()) + { + Point3F listener; + alxListenerGetPoint3F(AL_POSITION, &listener); + + // get the 'sort' value for this sound (could be based on time played...), + // and add to the culled list + LoopingList::iterator itr; + culledList.clear(); + + for(itr = mLoopingCulledList.begin(); itr != mLoopingCulledList.end(); itr++) + { + if((*itr)->mScore <= MIN_UNCULL_GAIN) + continue; + + if((updateTime - (*itr)->mCullTime) < MIN_UNCULL_PERIOD) + continue; + + culledList.push_back(*itr); + } + + if(!culledList.size()) + return; + + U32 index = MAX_AUDIOSOURCES; + + if(culledList.size() > 1) + culledList.sort(); + + for(itr = culledList.begin(); itr != culledList.end(); itr++) + { + if(!findFreeSource(&index)) + { + // score does not include master volume + if(!cullSource(&index, (*itr)->mScore)) + break; + + // check buffer + if(!bool((*itr)->mBuffer)) + { + // remove from culled list + LoopingList::iterator tmp; + tmp = mLoopingCulledList.findImage((*itr)->mHandle); + AssertFatal(tmp, "alxLoopingUpdate: failed to find culled source"); + mLoopingCulledList.erase_fast(tmp); + + // remove from looping list (and free) + tmp = mLoopingList.findImage((*itr)->mHandle); + if(tmp) + { + (*tmp)->clear(); + mLoopingFreeList.push_back(*tmp); + mLoopingList.erase_fast(tmp); + } + + continue; + } + } + + // remove from culled list + LoopingList::iterator tmp = mLoopingCulledList.findImage((*itr)->mHandle); + AssertFatal(tmp, "alxLoopingUpdate: failed to find culled source"); + mLoopingCulledList.erase_fast(tmp); + + // restore all state data + mHandle[index] = (*itr)->mHandle; + mBuffer[index] = (*itr)->mBuffer; + mScore[index] = (*itr)->mScore; + mSourceVolume[index] = (*itr)->mDescription.mVolume; + mType[index] = (*itr)->mDescription.mType; + mSampleEnvironment[index] = (*itr)->mEnvironment; + + ALuint source = mSource[index]; + + // setup play info + alGetError(); + + alxSourcePlay(source, *itr); + if(mEnvironmentEnabled) + alxSourceEnvironment(source, *itr); + + alxPlay(mHandle[index]); + } + } +} + +//-------------------------------------------------------------------------- +void alxCloseHandles() +{ + for(U32 i = 0; i < mNumSources; i++) + { + if(mHandle[i] & AUDIOHANDLE_LOADING_BIT) + continue; + + if(mHandle[i] == NULL_AUDIOHANDLE) + continue; + + ALint state = 0; + alGetSourcei(mSource[i], AL_SOURCE_STATE, &state); + if(state == AL_PLAYING) + continue; + + // voice + if(mHandle[i] & AUDIOHANDLE_VOICE_BIT) + { + VectorPtr::iterator itr = alxFindVoiceStream(mHandle[i]); + if(itr) + alxFreeVoiceStream(itr); + } + else if(!(mHandle[i] & AUDIOHANDLE_INACTIVE_BIT)) + { + // should be playing? must have encounted an error.. remove + LoopingList::iterator itr = mLoopingList.findImage(mHandle[i]); + if(itr && !((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT)) + { + AssertFatal(!mLoopingInactiveList.findImage((*itr)->mHandle), "alxCloseHandles: image incorrectly in inactive list"); + AssertFatal(!mLoopingCulledList.findImage((*itr)->mHandle), "alxCloseHandles: image already in culled list"); + mLoopingCulledList.push_back(*itr); + (*itr)->mHandle |= AUDIOHANDLE_INACTIVE_BIT; + + mHandle[i] = NULL_AUDIOHANDLE; + mBuffer[i] = 0; + } + } + + mHandle[i] = NULL_AUDIOHANDLE; + mBuffer[i] = 0; + } +} + +//---------------------------------------------------------------------------------- +// - update the score for each audio source. this is used for culing sources. +// normal ranges are between 0.f->1.f, voice/loading/music streams are scored +// outside this range so that they will not be culled +// - does not scale by attenuated volumes +void alxUpdateScores(bool sourcesOnly) +{ + Point3F listener; + alxGetListener3f(AL_POSITION, &listener.x, &listener.y, &listener.z); + + // do the base sources + for(U32 i = 0; i < mNumSources; i++) + { + if(mHandle[i] == NULL_AUDIOHANDLE) + { + mScore[i] = 0.f; + continue; + } + + // thread loading buffer or voice buffer? + if(mHandle[i] & (AUDIOHANDLE_LOADING_BIT|AUDIOHANDLE_VOICE_BIT)) + { + mScore[i] = 3.f; + continue; + } + + // streaming? + ALint val = AL_FALSE; + alGetSourcei(mSource[i], AL_STREAMING, &val); + if(val == AL_TRUE) + { + mScore[i] = 3.f; + continue; + } + + // grab the volume.. (not attenuated by master for score) + F32 volume = mSourceVolume[i] * mAudioTypeVolume[mType[i]]; + + // 3d? + val = AL_FALSE; + alGetSourcei(mSource[i], AL_SOURCE_AMBIENT, &val); + + if(val == AL_FALSE) + { + // approximate 3d volume + Point3F pos; + alGetSourcefv(mSource[i], AL_POSITION, (ALfloat * )((F32*)pos)); + + ALfloat min, max; + alGetSourcef(mSource[i], AL_MIN_DISTANCE, &min); + alGetSourcef(mSource[i], AL_MAX_DISTANCE, &max); + + pos -= listener; + F32 dist = pos.magnitudeSafe(); + + if(dist >= max) + mScore[i] = 0.f; + else if(dist > min) + mScore[i] = min / dist; + else + mScore[i] = volume; + } + else + mScore[i] = volume; + } + + if(sourcesOnly) + return; + + S32 updateTime = Platform::getRealMilliseconds(); + + // update the loopers + for(LoopingList::iterator itr = mLoopingList.begin(); itr != mLoopingList.end(); itr++) + { + if(!((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT)) + continue; + + if((updateTime - (*itr)->mCullTime) < MIN_UNCULL_PERIOD) + continue; + + if((*itr)->mDescription.mIs3D) + { + Point3F pos = (*itr)->mPosition - listener; + F32 dist = pos.magnitudeSafe(); + + F32 min = (*itr)->mDescription.mMinDistance; + F32 max = (*itr)->mDescription.mMaxDistance; + + if(dist >= max) + (*itr)->mScore = 0.f; + else if(dist > min) + (*itr)->mScore = (*itr)->mDescription.mVolume * (min / dist); + else + (*itr)->mScore = (*itr)->mDescription.mVolume; + } + else + (*itr)->mScore = (*itr)->mDescription.mVolume; + + // attenuate by the channel gain + (*itr)->mScore *= mAudioTypeVolume[(*itr)->mDescription.mType]; + } +} + +// the directx buffers are set to mute at max distance, but many of the providers seem to +// ignore this flag... that is why this is here +void alxUpdateMaxDistance() +{ +#ifndef __linux + Point3F listener; + alxGetListener3f(AL_POSITION, &listener.x, &listener.y, &listener.z); + + for(U32 i = 0; i < mNumSources; i++) + { + if(mHandle[i] == NULL_AUDIOHANDLE) + continue; + + ALint val = AL_FALSE; + alGetSourcei(mSource[i], AL_SOURCE_AMBIENT, &val); + if(val == AL_TRUE) + continue; + + val = AL_FALSE; + alGetSourcei(mSource[i], AL_STREAMING, &val); + if(val == AL_TRUE) + continue; + + Point3F pos; + alGetSourcefv(mSource[i], AL_POSITION, (F32*)pos); + + F32 dist = 0.f; + alGetSourcef(mSource[i], AL_MAX_DISTANCE, &dist); + + pos -= listener; + dist -= pos.len(); + + alSourcef(mSource[i], AL_GAIN_LINEAR, (dist < 0.f) ? 0.f : mSourceVolume[i] * mAudioTypeVolume[mType[i]] * mMasterVolume); + } +#endif +} + +//-------------------------------------------------------------------------- +// Called to update alx system +//-------------------------------------------------------------------------- +void alxUpdate() +{ + if(mForceMaxDistanceUpdate) + alxUpdateMaxDistance(); + + alxCloseHandles(); + alxUpdateScores(false); + alxLoopingUpdate(); + alxCaptureUpdate(); + alxStreamUpdate(); +#ifndef DISABLE_AUDIO_THREAD + AudioThread::process(); +#endif + +#ifdef GATHER_METRICS + alxGatherMetrics(); +#endif + +#if defined(__linux) && !defined(DEDICATED) + alxFakeCallbackUpdate(); +#endif +} + +//-------------------------------------------------------------------------- +// Misc +//-------------------------------------------------------------------------- +// client-side function only +ALuint alxGetWaveLen(ALuint buffer) +{ + if(buffer == AL_INVALID) + return(0); + + ALint frequency = 0; + ALint bits = 0; + ALint channels = 0; + ALint size; + + alGetBufferi(buffer, AL_FREQUENCY, &frequency); + alGetBufferi(buffer, AL_BITS, &bits); + alGetBufferi(buffer, AL_CHANNELS, &channels); + alGetBufferi(buffer, AL_SIZE, &size); + + if(!frequency || !bits || !channels) + { + Con::errorf(ConsoleLogEntry::General, "alxGetWaveLen: invalid buffer"); + return(0); + } + + F64 len = (F64(size) * 8000.f) / F64(frequency * bits * channels); + return(len); +} + +//-------------------------------------------------------------------------- +// Console functions +//-------------------------------------------------------------------------- +static void cAudio_detect(SimObject *, S32, const char **) +{ + Audio::detect(); +} + +static void cAudio_destroy(SimObject *, S32, const char **) +{ + Audio::destroy(); +} + +static bool cAudio_setDriver(SimObject *, S32, const char *argv[]) +{ + return(Audio::setDriver(argv[1])); +} + +//-------------------------------------------------------------------------- +static S32 cAudio_alxCreateSource(SimObject *, S32 argc, const char *argv[]) +{ + AudioDescription *description = NULL; + AudioProfile *profile = dynamic_cast( Sim::findObject( argv[1] ) ); + if (profile == NULL) + { + description = dynamic_cast( Sim::findObject( argv[1] ) ); + if (description == NULL) + { + Con::printf("Unable to locate audio profile/description '%s'", argv[1]); + return NULL_AUDIOHANDLE; + } + } + + if (profile) + { + if (argc == 2) + return alxCreateSource(profile); + + MatrixF transform; + transform.set(EulerF(0,0,0), Point3F( dAtof(argv[2]),dAtof(argv[3]),dAtof(argv[4]) )); + return alxCreateSource(profile, &transform); + } + + if (description) + { + if (argc == 3) + return alxCreateSource(description, argv[2]); + + MatrixF transform; + transform.set(EulerF(0,0,0), Point3F( dAtof(argv[3]),dAtof(argv[4]),dAtof(argv[5]) )); + return alxCreateSource(description, argv[2], &transform); + } + + return NULL_AUDIOHANDLE; +} + +//-------------------------------------------------------------------------- +// Expose all al get/set methods... +//-------------------------------------------------------------------------- +enum { + Source = BIT(0), + Listener = BIT(1), + Context = BIT(2), + Environment = BIT(3), + Get = BIT(4), + Set = BIT(5), + Int = BIT(6), + Float = BIT(7), + Float3 = BIT(8), + Float6 = BIT(9), + DebugGet = BIT(10), + DebugSet = BIT(11), + Debug = (DebugGet|DebugSet), +}; + +static ALenum getEnum(const char * name, U32 flags) +{ + AssertFatal(name, "getEnum: bad param"); + + static struct { + char * mName; + ALenum mAlenum; + U32 mFlags; + } table[] = { + //----------------------------------------------------------------------------------------------------------------- + // "name" ENUM Flags + //----------------------------------------------------------------------------------------------------------------- + { "AL_PAN", AL_PAN, (Source|Get|Set|Float) }, + { "AL_GAIN", AL_GAIN, (Source|Listener|Get|Set|Float) }, + { "AL_GAIN_LINEAR", AL_GAIN_LINEAR, (Source|Listener|Get|Set|Float) }, + { "AL_PITCH", AL_PITCH, (Source|Get|Set|Float) }, + { "AL_MIN_DISTANCE", AL_MIN_DISTANCE, (Source|Get|Set|Float) }, + { "AL_MAX_DISTANCE", AL_MAX_DISTANCE, (Source|Get|Set|Float) }, + { "AL_CONE_OUTER_GAIN", AL_CONE_OUTER_GAIN, (Source|Get|Set|Float) }, + { "AL_POSITION", AL_POSITION, (Source|Listener|Get|Set|Float3) }, + { "AL_DIRECTION", AL_DIRECTION, (Source|Get|Set|Float3) }, + { "AL_VELOCITY", AL_VELOCITY, (Source|Listener|Get|Set|Float3) }, + { "AL_ORIENTATION", AL_ORIENTATION, (Listener|Set|Float6) }, + { "AL_CONE_INNER_ANGLE", AL_CONE_INNER_ANGLE, (Source|Get|Set|Int) }, + { "AL_CONE_OUTER_ANGLE", AL_CONE_OUTER_ANGLE, (Source|Get|Set|Int) }, + { "AL_SOURCE_LOOPING", AL_SOURCE_LOOPING, (Source|Get|Set|Int) }, + { "AL_STREAMING", AL_STREAMING, (Source|Get|Set|Int) }, + { "AL_BUFFER", AL_BUFFER, (Source|Get|Set|Int) }, + { "AL_SOURCE_AMBIENT", AL_SOURCE_AMBIENT, (Source|Get|Set|Int) }, + + { "ALC_PROVIDER", ALC_PROVIDER, (Context|Get|Set|Int) }, + { "ALC_PROVIDER_COUNT", ALC_PROVIDER_COUNT, (Context|Get|Int) }, + { "ALC_PROVIDER_NAME", ALC_PROVIDER_NAME, (Context|Get|Int) }, + { "ALC_SPEAKER", ALC_SPEAKER, (Context|Get|Set|Int) }, + { "ALC_SPEAKER_COUNT", ALC_SPEAKER_COUNT, (Context|Get|Int) }, + { "ALC_SPEAKER_NAME", ALC_SPEAKER_NAME, (Context|Get|Int) }, + { "ALC_BUFFER_DYNAMIC_MEMORY_SIZE", ALC_BUFFER_DYNAMIC_MEMORY_SIZE, (Context|Get|Set|Int) }, + { "ALC_BUFFER_DYNAMIC_MEMORY_USAGE",ALC_BUFFER_DYNAMIC_MEMORY_USAGE, (Context|Get|Int) }, + { "ALC_BUFFER_DYNAMIC_COUNT", ALC_BUFFER_DYNAMIC_COUNT, (Context|Get|Int) }, + { "ALC_BUFFER_MEMORY_USAGE", ALC_BUFFER_MEMORY_USAGE, (Context|Get|Int) }, + { "ALC_BUFFER_COUNT", ALC_BUFFER_COUNT, (Context|Get|Int) }, + { "ALC_BUFFER_LATENCY", ALC_BUFFER_LATENCY, (Context|Get|Int) }, + + // environment + { "AL_ENV_ROOM_IASIG", AL_ENV_ROOM_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_ROOM_HIGH_FREQUENCY_IASIG", AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_REFLECTIONS_IASIG", AL_ENV_REFLECTIONS_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_REVERB_IASIG", AL_ENV_REVERB_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG", AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DECAY_TIME_IASIG", AL_ENV_DECAY_TIME_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG", AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_REFLECTIONS_DELAY_IASIG", AL_ENV_REFLECTIONS_DELAY_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_REVERB_DELAY_IASIG", AL_ENV_REVERB_DELAY_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DIFFUSION_IASIG", AL_ENV_DIFFUSION_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DENSITY_IASIG", AL_ENV_DENSITY_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG", AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG, (Environment|Get|Set|Float) }, + + { "AL_ENV_ROOM_VOLUME_EXT", AL_ENV_ROOM_VOLUME_EXT, (Environment|Get|Set|Int) }, + { "AL_ENV_FLAGS_EXT", AL_ENV_FLAGS_EXT, (Environment|Get|Set|Int) }, + { "AL_ENV_EFFECT_VOLUME_EXT", AL_ENV_EFFECT_VOLUME_EXT, (Environment|Get|Set|Float) }, + { "AL_ENV_DAMPING_EXT", AL_ENV_DAMPING_EXT, (Environment|Get|Set|Float) }, + { "AL_ENV_ENVIRONMENT_SIZE_EXT", AL_ENV_ENVIRONMENT_SIZE_EXT, (Environment|Get|Set|Float) }, + + // sample environment + { "AL_ENV_SAMPLE_DIRECT_EXT", AL_ENV_SAMPLE_DIRECT_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_DIRECT_HF_EXT", AL_ENV_SAMPLE_DIRECT_HF_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_ROOM_EXT", AL_ENV_SAMPLE_ROOM_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_ROOM_HF_EXT", AL_ENV_SAMPLE_ROOM_HF_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT", AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_FLAGS_EXT", AL_ENV_SAMPLE_FLAGS_EXT, (Source|Get|Set|Int) }, + + { "AL_ENV_SAMPLE_REVERB_MIX_EXT", AL_ENV_SAMPLE_REVERB_MIX_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OBSTRUCTION_EXT", AL_ENV_SAMPLE_OBSTRUCTION_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT", AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OCCLUSION_EXT", AL_ENV_SAMPLE_OCCLUSION_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT", AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_AIR_ABSORPTION_EXT", AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, (Source|Get|Set|Float) }, + }; + + for(U32 i = 0; i < (sizeof(table) / sizeof(table[0])); i++) + { +#ifdef DEBUG + if(((table[i].mFlags & ~Debug) & flags) != flags) + continue; +#else + if((table[i].mFlags & flags) != flags) + continue; +#endif + if(!dStricmp(table[i].mName, name)) + return(table[i].mAlenum); + } + + return(AL_INVALID); +} + +//-------------------------------------------------------------------------- +// Source +//-------------------------------------------------------------------------- +static void cAudio_alxSourcef(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Set|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcef: invalid enum name '%s'", argv[2]); + return; + } + + alxSourcef(dAtoi(argv[1]), e, dAtof(argv[3])); +} + +static void cAudio_alxSource3f(SimObject *, S32 argc, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Set|Float3)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: invalid enum name '%s'", argv[2]); + return; + } + + if(argc != 3 || argc != 6) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: wrong number of args"); + return; + } + + Point3F pos; + if(argc == 3) + dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z); + else + { + pos.x = dAtof(argv[1]); + pos.y = dAtof(argv[2]); + pos.z = dAtof(argv[3]); + } + + alxSource3f(dAtoi(argv[1]), e, pos.x, pos.y, pos.z); +} + +static void cAudio_alxSourcei(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Set|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcei: invalid enum name '%s'", argv[2]); + return; + } + + alxSourcei(dAtoi(argv[1]), e, dAtoi(argv[3])); +} + +//-------------------------------------------------------------------------- + +static F32 cAudio_alxGetSourcef(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcef: invalid enum name '%s'", argv[2]); + return(0.f); + } + + F32 value; + alxGetSourcef(dAtoi(argv[1]), e, &value); + return(value); +} + +static const char * cAudio_alxGetSource3f(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSource3f: invalid enum name '%s'", argv[2]); + return("0 0 0"); + } + + F32 value1, value2, value3; + alxGetSource3f(dAtoi(argv[1]), e, &value1, &value2, &value3); + + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3); + return(ret); +} + +static S32 cAudio_alxGetSourcei(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcei: invalid enum name '%s'", argv[2]); + return(0); + } + + S32 value; + alxGetSourcei(dAtoi(argv[1]), e, &value); + return(value); +} + +//-------------------------------------------------------------------------- +// Listener +//-------------------------------------------------------------------------- +static void cAudio_alxListenerf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Listener|Set|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxListenerf: invalid enum name '%s'", argv[1]); + return; + } + + alxListenerf(e, dAtof(argv[2])); +} + +static void cAudio_alxListener3f(SimObject *, S32 argc, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Listener|Set|Float3)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxListener3f: invalid enum name '%s'", argv[1]); + return; + } + + if(argc != 3 || argc != 5) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxListener3f: wrong number of args"); + return; + } + + Point3F pos; + if(argc == 3) + dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z); + else + { + pos.x = dAtof(argv[2]); + pos.y = dAtof(argv[3]); + pos.z = dAtof(argv[4]); + } + + alxListener3f(e, pos.x, pos.y, pos.z); +} + +//-------------------------------------------------------------------------- +static F32 cAudio_alxGetListenerf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetListenerf: invalid enum name '%s'", argv[1]); + return(0.f); + } + + F32 value; + alxGetListenerf(e, &value); + return(value); +} + +static const char * cAudio_alxGetListener3f(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetListener3f: invalid enum name '%s'", argv[1]); + return("0 0 0"); + } + + F32 value1, value2, value3; + alxGetListener3f(e, &value1, &value2, &value3); + + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3); + return(ret); +} + +static S32 cAudio_alxGetListeneri(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Source|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetListeneri: invalid enum name '%s'", argv[1]); + return(0); + } + + S32 value; + alxGetListeneri(e, &value); + return(value); +} + +//-------------------------------------------------------------------------- +// Play/Stop +//-------------------------------------------------------------------------- +static S32 cAudio_alxPlay(SimObject *, S32 argc, const char *argv[]) +{ + if (argc == 2) + { + AUDIOHANDLE handle = dAtoi(argv[1]); + return alxPlay(handle); + } + + AudioProfile *profile = dynamic_cast( Sim::findObject( argv[1] ) ); + if (profile == NULL) + { + Con::printf("Unable to locate audio profile '%s'", argv[1]); + return NULL_AUDIOHANDLE; + } + + Point3F pos(0.f, 0.f, 0.f); + if(argc == 3) + dSscanf(argv[2], "%f %f %f", &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); + + return alxPlay(profile, &transform, NULL); +} + +static void cAudio_alxStop(SimObject *, S32, const char ** argv) +{ + AUDIOHANDLE handle = dAtoi(argv[1]); + if(handle == NULL_AUDIOHANDLE) + return; + alxStop(handle); +} + +static void cAudio_alxStopAll(SimObject *, S32, const char **) +{ + alxStopAll(); +} + +//-------------------------------------------------------------------------- +// Capture +//-------------------------------------------------------------------------- +static bool cAudio_alxCaptureInit(SimObject *, S32, const char **) +{ + return(alxCaptureInit()); +} + +static void cAudio_alxCaptureDestroy(SimObject *, S32, const char **) +{ + alxCaptureDestroy(); +} + +static void cAudio_alxCaptureStart(SimObject *, S32 argc, const char ** argv) +{ + alxCaptureStart((argc == 2) ? dAtob(argv[1]) : false); +} + +static void cAudio_alxCaptureStop(SimObject *, S32, const char **) +{ + alxCaptureStop(); +} + +static bool cAudio_alxIsCapturing(SimObject *, S32, const char **) +{ + return(alxIsCapturing()); +} + +//-------------------------------------------------------------------------- +// Environment: +//-------------------------------------------------------------------------- +void alxEnvironmenti(ALenum pname, ALint value) +{ + alEnvironmentiIASIG(mEnvironment, pname, value); +} + +void alxEnvironmentf(ALenum pname, ALfloat value) +{ + alEnvironmentfIASIG(mEnvironment, pname, value); +} + +void alxGetEnvironmenti(ALenum pname, ALint * value) +{ + alGetEnvironmentiIASIG_EXT(mEnvironment, pname, value); +} + +void alxGetEnvironmentf(ALenum pname, ALfloat * value) +{ + alGetEnvironmentfIASIG_EXT(mEnvironment, pname, value); +} + +void alxEnableEnvironmental(bool enable) +{ + if(mEnvironmentEnabled == enable) + return; + + // go through the playing sources and update their reverb mix + // - only 3d samples get environmental fx + // - only loopers can reenable fx + for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) + { + if(mHandle[i] == NULL_AUDIOHANDLE) + continue; + + ALint val = AL_FALSE; + + // 3d? + alGetSourcei(mSource[i], AL_SOURCE_AMBIENT, &val); + if(val == AL_TRUE) + continue; + + // stopped? + val = AL_STOPPED; + alGetSourcei(mSource[i], AL_SOURCE_STATE, &val); + + // only looping sources can reenable environmental effects (no description around + // for the non-loopers) + if(enable) + { + LoopingList::iterator itr = mLoopingList.findImage(mHandle[i]); + if(!itr) + continue; + + alSourcef(mSource[i], AL_ENV_SAMPLE_REVERB_MIX_EXT, (*itr)->mDescription.mEnvironmentLevel); + } + else + alSourcef(mSource[i], AL_ENV_SAMPLE_REVERB_MIX_EXT, 0.f); + } + + mEnvironmentEnabled = enable; +} + +void alxSetEnvironment(const AudioEnvironment * env) +{ + mCurrentEnvironment = const_cast(env); + + // reset environmental audio? + if(!env) + { + alxEnvironmenti(AL_ENV_ROOM_IASIG, AL_ENVIRONMENT_GENERIC); + return; + } + + // room trashes all the values + if(env->mUseRoom) + { + alxEnvironmenti(AL_ENV_ROOM_IASIG, env->mRoom); + return; + } + + // set all the params + alxEnvironmenti(AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, env->mRoomHF); + alxEnvironmenti(AL_ENV_REFLECTIONS_IASIG, env->mReflections); + alxEnvironmenti(AL_ENV_REVERB_IASIG, env->mReverb); + + alxEnvironmentf(AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, env->mRoomRolloffFactor); + alxEnvironmentf(AL_ENV_DECAY_TIME_IASIG, env->mDecayTime); + alxEnvironmentf(AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, env->mDecayTime); + alxEnvironmentf(AL_ENV_REFLECTIONS_DELAY_IASIG, env->mReflectionsDelay); + alxEnvironmentf(AL_ENV_REVERB_DELAY_IASIG, env->mReverbDelay); + alxEnvironmentf(AL_ENV_DENSITY_IASIG, env->mAirAbsorption); + alxEnvironmentf(AL_ENV_DIFFUSION_IASIG, env->mEnvironmentDiffusion); + + // ext: + alxEnvironmenti(AL_ENV_ROOM_VOLUME_EXT, env->mRoomVolume); + alxEnvironmenti(AL_ENV_FLAGS_EXT, env->mFlags); + + alxEnvironmentf(AL_ENV_EFFECT_VOLUME_EXT, env->mEffectVolume); + alxEnvironmentf(AL_ENV_DAMPING_EXT, env->mDamping); + alxEnvironmentf(AL_ENV_ENVIRONMENT_SIZE_EXT, env->mEnvironmentSize); +} + +const AudioEnvironment * alxGetEnvironment() +{ + return(mCurrentEnvironment); +} + +//-------------------------------------------------------------------------- +static void cAudio_alxEnvironmenti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Set|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmenti: invalid enum name '%s'", argv[1]); + return; + } + + alxEnvironmenti(e, dAtoi(argv[2])); +} + +static void cAudio_alxEnvironmentf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Set|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmentf: invalid enum name '%s'", argv[1]); + return; + } + + alxEnvironmenti(e, dAtof(argv[2])); +} + +static S32 cAudio_alxGetEnvironmenti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmenti: invalid enum name '%s'", argv[1]); + return(0); + } + + S32 value; + alxGetEnvironmenti(e, &value); + return(value); +} + +static F32 cAudio_alxGetEnvironmentf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmentf: invalid enum name '%s'", argv[1]); + return(0.f); + } + + F32 value; + alxGetEnvironmentf(e, &value); + return(value); +} + +static void cAudio_alxSetEnvironment(SimObject *, S32, const char ** argv) +{ + AudioEnvironment * environment = dynamic_cast(Sim::findObject(argv[1])); + alxSetEnvironment(environment); +} + +static void cAudio_alxEnableEnvironmental(SimObject *, S32, const char ** argv) +{ + alxEnableEnvironmental(dAtob(argv[1])); +} + +//-------------------------------------------------------------------------- +// Misc +//-------------------------------------------------------------------------- +static S32 cAudio_alxGetWaveLen(SimObject *, S32, const char ** argv) +{ + // filename or profile? + AudioProfile * profile = 0; + const char * fileName = 0; + + if(!dStrrchr(argv[1], '.')) + { + profile = dynamic_cast(Sim::findObject(argv[1])); + if(!profile) + { + Con::errorf(ConsoleLogEntry::General, "Unable to locate audio profile: '%s'", argv[1]); + return(0); + } + + fileName = profile->mFilename; + } + else + fileName = argv[1]; + + Resource buffer = AudioBuffer::find(fileName); + if(!bool(buffer)) + { + Con::errorf(ConsoleLogEntry::General, "Failed to find wave file: '%s'", fileName); + return(0); + } + + ALuint alBuffer = buffer->getALBuffer(true); + if(alBuffer == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetWaveLen: invalid buffer"); + return(0); + } + return(alxGetWaveLen(alBuffer)); +} + +static const char* cGetAudioDriverList( SimObject*, S32, const char** ) +{ + return( Audio::getDriverListString() ); +} + +static const char* cGetAudioDriverInfo( SimObject*, S32, const char** ) +{ + return( Audio::getCurrentDriverInfo() ); +} + +static F32 cAudio_alxGetChannelVolume(SimObject *, S32, const char ** argv) +{ + U32 type = dAtoi(argv[1]); + if(type >= Audio::NumAudioTypes) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetChannelVolume: invalid channel '%d'", dAtoi(argv[1])); + return(0.f); + } + + return(mAudioTypeVolume[type]); +} + +static void cAudio_alxSetChannelVolume(SimObject *, S32, const char ** argv) +{ + U32 type = dAtoi(argv[1]); + F32 volume = mClampF(dAtof(argv[2]), 0.f, 1.f); + + if(type >= Audio::NumAudioTypes) + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSetChannelVolume: invalid channel '%d'", dAtoi(argv[1])); + else + { + mAudioTypeVolume[type] = volume; + alxUpdateTypeGain(1 << type); + } +} + +static void cAudio_alxSetCaptureGainScale(SimObject *, S32, const char ** argv) +{ + mCaptureGainScale = mClampF(dAtof(argv[1]), MIN_CAPTURE_SCALE, MAX_CAPTURE_SCALE); +} + +static F32 cAudio_alxGetCaptureGainScale(SimObject *, S32, const char **) +{ + return(mCaptureGainScale); +} + +static void cAudio_alxForceMaxDistanceUpdate(SimObject *, S32, const char ** argv) +{ + mForceMaxDistanceUpdate = dAtob(argv[1]); +} + +static void cAudio_alxDisableOuterFalloffs(SimObject *, S32, const char ** argv) +{ + alxDisableOuterFalloffs(dAtob(argv[1])); +} + +static void cAudio_alxSetInnerFalloffScale(SimObject *, S32, const char ** argv) +{ + alxSetInnerFalloffScale(dAtof(argv[1])); +} + +static F32 cAudio_alxGetInnerFalloffScale(SimObject *, S32, const char **) +{ + return(alxGetInnerFalloffScale()); +} + +// Music: ---------------------------------------------------------------- +static void cAudio_alxPlayMusic(SimObject *, S32, const char ** argv) +{ + alxPlayMusicStream(StringTable->insert(argv[1])); +} + +static void cAudio_alxStopMusic(SimObject *, S32, const char **) +{ + alxStopMusicStream(); +} + +// Context: ---------------------------------------------------------------- +void alxContexti(ALenum pname, ALint value) +{ + alContexti_EXT(pname, value); +} + +void alxGetContexti(ALenum pname, ALint * value) +{ + alGetContexti_EXT(pname, value); +} + +void alxGetContextstr(ALenum pname, ALuint idx, ALubyte ** value) +{ + alGetContextstr_EXT(pname, idx, value); +} + +static void cAudio_alxContexti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Context|Set|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxContexti: invalid enum name '%s'", argv[1]); + return; + } + + alxContexti(e, dAtoi(argv[2])); +} + +static S32 cAudio_alxGetContexti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Context|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContexti: invalid enum name '%s'", argv[2]); + return(0); + } + + ALint value = 0; + alxGetContexti(e, &value); + return(value); +} + +static const char * cAudio_alxGetContextstr(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Context|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContextstr: invalid enum name '%s'", argv[2]); + return(""); + } + + ALubyte * str = (ALubyte *)""; + alxGetContextstr(e, dAtoi(argv[2]), &str); + return(StringTable->insert((const char *)str)); +} + +static bool cAudio_isEnabled(SimObject *, S32, const char ** argv) +{ + if(!dStricmp(argv[1], "system")) + return(mInitialized); + if(!dStricmp(argv[1], "capture")) + return(mCaptureInitialized); + if(!dStricmp(argv[1], "environment_iasig")) + return(mEnvironment && mEnvironmentEnabled); + return(false); +} + +static bool cAudio_isExtensionPresent(SimObject *, S32, const char ** argv) +{ + return((bool)alIsExtensionPresent((const ALubyte *)argv[1])); +} + + +// Namespace: Audio --------------------------------------------------------- +namespace Audio +{ + +//--------------------------------------------------------------------------- +// the following db<->linear conversion functions come from Loki openAL linux driver +// code, here more for completeness than anything else (all current audio code +// uses AL_GAIN_LINEAR)... in Audio:: so that looping updates and audio channel updates +// can convert gain types and to give the miles driver access +static const F32 logtab[] = { + 0.00, 0.001, 0.002, 0.003, 0.004, + 0.005, 0.01, 0.011, 0.012, 0.013, + 0.014, 0.015, 0.016, 0.02, 0.021, + 0.022, 0.023, 0.024, 0.025, 0.03, + 0.031, 0.032, 0.033, 0.034, 0.04, + 0.041, 0.042, 0.043, 0.044, 0.05, + 0.051, 0.052, 0.053, 0.054, 0.06, + 0.061, 0.062, 0.063, 0.064, 0.07, + 0.071, 0.072, 0.073, 0.08, 0.081, + 0.082, 0.083, 0.084, 0.09, 0.091, + 0.092, 0.093, 0.094, 0.10, 0.101, + 0.102, 0.103, 0.11, 0.111, 0.112, + 0.113, 0.12, 0.121, 0.122, 0.123, + 0.124, 0.13, 0.131, 0.132, 0.14, + 0.141, 0.142, 0.143, 0.15, 0.151, + 0.152, 0.16, 0.161, 0.162, 0.17, + 0.171, 0.172, 0.18, 0.181, 0.19, + 0.191, 0.192, 0.20, 0.201, 0.21, + 0.211, 0.22, 0.221, 0.23, 0.231, + 0.24, 0.25, 0.251, 0.26, 0.27, + 0.271, 0.28, 0.29, 0.30, 0.301, + 0.31, 0.32, 0.33, 0.34, 0.35, + 0.36, 0.37, 0.38, 0.39, 0.40, + 0.41, 0.43, 0.50, 0.60, 0.65, + 0.70, 0.75, 0.80, 0.85, 0.90, + 0.95, 0.97, 0.99 }; +const int logmax = sizeof logtab / sizeof *logtab; + +F32 DBToLinear(F32 value) +{ + if(value <= 0.f) + return(0.f); + if(value >= 1.f) + return(1.f); + + S32 max = logmax; + S32 min = 0; + S32 mid; + S32 last = -1; + + mid = (max - min) / 2; + do { + last = mid; + + if(logtab[mid] == value) + break; + + if(logtab[mid] < value) + min = mid; + else + max = mid; + + mid = min + ((max - min) / 2); + } while(last != mid); + + return((F32)mid / logmax); +} + +F32 linearToDB(F32 value) +{ + if(value <= 0.f) + return(0.f); + if(value >= 1.f) + return(1.f); + return(logtab[(U32)(logmax * value)]); +} +//--------------------------------------------------------------------------- + +static ALvoid errorCallback(ALbyte *msg) +{ + // used to allow our OpenAL implementation to display info on the console + Con::errorf(ConsoleLogEntry::General, (const char *)msg); +} + +//-------------------------------------------------------------------------- +bool prepareContext() +{ +// mForceMaxDistanceUpdate = Con::getBoolVariable("$pref::audio::forceMaxDistanceUpdate", false); + mForceMaxDistanceUpdate = false; + + // allocate source channels: can only get 16 sources on NT, so check the max before +// jff: todo, allow for more than 16 channels on non-NT boxes + mNumSources = mRequestSources; + alGenSources(mRequestSources, mSource); + + // invalidate all existing handles + dMemset(mHandle, NULL_AUDIOHANDLE, sizeof(mHandle)); + mCaptureInitialized = AL_FALSE; + + // pre-load profile data + SimGroup* grp = Sim::getDataBlockGroup(); + if (grp != NULL) + { + SimObjectList::iterator itr = grp->begin(); + for (; itr != grp->end(); itr++) + { + AudioProfile *profile = dynamic_cast(*itr); + if(profile != NULL && profile->isPreload()) + { + Resource buffer = AudioBuffer::find(profile->mFilename); + if((bool)buffer) + { + ALuint bufferId = buffer->getALBuffer(true); + alBufferi_EXT(bufferId, AL_BUFFER_KEEP_RESIDENT, AL_TRUE); + } + } + } + } + mInitialized = true; + return(true); +} + +void shutdownContext() +{ + alxCaptureStop(); + + // invalidate active handles + dMemset(mSource, 0, sizeof(mSource)); +} + +//-------------------------------------------------------------------------- +void init() +{ + Con::addCommand("AudioDetect", cAudio_detect, "AudioDetect()", 1, 1); + Con::addCommand("AudioDestroy", cAudio_destroy, "AudioDestroy()", 1, 1); + Con::addCommand("AudioSetDriver", cAudio_setDriver, "AudioSetDriver(name)", 2, 2); + + Con::addCommand("alxIsEnabled", cAudio_isEnabled, "alxIsEnabled(name)", 2, 2); + + Con::addCommand("alxIsExtensionPresent", cAudio_isExtensionPresent, "alxIsExtensionPresent(name)", 2, 2); + Con::addCommand("alxCreateSource", cAudio_alxCreateSource, "alxCreateSource(profile, {x,y,z} | description, filename, {x,y,z})", 2, 6); + + Con::addCommand("alxSourcef", cAudio_alxSourcef, "alxSourcef(handle, ALenum, value)", 4, 4); + Con::addCommand("alxSource3f", cAudio_alxSource3f, "alxSource3f(handle, ALenum, \"x y z\" | x, y, z)", 3, 6); + Con::addCommand("alxSourcei", cAudio_alxSourcei, "alxSourcei(handle, ALenum, value)", 4, 4); + + Con::addCommand("alxGetSourcef", cAudio_alxGetSourcef, "alxGetSourcef(handle, ALenum)", 3, 3); + Con::addCommand("alxGetSource3f", cAudio_alxGetSource3f, "alxGetSource3f(handle, ALenum)", 3, 3); + Con::addCommand("alxGetSourcei", cAudio_alxGetSourcei, "alxGetSourcei(handle, ALenum)", 3, 3); + + Con::addCommand("alxListenerf", cAudio_alxListenerf, "alxListenerf(ALenum, value)", 3, 3); + Con::addCommand("alxListener3f", cAudio_alxListener3f, "alxListener3f(ALenum, \"x y z\" | x, y, z)", 3, 5); + + Con::addCommand("alxGetListenerf", cAudio_alxGetListenerf, "alxGetListenerf(Alenum)", 2, 2); + Con::addCommand("alxGetListener3f", cAudio_alxGetListener3f, "alxGetListener3f(Alenum)", 2, 2); + Con::addCommand("alxGetListeneri", cAudio_alxGetListeneri, "alxGetListeneri(Alenum)", 2, 2); + + Con::addCommand("alxContexti", cAudio_alxContexti, "alxContexti(Alenum, value)", 3, 3); + Con::addCommand("alxGetContexti", cAudio_alxGetContexti, "alxGetContexti(Alenum)", 2, 2); + Con::addCommand("alxGetContextstr", cAudio_alxGetContextstr, "alxGetContextstr(Alenum, idx)", 3, 3); + + Con::addCommand("alxPlay", cAudio_alxPlay, "alxPlay(handle) | alxPlay(profile, {x,y,z})", 2, 5); + Con::addCommand("alxStop", cAudio_alxStop, "alxStop(handle)", 2, 2); + Con::addCommand("alxStopAll", cAudio_alxStopAll, "alxStopAll()", 1, 1); + + Con::addCommand("alxCaptureInit", cAudio_alxCaptureInit, "alxCaptureInit()", 1, 1); + Con::addCommand("alxCaptureDestroy", cAudio_alxCaptureDestroy, "alxCaptureDestroy()", 1, 1); + Con::addCommand("alxCaptureStart", cAudio_alxCaptureStart, "alxCaptureStart()", 1, 2); + Con::addCommand("alxCaptureStop", cAudio_alxCaptureStop, "alxCaptureStop()", 1, 1); + Con::addCommand("alxIsCapturing", cAudio_alxIsCapturing, "alxIsCapturing()", 1, 1); + + Con::addCommand("alxEnvironmenti", cAudio_alxEnvironmenti, "alxEnvironmenti(Alenum, value)", 3, 3); + Con::addCommand("alxEnvironmentf", cAudio_alxEnvironmentf, "alxEnvironmentf(Alenum, value)", 3, 3); + Con::addCommand("alxGetEnvironmenti", cAudio_alxGetEnvironmenti, "alxGetEnvironmenti(Alenum)", 2, 2); + Con::addCommand("alxGetEnvironmentf", cAudio_alxGetEnvironmentf, "alxGetEnvironmentf(Alenum)", 2, 2); + + Con::addCommand("alxSetEnvironment", cAudio_alxSetEnvironment, "alxSetEnvironment(AudioEnvironmentData)", 2, 2); + Con::addCommand("alxEnableEnvironmental", cAudio_alxEnableEnvironmental, "alxEnableEnvironmental(bool)", 2, 2 ); + + Con::addCommand("alxGetWaveLen", cAudio_alxGetWaveLen, "alxGetWaveLen(profile|filename)", 2, 2); + + Con::addCommand("getAudioDriverList", cGetAudioDriverList, "getAudioDriverList();", 1, 1); + Con::addCommand("getAudioDriverInfo", cGetAudioDriverInfo, "getAudioDriverInfo();", 1, 1); + + ResourceManager->registerExtension(".wav", AudioBuffer::construct); + + Con::setIntVariable("DefaultAudioType", Audio::DefaultAudioType); + Con::setIntVariable("ChatAudioType", Audio::ChatAudioType); + Con::setIntVariable("GuiAudioType", Audio::GuiAudioType); + Con::setIntVariable("EffectAudioType", Audio::EffectAudioType); + Con::setIntVariable("VoiceAudioType", Audio::VoiceAudioType); + Con::setIntVariable("MusicAudioType", Audio::MusicAudioType); + + Con::addCommand("alxGetChannelVolume", cAudio_alxGetChannelVolume, "alxGetChannelVolume(channel)", 2, 2); + Con::addCommand("alxSetChannelVolume", cAudio_alxSetChannelVolume, "alxSetChannelVolume(channel, volume)", 3, 3); + + Con::addCommand("alxSetCaptureGainScale", cAudio_alxSetCaptureGainScale, "alxSetCaptureGainScale(scale)", 2, 2); + Con::addCommand("alxGetCaptureGainScale", cAudio_alxGetCaptureGainScale, "alxGetCaptureGainScale()", 1, 1); + + Con::addCommand("alxPlayMusic", cAudio_alxPlayMusic, "alxPlayMusic(file)", 2, 2); + Con::addCommand("alxStopMusic", cAudio_alxStopMusic, "alxStopMusic()", 1, 1); + + Con::addCommand("alxDisableOuterFalloffs", cAudio_alxDisableOuterFalloffs, "alxDisableOuterFalloffs(bool)", 2, 2); + Con::addCommand("alxSetInnerFalloffScale", cAudio_alxSetInnerFalloffScale, "alxSetInnerFalloffScale(scale)", 2, 2); + Con::addCommand("alxGetInnerFalloffScale", cAudio_alxGetInnerFalloffScale, "alxGetInnerFalloffScale()", 1, 1); + + Con::addCommand("alxForceMaxDistanceUpdate", cAudio_alxForceMaxDistanceUpdate, "alxForceMaxDistanceUpdate(bool)", 2, 2); + + // default all channels to full gain + for(U32 i = 0; i < Audio::NumAudioTypes; i++) + mAudioTypeVolume[i] = 1.f; + +#ifndef DISABLE_AUDIO_THREAD + AudioThread::create(); +#endif +} + +//-------------------------------------------------------------------------- +void detect() +{ + // destroy method takes down the audio thread + if(mDriverInfoList.size()) + { + destroy(); +#ifndef DISABLE_AUDIO_THREAD + AudioThread::create(); +#endif + } + + DriverInfo di; + di.mName = dStrdup("None"); + di.mVender = dStrdup((const char*)alGetString(AL_VENDOR)); + di.mVersion = dStrdup((const char*)alGetString(AL_VERSION)); + di.mRenderer = dStrdup((const char*)alGetString(AL_RENDERER)); + di.mExtensions = dStrdup((const char*)alGetString(AL_EXTENSIONS)); + mDriverInfoList.push_back(di); + mActiveDriver = mDriverInfoList.begin(); + + const char *str = Con::getVariable("$pref::Audio::drivers"); + if (str) + { + char driverString[1024]; + dStrcpy(driverString, str); + char *tok = dStrtok(driverString, " \t"); + while (tok != NULL) + { + if(libraryInit((const char *)tok)) + { + if(alGetError() == AL_NO_ERROR) + { + if(alIsExtensionPresent((const ALubyte *)"AL_EXT_DYNAMIX")) + { + di.mName = dStrdup(tok); + di.mVender = dStrdup((const char*)alGetString(AL_VENDOR)); + di.mVersion = dStrdup((const char*)alGetString(AL_VERSION)); + di.mRenderer = dStrdup((const char*)alGetString(AL_RENDERER)); + di.mExtensions = dStrdup((const char*)alGetString(AL_EXTENSIONS)); + mDriverInfoList.push_back(di); + } + } + libraryShutdown(); + } + tok = dStrtok(NULL, " \t"); + } + } +} + +//-------------------------------------------------------------------------- +void destroy() +{ +#ifndef DISABLE_AUDIO_THREAD + AudioThread::destroy(); +#endif + + alxCaptureDestroy(); + + if(mInitialized) + { + alxEnvironmentDestroy(); + alutExit(); + } + + libraryShutdown(); + + while(mLoopingList.size()) + { + mLoopingList.last()->mBuffer.purge(); + delete mLoopingList.last(); + mLoopingList.pop_back(); + } + + while(mLoopingFreeList.size()) + { + mLoopingFreeList.last()->mBuffer.purge(); + delete mLoopingFreeList.last(); + mLoopingFreeList.pop_back(); + } + + mInitialized = false; + while (mDriverInfoList.size()) + { + dFree(mDriverInfoList.last().mName); + dFree(mDriverInfoList.last().mVender); + dFree(mDriverInfoList.last().mVersion); + dFree(mDriverInfoList.last().mRenderer); + dFree(mDriverInfoList.last().mExtensions); + mDriverInfoList.pop_back(); + } + mActiveDriver = mDriverInfoList.begin(); + + for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) + mBuffer[i] = 0; +} + +//-------------------------------------------------------------------------- +bool setDriver(const char *name) +{ + // handle a couple special keywords + if (name == NULL || dStricmp(name, "none") == 0) + name = mDriverInfoList.first().mName; + + if (dStricmp(name, "default") == 0) + name = mDriverInfoList.last().mName; + + // don't reset if driver is already active + if (mActiveDriver && (dStricmp(name, mActiveDriver->mName) == 0) ) + return true; + + DriverInfo *newDriver = NULL; + + // locate the driver + Vector::iterator itr = mDriverInfoList.begin(); + for (; itr != mDriverInfoList.end(); itr++) + { + if (dStricmp(name, itr->mName) == 0) + { + newDriver = itr; + break; + } + } + + // no driver by that name + if (newDriver == NULL) + { + Con::errorf(ConsoleLogEntry::General, "Unknown OpenAL audio driver '%s'", name ); + return false; + } + + // shut down existing driver + if (mActiveDriver) + { + alxCaptureDestroy(); + alutExit(); + libraryShutdown(); + mActiveDriver = NULL; + } + + // stub driver + if (newDriver == mDriverInfoList.begin()) + { + libraryShutdown(); + prepareContext(); + mActiveDriver = mDriverInfoList.begin(); + return true; + } + + // load driver + if(!libraryInit((const char *)name)) + { + // install stub driver + libraryShutdown(); + mActiveDriver = mDriverInfoList.begin(); + return(false); + } + + + S32 attribs[] = { ALC_FREQUENCY, 0, + ALC_RESOLUTION, 0, + ALC_CHANNELS, 0, 0 }; + + // grab the console prefs + attribs[1] = Con::getIntVariable("$pref::Audio::frequency", ALX_DEF_SAMPLE_RATE); + attribs[3] = Con::getIntVariable("$pref::Audio::sampleBits", ALX_DEF_SAMPLE_BITS); + attribs[5] = Con::getIntVariable("$pref::Audio::channels", ALX_DEF_CHANNELS); + + // initialize the system + alutInit(attribs, 0); + if(alGetError() != AL_NO_ERROR) + { + // exit, load stub + alutExit(); + libraryShutdown(); + mActiveDriver = mDriverInfoList.begin(); + return(false); + } + + // must have dynamix extension + if(alIsExtensionPresent((const ALubyte *)"AL_EXT_DYNAMIX")) + { + // bind the _EXT methods + libraryInitExtensions(); + prepareContext(); + mActiveDriver = itr; + + Con::setVariable("$pref::Audio::activeDriver", mActiveDriver->mName); + mInitialized = true; + + // generate an environment + alxEnvironmentInit(); + +// set the listener gain to full and handle through this layer? +#ifdef HANDLE_LISTENER_GAIN + alListenerf(AL_GAIN_LINEAR, 1.f); +#endif + return(true); + } + + // driver load failed, load the stub driver + libraryShutdown(); + mActiveDriver = mDriverInfoList.begin(); + return(false); +} + +//-------------------------------------------------------------------------- +const Vector* getDriverList() +{ + return &mDriverInfoList; +} + +//-------------------------------------------------------------------------- +const char* getDriverListString() +{ + U32 deviceCount = mDriverInfoList.size(); + if ( deviceCount > 0 ) // It better be... + { + U32 i, bufSize = 0; + for ( i = 0; i < deviceCount; i++ ) + bufSize += ( dStrlen( mDriverInfoList[i].mName ) + 1 ); + + char* returnString = Con::getReturnBuffer( bufSize ); + dStrcpy( returnString, mDriverInfoList[0].mName ); + for ( i = 1; i < deviceCount; i++ ) + { + dStrcat( returnString, "\t" ); + dStrcat( returnString, mDriverInfoList[i].mName ); + } + + return( returnString ); + } + + return( "" ); +} + +//-------------------------------------------------------------------------- +const char* getCurrentDriverInfo() +{ + if ( mActiveDriver ) + { + U32 bufSize = ( mActiveDriver->mName ? dStrlen( mActiveDriver->mName ) : 0 ) + + ( mActiveDriver->mVender ? dStrlen( mActiveDriver->mVender ) : 0 ) + + ( mActiveDriver->mRenderer ? dStrlen( mActiveDriver->mRenderer ) : 0 ) + + ( mActiveDriver->mVersion ? dStrlen( mActiveDriver->mVersion ) : 0 ) + + ( mActiveDriver->mExtensions ? dStrlen( mActiveDriver->mExtensions ) : 0 ) + + 5; + + char* returnString = Con::getReturnBuffer( bufSize ); + dSprintf( returnString, bufSize, "%s\t%s\t%s\t%s\t%s", + ( mActiveDriver->mName ? mActiveDriver->mName : "" ), + ( mActiveDriver->mVender ? mActiveDriver->mVender : "" ), + ( mActiveDriver->mRenderer ? mActiveDriver->mRenderer : "" ), + ( mActiveDriver->mVersion ? mActiveDriver->mVersion : "" ), + ( mActiveDriver->mExtensions ? mActiveDriver->mExtensions : "" ) ); + return( returnString ); + } + + return( "" ); +} + +} // end OpenAL namespace diff --git a/audio/audio.h b/audio/audio.h new file mode 100644 index 0000000..916cee9 --- /dev/null +++ b/audio/audio.h @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUDIO_H_ +#define _AUDIO_H_ + +#ifndef _PLATFORMAL_H_ +#include "PlatformWin32/platformAL.h" +#endif +#ifndef _PLATFORMAUDIO_H_ +#include "Platform/platformAudio.h" +#endif +#ifndef _AUDIODATABLOCK_H_ +#include "audio/audioDataBlock.h" +#endif + +#endif // _H_AUDIO_ diff --git a/audio/audioBuffer.cc b/audio/audioBuffer.cc new file mode 100644 index 0000000..716aa89 --- /dev/null +++ b/audio/audioBuffer.cc @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "audio/audioBuffer.h" +#include "Core/stream.h" +#include "console/console.h" +#include "Core/fileStream.h" +#include "audio/audioThread.h" + +//-------------------------------------- +AudioBuffer::AudioBuffer(StringTableEntry filename) +{ + AssertFatal(StringTable->lookup(filename), "AudioBuffer:: filename is not a string table entry"); + + mFilename = filename; + mLoading = false; + malBuffer = AL_INVALID; +} + +AudioBuffer::~AudioBuffer() +{ + if( malBuffer != AL_INVALID ) { + alDeleteBuffers( 1, &malBuffer ); + } +} + +//-------------------------------------- +Resource AudioBuffer::find(const char *filename) +{ + Resource buffer = ResourceManager->load(filename); + if (bool(buffer) == false) + { + char buf[512]; + dSprintf(buf, sizeof(buf), "audio/%s", filename); + + // see if the file exists + if (ResourceManager->getPathOf(buf)) + { + AudioBuffer *temp = new AudioBuffer(StringTable->insert(filename)); + ResourceManager->add(filename, temp); + buffer = ResourceManager->load(filename); + } + } + return buffer; +} + +ResourceInstance* AudioBuffer::construct(Stream &) +{ + return NULL; +} + +//----------------------------------------------------------------- +ALuint AudioBuffer::getALBuffer(bool block) +{ +// jff: block until thread is completely stable (resourcemanager is not +// thread safe yet (though it never seems to crash there...) ) + block = true; +// sol: if the above "block = true" is removed, uncomment the following +// error so that the Linux build maintainer knows to re-enable the audio +// thread. It's disabled at the moment as an optimization. +//#ifdef __linux +//#error Linux version needs to re-enable audio thread in platformLinux/audio.cc +//#endif + + // clear the error state + alGetError(); + + if (alIsBuffer(malBuffer)) + return malBuffer; + + alGenBuffers(1, &malBuffer); + if(alGetError() != AL_NO_ERROR) + return(AL_INVALID); + + char buffer[512]; + dSprintf(buffer, sizeof(buffer), "audio/%s", mFilename); + + ResourceObject * obj = ResourceManager->find(buffer); + if(obj) + { + if(block) + { + bool readWavSuccess = readWAV(obj); + if(readWavSuccess) + return(malBuffer); + } + else if(gAudioThread) + { + gAudioThread->loadResource(obj, this); + return(malBuffer); + } + } + + alDeleteBuffers(1, &malBuffer); + return(AL_INVALID); +} + +bool AudioBuffer::readWAV(ResourceObject *obj) +{ + if(Audio::doesSupportDynamix()) + { + U32 size = obj->fileSize; + Stream *str = ResourceManager->openStream(obj); + void * data = dMalloc(obj->fileSize); + str->read(obj->fileSize, data); + ResourceManager->closeStream(str); + + if(alBufferSyncData_EXT(malBuffer, AL_FORMAT_WAVE_EXT, data, obj->fileSize, 0)) + return(true); + + dFree(data); + } + return(false); +} + diff --git a/audio/audioBuffer.h b/audio/audioBuffer.h new file mode 100644 index 0000000..c3dd6f8 --- /dev/null +++ b/audio/audioBuffer.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUDIOBUFFER_H_ +#define _AUDIOBUFFER_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _PLATFORMAL_H_ +#include "PlatformWin32/platformAL.h" +#endif +#ifndef _RESMANAGER_H_ +#include "Core/resManager.h" +#endif + +//-------------------------------------------------------------------------- + +class AudioBuffer: public ResourceInstance +{ + friend class AudioThread; + +private: + StringTableEntry mFilename; + bool mLoading; + ALuint malBuffer; + + bool readRIFFchunk(Stream &s, const char *seekLabel, U32 *size); + bool readWAV(ResourceObject *obj); + +public: + AudioBuffer(StringTableEntry filename); + ~AudioBuffer(); + ALuint getALBuffer(bool block = false); + bool isLoading() {return(mLoading);} + + static Resource find(const char *filename); + static ResourceInstance* construct(Stream& stream); + +}; + + +#endif // _H_AUDIOBUFFER_ diff --git a/audio/audioCodec.cc b/audio/audioCodec.cc new file mode 100644 index 0000000..963c0ce --- /dev/null +++ b/audio/audioCodec.cc @@ -0,0 +1,311 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "audio/audioCodec.h" +//#include "audio/audioCodecGSM.h" + +#ifndef __linux +#include "audio/audioCodecMiles.h" +#endif + +//------------------------------------------------------------------------- +// Class AudioCodecManager: +//------------------------------------------------------------------------- +AudioCodecManager::CodecInfo AudioCodecManager::smCodecTable[] = +{ + #ifndef __linux // miles only exists on Win32! + { AUDIO_CODEC_V12, MilesEncoderCodec::create, MilesDecoderCodec::create, 0, 0}, + { AUDIO_CODEC_V24, MilesEncoderCodec::create, MilesDecoderCodec::create, 0, 0}, + { AUDIO_CODEC_V29, MilesEncoderCodec::create, MilesDecoderCodec::create, 0, 0}, + #else + { AUDIO_CODEC_V12, 0, 0, 0, 0 }, + { AUDIO_CODEC_V24, 0, 0, 0, 0 }, + { AUDIO_CODEC_V29, 0, 0, 0, 0 }, + #endif +// { AUDIO_CODEC_GSM, GSMEncoderCodec::create, GSMDecoderCodec::create, 0, 0 }, +}; + + +//------------------------------------------------------------------------- +void AudioCodecManager::destroy() +{ + for(U32 i = 0; i < sizeof(smCodecTable) / sizeof(smCodecTable[0]); i++) + { + delete smCodecTable[i].mEncoder; + delete smCodecTable[i].mDecoder; + smCodecTable[i].mEncoder = 0; + smCodecTable[i].mDecoder = 0; + } +} + +VoiceEncoderCodec * AudioCodecManager::createEncoderCodec(S32 codecId) +{ + for(U32 i = 0; i < sizeof(smCodecTable) / sizeof(smCodecTable[0]); i++) + if((smCodecTable[i].mId == codecId) && smCodecTable[i].mCreateEncoder) + { + // already created? + if(smCodecTable[i].mEncoder) + return(smCodecTable[i].mEncoder); + + VoiceCodec * codec = smCodecTable[i].mCreateEncoder(codecId); + if(!dynamic_cast(codec)) + { + delete codec; + return(0); + } + else + { + smCodecTable[i].mEncoder = static_cast(codec); + return(smCodecTable[i].mEncoder); + } + } + return(0); +} + +VoiceDecoderCodec * AudioCodecManager::createDecoderCodec(S32 codecId) +{ + for(U32 i = 0; i < sizeof(smCodecTable) / sizeof(smCodecTable[0]); i++) + if((smCodecTable[i].mId == codecId) && smCodecTable[i].mCreateDecoder) + { + // already created? + if(smCodecTable[i].mDecoder) + return(smCodecTable[i].mDecoder); + + VoiceCodec * codec = smCodecTable[i].mCreateDecoder(codecId); + if(!dynamic_cast(codec)) + { + delete codec; + return(0); + } + else + { + smCodecTable[i].mDecoder = static_cast(codec); + return(smCodecTable[i].mDecoder); + } + } + return(0); +} + +//------------------------------------------------------------------------- +// Class VoiceEncoderStream: +//------------------------------------------------------------------------- +VoiceEncoderStream::VoiceEncoderStream() +{ + mEncoder = 0; + mEncoderId = AUDIO_CODEC_NONE; + mConnection = 0; + mStreamId = 0; + mSequence = 0; + mStream = 0; + + mQueue.setSize(VOICE_CHANNELS * VOICE_FREQUENCY * (VOICE_BITS >> 3) * VOICE_LENGTH); +} + +VoiceEncoderStream::~VoiceEncoderStream() +{ + close(); +} + +//------------------------------------------------------------------------- +bool VoiceEncoderStream::setConnection(GameConnection * con) +{ + mStreamId++; + mSequence = 0; + mConnection = con; + mQueue.clear(); + return(true); +} + +//------------------------------------------------------------------------- +bool VoiceEncoderStream::setCodec(S32 codecId) +{ + close(); + + mEncoder = 0; + mEncoderId = AUDIO_CODEC_NONE; + + if(codecId == AUDIO_CODEC_NONE) + return(true); + + mEncoder = AudioCodecManager::createEncoderCodec(codecId); + if(mEncoder) + { + if(dynamic_cast(mEncoder)) + { + mEncoderId = codecId; + return(true); + } + else + { + delete mEncoder; + mEncoder = 0; + } + } + return(false); +} + +//------------------------------------------------------------------------- +bool VoiceEncoderStream::open() +{ + close(); + + if(mEncoder) + mStream = mEncoder->openStream(); + return(bool(mStream)); +} + +void VoiceEncoderStream::close() +{ + if(mEncoder && mStream) + { + mEncoder->closeStream(mStream); + mStream = 0; + } +} + +//------------------------------------------------------------------------- +bool VoiceEncoderStream::setBuffer(const U8 * data, U32 size) +{ + AssertFatal(data, "VoiceEncoderStream::setBuffer: invalid buffer ptr"); + + if(size > mQueue.getFree()) + return(false); + + mQueue.enqueue(data, size); + return(true); +} + +//------------------------------------------------------------------------- +void VoiceEncoderStream::process(bool flush) +{ + if(!mEncoder || !mStream) + return; + + while(flush || (mQueue.getUsed() >= 1800)) + { + SimVoiceStreamEvent * mEvent = new SimVoiceStreamEvent(mStreamId, mSequence++, mEncoderId); + U32 amount = mEncoder->process(mStream, &mQueue, mEvent->getData(), SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE); + + mEvent->setDataSize(amount); + mConnection->postNetEvent(mEvent); + + if(flush && (amount < SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE)) + break; + } +} + +void VoiceEncoderStream::flush() +{ + process(true); + mQueue.clear(); +} + + +//------------------------------------------------------------------------- +// Class VoiceEncoder: +//------------------------------------------------------------------------- +VoiceDecoderStream::VoiceDecoderStream() +{ + mDecoder = 0; + mDecoderId = AUDIO_CODEC_NONE; + + mInQueue.setSize(VOICE_CHANNELS * VOICE_FREQUENCY * (VOICE_BITS >> 3) * VOICE_LENGTH); + mOutQueue.setSize(VOICE_CHANNELS * VOICE_FREQUENCY * (VOICE_BITS >> 3) * VOICE_LENGTH); + + mStream = 0; +} + +VoiceDecoderStream::~VoiceDecoderStream() +{ + close(); +} + +//------------------------------------------------------------------------- +bool VoiceDecoderStream::setCodec(S32 codecId) +{ + close(); + + mDecoder = 0; + mDecoderId = AUDIO_CODEC_NONE; + + if(codecId == AUDIO_CODEC_NONE) + return(true); + + mDecoder = AudioCodecManager::createDecoderCodec(codecId); + if(mDecoder) + { + if(dynamic_cast(mDecoder)) + { + mDecoderId = codecId; + return(true); + } + else + { + delete mDecoder; + mDecoder = 0; + } + } + return(false); +} + +//------------------------------------------------------------------------- +bool VoiceDecoderStream::open() +{ + if(!mDecoder) + return(false); + + close(); + + if(mDecoder) + mStream = mDecoder->openStream(); + return(bool(mStream)); +} + +void VoiceDecoderStream::close() +{ + if(mDecoder && mStream) + { + mDecoder->closeStream(mStream); + mStream = 0; + } +} + +//------------------------------------------------------------------------- +bool VoiceDecoderStream::setBuffer(U8 * data, U32 size) +{ + AssertFatal(data, "VoiceDecoderStream::setBuffer: invalid data ptr"); + + if(size > mInQueue.getFree()) + return(false); + + mInQueue.enqueue(data, size); + return(true); +} + +U32 VoiceDecoderStream::getBuffer(U8 ** data, U32 * size) +{ + *data = mOutQueue.getHead(); + *size = mOutQueue.getContiguousUsed(); + + mOutQueue.dequeue(*size); + return(*size); +} + +void VoiceDecoderStream::process(bool flush) +{ + if(!mDecoder || !mStream) + return; + + while( flush || (mInQueue.getUsed() && !mOutQueue.isFull()) ) + { + U32 amount = mDecoder->process(mStream, &mInQueue, mOutQueue.getTail(), mOutQueue.getContiguousFree()); + mOutQueue.enqueue(amount); + + if(flush && (amount == 0)) + break; + } +} diff --git a/audio/audioCodec.h b/audio/audioCodec.h new file mode 100644 index 0000000..e7ca499 --- /dev/null +++ b/audio/audioCodec.h @@ -0,0 +1,147 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUDIOCODEC_H_ +#define _AUDIOCODEC_H_ + +#ifndef _AUDIONET_H_ +#include "audio/audioNet.h" +#endif +#ifndef _GAMECONNECTION_H_ +#include "game/gameConnection.h" +#endif +#ifndef _FILESTREAM_H_ +#include "core/fileStream.h" +#endif +#ifndef _BUFFERQUEUE_H_ +#include "audio/bufferQueue.h" +#endif + +//-------------------------------------------------------------------------- +#define VOICE_FREQUENCY 8000 +#define VOICE_BITS 16 +#define VOICE_CHANNELS 1 +#define VOICE_LENGTH 5 // in seconds + +enum { + AUDIO_CODEC_V12 = 0, + AUDIO_CODEC_V24, + AUDIO_CODEC_V29, + AUDIO_CODEC_GSM, + + AUDIO_NUM_CODECS, + AUDIO_CODEC_NONE = -1 +}; + +//-------------------------------------------------------------------------- +// Class VoiceCodec: +//-------------------------------------------------------------------------- +class VoiceCodec +{ + public: + VoiceCodec() {}; + virtual ~VoiceCodec() {}; + + virtual bool open() = 0; + virtual void close() = 0; + + virtual void * openStream() = 0; + virtual void closeStream(void * stream) = 0; + + virtual U32 process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen) = 0; +}; + +class VoiceDecoderCodec : public VoiceCodec {}; +class VoiceEncoderCodec : public VoiceCodec {}; + +typedef VoiceCodec * (*CODEC_CREATE_PROC)(U32 codecId); + +//------------------------------------------------------------------------- +// Struct AudioCodecManager: +//------------------------------------------------------------------------- +struct AudioCodecManager +{ + struct CodecInfo + { + S32 mId; + CODEC_CREATE_PROC mCreateEncoder; + CODEC_CREATE_PROC mCreateDecoder; + + VoiceEncoderCodec * mEncoder; + VoiceDecoderCodec * mDecoder; + }; + static CodecInfo smCodecTable[]; + + static VoiceEncoderCodec * createEncoderCodec(S32 codecId); + static VoiceDecoderCodec * createDecoderCodec(S32 codecId); + + static void destroy(); +}; + +//------------------------------------------------------------------------- +// Class VoiceEncoderStream: +//------------------------------------------------------------------------- +class VoiceEncoderStream +{ + private: + BufferQueue mQueue; + VoiceEncoderCodec * mEncoder; + S32 mEncoderId; + + GameConnection * mConnection; + U8 mStreamId; + U8 mSequence; + + void * mStream; + + public: + VoiceEncoderStream(); + ~VoiceEncoderStream(); + + S32 getCodec() { return(mEncoderId); } + bool setCodec(S32 codecId); + + bool open(); + void close(); + + bool setConnection(GameConnection *); + bool setBuffer(const U8 * data, U32 size); + + void process(bool flush=false); + void flush(); +}; + +//------------------------------------------------------------------------- +// Class VoiceDecoderStream: +//------------------------------------------------------------------------- +class VoiceDecoderStream +{ + private: + VoiceDecoderCodec * mDecoder; + S32 mDecoderId; + + BufferQueue mInQueue; + BufferQueue mOutQueue; + + void * mStream; + + public: + VoiceDecoderStream(); + ~VoiceDecoderStream(); + + S32 getCodec() { return(mDecoderId); } + bool setCodec(S32 codecId); + + bool open(); + void close(); + + bool setBuffer(U8 * data, U32 size); + U32 getBuffer(U8 ** data, U32 * size); + void process(bool flush=false); +}; + +#endif // _INC_AUDIOCODEC diff --git a/audio/audioCodecGSM.cc b/audio/audioCodecGSM.cc new file mode 100644 index 0000000..7901485 --- /dev/null +++ b/audio/audioCodecGSM.cc @@ -0,0 +1,195 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include + +extern "C" +{ +#include "inc/gsm.h" +} +#include "audio/audioCodecGSM.h" + +// The number of samples encoded at once in the GSM spec +#define GSM_FRAMESIZE 160 +#define GSM_FRAMESIZE_BYTES GSM_FRAMESIZE*sizeof(gsm_signal) + +//-------------------------------------------------------------------------- +// Class GSMEncoderCodec: +//-------------------------------------------------------------------------- +GSMEncoderCodec::GSMEncoderCodec() +{ +} + +GSMEncoderCodec::~GSMEncoderCodec() +{ + close(); +} + +VoiceCodec * GSMEncoderCodec::create(U32 codecId) +{ + if(codecId != AUDIO_CODEC_GSM) + return(0); + + // create and open the codec + GSMEncoderCodec * codec = new GSMEncoderCodec(); + if(!codec->open()) + { + delete codec; + codec = 0; + } + + return(codec); +} + +//-------------------------------------------------------------------------- +bool GSMEncoderCodec::open() +{ + return true; +} + +void GSMEncoderCodec::close() +{ +} + +//-------------------------------------------------------------------------- +void * GSMEncoderCodec::openStream() +{ + return gsm_create(); +} + +void GSMEncoderCodec::closeStream(void * stream) +{ + if ( stream ) + gsm_destroy((gsm)stream); +} + +U32 GSMEncoderCodec::process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen) +{ + gsm_frame frame; + gsm_signal gsmdata[ GSM_FRAMESIZE]; + gsm_signal samples[2*GSM_FRAMESIZE]; + U32 encoded; + U8 *output; + const U32 framesize = (sizeof frame); + + // Sanity check + if ( !stream || !queue ) { + return 0; + } + output = const_cast(data); + encoded = 0; + while ( maxLen >= framesize ) { + if ( queue->getUsed() < (sizeof samples) ) { + break; + } + queue->dequeue((U8*)samples, (sizeof samples)); + + // Convert the samples to 4000 Hz + for ( int i=0, j=0; iopen()) + { + delete codec; + codec = 0; + } + + return(codec); +} + +//-------------------------------------------------------------------------- +bool GSMDecoderCodec::open() +{ + return(true); +} + +void GSMDecoderCodec::close() +{ +} + +//-------------------------------------------------------------------------- +void * GSMDecoderCodec::openStream() +{ + return gsm_create(); +} + +void GSMDecoderCodec::closeStream(void * stream) +{ + if ( stream ) + gsm_destroy((gsm)stream); +} + +U32 GSMDecoderCodec::process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen) +{ + gsm_frame frame; + gsm_signal gsmdata[GSM_FRAMESIZE]; + U32 decoded; + U8 *output; + const U32 framesize = 2*GSM_FRAMESIZE_BYTES; + + // Sanity check + if ( !stream || !queue ) { + return 0; + } + output = const_cast(data); + decoded = 0; + while ( maxLen >= framesize ) { + gsm_signal *samples = (gsm_signal *)output; + + // Decode another frame of data + if ( queue->getUsed() < (sizeof frame) ) { + break; + } + queue->dequeue((U8*)frame, (sizeof frame)); + + gsm_decode((gsm)stream, frame, gsmdata); + + // Convert the samples from 4000 Hz + for ( int i=0, j=0; iopen()) + { + delete codec; + codec = 0; + } + + return(codec); +} + +//-------------------------------------------------------------------------- +bool MilesEncoderCodec::open() +{ + if(mProvider) + return(true); + + RIB_INTERFACE_ENTRY ENCODER_REQUEST[] = { + { RIB_FUNCTION, "ASI_stream_open", (U32)&mProcStreamOpen, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_close", (U32)&mProcStreamClose, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_process", (U32)&mProcStreamProcess, RIB_NONE } }; + + mProvider = RIB_find_file_provider("ASI codec", "Output file types", mExtension); + if(mProvider && (RIB_request(mProvider, "ASI stream", ENCODER_REQUEST) == RIB_NOERR)) + return(true); + + return(false); +} + +void MilesEncoderCodec::close() +{ + if(mProvider) + { + RIB_free_provider_handle(mProvider); + mProvider = 0; + } +} + +//-------------------------------------------------------------------------- +void * MilesEncoderCodec::openStream() +{ + HASISTREAM handle = mProcStreamOpen((U32)this, callback_router, 0); + if(!handle) + return(0); + + HASISTREAM * stream = new HASISTREAM; + *stream = handle; + return(static_cast(stream)); +} + +void MilesEncoderCodec::closeStream(void * stream) +{ + AssertFatal(stream, "MilesEncoderCodec::closeStream: invalid stream ptr"); + HASISTREAM * pHandle = static_cast(stream); + + if(mProvider) + mProcStreamClose(*pHandle); + delete pHandle; +} + +//-------------------------------------------------------------------------- +S32 AILCALLBACK MilesEncoderCodec::callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset) +{ + return(((MilesEncoderCodec*)user)->callback(dest, bytesRequested, offset)); +} + +S32 MilesEncoderCodec::callback(void * dest, S32 bytesRequested, S32 offset) +{ + offset; + return(mQueue->dequeue((U8*)dest, bytesRequested)); +} + +U32 MilesEncoderCodec::process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen) +{ + if(!stream || !queue) + return(0); + + mQueue = queue; + U32 amount = U32(mProcStreamProcess(*static_cast(stream), static_cast(const_cast(data)), S32(maxLen))); + mQueue = 0; + + return(amount); +} + + +//-------------------------------------------------------------------------- +// Class MilesDecoderCodec: +//-------------------------------------------------------------------------- +MilesDecoderCodec::MilesDecoderCodec(const char * ext) : VoiceDecoderCodec() +{ + mProvider = 0; + + mProcStreamOpen = 0; + mProcStreamProcess = 0; + mProcStreamSeek = 0; + mProcStreamClose = 0; + mProcStreamAttribute = 0; + mProcStreamSetPreference = 0; + + mAttribOutputSampleRate = 0; + mAttribOutputBits = 0; + mAttribOutputChannels = 0; + mAttribRequestedRate = 0; + + mQueue = 0; + mExtension = dStrdup(ext); +} + +MilesDecoderCodec::~MilesDecoderCodec() +{ + close(); + dFree(mExtension); +} + +VoiceCodec * MilesDecoderCodec::create(U32 codecId) +{ + const char * ext = 0; + switch(codecId) + { + case AUDIO_CODEC_V12: ext = ".v12"; break; + case AUDIO_CODEC_V24: ext = ".v24"; break; + case AUDIO_CODEC_V29: ext = ".v29"; break; + + default: + return(0); + } + + // create and open the codec + MilesDecoderCodec * codec = new MilesDecoderCodec(ext); + if(!codec->open()) + { + delete codec; + codec = 0; + } + + return(codec); +} + +//-------------------------------------------------------------------------- +bool MilesDecoderCodec::open() +{ + if(mProvider) + return(true); + + RIB_INTERFACE_ENTRY DECODER_REQUEST[] = { + { RIB_FUNCTION, "ASI_stream_attribute", (U32) &mProcStreamAttribute, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_open", (U32) &mProcStreamOpen, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_seek", (U32) &mProcStreamSeek, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_close", (U32) &mProcStreamClose, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_process", (U32) &mProcStreamProcess, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_set_preference", (U32) &mProcStreamSetPreference, RIB_NONE }, + { RIB_ATTRIBUTE, "Output sample rate", (U32) &mAttribOutputSampleRate, RIB_NONE }, + { RIB_ATTRIBUTE, "Output sample width", (U32) &mAttribOutputBits, RIB_NONE }, + { RIB_ATTRIBUTE, "Output channels", (U32) &mAttribOutputChannels, RIB_NONE }, + { RIB_PREFERENCE, "Requested sample rate", (U32) &mAttribRequestedRate, RIB_NONE } }; + + mProvider = RIB_find_file_provider("ASI codec", "Input file types", mExtension); + if(mProvider && (RIB_request(mProvider, "ASI stream", DECODER_REQUEST) == RIB_NOERR)) + return(true); + + return(false); +} + +void MilesDecoderCodec::close() +{ + if(mProvider) + { + RIB_free_provider_handle(mProvider); + mProvider = 0; + } +} + +//-------------------------------------------------------------------------- +void * MilesDecoderCodec::openStream() +{ + if(!mProvider) + return(0); + + HASISTREAM handle = mProcStreamOpen((U32)this, callback_router, 0); + if(!handle) + return(0); + + // properties... + U32 requestedRate = VOICE_FREQUENCY; + mProcStreamSetPreference(handle, mAttribRequestedRate, &requestedRate); + + U32 chan = mProcStreamAttribute(handle, mAttribOutputChannels); + U32 rate = mProcStreamAttribute(handle, mAttribOutputSampleRate); + U32 bits = mProcStreamAttribute(handle, mAttribOutputBits); + + if((chan != VOICE_CHANNELS) || (rate != VOICE_FREQUENCY) || (bits != VOICE_BITS)) + { + mProcStreamClose(handle); + return(0); + } + + HASISTREAM * stream = new HASISTREAM; + *stream = handle; + return(static_cast(stream)); +} + +void MilesDecoderCodec::closeStream(void * stream) +{ + AssertFatal(stream, "MilesDecoderCodec::closeStream: invalid stream ptr"); + HASISTREAM * pHandle = static_cast(stream); + + if(mProvider) + mProcStreamClose(*pHandle); + delete pHandle; +} + +//-------------------------------------------------------------------------- +S32 AILCALLBACK MilesDecoderCodec::callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset) +{ + return(((MilesDecoderCodec*)user)->callback(dest, bytesRequested, offset)); +} + +S32 MilesDecoderCodec::callback(void * dest, S32 bytesRequested, S32 offset) +{ + offset; + return(mQueue->dequeue((U8*)dest, bytesRequested)); +} + +U32 MilesDecoderCodec::process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen) +{ + if(!stream || !queue) + return(0); + + mQueue = queue; + U32 amount = U32(mProcStreamProcess(*static_cast(stream), static_cast(const_cast(data)), S32(maxLen))); + mQueue = 0; + + return(amount); +} diff --git a/audio/audioCodecMiles.h b/audio/audioCodecMiles.h new file mode 100644 index 0000000..b696f74 --- /dev/null +++ b/audio/audioCodecMiles.h @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUDIOCODECMILES_H_ +#define _AUDIOCODECMILES_H_ + +#ifndef _AUDIOCODEC_H_ +#include "audio/audioCodec.h" +#endif +#ifndef _MSS_H_ +#include "mss.h" +#endif + +class MilesEncoderCodec : public VoiceEncoderCodec +{ + private: + MilesEncoderCodec(const char * ext); + ~MilesEncoderCodec(); + + char * mExtension; + HPROVIDER mProvider; + + ASI_STREAM_OPEN mProcStreamOpen; + ASI_STREAM_PROCESS mProcStreamProcess; + ASI_STREAM_CLOSE mProcStreamClose; + + BufferQueue * mQueue; + + static S32 AILCALLBACK callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset); + S32 callback(void * dest, S32 bytesRequested, S32 offset); + + public: + static VoiceCodec * create(U32 codecId); + + bool open(); + void close(); + + void * openStream(); + void closeStream(void * stream); + + U32 process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen); +}; + +class MilesDecoderCodec : public VoiceDecoderCodec +{ + private: + + MilesDecoderCodec(const char * ext); + ~MilesDecoderCodec(); + + char * mExtension; + HPROVIDER mProvider; + + ASI_STREAM_OPEN mProcStreamOpen; + ASI_STREAM_PROCESS mProcStreamProcess; + ASI_STREAM_SEEK mProcStreamSeek; + ASI_STREAM_CLOSE mProcStreamClose; + ASI_STREAM_ATTRIBUTE mProcStreamAttribute; + ASI_STREAM_SET_PREFERENCE mProcStreamSetPreference; + + HATTRIB mAttribOutputSampleRate; + HATTRIB mAttribOutputBits; + HATTRIB mAttribOutputChannels; + HATTRIB mAttribRequestedRate; + + BufferQueue * mQueue; + + static S32 AILCALLBACK callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset); + S32 callback(void * dest, S32 bytesRequested, S32 offset); + + public: + static VoiceCodec * create(U32 codecId); + + bool open(); + void close(); + + void * openStream(); + void closeStream(void * stream); + + U32 process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen); +}; + +#endif // _INC_AUDIOCODEMILES diff --git a/audio/audioDataBlock.cc b/audio/audioDataBlock.cc new file mode 100644 index 0000000..3a1bb44 --- /dev/null +++ b/audio/audioDataBlock.cc @@ -0,0 +1,512 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "audio/audioDataBlock.h" +#include "console/consoleTypes.h" +#include "PlatformWin32/platformAL.h" + +//-------------------------------------------------------------------------- +namespace +{ + void writeRangedF32(BitStream * bstream, F32 val, F32 min, F32 max, U32 numBits) + { + val = (mClampF(val, min, max) - min) / (max - min); + bstream->writeInt(val * ((1 << numBits) - 1), numBits); + } + + F32 readRangedF32(BitStream * bstream, F32 min, F32 max, U32 numBits) + { + return(min + (F32(bstream->readInt(numBits)) / F32((1 << numBits) - 1)) * (max - min)); + } + + void writeRangedS32(BitStream * bstream, S32 val, S32 min, S32 max) + { + bstream->writeRangedU32((val - min), 0, (max - min)); + } + + S32 readRangedS32(BitStream * bstream, S32 min, S32 max) + { + return(bstream->readRangedU32(0, (max - min)) + min); + } +}; + +//-------------------------------------------------------------------------- +// Class AudioEnvironment: +//-------------------------------------------------------------------------- +IMPLEMENT_CO_DATABLOCK_V1(AudioEnvironment); + +AudioEnvironment::AudioEnvironment() +{ + mUseRoom = true; + mRoom = AL_ENVIRONMENT_GENERIC; + mRoomHF = 0; + mReflections = 0; + mReverb = 0; + mRoomRolloffFactor = 0.1f; + mDecayTime = 0.1f; + mDecayHFRatio = 0.1f; + mReflectionsDelay = 0.f; + mReverbDelay = 0.f; + mRoomVolume = 0; + mEffectVolume = 0.f; + mDamping = 0.f; + mEnvironmentSize = 10.f; + mEnvironmentDiffusion = 1.f; + mAirAbsorption = 0.f; + mFlags = 0; +} + +static EnumTable::Enums roomEnums[] = +{ + { AL_ENVIRONMENT_GENERIC, "GENERIC" }, // 0 + { AL_ENVIRONMENT_PADDEDCELL, "PADDEDCELL" }, + { AL_ENVIRONMENT_ROOM, "ROOM" }, + { AL_ENVIRONMENT_BATHROOM, "BATHROOM" }, + { AL_ENVIRONMENT_LIVINGROOM, "LIVINGROOM" }, + { AL_ENVIRONMENT_STONEROOM, "STONEROOM" }, // 5 + { AL_ENVIRONMENT_AUDITORIUM, "AUDITORIUM" }, + { AL_ENVIRONMENT_CONCERTHALL, "CONCERTHALL" }, + { AL_ENVIRONMENT_CAVE, "CAVE" }, + { AL_ENVIRONMENT_ARENA, "ARENA" }, + { AL_ENVIRONMENT_HANGAR, "HANGAR" }, // 10 + { AL_ENVIRONMENT_CARPETEDHALLWAY, "CARPETEDHALLWAY" }, + { AL_ENVIRONMENT_HALLWAY, "HALLWAY" }, + { AL_ENVIRONMENT_STONECORRIDOR, "STONECORRIDOR" }, + { AL_ENVIRONMENT_ALLEY, "ALLEY" }, + { AL_ENVIRONMENT_FOREST, "FOREST" }, // 15 + { AL_ENVIRONMENT_CITY, "CITY" }, + { AL_ENVIRONMENT_MOUNTAINS, "MOUNTAINS" }, + { AL_ENVIRONMENT_QUARRY, "QUARRY" }, + { AL_ENVIRONMENT_PLAIN, "PLAIN" }, + { AL_ENVIRONMENT_PARKINGLOT, "PARKINGLOT" }, // 20 + { AL_ENVIRONMENT_SEWERPIPE, "SEWERPIPE" }, + { AL_ENVIRONMENT_UNDERWATER, "UNDERWATER" }, + { AL_ENVIRONMENT_DRUGGED, "DRUGGED" }, + { AL_ENVIRONMENT_DIZZY, "DIZZY" }, + { AL_ENVIRONMENT_PSYCHOTIC, "PSYCHOTIC" }, // 25 +}; +static EnumTable gAudioEnvironmentRoomTypes(sizeof(roomEnums) / sizeof(roomEnums[0]), &roomEnums[0]); + + +//-------------------------------------------------------------------------- +IMPLEMENT_GETDATATYPE(AudioEnvironment) +IMPLEMENT_SETDATATYPE(AudioEnvironment) + +void AudioEnvironment::consoleInit() +{ + addField("useRoom", TypeBool, Offset(mUseRoom, AudioEnvironment)); + addField("room", TypeEnum, Offset(mRoom, AudioEnvironment), 1, &gAudioEnvironmentRoomTypes); + addField("roomHF", TypeS32, Offset(mRoomHF, AudioEnvironment)); + addField("reflections", TypeS32, Offset(mReflections, AudioEnvironment)); + addField("reverb", TypeS32, Offset(mReverb, AudioEnvironment)); + addField("roomRolloffFactor", TypeF32, Offset(mRoomRolloffFactor, AudioEnvironment)); + addField("decayTime", TypeF32, Offset(mDecayTime, AudioEnvironment)); + addField("decayHFRatio", TypeF32, Offset(mDecayHFRatio, AudioEnvironment)); + addField("reflectionsDelay", TypeF32, Offset(mReflectionsDelay, AudioEnvironment)); + addField("reverbDelay", TypeF32, Offset(mReverbDelay, AudioEnvironment)); + addField("roomVolume", TypeS32, Offset(mRoomVolume, AudioEnvironment)); + addField("effectVolume", TypeF32, Offset(mEffectVolume, AudioEnvironment)); + addField("damping", TypeF32, Offset(mDamping, AudioEnvironment)); + addField("environmentSize", TypeF32, Offset(mEnvironmentSize, AudioEnvironment)); + addField("environmentDiffusion", TypeF32, Offset(mEnvironmentDiffusion, AudioEnvironment)); + addField("airAbsorption", TypeF32, Offset(mAirAbsorption, AudioEnvironment)); + addField("flags", TypeS32, Offset(mFlags, AudioEnvironment)); + + Con::registerType(TypeAudioEnvironmentPtr, sizeof(AudioEnvironment*), + REF_GETDATATYPE(AudioEnvironment), + REF_SETDATATYPE(AudioEnvironment)); +} + +void AudioEnvironment::packData(BitStream* stream) +{ + Parent::packData(stream); + if(stream->writeFlag(mUseRoom)) + stream->writeRangedU32(mRoom, AL_ENVIRONMENT_GENERIC, AL_ENVIRONMENT_COUNT); + else + { + writeRangedS32(stream, mRoomHF, -10000, 0); + writeRangedS32(stream, mReflections, -10000, 10000); + writeRangedS32(stream, mReverb, -10000, 2000); + writeRangedF32(stream, mRoomRolloffFactor, 0.1f, 10.f, 8); + writeRangedF32(stream, mDecayTime, 0.1f, 20.f, 8); + writeRangedF32(stream, mDecayHFRatio, 0.1f, 20.f, 8); + writeRangedF32(stream, mReflectionsDelay, 0.f, 0.3f, 9); + writeRangedF32(stream, mReverbDelay, 0.f, 0.1f, 7); + writeRangedS32(stream, mRoomVolume, -10000, 0); + writeRangedF32(stream, mEffectVolume, 0.f, 1.f, 8); + writeRangedF32(stream, mDamping, 0.f, 2.f, 9); + writeRangedF32(stream, mEnvironmentSize, 1.f, 100.f, 10); + writeRangedF32(stream, mEnvironmentDiffusion, 0.f, 1.f, 8); + writeRangedF32(stream, mAirAbsorption, -100.f, 0.f, 10); + stream->writeInt(mFlags, 6); + } +} + +void AudioEnvironment::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mUseRoom = stream->readFlag(); + if(mUseRoom) + mRoom = stream->readRangedU32(AL_ENVIRONMENT_GENERIC, AL_ENVIRONMENT_COUNT); + else + { + mRoomHF = readRangedS32(stream, -10000, 0); + mReflections = readRangedS32(stream, -10000, 10000); + mReverb = readRangedS32(stream, -10000, 2000); + mRoomRolloffFactor = readRangedF32(stream, 0.1f, 10.f, 8); + mDecayTime = readRangedF32(stream, 0.1f, 20.f, 8); + mDecayHFRatio = readRangedF32(stream, 0.1f, 20.f, 8); + mReflectionsDelay = readRangedF32(stream, 0.f, 0.3f, 9); + mReverbDelay = readRangedF32(stream, 0.f, 0.1f, 7); + mRoomVolume = readRangedS32(stream, -10000, 0); + mEffectVolume = readRangedF32(stream, 0.f, 1.f, 8); + mDamping = readRangedF32(stream, 0.f, 2.f, 9); + mEnvironmentSize = readRangedF32(stream, 1.f, 100.f, 10); + mEnvironmentDiffusion = readRangedF32(stream, 0.f, 1.f, 8); + mAirAbsorption = readRangedF32(stream, -100.f, 0.f, 10); + mFlags = stream->readInt(6); + } +} + +//-------------------------------------------------------------------------- +// Class AudioEnvironmentProfile: +//-------------------------------------------------------------------------- +IMPLEMENT_CO_DATABLOCK_V1(AudioSampleEnvironment); + +AudioSampleEnvironment::AudioSampleEnvironment() +{ + mDirect = 0; + mDirectHF = 0; + mRoom = 0; + mRoomHF = 0; + mObstruction = 0.f; + mObstructionLFRatio = 0.f; + mOcclusion = 0.f; + mOcclusionLFRatio = 0.f; + mOcclusionRoomRatio = 0.f; + mRoomRolloff = 0.f; + mAirAbsorption = 0.f; + mOutsideVolumeHF = 0.f; + mFlags = 0; +} + +//-------------------------------------------------------------------------- +IMPLEMENT_GETDATATYPE(AudioSampleEnvironment) +IMPLEMENT_SETDATATYPE(AudioSampleEnvironment) + +void AudioSampleEnvironment::consoleInit() +{ + addField("direct", TypeS32, Offset(mDirect, AudioSampleEnvironment)); + addField("directHF", TypeS32, Offset(mDirectHF, AudioSampleEnvironment)); + addField("room", TypeS32, Offset(mRoom, AudioSampleEnvironment)); + addField("obstruction", TypeF32, Offset(mObstruction, AudioSampleEnvironment)); + addField("obstructionLFRatio", TypeF32, Offset(mObstructionLFRatio, AudioSampleEnvironment)); + addField("occlusion", TypeF32, Offset(mOcclusion, AudioSampleEnvironment)); + addField("occlusionLFRatio", TypeF32, Offset(mOcclusionLFRatio, AudioSampleEnvironment)); + addField("occlusionRoomRatio", TypeF32, Offset(mOcclusionRoomRatio, AudioSampleEnvironment)); + addField("roomRolloff", TypeF32, Offset(mRoomRolloff, AudioSampleEnvironment)); + addField("airAbsorption", TypeF32, Offset(mAirAbsorption, AudioSampleEnvironment)); + addField("outsideVolumeHF", TypeS32, Offset(mOutsideVolumeHF, AudioSampleEnvironment)); + addField("flags", TypeS32, Offset(mFlags, AudioSampleEnvironment)); + + Con::registerType(TypeAudioSampleEnvironmentPtr, + sizeof(AudioSampleEnvironment*), + REF_GETDATATYPE(AudioSampleEnvironment), + REF_SETDATATYPE(AudioSampleEnvironment)); +} + +void AudioSampleEnvironment::packData(BitStream* stream) +{ + Parent::packData(stream); + writeRangedS32(stream, mDirect, -10000, 1000); + writeRangedS32(stream, mDirectHF, -10000, 0); + writeRangedS32(stream, mRoom, -10000, 1000); + writeRangedS32(stream, mRoomHF, -10000, 0); + writeRangedF32(stream, mObstruction, 0.f, 1.f, 9); + writeRangedF32(stream, mObstructionLFRatio, 0.f, 1.f, 8); + writeRangedF32(stream, mOcclusion, 0.f, 1.f, 9); + writeRangedF32(stream, mOcclusionLFRatio, 0.f, 1.f, 8); + writeRangedF32(stream, mOcclusionRoomRatio, 0.f, 10.f, 9); + writeRangedF32(stream, mRoomRolloff, 0.f, 10.f, 9); + writeRangedF32(stream, mAirAbsorption, 0.f, 10.f, 9); + writeRangedS32(stream, mOutsideVolumeHF, -10000, 0); + stream->writeInt(mFlags, 3); +} + +void AudioSampleEnvironment::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mDirect = readRangedS32(stream, -10000, 1000); + mDirectHF = readRangedS32(stream, -10000, 0); + mRoom = readRangedS32(stream, -10000, 1000); + mRoomHF = readRangedS32(stream, -10000, 0); + mObstruction = readRangedF32(stream, 0.f, 1.f, 9); + mObstructionLFRatio = readRangedF32(stream, 0.f, 1.f, 8); + mOcclusion = readRangedF32(stream, 0.f, 1.f, 9); + mOcclusionLFRatio = readRangedF32(stream, 0.f, 1.f, 8); + mOcclusionRoomRatio = readRangedF32(stream, 0.f, 10.f, 9); + mRoomRolloff = readRangedF32(stream, 0.f, 10.f, 9); + mAirAbsorption = readRangedF32(stream, 0.f, 10.f, 9); + mOutsideVolumeHF = readRangedS32(stream, -10000, 0); + mFlags = stream->readInt(3); +} + +//-------------------------------------------------------------------------- +// Class AudioDescription: +//-------------------------------------------------------------------------- +IMPLEMENT_CO_DATABLOCK_V1(AudioDescription); + +AudioDescription::AudioDescription() +{ + mDescription.mVolume = 1.0f; + mDescription.mIsLooping = false; + mDescription.mIs3D = false; + mDescription.mMinDistance = 1.0f; + mDescription.mMaxDistance = 100.0f; + mDescription.mConeInsideAngle = 360; + mDescription.mConeOutsideAngle = 360; + mDescription.mConeOutsideVolume = 1.0f; + mDescription.mConeVector.set(0, 0, 1); + mDescription.mEnvironmentLevel = 0.f; + mDescription.mLoopCount = -1; + mDescription.mMinLoopGap = 0; + mDescription.mMaxLoopGap = 0; + mDescription.mType = Audio::DefaultAudioType; +} + +//-------------------------------------------------------------------------- +IMPLEMENT_GETDATATYPE(AudioDescription) +IMPLEMENT_SETDATATYPE(AudioDescription) + +void AudioDescription::consoleInit() +{ + addField("volume", TypeF32, Offset(mDescription.mVolume, AudioDescription)); + addField("isLooping", TypeBool, Offset(mDescription.mIsLooping, AudioDescription)); + addField("is3D", TypeBool, Offset(mDescription.mIs3D, AudioDescription)); + addField("minDistance", TypeF32, Offset(mDescription.mMinDistance, AudioDescription)); + addField("maxDistance", TypeF32, Offset(mDescription.mMaxDistance, AudioDescription)); + addField("coneInsideAngle", TypeS32, Offset(mDescription.mConeInsideAngle, AudioDescription)); + addField("coneOutsideAngle", TypeS32, Offset(mDescription.mConeOutsideAngle, AudioDescription)); + addField("coneOutsideVolume", TypeF32, Offset(mDescription.mConeOutsideVolume, AudioDescription)); + addField("coneVector", TypePoint3F, Offset(mDescription.mConeVector, AudioDescription)); + addField("environmentLevel", TypeF32, Offset(mDescription.mEnvironmentLevel, AudioDescription)); + addField("loopCount", TypeS32, Offset(mDescription.mLoopCount, AudioDescription)); + addField("minLoopGap", TypeS32, Offset(mDescription.mMinLoopGap, AudioDescription)); + addField("maxLoopGap", TypeS32, Offset(mDescription.mMaxLoopGap, AudioDescription)); + addField("type", TypeS32, Offset(mDescription.mType, AudioDescription)); + + Con::registerType(TypeAudioDescriptionPtr, sizeof(AudioDescription*), + REF_GETDATATYPE(AudioDescription), + REF_SETDATATYPE(AudioDescription)); +} + +//-------------------------------------------------------------------------- +bool AudioDescription::onAdd() +{ + if (!Parent::onAdd()) + return false; + + // validate the data + mDescription.mVolume = mClampF(mDescription.mVolume, 0.0f, 1.0f); + mDescription.mLoopCount = mClamp(mDescription.mLoopCount, -1, mDescription.mLoopCount); + mDescription.mMaxLoopGap = mClamp(mDescription.mMaxLoopGap, mDescription.mMinLoopGap, mDescription.mMaxLoopGap); + mDescription.mMinLoopGap = mClamp(mDescription.mMinLoopGap, 0, mDescription.mMaxLoopGap); + + if (mDescription.mIs3D) + { + // validate the data + mDescription.mMinDistance = mClampF(mDescription.mMinDistance, 0.f, mDescription.mMinDistance); + mDescription.mMaxDistance = (mDescription.mMaxDistance > mDescription.mMinDistance) ? mDescription.mMaxDistance : (mDescription.mMinDistance+0.01f); + mDescription.mConeInsideAngle = mClamp(mDescription.mConeInsideAngle, 0, 360); + mDescription.mConeOutsideAngle = mClamp(mDescription.mConeOutsideAngle, mDescription.mConeInsideAngle, 360); + mDescription.mConeOutsideVolume = mClampF(mDescription.mConeOutsideVolume, 0.0f, 1.0f); + mDescription.mConeVector.normalize(); + mDescription.mEnvironmentLevel = mClampF(mDescription.mEnvironmentLevel, 0.f, 1.f); + } + + if(mDescription.mType >= Audio::NumAudioTypes) + mDescription.mType = Audio::DefaultAudioType; + + return true; +} + + +//-------------------------------------------------------------------------- +void AudioDescription::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->writeFloat(mDescription.mVolume, 6); + if(stream->writeFlag(mDescription.mIsLooping)) + { + stream->write(mDescription.mLoopCount); + stream->write(mDescription.mMinLoopGap); + stream->write(mDescription.mMaxLoopGap); + } + + stream->writeFlag(mDescription.mIs3D); + if (mDescription.mIs3D) + { + stream->write(mDescription.mMinDistance); + stream->write(mDescription.mMaxDistance); + stream->writeInt(mDescription.mConeInsideAngle, 9); + stream->writeInt(mDescription.mConeOutsideAngle, 9); + stream->writeInt(mDescription.mConeOutsideVolume, 6); + stream->writeNormalVector(mDescription.mConeVector, 8); + stream->write(mDescription.mEnvironmentLevel); + } + stream->writeInt(mDescription.mType, 3); +} + +//-------------------------------------------------------------------------- +void AudioDescription::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mDescription.mVolume = stream->readFloat(6); + mDescription.mIsLooping = stream->readFlag(); + if(mDescription.mIsLooping) + { + stream->read(&mDescription.mLoopCount); + stream->read(&mDescription.mMinLoopGap); + stream->read(&mDescription.mMaxLoopGap); + } + + mDescription.mIs3D = stream->readFlag(); + if ( mDescription.mIs3D ) + { + stream->read(&mDescription.mMinDistance); + stream->read(&mDescription.mMaxDistance); + mDescription.mConeInsideAngle = stream->readInt(9); + mDescription.mConeOutsideAngle = stream->readInt(9); + mDescription.mConeOutsideVolume = stream->readFloat(6); + stream->readNormalVector(&mDescription.mConeVector, 8); + stream->read(&mDescription.mEnvironmentLevel); + } + mDescription.mType = stream->readInt(3); +} + +//-------------------------------------------------------------------------- +// Class AudioProfile: +//-------------------------------------------------------------------------- +IMPLEMENT_CO_DATABLOCK_V1(AudioProfile); + +AudioProfile::AudioProfile() +{ + mFilename = NULL; + mDescriptionObject = NULL; + mSampleEnvironment = 0; + mPreload = false; +} + +//-------------------------------------------------------------------------- +IMPLEMENT_GETDATATYPE(AudioProfile) +IMPLEMENT_SETDATATYPE(AudioProfile) + +void AudioProfile::consoleInit() +{ + addField("filename", TypeString, Offset(mFilename, AudioProfile)); + addField("description", TypeAudioDescriptionPtr, Offset(mDescriptionObject, AudioProfile)); + addField("environment", TypeAudioSampleEnvironmentPtr, Offset(mSampleEnvironment, AudioProfile)); + addField("preload", TypeBool, Offset(mPreload, AudioProfile)); + + Con::registerType(TypeAudioProfilePtr, sizeof(AudioProfile*), + REF_GETDATATYPE(AudioProfile), + REF_SETDATATYPE(AudioProfile)); +} + +//-------------------------------------------------------------------------- +bool AudioProfile::onAdd() +{ + if (!Parent::onAdd()) + return false; + + // if this is client side, make sure that description is as well + if(mDescriptionObject) + { // client side dataBlock id's are not in the dataBlock id range + if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast) + { + SimObjectId pid = mDescriptionObject->getId(); + if (pid < DataBlockObjectIdFirst || pid > DataBlockObjectIdLast) + { + Con::errorf(ConsoleLogEntry::General,"AudioProfile: data dataBlock not networkable (use datablock to create)."); + return false; + } + } + } + + if(mPreload && mFilename != NULL) + { + Resource buffer = AudioBuffer::find(mFilename); + if(bool(buffer)) + { + ALuint bufferId = buffer->getALBuffer(true); + alBufferi_EXT(bufferId, AL_BUFFER_KEEP_RESIDENT, AL_TRUE); + } + } + + return(true); +} + +//-------------------------------------------------------------------------- +void AudioProfile::packData(BitStream* stream) +{ + Parent::packData(stream); + + // audio description: + if (stream->writeFlag(mDescriptionObject)) + stream->writeRangedU32(mDescriptionObject->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + // environmental info: + if (stream->writeFlag(mSampleEnvironment)) + stream->writeRangedU32(mSampleEnvironment->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + // + char buffer[256]; + if(!mFilename) + buffer[0] = 0; + else + dStrcpy(buffer, mFilename); + S32 len = dStrlen(buffer); + if(len > 3 && !dStricmp(buffer + len - 4, ".wav")) + buffer[len-4] = 0; + stream->writeString(buffer); + +} + +//-------------------------------------------------------------------------- +void AudioProfile::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + // audio datablock: + if (stream->readFlag()) { + SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + Sim::findObject(id, mDescriptionObject); + } + + // sample environment: + if (stream->readFlag()) { + SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + Sim::findObject(id, mSampleEnvironment); + } + + char buffer[256]; + stream->readString(buffer); + dStrcat(buffer, ".wav"); + + mFilename = StringTable->insert(buffer); +} + + + + + + + diff --git a/audio/audioDataBlock.h b/audio/audioDataBlock.h new file mode 100644 index 0000000..399e9be --- /dev/null +++ b/audio/audioDataBlock.h @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUDIODATABLOCK_H_ +#define _AUDIODATABLOCK_H_ + +#ifndef _PLATFORMAUDIO_H_ +#include "Platform/platformAudio.h" +#endif +#ifndef _AUDIOBUFFER_H_ +#include "audio/audioBuffer.h" +#endif +#ifndef _BITSTREAM_H_ +#include "Core/bitStream.h" +#endif +#ifndef _NETOBJECT_H_ +#include "Sim/netObject.h" +#endif + +//-------------------------------------------------------------------------- +class AudioEnvironment : public SimDataBlock +{ + 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; + + AudioEnvironment(); + + static void consoleInit(); + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + DECLARE_CONOBJECT(AudioEnvironment); +}; + +//-------------------------------------------------------------------------- +class AudioSampleEnvironment : public SimDataBlock +{ + typedef SimDataBlock Parent; + + public: + + S32 mDirect; + S32 mDirectHF; + S32 mRoom; + S32 mRoomHF; + F32 mObstruction; + F32 mObstructionLFRatio; + F32 mOcclusion; + F32 mOcclusionLFRatio; + F32 mOcclusionRoomRatio; + F32 mRoomRolloff; + F32 mAirAbsorption; + S32 mOutsideVolumeHF; + S32 mFlags; + + AudioSampleEnvironment(); + + static void consoleInit(); + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + DECLARE_CONOBJECT(AudioSampleEnvironment); +}; + +//-------------------------------------------------------------------------- +class AudioDescription: public SimDataBlock +{ + typedef SimDataBlock Parent; + public: + + // field info + Audio::Description mDescription; + + AudioDescription(); + DECLARE_CONOBJECT(AudioDescription); + static void consoleInit(); + virtual bool onAdd(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + + const Audio::Description* getDescription() const { return &mDescription; } +}; + +//---------------------------------------------------------------------------- +class AudioProfile: public SimDataBlock +{ + typedef SimDataBlock Parent; + public: + + // field info + AudioDescription * mDescriptionObject; + AudioSampleEnvironment * mSampleEnvironment; + + StringTableEntry mFilename; + bool mPreload; + + AudioProfile(); + DECLARE_CONOBJECT(AudioProfile); + static void consoleInit(); + + virtual bool onAdd(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + + const Audio::Description* getDescription() const { return mDescriptionObject ? mDescriptionObject->getDescription() : NULL; } + bool isPreload() { return mPreload; } +}; + +#endif // _H_AUDIODATABLOCK_ diff --git a/audio/audioFunctions.cc b/audio/audioFunctions.cc new file mode 100644 index 0000000..a3c4914 --- /dev/null +++ b/audio/audioFunctions.cc @@ -0,0 +1,773 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformAudio.h" +#include "console/simBase.h" +#include "audio/audioDataBlock.h" + + + +extern F32 mAudioTypeVolume[Audio::NumAudioTypes]; + +//-------------------------------------------------------------------------- +// Expose all al get/set methods... +//-------------------------------------------------------------------------- +enum { + Source = BIT(0), + Listener = BIT(1), + Context = BIT(2), + Environment = BIT(3), + Get = BIT(4), + Set = BIT(5), + Int = BIT(6), + Float = BIT(7), + Float3 = BIT(8), + Float6 = BIT(9) +}; + +static ALenum getEnum(const char * name, U32 flags) +{ + AssertFatal(name, "getEnum: bad param"); + + static struct { + char * mName; + ALenum mAlenum; + U32 mFlags; + } table[] = { + //----------------------------------------------------------------------------------------------------------------- + // "name" ENUM Flags + //----------------------------------------------------------------------------------------------------------------- + //{ "AL_SOURCE_TYPE", AL_SOURCE_TYPE, (Source|Get|Set|Float) }, + { "AL_GAIN", AL_GAIN, (Source|Listener|Get|Set|Float) }, + { "AL_GAIN_LINEAR", AL_GAIN_LINEAR, (Source|Listener|Get|Set|Float) }, + { "AL_PITCH", AL_PITCH, (Source|Get|Set|Float) }, + //{ "AL_MIN_DISTANCE", AL_MIN_DISTANCE, (Source|Get|Set|Float) }, + { "AL_MAX_DISTANCE", AL_MAX_DISTANCE, (Source|Get|Set|Float) }, + { "AL_CONE_OUTER_GAIN", AL_CONE_OUTER_GAIN, (Source|Get|Set|Float) }, + { "AL_POSITION", AL_POSITION, (Source|Listener|Get|Set|Float3) }, + { "AL_DIRECTION", AL_DIRECTION, (Source|Get|Set|Float3) }, + { "AL_VELOCITY", AL_VELOCITY, (Source|Listener|Get|Set|Float3) }, + { "AL_ORIENTATION", AL_ORIENTATION, (Listener|Set|Float6) }, + { "AL_CONE_INNER_ANGLE", AL_CONE_INNER_ANGLE, (Source|Get|Set|Int) }, + { "AL_CONE_OUTER_ANGLE", AL_CONE_OUTER_ANGLE, (Source|Get|Set|Int) }, + //{ "AL_SOURCE_LOOPING", AL_SOURCE_LOOPING, (Source|Get|Set|Int) }, + //{ "AL_STREAMING", AL_STREAMING, (Source|Get|Set|Int) }, + //{ "AL_BUFFER", AL_BUFFER, (Source|Get|Set|Int) }, + //{ "AL_SOURCE_AMBIENT", AL_SOURCE_AMBIENT, (Source|Get|Set|Int) }, + + { "AL_VENDOR", AL_VENDOR, (Context|Get) }, + { "AL_VERSION", AL_VERSION, (Context|Get) }, + { "AL_RENDERER", AL_RENDERER, (Context|Get) }, + { "AL_EXTENSIONS", AL_EXTENSIONS, (Context|Get) }, + + //{ "ALC_PROVIDER", ALC_PROVIDER, (Context|Get|Set|Int) }, + //{ "ALC_PROVIDER_COUNT", ALC_PROVIDER_COUNT, (Context|Get|Int) }, + //{ "ALC_PROVIDER_NAME", ALC_PROVIDER_NAME, (Context|Get|Int) }, + //{ "ALC_SPEAKER", ALC_SPEAKER, (Context|Get|Set|Int) }, + //{ "ALC_SPEAKER_COUNT", ALC_SPEAKER_COUNT, (Context|Get|Int) }, + //{ "ALC_SPEAKER_NAME", ALC_SPEAKER_NAME, (Context|Get|Int) }, + //{ "ALC_BUFFER_DYNAMIC_MEMORY_SIZE", ALC_BUFFER_DYNAMIC_MEMORY_SIZE, (Context|Get|Set|Int) }, + //{ "ALC_BUFFER_DYNAMIC_MEMORY_USAGE",ALC_BUFFER_DYNAMIC_MEMORY_USAGE, (Context|Get|Int) }, + //{ "ALC_BUFFER_DYNAMIC_COUNT", ALC_BUFFER_DYNAMIC_COUNT, (Context|Get|Int) }, + //{ "ALC_BUFFER_MEMORY_USAGE", ALC_BUFFER_MEMORY_USAGE, (Context|Get|Int) }, + //{ "ALC_BUFFER_COUNT", ALC_BUFFER_COUNT, (Context|Get|Int) }, + //{ "ALC_BUFFER_LATENCY", ALC_BUFFER_LATENCY, (Context|Get|Int) }, + /* + // environment + { "AL_ENV_ROOM_IASIG", AL_ENV_ROOM_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_ROOM_HIGH_FREQUENCY_IASIG", AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_REFLECTIONS_IASIG", AL_ENV_REFLECTIONS_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_REVERB_IASIG", AL_ENV_REVERB_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG", AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DECAY_TIME_IASIG", AL_ENV_DECAY_TIME_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG", AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_REFLECTIONS_DELAY_IASIG", AL_ENV_REFLECTIONS_DELAY_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_REVERB_DELAY_IASIG", AL_ENV_REVERB_DELAY_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DIFFUSION_IASIG", AL_ENV_DIFFUSION_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DENSITY_IASIG", AL_ENV_DENSITY_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG", AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG, (Environment|Get|Set|Float) }, + + { "AL_ENV_ROOM_VOLUME_EXT", AL_ENV_ROOM_VOLUME_EXT, (Environment|Get|Set|Int) }, + { "AL_ENV_FLAGS_EXT", AL_ENV_FLAGS_EXT, (Environment|Get|Set|Int) }, + { "AL_ENV_EFFECT_VOLUME_EXT", AL_ENV_EFFECT_VOLUME_EXT, (Environment|Get|Set|Float) }, + { "AL_ENV_DAMPING_EXT", AL_ENV_DAMPING_EXT, (Environment|Get|Set|Float) }, + { "AL_ENV_ENVIRONMENT_SIZE_EXT", AL_ENV_ENVIRONMENT_SIZE_EXT, (Environment|Get|Set|Float) }, + + // sample environment + { "AL_ENV_SAMPLE_DIRECT_EXT", AL_ENV_SAMPLE_DIRECT_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_DIRECT_HF_EXT", AL_ENV_SAMPLE_DIRECT_HF_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_ROOM_EXT", AL_ENV_SAMPLE_ROOM_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_ROOM_HF_EXT", AL_ENV_SAMPLE_ROOM_HF_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT", AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_FLAGS_EXT", AL_ENV_SAMPLE_FLAGS_EXT, (Source|Get|Set|Int) }, + + { "AL_ENV_SAMPLE_REVERB_MIX_EXT", AL_ENV_SAMPLE_REVERB_MIX_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OBSTRUCTION_EXT", AL_ENV_SAMPLE_OBSTRUCTION_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT", AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OCCLUSION_EXT", AL_ENV_SAMPLE_OCCLUSION_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT", AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_AIR_ABSORPTION_EXT", AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, (Source|Get|Set|Float) }, + */ + }; + for(U32 i = 0; i < (sizeof(table) / sizeof(table[0])); i++) + { + if((table[i].mFlags & flags) != flags) + continue; + + if(dStricmp(table[i].mName, name) == 0) + return(table[i].mAlenum); + } + + return(AL_INVALID); +} + + +//----------------------------------------------- +ConsoleFunction(OpenALInitDriver, bool, 1, 1, "OpenALInitDriver()") +{ + if(Audio::OpenALInit()) + { + ResourceManager->registerExtension(".wav", AudioBuffer::construct); + Con::setIntVariable("DefaultAudioType", Audio::DefaultAudioType); + Con::setIntVariable("ChatAudioType", Audio::ChatAudioType); + Con::setIntVariable("GuiAudioType", Audio::GuiAudioType); + Con::setIntVariable("EffectAudioType", Audio::EffectAudioType); + + return true; + } + return false; +} + +//----------------------------------------------- +ConsoleFunction(OpenALShutdownDriver, void, 1, 1, "OpenALShutdownDriver()") +{ + Audio::OpenALShutdown(); +} + + +//----------------------------------------------- +ConsoleFunction(alGetString, const char *, 2, 2, "alGetString(enum)") +{ + argc; + ALenum e = getEnum(argv[1], (Context|Get)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "alGetString: invalid enum name '%s'", argv[1]); + return ""; + } + + return (const char*)alGetString(e); +} + + +//-------------------------------------------------------------------------- +// Source +//-------------------------------------------------------------------------- +ConsoleFunction(alxCreateSource, S32, 2, 6, "alxCreateSource(profile, {x,y,z} | description, filename, {x,y,z})") +{ + AudioDescription *description = NULL; + AudioProfile *profile = dynamic_cast( Sim::findObject( argv[1] ) ); + if (profile == NULL) + { + description = dynamic_cast( Sim::findObject( argv[1] ) ); + if (description == NULL) + { + Con::printf("Unable to locate audio profile/description '%s'", argv[1]); + return NULL_AUDIOHANDLE; + } + } + + if (profile) + { + if (argc == 2) + return alxCreateSource(profile); + + MatrixF transform; + transform.set(EulerF(0,0,0), Point3F( dAtof(argv[2]),dAtof(argv[3]),dAtof(argv[4]) )); + return alxCreateSource(profile, &transform); + } + + if (description) + { + if (argc == 3) + return alxCreateSource(description, argv[2]); + + MatrixF transform; + transform.set(EulerF(0,0,0), Point3F( dAtof(argv[3]),dAtof(argv[4]),dAtof(argv[5]) )); + return alxCreateSource(description, argv[2], &transform); + } + + return NULL_AUDIOHANDLE; +} + + +//----------------------------------------------- +ConsoleFunction(alxSourcef, void, 4, 4, "alxSourcef(handle, ALenum, value)") +{ + ALenum e = getEnum(argv[2], (Source|Set|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcef: invalid enum name '%s'", argv[2]); + return; + } + + alxSourcef(dAtoi(argv[1]), e, dAtof(argv[3])); +} + + +//----------------------------------------------- +ConsoleFunction(alxSource3f, void, 3, 6, "alxSource3f(handle, ALenum, \"x y z\" | x, y, z)") +{ + ALenum e = getEnum(argv[2], (Source|Set|Float3)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: invalid enum name '%s'", argv[2]); + return; + } + + if(argc != 3 || argc != 6) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: wrong number of args"); + return; + } + + Point3F pos; + if(argc == 3) + dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z); + else + { + pos.x = dAtof(argv[1]); + pos.y = dAtof(argv[2]); + pos.z = dAtof(argv[3]); + } + + alxSource3f(dAtoi(argv[1]), e, pos.x, pos.y, pos.z); +} + + +//----------------------------------------------- +ConsoleFunction(alxSourcei, void, 4, 4, "alxSourcei(handle, ALenum, value)") +{ + ALenum e = getEnum(argv[2], (Source|Set|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcei: invalid enum name '%s'", argv[2]); + return; + } + + alxSourcei(dAtoi(argv[1]), e, dAtoi(argv[3])); +} + + +//----------------------------------------------- +ConsoleFunction(alxGetSourcef, F32, 3, 3, "alxGetSourcef(handle, ALenum)") +{ + ALenum e = getEnum(argv[2], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcef: invalid enum name '%s'", argv[2]); + return(0.f); + } + + F32 value; + alxGetSourcef(dAtoi(argv[1]), e, &value); + return(value); +} + + +//----------------------------------------------- +ConsoleFunction(alxGetSource3f, const char *, 3, 3, "alxGetSource3f(handle, ALenum)" ) +{ + ALenum e = getEnum(argv[2], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSource3f: invalid enum name '%s'", argv[2]); + return("0 0 0"); + } + + F32 value1, value2, value3; + alxGetSource3f(dAtoi(argv[1]), e, &value1, &value2, &value3); + + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3); + return(ret); +} + + +//----------------------------------------------- +ConsoleFunction(alxGetSourcei, S32, 3, 3, "alxGetSourcei(handle, ALenum)") +{ + ALenum e = getEnum(argv[2], (Source|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcei: invalid enum name '%s'", argv[2]); + return(0); + } + + S32 value; + alxGetSourcei(dAtoi(argv[1]), e, &value); + return(value); +} + + +//----------------------------------------------- +ConsoleFunction(alxPlay, S32, 2, 5, "alxPlay(handle) | alxPlay(profile, {x,y,z})") +{ + if (argc == 2) + { + AUDIOHANDLE handle = dAtoi(argv[1]); + return alxPlay(handle); + } + + AudioProfile *profile = dynamic_cast( Sim::findObject( argv[1] ) ); + if (profile == NULL) + { + Con::printf("Unable to locate audio profile '%s'", argv[1]); + return NULL_AUDIOHANDLE; + } + + Point3F pos(0.f, 0.f, 0.f); + if(argc == 3) + dSscanf(argv[2], "%f %f %f", &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); + + return alxPlay(profile, &transform, NULL); +} + +//----------------------------------------------- +ConsoleFunction(alxStop, void, 2, ,2, "alxStop(handle)") +{ + AUDIOHANDLE handle = dAtoi(argv[1]); + if(handle == NULL_AUDIOHANDLE) + return; + alxStop(handle); +} + +//----------------------------------------------- +ConsoleFunction(alxStopAll, void, 1, 1, "alxStopAll()") +{ + alxStopAll(); +} + + +//-------------------------------------------------------------------------- +// Listener +//-------------------------------------------------------------------------- +ConsoleFunction(alxListenerf, void, 2, 2, "alxListener(ALenum, value)") +{ + ALenum e = getEnum(argv[1], (Listener|Set|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "alxListenerf: invalid enum name '%s'", argv[1]); + return; + } + + alxListenerf(e, dAtof(argv[2])); +} + + +//----------------------------------------------- +ConsoleFunction(alxListener3f, void, 3, 5, "alxListener3f(ALenum, \"x y z\" | x, y, z)") +{ + ALenum e = getEnum(argv[1], (Listener|Set|Float3)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "alxListener3f: invalid enum name '%s'", argv[1]); + return; + } + + if(argc != 3 || argc != 5) + { + Con::errorf(ConsoleLogEntry::General, "alxListener3f: wrong number of arguments"); + return; + } + + Point3F pos; + if(argc == 3) + dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z); + else + { + pos.x = dAtof(argv[2]); + pos.y = dAtof(argv[3]); + pos.z = dAtof(argv[4]); + } + + alxListener3f(e, pos.x, pos.y, pos.z); +} + + +//----------------------------------------------- +ConsoleFunction(alxGetListenerf, F32, 2, 2, "alxGetListenerf(Alenum)") +{ + ALenum e = getEnum(argv[1], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "alxGetListenerf: invalid enum name '%s'", argv[1]); + return(0.f); + } + + F32 value; + alxGetListenerf(e, &value); + return(value); +} + + +//----------------------------------------------- +ConsoleFunction(alxGetListener3f, const char *, 2, 2, "alxGetListener3f(Alenum)") +{ + ALenum e = getEnum(argv[2], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "alxGetListener3f: invalid enum name '%s'", argv[1]); + return("0 0 0"); + } + + F32 value1, value2, value3; + alxGetListener3f(e, &value1, &value2, &value3); + + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3); + return(ret); +} + + +//----------------------------------------------- +ConsoleFunction(alxGetListeneri, S32, 2, 2, "alxGetListeneri(Alenum)") +{ + ALenum e = getEnum(argv[1], (Source|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "alxGetListeneri: invalid enum name '%s'", argv[1]); + return(0); + } + + S32 value; + alxGetListeneri(e, &value); + return(value); +} + + +//-------------------------------------------------------------------------- +// Channel Volumes +//-------------------------------------------------------------------------- +ConsoleFunction(alxGetChannelVolume, S32, 2, 2, "alxGetChannelVolume(channel_id)") +{ + U32 type = dAtoi(argv[1]); + if(type >= Audio::NumAudioTypes) + { + Con::errorf(ConsoleLogEntry::General, "alxGetChannelVolume: invalid channel '%d'", dAtoi(argv[1])); + return(0.f); + } + + return(mAudioTypeVolume[type]); +} + +//----------------------------------------------- +ConsoleFunction(alxSetChannelVolume, bool, 3, 3, "alxGetChannelVolume(channel_id, volume 0.0-1.0)") +{ + U32 type = dAtoi(argv[1]); + F32 volume = mClampF(dAtof(argv[2]), 0.f, 1.f); + + if(type >= Audio::NumAudioTypes) + { + Con::errorf(ConsoleLogEntry::General, "alxSetChannelVolume: invalid channel '%d'", dAtoi(argv[1])); + return false; + } + + mAudioTypeVolume[type] = volume; +#pragma message("todo") /* + alxUpdateTypeGain(1 << type); +*/ + return true; +} + +//----------------------------------------------- +//----------------------------------------------- +//----------------------------------------------- +//----------------------------------------------- + + +#if 0 + +ConsoleFunction(ExpandFilename, const char*, 2, 2, "ExpandFilename(filename)") +{ +} + + +//-------------------------------------------------------------------------- +void init() +{ + Con::addCommand("alxIsEnabled", cAudio_isEnabled, "alxIsEnabled(name)", 2, 2); + + Con::addCommand("alxIsExtensionPresent", cAudio_isExtensionPresent, "alxIsExtensionPresent(name)", 2, 2); + + Con::addCommand("alxContexti", cAudio_alxContexti, "alxContexti(Alenum, value)", 3, 3); + Con::addCommand("alxGetContexti", cAudio_alxGetContexti, "alxGetContexti(Alenum)", 2, 2); + Con::addCommand("alxGetContextstr", cAudio_alxGetContextstr, "alxGetContextstr(Alenum, idx)", 3, 3); + + Con::addCommand("alxEnvironmenti", cAudio_alxEnvironmenti, "alxEnvironmenti(Alenum, value)", 3, 3); + Con::addCommand("alxEnvironmentf", cAudio_alxEnvironmentf, "alxEnvironmentf(Alenum, value)", 3, 3); + Con::addCommand("alxGetEnvironmenti", cAudio_alxGetEnvironmenti, "alxGetEnvironmenti(Alenum)", 2, 2); + Con::addCommand("alxGetEnvironmentf", cAudio_alxGetEnvironmentf, "alxGetEnvironmentf(Alenum)", 2, 2); + + Con::addCommand("alxSetEnvironment", cAudio_alxSetEnvironment, "alxSetEnvironment(AudioEnvironmentData)", 2, 2); + Con::addCommand("alxEnableEnvironmental", cAudio_alxEnableEnvironmental, "alxEnableEnvironmental(bool)", 2, 2 ); + + Con::addCommand("alxGetWaveLen", cAudio_alxGetWaveLen, "alxGetWaveLen(profile|filename)", 2, 2); + + Con::addCommand("getAudioDriverList", cGetAudioDriverList, "getAudioDriverList();", 1, 1); + Con::addCommand("getAudioDriverInfo", cGetAudioDriverInfo, "getAudioDriverInfo();", 1, 1); + + Con::addCommand("alxSetCaptureGainScale", cAudio_alxSetCaptureGainScale, "alxSetCaptureGainScale(scale)", 2, 2); + Con::addCommand("alxGetCaptureGainScale", cAudio_alxGetCaptureGainScale, "alxGetCaptureGainScale()", 1, 1); + + Con::addCommand("alxDisableOuterFalloffs", cAudio_alxDisableOuterFalloffs, "alxDisableOuterFalloffs(bool)", 2, 2); + Con::addCommand("alxSetInnerFalloffScale", cAudio_alxSetInnerFalloffScale, "alxSetInnerFalloffScale(scale)", 2, 2); + Con::addCommand("alxGetInnerFalloffScale", cAudio_alxGetInnerFalloffScale, "alxGetInnerFalloffScale()", 1, 1); + + Con::addCommand("alxForceMaxDistanceUpdate", cAudio_alxForceMaxDistanceUpdate, "alxForceMaxDistanceUpdate(bool)", 2, 2); + + // default all channels to full gain + for(U32 i = 0; i < Audio::NumAudioTypes; i++) + mAudioTypeVolume[i] = 1.f; +} + + +//-------------------------------------------------------------------------- +// Console functions +//-------------------------------------------------------------------------- +static bool cAudio_setDriver(SimObject *, S32, const char *argv[]) +{ + return(Audio::setDriver(argv[1])); +} + + + + +//-------------------------------------------------------------------------- +static void cAudio_alxEnvironmenti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Set|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmenti: invalid enum name '%s'", argv[1]); + return; + } + + alxEnvironmenti(e, dAtoi(argv[2])); +} + +static void cAudio_alxEnvironmentf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Set|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmentf: invalid enum name '%s'", argv[1]); + return; + } + + alxEnvironmenti(e, dAtof(argv[2])); +} + +static S32 cAudio_alxGetEnvironmenti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmenti: invalid enum name '%s'", argv[1]); + return(0); + } + + S32 value; + alxGetEnvironmenti(e, &value); + return(value); +} + +static F32 cAudio_alxGetEnvironmentf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmentf: invalid enum name '%s'", argv[1]); + return(0.f); + } + + F32 value; + alxGetEnvironmentf(e, &value); + return(value); +} + +static void cAudio_alxSetEnvironment(SimObject *, S32, const char ** argv) +{ + AudioEnvironment * environment = dynamic_cast(Sim::findObject(argv[1])); + alxSetEnvironment(environment); +} + +static void cAudio_alxEnableEnvironmental(SimObject *, S32, const char ** argv) +{ + alxEnableEnvironmental(dAtob(argv[1])); +} + +//-------------------------------------------------------------------------- +// Misc +//-------------------------------------------------------------------------- +static S32 cAudio_alxGetWaveLen(SimObject *, S32, const char ** argv) +{ + // filename or profile? + AudioProfile * profile = 0; + const char * fileName = 0; + + if(!dStrrchr(argv[1], '.')) + { + profile = dynamic_cast(Sim::findObject(argv[1])); + if(!profile) + { + Con::errorf(ConsoleLogEntry::General, "Unable to locate audio profile: '%s'", argv[1]); + return(0); + } + + fileName = profile->mFilename; + } + else + fileName = argv[1]; + + Resource buffer = AudioBuffer::find(fileName); + if(!bool(buffer)) + { + Con::errorf(ConsoleLogEntry::General, "Failed to find wave file: '%s'", fileName); + return(0); + } + + ALuint alBuffer = buffer->getALBuffer(true); + if(alBuffer == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetWaveLen: invalid buffer"); + return(0); + } + return(alxGetWaveLen(alBuffer)); +} + +static const char* cGetAudioDriverList( SimObject*, S32, const char** ) +{ + return( Audio::getDriverListString() ); +} + +static const char* cGetAudioDriverInfo( SimObject*, S32, const char** ) +{ + return( Audio::getCurrentDriverInfo() ); +} + +static void cAudio_alxSetCaptureGainScale(SimObject *, S32, const char ** argv) +{ + mCaptureGainScale = mClampF(dAtof(argv[1]), MIN_CAPTURE_SCALE, MAX_CAPTURE_SCALE); +} + +static F32 cAudio_alxGetCaptureGainScale(SimObject *, S32, const char **) +{ + return(mCaptureGainScale); +} + +static void cAudio_alxForceMaxDistanceUpdate(SimObject *, S32, const char ** argv) +{ + mForceMaxDistanceUpdate = dAtob(argv[1]); +} + +static void cAudio_alxDisableOuterFalloffs(SimObject *, S32, const char ** argv) +{ + alxDisableOuterFalloffs(dAtob(argv[1])); +} + +static void cAudio_alxSetInnerFalloffScale(SimObject *, S32, const char ** argv) +{ + alxSetInnerFalloffScale(dAtof(argv[1])); +} + +static F32 cAudio_alxGetInnerFalloffScale(SimObject *, S32, const char **) +{ + return(alxGetInnerFalloffScale()); +} + +// Music: ---------------------------------------------------------------- +static void cAudio_alxPlayMusic(SimObject *, S32, const char ** argv) +{ + alxPlayMusicStream(StringTable->insert(argv[1])); +} + +static void cAudio_alxStopMusic(SimObject *, S32, const char **) +{ + alxStopMusicStream(); +} + +static void cAudio_alxContexti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Context|Set|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxContexti: invalid enum name '%s'", argv[1]); + return; + } + + alxContexti(e, dAtoi(argv[2])); +} + +static S32 cAudio_alxGetContexti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Context|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContexti: invalid enum name '%s'", argv[2]); + return(0); + } + + ALint value = 0; + alxGetContexti(e, &value); + return(value); +} + +static const char * cAudio_alxGetContextstr(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Context|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContextstr: invalid enum name '%s'", argv[2]); + return(""); + } + + ALubyte * str = (ALubyte *)""; + alxGetContextstr(e, dAtoi(argv[2]), &str); + return(StringTable->insert((const char *)str)); +} + +static bool cAudio_isEnabled(SimObject *, S32, const char ** argv) +{ + if(!dStricmp(argv[1], "system")) + return(mInitialized); + if(!dStricmp(argv[1], "capture")) + return(mCaptureInitialized); + if(!dStricmp(argv[1], "environment_iasig")) + return(mEnvironment && mEnvironmentEnabled); + return(false); +} + +static bool cAudio_isExtensionPresent(SimObject *, S32, const char ** argv) +{ +#pragma message("todo") /* + return((bool)alIsExtensionPresent((const ALubyte *)argv[1])); +*/ + return false; +} + + + + +#endif \ No newline at end of file diff --git a/audio/audioMss.cc b/audio/audioMss.cc new file mode 100644 index 0000000..61d16c4 --- /dev/null +++ b/audio/audioMss.cc @@ -0,0 +1,352 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "audio/audioMss.h" + +namespace { + const char * cVoiceCodecs [] = { ".v12", ".v24", ".v29" }; + const U32 cNumVoiceCodecs = sizeof(cVoiceCodecs) / sizeof(cVoiceCodecs[0]); +}; + +HPROVIDER EncoderStream::hProvider = 0; // ASI provider used to encode data +U32 EncoderStream::mCodecLevel = 0; + +ASI_STREAM_OPEN EncoderStream::streamOpen; +ASI_STREAM_PROCESS EncoderStream::streamProcess; +ASI_STREAM_CLOSE EncoderStream::streamClose; + +//-------------------------------------- +// - can encode using just one method (hence the statics) +EncoderStream::EncoderStream() +{ + hStream = NULL; + mStreamId = 0; + mConnection = NULL; +} + +//-------------------------------------- +EncoderStream::~EncoderStream() +{ + closeStream(); +} + +//-------------------------------------- +bool EncoderStream::open(U32 codecLevel) +{ + if(codecLevel >= cNumVoiceCodecs) + return(false); + + mCodecLevel = codecLevel; + + // there can be only one + if(hProvider) + return true; + + RIB_INTERFACE_ENTRY ENCODER_REQUEST[] = { + { RIB_FUNCTION, "ASI_stream_open", (U32) &streamOpen, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_close", (U32) &streamClose, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_process", (U32) &streamProcess, RIB_NONE } }; + + hProvider = RIB_find_file_provider("ASI codec", "Output file types", const_cast(cVoiceCodecs[codecLevel])); + if (hProvider != NULL) + if (RIB_request(hProvider, "ASI stream", ENCODER_REQUEST) == RIB_NOERR) + { + return true; + } + + Con::printf("EncoderStream::open FAILED"); + close(); + return false; +} + +//-------------------------------------- +void EncoderStream::close() +{ + if (hProvider) + { + RIB_free_provider_handle(hProvider); + hProvider = NULL; + } +} + + +//-------------------------------------- +bool EncoderStream::openStream() +{ + if (!hProvider) + return false; + + closeStream(); + mConnection = NULL; + hStream = streamOpen((U32)this, callback_router, 0); + mQueue.setSize(8000*2*5); + + if (hStream) + Con::printf("EncoderStream::open %d", mStreamId); + else + Con::printf("EncoderStream::open FAILED"); + + return (hStream != NULL); +} + + +//-------------------------------------- +void EncoderStream::closeStream() +{ + if (hStream) + { + streamClose(hStream); + hStream = NULL; + } + + mSequence = 0; +} + + +bool EncoderStream::setConnection(GameConnection *con) +{ + mStreamId++; + mSequence = 0; + mConnection = con; + mQueue.clear(); + return true; +} + + +//-------------------------------------- +S32 AILCALLBACK EncoderStream::callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset) +{ + return ((EncoderStream*)user)->callback(dest, bytesRequested, offset); +} + +//-------------------------------------------------------------------------- +S32 EncoderStream::callback(void *dest, S32 bytesRequested, S32 offset) +{ + offset; // unused + return mQueue.dequeue((U8*)dest, bytesRequested); +} + +//-------------------------------------- +void EncoderStream::setBuffer(const U8 *buffer, U32 size) +{ + if (size > mQueue.getFree()) + { + AssertWarn(0, "EncoderStream: queue full"); + return; + } + + mQueue.enqueue(buffer, size); +} + + +//-------------------------------------- +void EncoderStream::process(bool flush) +{ + if (!hStream) + return; + + while ( flush || (mQueue.getUsed() >= 1800) ) + { + SimVoiceStreamEvent *mEvent = new SimVoiceStreamEvent(mStreamId, mSequence++, mCodecLevel); + S32 amount = streamProcess(hStream, mEvent->getData(), SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE); + mEvent->setDataSize(amount); + + mConnection->postNetEvent(mEvent); + + if (flush && amount < SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE) + break; + } +} + + +//-------------------------------------- +void EncoderStream::flush() +{ + process(true); + mQueue.clear(); +} + + +//-------------------------------------------------------------------------- +// Class VoiceDecoder: +//-------------------------------------------------------------------------- +VoiceDecoder::VoiceDecoder() +{ + hProvider = 0; + mCodecLevel = U32(-1); +} + +VoiceDecoder::~VoiceDecoder() +{ + close(); +} + +bool VoiceDecoder::open(U32 codecLevel) +{ + if(codecLevel >= cNumVoiceCodecs) + return(false); + + mCodecLevel = codecLevel; + if(hProvider) + return true; + + RIB_INTERFACE_ENTRY DECODER_REQUEST[] = { + { RIB_FUNCTION, "ASI_stream_attribute", (U32) &streamAttribute, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_open", (U32) &streamOpen, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_seek", (U32) &streamSeek, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_close", (U32) &streamClose, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_process", (U32) &streamProcess, RIB_NONE }, + { RIB_FUNCTION, "ASI_stream_set_preference", (U32) &streamSetPreference, RIB_NONE }, + { RIB_ATTRIBUTE, "Output sample rate", (U32) &OUTPUT_SAMPLE_RATE, RIB_NONE }, + { RIB_ATTRIBUTE, "Output sample width", (U32) &OUTPUT_BITS, RIB_NONE }, + { RIB_ATTRIBUTE, "Output channels", (U32) &OUTPUT_CHANNELS, RIB_NONE }, + { RIB_PREFERENCE, "Requested sample rate", (U32) &REQUESTED_RATE, RIB_NONE } }; + + hProvider = RIB_find_file_provider("ASI codec", "Input file types", cVoiceCodecs[codecLevel]); + if (hProvider != NULL) + if (RIB_request(hProvider, "ASI stream", DECODER_REQUEST) == RIB_NOERR) + return true; + + Con::printf("VoiceDecoder::open FAILED"); + close(); + return false; +} + +void VoiceDecoder::close() +{ + if (hProvider) + { + AssertWarn(hProvider, "No Provider?") + RIB_free_provider_handle(hProvider); + hProvider = NULL; + } +} + +//-------------------------------------------------------------------------- +DecoderStream::DecoderStream() +{ + hStream = NULL; + mDecoder = 0; +} + +//-------------------------------------- +DecoderStream::~DecoderStream() +{ + close(); +} + +//-------------------------------------- +bool DecoderStream::open() +{ + if(!mDecoder) + return false; + + close(); + hStream = mDecoder->streamOpen((U32)this, callback_router, 0); + + if (hStream) + Con::printf("DecoderStream::open"); + else + { + Con::printf("DecoderStream::open FAILED"); + return false; + } + + mInQueue.setSize(8000*5); + mOutQueue.setSize(8000*2*5); + + // request an output rate + U32 request = OUTPUT_RATE; + mDecoder->streamSetPreference(hStream, mDecoder->REQUESTED_RATE, &request); + + U32 nch = mDecoder->streamAttribute(hStream, mDecoder->OUTPUT_CHANNELS); + U32 rate = mDecoder->streamAttribute(hStream, mDecoder->OUTPUT_SAMPLE_RATE); + U32 bits = mDecoder->streamAttribute(hStream, mDecoder->OUTPUT_BITS); + + // make a few assumptions + if (nch != 1 || rate != OUTPUT_RATE || bits != 16) + { + close(); + return false; + } + + return true; +} + + +//-------------------------------------- +void DecoderStream::close() +{ + if(hStream && mDecoder) + mDecoder->streamClose(hStream); + hStream = 0; +} + +void DecoderStream::setDecoder(VoiceDecoder * decoder) +{ + if(mDecoder && mDecoder != decoder) + close(); + mDecoder = decoder; +} + +//-------------------------------------- +S32 AILCALLBACK DecoderStream::callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset) +{ + return ((DecoderStream*)user)->callback(dest, bytesRequested, offset); +} + + +//-------------------------------------------------------------------------- +S32 DecoderStream::callback(void *dest, S32 bytesRequested, S32 offset) +{ + offset; // unused + return mInQueue.dequeue((U8*)dest, bytesRequested); +} + + +//-------------------------------------- +U32 DecoderStream::getBuffer(U8 **data, U32 *size) +{ + *data = mOutQueue.getHead(); + *size = mOutQueue.getContiguousUsed(); + mOutQueue.dequeue(*size); + return *size; +} + + +//-------------------------------------- +void DecoderStream::setBuffer(U8 *data, U32 size) +{ + if (size > mInQueue.getFree()) + { + AssertWarn(0, "DecoderStream: queue full"); + return; + } + + mInQueue.enqueue(data, size); +} + + +//-------------------------------------- +void DecoderStream::process(bool flush) +{ + if (!hStream) + return; + + while ( flush || (mInQueue.getUsed() && !mOutQueue.isFull()) ) + { + + S32 amount = mDecoder->streamProcess(hStream, mOutQueue.getTail(), mOutQueue.getContiguousFree()); + mOutQueue.enqueue(amount); + + if (flush && amount == 0) + break; + } +} + + diff --git a/audio/audioMss.h b/audio/audioMss.h new file mode 100644 index 0000000..60221b9 --- /dev/null +++ b/audio/audioMss.h @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUDIOMSS_H_ +#define _AUDIOMSS_H_ + +#ifndef _AUDIONET_H_ +#include "audio/audioNet.h" +#endif +#ifndef _GAMECONNECTION_H_ +#include "game/gameConnection.h" +#endif +#ifndef _MSS_H_ +#include "mss.h" +#endif +#ifndef _FILESTREAM_H_ +#include "core/fileStream.h" +#endif +#ifndef _BUFFERQUEUE_H_ +#include "audio/bufferQueue.h" +#endif + +//-------------------------------------------------------------------------- +// we can only have one encoder... but multiple decoders can be installed +class EncoderStream +{ +private: + static HPROVIDER hProvider; // ASI provider used to encode data + static U32 mCodecLevel; + + static ASI_STREAM_OPEN streamOpen; + static ASI_STREAM_PROCESS streamProcess; + static ASI_STREAM_CLOSE streamClose; + + HASISTREAM hStream; + BufferQueue mQueue; + + GameConnection *mConnection; + U8 mStreamId; + U8 mSequence; + + S32 callback(void *dest, S32 bytesRequested, S32 offset); + static S32 AILCALLBACK callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset); + void sendPacket(bool flush=false); + +public: + + EncoderStream(); + ~EncoderStream(); + + static bool open(U32 codecLevel); + static void close(); + + bool openStream(); + void closeStream(); + + bool setConnection(GameConnection *con); + void setBuffer(const U8 *buffer, U32 size); + void process(bool flush=false); + void flush(); +}; + +//-------------------------------------------------------------------------- +class VoiceDecoder +{ + friend class DecoderStream; + +private: + HPROVIDER hProvider; // ASI provider used to decode data + + ASI_STREAM_OPEN streamOpen; + ASI_STREAM_PROCESS streamProcess; + ASI_STREAM_SEEK streamSeek; + ASI_STREAM_CLOSE streamClose; + ASI_STREAM_ATTRIBUTE streamAttribute; + ASI_STREAM_SET_PREFERENCE streamSetPreference; + + HATTRIB OUTPUT_SAMPLE_RATE; + HATTRIB OUTPUT_BITS; + HATTRIB OUTPUT_CHANNELS; + HATTRIB REQUESTED_RATE; + + U32 mCodecLevel; + +public: + VoiceDecoder(); + ~VoiceDecoder(); + + U32 getCodecLevel() {return(mCodecLevel);} + bool open(U32 codecLevel); + void close(); +}; + +class DecoderStream +{ + HASISTREAM hStream; + + GameConnection *mConnection; + BufferQueue mInQueue; + BufferQueue mOutQueue; + + enum { OUTPUT_RATE = 8000 }; + + VoiceDecoder * mDecoder; + + //-------------------------------------- + S32 callback(void *dest, S32 bytesRequested, S32 offset); + static S32 AILCALLBACK callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset); + +public: + DecoderStream(); + ~DecoderStream(); + + void setDecoder(VoiceDecoder * decoder); + bool open(); + void close(); + + void setBuffer(U8 *data, U32 size); + U32 getBuffer(U8 **data, U32 *size); + void process(bool flush=false); +}; + + +#endif // _H_AUDIOMSS_ diff --git a/audio/audioNet.cc b/audio/audioNet.cc new file mode 100644 index 0000000..ba940ce --- /dev/null +++ b/audio/audioNet.cc @@ -0,0 +1,197 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platformAudio.h" +#include "audio/audioNet.h" +#include "core/bitStream.h" +#include "game/shapeBase.h" +#include "game/gameConnection.h" +#include "audio/audioCodec.h" + +#define SIZE_BITS 5 +#define SEQUENCE_BITS 6 + +// GSM: +//#define SIZE_BITS 6 +//#define SEQUENCE_BITS 7 + +//-------------------------------------------------------------------------- + +SimVoiceStreamEvent::SimVoiceStreamEvent(U8 streamId, U32 seq, U8 codecId) +{ + mGuaranteeType = Unguaranteed; + mSize = VOICE_PACKET_DATA_SIZE; + mData = new U8[VOICE_PACKET_DATA_SIZE+1]; + + mCodecId = codecId; + mStreamId = streamId & STREAM_MASK; + mSequence = seq; + mClientId = 0; + mObjectId = 0; + + // NOTE: the first byte in the data is used as a lock count + // this will allow us to pass the data from object to object + // without making multiple copies of it. + if(mData) + (*mData) = 1; +} + +SimVoiceStreamEvent::SimVoiceStreamEvent(const SimVoiceStreamEvent *event) +{ + mGuaranteeType = Unguaranteed; + mData = event->mData; + mSize = event->mSize; + mCodecId = event->mCodecId; + mClientId = event->mClientId; + mStreamId = event->mStreamId; + mSequence = event->mSequence; + mObjectId = event->mObjectId; + + // NOTE: the first byte in the data is used as a lock count + // this will allow us to pass the data from object to object + // without making multiple copies of it. + if (mData) + (*mData)++; // increment lock count +} + + + +//-------------------------------------- +SimVoiceStreamEvent::~SimVoiceStreamEvent() +{ + if (mData) + { + (*mData)--; // decrement lock ount + if (*mData == 0) // if zero we are responsible for deleting the data + delete [] mData; + } +} + + +//-------------------------------------- +void SimVoiceStreamEvent::pack(NetConnection *con, BitStream *bstream) +{ + AssertFatal((1<= VOICE_PACKET_DATA_SIZE, "SimVoiceStreamEvent::pack: insuffecient bits to encode size."); + AssertFatal((1<= mSequence, "SimVoiceStreamEvent::pack: insuffecient bits to encode sequence."); + AssertFatal((1<<2 >= AUDIO_NUM_CODECS), "SimVoiceStreamEvent::pack: blah"); + + bstream->writeInt(mStreamId, 5); + bstream->writeInt(mSequence, SEQUENCE_BITS); + bstream->writeInt(mCodecId, 2); + + if(con->isServerConnection()) + { + // client side + } + else + { + // server side + bstream->write(mClientId); + if(mSequence == 0) + bstream->writeInt(mObjectId, NetConnection::GhostIdBitSize); + } + + // only the EOS packets are not VOICE_PACKET_DATA_SIZE bytes long. + if(bstream->writeFlag(mSize != VOICE_PACKET_DATA_SIZE)) + bstream->writeInt(mSize, SIZE_BITS); + + bstream->_write(mSize, mData+1); +} + +//-------------------------------------- +void SimVoiceStreamEvent::write(NetConnection *con, BitStream *bstream) +{ + pack(con, bstream); +} + +//-------------------------------------- +void SimVoiceStreamEvent::unpack(NetConnection *con, BitStream *bstream) +{ + mSize = VOICE_PACKET_DATA_SIZE; + + mStreamId = bstream->readInt(5); + mSequence = bstream->readInt(SEQUENCE_BITS); + mCodecId = bstream->readInt(2); + + if(con->isServerConnection()) + { + // client side + bstream->read(&mClientId); + if (mSequence == 0) + mObjectId = bstream->readInt(NetConnection::GhostIdBitSize); + } + else + { + // server side + mClientId = con->getId(); + if(mSequence == 0) + { + ShapeBase *base = ((GameConnection*)con)->getControlObject(); + mObjectId = base ? con->getGhostIndex(base) : 0; + } + } + + // not a full packet? + if(bstream->readFlag()) + mSize = getMin(bstream->readInt(SIZE_BITS), VOICE_PACKET_DATA_SIZE); + + // read data (skip lock byte) + bstream->_read(mSize, mData+1); +} + +//-------------------------------------- +void SimVoiceStreamEvent::process(NetConnection *con) +{ + if (con->isServerConnection()) + processClient(con); + else + processServer(con); +} + + +//-------------------------------------- +void SimVoiceStreamEvent::processClient(NetConnection *con) +{ + con; + alxReceiveVoiceStream(this); +} + +//-------------------------------------- +void SimVoiceStreamEvent::processServer(NetConnection *con) +{ + if(con->getProtocolVersion() < MIN_PROTOCOL_VERSION) + return; + + GameConnection *itr = static_cast(con->getConnectionList()); + GameConnection *gc = dynamic_cast(con); + if(!gc) + return; + + if(U32(gc->getVoiceEncodingLevel()) != mCodecId) + return; + + while (itr != NULL) + { + if(!itr->isServerConnection() && (itr->getProtocolVersion() >= MIN_PROTOCOL_VERSION)) + { + if(itr->canListen(gc) || itr->isListening(gc->getVoiceID())) + { + if (mSequence == 0) + itr->willListen(gc->getVoiceID()); + + if ( itr->isListening(gc->getVoiceID()) ) + itr->postNetEvent( new SimVoiceStreamEvent(this) ); + + if (mSize < VOICE_PACKET_DATA_SIZE) + itr->stopListening(gc->getVoiceID()); + } + } + itr = static_cast(itr->getNext()); + } +} + +IMPLEMENT_CO_NETEVENT_V1(SimVoiceStreamEvent); diff --git a/audio/audioNet.h b/audio/audioNet.h new file mode 100644 index 0000000..aaf4a59 --- /dev/null +++ b/audio/audioNet.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUDIONET_H_ +#define _AUDIONET_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _NETCONNECTION_H_ +#include "sim/netConnection.h" +#endif + +class GameConnection; + +//-------------------------------------- +struct SimVoiceStreamEvent: public NetEvent +{ +private: + void processClient(NetConnection *); + void processServer(NetConnection *); + + enum { MIN_PROTOCOL_VERSION = 34 }; + enum { STREAM_MASK = 0x1f }; // 5 bits + +public: + U8 *mData; + U8 mSize; + U32 mClientId; + U8 mStreamId; + U8 mSequence; + U8 mCodecId; + SimObjectId mObjectId; + + enum { VOICE_PACKET_DATA_SIZE = 30 }; + +// // GSM: +// enum { VOICE_PACKET_DATA_SIZE = 33 }; + + SimVoiceStreamEvent(U8 streamId=0, U32 seq=0, U8 codecId=0); + SimVoiceStreamEvent(const SimVoiceStreamEvent *event); + ~SimVoiceStreamEvent(); + void pack(NetConnection *, BitStream *bstream); + void write(NetConnection *, BitStream *bstream); + void unpack(NetConnection *, BitStream *bstream); + void process(NetConnection *); + DECLARE_CONOBJECT(SimVoiceStreamEvent); + + U8* getData() { return mData+1; } + U32 getSize() { return mSize; } + void setDataSize(U32 size) + { + mSize = size; + if (mSize < VOICE_PACKET_DATA_SIZE) // if end of stream make sure we notify + mGuaranteeType = Guaranteed; + } +}; + + + +#endif // _H_AUDIONET_ diff --git a/audio/audioThread.cc b/audio/audioThread.cc new file mode 100644 index 0000000..873cffe --- /dev/null +++ b/audio/audioThread.cc @@ -0,0 +1,218 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "audio/audioThread.h" + +// AudioResourceQueue: ----------------------------------------------------- +AudioResourceQueue::AudioResourceQueue() +{ + mHead = mTail = 0; +} + +void AudioResourceQueue::enqueue(AudioResourceEntry * entry) +{ + AssertFatal(entry, "AudioResourceQueue::enqueue: invalid entry"); + entry->mNext = 0; + + if(!mHead) + mHead = mTail = entry; + else + { + mTail->mNext = entry; + mTail = entry; + } +} + +AudioResourceEntry * AudioResourceQueue::dequeue() +{ + AudioResourceEntry * entry = mHead; + + if(mHead == mTail) + mHead = mTail = 0; + else + mHead = mHead->mNext; + + return(entry); +} + +// AudioThread: ------------------------------------------------------------ +AudioThread * gAudioThread = 0; + +AudioThread::AudioThread() : Thread(0, 0, false) +{ + mStopSemaphore = Semaphore::createSemaphore(0); + mWakeSemaphore = Semaphore::createSemaphore(); + mMutex = Mutex::createMutex(); + + // Now that the semaphores are created, start up the thread + start(); +} + +AudioThread::~AudioThread() +{ + Semaphore::destroySemaphore(mStopSemaphore); + Semaphore::destroySemaphore(mWakeSemaphore); + Mutex::destroyMutex(mMutex); +} + +void AudioThread::wake() +{ + if(!isAlive()) + return; + + Semaphore::releaseSemaphore(mWakeSemaphore); +} + +void AudioThread::stop() +{ + if(!isAlive()) + return; + + Semaphore::releaseSemaphore(mStopSemaphore); + wake(); + join(); +} + +void AudioThread::run(S32) +{ + while(1) + { + Semaphore::acquireSemaphore(mWakeSemaphore); + + if(Semaphore::acquireSemaphore(mStopSemaphore, false)) + return; + + lock(); + AudioResourceEntry * entry = mLoadingQueue.mHead; + + if(!entry) + { + unlock(); + continue; + } + + // load it in + Stream * stream = ResourceManager->openStream(entry->mResourceObj); + stream->read(entry->mResourceObj->fileSize, entry->mData); + ResourceManager->closeStream(stream); + + mLoadedQueue.enqueue(mLoadingQueue.dequeue()); + unlock(); + } +} + +//-------------------------------------------------------------------------- +void AudioThread::loadResource(ResourceObject * obj, AudioBuffer * buffer) +{ + AssertFatal(!buffer->mLoading, "AudioThread::loadResource: buffer already loading"); + AudioResourceEntry * entry = new AudioResourceEntry(); + + entry->mResourceObj = obj; + entry->mBuffer = buffer; + entry->mData = dMalloc(obj->fileSize); + dMemset(entry->mData, 0, obj->fileSize); + + buffer->mLoading = true; + + lock(); + mLoadingQueue.enqueue(entry); + unlock(); + + Semaphore::acquireSemaphore(mWakeSemaphore, false); + Semaphore::releaseSemaphore(mWakeSemaphore); +} + +void AudioThread::setBufferPlayHandle(AudioBuffer * buffer, AUDIOHANDLE handle) +{ + lock(); + + // search the loading list + AudioResourceEntry * entry = mLoadingQueue.mHead; + bool found = false; + + while(entry && !found) + { + if(entry->mBuffer == buffer) + { + entry->mPlayHandle = handle; + found = true; + } + entry = entry->mNext; + } + + // search the loaded list + if(!found) + { + entry = mLoadedQueue.mHead; + + while(entry && !found) + { + if(entry->mBuffer == buffer) + { + entry->mPlayHandle = handle; + found = true; + } + entry = entry->mNext; + } + } + + unlock(); +} + +AudioResourceEntry * AudioThread::getLoadedList() +{ + lock(); + AudioResourceEntry * list = mLoadedQueue.mHead; + mLoadedQueue.mHead = mLoadedQueue.mTail = 0; + unlock(); + + return(list); +} + +// static methods: -------------------------------------------------------- +void AudioThread::create() +{ + if(gAudioThread) + return; + + gAudioThread = new AudioThread(); +} + +void AudioThread::destroy() +{ + if(!gAudioThread) + return; + + gAudioThread->stop(); + delete gAudioThread; + gAudioThread = 0; +} + +void AudioThread::process() +{ + if(!gAudioThread) + return; + + AudioResourceEntry * entry = gAudioThread->getLoadedList(); + + // sync all the loaded buffers and play those marked + while(entry) + { + entry->mBuffer->mLoading = false; + + if(alBufferSyncData_EXT(entry->mBuffer->malBuffer, AL_FORMAT_WAVE_EXT, entry->mData, + entry->mResourceObj->fileSize, 0)) + { + if(entry->mPlayHandle != NULL_AUDIOHANDLE) + alxPlay(entry->mPlayHandle); + } + + AudioResourceEntry * next = entry->mNext; + delete entry; + entry = next; + } +} diff --git a/audio/audioThread.h b/audio/audioThread.h new file mode 100644 index 0000000..e7cc75a --- /dev/null +++ b/audio/audioThread.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUDIOTHREAD_H_ +#define _AUDIOTHREAD_H_ + +#ifndef _PLATFORMTHREAD_H_ +#include "Platform/platformThread.h" +#endif +#ifndef _PLATFORMSEMAPHORE_H_ +#include "Platform/platformSemaphore.h" +#endif +#ifndef _PLATFORMMUTEX_H_ +#include "Platform/platformMutex.h" +#endif +#ifndef _AUDIO_H_ +#include "audio/audio.h" +#endif + +struct AudioResourceEntry +{ + ResourceObject * mResourceObj; + AudioBuffer * mBuffer; + void * mData; + AUDIOHANDLE mPlayHandle; + AudioResourceEntry * mNext; + + AudioResourceEntry() + { + mResourceObj = 0; + mBuffer = 0; + mData = 0; + mPlayHandle = NULL_AUDIOHANDLE; + mNext = 0; + } +}; + +struct AudioResourceQueue +{ + AudioResourceEntry * mHead; + AudioResourceEntry * mTail; + + AudioResourceQueue(); + + void enqueue(AudioResourceEntry * entry); + AudioResourceEntry * dequeue(); +}; + +class AudioThread : public Thread +{ + private: + + void * mStopSemaphore; + void * mWakeSemaphore; + void * mMutex; + + // accessed in both threads. memory must be managed in main thread + AudioResourceQueue mLoadingQueue; + AudioResourceQueue mLoadedQueue; + + public: + + AudioThread(); + ~AudioThread(); + + void wake(); + void stop(); + + void lock() { Mutex::lockMutex(mMutex); } + void unlock() { Mutex::unlockMutex(mMutex); } + + void run(S32 arg); + + void setBufferPlayHandle(AudioBuffer * buffer, AUDIOHANDLE handle); + void loadResource(ResourceObject * resourceObj, AudioBuffer * buffer); + AudioResourceEntry * getLoadedList(); + + // static methods + static void create(); + static void destroy(); + static void process(); +}; + +extern AudioThread * gAudioThread; + +#endif diff --git a/audio/bufferQueue.cc b/audio/bufferQueue.cc new file mode 100644 index 0000000..e32b4a4 --- /dev/null +++ b/audio/bufferQueue.cc @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "audio/bufferQueue.h" + +//-------------------------------------------------------------------------- +// Class BufferQueue: +//-------------------------------------------------------------------------- +BufferQueue::BufferQueue() +{ + mQueue = NULL; + mSize = 0; + mHead = NULL; + mTail = NULL; + mIsFull= false; +} + +BufferQueue::~BufferQueue() +{ + setSize(0); +} + +//-------------------------------------------------------------------------- +void BufferQueue::setSize(U32 size) +{ + mIsFull = false; + if (size == mSize) + { + clear(); + return; + } + mSize = size; + + if (mQueue) + { + delete [] mQueue; + mQueue = NULL; + } + + if (mSize) + mQueue = new U8[mSize]; + + mTail = mQueue; + mHead = mQueue; +} + +//-------------------------------------------------------------------------- +void BufferQueue::enqueue(U32 size) +{ + AssertFatal(size <= getContiguousFree(), "BufferQueue: enqueue overflow."); + + if (size == 0) + return; + + advanceTail(size); +} + +void BufferQueue::enqueue(const U8* data, U32 size) +{ + AssertFatal(size <= getFree(), "BufferQueue: enqueue overflow."); + if (size == 0) + return; + + U32 con= getContiguousFree(); + if (size <= con) + dMemcpy(mTail, data, size); + else + { + dMemcpy(mTail, data, con); + dMemcpy(mQueue, &data[con], size-con); + } + + advanceTail(size); +} + + +//-------------------------------------------------------------------------- +U32 BufferQueue::dequeue(U32 request) +{ + if (request == 0) + return 0; + + request = getMin(request, getContiguousUsed()); + + advanceHead(request); + return request; +} + +U32 BufferQueue::dequeue(U8* data, U32 request) +{ + if (request == 0) + return 0; + + request = getMin(request, getUsed()); + U32 con=getContiguousUsed(); + + if (con >= request) + dMemcpy(data, mHead, request); + else + { + dMemcpy(data, mHead, con); + dMemcpy(data+con, mQueue, request-con); + } + + advanceHead(request); + return request; +} diff --git a/audio/bufferQueue.h b/audio/bufferQueue.h new file mode 100644 index 0000000..30b64e4 --- /dev/null +++ b/audio/bufferQueue.h @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BUFFERQUEUE_H_ +#define _BUFFERQUEUE_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +class BufferQueue +{ + private: + + U8 *mQueue; + U8 *mHead; + U8 *mTail; + U32 mSize; + bool mIsFull; + + void advanceHead(U32 n); + void advanceTail(U32 n); + + public: + + BufferQueue(); + ~BufferQueue(); + + void setSize(U32 size); + bool isFull() { return mIsFull; } + bool isEmpty() { return !mIsFull && mHead == mTail; } + void clear() { mHead = mTail = mQueue; mIsFull = false; } + + U8* getTail() { return mIsFull ? NULL : mTail; } + U8* getHead() { return mHead; } + + // inlined + U32 getFree(); + U32 getUsed(); + U32 getContiguousFree(); + U32 getContiguousUsed(); + + void enqueue(U32 size); + void enqueue(const U8* data, U32 size); + U32 dequeue(U32 size); + U32 dequeue(U8* data, U32 size); +}; + + +//-------------------------------------------------------------------------- +// BufferQueue: inlined functions +//-------------------------------------------------------------------------- +inline void BufferQueue::advanceHead(U32 n) +{ + mHead += n; // advance head + if (mHead >= (mQueue + mSize)) // wrap around the queue + mHead -= mSize; + mIsFull = false; +} + +inline void BufferQueue::advanceTail(U32 n) +{ + mTail += n; // advance tail + if (mTail >= (mQueue + mSize)) // wrap around the queue + mTail -= mSize; + + mIsFull = (mTail == mHead); +} + +inline U32 BufferQueue::getFree() +{ + if (mIsFull == false) + return (mTail < mHead) ? (mHead - mTail) : (mSize - (mTail-mHead)); + else + return 0; +} + +inline U32 BufferQueue::getUsed() +{ + if (mIsFull == false) + return (mHead <= mTail) ? (mTail - mHead) : (mSize - (mHead-mTail)); + else + return mSize; +} + +inline U32 BufferQueue::getContiguousFree() +{ + if (mIsFull == false) + return (mTail < mHead) ? (mHead - mTail) : (mSize - (mTail-mQueue)); + else + return 0; +} + +inline U32 BufferQueue::getContiguousUsed() +{ + if (mIsFull == false) + return (mHead <= mTail) ? (mTail - mHead) : (mSize - (mHead-mQueue)); + else + return mSize; +} + +#endif // _INC_BUFFERQUEUE diff --git a/collision/abstractPolyList.cc b/collision/abstractPolyList.cc new file mode 100644 index 0000000..c209a8a --- /dev/null +++ b/collision/abstractPolyList.cc @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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) +{ + Point3F pos = box.min; + F32 dx = box.max.x - box.min.x; + F32 dy = box.max.y - box.min.y; + F32 dz = box.max.z - box.min.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(0,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..a83c413 --- /dev/null +++ b/collision/abstractPolyList.h @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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/tVector.h" +#endif + +class SceneObject; + +//---------------------------------------------------------------------------- + +class AbstractPolyList +{ +protected: + // User set state + SceneObject* mCurrObject; + + MatrixF mBaseMatrix; + MatrixF mMatrix; + Point3F mScale; + + PlaneTransformer mPlaneTransformer; + + bool mInterestNormalRegistered; + Point3F mInterestNormal; + +public: + AbstractPolyList(); + virtual ~AbstractPolyList(); + + // Common functionality + void setBaseTransform(const MatrixF& mat); + + void setTransform(const MatrixF* mat, const Point3F& scale); + void getTransform(MatrixF* mat, Point3F * scale); + void setObject(SceneObject*); + void addBox(const Box3F &box); + void doConstruct(); + + // Interface functions + virtual bool isEmpty() const = 0; + virtual U32 addPoint(const Point3F& p) = 0; + virtual U32 addPlane(const PlaneF& plane) = 0; + virtual void begin(U32 material,U32 surfaceKey) = 0; + virtual void plane(U32 v1,U32 v2,U32 v3) = 0; + virtual void plane(const PlaneF& p) = 0; + virtual void plane(const U32 index) = 0; + virtual void vertex(U32 vi) = 0; + virtual void end() = 0; + virtual bool getMapping(MatrixF *, Box3F *); + + // Interest functionality + void setInterestNormal(const Point3F& /*normal*/); + void clearInterestNormal() { mInterestNormalRegistered = false; } + virtual bool isInterestedInPlane(const PlaneF& /*plane*/); + virtual bool isInterestedInPlane(const U32 index); + + protected: + 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; + mMatrix.mul(*mat); + mScale = scale; + + mPlaneTransformer.set(mMatrix, mScale); +} + +inline void AbstractPolyList::getTransform(MatrixF* mat, Point3F * scale) +{ + *mat = mMatrix; + *scale = mScale; +} + +inline void AbstractPolyList::setObject(SceneObject* obj) +{ + mCurrObject = obj; +} + + +#endif diff --git a/collision/boxConvex.cc b/collision/boxConvex.cc new file mode 100644 index 0000000..00bc473 --- /dev/null +++ b/collision/boxConvex.cc @@ -0,0 +1,201 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mMath.h" +#include "game/gameBase.h" +#include "collision/boxConvex.h" + + +//---------------------------------------------------------------------------- + +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 }, +}; + +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..7f1c2c4 --- /dev/null +++ b/collision/boxConvex.h @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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.cc b/collision/clippedPolyList.cc new file mode 100644 index 0000000..3e93a5d --- /dev/null +++ b/collision/clippedPolyList.cc @@ -0,0 +1,277 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "dgl/dgl.h" +#include "Math/mMath.h" +#include "console/console.h" +#include "Collision/clippedPolyList.h" + + +//---------------------------------------------------------------------------- + +ClippedPolyList::ClippedPolyList() +{ + 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); +} + +ClippedPolyList::~ClippedPolyList() +{ +} + + +//---------------------------------------------------------------------------- + +void ClippedPolyList::clear() +{ + // Only clears internal data + mPolyList.clear(); + mVertexList.clear(); + mIndexList.clear(); + mPolyPlaneList.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 + // v.mask = 0; + // for (U32 i = 0; i < mPlaneList.size(); i++) + // if (mPlaneList[i].distToPlane(v.point) > 0) + // v.mask |= 1 << i; + // return mVertexList.size() - 1; + + // 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(U32 material,U32 surfaceKey) +{ + mPolyList.increment(); + Poly& poly = mPolyList.last(); + poly.object = mCurrObject; + poly.material = material; + poly.vertexStart = mIndexList.size(); + poly.surfaceKey = surfaceKey; +} + + +//---------------------------------------------------------------------------- + +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 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; + 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; + } + } + } + + // 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::render() +{ + glVertexPointer(3,GL_FLOAT,sizeof(Vertex),mVertexList.address()); + glEnableClientState(GL_VERTEX_ARRAY); + glColor4f(1,0,0,0.25); + glEnable(GL_BLEND); + glDisable(GL_CULL_FACE); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + + Poly * p; + for (p = mPolyList.begin(); p < mPolyList.end(); p++) { + glDrawElements(GL_POLYGON,p->vertexCount, + GL_UNSIGNED_INT,&mIndexList[p->vertexStart]); + } + + glColor3f(0.6,0.6,0.6); + glDisable(GL_BLEND); + for (p = mPolyList.begin(); p < mPolyList.end(); p++) { + glDrawElements(GL_LINE_LOOP,p->vertexCount, + GL_UNSIGNED_INT,&mIndexList[p->vertexStart]); + } + + glDisableClientState(GL_VERTEX_ARRAY); +} diff --git a/collision/clippedPolyList.h b/collision/clippedPolyList.h new file mode 100644 index 0000000..0f9cd53 --- /dev/null +++ b/collision/clippedPolyList.h @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CLIPPEDPOLYLIST_H_ +#define _CLIPPEDPOLYLIST_H_ + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _ABSTRACTPOLYLIST_H_ +#include "Collision/abstractPolyList.h" +#endif + + +//---------------------------------------------------------------------------- + +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; + U32 material; + U32 vertexStart; + U32 vertexCount; + U32 surfaceKey; + }; + + typedef Vector PlaneList; + typedef Vector VertexList; + typedef Vector PolyList; + typedef Vector IndexList; + + // Internal data + PolyList mPolyList; + VertexList mVertexList; + IndexList mIndexList; + + PlaneList mPolyPlaneList; + + // Data set by caller + PlaneList mPlaneList; + VectorF mNormal; + + // + ClippedPolyList(); + ~ClippedPolyList(); + void clear(); + void render(); + + // Virtual methods + bool isEmpty() const; + U32 addPoint(const Point3F& p); + U32 addPlane(const PlaneF& plane); + void begin(U32 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/collision.h b/collision/collision.h new file mode 100644 index 0000000..c75a87a --- /dev/null +++ b/collision/collision.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLISION_H_ +#define _COLLISION_H_ + +#ifndef _DATACHUNKER_H_ +#include "Core/dataChunker.h" +#endif +#ifndef _MPLANE_H_ +#include "Math/mPlane.h" +#endif + +class SceneObject; + +//---------------------------------------------------------------------------- + +struct Collision +{ + SceneObject* object; + Point3F point; + VectorF normal; + U32 material; + // 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; +}; + +struct CollisionList +{ + enum { + MaxCollisions = 64 + }; + int count; + Collision collision[MaxCollisions]; + F32 t; + // MaxHeight is currently only set by the extrudedPolyList + // clipper. It represents the maximum vertex z value of + // the returned collision surfaces. + F32 maxHeight; +}; + + +//---------------------------------------------------------------------------- +// BSP Collision tree +// Solid nodes are represented by structures with NULL frontNode and +// backNoide 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; + +#endif diff --git a/collision/concretePolyList.cc b/collision/concretePolyList.cc new file mode 100644 index 0000000..110d76e --- /dev/null +++ b/collision/concretePolyList.cc @@ -0,0 +1,150 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "math/mMath.h" +#include "console/console.h" +#include "collision/concretePolyList.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(U32 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, mPolyPlaneList.last()); +} + +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 false; +} + +void ConcretePolyList::end() +{ + Poly& poly = mPolyList.last(); + poly.vertexCount = mIndexList.size() - poly.vertexStart; +} + + +void ConcretePolyList::render() +{ + glVertexPointer(3,GL_FLOAT,sizeof(Point3F),mVertexList.address()); + glEnableClientState(GL_VERTEX_ARRAY); + glColor4f(1,0,0,0.25); + glEnable(GL_BLEND); + glDisable(GL_CULL_FACE); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + + Poly * p; + for (p = mPolyList.begin(); p < mPolyList.end(); p++) { + if(p->material != 0xFFFFFFFF) + glDrawElements(GL_POLYGON,p->vertexCount, + GL_UNSIGNED_INT,&mIndexList[p->vertexStart]); + } + + glColor3f(0.6,0.6,0.6); + glDisable(GL_BLEND); + for (p = mPolyList.begin(); p < mPolyList.end(); p++) { + glDrawElements(GL_LINE_LOOP,p->vertexCount, + GL_UNSIGNED_INT,&mIndexList[p->vertexStart]); + } + + glDisableClientState(GL_VERTEX_ARRAY); +} diff --git a/collision/concretePolyList.h b/collision/concretePolyList.h new file mode 100644 index 0000000..f390d6a --- /dev/null +++ b/collision/concretePolyList.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONCRETEPOLYLIST_H_ +#define _CONCRETEPOLYLIST_H_ + +#ifndef _ABSTRACTPOLYLIST_H_ +#include "collision/abstractPolyList.h" +#endif + +class ConcretePolyList : public AbstractPolyList +{ + public: + + struct Poly { + PlaneF plane; + SceneObject* object; + U32 material; + U32 vertexStart; + U32 vertexCount; + U32 surfaceKey; + }; + + 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(U32 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.cc b/collision/convex.cc new file mode 100644 index 0000000..94f0c37 --- /dev/null +++ b/collision/convex.cc @@ -0,0 +1,936 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "Core/dataChunker.h" +#include "Collision/collision.h" +#include "sceneGraph/sceneGraph.h" +#include "Sim/sceneObject.h" +#include "terrain/terrData.h" +#include "Collision/convex.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); + + +//---------------------------------------------------------------------------- +// Feature Collision +//---------------------------------------------------------------------------- + +bool ConvexFeature::collide(ConvexFeature& cf,CollisionList* cList, F32 tol) +{ + // Our vertices vs. CF faces + const Point3F* vert = mVertexList.begin(); + const Point3F* vend = mVertexList.end(); + while (vert != vend) + { + cf.testVertex(*vert,cList,false, tol); + vert++; + } + + // CF vertices vs. our faces + vert = cf.mVertexList.begin(); + vend = cf.mVertexList.end(); + while (vert != vend) + { + U32 storeCount = cList->count; + 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->count) + { + cList->collision[cList->count - 1].material = cf.material; + cList->collision[cList->count - 1].object = cf.object; + } + vert++; + } + + // Edge vs. Edge + const Edge* edge = mEdgeList.begin(); + const Edge* eend = mEdgeList.end(); + while (edge != eend) + { + cf.testEdge(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); + F32 result = mDot(v,p - a); + return result <= 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->count >= CollisionList::MaxCollisions) + return; + + const Point3F& p0 = mVertexList[face->vertex[0]]; + const Point3F& p1 = mVertexList[face->vertex[1]]; + const Point3F& p2 = mVertexList[face->vertex[2]]; + F32 distance = mFabs(mDot(face->normal,v - p0)); + + if (distance > tol) + continue; + + 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->collision[cList->count++]; + AssertFatal(cList->count <= CollisionList::MaxCollisions, "Error, exceeded max collisions!"); + info.point = v; + info.normal = face->normal; + if (flip) + info.normal.neg(); + info.material = material; + info.object = object; + info.distance = distance; + } + } +} + +void ConvexFeature::testEdge(const Point3F& s1, const Point3F& e1, CollisionList* cList, F32 tol) +{ + + F32 tolSquared = tol*tol; + + // Test edges against edges + VectorF v1 = e1 - s1; + const Edge* edge = mEdgeList.begin(); + const Edge* end = mEdgeList.end(); + for (; edge != end; edge++) { + if (cList->count >= CollisionList::MaxCollisions) + return; + + const Point3F& s2 = mVertexList[edge->vertex[0]]; + const Point3F& e2 = mVertexList[edge->vertex[1]]; + VectorF n,v2 = e2 - s2; + + // Normal to both lines + mCross(v1,v2,&n); + if (n.lenSquared() < 1e-9) { + continue; + } + + Point3F is; + Point3F it; + F32 distance = sqrDistanceEdges(s1, e1, + s2, e2, + &is, &it); + + if (distance > tolSquared) + continue; + distance = mSqrt(distance); + + // Return a collision + Collision& info = cList->collision[cList->count++]; + AssertFatal(cList->count <= CollisionList::MaxCollisions, "Error, exceeded max collisions!"); + info.point = is; + info.normal = is - it; + if (info.normal.isZero()) + info.normal.set(0, 0, 1); + else + info.normal.normalize(); + info.distance = distance; + info.material = material; + info.object = object; + } +} + + +//---------------------------------------------------------------------------- +// 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 = -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.min.convolve(scale); + wBox.max.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())) { + 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, U32(&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) +{ + Box3F box1 = getBoundingBox(mat, scale); + box1.min -= Point3F(1, 1, 1); + box1.max += Point3F(1, 1, 1); + if (displacement) { + Point3F oldMin = box1.min; + Point3F oldMax = box1.max; + + box1.min.setMin(oldMin + *displacement); + box1.min.setMin(oldMax + *displacement); + box1.max.setMax(oldMin + *displacement); + box1.max.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 CollisionState; + 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) +{ + updateStateList(mat, scale); + F32 dist = +1E30; + CollisionState *st = 0; + + MatrixF axform = mat; + axform.scale(scale); + + for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) { + CollisionState* state = itr->mState; + if (state->mLista != itr) + state->swap(); + + MatrixF bxform = state->b->getTransform(); + bxform.scale(state->b->getScale()); + F32 dd = state->distance(axform, bxform); + if (dd < dist) { + dist = dd; + st = state; + } + } + return st; +} + + +//---------------------------------------------------------------------------- + +CollisionState* Convex::findClosestStateBounded(const MatrixF& mat, const Point3F& scale, const F32 dontCareDist) +{ + updateStateList(mat, scale); + F32 dist = +1E30; + CollisionState *st = 0; + + 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(); + + 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->distanceBounded(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) +{ + 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; + + MatrixF imat = mat; + imat.scale(scale); + imat.inverse(); + imat.mulV(-state->v,&v); + getFeatures(mat,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->count != 0); +} + +void Convex::getPolyList(AbstractPolyList*) +{ + +} + + + +// This function taken from: +// 3D Game Engine Design, David H. Eberly +// Ref page 43. Code from included cd. +// +F32 sqrDistanceEdges(const Point3F& start0, + const Point3F& end0, + const Point3F& start1, + const Point3F& end1, + Point3F* is, + Point3F* it) +{ + Point3F direction0 = end0 - start0; + Point3F direction1 = end1 - start1; + + Point3F kDiff = start0 - start1; + F32 fA00 = direction0.lenSquared(); + F32 fA01 = -mDot(direction0, direction1); + F32 fA11 = direction1.lenSquared(); + F32 fB0 = mDot(kDiff, direction0); + F32 fC = kDiff.lenSquared(); + F32 fDet = mAbs(fA00*fA11 - fA01*fA01); + F32 fB1, fS, fT, fSqrDist, fTmp; + + if ( fDet >= 0.00001 ) + { + // line segments are not parallel + fB1 = -mDot(kDiff, direction1); + fS = fA01*fB1-fA11*fB0; + fT = fA01*fB0-fA00*fB1; + + if ( fS >= 0.0 ) + { + if ( fS <= fDet ) + { + if ( fT >= 0.0 ) + { + if ( fT <= fDet ) // region 0 (interior) + { + // minimum at two interior points of 3D lines + F32 fInvDet = 1.0/fDet; + fS *= fInvDet; + fT *= fInvDet; + fSqrDist = (fS*(fA00*fS + fA01*fT + 2.0*fB0) + + fT*(fA01*fS + fA11*fT + 2.0*fB1) + fC); + } + else // region 3 (side) + { + fT = 1.0; + fTmp = fA01 + fB0; + if ( fTmp >= 0.0 ) + { + fS = 0.0; + fSqrDist = fA11 + 2.0*fB1 + fC; + } + else if ( -fTmp >= fA00 ) + { + fS = 1.0; + fSqrDist = fA00 + fA11 + fC + 2.0*(fB1 + fTmp); + } + else + { + fS = -fTmp/fA00; + fSqrDist = fTmp*fS+fA11+2.0*fB1+fC; + } + } + } + else // region 7 (side) + { + fT = 0.0; + if ( fB0 >= 0.0 ) + { + fS = 0.0; + fSqrDist = fC; + } + else if ( -fB0 >= fA00 ) + { + fS = 1.0; + fSqrDist = fA00+2.0*fB0+fC; + } + else + { + fS = -fB0/fA00; + fSqrDist = fB0*fS+fC; + } + } + } + else + { + if ( fT >= 0.0 ) + { + if ( fT <= fDet ) // region 1 (side) + { + fS = 1.0; + fTmp = fA01+fB1; + if ( fTmp >= 0.0 ) + { + fT = 0.0; + fSqrDist = fA00+2.0*fB0+fC; + } + else if ( -fTmp >= fA11 ) + { + fT = 1.0; + fSqrDist = fA00+fA11+fC+2.0*(fB0+fTmp); + } + else + { + fT = -fTmp/fA11; + fSqrDist = fTmp*fT+fA00+2.0*fB0+fC; + } + } + else // region 2 (corner) + { + fTmp = fA01+fB0; + if ( -fTmp <= fA00 ) + { + fT = 1.0; + if ( fTmp >= 0.0 ) + { + fS = 0.0; + fSqrDist = fA11+2.0*fB1+fC; + } + else + { + fS = -fTmp/fA00; + fSqrDist = fTmp*fS+fA11+2.0*fB1+fC; + } + } + else + { + fS = 1.0; + fTmp = fA01+fB1; + if ( fTmp >= 0.0 ) + { + fT = 0.0; + fSqrDist = fA00+2.0*fB0+fC; + } + else if ( -fTmp >= fA11 ) + { + fT = 1.0; + fSqrDist = fA00+fA11+fC+2.0*(fB0+fTmp); + } + else + { + fT = -fTmp/fA11; + fSqrDist = fTmp*fT+fA00+2.0*fB0+fC; + } + } + } + } + else // region 8 (corner) + { + if ( -fB0 < fA00 ) + { + fT = 0.0; + if ( fB0 >= 0.0 ) + { + fS = 0.0; + fSqrDist = fC; + } + else + { + fS = -fB0/fA00; + fSqrDist = fB0*fS+fC; + } + } + else + { + fS = 1.0; + fTmp = fA01+fB1; + if ( fTmp >= 0.0 ) + { + fT = 0.0; + fSqrDist = fA00+2.0*fB0+fC; + } + else if ( -fTmp >= fA11 ) + { + fT = 1.0; + fSqrDist = fA00+fA11+fC+2.0*(fB0+fTmp); + } + else + { + fT = -fTmp/fA11; + fSqrDist = fTmp*fT+fA00+2.0*fB0+fC; + } + } + } + } + } + else + { + if ( fT >= 0.0 ) + { + if ( fT <= fDet ) // region 5 (side) + { + fS = 0.0; + if ( fB1 >= 0.0 ) + { + fT = 0.0; + fSqrDist = fC; + } + else if ( -fB1 >= fA11 ) + { + fT = 1.0; + fSqrDist = fA11+2.0*fB1+fC; + } + else + { + fT = -fB1/fA11; + fSqrDist = fB1*fT+fC; + } + } + else // region 4 (corner) + { + fTmp = fA01+fB0; + if ( fTmp < 0.0 ) + { + fT = 1.0; + if ( -fTmp >= fA00 ) + { + fS = 1.0; + fSqrDist = fA00+fA11+fC+2.0*(fB1+fTmp); + } + else + { + fS = -fTmp/fA00; + fSqrDist = fTmp*fS+fA11+2.0*fB1+fC; + } + } + else + { + fS = 0.0; + if ( fB1 >= 0.0 ) + { + fT = 0.0; + fSqrDist = fC; + } + else if ( -fB1 >= fA11 ) + { + fT = 1.0; + fSqrDist = fA11+2.0*fB1+fC; + } + else + { + fT = -fB1/fA11; + fSqrDist = fB1*fT+fC; + } + } + } + } + else // region 6 (corner) + { + if ( fB0 < 0.0 ) + { + fT = 0.0; + if ( -fB0 >= fA00 ) + { + fS = 1.0; + fSqrDist = fA00+2.0*fB0+fC; + } + else + { + fS = -fB0/fA00; + fSqrDist = fB0*fS+fC; + } + } + else + { + fS = 0.0; + if ( fB1 >= 0.0 ) + { + fT = 0.0; + fSqrDist = fC; + } + else if ( -fB1 >= fA11 ) + { + fT = 1.0; + fSqrDist = fA11+2.0*fB1+fC; + } + else + { + fT = -fB1/fA11; + fSqrDist = fB1*fT+fC; + } + } + } + } + } + else + { + // line segments are parallel + if ( fA01 > 0.0 ) + { + // direction vectors form an obtuse angle + if ( fB0 >= 0.0 ) + { + fS = 0.0; + fT = 0.0; + fSqrDist = fC; + } + else if ( -fB0 <= fA00 ) + { + fS = -fB0/fA00; + fT = 0.0; + fSqrDist = fB0*fS+fC; + } + else + { + fB1 = -mDot(kDiff, direction1); + fS = 1.0; + fTmp = fA00+fB0; + if ( -fTmp >= fA01 ) + { + fT = 1.0; + fSqrDist = fA00+fA11+fC+2.0*(fA01+fB0+fB1); + } + else + { + fT = -fTmp/fA01; + fSqrDist = fA00+2.0*fB0+fC+fT*(fA11*fT+2.0*(fA01+fB1)); + } + } + } + else + { + // direction vectors form an acute angle + if ( -fB0 >= fA00 ) + { + fS = 1.0; + fT = 0.0; + fSqrDist = fA00+2.0*fB0+fC; + } + else if ( fB0 <= 0.0 ) + { + fS = -fB0/fA00; + fT = 0.0; + fSqrDist = fB0*fS+fC; + } + else + { + fB1 = -mDot(kDiff, direction1); + fS = 0.0; + if ( fB0 >= -fA01 ) + { + fT = 1.0; + fSqrDist = fA11+2.0*fB1+fC; + } + else + { + fT = -fB0/fA01; + fSqrDist = fC+fT*(2.0*fB1+fA11*fT); + } + } + } + } + + *is = start0 + direction0 * fS; + *it = start1 + direction1 * fT; + + return mFabs(fSqrDist); +} diff --git a/collision/convex.h b/collision/convex.h new file mode 100644 index 0000000..6235d1c --- /dev/null +++ b/collision/convex.h @@ -0,0 +1,173 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONVEX_H_ +#define _CONVEX_H_ + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _GJK_H_ +#include "Collision/gjk.h" +#endif + +struct Collision; +struct CollisionList; +class AbstractPolyList; +class SceneObject; +class Convex; + + +//---------------------------------------------------------------------------- + +class ConvexFeature +{ +public: + struct Edge { + S32 vertex[2]; + }; + struct Face { + VectorF normal; + S32 vertex[3]; + }; + + Vector mVertexList; + Vector mEdgeList; + Vector mFaceList; + S32 material; + SceneObject* object; + + ConvexFeature() : mVertexList(64), mEdgeList(128), mFaceList(64) { + 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(const Point3F& s1, const Point3F& e1, CollisionList* cList, F32 tol); +}; + + +//---------------------------------------------------------------------------- + +enum ConvexType { + TSConvexType, + BoxConvexType, + TerrainConvexType, + InteriorConvexType, + ShapeBaseConvexType, + TSStaticConvexType +}; + + +//---------------------------------------------------------------------------- + +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 { + Convex* mNext; + Convex* mPrev; + + void linkAfter(Convex* next); + void unlink(); + + U32 mTag; + static U32 sTag; + +protected: + CollisionStateList mList; + CollisionWorkingList mWorking; + CollisionWorkingList mReference; + SceneObject* mObject; + ConvexType mType; + + // +public: + Convex(); + virtual ~Convex(); + + // + void registerObject(Convex *convex); + void collectGarbage(); + void nukeList(); + + // + ConvexType getType() { return mType; } + SceneObject* getObject() { return mObject; } + + void render(); + + void addToWorkingList(Convex* ptr); + CollisionWorkingList& getWorkingList() { return mWorking; } + + CollisionState* findClosestState(const MatrixF& mat, const Point3F& scale); + CollisionState* findClosestStateBounded(const MatrixF& mat, const Point3F& scale, const F32 dontCareDist); + + CollisionStateList* getStateList() { return mList.mNext; } + + void updateStateList(const MatrixF& mat, const Point3F& scale, const Point3F* displacement = NULL); + void updateWorkingList(const Box3F& box, const U32 colMask); + + virtual const MatrixF& getTransform() const; + virtual const Point3F& getScale() const; + virtual Box3F getBoundingBox() const; + virtual Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const; + virtual Point3F support(const VectorF& v) const; + virtual void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf); + + virtual void getPolyList(AbstractPolyList* list); + + bool getCollisionInfo(const MatrixF& mat, const Point3F& scale, CollisionList* cList,F32 tol); +}; + +#endif diff --git a/collision/depthSortList.cc b/collision/depthSortList.cc new file mode 100644 index 0000000..fd5afca --- /dev/null +++ b/collision/depthSortList.cc @@ -0,0 +1,900 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "dgl/dgl.h" +#include "Math/mMath.h" +#include "console/console.h" +#include "Collision/depthSortList.h" +#include "Core/color.h" +#include "Core/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; + +// if polys are correctly sorted then writing depth values should result in no +// overlap of polys when looking down from camera...otoh, if polys are out of +// order, we should see overlap +bool DepthSortList::renderWithDepth = false; + +//---------------------------------------------------------------------------- +// following copied from shapeBase.cc because I didn't want to reference +// something from the test program in the core. This should really be a +// stand-alone function, but... + +static ColorF cubeColors[8] = { + ColorF(0, 0, 0), ColorF(1, 0, 0), ColorF(0, 1, 0), ColorF(0, 0, 1), + ColorF(1, 1, 0), ColorF(1, 0, 1), ColorF(0, 1, 1), ColorF(1, 1, 1) +}; + +static 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 U32 cubeFaces[6][4] = { + { 0, 2, 6, 4 }, { 0, 2, 3, 1 }, { 0, 1, 5, 4 }, + { 3, 2, 6, 7 }, { 7, 6, 4, 5 }, { 3, 7, 5, 1 } +}; + +void DepthSortList::wireCube(const Point3F& size, const Point3F& pos) +{ + glDisable(GL_CULL_FACE); + + for(int i = 0; i < 6; i++) { + glBegin(GL_LINE_LOOP); + for(int vert = 0; vert < 4; vert++) { + int idx = cubeFaces[i][vert]; + glColor3f(cubeColors[idx].red, cubeColors[idx].green, cubeColors[idx].blue); + glVertex3f(cubePoints[idx].x * size.x + pos.x, cubePoints[idx].y * size.y + pos.y, cubePoints[idx].z * size.z + pos.z); + } + glEnd(); + } +} + +//---------------------------------------------------------------------------- + +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->min.set(-mExtent.x, 0.0f, -mExtent.z); + box->max.set( mExtent.x, 2.0f * mExtent.y, mExtent.z); + + return true; +} + +//---------------------------------------------------------------------------- + +void DepthSortList::render() +{ + glPushMatrix(); + glPushAttrib(GL_DEPTH_BUFFER_BIT); + + MatrixF mat = mBaseMatrix; + mat.inverse(); + dglMultMatrix(&mat); + + glVertexPointer(3,GL_FLOAT,sizeof(Vertex),mVertexList.address()); + glEnableClientState(GL_VERTEX_ARRAY); + glColor4f(1,0,0,0.25); + glEnable(GL_BLEND); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + if (renderWithDepth) + glDepthMask(GL_TRUE); + else + glDepthMask(GL_FALSE); + glDisable(GL_CULL_FACE); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + + Poly * p; + S32 only = Con::getIntVariable("$only",-1); + for (S32 i=0; i=0 && only!=i) + continue; + p = &mPolyList[mPolyIndexList[i]]; + glDrawElements(GL_POLYGON,p->vertexCount, + GL_UNSIGNED_INT,&mIndexList[p->vertexStart]); + } + + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisableClientState(GL_VERTEX_ARRAY); + + // draw outline around clip zone... + wireCube(mExtent,Point3F(0,mExtent.y*0.5f,0)); + + glPopMatrix(); + glPopAttrib(); +} + +//---------------------------------------------------------------------------- + +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"); + + 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; + 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; + 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; + + // 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; + + // 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; +Vector gWorkListB; + +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 parition of the poly on the + // x-z plane s.t. 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(); + + S32 i=0; + while (sourceList->size()) + { + if (i>=mBase && !sortNext()) + // that's it, no more polys to sort + break; + AssertFatal(i<=mBase,"DepthSortList::depthPartition"); + + // use the topmost poly as the cookie cutter + Poly & cutter = mPolyList[mPolyIndexList[i]]; + S32 startVert = partitionVerts.size(); + S32 j; + for (j=0; jsize(); j++) + { + Poly & toCut = (*sourceList)[j]; + cookieCutter(cutter,toCut,*scraps,partition,partitionVerts); + } + + // project all the new verts onto the cutter's plane + AssertFatal(mFabs(cutter.plane.y)>=MIN_Y_DOT,"DepthSortList::depthPartition"); + 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); + + void wireCube(const Point3F & size, const Point3F & pos); + + 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]; } + + // + void render(); + + static bool renderWithDepth; +}; + +inline void DepthSortList::getOrderedPoly(U32 ith, Poly ** poly, PolyExtents ** polyExtent) +{ + *poly = &mPolyList[mPolyIndexList[ith]]; + *polyExtent = &mPolyExtentsList[mPolyIndexList[ith]]; +} + +#endif diff --git a/collision/earlyOutPolyList.cc b/collision/earlyOutPolyList.cc new file mode 100644 index 0000000..25d814b --- /dev/null +++ b/collision/earlyOutPolyList.cc @@ -0,0 +1,269 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#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(U32 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, mPolyPlaneList.last()); +} + +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..01b95e2 --- /dev/null +++ b/collision/earlyOutPolyList.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EARLYOUTPOLYLIST_H_ +#define _EARLYOUTPOLYLIST_H_ + +#ifndef _ABSTRACTPOLYLIST_H_ +#include "Collision/abstractPolyList.h" +#endif + +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; + U32 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(U32 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.cc b/collision/extrudedPolyList.cc new file mode 100644 index 0000000..758aa22 --- /dev/null +++ b/collision/extrudedPolyList.cc @@ -0,0 +1,497 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "math/mMath.h" +#include "console/console.h" +#include "collision/extrudedPolyList.h" +#include "collision/polyhedron.h" +#include "collision/collision.h" + + +const F32 sgFrontEpsilon = 0.01; + +//---------------------------------------------------------------------------- + +// use table for U64 shifts of the form: 1 << N +// because compiler makes it a function call if done directly... +U32 U32leftShift[33] = +{ + U32(1) << 0, + U32(1) << 1, + U32(1) << 2, + U32(1) << 3, + U32(1) << 4, + U32(1) << 5, + U32(1) << 6, + U32(1) << 7, + U32(1) << 8, + U32(1) << 9, + + U32(1) << 10, + U32(1) << 11, + U32(1) << 12, + U32(1) << 13, + U32(1) << 14, + U32(1) << 15, + U32(1) << 16, + U32(1) << 17, + U32(1) << 18, + U32(1) << 19, + + U32(1) << 20, + U32(1) << 21, + U32(1) << 22, + U32(1) << 23, + U32(1) << 24, + U32(1) << 25, + U32(1) << 26, + U32(1) << 27, + U32(1) << 28, + U32(1) << 29, + + U32(1) << 30, + U32(1) << 31, + U32(0) // one more for good measure +}; + +// Minimum distance from a face +F32 ExtrudedPolyList::FaceEpsilon = 0.01; + +// Value used to compare collision times +F32 ExtrudedPolyList::EqualEpsilon = 0.0001; + +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,0,0); + mIndexList.reserve(128); + mVertexList.reserve(64); + mPolyPlaneList.reserve(64); + mPlaneList.reserve(64); + mCollisionList = 0; +} + +ExtrudedPolyList::~ExtrudedPolyList() +{ +} + + +//---------------------------------------------------------------------------- + +bool ExtrudedPolyList::isEmpty() const +{ + return mCollisionList->count == 0; +} + + +//---------------------------------------------------------------------------- + +void ExtrudedPolyList::extrude(const Polyhedron& pt, const VectorF& vector) +{ + // Clear state + mIndexList.clear(); + mVertexList.clear(); + mPlaneList.clear(); + mPolyPlaneList.clear(); + mFaceShift = 0; + + // Determin 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.faceShift = FaceEpsilon / dot; + eface.maxDistance = dot; + eface.plane = face; + eface.planeMask = U32leftShift[mPlaneList.size()]; // U64(1) << 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 = U32leftShift[mPlaneList.size()-2]; // U64(1) << (mPlaneList.size()-2) + ef1.planeMask |= pmask; + ef2.planeMask |= pmask << 1; + } + } +} + + +//---------------------------------------------------------------------------- + +void ExtrudedPolyList::setCollisionList(CollisionList* info) +{ + mCollisionList = info; + mCollisionList->count = 0; + mCollisionList->t = 2; +} + + +//---------------------------------------------------------------------------- + +void ExtrudedPolyList::adjustCollisionTime() +{ + // Called after all the polys have been added. + // There was a reason for doing it here instead of subtracting + // the face Epsilon (faceShift) when the closest point is calculated, + // but I can't remember what it is... + if (mCollisionList->count) { + mCollisionList->t -= mFaceShift; + if (mCollisionList->t > 1) + mCollisionList->t = 1; + else + if (mCollisionList->t < 0) + mCollisionList->t = 0; + } +} + + +//---------------------------------------------------------------------------- + +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 += 2) + { + F32 dist = mPlaneList[i].distToPlane(v.point); + if (dist >= sgFrontEpsilon) + v.mask |= U32leftShift[i]; // U64(1) << i; + else + v.mask |= U32leftShift[i+1]; // U64(2) << 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(U32 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); +} + +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 + if (mCollisionList->count >= CollisionList::MaxCollisions || + mDot(mPoly.plane, mNormalVelocity) > 0) + return; + + // Test the mPoly against the planes each extruded face. + U32 cFaceCount = 0; + ExtrudedFace* cFace[30]; + bool cEdgeColl[30]; + ExtrudedFace* face = mExtrudedList.begin(); + ExtrudedFace* end = mExtrudedList.end(); + for (; face != end; face++) + { + if (face->active && (face->faceDot = -mDot(face->plane,mPoly.plane)) > 0) + { + if (testPoly(*face)) { + cFace[cFaceCount] = face; + cEdgeColl[cFaceCount++] = false; + } + } + } + if (!cFaceCount) + { + face = mExtrudedList.begin(); + end = mExtrudedList.end(); + for (; face != end; face++) + { + if (face->active && (face->faceDot = -mDot(face->plane,mPoly.plane)) <= 0) + { + if (testPoly(*face)) { + cFace[cFaceCount] = face; + cEdgeColl[cFaceCount++] = true; + } + } + } + } + if (!cFaceCount) + return; + + // Pick the best collision face + face = cFace[0]; + bool edge = cEdgeColl[0]; + for (U32 f = 1; f < cFaceCount; f++) + { + if (cFace[f]->faceDot > face->faceDot) + { + face = cFace[f]; + edge = cEdgeColl[f]; + } + } + // Add it to the collision list if it's better and/or equal + // to our current best. + if (face->time <= mCollisionList->t + EqualEpsilon) { + if (face->time < mCollisionList->t - EqualEpsilon) { + mFaceShift = face->faceShift; + mCollisionList->t = face->time; + mCollisionList->count = 0; + mCollisionList->maxHeight = face->height; + } + else { + if (face->height > mCollisionList->maxHeight) + mCollisionList->maxHeight = face->height; + if (face->faceShift > mFaceShift) + mFaceShift = face->faceShift; + } + Collision& collision = mCollisionList->collision[mCollisionList->count++]; + collision.point = face->point; + collision.faceDot = face->faceDot; + collision.face = face - mExtrudedList.begin(); + collision.object = mPoly.object; + if (edge == false) + collision.normal = mPoly.plane; + else + collision.normal = -face->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 = U32leftShift[p]; // U64(1) << p; + U32 newStart = mIndexList.size(); + + // Only test against this plane if we have something + // on both sides + if (face.planeMask & crossMask & pmask) { + 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++) { + U32 mask = U32leftShift[i]; // U64(1) << i; + if (face.planeMask & mask && + mPlaneList[i].distToPlane(iv.point) > 0) { + iv.mask = mask; + break; + } + } + } + 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; + F32 bd = 1E30; + F32 height = -1E30; + 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; + } + + // Remove temporary data + mVertexList.setSize(oVertexSize); + mIndexList.setSize(oIndexSize); + + // Add it to the collision list if it's better and/or equal + // to our current best. + if (bd < face.maxDistance + FaceEpsilon) { + face.time = bd / face.maxDistance; + face.height = height; + face.point = bp; + return true; + } + return false; +} + + +//---------------------------------------------------------------------------- + +void ExtrudedPolyList::render() +{ + if (!mCollisionList) + return; + + glBegin(GL_LINES); + glColor3f(1,1,0); + + for (U32 d = 0; d < mCollisionList->count; d++) { + Collision& face = mCollisionList->collision[d]; + Point3F ep = face.point; + ep += face.normal; + glVertex3fv(face.point); + glVertex3fv(ep); + } + + glEnd(); +} + +//-------------------------------------------------------------------------- +void ExtrudedPolyList::setVelocity(const VectorF& velocity) +{ + mVelocity = velocity; + if (velocity.isZero() == false) + { + mNormalVelocity = velocity; + mNormalVelocity.normalize(); + setInterestNormal(mNormalVelocity); + } + else + { + mNormalVelocity.set(0, 0, 0); + clearInterestNormal(); + } +} + + diff --git a/collision/extrudedPolyList.h b/collision/extrudedPolyList.h new file mode 100644 index 0000000..000e5e7 --- /dev/null +++ b/collision/extrudedPolyList.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EXTRUDEDPOLYLIST_H_ +#define _EXTRUDEDPOLYLIST_H_ + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _ABSTRACTPOLYLIST_H_ +#include "Collision/abstractPolyList.h" +#endif + +struct Polyhedron; +struct CollisionList; + +//---------------------------------------------------------------------------- + +class ExtrudedPolyList: public AbstractPolyList +{ +public: + struct Vertex { + Point3F point; + U32 mask; + }; + + struct Poly { + PlaneF plane; + SceneObject* object; + U32 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(); + void render(); + + // Virtual methods + bool isEmpty() const; + U32 addPoint(const Point3F& p); + U32 addPlane(const PlaneF& plane); + void begin(U32 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.cc b/collision/gjk.cc new file mode 100644 index 0000000..df8ef45 --- /dev/null +++ b/collision/gjk.cc @@ -0,0 +1,500 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "Core/dataChunker.h" +#include "Collision/collision.h" +#include "sceneGraph/sceneGraph.h" +#include "Sim/sceneObject.h" +#include "terrain/terrData.h" +#include "Collision/convex.h" +#include "Collision/gjk.h" + + +//---------------------------------------------------------------------------- + +static F32 rel_error = 1E-6; // relative error in the computed distance + +static F32 sTolerance = 1E-3; // Distance tolerance +static F32 sEpsilon2 = 1E-20; // Zero length vector + +S32 num_iterations = 0; +S32 num_irregularities = 0; + + +//---------------------------------------------------------------------------- + +CollisionState::CollisionState() +{ + mLista = mListb = 0; + a = b = 0; +} + +CollisionState::~CollisionState() +{ + if (mLista) + mLista->free(); + if (mListb) + mListb->free(); +} + + +//---------------------------------------------------------------------------- + +void CollisionState::swap() +{ + Convex* t = a; a = b; b = t; + CollisionStateList* l = mLista; mLista = mListb; mListb = l; + v.neg(); +} + + +//---------------------------------------------------------------------------- + +void CollisionState::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 CollisionState::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 CollisionState::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 CollisionState::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 CollisionState::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 CollisionState::nextBit() +{ + last = 0; + last_bit = 1; + while (bits & last_bit) { + ++last; + last_bit <<= 1; + } +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +void CollisionState::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 CollisionState::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 CollisionState::getCollisionInfo(const MatrixF& mat, + Collision* info) +{ + AssertFatal(false, "Not fixed scale problems"); + // 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 CollisionState::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 CollisionState::intersect(const MatrixF& a2w, + const MatrixF& b2w) +{ + num_iterations = 0; + MatrixF w2a,w2b; + + w2a = a2w; + w2b = b2w; + w2a.inverse(); + w2b.inverse(); + + 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_irregularities; + return false; + } + } + while (bits < 15 && v.lenSquared() > sEpsilon2); + return true; +} + + +//---------------------------------------------------------------------------- + +F32 CollisionState::distance(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; + 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 (mFabs(dist - mu) <= dist * rel_error) + return dist; + + ++num_iterations; + if (degenerate(w) || num_iterations > 100) { + ++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; +} + + +F32 CollisionState::distanceBounded(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 > 100) { + ++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; +} + + +//---------------------------------------------------------------------------- + +void Convex::render() +{ + for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) + if (itr->mState->mLista == itr) + itr->mState->render(); +} + +inline void renderCross(const Point3F x) +{ + glVertex3fv(x + VectorF(0,0,+0.1)); + glVertex3fv(x + VectorF(0,0,-0.1)); + glVertex3fv(x + VectorF(0,+0.1,0)); + glVertex3fv(x + VectorF(0,-0.1,0)); + glVertex3fv(x + VectorF(-0.1,0,0)); + glVertex3fv(x + VectorF(+0.1,0,0)); +} + +void CollisionState::render() +{ + glBegin(GL_LINES); + glColor3f(1,1,0); + + // Cross for each witness point + for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) { + Point3F x; + if (bits & bit) { + a->getTransform().mulP(p[i],&x); + renderCross(x); + b->getTransform().mulP(q[i],&x); + renderCross(x); + } + } + + // Cross and line for closest points + Point3F sp,ep; + if (bits) { + Point3F p1,p2; + getClosestPoints(p1,p2); + a->getTransform().mulP(p1,&sp); + b->getTransform().mulP(p2,&ep); + } + else { + a->getTransform().mulP(p[0],&sp); + b->getTransform().mulP(q[0],&ep); + } + renderCross(sp); + renderCross(ep); + glVertex3fv(sp); + glVertex3fv(ep); + + glEnd(); +} diff --git a/collision/gjk.h b/collision/gjk.h new file mode 100644 index 0000000..64e7cb3 --- /dev/null +++ b/collision/gjk.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GJK_H_ +#define _GJK_H_ + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + +class Convex; +struct CollisionStateList; +struct Collision; + +//---------------------------------------------------------------------------- + +struct CollisionState +{ + CollisionStateList* mLista; + CollisionStateList* mListb; + Convex* a; + Convex* b; + + VectorF v; // Vector between closest points + F32 dist; // v.len() + + // 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<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(U32,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) +{ +} + +void PlaneExtractorPolyList::render() +{ +} + diff --git a/collision/planeExtractor.h b/collision/planeExtractor.h new file mode 100644 index 0000000..3c3d22f --- /dev/null +++ b/collision/planeExtractor.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLANEEXTRACTOR_H_ +#define _PLANEEXTRACTOR_H_ + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _ABSTRACTPOLYLIST_H_ +#include "Collision/abstractPolyList.h" +#endif + + +//---------------------------------------------------------------------------- + +class PlaneExtractorPolyList: public AbstractPolyList +{ + void memcpy(U32* d, U32* s,U32 size); + +public: + // Internal data + typedef Vector VertexList; + VertexList mVertexList; + + Vector mPolyPlaneList; + + // Set by caller + Vector* mPlaneList; + + // + PlaneExtractorPolyList(); + ~PlaneExtractorPolyList(); + void clear(); + void render(); + + // Virtual methods + bool isEmpty() const; + U32 addPoint(const Point3F& p); + U32 addPlane(const PlaneF& plane); + void begin(U32 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.cc b/collision/polyhedron.cc new file mode 100644 index 0000000..655b80f --- /dev/null +++ b/collision/polyhedron.cc @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "dgl/dgl.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.min,&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(); + S32 nextEdge = 0; + 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++; + } +} + + +//---------------------------------------------------------------------------- + +void Polyhedron::render() +{ + glVertexPointer(3,GL_FLOAT,sizeof(Point3F),pointList.address()); + glEnableClientState(GL_VERTEX_ARRAY); + glColor3f(1, 0, 1); + + for (S32 e = 0; e < edgeList.size(); e++) + glDrawElements(GL_LINES,2,GL_UNSIGNED_INT,&edgeList[e].vertex); + + for (U32 i = 0; i < planeList.size(); i++) { + Point3F origin(0, 0, 0); + U32 num = 0; + for (U32 j = 0; j < edgeList.size(); j++) { + if (edgeList[j].face[0] == i || edgeList[j].face[1] == i) { + origin += pointList[edgeList[j].vertex[0]]; + origin += pointList[edgeList[j].vertex[1]]; + num += 2; + } + } + origin /= F32(num); + + glColor3f(1, 1, 1); + glBegin(GL_LINES); + glVertex3fv(origin); + glVertex3f(origin.x + planeList[i].x * 0.2, + origin.y + planeList[i].y * 0.2, + origin.z + planeList[i].z * 0.2); + glEnd(); + } + + glDisableClientState(GL_VERTEX_ARRAY); +} + +void Polyhedron::render(const VectorF& vec,F32 time) +{ + bool faceVisible[50]; + for (int f = 0; f < planeList.size(); f++) + faceVisible[f] = mDot(planeList[f],vec) > 0; + + VectorF tvec = vec; + tvec *= time; + for (int e = 0; e < edgeList.size(); e++) { + Polyhedron::Edge const& edge = edgeList[e]; + if (faceVisible[edge.face[0]] || faceVisible[edge.face[1]]) { + Point3F pp; + + glBegin(GL_LINE_LOOP); + glColor3f(0.5,0.5,0.5); + const Point3F& p1 = pointList[edge.vertex[0]]; + const Point3F& p2 = pointList[edge.vertex[1]]; + glVertex3fv(p1); + glVertex3fv(p2); + pp = p2 + vec; + glVertex3fv(pp); + pp = p1 + vec; + glVertex3fv(pp); + glEnd(); + + if (time <= 1.0) { + glBegin(GL_LINES); + glColor3f(0.5,0.5,0); + pp = pointList[edge.vertex[0]]; + pp += tvec; + glVertex3fv(pp); + pp = pointList[edge.vertex[1]]; + pp += tvec; + glVertex3fv(pp); + glEnd(); + } + } + } +} diff --git a/collision/polyhedron.h b/collision/polyhedron.h new file mode 100644 index 0000000..8e7c054 --- /dev/null +++ b/collision/polyhedron.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POLYHEDRON_H_ +#define _POLYHEDRON_H_ + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/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); + void render(); + void render(const VectorF& vec,F32 time); +}; + + +#endif diff --git a/collision/polytope.cc b/collision/polytope.cc new file mode 100644 index 0000000..df90b07 --- /dev/null +++ b/collision/polytope.cc @@ -0,0 +1,435 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "dgl/dgl.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.min,&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::render() +{ + glVertexPointer(3,GL_FLOAT,sizeof(Vertex),mVertexList.address()); + glEnableClientState(GL_VERTEX_ARRAY); + glColor3f(0.5, 0.5, 0.5); + + for (VolumeList::iterator itr = mVolumeList.begin(); + itr < mVolumeList.end(); itr++) { + for (S32 e = itr->edgeList; e >= 0; e = mEdgeList[e].next) + glDrawElements(GL_LINES,2,GL_UNSIGNED_INT,&mEdgeList[e].vertex); + glColor3f(1, 1, 1); + } + + glDisableClientState(GL_VERTEX_ARRAY); +} + + +//---------------------------------------------------------------------------- + +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.0E30; + int bestVertex = -1; + Poly:Volume* bestVolume; + 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..a1f6ff1 --- /dev/null +++ b/collision/polytope.h @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POLYTOPE_H_ +#define _POLYTOPE_H_ + +#ifndef _TVECTOR_H_ +#include "Core/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); + void render(); +}; + + + + +#endif diff --git a/console/ast.h b/console/ast.h new file mode 100644 index 0000000..32a31ac --- /dev/null +++ b/console/ast.h @@ -0,0 +1,439 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AST_H_ +#define _AST_H_ + +// TO-DO: Console debugger stuff to be cleaned up later +// extern void dbgDebuggerAddBreakPosition(const char *fileName, S32 lineNumber); +// extern void dbgDebuggerRemoveBreakPosition(const char *fileName, S32 lineNumber); + +class ExprEvalState; +class Namespace; +class SimObject; +class SimGroup; + +enum TypeReq { + TypeReqNone, + TypeReqUInt, + TypeReqFloat, + TypeReqString +}; + +struct StmtNode +{ + StmtNode *next; + + StmtNode(); + void append(StmtNode *next); + StmtNode *getNext() { return next; } + + StringTableEntry dbgFileName; + S32 dbgLineNumber; + + void addBreakCount(); + void addBreakLine(U32 ip); + virtual U32 precompileStmt(U32 loopCount) = 0; + virtual U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint) = 0; + virtual void setPackage(StringTableEntry packageName); +}; + +struct BreakStmtNode : StmtNode +{ + static BreakStmtNode *alloc(); + + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); +}; + +struct ContinueStmtNode : StmtNode +{ + static ContinueStmtNode *alloc(); + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); +}; + +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; + U32 compileX(U32 *codeStream, U32 ip, TypeReq type); + 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); +}; + +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); +}; + +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); +}; + +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(); +}; + +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(); +}; + +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(); +}; + +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(); +}; + +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(); +}; + +struct CommaCatExprNode : BinaryExprNode +{ + static CommaCatExprNode *alloc(ExprNode *left, ExprNode *right); + + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); +}; + +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(); +}; + +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(); +}; + +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(); +}; + +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(); +}; + +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(); +}; + +struct StrConstNode : ExprNode +{ + char *str; + F64 fVal; + U32 index; + bool tag; + + static StrConstNode *alloc(char *str, bool tag); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); +}; + +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(); +}; + +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(); +}; + +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(); +}; + +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); +}; + +struct TTagDerefNode : ExprNode +{ + ExprNode *expr; + + static TTagDerefNode *alloc(ExprNode *expr); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); +}; + +struct TTagExprNode : ExprNode +{ + StringTableEntry tag; + + static TTagExprNode *alloc(StringTableEntry tag); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); +}; + +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(); +}; + +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(); +}; + +struct SlotAssignNode : ExprNode +{ + ExprNode *objectExpr, *arrayExpr; + StringTableEntry slotName; + ExprNode *valueExpr; + + static SlotAssignNode *alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); +}; + +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(); +}; + +struct ObjectDeclNode : ExprNode +{ + ExprNode *classNameExpr; + StringTableEntry parentObject; + ExprNode *objectNameExpr; + ExprNode *argList; + SlotAssignNode *slotDecls; + ObjectDeclNode *subObjects; + bool structDecl; + U32 failOffset; + + static ObjectDeclNode *alloc(ExprNode *classNameExpr, ExprNode *objectNameExpr, ExprNode *argList, StringTableEntry parentObject, SlotAssignNode *slotDecls, ObjectDeclNode *subObjects, bool structDecl); + U32 precompile(TypeReq type); + U32 precompileSubObject(bool); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + U32 compileSubObject(U32 *codeStream, U32 ip, bool); + TypeReq getPreferredType(); +}; + +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); +}; + +extern StmtNode *statementList; +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/compiledEval.cc b/console/compiledEval.cc new file mode 100644 index 0000000..7819cc3 --- /dev/null +++ b/console/compiledEval.cc @@ -0,0 +1,1222 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" + +#include "console/ast.h" +#include "core/tAlgorithm.h" +#include "core/resManager.h" + +#include "core/findMatch.h" +#include "console/consoleInternal.h" +#include "core/fileStream.h" +#include "console/compiler.h" +#include "console/gram.h" + +#include "console/simBase.h" +#include "console/telnetDebugger.h" +#include "sim/netStringTable.h" + +enum { + MaxStackSize = 1024 +}; + +F64 floatStack[MaxStackSize]; +U32 intStack[MaxStackSize]; + +U32 FLT = 0; +U32 UINT = 0; + +static 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; +} + +extern U32 GameAddTaggedString(const char *string); + +//------------------------------------------------------------ + +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; +} + +//------------------------------------------------------------ + +struct StringStack +{ + enum { + MaxStackDepth = 1024, + MaxArgs = 20, + ReturnBufferSpace = 512 + }; + char *buffer; + U32 bufferSize; + const char *argv[MaxArgs]; + U32 frameOffsets[MaxStackDepth]; + U32 startOffsets[MaxStackDepth]; + + U32 numFrames; + U32 argc; + + U32 start; + U32 len; + U32 startStackSize; + U32 functionOffset; + + U32 argBufferSize; + char *argBuffer; + + void validateBufferSize(U32 size) + { + if(size > bufferSize) + { + bufferSize = size + 2048; + buffer = (char *) dRealloc(buffer, bufferSize); + } + } + void validateArgBufferSize(U32 size) + { + if(size > argBufferSize) + { + argBufferSize = size + 2048; + argBuffer = (char *) dRealloc(argBuffer, argBufferSize); + } + } + StringStack() + { + bufferSize = 0; + buffer = NULL; + numFrames = 0; + start = 0; + len = 0; + startStackSize = 0; + functionOffset = 0; + validateBufferSize(8092); + validateArgBufferSize(2048); + } + + void setIntValue(U32 i) + { + validateBufferSize(start + 32); + dSprintf(buffer + start, 32, "%d", i); + len = dStrlen(buffer + start); + } + + void setFloatValue(F64 v) + { + validateBufferSize(start + 32); + dSprintf(buffer + start, 32, "%g", v); + len = dStrlen(buffer + start); + } + + char *getReturnBuffer(U32 size) + { + if(size > ReturnBufferSpace) + { + validateArgBufferSize(size); + return argBuffer; + } + else + { + validateBufferSize(start + size); + return buffer + start; + } + } + + char *getArgBuffer(U32 size) + { + validateBufferSize(start + functionOffset + size); + char *ret = buffer + start + functionOffset; + functionOffset += size; + return ret; + } + void clearFunctionOffset() + { + functionOffset = 0; + } + + void setStringValue(const char *s) + { + if(!s) + { + len = 0; + buffer[start] = 0; + return; + } + len = dStrlen(s); + + validateBufferSize(start + len + 2); + dStrcpy(buffer + start, s); + } + + inline StringTableEntry getSTValue() + { + return StringTable->insert(buffer + start); + } + + inline U32 getIntValue() + { + return dAtoi(buffer + start); + } + + inline F64 getFloatValue() + { + return dAtof(buffer + start); + } + + inline const char *getStringValue() + { + return buffer + start; + } + + void advance() + { + startOffsets[startStackSize++] = start; + start += len; + len = 0; + } + + void advanceChar(char c) + { + startOffsets[startStackSize++] = start; + start += len; + buffer[start] = c; + buffer[start+1] = 0; + start += 1; + len = 0; + } + void push() + { + advanceChar(0); + } + inline void setLen(U32 newlen) + { + len = newlen; + } + void rewind() + { + start = startOffsets[--startStackSize]; + len = dStrlen(buffer + start); + } + void rewindTerminate() + { + buffer[start] = 0; + start = startOffsets[--startStackSize]; + len = dStrlen(buffer + start); + } + U32 compare() + { + U32 oldStart = start; + start = startOffsets[--startStackSize]; + + U32 ret = !dStricmp(buffer + start, buffer + oldStart); + len = 0; + buffer[start] = 0; + return ret; + } + + void pushFrame() + { + frameOffsets[numFrames++] = startStackSize; + startOffsets[startStackSize++] = start; + start += ReturnBufferSpace; + validateBufferSize(0); + } + void getArgcArgv(StringTableEntry name, U32 *argc, const char ***in_argv); +} STR; + +void StringStack::getArgcArgv(StringTableEntry name, U32 *argc, const char ***in_argv) +{ + U32 startStack = frameOffsets[--numFrames] + 1; + U32 argCount = getMin(startStackSize - startStack, (U32)MaxArgs); + *in_argv = argv; + argv[0] = name; + for(U32 i = 0; i < argCount; i++) + argv[i+1] = buffer + startOffsets[startStack + i]; + argCount++; + startStackSize = startStack - 1; + *argc = argCount; + start = startOffsets[startStackSize]; + len = 0; +} + +//------------------------------------------------------------ + +namespace Con +{ + +char *getReturnBuffer(U32 bufferSize) +{ + return STR.getReturnBuffer(bufferSize); +} + +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; +} +} + +//------------------------------------------------------------ + +inline void ExprEvalState::setCurVarName(StringTableEntry name) +{ + if(name[0] == '$') + currentVariable = globalVars.lookup(name); + else if(stack.size()) + currentVariable = stack.last()->lookup(name); +// if(!currentVariable) +// 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."); + currentVariable->setIntValue(val); +} + +inline void ExprEvalState::setFloatVariable(F64 val) +{ + AssertFatal(currentVariable != NULL, "Invalid evaluator state."); + currentVariable->setFloatValue(val); +} + +inline void ExprEvalState::setStringVariable(const char *val) +{ + AssertFatal(currentVariable != NULL, "Invalid evaluator state."); + currentVariable->setStringValue(val); +} + +//------------------------------------------------------------ + +const char *CodeBlock::exec(U32 ip, const char *functionName, Namespace *thisNamespace, U32 argc, const char **argv, bool noCalls) +{ + static char traceBuffer[1024]; + U32 i; + + incRefCount(); + F64 *curFloatTable; + char *curStringTable; + STR.clearFunctionOffset(); + StringTableEntry thisFunctionName = NULL; + 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 + gEvalState.pushFrame(thisFunctionName, thisNamespace); + if(gEvalState.traceOn) + { + traceBuffer[0] = 0; + for(i = 0; i < gEvalState.stack.size() - 1; i++) + dStrcat(traceBuffer, " "); + dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer), + "Entering %s::%s(", thisNamespace ? thisNamespace->mName : "", thisFunctionName); + for(i = 0; i < argc; i++) + { + dStrcat(traceBuffer, argv[i+1]); + if(i != argc - 1) + dStrcat(traceBuffer, ", "); + } + dStrcat(traceBuffer, ")"); + Con::printf("%s", traceBuffer); + } + 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; + } + else + { + curFloatTable = globalFloats; + curStringTable = globalStrings; + } + + StringTableEntry var, objParent; + U32 failJump; + StringTableEntry fnName; + StringTableEntry fnNamespace, fnPackage; + SimObject *currentNewObject = 0; + StringTableEntry curField; + SimObject *curObject; + SimObject *saveObject=NULL; + Namespace::Entry *nsEntry; + Namespace *ns; + + U32 callArgc; + const char **callArgv; + + static char curFieldArray[256]; + const char * val; + 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]); + Namespace::unlinkPackages(); + ns = Namespace::find(fnNamespace, fnPackage); + // if no body, set the IP to 0 + ns->addFunction(fnName, this, hasBody ? ip : 0); + Namespace::relinkPackages(); + //Con::printf("Adding function %s::%s (%d)", fnNamespace, fnName, ip); + } + ip = code[ip + 4]; + break; + + case OP_CREATE_OBJECT: + { + if(noCalls) + { + ip = failJump; + break; + } + objParent = U32toSTE(code[ip]); + bool dataBlock = code[ip + 1]; + failJump = code[ip + 2]; + + STR.getArgcArgv(NULL, &callArgc, &callArgv); + //Con::printf("Creating object %s of class %s", argv[1], objClass); + // objectName = argv[1]... + currentNewObject = NULL; + if(dataBlock) + { + SimObject *db = Sim::getDataBlockGroup()->findObject(callArgv[2]); + if(db && dStricmp(db->getClassName(), callArgv[1])) + { + Con::errorf(ConsoleLogEntry::General, "Cannot re-declare data block %s with a different class.", callArgv[2]); + ip = failJump; + break; + } + if(db) + currentNewObject = db; + } + if(!currentNewObject) + { + ConsoleObject *object = ConsoleObject::create(callArgv[1]); + if(!object) + { + Con::warnf(ConsoleLogEntry::General, "%s: Unable to instantiate non-conobject class %s.", getFileLine(ip-1), callArgv[1]); + ip = failJump; + break; + } + if(dataBlock) + { + SimDataBlock *dataBlock = dynamic_cast(object); + if(dataBlock) + { + dataBlock->assignId(); + if(*objParent) // it has a parent object + { + SimDataBlock *parent; + if(Sim::findObject(objParent, parent)) + { + // now loop through all the fields: + dataBlock->assignFieldsFrom(parent); + } + else + { + Con::warnf(ConsoleLogEntry::General, "%s: Unable to find parent object %s for %s.", getFileLine(ip-1), objParent, callArgv[1]); + } + } + } + else + { + Con::warnf(ConsoleLogEntry::General, "%s: Unable to instantiate non-datablock class %s.", getFileLine(ip-1), callArgv[1]); + delete object; + ip = failJump; + break; + } + } + currentNewObject = dynamic_cast(object); + if(!currentNewObject) + { + Con::warnf(ConsoleLogEntry::General, "%s: Unable to instantiate non-SimObject class %s.", getFileLine(ip-1), callArgv[1]); + delete object; + ip = failJump; + break; + } + if(callArgv[2][0]) + currentNewObject->assignName(callArgv[2]); + if(!currentNewObject->processArguments(callArgc-3, callArgv+3)) + { + delete currentNewObject; + currentNewObject = NULL; + ip = failJump; + break; + } + if(!dataBlock) + { + currentNewObject->setModStaticFields(true); + currentNewObject->setModDynamicFields(true); + } + } + ip += 3; + break; + } + case OP_CREATE_DATABLOCK: + break; + case OP_NAME_OBJECT: + break; + case OP_ADD_OBJECT: + { + bool root = code[ip++]; + //Con::printf("Adding object %s", currentNewObject->getName()); + if(currentNewObject->isProperlyAdded() == false && !currentNewObject->registerObject()) + { + Con::warnf(ConsoleLogEntry::General, "%s: Register object failed for object %s.", getFileLine(ip-2), currentNewObject->getName()); + delete currentNewObject; + ip = failJump; + break; + } + SimDataBlock *dataBlock = dynamic_cast(currentNewObject); + static char errorBuffer[256]; + if(dataBlock && !dataBlock->preload(true, errorBuffer)) + { + Con::errorf(ConsoleLogEntry::General, "%s: preload failed for %s: %s.", getFileLine(ip-2), + currentNewObject->getName(), errorBuffer); + dataBlock->deleteObject(); + ip = failJump; + break; + } + + U32 groupAddId = intStack[UINT]; + SimGroup *grp = NULL; + SimSet *set = NULL; + if(!root || !currentNewObject->getGroup()) + { + if(root) + { + const char *addGroupName = Con::getVariable("instantGroup"); + if(!Sim::findObject(addGroupName, grp)) + Sim::findObject(RootGroupId, grp); + } + else + { + if(!Sim::findObject(groupAddId, grp)) + Sim::findObject(groupAddId, set); + } + 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); + } + if(root) // root object + intStack[UINT] = currentNewObject->getId(); + else + intStack[++UINT] = currentNewObject->getId(); + + break; + } + case OP_END_OBJECT: + { + bool root = code[ip++]; + if(!root) + UINT--; + // set the parent group to this object's parent. + 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; + 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: + intStack[UINT-1] = intStack[UINT] % intStack[UINT-1]; + 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++; + gEvalState.setCurVarName(var); + break; + + case OP_SETCURVAR_CREATE: + var = U32toSTE(code[ip]); + ip++; + gEvalState.setCurVarNameCreate(var); + break; + + case OP_SETCURVAR_ARRAY: + var = STR.getSTValue(); + gEvalState.setCurVarName(var); + break; + + case OP_SETCURVAR_ARRAY_CREATE: + var = STR.getSTValue(); + gEvalState.setCurVarNameCreate(var); + 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: + curObject = Sim::findObject(STR.getStringValue()); + break; + + case OP_SETCUROBJECT_NEW: + curObject = currentNewObject; + break; + + case OP_SETCURFIELD: + curField = U32toSTE(code[ip]); + curFieldArray[0] = 0; + ip++; + break; + + case OP_SETCURFIELD_ARRAY: + dStrcpy(curFieldArray, STR.getStringValue()); + break; + + case OP_LOADFIELD_UINT: + if(curObject) + intStack[UINT+1] = U32(dAtoi(curObject->getDataField(curField, curFieldArray))); + else + intStack[UINT+1] = 0; + UINT++; + break; + + case OP_LOADFIELD_FLT: + if(curObject) + floatStack[FLT+1] = dAtof(curObject->getDataField(curField, curFieldArray)); + else + floatStack[FLT+1] = 0; + FLT++; + break; + + case OP_LOADFIELD_STR: + if(curObject) + val = curObject->getDataField(curField, curFieldArray); + else + val = ""; + STR.setStringValue(val); + break; + + case OP_SAVEFIELD_UINT: + STR.setIntValue(intStack[UINT]); + if(curObject) + curObject->setDataField(curField, curFieldArray, STR.getStringValue()); + break; + + case OP_SAVEFIELD_FLT: + STR.setFloatValue(floatStack[FLT]); + if(curObject) + curObject->setDataField(curField, curFieldArray, STR.getStringValue()); + break; + + case OP_SAVEFIELD_STR: + if(curObject) + curObject->setDataField(curField, curFieldArray, STR.getStringValue()); + 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: + break; + case OP_FLT_TO_UINT: + intStack[UINT+1] = (unsigned int)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] = 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_LOADIMMED_IDENT: + STR.setStringValue(U32toSTE(code[ip++])); + break; + + case OP_CALLFUNC_RESOLVE: + fnNamespace = U32toSTE(code[ip+1]); + fnName = U32toSTE(code[ip]); + 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.getArgcArgv(fnName, &callArgc, &callArgv); + break; + } + code[ip+1] = *((U32 *) &nsEntry); // fall through + code[ip-1] = OP_CALLFUNC; + case OP_CALLFUNC: + { + fnName = U32toSTE(code[ip]); + if(argv) // if this is called from inside a function, append the ip and codeptr + { + gEvalState.stack.last()->code = this; + gEvalState.stack.last()->ip = ip - 1; + } + + U32 callType = code[ip+2]; + + ip += 3; + STR.getArgcArgv(fnName, &callArgc, &callArgv); + + if(callType == FuncCallExprNode::FunctionCall) + nsEntry = *((Namespace::Entry **) &code[ip-2]); + else if(callType == FuncCallExprNode::MethodCall) + { + gEvalState.thisObject = Sim::findObject(callArgv[1]); + if(!gEvalState.thisObject) + { + gEvalState.thisObject = 0; + Con::warnf(ConsoleLogEntry::General,"%s: Unable to find object: '%s' attempting to call function '%s'", getFileLine(ip-4), callArgv[1], fnName); + break; + } + 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; + } + } + + if(!nsEntry || noCalls) + { + if(!noCalls) + { + 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(), getNamespaceList(ns) ); + } + } + STR.setStringValue(""); + break; + } + if(nsEntry->mType == Namespace::Entry::ScriptFunctionType) + { + if(nsEntry->mFunctionOffset) + nsEntry->mCode->exec(nsEntry->mFunctionOffset, fnName, nsEntry->mNamespace, callArgc, callArgv, false); + else // no body + STR.setStringValue(""); + } + else + { + 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), ns->mName, fnName); + Con::warnf(ConsoleLogEntry::Script, "%s: usage: %s", getFileLine(ip-4), nsEntry->mUsage); + } + else + { + switch(nsEntry->mType) + { + case Namespace::Entry::StringCallbackType: + { + const char *ret = nsEntry->cb.mStringCallbackFunc(gEvalState.thisObject, callArgc, callArgv); + 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); + 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); + if(code[ip] == OP_STR_TO_UINT) + { + ip++; + intStack[++UINT] = (unsigned int)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.setStringValue(""); + break; + case Namespace::Entry::BoolCallbackType: + { + bool result = nsEntry->cb.mBoolCallbackFunc(gEvalState.thisObject, callArgc, callArgv); + 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; + TelDebugger->popStackFrame(); + break; + } + case OP_PROCESS_ARGS: + 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_BREAK: + { + if(argv) // if this is called from inside a function, append the ip and codeptr + { + gEvalState.stack.last()->code = this; + gEvalState.stack.last()->ip = ip - 1; + } + else + goto breakContinue; + 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(argv) + { + if(gEvalState.traceOn) + { + traceBuffer[0] = 0; + for(U32 i = 0; i < gEvalState.stack.size() - 1; i++) + dStrcat(traceBuffer, " "); + dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer), + "Leaving %s::%s - return %s", thisNamespace ? thisNamespace->mName : "", thisFunctionName, STR.getStringValue()); + Con::printf("%s", traceBuffer); + } + gEvalState.popFrame(); + } + else + { + delete[] const_cast(globalStrings); + delete[] globalFloats; + globalStrings = NULL; + globalFloats = NULL; + } + decRefCount(); + return STR.getStringValue(); +} + +//------------------------------------------------------------ diff --git a/console/compiler.cc b/console/compiler.cc new file mode 100644 index 0000000..cd0197e --- /dev/null +++ b/console/compiler.cc @@ -0,0 +1,2609 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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/resManager.h" + +#include "Core/findMatch.h" +#include "console/consoleInternal.h" +#include "Core/fileStream.h" +#include "console/compiler.h" +#include "console/gram.h" + +#include "console/simBase.h" + +extern const char *CMDGetCurrentFile(); +extern S32 CMDGetCurrentLine(); + +DataChunker consoleAllocator; +extern void SetScanBuffer(const char *sb, const char *fn); +extern S32 CMDparse(); +extern void CMD_reset(); + +static U32 evalSTEtoU32(StringTableEntry ste, U32) +{ + return *((U32 *) &ste); +} + +static CompilerIdentTable gIdentTable; + +static U32 compileSTEtoU32(StringTableEntry ste, U32 ip) +{ + if(ste) + gIdentTable.add(ste, ip); + return 0; +} + +U32 (*STEtoU32)(StringTableEntry ste, U32 ip) = evalSTEtoU32; + + +//------------------------------------------------------------ + +CodeBlock *CodeBlock::find(StringTableEntry name) +{ + for(CodeBlock *walk = codeBlockList; walk; walk = walk->nextFile) + if(walk->name == name) + return walk; + return NULL; +} + +CodeBlock::CodeBlock() +{ + globalStrings = NULL; + functionStrings = NULL; + globalFloats = NULL; + functionFloats = NULL; + lineBreakPairs = NULL; + breakList = NULL; + breakListSize = 0; + + refCount = 0; + code = NULL; + name = NULL; +} + +CodeBlock::~CodeBlock() +{ + if(name) + removeFromCodeList(); + delete[] const_cast(globalStrings); + delete[] const_cast(functionStrings); + delete[] globalFloats; + delete[] functionFloats; + delete[] code; + delete[] breakList; +} + +static bool inFunction; +static U32 breakLineCount; +static CodeBlock *curCode = NULL; +CodeBlock *codeBlockList = NULL; + +void CodeBlock::addToCodeList() +{ + // remove any code blocks with my name + for(CodeBlock **walk = &codeBlockList; *walk;walk = &((*walk)->nextFile)) + { + if((*walk)->name == name) + { + *walk = (*walk)->nextFile; + break; + } + } + nextFile = codeBlockList; + codeBlockList = 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; + } +} + +void CodeBlock::setBreakpoint(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]] = OP_BREAK; + return; + } + } +} + +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 = &codeBlockList; *walk; walk = &((*walk)->nextFile)) + { + if(*walk == this) + { + *walk = nextFile; + // clear out all breakpoints + clearAllBreaks(); + return; + } + } +} + +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]]; + } +} + +bool CodeBlock::read(StringTableEntry fileName, Stream &st) +{ + name = fileName; + addToCodeList(); + + U32 globalSize,size,i; + st.read(&size); + if(size) + { + globalSize = size; + globalStrings = new char[size]; + st.read(size, globalStrings); + } + 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) + { + functionStrings = new char[size]; + st.read(size, functionStrings); + } + 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; + + 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 gConsoleSyntaxError; + +bool CodeBlock::compile(const char *codeFileName, StringTableEntry fileName, const char *script) +{ + gConsoleSyntaxError = false; + + consoleAllocator.freeBlocks(); + + STEtoU32 = compileSTEtoU32; + + statementList = NULL; + SetScanBuffer(script, fileName); + CMD_reset(); + CMDparse(); + if(gConsoleSyntaxError) + { + consoleAllocator.freeBlocks(); + return false; + } + + FileStream st; + if(!ResourceManager->openFileForWrite(st, ResourceManager->getModPathOf(fileName), codeFileName)) + return false; + st.write(U32(ConsoleDSOVersion)); + + currentStringTable = &gGlobalStringTable; + currentFloatTable = &gGlobalFloatTable; + gGlobalFloatTable.reset(); + gGlobalStringTable.reset(); + gFunctionFloatTable.reset(); + gFunctionStringTable.reset(); + gIdentTable.reset(); + + inFunction = false; + breakLineCount = 0; + curCode = this; + if(statementList) + codeSize = precompileBlock(statementList, 0) + 1; + else + codeSize = 1; + + lineBreakPairCount = breakLineCount; + code = new U32[codeSize + breakLineCount * 2]; + lineBreakPairs = code + codeSize; + + gGlobalStringTable.write(st); + gGlobalFloatTable.write(st); + gFunctionStringTable.write(st); + gFunctionFloatTable.write(st); + + breakLineCount = 0; + U32 lastIp; + if(statementList) + lastIp = compileBlock(statementList, code, 0, 0, 0); + else + lastIp = 0; + + if(lastIp != codeSize - 1) + Con::errorf(ConsoleLogEntry::General, "precompile size mismatch"); + + code[lastIp++] = OP_RETURN; + U32 totSize = codeSize + breakLineCount * 2; + st.write(codeSize); + st.write(lineBreakPairCount); + + 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]); + } + } + for(i = codeSize; i < totSize; i++) + st.write(code[i]); + + gIdentTable.write(st); + + consoleAllocator.freeBlocks(); + st.close(); + + return true; +} + +const char *CodeBlock::compileExec(StringTableEntry fileName, const char *string, bool noCalls) +{ + STEtoU32 = evalSTEtoU32; + consoleAllocator.freeBlocks(); + name = fileName; + if(name) + addToCodeList(); + + statementList = NULL; + SetScanBuffer(string, fileName); + CMD_reset(); + CMDparse(); + + if(!statementList) + { + delete this; + return ""; + } + + currentStringTable = &gGlobalStringTable; + currentFloatTable = &gGlobalFloatTable; + gGlobalFloatTable.reset(); + gGlobalStringTable.reset(); + gFunctionFloatTable.reset(); + gFunctionStringTable.reset(); + + inFunction = false; + breakLineCount = 0; + curCode = this; + + codeSize = precompileBlock(statementList, 0) + 1; + + lineBreakPairCount = breakLineCount; + + globalStrings = gGlobalStringTable.build(); + functionStrings = gFunctionStringTable.build(); + globalFloats = gGlobalFloatTable.build(); + functionFloats = gFunctionFloatTable.build(); + + code = new U32[codeSize + lineBreakPairCount * 2]; + lineBreakPairs = code + codeSize; + + breakLineCount = 0; + U32 lastIp = compileBlock(statementList, code, 0, 0, 0); + code[lastIp++] = OP_RETURN; + consoleAllocator.freeBlocks(); + if(lineBreakPairCount && fileName) + calcBreakList(); + + if(lastIp != codeSize) + Con::warnf(ConsoleLogEntry::General, "precompile size mismatch"); + return exec(0, fileName, NULL, 0, 0, noCalls); +} + +void CodeBlock::incRefCount() +{ + refCount++; +} + +void CodeBlock::decRefCount() +{ + refCount--; + if(!refCount) + delete this; +} + +void StmtNode::addBreakCount() +{ + if(inFunction) + breakLineCount++; +} + +void StmtNode::addBreakLine(U32 ip) +{ + if(inFunction) + { + U32 line = breakLineCount * 2; + breakLineCount++; + if(curCode->lineBreakPairs) + { + curCode->lineBreakPairs[line] = dbgLineNumber; + curCode->lineBreakPairs[line+1] = ip; + } + } +} + +//------------------------------------------------------------ + +U32 CompilerStringTable::add(const char *str, bool caseSens, bool tag) +{ + 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; + } + } + Entry *newStr = (Entry *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(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); +} + +CompilerStringTable *currentStringTable, gGlobalStringTable, gFunctionStringTable; + +//------------------------------------------------------------ + +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 *) consoleAllocator.alloc(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); +} + +CompilerFloatTable *currentFloatTable, gGlobalFloatTable, gFunctionFloatTable; + +//------------------------------------------------------------ + +static void precompileIdent(StringTableEntry ident) +{ + if(ident) + gGlobalStringTable.add(ident); +} + +void CompilerIdentTable::reset() +{ + list = NULL; +} + +void CompilerIdentTable::add(StringTableEntry ste, U32 ip) +{ + U32 index = gGlobalStringTable.add(ste, false); + Entry *newEntry = (Entry *) consoleAllocator.alloc(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); + } +} + +//------------------------------------------------------------ + +extern const char *CMDgetFileLine(int &lineNumber); + +StmtNode::StmtNode() +{ + next = NULL; + dbgFileName = CMDGetCurrentFile(); + dbgLineNumber = CMDGetCurrentLine(); +} + +void StmtNode::setPackage(StringTableEntry) +{ +} + +U32 precompileBlock(StmtNode *block, U32 loopCount) +{ + U32 sum = 0; + for(StmtNode *walk = block; walk; walk = walk->getNext()) + sum += walk->precompileStmt(loopCount); + 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; +} + +void StmtNode::append(StmtNode *next) +{ + StmtNode *walk = this; + while(walk->next) + walk = walk->next; + walk->next = next; +} + +U32 ExprNode::compileX(U32 *codeStream, U32 ip, TypeReq type) +{ + U32 size = precompile(type); + U32 start = ip; + U32 ret = compile(codeStream, ip, type); + if(ret - start != size) + { + precompile(type); + compile(codeStream, ip, type); + } + return ret; +} + +//------------------------------------------------------------ +// +// Console language node allocators +// +//------------------------------------------------------------ + +BreakStmtNode *BreakStmtNode::alloc() +{ + BreakStmtNode *ret = (BreakStmtNode *) consoleAllocator.alloc(sizeof(BreakStmtNode)); + constructInPlace(ret); + return ret; +} + +ContinueStmtNode *ContinueStmtNode::alloc() +{ + ContinueStmtNode *ret = (ContinueStmtNode *) consoleAllocator.alloc(sizeof(ContinueStmtNode)); + constructInPlace(ret); + return ret; +} + +ReturnStmtNode *ReturnStmtNode::alloc(ExprNode *expr) +{ + ReturnStmtNode *ret = (ReturnStmtNode *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(sizeof(LoopStmtNode)); + constructInPlace(ret); + ret->dbgLineNumber = lineNumber; + ret->testExpr = testExpr; + ret->initExpr = initExpr; + ret->endLoopExpr = endLoopExpr; + ret->loopBlock = loopBlock; + ret->isDoLoop = isDoLoop; + + return ret; +} + +FloatBinaryExprNode *FloatBinaryExprNode::alloc(S32 op, ExprNode *left, ExprNode *right) +{ + FloatBinaryExprNode *ret = (FloatBinaryExprNode *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(sizeof(CommaCatExprNode)); + constructInPlace(ret); + ret->left = left; + ret->right = right; + + return ret; +} + +IntUnaryExprNode *IntUnaryExprNode::alloc(S32 op, ExprNode *expr) +{ + IntUnaryExprNode *ret = (IntUnaryExprNode *) consoleAllocator.alloc(sizeof(IntUnaryExprNode)); + constructInPlace(ret); + ret->op = op; + ret->expr = expr; + return ret; +} + +FloatUnaryExprNode *FloatUnaryExprNode::alloc(S32 op, ExprNode *expr) +{ + FloatUnaryExprNode *ret = (FloatUnaryExprNode *) consoleAllocator.alloc(sizeof(FloatUnaryExprNode)); + constructInPlace(ret); + ret->op = op; + ret->expr = expr; + return ret; +} + +VarNode *VarNode::alloc(StringTableEntry varName, ExprNode *arrayIndex) +{ + VarNode *ret = (VarNode *) consoleAllocator.alloc(sizeof(VarNode)); + constructInPlace(ret); + ret->varName = varName; + ret->arrayIndex = arrayIndex; + return ret; +} + +IntNode *IntNode::alloc(S32 value) +{ + IntNode *ret = (IntNode *) consoleAllocator.alloc(sizeof(IntNode)); + constructInPlace(ret); + ret->value = value; + return ret; +} + +ConditionalExprNode *ConditionalExprNode::alloc(ExprNode *testExpr, ExprNode *trueExpr, ExprNode *falseExpr) +{ + ConditionalExprNode *ret = (ConditionalExprNode *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(sizeof(FloatNode)); + constructInPlace(ret); + ret->value = value; + return ret; +} + +StrConstNode *StrConstNode::alloc(char *str, bool tag) +{ + StrConstNode *ret = (StrConstNode *) consoleAllocator.alloc(sizeof(StrConstNode)); + constructInPlace(ret); + ret->str = (char *) consoleAllocator.alloc(dStrlen(str) + 1); + ret->tag = tag; + dStrcpy(ret->str, str); + + return ret; +} + +ConstantNode *ConstantNode::alloc(StringTableEntry value) +{ + ConstantNode *ret = (ConstantNode *) consoleAllocator.alloc(sizeof(ConstantNode)); + constructInPlace(ret); + ret->value = value; + return ret; +} + +AssignExprNode *AssignExprNode::alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr) +{ + AssignExprNode *ret = (AssignExprNode *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(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 *) consoleAllocator.alloc(sizeof(TTagSetStmtNode)); + constructInPlace(ret); + ret->tag = tag; + ret->valueExpr = valueExpr; + ret->stringExpr = stringExpr; + return ret; +} + +TTagDerefNode *TTagDerefNode::alloc(ExprNode *expr) +{ + TTagDerefNode *ret = (TTagDerefNode *) consoleAllocator.alloc(sizeof(TTagDerefNode)); + constructInPlace(ret); + ret->expr = expr; + return ret; +} + +TTagExprNode *TTagExprNode::alloc(StringTableEntry tag) +{ + TTagExprNode *ret = (TTagExprNode *) consoleAllocator.alloc(sizeof(TTagExprNode)); + constructInPlace(ret); + ret->tag = tag; + return ret; +} + +FuncCallExprNode *FuncCallExprNode::alloc(StringTableEntry funcName, StringTableEntry nameSpace, ExprNode *args, bool dot) +{ + FuncCallExprNode *ret = (FuncCallExprNode *) consoleAllocator.alloc(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; +} + +SlotAccessNode *SlotAccessNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName) +{ + SlotAccessNode *ret = (SlotAccessNode *) consoleAllocator.alloc(sizeof(SlotAccessNode)); + constructInPlace(ret); + ret->objectExpr = objectExpr; + ret->arrayExpr = arrayExpr; + ret->slotName = slotName; + return ret; +} + +SlotAssignNode *SlotAssignNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr) +{ + SlotAssignNode *ret = (SlotAssignNode *) consoleAllocator.alloc(sizeof(SlotAssignNode)); + constructInPlace(ret); + ret->objectExpr = objectExpr; + ret->arrayExpr = arrayExpr; + ret->slotName = slotName; + ret->valueExpr = valueExpr; + return ret; +} + +SlotAssignOpNode *SlotAssignOpNode::alloc(ExprNode *objectExpr, StringTableEntry slotName, ExprNode *arrayExpr, S32 op, ExprNode *valueExpr) +{ + SlotAssignOpNode *ret = (SlotAssignOpNode *) consoleAllocator.alloc(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 structDecl) +{ + ObjectDeclNode *ret = (ObjectDeclNode *) consoleAllocator.alloc(sizeof(ObjectDeclNode)); + constructInPlace(ret); + ret->classNameExpr = classNameExpr; + ret->objectNameExpr = objectNameExpr; + ret->argList = argList; + ret->slotDecls = slotDecls; + ret->subObjects = subObjects; + ret->structDecl = structDecl; + 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 *) consoleAllocator.alloc(sizeof(FunctionDeclStmtNode)); + constructInPlace(ret); + ret->fnName = fnName; + ret->args = args; + ret->stmts = stmts; + ret->nameSpace = nameSpace; + ret->package = NULL; + return ret; +} + +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; + } + } + 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; + } + } + 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; + } + } + 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(); + if(!expr) + return 1; + else + return 1 + expr->precompile(TypeReqString); +} + +U32 ReturnStmtNode::compileStmt(U32 *codeStream, U32 ip, U32, U32) +{ + addBreakLine(ip); + if(!expr) + codeStream[ip++] = OP_RETURN; + 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; + } + 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; + 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); + if(arrayIndex) + return arrayIndex->precompile(TypeReqString) + 6; + else + return 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; + } + return ip; +} + +TypeReq VarNode::getPreferredType() +{ + return TypeReqNone; // no preferred type +} + +//------------------------------------------------------------ + +U32 IntNode::precompile(TypeReq type) +{ + if(type == TypeReqNone) + return 0; + if(type == TypeReqString) + index = currentStringTable->addIntString(value); + else if(type == TypeReqFloat) + index = currentFloatTable->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; + } + return ip; +} + +TypeReq IntNode::getPreferredType() +{ + return TypeReqUInt; +} + +//------------------------------------------------------------ + +U32 FloatNode::precompile(TypeReq type) +{ + if(type == TypeReqNone) + return 0; + if(type == TypeReqString) + index = currentStringTable->addFloatString(value); + else if(type == TypeReqFloat) + index = currentFloatTable->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; + } + return ip; +} + +TypeReq FloatNode::getPreferredType() +{ + return TypeReqFloat; +} + +//------------------------------------------------------------ + +U32 StrConstNode::precompile(TypeReq type) +{ + if(type == TypeReqString) + { + index = currentStringTable->add(str, true, tag); + return 2; + } + else if(type == TypeReqNone) + return 0; + + fVal = consoleStringToNumber(str, dbgFileName, dbgLineNumber); + if(type == TypeReqFloat) + index = currentFloatTable->add(fVal); + return 2; +} + +U32 StrConstNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + 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; + } + 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 = currentFloatTable->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; + } + 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 + U32 addSize = 0; + if(type != subType) + addSize = 1; + + U32 retSize = expr->precompile(subType); + precompileIdent(varName); + if(arrayIndex) + { + if(subType == TypeReqString) + return arrayIndex->precompile(TypeReqString) + retSize + addSize + 8; + else + return arrayIndex->precompile(TypeReqString) + retSize + addSize + 6; + } + else + return retSize + addSize + 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; + } + 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) +{ + 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 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; + } + return ip; +} + +TypeReq SlotAccessNode::getPreferredType() +{ + return TypeReqNone; +} + +//------------------------------------------------------------ + +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; + 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(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 + // className 1 + // datablock? 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 + + 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); + + 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 = 10 + nameSize + argSize + slotSize + subObjSize; + return failOffset; +} + +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++] = structDecl; + 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 || structDecl; + 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 + currentStringTable = &gFunctionStringTable; + currentFloatTable = &gFunctionFloatTable; + argc = 0; + for(VarNode *walk = args; walk; walk = (VarNode *)((StmtNode*)walk)->getNext()) + argc++; + inFunction = true; + precompileIdent(fnName); + precompileIdent(nameSpace); + precompileIdent(package); + U32 subSize = precompileBlock(stmts, 0); + inFunction = false; + + currentStringTable = &gGlobalStringTable; + currentFloatTable = &gGlobalFloatTable; + + 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++; + } + inFunction = true; + ip = compileBlock(stmts, codeStream, ip, 0, 0); + inFunction = false; + codeStream[ip++] = OP_RETURN; + return ip; +} + + diff --git a/console/compiler.h b/console/compiler.h new file mode 100644 index 0000000..550639f --- /dev/null +++ b/console/compiler.h @@ -0,0 +1,246 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +enum CompiledInstructions +{ + OP_FUNC_DECL, + OP_CREATE_OBJECT, + OP_CREATE_DATABLOCK, + + OP_NAME_OBJECT, + OP_ADD_OBJECT, + OP_END_OBJECT, + OP_JMPIFFNOT, + OP_JMPIFNOT, + OP_JMPIFF, + OP_JMPIF, + OP_JMPIFNOT_NP, + OP_JMPIF_NP, + OP_JMP, + OP_RETURN, + 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_SETCURFIELD, + OP_SETCURFIELD_ARRAY, + + 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_LOADIMMED_IDENT, + + OP_CALLFUNC_RESOLVE, + OP_CALLFUNC, + OP_PROCESS_ARGS, + + 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_BREAK, + + OP_INVALID +}; + + +// DON'T CHANGE THESE LINES! +// DON'T CHANGE THESE LINES! +// The script updateConsoleVersion.pl in //Dev/environment/usr/bin/ depends on them +// being exactly as follows +// +enum { + ConsoleDSOVersion = 33 +}; +// +// DON'T CHANGE THESE LINES! +// DON'T CHANGE THESE LINES! +// DON'T CHANGE THESE LINES! + +class Stream; + +class CodeBlock +{ +public: + StringTableEntry name; + + char *globalStrings; + char *functionStrings; + + F64 *globalFloats; + F64 *functionFloats; + + U32 codeSize; + U32 *code; + + U32 refCount; + U32 lineBreakPairCount; + U32 *lineBreakPairs; + U32 breakListSize; + U32 *breakList; + CodeBlock *nextFile; + + static CodeBlock *find(StringTableEntry); + CodeBlock(); + ~CodeBlock(); + + void addToCodeList(); + void removeFromCodeList(); + void calcBreakList(); + void clearAllBreaks(); + void setAllBreaks(); + void clearBreakpoint(U32 lineNumber); + void setBreakpoint(U32 lineNumber); + void findBreakLine(U32 ip, U32 &line, U32 &instruction); + const char *getFileLine(U32 ip); + + bool read(StringTableEntry fileName, Stream &st); + bool compile(const char *dsoName, StringTableEntry fileName, const char *script); + + void incRefCount(); + void decRefCount(); + const char *compileExec(StringTableEntry fileName, const char *script, bool noCalls); + const char *exec(U32 offset, const char *fnName, Namespace *ns, U32 argc, const char **argv, bool noCalls); +}; +extern CodeBlock *codeBlockList; + +extern F64 consoleStringToNumber(const char *str, StringTableEntry file = 0, U32 line = 0); +extern U32 precompileBlock(StmtNode *block, U32 loopCount); +extern 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); +}; + +extern CompilerStringTable *currentStringTable; +extern CompilerStringTable gGlobalStringTable; +extern CompilerStringTable gFunctionStringTable; + +//------------------------------------------------------------ + +struct CompilerFloatTable +{ + struct Entry + { + F64 val; + Entry *next; + }; + U32 count; + Entry *list; + + U32 add(F64 value); + void reset(); + F64 *build(); + void write(Stream &st); +}; + +extern CompilerFloatTable *currentFloatTable, gGlobalFloatTable, gFunctionFloatTable; + +extern U32 (*STEtoU32)(StringTableEntry ste, U32 ip); + +inline StringTableEntry U32toSTE(U32 u) +{ + return *((StringTableEntry *) &u); +} + diff --git a/console/console.cc b/console/console.cc new file mode 100644 index 0000000..4e766fe --- /dev/null +++ b/console/console.cc @@ -0,0 +1,688 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/consoleObject.h" +#include "Core/fileStream.h" +#include "Core/resManager.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 + +ExprEvalState gEvalState; +StmtNode *statementList; +ConsoleConstructor *ConsoleConstructor::first = NULL; + +static char scratchBuffer[1024]; +// TO-DO: Console debugger stuff to be cleaned up later +static S32 dbgGetCurrentFrame(void) +{ + return gEvalState.stack.size() - 1; +} + +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) +{ + mina = minArgs; + maxa = maxArgs; + funcName = fName; + usage = usg; + className = cName; + sc = 0; fc = 0; vc = 0; bc = 0; ic = 0; + next = first; + first = this; +} + +void ConsoleConstructor::setup() +{ + for(ConsoleConstructor *walk = first; walk; walk = walk->next) + { + if(walk->sc) + Con::addCommand(walk->className, walk->funcName, walk->sc, walk->usage, walk->mina, walk->maxa); + else if(walk->ic) + Con::addCommand(walk->className, walk->funcName, walk->ic, walk->usage, walk->mina, walk->maxa); + else if(walk->fc) + Con::addCommand(walk->className, walk->funcName, walk->fc, walk->usage, walk->mina, walk->maxa); + else if(walk->vc) + Con::addCommand(walk->className, walk->funcName, walk->vc, walk->usage, walk->mina, walk->maxa); + else if(walk->bc) + Con::addCommand(walk->className, walk->funcName, walk->bc, walk->usage, walk->mina, walk->maxa); + + } +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, StringCallback sfunc, const char *usage, S32 minArgs, S32 maxArgs) +{ + init(className, funcName, usage, minArgs, maxArgs); + sc = sfunc; +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, IntCallback ifunc, const char *usage, S32 minArgs, S32 maxArgs) +{ + init(className, funcName, usage, minArgs, maxArgs); + ic = ifunc; +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, FloatCallback ffunc, const char *usage, S32 minArgs, S32 maxArgs) +{ + init(className, funcName, usage, minArgs, maxArgs); + fc = ffunc; +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, VoidCallback vfunc, const char *usage, S32 minArgs, S32 maxArgs) +{ + init(className, funcName, usage, minArgs, maxArgs); + vc = vfunc; +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, BoolCallback bfunc, const char *usage, S32 minArgs, S32 maxArgs) +{ + init(className, funcName, usage, minArgs, maxArgs); + bc = bfunc; +} + +namespace Con +{ + +static Vector gConsumers(__FILE__, __LINE__); +static DataChunker consoleLogChunker; +static Vector consoleLog(__FILE__, __LINE__); +static bool logBufferEnabled=true; +static S32 printLevel = 10; +static FileStream consoleLogFile; +static const char *defLogFileName = "console.log"; +static S32 consoleLogMode = 2; // default to no logging +static bool active = false; +static bool newLogFile; +static const char *logFileName; + +static void cClearScreen(SimObject *, S32 , const char **) +{ + consoleLogChunker.freeBlocks(); + consoleLog.setSize(0); +}; + +static const char* cGetClipboard(SimObject *, S32 , const char **) +{ + return Platform::getClipboard(); +}; + +static bool cSetClipboard(SimObject *, S32 , const char **argv) +{ + return Platform::setClipboard(argv[1]); +}; + +void init() +{ + AssertFatal(active == false, "Con::init should only be called once."); + + active = true; + logFileName = NULL; + newLogFile = true; + + Namespace::init(); + // Commands + addCommand("getClipboard", cGetClipboard, "getClipboard()", 1, 1); + addCommand("cls", cClearScreen, "cls()", 1, 1); + addCommand("getClipboard", cGetClipboard, "getClipboard()", 1, 1); + addCommand("setClipboard", cSetClipboard, "setClipboard(text)", 2, 2); + + ConsoleConstructor::setup(); + + // Variables + setVariable("Con::prompt", "% "); + addVariable("Con::logBufferEnabled", TypeBool, &logBufferEnabled); + addVariable("Con::printLevel", TypeS32, &printLevel); + + AbstractClassRep::initialize(); +} + +//-------------------------------------- + +void shutdown() +{ + AssertFatal(active == true, "Con::shutdown should only be called once."); + active = false; + + consoleLogFile.close(); + Namespace::shutdown(); +} + +bool isActive() +{ + return active; +} + +//-------------------------------------- + +void getLog(ConsoleLogEntry *&log, U32 &size) +{ + log = &consoleLog[0]; + size = consoleLog.size(); +} + +const char *tabComplete(const char *prevText, S32 baseLen, bool fForward) +{ + if (!prevText[0]) + return ""; + else if(prevText[0] == '$') + return gEvalState.globalVars.tabComplete(prevText, baseLen, fForward); + else + return Namespace::global()->tabComplete(prevText, baseLen, fForward); +} + +//------------------------------------------------------------------------------ +static void log(const char *string) +{ + if(!consoleLogMode) + return; + + if(consoleLogMode == 1) + consoleLogFile.open(defLogFileName, FileStream::ReadWrite); + + if(consoleLogFile.getStatus() == Stream::Ok) + { + consoleLogFile.setPosition(consoleLogFile.getStreamSize()); + if (newLogFile) + { + Platform::LocalTime lt; + Platform::getLocalTime(lt); + + char buffer[128]; + dSprintf(buffer, sizeof(buffer), "-------------------------- %d/%d/%d -- %02d:%02d:%02d -----\r\n", + lt.month, + lt.monthday, + lt.year, + lt.hour, + lt.min, + lt.sec); + consoleLogFile.write(dStrlen(buffer), buffer); + newLogFile = false; + } + consoleLogFile.write(dStrlen(string), string); + consoleLogFile.write(2, "\r\n"); + } + if(consoleLogMode == 1) + consoleLogFile.close(); +} + +//------------------------------------------------------------------------------ + +static void _printf(ConsoleLogEntry::Level level, ConsoleLogEntry::Type type, const char* fmt, va_list argptr) +{ + char buffer[1024]; + U32 offset = 0; + if(gEvalState.traceOn && gEvalState.stack.size()) + { + offset = (gEvalState.stack.size() - 1) * 2; + for(U32 i = 0; i < offset; i++) + buffer[i] = ' '; + } + dVsprintf(buffer + offset, sizeof(buffer) - offset, fmt, argptr); + + for(U32 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) + { + ConsoleLogEntry entry; + entry.mLevel = level; + entry.mType = type; + entry.mString = (const char *)consoleLogChunker.alloc(dStrlen(pos) + 1); + dStrcpy(const_cast(entry.mString), pos); + consoleLog.push_back(entry); + } + if(!eofPos) + break; + pos = eofPos + 1; + } + } +} + +//------------------------------------------------------------------------------ +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) +{ + 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), "%f", value); + setVariable(varName, scratchBuffer); +} + +//--------------------------------------------------------------------------- +void addConsumer(ConsumerCallback consumer) +{ + gConsumers.push_back(consumer); +} + +void removeConsumer(ConsumerCallback) +{ +} + +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, "CONSOLE: 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) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +void addCommand(const char *nsName, const char *name,VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +void addCommand(const char *nsName, const char *name,IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +void addCommand(const char *nsName, const char *name,FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +void addCommand(const char *nsName, const char *name,BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +void addCommand(const char *name,StringCallback cb,const char *usage, S32 minArgs, S32 maxArgs) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +void addCommand(const char *name,VoidCallback cb,const char *usage, S32 minArgs, S32 maxArgs) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +void addCommand(const char *name,IntCallback cb,const char *usage, S32 minArgs, S32 maxArgs) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +void addCommand(const char *name,FloatCallback cb,const char *usage, S32 minArgs, S32 maxArgs) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +void addCommand(const char *name,BoolCallback cb,const char *usage, S32 minArgs, S32 maxArgs) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs); +} + +const char *evaluate(const char* string, bool echo, const char *fileName) +{ + if (echo) + printf("%s%s", getVariable( "$Con::Prompt" ), string); + + if(fileName) + fileName = StringTable->insert(fileName); + + CodeBlock *newCodeBlock = new CodeBlock(); + return newCodeBlock->compileExec(fileName, string, false); +} + +//------------------------------------------------------------------------------ +const char *evaluatef(const char* string, ...) +{ + char buffer[512]; + va_list args; + va_start(args, string); + dVsprintf(buffer, sizeof(buffer), string, args); + CodeBlock *newCodeBlock = new CodeBlock(); + return newCodeBlock->compileExec(NULL, buffer, false); +} + +const char *execute(S32 argc, const char *argv[]) +{ + Namespace::Entry *ent; + StringTableEntry funcName = StringTable->insert(argv[0]); + ent = Namespace::global()->lookup(funcName); + + if(!ent) + { + warnf(ConsoleLogEntry::Script, "%s: Unknown command.", argv[0]); + return ""; + } + return ent->execute(argc, argv, &gEvalState); +} + +//------------------------------------------------------------------------------ +const char *execute(SimObject *object, S32 argc, const char *argv[]) +{ + static char idBuf[12]; + if(argc < 2) + return ""; + 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) + { + //warnf(ConsoleLogEntry::Script, "%s: undefined for object '%s' - id %d", funcName, object->getName(), object->getId()); + return ""; + } + else + { + SimObject *save = gEvalState.thisObject; + gEvalState.thisObject = object; + const char *ret = ent->execute(argc, argv, &gEvalState); + gEvalState.thisObject = save; + return ret; + } + } + warnf(ConsoleLogEntry::Script, "Con::execute - %d has no namespace: %s", object->getId(), argv[0]); + return ""; +} + +const char *executef(SimObject *object, S32 argc, ...) +{ + static char idBuf[12]; + const char *argv[128]; + + va_list args; + va_start(args, argc); + for(S32 i = 0; i < argc; i++) + argv[i+1] = va_arg(args, const char *); + va_end(args); + argv[0] = argv[1]; + argc++; + + return execute(object, argc, argv); +} + +//------------------------------------------------------------------------------ +const char *executef(S32 argc, ...) +{ + const char *argv[128]; + + va_list args; + va_start(args, argc); + for(S32 i = 0; i < argc; i++) + argv[i] = va_arg(args, const char *); + va_end(args); + return execute(argc, argv); +} + +//------------------------------------------------------------------------------ +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 != consoleLogMode) + { + if(newMode && !consoleLogMode) + newLogFile = true; + if(consoleLogMode == 2) + consoleLogFile.close(); + else if(newMode == 2) + consoleLogFile.open(defLogFileName, FileStream::Write); + consoleLogMode = newMode; + } +} + +Namespace *lookupNamespace(const char *ns) +{ + if(!ns) + return Namespace::global(); + return Namespace::find(StringTable->insert(ns)); +} + +void linkNamespaces(const char *parent, const char *child) +{ + Namespace *pns = lookupNamespace(parent); + Namespace *cns = lookupNamespace(child); + if(pns && cns) + cns->classLinkTo(pns); +} + +void classLinkNamespaces(Namespace *parent, Namespace *child) +{ + if(parent && child) + child->classLinkTo(parent); +} + +enum { + MaxDataTypes = 256 +}; + +static S32 typeSizes[MaxDataTypes]; +static GetDataFunction fnGetData[MaxDataTypes]; +static SetDataFunction fnSetData[MaxDataTypes]; + +void registerType(S32 type, S32 size, GetDataFunction gdf, SetDataFunction sdf) +{ + fnGetData[type] = gdf; + fnSetData[type] = sdf; + typeSizes[type] = size; +} + +void setData(S32 type, void *dptr, S32 index, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag) +{ + SetDataFunction fn = fnSetData[type]; + AssertFatal(fn != NULL, "Error, mull setData fn"); + fn((void *) (U32(dptr) + index * typeSizes[type]),argc, argv, tbl, flag); +} + +const char *getData(S32 type, void *dptr, S32 index, EnumTable *tbl, BitSet32 flag) +{ + GetDataFunction fn = fnGetData[type]; + AssertFatal(fn != NULL, "Error, mull getData fn"); + return fn((void *) (U32(dptr) + index * typeSizes[type]), tbl, flag); +} + +} // end of Console namespace diff --git a/console/console.h b/console/console.h new file mode 100644 index 0000000..06da8d6 --- /dev/null +++ b/console/console.h @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONSOLE_H_ +#define _CONSOLE_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _BITSET_H_ +#include "Core/bitSet.h" +#endif + +class SimObject; +struct EnumTable; +class Namespace; + +enum +{ + StringTagPrefixByte = 0x01 +}; + +struct ConsoleLogEntry +{ + enum Level + { + Normal = 0, + Warning, + Error, + NUM_CLASS + } mLevel; + enum Type + { + General = 0, + Assert, + Script, + GUI, + Network, + NUM_TYPE + } mType; + const char *mString; +}; + +struct EnumTable +{ + S32 size; + + struct Enums + { + S32 index; + const char *label; + }; + + Enums *table; + EnumTable(S32 sSize, Enums *sTable) + { size = sSize; table = sTable; } +}; + +//--------------------------------------------------------------------------- +typedef const char *StringTableEntry; +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[]); +typedef bool (*BoolCallback)(SimObject *obj, S32 argc, const char *argv[]); +typedef void (*ConsumerCallback)(ConsoleLogEntry::Level level, const char *consoleLine); + +typedef const char* (*GetDataFunction)(void *dptr, EnumTable *tbl, BitSet32 flag); +typedef void (*SetDataFunction)(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); + + +namespace Con +{ + enum { + MaxLineLength = 512 + }; + + void init(); + void shutdown(); + bool isActive(); + + void addConsumer(ConsumerCallback cb); + void removeConsumer(ConsumerCallback cb); + + void setVariable(const char *name, const char *value); + bool addVariable(const char *name, S32, void *); + bool removeVariable(const char *name); + const char* getVariable(const char* name); + const char* getLocalVariable(const char* name); + void setLocalVariable(const char *name, const char *value); + + void addCommand(const char *name, StringCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(const char *name, IntCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(const char *name, FloatCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(const char *name, VoidCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(const char *name, BoolCallback, const char *usage, S32 minArgs, S32 maxArgs); + + void addCommand(const char *nameSpace, const char *name,StringCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(const char *nameSpace, const char *name,IntCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(const char *nameSpace, const char *name,FloatCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(const char *nameSpace, const char *name,VoidCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(const char *nameSpace, const char *name,BoolCallback, const char *usage, S32 minArgs, S32 maxArgs); + + bool removeCommand(const char *name); + void printf(const char *_format, ...); + void warnf(ConsoleLogEntry::Type type, const char *_format, ...); + void errorf(ConsoleLogEntry::Type type, const char *_format, ...); + void warnf(const char *_format, ...); + void errorf(const char *_format, ...); + + const char *execute(S32 argc, const char* argv[]); + const char *executef(S32 argc, ...); // first param is funcName, remaining params are args + + // first param is func name, second param MUST be empty (gets filled with object ID) + // also, MUST have at least those two params + const char *execute(SimObject *, S32 argc, const char *argv[]); + + const char *executef(SimObject *, S32 argc, ...); // first param is funcName, remaining params are args + + const char *evaluate(const char* string, bool echo = false, const char *fileName = NULL); + const char *evaluatef(const char* string, ...); + + bool isFunction(const char *fn); + + void setBoolVariable(const char* name,bool var); + bool getBoolVariable(const char* name,bool def = false); + void setIntVariable(const char* name,S32 var); + S32 getIntVariable(const char* name,S32 def = 0); + void setFloatVariable(const char* name,F32 var); + F32 getFloatVariable(const char* name,F32 def = .0f); + + // console function implementation helpers + char *getReturnBuffer(U32 bufferSize); + + char *getArgBuffer(U32 bufferSize); + char *getFloatArg(F64 arg); + char *getIntArg(S32 arg); + + + const char *tabComplete(const char *prevText, S32 baseLen, bool); + void exportVariables(const char *varString, Vector &varName, Vector &value); + + Namespace *lookupNamespace(const char *nsName); + void linkNamespaces(const char *parentName, const char *childName); + + // this should only be called from consoleObject.h + void classLinkNamespaces(Namespace *parent, Namespace *child); + + void getLog(ConsoleLogEntry * &log, U32 &size); + // dynamic data management functions: + void setLogMode(S32 mode); + + void registerType(S32 type, S32 size, GetDataFunction gdf, SetDataFunction sdf); + void setData(S32 type, void *dptr, S32 index, S32 argc, const char **argv, EnumTable *tbl = NULL, BitSet32 flag = 0); + const char *getData(S32 type, void *dptr, S32 index, EnumTable *tbl = NULL, BitSet32 flag = 0); +} + +extern void expandEscape(char *dest, const char *src); +extern bool collapseEscape(char *buf); +extern S32 HashPointer(StringTableEntry ptr); + +struct ConsoleConstructor +{ + StringCallback sc; + IntCallback ic; + FloatCallback fc; + VoidCallback vc; + BoolCallback bc; + S32 mina, maxa; + const char *usage; + const char *funcName; + const char *className; + ConsoleConstructor *next; + static ConsoleConstructor *first; + + void init(const char *cName, const char *fName, const char *usg, S32 minArgs, S32 maxArgs); + static void setup(); + ConsoleConstructor(const char *className, const char *funcName, StringCallback sfunc, const char *usage, S32 minArgs, S32 maxArgs); + ConsoleConstructor(const char *className, const char *funcName, IntCallback ifunc, const char *usage, S32 minArgs, S32 maxArgs); + ConsoleConstructor(const char *className, const char *funcName, FloatCallback ffunc, const char *usage, S32 minArgs, S32 maxArgs); + ConsoleConstructor(const char *className, const char *funcName, VoidCallback vfunc, const char *usage, S32 minArgs, S32 maxArgs); + ConsoleConstructor(const char *className, const char *funcName, BoolCallback bfunc, const char *usage, S32 minArgs, S32 maxArgs); +}; + +#define ConsoleFunction(name,returnType,minArgs,maxArgs,usage) \ + static returnType c##name(SimObject *, S32, const char **argv); \ + static ConsoleConstructor g##name##obj(NULL,#name,c##name,usage,minArgs,maxArgs);\ + static returnType c##name(SimObject *, S32 argc, const char **argv) + +#define ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage) \ + static returnType c##className##name(SimObject *, S32, const char **argv); \ + static ConsoleConstructor className##name##obj(#className,#name,c##className##name,usage,minArgs,maxArgs);\ + static returnType c##className##name(SimObject *object, S32 argc, const char **argv) + +#endif diff --git a/console/consoleDoc.cc b/console/consoleDoc.cc new file mode 100644 index 0000000..1339d10 --- /dev/null +++ b/console/consoleDoc.cc @@ -0,0 +1,136 @@ + +#include "platform/platform.h" +#include "console/console.h" + +#include "console/ast.h" +#include "core/tAlgorithm.h" +#include "core/resManager.h" + +#include "core/findMatch.h" +#include "console/consoleInternal.h" +#include "console/consoleObject.h" +#include "core/fileStream.h" +#include "console/compiler.h" + +ConsoleFunction(dumpConsoleClasses, void, 1, 1, "()dumps all declared console classes to the console in C++ syntax for documenting with dOxygen or another auto documentation tool") +{ + Namespace::dumpClasses(); +} + +const char *typeNames[] = { + "Script", + "string", + "int", + "float", + "void", + "bool", +}; + +void Namespace::dumpClasses() +{ + Vector vec; + // the program + trashCache(); + + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + walk->mHashSequence = 0; + + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + { + Vector stack; + Namespace *parentWalk = walk; + while(parentWalk) + { + if(parentWalk->mHashSequence != 0) + break; + if(parentWalk->mPackage == 0) + { + parentWalk->mHashSequence = 1; + stack.push_back(parentWalk); + } + parentWalk = parentWalk->mParent; + } + while(stack.size()) + { + vec.push_back(stack[stack.size() - 1]); + stack.pop_back(); + } + } + 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; + const char *virt = "virtual "; + if(vec[i]->mEntryList == NULL && vec[i]->mClassRep == NULL) + continue; + + if(!className) + { + Con::printf("namespace Global {"); + virt = ""; + } + else if(!superClassName) + Con::printf("class %s { public:", className); + else + Con::printf("class %s : public %s { public:", className, superClassName); + + + for(Entry *ewalk = vec[i]->mEntryList; ewalk; ewalk = ewalk->mNext) + { + char buffer[1024]; + if(ewalk->mType > Entry::ScriptFunctionType) + { + if(ewalk->mUsage[0] != '(') + Con::printf(" %s %s %s() {} // %s", virt, typeNames[ewalk->mType], ewalk->mFunctionName, ewalk->mUsage); + else + { + const char *use = ewalk->mUsage; + const char *end = dStrchr(use, ')'); + if(!end) + end = use + 1; + use++; + U32 len = end - use; + dStrncpy(buffer, use, len); + buffer[len] = 0; + Con::printf(" %s %s %s(%s) {} // %s", virt, typeNames[ewalk->mType], ewalk->mFunctionName, buffer, end + 1); + } + } + else if(ewalk->mFunctionOffset) + { + ewalk->mCode->getFunctionArgs(buffer, ewalk->mFunctionOffset); + Con::printf(" %s void %s(%s) {} // declared script function", virt, ewalk->mFunctionName, buffer); + } + } + AbstractClassRep *rep = vec[i]->mClassRep; + AbstractClassRep::FieldList emptyList; + AbstractClassRep::FieldList *parentList = &emptyList; + AbstractClassRep::FieldList *fieldList = &emptyList; + + if(rep) + { + AbstractClassRep *parentRep = vec[i]->mParent ? vec[i]->mParent->mClassRep : NULL; + if(parentRep) + parentList = &(parentRep->mFieldList); + fieldList = &(rep->mFieldList); + for(U32 j = 0; j < fieldList->size(); j++) + { + if((*fieldList)[j].type == AbstractClassRep::DepricatedFieldType) + continue; + + bool found = false; + for(U32 k = 0; k < parentList->size(); k++) + { + if((*parentList)[k].pFieldname == (*fieldList)[j].pFieldname) + { + found = true; + break; + } + } + if(!found) + Con::printf(" %s %s;", Con::getTypeName((*fieldList)[j].type), (*fieldList)[j].pFieldname); + } + } + Con::printf("};"); + } +} \ No newline at end of file diff --git a/console/consoleFunctions.cc b/console/consoleFunctions.cc new file mode 100644 index 0000000..235865f --- /dev/null +++ b/console/consoleFunctions.cc @@ -0,0 +1,1110 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "core/resManager.h" +#include "core/fileStream.h" +#include "console/compiler.h" +#include "platform/event.h" +#include "platform/gameInterface.h" + +//---------------------------------------------------------------- + +ConsoleFunction(strcmp, S32, 3, 3, "strcmp(one, two)") +{ + argc; + return dStrcmp(argv[1], argv[2]); +} + +ConsoleFunction(stricmp, S32, 3, 3, "stricmp(one, two)") +{ + argc; + return dStricmp(argv[1], argv[2]); +} + +ConsoleFunction(strlen, S32, 2, 2, "strlen(str)") +{ + argc; + return dStrlen(argv[1]); +} + +ConsoleFunction(strstr, S32 , 3, 3, "strstr(string, substr)") +{ + 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, "pos = strpos(string hay, string needle [, int offset]);") +{ + S32 ret = -1; + 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,"ltrim(string)") +{ + argc; + const char *ret = argv[1]; + while(*ret == ' ' || *ret == '\n' || *ret == '\t') + ret++; + return ret; +} + +ConsoleFunction(rtrim, const char *,2,2,"rtrim(string)") +{ + 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,"trim(string)") +{ + 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, "stripChars( string, chars )" ) +{ + 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(strlwr,const char *,2,2,"strlwr(string)") +{ + argc; + char *ret = Con::getReturnBuffer(dStrlen(argv[1]) + 1); + dStrcpy(ret, argv[1]); + return dStrlwr(ret); +} + +ConsoleFunction(strupr,const char *,2,2,"strupr(string)") +{ + argc; + char *ret = Con::getReturnBuffer(dStrlen(argv[1]) + 1); + dStrcpy(ret, argv[1]); + return dStrupr(ret); +} + +ConsoleFunction(strchr,const char *,3,3,"strchr(string,char)") +{ + argc; + const char *ret = dStrchr(argv[1], argv[2][0]); + return ret ? ret : ""; +} + +ConsoleFunction(strreplace, const char *, 4, 4, "strreplace(string, from, to)") +{ + 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, start, numChars)") +{ + 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; +} + +//-------------------------------------- +static 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 ""; + char *ret = Con::getReturnBuffer(sz+1); + dStrncpy(ret, string, sz); + ret[sz] = '\0'; + return ret; +} + +static const char *getUnits(const char *string, S32 startIndex, S32 endIndex, const char *set) +{ + 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++; + U32 totalSize = (U32(string - startString)); + char *ret = Con::getReturnBuffer(totalSize); + dStrncpy(ret, startString, totalSize - 1); + ret[totalSize-1] = '\0'; + return ret; +} + +static 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; +} + + +static const char* setUnit(const char *string, U32 index, const char *replace, const char *set) +{ + U32 sz; + const char *start = string; + char *ret = Con::getReturnBuffer(dStrlen(string) + dStrlen(replace) + 1); + 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; +} + + +static const char* removeUnit(const char *string, U32 index, const char *set) +{ + U32 sz; + const char *start = string; + char *ret = Con::getReturnBuffer(dStrlen(string) + 1); + ret[0] = '\0'; + U32 padCount = 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(!sz && !string[sz]) + return ret; + + string += sz + 1; // skip the extra field delimiter + dStrcat(ret, string); + return ret; +} + + +//-------------------------------------- +ConsoleFunction(getWord, const char *, 3, 3, "getWord(text, index)") +{ + argc; + return getUnit(argv[1], dAtoi(argv[2]), " \t\n"); +} + +ConsoleFunction(getWords, const char *, 3, 4, "getWord(text, index [,endIndex])") +{ + U32 endIndex; + if(argc==3) + endIndex = 1000000; + else + endIndex = dAtoi(argv[3]); + return getUnits(argv[1], dAtoi(argv[2]), endIndex, " \t\n"); +} + +ConsoleFunction(setWord, const char *, 4, 4, "newText = setWord(text, index, replace)") +{ + argc; + return setUnit(argv[1], dAtoi(argv[2]), argv[3], " \t\n"); +} + +ConsoleFunction(removeWord, const char *, 3, 3, "newText = removeWord(text, index)") +{ + argc; + return removeUnit(argv[1], dAtoi(argv[2]), " \t\n"); +} + +ConsoleFunction(getWordCount, S32, 2, 2, "getWordCount(text)") +{ + argc; + return getUnitCount(argv[1], " \t\n"); +} + +//-------------------------------------- +ConsoleFunction(getField, const char *, 3, 3, "getField(text, index)") +{ + argc; + return 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 getUnits(argv[1], dAtoi(argv[2]), endIndex, "\t\n"); +} + +ConsoleFunction(setField, const char *, 4, 4, "newText = setField(text, index, replace)") +{ + argc; + return setUnit(argv[1], dAtoi(argv[2]), argv[3], "\t\n"); +} + +ConsoleFunction(removeField, const char *, 3, 3, "newText = removeField(text, index)" ) +{ + argc; + return removeUnit(argv[1], dAtoi(argv[2]), "\t\n"); +} + +ConsoleFunction(getFieldCount, S32, 2, 2, "getFieldCount(text)") +{ + argc; + return getUnitCount(argv[1], "\t\n"); +} + +//-------------------------------------- +ConsoleFunction(getRecord, const char *, 3, 3, "getRecord(text, index)") +{ + argc; + return 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 getUnits(argv[1], dAtoi(argv[2]), endIndex, "\n"); +} + +ConsoleFunction(setRecord, const char *, 4, 4, "newText = setRecord(text, index, replace)") +{ + argc; + return setUnit(argv[1], dAtoi(argv[2]), argv[3], "\n"); +} + +ConsoleFunction(removeRecord, const char *, 3, 3, "newText = removeRecord(text, index)" ) +{ + argc; + return removeUnit(argv[1], dAtoi(argv[2]), "\n"); +} + +ConsoleFunction(getRecordCount, S32, 2, 2, "getRecordCount(text)") +{ + argc; + return getUnitCount(argv[1], "\n"); +} +//-------------------------------------- +ConsoleFunction(firstWord, const char *, 2, 2, "firstWord(text)") +{ + 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)") +{ + 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; +} + +//---------------------------------------------------------------- + +ConsoleFunction(detag, const char *, 2, 2, "detag(textTagString)") +{ + 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)") +{ + 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]); +} + +//---------------------------------------------------------------- + +ConsoleFunction(echo, void, 2, 0, "echo(text [, ... ])") +{ + U32 len = 0; + S32 i; + if(gEvalState.traceOn) + len = (gEvalState.stack.size() - 1) * 2; + for(i = 1; i < argc; i++) + len += dStrlen(argv[i]); + + char *ret = Con::getReturnBuffer(len + 1); + ret[0] = 0; + if(gEvalState.traceOn) + { + for(i = 0; i < (S32)gEvalState.stack.size() - 1; i++) + dStrcat(ret, " "); + } + 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; + if(gEvalState.traceOn) + len = (gEvalState.stack.size() - 1) * 2; + for(i = 1; i < argc; i++) + len += dStrlen(argv[i]); + + char *ret = Con::getReturnBuffer(len + 1); + ret[0] = 0; + if(gEvalState.traceOn) + { + for(i = 0; i < (S32)gEvalState.stack.size() - 1; i++) + dStrcat(ret, " "); + } + 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; + if(gEvalState.traceOn) + len = (gEvalState.stack.size() - 1) * 2; + for(i = 1; i < argc; i++) + len += dStrlen(argv[i]); + + char *ret = Con::getReturnBuffer(len + 1); + ret[0] = 0; + if(gEvalState.traceOn) + { + for(i = 0; i < (S32)gEvalState.stack.size() - 1; i++) + dStrcat(ret, " "); + } + for(i = 1; i < argc; i++) + dStrcat(ret, argv[i]); + + Con::errorf(ConsoleLogEntry::General, "%s", ret); + ret[0] = 0; +} + +ConsoleFunction(expandEscape, const char *, 2, 2, "expandEscape(text)") +{ + 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)") +{ + argc; + char *ret = Con::getReturnBuffer(dStrlen(argv[1]) + 1); // worst case situation + dStrcpy( ret, argv[1] ); + collapseEscape( ret ); + return ret; +} + +//---------------------------------------------------------------- + +ConsoleFunction(quit, void, 1, 1, "quit()") +{ + argc; argv; + Platform::postQuitMessage(0); +} + +//---------------------------------------------------------------- +ConsoleFunction(call, const char *, 2, 0, "call(funcName [,args ...])") +{ + return Con::execute(argc - 1, argv + 1); +} + +static U32 execDepth = 0; +static U32 journalDepth = 1; + +ConsoleFunction(compile, bool, 2, 2, "compile(fileName)") +{ + argc; + char nameBuffer[512]; + char* script = NULL; + U32 scriptSize = 0; + + Stream *compiledStream = NULL; + FileTime comModifyTime, scrModifyTime; + + const char *scriptFileName = argv[1]; + + dSprintf(nameBuffer, sizeof(nameBuffer), "%s.dso", scriptFileName); + ResourceObject *rScr = ResourceManager->find(scriptFileName); + ResourceObject *rCom = ResourceManager->find(nameBuffer); + + if(rCom) + rCom->getFileTimes(NULL, &comModifyTime); + if(rScr) + rScr->getFileTimes(NULL, &scrModifyTime); + + Stream *s = ResourceManager->openStream(scriptFileName); + if(s) + { + scriptSize = ResourceManager->getSize(scriptFileName); + script = new char [scriptSize+1]; + s->read(scriptSize, script); + ResourceManager->closeStream(s); + script[scriptSize] = 0; + } + + if (!scriptSize || !script) + { + delete [] script; + Con::errorf(ConsoleLogEntry::Script, "exec: invalid script file %s.", scriptFileName); + return false; + } + // compile this baddie. + Con::printf("Compiling %s...", scriptFileName); + CodeBlock *code = new CodeBlock(); + code->compile(nameBuffer, scriptFileName, script); + delete code; + code = NULL; + + 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; + } + + const char *ext = dStrrchr(argv[1], '.'); + + if(!ext) + { + Con::errorf(ConsoleLogEntry::Script, "exec: invalid script file name %s.", argv[1]); + execDepth--; + return false; + } + StringTableEntry scriptFileName = StringTable->insert(argv[1]); + + bool compiled = dStricmp(ext, ".mis") && !journal; + + if(journal && Game->isJournalReading()) + { + char fileNameBuf[256]; + bool fileRead; + U32 fileSize; + + Game->getJournalStream()->readString(fileNameBuf); + Game->getJournalStream()->read(&fileRead); + if(!fileRead) + { + Con::errorf(ConsoleLogEntry::Script, "Journal script read (failed) for %s", fileNameBuf); + execDepth--; + return false; + } + Game->journalRead(&fileSize); + char *script = new char[fileSize + 1]; + Game->journalRead(fileSize, script); + script[fileSize] = 0; + Con::printf("Executing (journal-read) %s.", scriptFileName); + CodeBlock *newCodeBlock = new CodeBlock(); + newCodeBlock->compileExec(scriptFileName, script, noCalls); + delete [] script; + + execDepth--; + return true; + } + + ResourceObject *rScr = ResourceManager->find(scriptFileName); + ResourceObject *rCom = NULL; + + char nameBuffer[512]; + char* script = NULL; + U32 scriptSize = 0; + U32 version; + + Stream *compiledStream = NULL; + FileTime comModifyTime, scrModifyTime; + + if(compiled) + { + dSprintf(nameBuffer, sizeof(nameBuffer), "%s.dso", scriptFileName); + rCom = ResourceManager->find(nameBuffer); + + if(rCom) + rCom->getFileTimes(NULL, &comModifyTime); + if(rScr) + rScr->getFileTimes(NULL, &scrModifyTime); + } + + if(compiled && rCom && (!rScr || Platform::compareFileTimes(comModifyTime, scrModifyTime) >= 0)) + { + compiledStream = ResourceManager->openStream(nameBuffer); + compiledStream->read(&version); + if(version != ConsoleDSOVersion) + { + ResourceManager->closeStream(compiledStream); + compiledStream = NULL; + } + } + + if(journal && Game->isJournalWriting()) + Game->getJournalStream()->writeString(scriptFileName); + + if(rScr && !compiledStream) + { + Stream *s = ResourceManager->openStream(scriptFileName); + if(journal && Game->isJournalWriting()) + Game->getJournalStream()->write(bool(s != NULL)); + + if(s) + { + scriptSize = ResourceManager->getSize(scriptFileName); + script = new char [scriptSize+1]; + s->read(scriptSize, script); + + if(journal && Game->isJournalWriting()) + { + Game->journalWrite(scriptSize); + Game->journalWrite(scriptSize, script); + } + ResourceManager->closeStream(s); + script[scriptSize] = 0; + } + + if (!scriptSize || !script) + { + delete [] script; + Con::errorf(ConsoleLogEntry::Script, "exec: invalid script file %s.", scriptFileName); + execDepth--; + return false; + } + if(compiled) + { + // compile this baddie. + Con::printf("Compiling %s...", scriptFileName); + CodeBlock *code = new CodeBlock(); + code->compile(nameBuffer, scriptFileName, script); + delete code; + code = NULL; + + compiledStream = ResourceManager->openStream(nameBuffer); + if(compiledStream) + compiledStream->read(&version); + } + } + else + { + if(journal && Game->isJournalWriting()) + Game->getJournalStream()->write(bool(false)); + } + + if(rScr && !compiledStream) + { + // let's just try executing it directly... maybe we're on a readonly volume + Con::printf("Executing %s.", scriptFileName); + CodeBlock *newCodeBlock = new CodeBlock(); + StringTableEntry name = StringTable->insert(scriptFileName); + + newCodeBlock->compileExec(name, script, noCalls); + } + delete [] script; + ret = true; + if(compiledStream) + { + Con::printf("Loading compiled script %s.", scriptFileName); + CodeBlock *code = new CodeBlock; + code->read(scriptFileName, *compiledStream); + ResourceManager->closeStream(compiledStream); + code->exec(0, scriptFileName, NULL, 0, NULL, noCalls); + } + else + { + Con::warnf(ConsoleLogEntry::Script, "Missing file: %s!", scriptFileName); + ret = false; + } + execDepth--; + return ret; +} + +ConsoleFunction(eval, const char *, 2, 2, "eval(consoleString)") +{ + argc; + return Con::evaluate(argv[1], false, NULL); +} + +//---------------------------------------------------------------- + +ConsoleFunction(export, void, 2, 4, "export(searchString [, fileName [,append]])") +{ + const char *fileName =(argc >= 3) ? argv[2] : NULL; + bool append = (argc == 4) ? dAtob(argv[3]) : false; + + gEvalState.globalVars.exportVariables(argv[1], fileName, append); +} + +ConsoleFunction(deleteVariables, void, 2, 2, "deleteVariables(wildCard)") +{ + argc; + gEvalState.globalVars.deleteVariables(argv[1]); +} + +//---------------------------------------------------------------- + +ConsoleFunction(trace, void, 2, 2, "trace(bool)") +{ + argc; + gEvalState.traceOn = dAtob(argv[1]); + Con::printf("Console trace is %s", gEvalState.traceOn ? "on." : "off."); +} + +//---------------------------------------------------------------- + +#if defined(DEBUG) || defined(INTERNAL_RELEASE) +ConsoleFunction(debug, void, 1, 1, "debug()") +{ + argv; argc; + Platform::debugBreak(); +} +#endif + + + +//---------------------------------------------------------------- + +//---------------------------------------------------------------- + +static ResourceObject *firstMatch = NULL; + +ConsoleFunction(findFirstFile, const char *, 2, 2, "findFirstFile(pattern)") +{ + argc; + const char *fn; + firstMatch = ResourceManager->findMatch(argv[1], &fn, NULL); + if(firstMatch) + return fn; + else + return ""; +} + +ConsoleFunction(findNextFile, const char *, 2, 2, "findNextFile(pattern)") +{ + argc; + const char *fn; + firstMatch = ResourceManager->findMatch(argv[1], &fn, firstMatch); + if(firstMatch) + return fn; + else + return ""; +} + +ConsoleFunction(getFileCount, S32, 2, 2, "getFileCount(pattern)") +{ + argc; + const char* fn; + U32 count = 0; + firstMatch = ResourceManager->findMatch(argv[1], &fn, NULL); + if ( firstMatch ) + { + count++; + while ( 1 ) + { + firstMatch = ResourceManager->findMatch(argv[1], &fn, firstMatch); + if ( firstMatch ) + count++; + else + break; + } + } + + return( count ); +} + +ConsoleFunction(getFileCRC, S32, 2, 2, "getFileCRC(filename)") +{ + argc; + U32 crcVal; + if(!ResourceManager->getCrc(argv[1], crcVal)) + return(-1); + return(S32(crcVal)); +} + +ConsoleFunction(isFile, bool, 2, 2, "isFile(fileName)") +{ + argc; + return bool(ResourceManager->find(argv[1])); +} + +ConsoleFunction(isWriteableFileName, bool, 2, 2, "isWriteableFileName(fileName)") +{ + argc; + // in a writeable directory? + if(!ResourceManager->isValidWriteFileName(argv[1])) + return(false); + + // exists? + FileStream fs; + if(!fs.open(argv[1], FileStream::Read)) + return(true); + + // writeable? (ReadWrite will create file if it does not exist) + fs.close(); + if(!fs.open(argv[1], FileStream::ReadWrite)) + return(false); + + return(true); +} + +ConsoleFunction(cdFileCheck, bool, 1, 1, "cdFileCheck(fileName, volume, serial)") +{ + argc; argv; + //see if the file exists... + //return Platform::cdFileExists("575060.bin", "UKD_575060.001", 0x7B9EE4E7); + bool ret = Platform::cdFileExists("\\Tribes2\\GameData\\Tribes2.exe", "T2 INSTALL", 0); +#if defined(DEBUG) || defined(INTERNAL_RELEASE) + return 1; +#else + return ret; +#endif +} + +//---------------------------------------------------------------- + +ConsoleFunction(fileExt, const char *, 2, 2, "fileExt(fileName)") +{ + argc; + const char *ret = dStrrchr(argv[1], '.'); + if(ret) + return ret; + return ""; +} + +ConsoleFunction(fileBase, const char *, 2, 2, "fileBase(fileName)") +{ + argc; + const char *path = dStrrchr(argv[1], '/'); + if(!path) + path = argv[1]; + else + path++; + char *ret = Con::getReturnBuffer(dStrlen(path) + 1); + dStrcpy(ret, path); + char *ext = dStrrchr(ret, '.'); + if(ext) + *ext = 0; + return ret; +} + +ConsoleFunction(filePath, const char *, 2, 2, "filePath(fileName)") +{ + argc; + const char *path = dStrrchr(argv[1], '/'); + if(!path) + return ""; + U32 len = path - argv[1]; + char *ret = Con::getReturnBuffer(len + 1); + dStrncpy(ret, argv[1], len); + ret[len] = 0; + 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)") +{ + 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; + Con::setLocalVariable(token,tmp); + + // advance str past the 'delim space' + while (isInSet(*str, delim)) + str++; + } + + return str; +} + +//---------------------------------------------------------------- + +ConsoleFunction(setLogMode, void, 2, 2, "setLogMode(mode);") +{ + argc; + Con::setLogMode(dAtoi(argv[1])); +} + +ConsoleFunction(setEchoFileLoads, void, 2, 2, "setEchoFileLoads(bool);") +{ + argc; + ResourceManager->setFileNameEcho(dAtob(argv[1])); +} + + diff --git a/console/consoleInternal.cc b/console/consoleInternal.cc new file mode 100644 index 0000000..a3fc4c1 --- /dev/null +++ b/console/consoleInternal.cc @@ -0,0 +1,943 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" + +#include "console/ast.h" +#include "core/tAlgorithm.h" +#include "core/resManager.h" + +#include "core/findMatch.h" +#include "console/consoleInternal.h" +#include "core/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 < hashTableSize;i ++) + { + Entry *walk = hashTable[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; + + if(fileName) + { + if(!ResourceManager->openFileForWrite(strm, ResourceManager->getBasePath(), fileName, append ? FileStream::ReadWrite : FileStream::Write)) + { + 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(fileName) + strm.write(dStrlen(buffer), buffer); + else + Con::printf("%s", buffer); + } + if(fileName) + strm.close(); +} + +void Dictionary::deleteVariables(const char *varString) +{ + const char *searchStr = varString; + + for(S32 i = 0; i < hashTableSize; i++) + { + Entry *walk = hashTable[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(U32(ptr) >> 2); +} + +Dictionary::Entry *Dictionary::lookup(StringTableEntry name) +{ + Entry *walk = hashTable[HashPointer(name) % hashTableSize]; + while(walk) + { + if(walk->name == name) + return walk; + else + walk = walk->nextEntry; + } + + return NULL; +} + +Dictionary::Entry *Dictionary::add(StringTableEntry name) +{ + Entry *walk = hashTable[HashPointer(name) % hashTableSize]; + while(walk) + { + if(walk->name == name) + return walk; + else + walk = walk->nextEntry; + } + Entry *ret; + entryCount++; + + if(entryCount > hashTableSize * 2) + { + Entry head(NULL), *walk; + S32 i; + walk = &head; + walk->nextEntry = 0; + for(i = 0; i < hashTableSize; i++) { + while(walk->nextEntry) { + walk = walk->nextEntry; + } + walk->nextEntry = hashTable[i]; + } + delete[] hashTable; + hashTableSize = hashTableSize * 4 - 1; + hashTable = new Entry *[hashTableSize]; + for(i = 0; i < hashTableSize; i++) + hashTable[i] = NULL; + walk = head.nextEntry; + while(walk) + { + Entry *temp = walk->nextEntry; + S32 idx = HashPointer(walk->name) % hashTableSize; + walk->nextEntry = hashTable[idx]; + hashTable[idx] = walk; + walk = temp; + } + } + + ret = new Entry(name); + S32 idx = HashPointer(name) % hashTableSize; + ret->nextEntry = hashTable[idx]; + hashTable[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[HashPointer(ent->name) % hashTableSize]; + while(*walk != ent) + walk = &((*walk)->nextEntry); + + *walk = (ent->nextEntry); + delete ent; + entryCount--; +} + + +Dictionary::Dictionary(ExprEvalState *state) +{ + setState(state); +} + +void Dictionary::setState(ExprEvalState *state) +{ + S32 i; + entryCount = 0; + exprState = state; + hashTableSize = ST_INIT_SIZE; + hashTable = new Entry *[hashTableSize]; + + for(i = 0; i < hashTableSize; i++) + hashTable[i] = NULL; +} + +Dictionary::~Dictionary() +{ + reset(); + delete [] hashTable; +} + +void Dictionary::reset() +{ + S32 i; + Entry *walk, *temp; + + for(i = 0; i < hashTableSize; i++) + { + walk = hashTable[i]; + while(walk) + { + temp = walk->nextEntry; + delete walk; + walk = temp; + } + hashTable[i] = NULL; + } + hashTableSize = ST_INIT_SIZE; + entryCount = 0; +} + + +const char *Dictionary::tabComplete(const char *prevText, S32 baseLen, bool fForward) +{ + S32 i; + + const char *bestMatch = NULL; + for(i = 0; i < hashTableSize; i++) + { + Entry *walk = hashTable[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); +} + +struct InternalPoint3F { F32 x,y,z; InternalPoint3F(F32 F){x=y=z=F;} }; + +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; + return ""; +} + +void Dictionary::Entry::setStringValue(const char * value) +{ + if(type <= TypeInternalString) + { + if(!value[0] && name[0] == '$') + { + gEvalState.globalVars.remove(this); + return; + } + fval = dAtof(value); + ival = fval; + type = TypeInternalString; + + // may as well add to the next cache line + U32 newLen = ((dStrlen(value) + 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); +} + +void ExprEvalState::popFrame() +{ + Dictionary *last = stack.last(); + stack.pop_back(); + delete last; +} + +ExprEvalState::ExprEvalState() +{ + VECTOR_SET_ASSOCIATION(stack); + globalVars.setState(this); + thisObject = NULL; + traceOn = false; +} + +ExprEvalState::~ExprEvalState() +{ + while(stack.size()) + popFrame(); +} + +ConsoleFunction(backtrace, void, 1, 1, "backtrace();") +{ + argc; 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; + } +} + +Namespace::Namespace() +{ + mPackage = NULL; + mName = NULL; + mParent = NULL; + mNext = NULL; + mEntryList = NULL; + mHashSize = 0; + mHashTable = 0; + mHashSequence = 0; + mRefCountToParent = 0; +} + +void Namespace::clearEntries() +{ + for(Entry *walk = mEntryList; walk; walk = walk->mNext) + walk->clear(); +} + +Namespace *Namespace::find(StringTableEntry name, StringTableEntry package) +{ + 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; +} + +void Namespace::classLinkTo(Namespace *parent) +{ + Namespace *walk = this; + while(walk->mParent && walk->mParent->mName == mName) + walk = walk->mParent; + + if(walk->mParent && walk->mParent != parent) + { + AssertFatal(0, avar("ERROR: Attempting to re-link namespace %s from %s to %s with non-zero refcount.", walk->mName, walk->mParent->mName, parent->mName)); + Con::errorf(ConsoleLogEntry::General, "Attempting to re-link namespace %s from %s to %s with non-zero refcount.", + walk->mName, walk->mParent->mName, parent->mName); + return; + } + mRefCountToParent++; + walk->mParent = parent; +} + +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::init() +{ + // create the global namespace + mGlobalNamespace = find(NULL); +} + +Namespace *Namespace::global() +{ + return mGlobalNamespace; +} + +void Namespace::shutdown() +{ + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + walk->clearEntries(); +} + +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(Vector *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; + mEntryList = ent; + return ent; +} + +void Namespace::addFunction(StringTableEntry name, CodeBlock *cb, U32 functionOffset) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = NULL; + ent->mCode = cb; + ent->mFunctionOffset = functionOffset; + ent->mCode->incRefCount(); + ent->mType = Entry::ScriptFunctionType; +} + +void Namespace::addCommand(StringTableEntry name,StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + + ent->mType = Entry::StringCallbackType; + ent->cb.mStringCallbackFunc = cb; +} + +void Namespace::addCommand(StringTableEntry name,IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + + ent->mType = Entry::IntCallbackType; + ent->cb.mIntCallbackFunc = cb; +} + +void Namespace::addCommand(StringTableEntry name,VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + + ent->mType = Entry::VoidCallbackType; + ent->cb.mVoidCallbackFunc = cb; +} + +void Namespace::addCommand(StringTableEntry name,FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + + ent->mType = Entry::FloatCallbackType; + ent->cb.mFloatCallbackFunc = cb; +} + +void Namespace::addCommand(StringTableEntry name,BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + + ent->mType = Entry::BoolCallbackType; + ent->cb.mBoolCallbackFunc = cb; +} + +extern S32 executeBlock(StmtNode *block, ExprEvalState *state); + +const char *Namespace::Entry::execute(S32 argc, const char **argv, ExprEvalState *state) +{ + if(mType == ScriptFunctionType) + { + if(mFunctionOffset) + return mCode->exec(mFunctionOffset, argv[0], mNamespace, argc, argv, false); + else + return ""; + } + 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]); +} + +ConsoleFunction(isPackage,bool,2,2,"isPackage(packageName)") +{ + argc; + StringTableEntry packageName = StringTable->insert(argv[1]); + return Namespace::isPackage(packageName); +} + +ConsoleFunction(activatePackage, void,2,2,"activatePackage(packageName)") +{ + argc; + StringTableEntry packageName = StringTable->insert(argv[1]); + Namespace::activatePackage(packageName); +} + +ConsoleFunction(deactivatePackage, void,2,2,"deactivatePackage(packageName)") +{ + argc; + StringTableEntry packageName = StringTable->insert(argv[1]); + Namespace::deactivatePackage(packageName); +} diff --git a/console/consoleInternal.h b/console/consoleInternal.h new file mode 100644 index 0000000..92dbfa8 --- /dev/null +++ b/console/consoleInternal.h @@ -0,0 +1,280 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONSOLEINTERNAL_H_ +#define _CONSOLEINTERNAL_H_ + +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif + +class ExprEvalState; +struct FunctionDecl; +class CodeBlock; + +class Namespace +{ + enum { + MaxActivePackages = 512, + }; + + static U32 mNumActivePackages; + static U32 mOldNumActivePackages; + static StringTableEntry mActivePackages[MaxActivePackages]; +public: + StringTableEntry mName; + StringTableEntry mPackage; + + Namespace *mParent; + Namespace *mNext; + U32 mRefCountToParent; + + struct Entry + { + enum { + InvalidFunctionType = -1, + ScriptFunctionType, + StringCallbackType, + IntCallbackType, + FloatCallbackType, + VoidCallbackType, + BoolCallbackType + }; + + Namespace *mNamespace; + Entry *mNext; + StringTableEntry mFunctionName; + S32 mType; + S32 mMinArgs; + S32 mMaxArgs; + const char *mUsage; + + CodeBlock *mCode; + U32 mFunctionOffset; + union { + StringCallback mStringCallbackFunc; + IntCallback mIntCallbackFunc; + VoidCallback mVoidCallbackFunc; + FloatCallback mFloatCallbackFunc; + BoolCallback mBoolCallbackFunc; + } cb; + Entry(); + void clear(); + + const char *execute(S32 argc, const char **argv, ExprEvalState *state); + + }; + Entry *mEntryList; + + Entry **mHashTable; + U32 mHashSize; + U32 mHashSequence; + + Namespace(); + void addFunction(StringTableEntry name, CodeBlock *cb, U32 functionOffset); + void addCommand(StringTableEntry name,StringCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(StringTableEntry name,IntCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(StringTableEntry name,FloatCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(StringTableEntry name,VoidCallback, const char *usage, S32 minArgs, S32 maxArgs); + void addCommand(StringTableEntry name,BoolCallback, const char *usage, S32 minArgs, S32 maxArgs); + + void getEntryList(Vector *); + + Entry *lookup(StringTableEntry name); + Entry *lookupRecursive(StringTableEntry name); + Entry *createLocalEntry(StringTableEntry name); + void buildHashTable(); + void clearEntries(); + void classLinkTo(Namespace *parent); + + 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 unlinkPackages(); + static void relinkPackages(); + static bool isPackage(StringTableEntry name); +}; + +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 = 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 = 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: + S32 hashTableSize; + S32 entryCount; + + Entry **hashTable; + ExprEvalState *exprState; +public: + StringTableEntry scopeName; + Namespace *scopeNamespace; + CodeBlock *code; + U32 ip; + + Dictionary() {} + Dictionary(ExprEvalState *state); + ~Dictionary(); + Entry *lookup(StringTableEntry name); + Entry *add(StringTableEntry name); + void setState(ExprEvalState *state); + void remove(Entry *); + void reset(); + + void exportVariables(const char *varString, const char *fileName, bool append); + 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: + // stuff for doing expression evaluation + + SimObject *thisObject; + Dictionary::Entry *currentVariable; + bool traceOn; + + ExprEvalState(); + ~ExprEvalState(); + + // 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(const char *frameName, Namespace *ns); + void popFrame(); +}; + +#endif diff --git a/console/consoleObject.cc b/console/consoleObject.cc new file mode 100644 index 0000000..aa449ee --- /dev/null +++ b/console/consoleObject.cc @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/consoleObject.h" +#include "core/stringTable.h" +#include "console/console.h" + +static AbstractClassRep::FieldList sg_tempFieldList; +AbstractClassRep *AbstractClassRep::classLinkList = NULL; +AbstractClassRep *AbstractClassRep::classTable[MaxClassId+1]; +bool AbstractClassRep::initialized = false; + +//-------------------------------------- + +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; +} + +//-------------------------------------- +void AbstractClassRep::registerClassRep(AbstractClassRep* in_pRep) +{ + AssertFatal(in_pRep != NULL, "Hmph, that's odd..."); + +#ifdef 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."); + } +#endif + in_pRep->nextClass = classLinkList; + classLinkList = in_pRep; +} + +//-------------------------------------- + +ConsoleObject* AbstractClassRep::create(const char* in_pClassName) +{ + AssertFatal(initialized, "creating an object before AbstractClassRep::initialize."); + + for (AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass) + if (!dStrcmp(walk->getClassName(), in_pClassName)) + return walk->create(); + + AssertWarn(0, avar("Couldn't find class rep for dynamic class: %s", in_pClassName)); + return NULL; +} + +//-------------------------------------- +ConsoleObject* AbstractClassRep::create(const S32 in_classId) +{ + AssertFatal(initialized, "creating an object before AbstractClassRep::initialize."); + AssertFatal(in_classId >= 0 && in_classId <= MaxClassId, "Class id out of range."); + AssertFatal(classTable[in_classId] != NULL, "No class with declared id type."); + + if(classTable[in_classId]) + return classTable[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->mClassIdBase != b->mClassIdBase) + return a->mClassIdBase - b->mClassIdBase; + if(a->mClassVersion != b->mClassVersion) + return a->mClassVersion - b->mClassVersion; + return dStrcmp(a->getClassName(), b->getClassName()); +} + +void AbstractClassRep::initialize() +{ + AssertFatal(!initialized, "Duplicate call to AbstractClassRep::initialize()"); + U32 i; + for(i = 0; i <= MaxClassId; i++) + classTable[i] = NULL; + + Vector dynamicTable(__FILE__, __LINE__); + + AbstractClassRep *walk; + + for (walk = classLinkList; walk; walk = walk->nextClass) + walk->mNamespace = Con::lookupNamespace(StringTable->insert(walk->getClassName())); + + for (walk = classLinkList; walk; walk = walk->nextClass) + { + sg_tempFieldList.setSize(0); + walk->init(); + if (sg_tempFieldList.size() != 0) + walk->mFieldList = sg_tempFieldList; + + if(walk->mClassIdBase != -1) + dynamicTable.push_back(walk); + } + dQsort((void *) &dynamicTable[0], dynamicTable.size(), sizeof(AbstractClassRep *), ACRCompare); + + S32 prevClassBase = -1; + S32 curClassId = 0; + + for(i = 0; i < dynamicTable.size(); i++) + { + AbstractClassRep *cur = dynamicTable[i]; + if(cur->mClassIdBase != prevClassBase) + { + AssertFatal(cur->mClassIdBase >= curClassId, avar("Class range too small %d", prevClassBase)); + prevClassBase = cur->mClassIdBase; + curClassId = cur->mClassIdBase; + } + cur->mClassId = curClassId++; + classTable[cur->mClassId] = cur; + } + initialized = true; + sg_tempFieldList.clear(); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- ConsoleObject +void ConsoleObject::addField(const char* in_pFieldname, + const U32 in_fieldType, + const U32 in_fieldOffset, + const U32 in_elementCount, + EnumTable *in_table) +{ + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(in_pFieldname); + f.type = in_fieldType; + f.offset = in_fieldOffset; + f.elementCount = in_elementCount; + f.table = in_table; + + sg_tempFieldList.push_back(f); +} + +void ConsoleObject::addDepricatedField(const char *fieldName) +{ + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(fieldName); + f.type = AbstractClassRep::DepricatedFieldType; + + 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() +{ +} + +//-------------------------------------- +AbstractClassRep* ConsoleObject::getClassRep() const +{ + return NULL; +} + + diff --git a/console/consoleObject.h b/console/consoleObject.h new file mode 100644 index 0000000..e31ec04 --- /dev/null +++ b/console/consoleObject.h @@ -0,0 +1,334 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONSOLEOBJECT_H_ +#define _CONSOLEOBJECT_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif +#ifndef _BITSET_H_ +#include "core/bitSet.h" +#endif +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif + +//------------------------------------------------------------------------------ +//-------------------------------------- AbstractClassRep +// + +class Namespace; + +enum { + NetObjectClassFirst = 0, + NetObjectClassLast = 127, + NetObjectClassBitSize = 7, + + DataBlockClassFirst = 128, + DataBlockClassLast = 255, + DataBlockClassBitSize = 7, + + NetEventClassFirst = 255, + NetEventClassLast = 318, + NetEventClassBitSize = 6, + + MaxClassId = 318 +}; + +class AbstractClassRep +{ + friend class ConsoleObject; + + //-------------------------------------- Public interface + public: + enum + { + DepricatedFieldType = 0xFFFFFFFF + }; + virtual ~AbstractClassRep() { } + AbstractClassRep() { + VECTOR_SET_ASSOCIATION(mFieldList); + } + + S32 getClassId() const; + const char* getClassName() const; + Namespace * getNameSpace(); + virtual ConsoleObject* create() const = 0; + AbstractClassRep *getNextClass(); + static AbstractClassRep *getClassList(); + + //-------------------------------------- Field interface + public: + struct Field { + const char* pFieldname; + U32 type; + U32 offset; + S32 elementCount; + EnumTable *table; + BitSet32 flag; + }; + typedef Vector FieldList; + + FieldList mFieldList; + const Field *findField(StringTableEntry fieldName) const; + public: + S32 mClassIdBase; + S32 mClassVersion; + S32 mClassNetClass; + static void initialize(); // Called from CMDCon::init once on startup + + protected: + static AbstractClassRep *classTable[MaxClassId+1]; + static AbstractClassRep *classLinkList; + static bool initialized; + + static void registerClassRep(AbstractClassRep*); + + protected: + virtual void init() const = 0; + + //--------------------------------------------------------------- + // each registered tagged class has a id tag and a compiler + // independant class name + // if the class can be constructed by tag (network) + // the tag is >= 0. + + protected: + + const char *mClassName; + S32 mClassId; + AbstractClassRep *nextClass; + Namespace *mNamespace; + + // Helper functions for ConsoleObject + protected: + static ConsoleObject* create(const char* in_pClassName); + static ConsoleObject* create(const S32 in_classId); + +}; + +inline AbstractClassRep *AbstractClassRep::getClassList() +{ + return classLinkList; +} + +inline AbstractClassRep *AbstractClassRep::getNextClass() +{ + return nextClass; +} + +inline S32 AbstractClassRep::getClassId() const +{ + return mClassId; +} + +inline const char* AbstractClassRep::getClassName() const +{ + return mClassName; +} + +inline Namespace *AbstractClassRep::getNameSpace() +{ + return mNamespace; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- ConcreteClassRep +// + +enum +{ + NetEventClassAny, + NetEventClassClient, + NetEventClassServer +}; + +template +class ConcreteClassRep : public AbstractClassRep +{ + public: + ConcreteClassRep(const char *name, S32 idBase = -1, S32 version=1,S32 netClass = NetEventClassAny) + { + // name is a static compiler string so no need to worry about copying or deleting + mClassName = name; + mClassId = -1; + mClassIdBase = idBase; + mClassVersion = version; + mClassNetClass = netClass; + registerClassRep(this); + }; + void init() const + { + AbstractClassRep *parent = T::getParentStaticClassRep(); + AbstractClassRep *child = T::getStaticClassRep(); + if(parent && child) + Con::classLinkNamespaces(parent->getNameSpace(), child->getNameSpace()); + T::initPersistFields(); + T::consoleInit(); + } + ConsoleObject* create() const { return new T; } +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- USER interface class. Derive from this, +// and access functionality through its +// static interface. +class ConsoleObject +{ + protected: + ConsoleObject() { /* disallowed */ } + ConsoleObject(const ConsoleObject&); // disallowed ? + + protected: + const AbstractClassRep::Field *findField(StringTableEntry fieldName) const; + + public: + virtual AbstractClassRep* getClassRep() const; + bool setField(const char *fieldName, const char *value); + virtual ~ConsoleObject(); + + // Object creation interface + public: + static ConsoleObject* create(const char* in_pClassName); + static ConsoleObject* create(const U32 in_classId); + + // Query interface + public: + static const char* lookupClassName(const U32 in_classTag); + + // Field interface + protected: + static void addField(const char* in_pFieldname, + const U32 in_fieldType, + const U32 in_fieldOffset, + const U32 in_elementCount = 1, + EnumTable *in_table = NULL); + static void addDepricatedField(const char *fieldName); + static bool removeField(const char* in_pFieldname); + + public: + static void initPersistFields(); // declare one of these in a class to register dynamic fields in a class. + static void consoleInit(); // declare one of these in a class to register console functions and establish namespace linkage + const AbstractClassRep::FieldList& getFieldList() const; + + static AbstractClassRep *getStaticClassRep() { return NULL; } + static AbstractClassRep *getParentStaticClassRep() { return NULL; } + + S32 getClassId() const; + const char *getClassName() const; +}; + + + +//------------------------------------------------------------------------------ +//-------------------------------------- Inlines +// +inline S32 ConsoleObject::getClassId() const +{ + AssertFatal(getClassRep() != NULL, + "Cannot get tag from non-declared dynamic class"); + return getClassRep()->getClassId(); +} + +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, + "Cannot get tag from non-declared dynamic class"); + 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 *) (S32(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 in_classId) +{ + return AbstractClassRep::create(in_classId); +} + +inline const AbstractClassRep::FieldList& ConsoleObject::getFieldList() const +{ + return getClassRep()->mFieldList; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- MACROS for dynamic objects... +// +#define DECLARE_CONOBJECT(className) \ + static ConcreteClassRep dynClassRep; \ + static AbstractClassRep* getParentStaticClassRep(); \ + static AbstractClassRep* getStaticClassRep(); \ + virtual AbstractClassRep* getClassRep() const + +#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) + +#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,NetObjectClassFirst,1) + +#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,DataBlockClassFirst,1) + +#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,NetEventClassFirst,1,NetEventClassAny) + +#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,NetEventClassFirst,1,NetEventClassClient) + +#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,NetEventClassFirst,1,NetEventClassServer) + +#endif //_CONSOLEOBJECT_H_ diff --git a/console/consoleTypes.cc b/console/consoleTypes.cc new file mode 100644 index 0000000..f456576 --- /dev/null +++ b/console/consoleTypes.cc @@ -0,0 +1,429 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "Core/stringTable.h" +#include "Core/color.h" +#include "console/simBase.h" + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +static const char *getDataTypeString(void *dptr, EnumTable *, BitSet32) +{ + return *((const char **)(dptr)); +} + +static void setDataTypeString(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + if(argc == 1) + *((const char **) dptr) = StringTable->insert(argv[0]); + else + Con::printf("(TypeString) Cannot set multiple args to a single string."); +} + +static void setDataTypeCaseString(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + if(argc == 1) + *((const char **) dptr) = StringTable->insert(argv[0], true); + else + Con::printf("(TypeCaseString) Cannot set multiple args to a single string."); +} + +static const char *getDataTypeCharArray(void *dptr, EnumTable *, BitSet32) +{ + return (const char *)(dptr); +} + +//---------------------------------------------------------------------------- + +static const char *getDataTypeU8(void *dptr, EnumTable *, BitSet32) +{ + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d", *((U8 *) dptr) ); + return returnBuffer; +} + +static void setDataTypeU8(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + if(argc == 1) + *((U8 *) dptr) = dAtoi(argv[0]); + else + Con::printf("(TypeU8) Cannot set multiple args to a single S32."); +} + +static const char *getDataTypeS32(void *dptr, EnumTable *, BitSet32) +{ + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d", *((S32 *) dptr) ); + return returnBuffer; +} + +static void setDataTypeS32(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + if(argc == 1) + *((S32 *) dptr) = dAtoi(argv[0]); + else + Con::printf("(TypeS32) Cannot set multiple args to a single S32."); +} + +//----------------------------------------------------------------------------- +static const char *getDataTypeS32Vector(void *dptr, EnumTable *, BitSet32) +{ + 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); + // 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; +} + +static void setDataTypeS32Vector(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + 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 ...\""); +} + +//----------------------------------------------------------------------------- +static const char *getDataTypeF32(void *dptr, EnumTable *, BitSet32) +{ + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g", *((F32 *) dptr) ); + return returnBuffer; +} + +static void setDataTypeF32(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + if(argc == 1) + *((F32 *) dptr) = dAtof(argv[0]); + else + Con::printf("(TypeF32) Cannot set multiple args to a single F32."); +} + +//----------------------------------------------------------------------------- +static const char *getDataTypeF32Vector(void *dptr, EnumTable *, BitSet32) +{ + 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, "%f ", *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; +} + +static void setDataTypeF32Vector(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + 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, "%f", &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 ...\""); +} + +//----------------------------------------------------------------------------- +static const char *getDataTypeBool(void *dptr, EnumTable *, BitSet32) +{ + return *((bool *) dptr) ? "1" : "0"; +} + +static void setDataTypeBool(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + if(argc == 1) + *((bool *) dptr) = dAtob(argv[0]); + else + Con::printf("(TypeBool) Cannot set multiple args to a single bool."); +} + +static const char *getDataTypeBoolVector(void *dptr, EnumTable *, BitSet32) +{ + 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); +} + +static void setDataTypeBoolVector(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + 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 ...\""); +} + +static const char *getDataTypeEnum(void *dptr, EnumTable *tbl, BitSet32) +{ + 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 ""; +} + +static void setDataTypeEnum(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32) +{ + 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; +} + +static const char *getDataTypeFlag(void *dptr, EnumTable *, BitSet32 flag) +{ + BitSet32 tempFlags = *(BitSet32 *)dptr; + if (tempFlags.test(flag)) return "true"; + else return "false"; +} + +static void setDataTypeFlag(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 flag) +{ + bool value = true; + if (argc != 1) + { + Con::printf("flag must be true or false"); + } + else + { + value = dAtob(argv[0]); + } + ((BitSet32 *)dptr)->set(flag, value); +} + +static const char *getDataTypeColorF(void *dptr, EnumTable *, BitSet32) +{ + ColorF * color = (ColorF*)dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%f %f %f %f", color->red, color->green, color->blue, color->alpha); + return(returnBuffer); +} + +static void setDataTypeColorF(void *dptr, S32 argc, const char ** argv, EnumTable *, BitSet32) +{ + ColorF *tmpColor = (ColorF *) dptr; + if(argc == 1) + { + tmpColor->set(0, 0, 0, 1); + F32 r,g,b,a; + S32 args = dSscanf(argv[0], "%f %f %f %f", &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] }"); +} + +static const char *getDataTypeColorI(void *dptr, EnumTable *, BitSet32) +{ + 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; +} + +static void setDataTypeColorI(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + 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] }"); +} + +static void setDataTypeSimObjectPtr(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + if(argc == 1) + { + SimObject **obj = (SimObject **)dptr; + *obj = Sim::findObject(argv[0]); + } + else + Con::printf("(TypeSimObjectPtr) Cannot set multiple args to a single S32."); +} + +static const char *getDataTypeSimObjectPtr(void *dptr, EnumTable *, BitSet32) +{ + SimObject **obj = (SimObject**)dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%s", *obj ? (*obj)->getName() : ""); + return returnBuffer; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void RegisterCoreTypes(void) +{ + Con::registerType(TypeS8, sizeof(U8), getDataTypeU8, setDataTypeU8); + Con::registerType(TypeS32, sizeof(S32), getDataTypeS32, setDataTypeS32); + Con::registerType(TypeS32Vector, sizeof(Vector), getDataTypeS32Vector, setDataTypeS32Vector); + Con::registerType(TypeBool, sizeof(bool), getDataTypeBool, setDataTypeBool); + Con::registerType(TypeBoolVector, sizeof(Vector), getDataTypeBoolVector, setDataTypeBoolVector); + Con::registerType(TypeF32, sizeof(F32), getDataTypeF32, setDataTypeF32); + Con::registerType(TypeF32Vector, sizeof(Vector), getDataTypeF32Vector, setDataTypeF32Vector); + Con::registerType(TypeString, sizeof(const char *), getDataTypeString, setDataTypeString); + Con::registerType(TypeCaseString, sizeof(const char *), getDataTypeString, setDataTypeCaseString); + Con::registerType(TypeEnum, sizeof(S32), getDataTypeEnum, setDataTypeEnum); + Con::registerType(TypeFlag, sizeof(S32), getDataTypeFlag, setDataTypeFlag); + Con::registerType(TypeColorI, sizeof(ColorI), getDataTypeColorI, setDataTypeColorI); + Con::registerType(TypeColorF, sizeof(ColorF), getDataTypeColorF, setDataTypeColorF); + Con::registerType(TypeSimObjectPtr, sizeof(SimObject*), getDataTypeSimObjectPtr, setDataTypeSimObjectPtr); +} + diff --git a/console/consoleTypes.h b/console/consoleTypes.h new file mode 100644 index 0000000..4bcb8b3 --- /dev/null +++ b/console/consoleTypes.h @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONSOLETYPES_H_ +#define _CONSOLETYPES_H_ + +#define Offset(x, cls) (S32)&(((cls *)0)->x) + +enum ConsoleDynamicTypes { + + //Registered in ConsoleTypes.cc + TypeS8 = 0, + TypeS32, + TypeS32Vector, + TypeBool, + TypeBoolVector, + TypeF32, + TypeF32Vector, + TypeString, + TypeCaseString, + TypeEnum, + TypeFlag, + TypeColorI, + TypeColorF, + TypeSimObjectPtr, + + //Registered in MathTypes.cc + TypePoint2I, + TypePoint2F, + TypePoint3F, + TypePoint4F, + TypeRectI, + TypeRectF, + TypeMatrixPosition, + TypeMatrixRotation, + TypeBox3F, + + //Registered in GuiTypes.cc + TypeGuiProfile, + + // Game types + TypeGameBaseDataPtr, + TypeExplosionDataPtr, + TypeShockwaveDataPtr, + TypeSplashDataPtr, + TypeEnergyProjectileDataPtr, + TypeBombProjectileDataPtr, + TypeParticleEmitterDataPtr, + TypeAudioDescriptionPtr, + TypeAudioProfilePtr, + TypeTriggerPolyhedron, + TypeProjectileDataPtr, + TypeCannedChatItemPtr, + TypeWayPointTeam, + TypeDebrisDataPtr, + TypeCommanderIconDataPtr, + TypeDecalDataPtr, + TypeEffectProfilePtr, + TypeAudioEnvironmentPtr, + TypeAudioSampleEnvironmentPtr, + + NumConsoleTypes +}; + +void RegisterCoreTypes(void); + +#endif diff --git a/console/gram.cc b/console/gram.cc new file mode 100644 index 0000000..68e72c3 --- /dev/null +++ b/console/gram.cc @@ -0,0 +1,2439 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/* yacc -P console\yyparse.c -p CMD -D console\gram.h -o console\gram.cc console\gram.y */ +#ifdef YYTRACE +#define YYDEBUG 1 +#else +#ifndef YYDEBUG +#define YYDEBUG 0 +#endif +#endif +/* + * Portable way of defining ANSI C prototypes + */ +#ifndef YY_ARGS +#ifdef __STDC__ +#define YY_ARGS(x) x +#else +#define YY_ARGS(x) () +#endif +#endif + +#ifdef YACC_WINDOWS + +#include + +/* + * the following is the handle to the current + * instance of a windows program. The user + * program calling CMDparse must supply this! + */ + +#ifdef STRICT +extern HINSTANCE hInst; +#else +extern HANDLE hInst; +#endif + +#endif /* YACC_WINDOWS */ + +#if YYDEBUG +typedef struct yyNamedType_tag { /* Tokens */ + char * name; /* printable name */ + short token; /* token # */ + short type; /* token type */ +} yyNamedType; +typedef struct yyTypedRules_tag { /* Typed rule table */ + char * name; /* compressed rule string */ + short type; /* rule result type */ +} yyTypedRules; + +#endif + +#line 1 "console/gram.y" + +#include "console/console.h" +#include "console/ast.h" +#include +#include "stdio.h" +#include "console/consoleInternal.h" + +#ifndef YYDEBUG +#define YYDEBUG +#endif + +#define YYSSIZE 350 + +int outtext(char *fmt, ...); +extern int serrors; +#define nil 0 +#undef YY_ARGS +#define YY_ARGS(x) x +#line 20 "console/gram.y" + + /* Reserved Word Definitions */ +#define rwDEFINE 257 +#define rwENDDEF 258 +#define rwDECLARE 259 +#define rwBREAK 260 +#define rwELSE 261 +#define rwCONTINUE 262 +#define rwGLOBAL 263 +#define rwIF 264 +#define rwNIL 265 +#define rwRETURN 266 +#define rwWHILE 267 +#define rwENDIF 268 +#define rwENDWHILE 269 +#define rwENDFOR 270 +#define rwDEFAULT 271 +#define rwFOR 272 +#define rwDATABLOCK 273 +#define rwSWITCH 274 +#define rwCASE 275 +#define rwSWITCHSTR 276 +#define rwCASEOR 277 +#define rwPACKAGE 278 +#define ILLEGAL_TOKEN 279 +#line 30 "console/gram.y" + + /* Constants and Identifier Definitions */ +#define CHRCONST 280 +#define INTCONST 281 +#define TTAG 282 +#define VAR 283 +#define IDENT 284 +#define STRATOM 285 +#define TAGATOM 286 +#define FLTCONST 287 +#line 42 "console/gram.y" + + /* Operator Definitions */ +#define opMINUSMINUS 288 +#define opPLUSPLUS 289 +#define STMT_SEP 290 +#define opSHL 291 +#define opSHR 292 +#define opPLASN 293 +#define opMIASN 294 +#define opMLASN 295 +#define opDVASN 296 +#define opMODASN 297 +#define opANDASN 298 +#define opXORASN 299 +#define opORASN 300 +#define opSLASN 301 +#define opSRASN 302 +#define opCAT 303 +#define opEQ 304 +#define opNE 305 +#define opGE 306 +#define opLE 307 +#define opAND 308 +#define opOR 309 +#define opSTREQ 310 +#define opCOLONCOLON 311 +typedef union { + char c; + int i; + const char *s; + char *str; + double f; + StmtNode *stmt; + ExprNode *expr; + SlotAssignNode *slist; + VarNode *var; + SlotDecl slot; + ObjectBlockDecl odcl; + ObjectDeclNode *od; + AssignDecl asn; + IfStmtNode *ifnode; +} YYSTYPE; +#define opMDASN 312 +#define opNDASN 313 +#define opNTASN 314 +#define opSTRNE 315 +#define UNARY 316 +extern int CMDchar, yyerrflag; +extern YYSTYPE CMDlval; +#if YYDEBUG +enum YY_Types { YY_t_NoneDefined, YY_t_i, YY_t_c, YY_t_s, YY_t_str, YY_t_f, YY_t_ifnode, YY_t_stmt, YY_t_expr, YY_t_od, YY_t_odcl, YY_t_slist, YY_t_slot, YY_t_var, YY_t_asn +}; +#endif +#if YYDEBUG +yyTypedRules yyRules[] = { + { "&00: %35 &00", 0}, + { "%35: %04", 0}, + { "%04:", 7}, + { "%04: %04 %03", 7}, + { "%03: %09", 7}, + { "%03: %06", 7}, + { "%03: %05", 7}, + { "%05: &23 &29 &49 %07 &50 &48", 7}, + { "%07: %06", 7}, + { "%07: %07 %06", 7}, + { "%08:", 7}, + { "%08: %08 %09", 7}, + { "%09: %19", 7}, + { "%09: %20", 7}, + { "%09: %21", 7}, + { "%09: %23", 7}, + { "%09: %02", 7}, + { "%09: &05 &48", 7}, + { "%09: &07 &48", 7}, + { "%09: &11 &48", 7}, + { "%09: &11 %27 &48", 7}, + { "%09: %31 &48", 7}, + { "%09: &27 &39 %27 &48", 7}, + { "%09: &27 &39 %27 &46 %27 &48", 7}, + { "%06: &02 &29 &44 %33 &45 &49 %08 &50", 7}, + { "%06: &02 &29 &78 &29 &44 %33 &45 &49 %08 &50", 7}, + { "%33:", 13}, + { "%33: %32", 13}, + { "%32: &28", 13}, + { "%32: %32 &46 &28", 13}, + { "%23: &18 &29 &44 &29 &45 &49 %28 &50 &48", 7}, + { "%23: &18 &29 &44 &29 &45 &47 &29 &49 %28 &50 &48", 7}, + { "%24: &04 %18 &44 %14 %15 &45 &49 %26 &50", 9}, + { "%24: &04 %18 &44 %14 %15 &45", 9}, + { "%14:", 8}, + { "%14: %27", 8}, + { "%15:", 8}, + { "%15: &46 %10", 8}, + { "%26:", 10}, + { "%26: %28", 10}, + { "%26: %25", 10}, + { "%26: %28 %25", 10}, + { "%25: %24 &48", 9}, + { "%25: %25 %24 &48", 9}, + { "%22: &49 %08 &50", 7}, + { "%22: %09", 7}, + { "%02: &19 &44 %27 &45 &49 %01 &50", 7}, + { "%02: &21 &44 %27 &45 &49 %01 &50", 7}, + { "%01: &20 %17 &47 %08", 6}, + { "%01: &20 %17 &47 %08 &16 &47 %08", 6}, + { "%01: &20 %17 &47 %08 %01", 6}, + { "%17: %27", 8}, + { "%17: %17 &22 %27", 8}, + { "%19: &09 &44 %27 &45 %22", 7}, + { "%19: &09 &44 %27 &45 %22 &06 %22", 7}, + { "%20: &12 &44 %27 &45 %22", 7}, + { "%21: &17 &44 %27 &48 %27 &48 %27 &45 %22", 7}, + { "%31: %16", 7}, + { "%27: %16", 8}, + { "%27: &44 %27 &45", 8}, + { "%27: %27 &51 %27", 8}, + { "%27: %27 &43 %27", 8}, + { "%27: %27 &42 %27", 8}, + { "%27: %27 &41 %27", 8}, + { "%27: %27 &33 %27", 8}, + { "%27: %27 &34 %27", 8}, + { "%27: %27 &35 %27", 8}, + { "%27: %27 &36 %27", 8}, + { "%27: &34 %27", 8}, + { "%27: &35 %27", 8}, + { "%27: &27", 8}, + { "%27: %27 &83 %27 &47 %27", 8}, + { "%27: %27 &37 %27", 8}, + { "%27: %27 &38 %27", 8}, + { "%27: %27 &73 %27", 8}, + { "%27: %27 &74 %27", 8}, + { "%27: %27 &71 %27", 8}, + { "%27: %27 &72 %27", 8}, + { "%27: %27 &76 %27", 8}, + { "%27: %27 &58 %27", 8}, + { "%27: %27 &59 %27", 8}, + { "%27: %27 &75 %27", 8}, + { "%27: %27 &77 %27", 8}, + { "%27: %27 &84 %27", 8}, + { "%27: %27 &54 %27", 8}, + { "%27: &53 %27", 8}, + { "%27: &52 %27", 8}, + { "%27: &31", 8}, + { "%27: &32", 8}, + { "%27: &26", 8}, + { "%27: &05", 8}, + { "%27: %30", 8}, + { "%27: &29", 8}, + { "%27: &30", 8}, + { "%27: &28", 8}, + { "%27: &28 &79 %12 &86", 8}, + { "%30: %27 &40 &29", 12}, + { "%30: %27 &40 &29 &79 %12 &86", 12}, + { "%18: &29", 8}, + { "%18: &44 %27 &45", 8}, + { "%34: &56", 14}, + { "%34: &55", 14}, + { "%34: &60 %27", 14}, + { "%34: &61 %27", 14}, + { "%34: &62 %27", 14}, + { "%34: &63 %27", 14}, + { "%34: &64 %27", 14}, + { "%34: &65 %27", 14}, + { "%34: &66 %27", 14}, + { "%34: &67 %27", 14}, + { "%34: &68 %27", 14}, + { "%34: &69 %27", 14}, + { "%16: %13", 8}, + { "%16: %24", 8}, + { "%16: &28 &39 %27", 8}, + { "%16: &28 &79 %12 &86 &39 %27", 8}, + { "%16: &28 %34", 8}, + { "%16: &28 &79 %12 &86 %34", 8}, + { "%16: %30 %34", 8}, + { "%16: %30 &39 %27", 8}, + { "%16: %30 &39 &49 %10 &50", 8}, + { "%13: &29 &44 %11 &45", 8}, + { "%13: &29 &78 &29 &44 %11 &45", 8}, + { "%13: %27 &40 &29 &44 %11 &45", 8}, + { "%11:", 8}, + { "%11: %10", 8}, + { "%10: %27", 8}, + { "%10: %10 &46 %27", 8}, + { "%28: %29", 11}, + { "%28: %28 %29", 11}, + { "%29: &29 &39 %27 &48", 11}, + { "%29: &18 &39 %27 &48", 11}, + { "%29: &29 &79 %12 &86 &39 %27 &48", 11}, + { "%12: %27", 8}, + { "%12: %12 &46 %27", 8}, +{ "$accept", 0},{ "error", 0} +}; +yyNamedType yyTokenTypes[] = { + { "$end", 0, 0}, + { "error", 256, 0}, + { "rwDEFINE", 257, 1}, + { "rwENDDEF", 258, 1}, + { "rwDECLARE", 259, 1}, + { "rwBREAK", 260, 1}, + { "rwELSE", 261, 1}, + { "rwCONTINUE", 262, 1}, + { "rwGLOBAL", 263, 1}, + { "rwIF", 264, 1}, + { "rwNIL", 265, 1}, + { "rwRETURN", 266, 1}, + { "rwWHILE", 267, 1}, + { "rwENDIF", 268, 1}, + { "rwENDWHILE", 269, 1}, + { "rwENDFOR", 270, 1}, + { "rwDEFAULT", 271, 1}, + { "rwFOR", 272, 1}, + { "rwDATABLOCK", 273, 1}, + { "rwSWITCH", 274, 1}, + { "rwCASE", 275, 1}, + { "rwSWITCHSTR", 276, 1}, + { "rwCASEOR", 277, 1}, + { "rwPACKAGE", 278, 1}, + { "ILLEGAL_TOKEN", 279, 0}, + { "CHRCONST", 280, 2}, + { "INTCONST", 281, 1}, + { "TTAG", 282, 3}, + { "VAR", 283, 3}, + { "IDENT", 284, 3}, + { "STRATOM", 285, 4}, + { "TAGATOM", 286, 4}, + { "FLTCONST", 287, 5}, + { "'+'", 43, 1}, + { "'-'", 45, 1}, + { "'*'", 42, 1}, + { "'/'", 47, 1}, + { "'<'", 60, 1}, + { "'>'", 62, 1}, + { "'='", 61, 1}, + { "'.'", 46, 1}, + { "'|'", 124, 1}, + { "'&'", 38, 1}, + { "'%'", 37, 1}, + { "'('", 40, 1}, + { "')'", 41, 1}, + { "','", 44, 1}, + { "':'", 58, 1}, + { "';'", 59, 1}, + { "'{'", 123, 1}, + { "'}'", 125, 1}, + { "'^'", 94, 1}, + { "'~'", 126, 1}, + { "'!'", 33, 1}, + { "'@'", 64, 1}, + { "opMINUSMINUS", 288, 1}, + { "opPLUSPLUS", 289, 1}, + { "STMT_SEP", 290, 1}, + { "opSHL", 291, 1}, + { "opSHR", 292, 1}, + { "opPLASN", 293, 1}, + { "opMIASN", 294, 1}, + { "opMLASN", 295, 1}, + { "opDVASN", 296, 1}, + { "opMODASN", 297, 1}, + { "opANDASN", 298, 1}, + { "opXORASN", 299, 1}, + { "opORASN", 300, 1}, + { "opSLASN", 301, 1}, + { "opSRASN", 302, 1}, + { "opCAT", 303, 1}, + { "opEQ", 304, 1}, + { "opNE", 305, 1}, + { "opGE", 306, 1}, + { "opLE", 307, 1}, + { "opAND", 308, 1}, + { "opOR", 309, 1}, + { "opSTREQ", 310, 1}, + { "opCOLONCOLON", 311, 1}, + { "'['", 91, 0}, + { "opMDASN", 312, 0}, + { "opNDASN", 313, 0}, + { "opNTASN", 314, 0}, + { "'?'", 63, 0}, + { "opSTRNE", 315, 0}, + { "UNARY", 316, 0}, + { "']'", 93, 0} + +}; +#endif +static short yydef[] = { + + 3, 65535, 49, 48, 47, 65531, 95, 91, 65527, 46, + 45, 30, 29, 62, 61, 60, 59, 58, 57, 56, + 55, 54, 53, 52, 69, 66, 67, 64, 51, 44, + 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, + 33, 32, 28, 27, 26, 25, 24, 23, 22, 21, + 65523, 65517, 50, 65513, 65509, 8, 65505, 5, 70, 68, + 31, 19, 65501, 63, 10, 6, 123, 17, 65497, 123, + 13, 12, 123, 18, 15, 14, 123, 16 +}; +static short yyex[] = { + + 0, 0, 65535, 1, 59, 20, 65535, 97, 41, 65, + 65535, 1, 41, 7, 44, 7, 65535, 1, 41, 4, + 65535, 1, 41, 65, 65535, 1, 41, 65, 65535, 1, + 41, 9, 65535, 1, 41, 4, 65535, 1, 125, 11, + 65535, 1 +}; +static short yyact[] = { + + 65310, 65314, 65311, 65312, 65309, 65322, 65320, 65528, 65325, 65317, + 65324, 65316, 65315, 65321, 65319, 65318, 65326, 65287, 65529, 65533, + 65532, 65289, 65285, 65286, 287, 286, 285, 284, 283, 282, + 281, 278, 276, 274, 273, 272, 267, 266, 264, 262, + 260, 259, 257, 126, 45, 42, 40, 33, 65327, 65338, + 65294, 65293, 65337, 65336, 65335, 65334, 65333, 65332, 65331, 65330, + 65329, 65328, 302, 301, 300, 299, 298, 297, 296, 295, + 294, 293, 289, 288, 91, 61, 65527, 65339, 311, 40, + 65340, 65294, 65293, 65337, 65336, 65335, 65334, 65333, 65332, 65331, + 65330, 65329, 65328, 302, 301, 300, 299, 298, 297, 296, + 295, 294, 293, 289, 288, 61, 65310, 65314, 65311, 65312, + 65309, 65320, 65288, 65287, 65284, 65533, 65532, 65289, 65285, 65286, + 287, 286, 285, 284, 283, 282, 281, 260, 259, 126, + 45, 42, 40, 33, 65362, 65361, 65357, 65359, 65358, 65341, + 65356, 65354, 65353, 65355, 65342, 65363, 65360, 65347, 65346, 65350, + 65349, 65352, 65351, 65345, 65348, 65344, 65343, 315, 310, 309, + 308, 307, 306, 305, 304, 292, 291, 124, 94, 64, + 63, 62, 60, 47, 46, 45, 43, 42, 38, 37, + 65365, 40, 65366, 40, 65367, 40, 65368, 40, 65369, 40, + 65370, 65291, 284, 40, 65372, 284, 65373, 284, 65374, 61, + 65262, 59, 65310, 65314, 65311, 65312, 65260, 65309, 65320, 65288, + 65287, 65284, 65533, 65532, 65289, 65285, 65286, 287, 286, 285, + 284, 283, 282, 281, 260, 259, 126, 59, 45, 42, + 40, 33, 65259, 59, 65258, 59, 65376, 284, 65378, 284, + 65310, 65314, 65311, 65312, 65380, 65309, 65320, 65288, 65287, 65284, + 65533, 65532, 65289, 65285, 65286, 287, 286, 285, 284, 283, + 282, 281, 260, 259, 126, 123, 45, 42, 40, 33, + 65341, 46, 65507, 284, 65362, 65361, 65283, 65357, 65359, 65358, + 65341, 65356, 65354, 65353, 65355, 65342, 65363, 65360, 65347, 65346, + 65350, 65349, 65352, 65351, 65345, 65348, 65344, 65343, 315, 310, + 309, 308, 307, 306, 305, 304, 292, 291, 124, 94, + 64, 63, 62, 60, 47, 46, 45, 43, 42, 41, + 38, 37, 65485, 40, 65388, 40, 65484, 65389, 311, 40, + 65362, 65361, 65357, 65359, 65358, 65341, 65356, 65261, 65354, 65353, + 65355, 65342, 65363, 65360, 65347, 65346, 65350, 65349, 65352, 65351, + 65345, 65348, 65344, 65343, 315, 310, 309, 308, 307, 306, + 305, 304, 292, 291, 124, 94, 64, 63, 62, 60, + 59, 47, 46, 45, 43, 42, 38, 37, 65391, 123, + 65392, 65483, 93, 44, 65482, 40, 65393, 44, 65301, 41, + 65481, 65395, 91, 40, 65362, 65357, 65359, 65358, 65341, 65356, + 65347, 65346, 292, 291, 47, 46, 45, 43, 42, 37, + 65362, 65361, 65357, 65359, 65358, 65341, 65356, 65354, 65353, 65342, + 65363, 65360, 65347, 65346, 65350, 65349, 65352, 65351, 65344, 65343, + 315, 310, 307, 306, 305, 304, 292, 291, 124, 94, + 64, 62, 60, 47, 46, 45, 43, 42, 38, 37, + 65362, 65357, 65359, 65358, 65341, 65356, 47, 46, 45, 43, + 42, 37, 65362, 65361, 65357, 65359, 65358, 65341, 65356, 65354, + 65353, 65342, 65363, 65360, 65347, 65346, 65350, 65349, 65352, 65351, + 65345, 65344, 65343, 315, 310, 308, 307, 306, 305, 304, + 292, 291, 124, 94, 64, 62, 60, 47, 46, 45, + 43, 42, 38, 37, 65362, 65357, 65359, 65358, 65341, 65356, + 65354, 65353, 65342, 65347, 65346, 65352, 65351, 65344, 65343, 315, + 310, 307, 306, 292, 291, 64, 62, 60, 47, 46, + 45, 43, 42, 37, 65362, 65357, 65359, 65358, 65341, 65356, + 65342, 65347, 65346, 65344, 65343, 315, 310, 292, 291, 64, + 47, 46, 45, 43, 42, 37, 65362, 65361, 65357, 65359, + 65358, 65341, 65356, 65396, 65354, 65353, 65355, 65342, 65363, 65360, + 65347, 65346, 65350, 65349, 65352, 65351, 65345, 65348, 65344, 65343, + 315, 310, 309, 308, 307, 306, 305, 304, 292, 291, + 124, 94, 64, 63, 62, 60, 58, 47, 46, 45, + 43, 42, 38, 37, 65362, 65357, 65341, 65356, 47, 46, + 42, 37, 65362, 65361, 65357, 65359, 65358, 65341, 65356, 65354, + 65353, 65342, 65363, 65347, 65346, 65350, 65349, 65352, 65351, 65344, + 65343, 315, 310, 307, 306, 305, 304, 292, 291, 94, + 64, 62, 60, 47, 46, 45, 43, 42, 38, 37, + 65362, 65357, 65359, 65358, 65341, 65356, 65354, 65353, 65342, 65347, + 65346, 65350, 65349, 65352, 65351, 65344, 65343, 315, 310, 307, + 306, 305, 304, 292, 291, 64, 62, 60, 47, 46, + 45, 43, 42, 37, 65362, 65361, 65357, 65359, 65358, 65341, + 65356, 65354, 65353, 65342, 65347, 65346, 65350, 65349, 65352, 65351, + 65344, 65343, 315, 310, 307, 306, 305, 304, 292, 291, + 64, 62, 60, 47, 46, 45, 43, 42, 38, 37, + 65362, 65361, 65357, 65359, 65358, 65341, 65356, 65397, 65354, 65353, + 65355, 65342, 65363, 65360, 65347, 65346, 65350, 65349, 65352, 65351, + 65345, 65348, 65344, 65343, 315, 310, 309, 308, 307, 306, + 305, 304, 292, 291, 124, 94, 64, 63, 62, 60, + 59, 47, 46, 45, 43, 42, 38, 37, 65362, 65361, + 65398, 65357, 65359, 65358, 65341, 65356, 65354, 65353, 65355, 65342, + 65363, 65360, 65347, 65346, 65350, 65349, 65352, 65351, 65345, 65348, + 65344, 65343, 315, 310, 309, 308, 307, 306, 305, 304, + 292, 291, 124, 94, 64, 63, 62, 60, 47, 46, + 45, 43, 42, 41, 38, 37, 65362, 65361, 65399, 65357, + 65359, 65358, 65341, 65356, 65354, 65353, 65355, 65342, 65363, 65360, + 65347, 65346, 65350, 65349, 65352, 65351, 65345, 65348, 65344, 65343, + 315, 310, 309, 308, 307, 306, 305, 304, 292, 291, + 124, 94, 64, 63, 62, 60, 47, 46, 45, 43, + 42, 41, 38, 37, 65362, 65361, 65400, 65357, 65359, 65358, + 65341, 65356, 65354, 65353, 65355, 65342, 65363, 65360, 65347, 65346, + 65350, 65349, 65352, 65351, 65345, 65348, 65344, 65343, 315, 310, + 309, 308, 307, 306, 305, 304, 292, 291, 124, 94, + 64, 63, 62, 60, 47, 46, 45, 43, 42, 41, + 38, 37, 65362, 65361, 65401, 65357, 65359, 65358, 65341, 65356, + 65354, 65353, 65355, 65342, 65363, 65360, 65347, 65346, 65350, 65349, + 65352, 65351, 65345, 65348, 65344, 65343, 315, 310, 309, 308, + 307, 306, 305, 304, 292, 291, 124, 94, 64, 63, + 62, 60, 47, 46, 45, 43, 42, 41, 38, 37, + 65362, 65361, 65292, 65357, 65359, 65358, 65341, 65356, 65354, 65353, + 65355, 65342, 65363, 65360, 65347, 65346, 65350, 65349, 65352, 65351, + 65345, 65348, 65344, 65343, 315, 310, 309, 308, 307, 306, + 305, 304, 292, 291, 124, 94, 64, 63, 62, 60, + 47, 46, 45, 43, 42, 41, 38, 37, 65402, 284, + 65403, 284, 65267, 283, 65362, 65361, 65357, 65359, 65405, 65358, + 65341, 65356, 65263, 65354, 65353, 65355, 65342, 65363, 65360, 65347, + 65346, 65350, 65349, 65352, 65351, 65345, 65348, 65344, 65343, 315, + 310, 309, 308, 307, 306, 305, 304, 292, 291, 124, + 94, 64, 63, 62, 60, 59, 47, 46, 45, 44, + 43, 42, 38, 37, 65322, 257, 65407, 65294, 65293, 65337, + 65336, 65335, 65334, 65333, 65332, 65331, 65330, 65329, 65328, 302, + 301, 300, 299, 298, 297, 296, 295, 294, 293, 289, + 288, 61, 65393, 65300, 125, 44, 65310, 65314, 65311, 65312, + 65256, 65309, 65320, 65528, 65325, 65317, 65324, 65316, 65315, 65321, + 65319, 65318, 65287, 65529, 65533, 65532, 65289, 65285, 65286, 287, + 286, 285, 284, 283, 282, 281, 276, 274, 273, 272, + 267, 266, 264, 262, 260, 259, 126, 123, 45, 42, + 40, 33, 65412, 123, 65413, 123, 65414, 44, 65416, 41, + 65473, 40, 65417, 44, 65418, 41, 65420, 65322, 257, 125, + 65302, 41, 65303, 41, 65392, 65290, 93, 44, 65362, 65361, + 65357, 65359, 65358, 65341, 65356, 65354, 65353, 65342, 65363, 65360, + 65347, 65346, 65350, 65349, 65352, 65351, 65345, 65348, 65344, 65343, + 315, 310, 309, 308, 307, 306, 305, 304, 292, 291, + 124, 94, 64, 62, 60, 47, 46, 45, 43, 42, + 38, 37, 65362, 65361, 65357, 65359, 65358, 65341, 65356, 65421, + 65354, 65353, 65355, 65342, 65363, 65360, 65347, 65346, 65350, 65349, + 65352, 65351, 65345, 65348, 65344, 65343, 315, 310, 309, 308, + 307, 306, 305, 304, 292, 291, 124, 94, 64, 63, + 62, 60, 59, 47, 46, 45, 43, 42, 38, 37, + 65423, 261, 65424, 275, 65470, 41, 65427, 65428, 123, 58, + 65268, 283, 65469, 123, 65362, 65361, 65357, 65359, 65358, 65341, + 65356, 65264, 65354, 65353, 65355, 65342, 65363, 65360, 65347, 65346, + 65350, 65349, 65352, 65351, 65345, 65348, 65344, 65343, 315, 310, + 309, 308, 307, 306, 305, 304, 292, 291, 124, 94, + 64, 63, 62, 60, 59, 47, 46, 45, 43, 42, + 38, 37, 65253, 59, 65310, 65314, 65311, 65312, 65274, 65309, + 65320, 65528, 65325, 65317, 65324, 65316, 65315, 65321, 65319, 65318, + 65287, 65529, 65533, 65532, 65289, 65285, 65286, 287, 286, 285, + 284, 283, 282, 281, 276, 274, 273, 272, 267, 266, + 264, 262, 260, 259, 126, 125, 45, 42, 40, 33, + 65277, 125, 65276, 125, 65467, 123, 65432, 284, 65433, 65434, + 284, 273, 65436, 41, 65362, 65361, 65438, 65357, 65359, 65358, + 65341, 65356, 65354, 65353, 65355, 65342, 65363, 65360, 65347, 65346, + 65350, 65349, 65352, 65351, 65345, 65348, 65344, 65343, 315, 310, + 309, 308, 307, 306, 305, 304, 292, 291, 124, 94, + 64, 63, 62, 60, 47, 46, 45, 43, 42, 41, + 38, 37, 65466, 65439, 277, 58, 65320, 65433, 65434, 284, + 273, 259, 65442, 123, 65443, 61, 65445, 65444, 91, 61, + 65446, 65433, 65434, 284, 273, 125, 65463, 123, 65310, 65314, + 65311, 65312, 65265, 65309, 65320, 65528, 65325, 65317, 65324, 65316, + 65315, 65321, 65319, 65318, 65287, 65529, 65533, 65532, 65289, 65285, + 65286, 287, 286, 285, 284, 283, 282, 281, 276, 274, + 273, 272, 267, 266, 264, 262, 260, 259, 126, 125, + 45, 42, 40, 33, 65272, 59, 65320, 259, 65271, 125, + 65269, 59, 65310, 65314, 65311, 65312, 65309, 65320, 65528, 65325, + 65317, 65324, 65316, 65453, 65315, 65321, 65319, 65424, 65318, 65287, + 65529, 65533, 65532, 65289, 65285, 65286, 287, 286, 285, 284, + 283, 282, 281, 276, 275, 274, 273, 272, 271, 267, + 266, 264, 262, 260, 259, 126, 45, 42, 40, 33, + 65273, 59, 65454, 65433, 65434, 284, 273, 125, 65362, 65361, + 65357, 65359, 65358, 65341, 65356, 65307, 65354, 65353, 65355, 65342, + 65363, 65360, 65347, 65346, 65350, 65349, 65352, 65351, 65345, 65348, + 65344, 65343, 315, 310, 309, 308, 307, 306, 305, 304, + 292, 291, 124, 94, 64, 63, 62, 60, 59, 47, + 46, 45, 43, 42, 38, 37, 65392, 65455, 93, 44, + 65362, 65361, 65357, 65359, 65358, 65341, 65356, 65306, 65354, 65353, + 65355, 65342, 65363, 65360, 65347, 65346, 65350, 65349, 65352, 65351, + 65345, 65348, 65344, 65343, 315, 310, 309, 308, 307, 306, + 305, 304, 292, 291, 124, 94, 64, 63, 62, 60, + 59, 47, 46, 45, 43, 42, 38, 37, 65310, 65314, + 65311, 65312, 65266, 65309, 65320, 65528, 65325, 65317, 65324, 65316, + 65315, 65321, 65319, 65318, 65287, 65529, 65533, 65532, 65289, 65285, + 65286, 287, 286, 285, 284, 283, 282, 281, 276, 274, + 273, 272, 267, 266, 264, 262, 260, 259, 126, 125, + 45, 42, 40, 33, 65459, 58, 65270, 59, 65456, 61, + 65310, 65314, 65311, 65312, 65309, 65320, 65528, 65325, 65317, 65324, + 65316, 65315, 65321, 65319, 65318, 65287, 65529, 65533, 65532, 65289, + 65285, 65286, 287, 286, 285, 284, 283, 282, 281, 276, + 274, 273, 272, 267, 266, 264, 262, 260, 259, 126, + 45, 42, 40, 33, 65362, 65361, 65357, 65359, 65358, 65341, + 65356, 65308, 65354, 65353, 65355, 65342, 65363, 65360, 65347, 65346, + 65350, 65349, 65352, 65351, 65345, 65348, 65344, 65343, 315, 310, + 309, 308, 307, 306, 305, 304, 292, 291, 124, 94, + 64, 63, 62, 60, 59, 47, 46, 45, 43, 42, + 38, 37, -1 +}; +static short yypact[] = { + + 24, 48, 62, 78, 93, 180, 199, 235, 120, 271, + 271, 271, 271, 157, 157, 157, 157, 157, 157, 157, + 157, 157, 157, 157, 157, 387, 157, 157, 392, 402, + 402, 402, 430, 456, 456, 483, 519, 519, 545, 545, + 545, 545, 271, 271, 608, 608, 631, 667, 271, 702, + 120, 1013, 1079, 120, 120, 157, 1147, 1153, 157, 157, + 1190, 1261, 1013, 157, 387, 1375, 1384, 157, 1439, 1504, + 1507, 1439, 1512, 157, 1536, 1507, 1720, 1742, 1788, 120, + 1719, 1717, 1715, 1691, 1644, 1618, 1592, 1565, 1561, 1511, + 120, 120, 120, 1380, 1509, 1505, 120, 1119, 1481, 1457, + 1453, 1448, 1445, 1443, 1434, 1408, 1383, 1380, 1377, 1373, + 1371, 120, 1119, 1347, 120, 1323, 1298, 1273, 1271, 1268, + 1265, 120, 1263, 1263, 1236, 1166, 1163, 1161, 120, 1158, + 120, 1155, 1151, 1149, 1145, 1143, 1119, 1119, 120, 120, + 120, 1094, 120, 120, 1065, 1039, 1011, 1009, 984, 936, + 888, 840, 792, 744, 580, 120, 389, 385, 382, 379, + 354, 120, 328, 325, 323, 120, 120, 120, 120, 120, + 120, 298, 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 273, 255, 239, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, 120, 237, + 233, 217, 201, 197, 195, 192, 189, 187, 185, 183, + 181, 120, 157, 120, 120, 120, 120 +}; +static short yygo[] = { + + 65278, 65426, 65425, 122, 74, 65243, 65249, 65535, 65252, 65255, + 65254, 65251, 144, 129, 65406, 65437, 65461, 65452, 65458, 65422, + 76, 72, 69, 66, 65250, 65275, 65275, 65275, 65275, 65257, + 137, 136, 112, 97, 0, 65471, 65394, 65510, 155, 121, + 65408, 65409, 65379, 54, 53, 65450, 65410, 65377, 140, 91, + 65295, 65479, 65415, 65530, 65530, 65530, 65530, 65530, 65530, 65530, + 65530, 65530, 65530, 65282, 137, 136, 113, 112, 98, 97, + 83, 77, 74, 0, 65431, 65371, 65247, 65246, 65245, 65281, + 65279, 65474, 65280, 136, 112, 97, 65244, 65440, 65447, 65440, + 65447, 65296, 75, 71, 70, 68, 65460, 65465, 71, 65441, + 65509, 65480, 65509, 65509, 65457, 65451, 65511, 65449, 65462, 65468, + 65430, 65509, 65472, 65419, 65411, 65475, 65511, 65476, 65477, 65509, + 65390, 65387, 65386, 65385, 65384, 65383, 65382, 65486, 65487, 65488, + 65489, 65490, 65491, 65492, 65493, 65381, 65494, 65495, 65496, 65497, + 65498, 65499, 65500, 65501, 65502, 65503, 65504, 65505, 65506, 65508, + 65511, 65512, 65513, 65514, 65515, 65516, 65517, 65518, 65519, 65520, + 65521, 65522, 65375, 65364, 65523, 65524, 65525, 65526, 65313, 226, + 225, 224, 223, 221, 211, 208, 207, 206, 205, 204, + 203, 202, 201, 200, 199, 198, 197, 195, 193, 192, + 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, + 181, 180, 179, 178, 177, 176, 175, 174, 173, 172, + 170, 169, 168, 167, 166, 165, 161, 155, 143, 142, + 140, 139, 138, 130, 128, 121, 114, 111, 96, 92, + 91, 90, 79, 54, 53, 50, 8, 65464, 65448, 65435, + 93, 68, 65305, 65305, 65305, 65304, 100, 87, 71, 65531, + 65323, 65478, 65429, 65404, 62, 65299, 65298, 65297, 52, 4, + 65534, -1 +}; +static short yypgo[] = { + + 0, 0, 0, 260, 253, 253, 91, 51, 51, 52, + 52, 99, 99, 99, 99, 2, 2, 74, 74, 76, + 250, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 249, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 63, 63, 63, 42, 42, 37, 37, 47, + 47, 245, 245, 245, 239, 239, 50, 50, 50, 63, + 63, 63, 63, 63, 63, 257, 257, 75, 75, 249, + 168, 168, 168, 168, 168, 168, 168, 168, 78, 77, + 76, 2, 5, 5, 82, 82, 97, 97, 91, 86, + 86, 251, 251, 11, 11, 29, 29, 29, 29, 29, + 29, 29, 19, 19, 14, 14, 8, 6, 6, 6, + 7, 7, 29, 29, 29, 29, 29, 0 +}; +static short yyrlen[] = { + + 0, 0, 0, 1, 0, 1, 6, 0, 1, 0, + 2, 0, 1, 1, 2, 4, 7, 1, 3, 5, + 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, + 2, 5, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 2, 2, 1, 1, 1, + 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 3, 6, 3, 0, 1, 1, 3, 1, + 3, 7, 4, 4, 2, 1, 6, 6, 4, 5, + 2, 5, 2, 1, 1, 1, 1, 3, 1, 6, + 1, 1, 1, 1, 1, 1, 3, 1, 9, 5, + 7, 5, 7, 7, 1, 3, 3, 2, 9, 11, + 9, 3, 1, 10, 8, 6, 4, 2, 3, 2, + 2, 2, 2, 0, 2, 1, 6, 1, 1, 1, + 2, 0, 1, 1, 1, 1, 1, 2 +}; +#define YYS0 287 +#define YYDELTA 156 +#define YYNPACT 227 +#define YYNDEF 78 + +#define YYr135 0 +#define YYr136 1 +#define YYr137 2 +#define YYr1 3 +#define YYr26 4 +#define YYr27 5 +#define YYr33 6 +#define YYr34 7 +#define YYr35 8 +#define YYr36 9 +#define YYr37 10 +#define YYr38 11 +#define YYr39 12 +#define YYr40 13 +#define YYr41 14 +#define YYr48 15 +#define YYr49 16 +#define YYr51 17 +#define YYr52 18 +#define YYr53 19 +#define YYr57 20 +#define YYr60 21 +#define YYr61 22 +#define YYr62 23 +#define YYr63 24 +#define YYr64 25 +#define YYr65 26 +#define YYr66 27 +#define YYr67 28 +#define YYr68 29 +#define YYr69 30 +#define YYr71 31 +#define YYr72 32 +#define YYr73 33 +#define YYr74 34 +#define YYr75 35 +#define YYr76 36 +#define YYr77 37 +#define YYr78 38 +#define YYr79 39 +#define YYr80 40 +#define YYr81 41 +#define YYr82 42 +#define YYr83 43 +#define YYr84 44 +#define YYr85 45 +#define YYr86 46 +#define YYr91 47 +#define YYr92 48 +#define YYr94 49 +#define YYr95 50 +#define YYr96 51 +#define YYr102 52 +#define YYr103 53 +#define YYr104 54 +#define YYr105 55 +#define YYr106 56 +#define YYr107 57 +#define YYr108 58 +#define YYr109 59 +#define YYr110 60 +#define YYr111 61 +#define YYr114 62 +#define YYr115 63 +#define YYr119 64 +#define YYr124 65 +#define YYr125 66 +#define YYr126 67 +#define YYr127 68 +#define YYr133 69 +#define YYr134 70 +#define YYr132 71 +#define YYr131 72 +#define YYr130 73 +#define YYr129 74 +#define YYr128 75 +#define YYr123 76 +#define YYr122 77 +#define YYr121 78 +#define YYr120 79 +#define YYr118 80 +#define YYr117 81 +#define YYr116 82 +#define YYr113 83 +#define YYr112 84 +#define YYr101 85 +#define YYr100 86 +#define YYr99 87 +#define YYr98 88 +#define YYr97 89 +#define YYr93 90 +#define YYr90 91 +#define YYr89 92 +#define YYr88 93 +#define YYr87 94 +#define YYr70 95 +#define YYr59 96 +#define YYr58 97 +#define YYr56 98 +#define YYr55 99 +#define YYr54 100 +#define YYr50 101 +#define YYr47 102 +#define YYr46 103 +#define YYr45 104 +#define YYr44 105 +#define YYr43 106 +#define YYr42 107 +#define YYr32 108 +#define YYr31 109 +#define YYr30 110 +#define YYr29 111 +#define YYr28 112 +#define YYr25 113 +#define YYr24 114 +#define YYr23 115 +#define YYr22 116 +#define YYr21 117 +#define YYr20 118 +#define YYr19 119 +#define YYr18 120 +#define YYr17 121 +#define YYr11 122 +#define YYr10 123 +#define YYr9 124 +#define YYr8 125 +#define YYr7 126 +#define YYr6 127 +#define YYr5 128 +#define YYr4 129 +#define YYr3 130 +#define YYr2 131 +#define YYrACCEPT YYr135 +#define YYrERROR YYr136 +#define YYrLR2 YYr137 +#if YYDEBUG +char * yysvar[] = { + "$accept", + "case_block", + "switch_stmt", + "decl", + "decl_list", + "package_decl", + "fn_decl_stmt", + "fn_decl_list", + "statement_list", + "stmt", + "expr_list", + "expr_list_decl", + "aidx_expr", + "funcall_expr", + "object_name", + "object_args", + "stmt_expr", + "case_expr", + "class_name_expr", + "if_stmt", + "while_stmt", + "for_stmt", + "stmt_block", + "datablock_decl", + "object_decl", + "object_decl_list", + "object_declare_block", + "expr", + "slot_assign_list", + "slot_assign", + "slot_acc", + "expression_stmt", + "var_list", + "var_list_decl", + "assign_op_struct", + "start", + 0 +}; +short yyrmap[] = { + + 135, 136, 137, 1, 26, 27, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 48, 49, 51, 52, 53, + 57, 60, 61, 62, 63, 64, 65, 66, 67, 68, + 69, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 91, 92, 94, + 95, 96, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 114, 115, 119, 124, 125, 126, 127, 133, + 134, 132, 131, 130, 129, 128, 123, 122, 121, 120, + 118, 117, 116, 113, 112, 101, 100, 99, 98, 97, + 93, 90, 89, 88, 87, 70, 59, 58, 56, 55, + 54, 50, 47, 46, 45, 44, 43, 42, 32, 31, + 30, 29, 28, 25, 24, 23, 22, 21, 20, 19, + 18, 17, 11, 10, 9, 8, 7, 6, 5, 4, + 3, 2, 12, 13, 14, 15, 16, 0 +}; +short yysmap[] = { + + 1, 2, 5, 7, 8, 18, 27, 31, 58, 63, + 65, 66, 67, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 121, 123, 125, 127, 128, 129, + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, + 140, 141, 143, 144, 145, 146, 147, 148, 149, 150, + 158, 161, 166, 167, 171, 180, 181, 185, 191, 195, + 199, 204, 210, 216, 226, 227, 232, 239, 243, 253, + 255, 256, 264, 267, 268, 271, 287, 290, 291, 289, + 284, 282, 280, 278, 276, 275, 274, 273, 270, 263, + 261, 260, 259, 258, 257, 254, 252, 251, 250, 249, + 248, 246, 245, 244, 240, 235, 230, 229, 228, 225, + 224, 223, 222, 221, 220, 215, 213, 212, 211, 209, + 208, 207, 206, 205, 200, 198, 197, 194, 193, 190, + 187, 186, 183, 182, 178, 177, 176, 175, 174, 173, + 172, 170, 168, 165, 164, 162, 160, 159, 157, 156, + 155, 154, 153, 152, 142, 126, 124, 122, 120, 108, + 104, 102, 101, 100, 99, 97, 96, 95, 94, 93, + 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, + 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, + 72, 71, 70, 69, 68, 59, 57, 56, 53, 52, + 51, 50, 49, 48, 47, 46, 45, 44, 43, 37, + 30, 29, 28, 26, 25, 24, 23, 22, 21, 20, + 19, 17, 16, 15, 14, 13, 12, 292, 283, 285, + 262, 247, 218, 217, 169, 196, 60, 192, 42, 3, + 4, 54, 55, 179, 98, 219, 6, 61, 9, 10, + 11, 62, 151, 64, 266, 201, 238, 279, 241, 242, + 202, 236, 281, 269, 272, 288, 277, 231, 184, 286, + 265, 233, 188, 103, 163, 105, 106, 107, 237, 203, + 214, 189, 234, 38, 39, 40, 41, 0, 36, 35, + 34, 33, 32 +}; +int yyntoken = 87; +int yynvar = 36; +int yynstate = 293; +int yynrule = 138; +#endif + +#if YYDEBUG +/* + * Package up YACC context for tracing + */ +typedef struct yyTraceItems_tag { + int state, lookahead, errflag, done; + int rule, npop; + short * states; + int nstates; + YYSTYPE * values; + int nvalues; + short * types; +} yyTraceItems; +#endif + +#line 2 "console/yyparse.c" + +/* + * Copyright 1985, 1990 by Mortice Kern Systems Inc. All rights reserved. + * + * Automaton to interpret LALR(1) tables. + * + * Macros: + * yyclearin - clear the lookahead token. + * yyerrok - forgive a pending error + * YYERROR - simulate an error + * YYACCEPT - halt and return 0 + * YYABORT - halt and return 1 + * YYRETURN(value) - halt and return value. You should use this + * instead of return(value). + * YYREAD - ensure CMDchar contains a lookahead token by reading + * one if it does not. See also YYSYNC. + * YYRECOVERING - 1 if syntax error detected and not recovered + * yet; otherwise, 0. + * + * Preprocessor flags: + * YYDEBUG - includes debug code if 1. The parser will print + * a travelogue of the parse if this is defined as 1 + * and CMDdebug is non-zero. + * yacc -t sets YYDEBUG to 1, but not CMDdebug. + * YYTRACE - turn on YYDEBUG, and undefine default trace functions + * so that the interactive functions in 'ytrack.c' will + * be used. + * YYSSIZE - size of state and value stacks (default 150). + * YYSTATIC - By default, the state stack is an automatic array. + * If this is defined, the stack will be static. + * In either case, the value stack is static. + * YYALLOC - Dynamically allocate both the state and value stacks + * by calling malloc() and free(). + * YYDYNAMIC - Dynamically allocate (and reallocate, if necessary) + * both the state and value stacks by calling malloc(), + * realloc(), and free(). + * YYSYNC - if defined, yacc guarantees to fetch a lookahead token + * before any action, even if it doesnt need it for a decision. + * If YYSYNC is defined, YYREAD will never be necessary unless + * the user explicitly sets CMDchar = -1 + * + * Copyright (c) 1983, by the University of Waterloo + */ +/* + * Prototypes + */ + +extern int CMDlex YY_ARGS((void)); +extern void CMDerror YY_ARGS((char *, ...)); + +#if YYDEBUG + +#include /* common prototypes */ +#include + +extern char * yyValue YY_ARGS((YYSTYPE, int)); /* print CMDlval */ +extern void yyShowState YY_ARGS((yyTraceItems *)); +extern void yyShowReduce YY_ARGS((yyTraceItems *)); +extern void yyShowGoto YY_ARGS((yyTraceItems *)); +extern void yyShowShift YY_ARGS((yyTraceItems *)); +extern void yyShowErrRecovery YY_ARGS((yyTraceItems *)); +extern void yyShowErrDiscard YY_ARGS((yyTraceItems *)); + +extern void yyShowRead YY_ARGS((int)); +#endif + +/* + * If YYDEBUG defined and CMDdebug set, + * tracing functions will be called at appropriate times in CMDparse() + * Pass state of YACC parse, as filled into yyTraceItems yyx + * If yyx.done is set by the tracing function, CMDparse() will terminate + * with a return value of -1 + */ +#define YY_TRACE(fn) { \ + yyx.state = yystate; yyx.lookahead = CMDchar; yyx.errflag =yyerrflag; \ + yyx.states = yys+1; yyx.nstates = yyps-yys; \ + yyx.values = yyv+1; yyx.nvalues = yypv-yyv; \ + yyx.types = yytypev+1; yyx.done = 0; \ + yyx.rule = yyi; yyx.npop = yyj; \ + fn(&yyx); \ + if (yyx.done) YYRETURN(-1); } + +#ifndef I18N +#define m_textmsg(id, str, cls) (str) +#else /*I18N*/ +#include +#endif/*I18N*/ + +#ifndef YYSSIZE +# define YYSSIZE 150 +#endif + +#ifdef YYDYNAMIC +#define YYALLOC +char *getenv(); +int atoi(); +int yysinc = -1; /* stack size increment, <0 = double, 0 = none, >0 = fixed */ +#endif + +#ifdef YYALLOC +int yyssize = YYSSIZE; +#endif + +#define YYERROR goto yyerrlabel +#define yyerrok yyerrflag = 0 +#if YYDEBUG +#define yyclearin { if (CMDdebug) yyShowRead(-1); CMDchar = -1; } +#else +#define yyclearin CMDchar = -1 +#endif +#define YYACCEPT YYRETURN(0) +#define YYABORT YYRETURN(1) +#define YYRECOVERING() (yyerrflag != 0) +#ifdef YYALLOC +#define YYRETURN(val) { retval = (val); goto yyReturn; } +#else +#define YYRETURN(val) return(val); +#endif +#if YYDEBUG +/* The if..else makes this macro behave exactly like a statement */ +# define YYREAD if (CMDchar < 0) { \ + if ((CMDchar = CMDlex()) < 0) { \ + if (CMDchar == -2) YYABORT; \ + CMDchar = 0; \ + } /* endif */ \ + if (CMDdebug) \ + yyShowRead(CMDchar); \ + } else +#else +# define YYREAD if (CMDchar < 0) { \ + if ((CMDchar = CMDlex()) < 0) { \ + if (CMDchar == -2) YYABORT; \ + CMDchar = 0; \ + } /* endif */ \ + } else +#endif + +#define YYERRCODE 1591738624 /* value of `error' */ +#define YYTOKEN_BASE 256 +#define YYQYYP yyq[yyq-yyp] + +/* + * Simulate bitwise negation as if was done on a two's complement machine. + * This makes the generated code portable to machines with different + * representations of integers (ie. signed magnitude). + */ +#define yyneg(s) (-((s)+1)) + +YYSTYPE yyval; /* $ */ +YYSTYPE *yypvt; /* $n */ +YYSTYPE CMDlval; /* CMDlex() sets this */ + +int CMDchar, /* current token */ + yyerrflag, /* error flag */ + yynerrs; /* error count */ + +#if YYDEBUG +int CMDdebug = 0; /* debug if this flag is set */ +extern char *yysvar[]; /* table of non-terminals (aka 'variables') */ +extern yyNamedType yyTokenTypes[]; /* table of terminals & their types */ +extern short yyrmap[], yysmap[]; /* map internal rule/states */ +extern int yynstate, yynvar, yyntoken, yynrule; + +extern int yyGetType YY_ARGS((int)); /* token type */ +extern char *yyptok YY_ARGS((int)); /* printable token string */ +extern int yyExpandName YY_ARGS((int, int, char *, int)); + /* expand yyRules[] or yyStates[] */ +static char * yygetState YY_ARGS((int)); + +#define yyassert(condition, msg, arg) \ + if (!(condition)) { \ + printf(m_textmsg(2824, "\nyacc bug: ", "E")); \ + printf(msg, arg); \ + YYABORT; } +#else /* !YYDEBUG */ +#define yyassert(condition, msg, arg) +#endif + + + +#ifdef YACC_WINDOWS + +/* + * the following is the CMDparse() function that will be + * callable by a windows type program. It in turn will + * load all needed resources, obtain pointers to these + * resources, and call a statically defined function + * win_yyparse(), which is the original CMDparse() fn + * When win_yyparse() is complete, it will return a + * value to the new CMDparse(), where it will be stored + * away temporarily, all resources will be freed, and + * that return value will be given back to the caller + * CMDparse(), as expected. + */ + +static int win_yyparse(); /* prototype */ + +int CMDparse() +{ + int wReturnValue; + HANDLE hRes_table; /* handle of resource after loading */ + short far *old_yydef; /* the following are used for saving */ + short far *old_yyex; /* the current pointers */ + short far *old_yyact; + short far *old_yypact; + short far *old_yygo; + short far *old_yypgo; + short far *old_yyrlen; + + /* + * the following code will load the required + * resources for a Windows based parser. + */ + + hRes_table = LoadResource (hInst, + FindResource (hInst, "UD_RES_yyYACC", "yyYACCTBL")); + + /* + * return an error code if any + * of the resources did not load + */ + + if (hRes_table == NULL) + return (1); + + /* + * the following code will lock the resources + * into fixed memory locations for the parser + * (also, save the current pointer values first) + */ + + old_yydef = yydef; + old_yyex = yyex; + old_yyact = yyact; + old_yypact = yypact; + old_yygo = yygo; + old_yypgo = yypgo; + old_yyrlen = yyrlen; + + yydef = (short far *)LockResource (hRes_table); + yyex = (short far *)(yydef + Sizeof_yydef); + yyact = (short far *)(yyex + Sizeof_yyex); + yypact = (short far *)(yyact + Sizeof_yyact); + yygo = (short far *)(yypact + Sizeof_yypact); + yypgo = (short far *)(yygo + Sizeof_yygo); + yyrlen = (short far *)(yypgo + Sizeof_yypgo); + + /* + * call the official CMDparse() function + */ + + wReturnValue = win_yyparse(); + + /* + * unlock the resources + */ + + UnlockResource (hRes_table); + + /* + * and now free the resource + */ + + FreeResource (hRes_table); + + /* + * restore previous pointer values + */ + + yydef = old_yydef; + yyex = old_yyex; + yyact = old_yyact; + yypact = old_yypact; + yygo = old_yygo; + yypgo = old_yypgo; + yyrlen = old_yyrlen; + + return (wReturnValue); +} /* end CMDparse */ + +static int win_yyparse() + +#else /* YACC_WINDOWS */ + +/* + * we are not compiling a windows resource + * based parser, so call CMDparse() the old + * standard way. + */ + +int CMDparse() + +#endif /* YACC_WINDOWS */ + +{ +#ifdef YACC_WINDOWS + register short far *yyp; /* for table lookup */ + register short far *yyq; +#else + register short *yyp; /* for table lookup */ + register short *yyq; +#endif /* YACC_WINDOWS */ + register short yyi; + register short *yyps; /* top of state stack */ + register short yystate; /* current state */ + register YYSTYPE *yypv; /* top of value stack */ + register int yyj; +#if YYDEBUG + yyTraceItems yyx; /* trace block */ + short * yytp; + int yyruletype = 0; +#endif +#ifdef YYSTATIC + static short yys[YYSSIZE + 1]; + static YYSTYPE yyv[YYSSIZE + 1]; +#if YYDEBUG + static short yytypev[YYSSIZE+1]; /* type assignments */ +#endif +#else /* ! YYSTATIC */ +#ifdef YYALLOC + YYSTYPE *yyv; + short *yys; +#if YYDEBUG + short *yytypev; +#endif + YYSTYPE save_yylval; + YYSTYPE save_yyval; + YYSTYPE *save_yypvt; + int save_yychar, save_yyerrflag, save_yynerrs; + int retval; /* return value holder */ +#else + short yys[YYSSIZE + 1]; + static YYSTYPE yyv[YYSSIZE + 1]; /* historically static */ +#if YYDEBUG + short yytypev[YYSSIZE+1]; /* mirror type table */ +#endif +#endif /* ! YYALLOC */ +#endif /* ! YYSTATIC */ +#ifdef YYDYNAMIC + char *envp; +#endif + + +#ifdef YYDYNAMIC + if ((envp = getenv("YYSTACKSIZE")) != (char *)0) { + yyssize = atoi(envp); + if (yyssize <= 0) + yyssize = YYSSIZE; + } + if ((envp = getenv("YYSTACKINC")) != (char *)0) + yysinc = atoi(envp); +#endif +#ifdef YYALLOC + yys = (short *) malloc((yyssize + 1) * sizeof(short)); + yyv = (YYSTYPE *) malloc((yyssize + 1) * sizeof(YYSTYPE)); +#if YYDEBUG + yytypev = (short *) malloc((yyssize + 1) * sizeof(short)); +#endif + if (yys == (short *)0 || yyv == (YYSTYPE *)0 +#if YYDEBUG + || yytypev == (short *) 0 +#endif + ) { + CMDerror(m_textmsg(4967, "Not enough space for parser stacks", + "E")); + return 1; + } + save_yylval = CMDlval; + save_yyval = yyval; + save_yypvt = yypvt; + save_yychar = CMDchar; + save_yyerrflag = yyerrflag; + save_yynerrs = yynerrs; +#endif + + yynerrs = 0; + yyerrflag = 0; + yyclearin; + yyps = yys; + yypv = yyv; + *yyps = yystate = YYS0; /* start state */ +#if YYDEBUG + yytp = yytypev; + yyi = yyj = 0; /* silence compiler warnings */ +#endif + +yyStack: + yyassert((unsigned)yystate < yynstate, m_textmsg(587, "state %d\n", ""), yystate); +#ifdef YYDYNAMIC + if (++yyps > &yys[yyssize]) { + int yynewsize; + int yysindex = yyps - yys; + int yyvindex = yypv - yyv; +#if YYDEBUG + int yytindex = yytp - yytypev; +#endif + if (yysinc == 0) { /* no increment */ + CMDerror(m_textmsg(4968, "Parser stack overflow", "E")); + YYABORT; + } else if (yysinc < 0) /* binary-exponential */ + yynewsize = yyssize * 2; + else /* fixed increment */ + yynewsize = yyssize + yysinc; + if (yynewsize < yyssize) { + CMDerror(m_textmsg(4967, + "Not enough space for parser stacks", + "E")); + YYABORT; + } + yyssize = yynewsize; + yys = (short *) realloc(yys, (yyssize + 1) * sizeof(short)); + yyps = yys + yysindex; + yyv = (YYSTYPE *) realloc(yyv, (yyssize + 1) * sizeof(YYSTYPE)); + yypv = yyv + yyvindex; +#if YYDEBUG + yytypev = (short *)realloc(yytypev,(yyssize + 1)*sizeof(short)); + yytp = yytypev + yytindex; +#endif + if (yys == (short *)0 || yyv == (YYSTYPE *)0 +#if YYDEBUG + || yytypev == (short *) 0 +#endif + ) { + CMDerror(m_textmsg(4967, + "Not enough space for parser stacks", + "E")); + YYABORT; + } + } +#else + if (++yyps > &yys[YYSSIZE]) { + CMDerror(m_textmsg(4968, "Parser stack overflow", "E")); + YYABORT; + } +#endif /* !YYDYNAMIC */ + *yyps = yystate; /* stack current state */ + *++yypv = yyval; /* ... and value */ +#if YYDEBUG + *++yytp = yyruletype; /* ... and type */ + + if (CMDdebug) + YY_TRACE(yyShowState) +#endif + + /* + * Look up next action in action table. + */ +yyEncore: +#ifdef YYSYNC + YYREAD; +#endif + +#ifdef YACC_WINDOWS + if (yystate >= Sizeof_yypact) /* simple state */ +#else /* YACC_WINDOWS */ + if (yystate >= sizeof yypact/sizeof yypact[0]) /* simple state */ +#endif /* YACC_WINDOWS */ + yyi = yystate - YYDELTA; /* reduce in any case */ + else { + if(*(yyp = &yyact[yypact[yystate]]) >= 0) { + /* Look for a shift on CMDchar */ +#ifndef YYSYNC + YYREAD; +#endif + yyq = yyp; + yyi = CMDchar; + while (yyi < *yyp++) + ; + if (yyi == yyp[-1]) { + yystate = yyneg(YYQYYP); +#if YYDEBUG + if (CMDdebug) { + yyruletype = yyGetType(CMDchar); + YY_TRACE(yyShowShift) + } +#endif + yyval = CMDlval; /* stack what CMDlex() set */ + yyclearin; /* clear token */ + if (yyerrflag) + yyerrflag--; /* successful shift */ + goto yyStack; + } + } + + /* + * Fell through - take default action + */ + +#ifdef YACC_WINDOWS + if (yystate >= Sizeof_yydef) +#else /* YACC_WINDOWS */ + if (yystate >= sizeof yydef /sizeof yydef[0]) +#endif /* YACC_WINDOWS */ + goto yyError; + if ((yyi = yydef[yystate]) < 0) { /* default == reduce? */ + /* Search exception table */ +#ifdef YACC_WINDOWS + yyassert((unsigned)yyneg(yyi) < Sizeof_yyex, + m_textmsg(2825, "exception %d\n", "I num"), yystate); +#else /* YACC_WINDOWS */ + yyassert((unsigned)yyneg(yyi) < sizeof yyex/sizeof yyex[0], + m_textmsg(2825, "exception %d\n", "I num"), yystate); +#endif /* YACC_WINDOWS */ + yyp = &yyex[yyneg(yyi)]; +#ifndef YYSYNC + YYREAD; +#endif + while((yyi = *yyp) >= 0 && yyi != CMDchar) + yyp += 2; + yyi = yyp[1]; + yyassert(yyi >= 0, + m_textmsg(2826, "Ex table not reduce %d\n", "I num"), yyi); + } + } + + yyassert((unsigned)yyi < yynrule, m_textmsg(2827, "reduce %d\n", "I num"), yyi); + yyj = yyrlen[yyi]; +#if YYDEBUG + if (CMDdebug) + YY_TRACE(yyShowReduce) + yytp -= yyj; +#endif + yyps -= yyj; /* pop stacks */ + yypvt = yypv; /* save top */ + yypv -= yyj; + yyval = yypv[1]; /* default action $ = $1 */ +#if YYDEBUG + yyruletype = yyRules[yyrmap[yyi]].type; +#endif + + switch (yyi) { /* perform semantic action */ + +case YYr1: { /* start : decl_list */ +#line 127 "console/gram.y" + +} break; + +case YYr2: { /* decl_list : */ +#line 132 "console/gram.y" + yyval.stmt = nil; +} break; + +case YYr3: { /* decl_list : decl_list decl */ +#line 134 "console/gram.y" + if(!statementList) { statementList = yypvt[0].stmt; } else { statementList->append(yypvt[0].stmt); } +} break; + +case YYr4: { /* decl : stmt */ +#line 139 "console/gram.y" + yyval.stmt = yypvt[0].stmt; +} break; + +case YYr5: { /* decl : fn_decl_stmt */ +#line 141 "console/gram.y" + yyval.stmt = yypvt[0].stmt; +} break; + +case YYr6: { /* decl : package_decl */ +#line 143 "console/gram.y" + yyval.stmt = yypvt[0].stmt; +} break; + +case YYr7: { /* package_decl : rwPACKAGE IDENT '{' fn_decl_list '}' ';' */ +#line 148 "console/gram.y" + yyval.stmt = yypvt[-2].stmt; for(StmtNode *walk = (yypvt[-2].stmt);walk;walk = walk->getNext() ) walk->setPackage(yypvt[-4].s); +} break; + +case YYr8: { /* fn_decl_list : fn_decl_stmt */ +#line 153 "console/gram.y" + yyval.stmt = yypvt[0].stmt; +} break; + +case YYr9: { /* fn_decl_list : fn_decl_list fn_decl_stmt */ +#line 155 "console/gram.y" + yyval.stmt = yypvt[-1].stmt; (yypvt[-1].stmt)->append(yypvt[0].stmt); +} break; + +case YYr10: { /* statement_list : */ +#line 160 "console/gram.y" + yyval.stmt = nil; +} break; + +case YYr11: { /* statement_list : statement_list stmt */ +#line 162 "console/gram.y" + if(!yypvt[-1].stmt) { yyval.stmt = yypvt[0].stmt; } else { (yypvt[-1].stmt)->append(yypvt[0].stmt); yyval.stmt = yypvt[-1].stmt; } +} break; + +case YYr17: { /* stmt : rwBREAK ';' */ +#line 172 "console/gram.y" + yyval.stmt = BreakStmtNode::alloc(); +} break; + +case YYr18: { /* stmt : rwCONTINUE ';' */ +#line 174 "console/gram.y" + yyval.stmt = ContinueStmtNode::alloc(); +} break; + +case YYr19: { /* stmt : rwRETURN ';' */ +#line 176 "console/gram.y" + yyval.stmt = ReturnStmtNode::alloc(NULL); +} break; + +case YYr20: { /* stmt : rwRETURN expr ';' */ +#line 178 "console/gram.y" + yyval.stmt = ReturnStmtNode::alloc(yypvt[-1].expr); +} break; + +case YYr21: { /* stmt : expression_stmt ';' */ +#line 180 "console/gram.y" + yyval.stmt = yypvt[-1].stmt; +} break; + +case YYr22: { /* stmt : TTAG '=' expr ';' */ +#line 182 "console/gram.y" + yyval.stmt = TTagSetStmtNode::alloc(yypvt[-3].s, yypvt[-1].expr, NULL); +} break; + +case YYr23: { /* stmt : TTAG '=' expr ',' expr ';' */ +#line 184 "console/gram.y" + yyval.stmt = TTagSetStmtNode::alloc(yypvt[-5].s, yypvt[-3].expr, yypvt[-1].expr); +} break; + +case YYr24: { /* fn_decl_stmt : rwDEFINE IDENT '(' var_list_decl ')' '{' statement_list '}' */ +#line 189 "console/gram.y" + yyval.stmt = FunctionDeclStmtNode::alloc(yypvt[-6].s, NULL, yypvt[-4].var, yypvt[-1].stmt); +} break; + +case YYr25: { /* fn_decl_stmt : rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' '{' statement_list '}' */ +#line 191 "console/gram.y" + yyval.stmt = FunctionDeclStmtNode::alloc(yypvt[-6].s, yypvt[-8].s, yypvt[-4].var, yypvt[-1].stmt); +} break; + +case YYr26: { /* var_list_decl : */ +#line 196 "console/gram.y" + yyval.var = NULL; +} break; + +case YYr27: { /* var_list_decl : var_list */ +#line 198 "console/gram.y" + yyval.var = yypvt[0].var; +} break; + +case YYr28: { /* var_list : VAR */ +#line 203 "console/gram.y" + yyval.var = VarNode::alloc(yypvt[0].s, NULL); +} break; + +case YYr29: { /* var_list : var_list ',' VAR */ +#line 205 "console/gram.y" + yyval.var = yypvt[-2].var; ((StmtNode*)(yypvt[-2].var))->append((StmtNode*)VarNode::alloc(yypvt[0].s, NULL)); +} break; + +case YYr30: { /* datablock_decl : rwDATABLOCK IDENT '(' IDENT ')' '{' slot_assign_list '}' ';' */ +#line 210 "console/gram.y" + yyval.stmt = ObjectDeclNode::alloc(ConstantNode::alloc(yypvt[-7].s), ConstantNode::alloc(yypvt[-5].s), NULL, NULL, yypvt[-2].slist, NULL, true); +} break; + +case YYr31: { /* datablock_decl : rwDATABLOCK IDENT '(' IDENT ')' ':' IDENT '{' slot_assign_list '}' ';' */ +#line 212 "console/gram.y" + yyval.stmt = ObjectDeclNode::alloc(ConstantNode::alloc(yypvt[-9].s), ConstantNode::alloc(yypvt[-7].s), NULL, yypvt[-4].s, yypvt[-2].slist, NULL, true); +} break; + +case YYr32: { /* object_decl : rwDECLARE class_name_expr '(' object_name object_args ')' '{' object_declare_block '}' */ +#line 217 "console/gram.y" + yyval.od = ObjectDeclNode::alloc(yypvt[-7].expr, yypvt[-5].expr, yypvt[-4].expr, NULL, yypvt[-1].odcl.slots, yypvt[-1].odcl.decls, false); +} break; + +case YYr33: { /* object_decl : rwDECLARE class_name_expr '(' object_name object_args ')' */ +#line 219 "console/gram.y" + yyval.od = ObjectDeclNode::alloc(yypvt[-4].expr, yypvt[-2].expr, yypvt[-1].expr, NULL, NULL, NULL, false); +} break; + +case YYr34: { /* object_name : */ +#line 224 "console/gram.y" + yyval.expr = StrConstNode::alloc("", false); +} break; + +case YYr35: { /* object_name : expr */ +#line 226 "console/gram.y" + yyval.expr = yypvt[0].expr; +} break; + +case YYr36: { /* object_args : */ +#line 231 "console/gram.y" + yyval.expr = NULL; +} break; + +case YYr37: { /* object_args : ',' expr_list */ +#line 233 "console/gram.y" + yyval.expr = yypvt[0].expr; +} break; + +case YYr38: { /* object_declare_block : */ +#line 238 "console/gram.y" + yyval.odcl.slots = NULL; yyval.odcl.decls = NULL; +} break; + +case YYr39: { /* object_declare_block : slot_assign_list */ +#line 240 "console/gram.y" + yyval.odcl.slots = yypvt[0].slist; yyval.odcl.decls = NULL; +} break; + +case YYr40: { /* object_declare_block : object_decl_list */ +#line 242 "console/gram.y" + yyval.odcl.slots = NULL; yyval.odcl.decls = yypvt[0].od; +} break; + +case YYr41: { /* object_declare_block : slot_assign_list object_decl_list */ +#line 244 "console/gram.y" + yyval.odcl.slots = yypvt[-1].slist; yyval.odcl.decls = yypvt[0].od; +} break; + +case YYr42: { /* object_decl_list : object_decl ';' */ +#line 249 "console/gram.y" + yyval.od = yypvt[-1].od; +} break; + +case YYr43: { /* object_decl_list : object_decl_list object_decl ';' */ +#line 251 "console/gram.y" + yypvt[-2].od->append(yypvt[-1].od); yyval.od = yypvt[-2].od; +} break; + +case YYr44: { /* stmt_block : '{' statement_list '}' */ +#line 256 "console/gram.y" + yyval.stmt = yypvt[-1].stmt; +} break; + +case YYr45: { /* stmt_block : stmt */ +#line 258 "console/gram.y" + yyval.stmt = yypvt[0].stmt; +} break; + +case YYr46: { /* switch_stmt : rwSWITCH '(' expr ')' '{' case_block '}' */ +#line 263 "console/gram.y" + yyval.stmt = yypvt[-1].ifnode; yypvt[-1].ifnode->propagateSwitchExpr(yypvt[-4].expr, false); +} break; + +case YYr47: { /* switch_stmt : rwSWITCHSTR '(' expr ')' '{' case_block '}' */ +#line 265 "console/gram.y" + yyval.stmt = yypvt[-1].ifnode; yypvt[-1].ifnode->propagateSwitchExpr(yypvt[-4].expr, true); +} break; + +case YYr48: { /* case_block : rwCASE case_expr ':' statement_list */ +#line 270 "console/gram.y" + yyval.ifnode = IfStmtNode::alloc(yypvt[-3].i, yypvt[-2].expr, yypvt[0].stmt, NULL, false); +} break; + +case YYr49: { /* case_block : rwCASE case_expr ':' statement_list rwDEFAULT ':' statement_list */ +#line 272 "console/gram.y" + yyval.ifnode = IfStmtNode::alloc(yypvt[-6].i, yypvt[-5].expr, yypvt[-3].stmt, yypvt[0].stmt, false); +} break; + +case YYr50: { /* case_block : rwCASE case_expr ':' statement_list case_block */ +#line 274 "console/gram.y" + yyval.ifnode = IfStmtNode::alloc(yypvt[-4].i, yypvt[-3].expr, yypvt[-1].stmt, yypvt[0].ifnode, true); +} break; + +case YYr51: { /* case_expr : expr */ +#line 279 "console/gram.y" + yyval.expr = yypvt[0].expr; +} break; + +case YYr52: { /* case_expr : case_expr rwCASEOR expr */ +#line 281 "console/gram.y" + (yypvt[-2].expr)->append(yypvt[0].expr); yyval.expr=yypvt[-2].expr; +} break; + +case YYr53: { /* if_stmt : rwIF '(' expr ')' stmt_block */ +#line 286 "console/gram.y" + yyval.stmt = IfStmtNode::alloc(yypvt[-4].i, yypvt[-2].expr, yypvt[0].stmt, NULL, false); +} break; + +case YYr54: { /* if_stmt : rwIF '(' expr ')' stmt_block rwELSE stmt_block */ +#line 288 "console/gram.y" + yyval.stmt = IfStmtNode::alloc(yypvt[-6].i, yypvt[-4].expr, yypvt[-2].stmt, yypvt[0].stmt, false); +} break; + +case YYr55: { /* while_stmt : rwWHILE '(' expr ')' stmt_block */ +#line 293 "console/gram.y" + yyval.stmt = LoopStmtNode::alloc(yypvt[-4].i, nil, yypvt[-2].expr, nil, yypvt[0].stmt, false); +} break; + +case YYr56: { /* for_stmt : rwFOR '(' expr ';' expr ';' expr ')' stmt_block */ +#line 298 "console/gram.y" + yyval.stmt = LoopStmtNode::alloc(yypvt[-8].i, yypvt[-6].expr, yypvt[-4].expr, yypvt[-2].expr, yypvt[0].stmt, false); +} break; + +case YYr57: { /* expression_stmt : stmt_expr */ +#line 303 "console/gram.y" + yyval.stmt = yypvt[0].expr; +} break; + +case YYr58: { /* expr : stmt_expr */ +#line 308 "console/gram.y" + yyval.expr = yypvt[0].expr; +} break; + +case YYr59: { /* expr : '(' expr ')' */ +#line 310 "console/gram.y" + yyval.expr = yypvt[-1].expr; +} break; + +case YYr60: { /* expr : expr '^' expr */ +#line 312 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr61: { /* expr : expr '%' expr */ +#line 314 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr62: { /* expr : expr '&' expr */ +#line 316 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr63: { /* expr : expr '|' expr */ +#line 318 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr64: { /* expr : expr '+' expr */ +#line 320 "console/gram.y" + yyval.expr = FloatBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr65: { /* expr : expr '-' expr */ +#line 322 "console/gram.y" + yyval.expr = FloatBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr66: { /* expr : expr '*' expr */ +#line 324 "console/gram.y" + yyval.expr = FloatBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr67: { /* expr : expr '/' expr */ +#line 326 "console/gram.y" + yyval.expr = FloatBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr68: { /* expr : '-' expr */ +#line 328 "console/gram.y" + yyval.expr = FloatUnaryExprNode::alloc(yypvt[-1].i, yypvt[0].expr); +} break; + +case YYr69: { /* expr : '*' expr */ +#line 330 "console/gram.y" + yyval.expr = TTagDerefNode::alloc(yypvt[0].expr); +} break; + +case YYr70: { /* expr : TTAG */ +#line 332 "console/gram.y" + yyval.expr = TTagExprNode::alloc(yypvt[0].s); +} break; + +case YYr71: { /* expr : expr '?' expr ':' expr */ +#line 334 "console/gram.y" + yyval.expr = ConditionalExprNode::alloc(yypvt[-4].expr, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr72: { /* expr : expr '<' expr */ +#line 336 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr73: { /* expr : expr '>' expr */ +#line 338 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr74: { /* expr : expr opGE expr */ +#line 340 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr75: { /* expr : expr opLE expr */ +#line 342 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr76: { /* expr : expr opEQ expr */ +#line 344 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr77: { /* expr : expr opNE expr */ +#line 346 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr78: { /* expr : expr opOR expr */ +#line 348 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr79: { /* expr : expr opSHL expr */ +#line 350 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr80: { /* expr : expr opSHR expr */ +#line 352 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr81: { /* expr : expr opAND expr */ +#line 354 "console/gram.y" + yyval.expr = IntBinaryExprNode::alloc(yypvt[-1].i, yypvt[-2].expr, yypvt[0].expr); +} break; + +case YYr82: { /* expr : expr opSTREQ expr */ +#line 356 "console/gram.y" + yyval.expr = StreqExprNode::alloc(yypvt[-2].expr, yypvt[0].expr, true); +} break; + +case YYr83: { /* expr : expr opSTRNE expr */ +#line 358 "console/gram.y" + yyval.expr = StreqExprNode::alloc(yypvt[-2].expr, yypvt[0].expr, false); +} break; + +case YYr84: { /* expr : expr '@' expr */ +#line 360 "console/gram.y" + yyval.expr = StrcatExprNode::alloc(yypvt[-2].expr, yypvt[0].expr, yypvt[-1].i); +} break; + +case YYr85: { /* expr : '!' expr */ +#line 362 "console/gram.y" + yyval.expr = IntUnaryExprNode::alloc(yypvt[-1].i, yypvt[0].expr); +} break; + +case YYr86: { /* expr : '~' expr */ +#line 364 "console/gram.y" + yyval.expr = IntUnaryExprNode::alloc(yypvt[-1].i, yypvt[0].expr); +} break; + +case YYr87: { /* expr : TAGATOM */ +#line 366 "console/gram.y" + yyval.expr = StrConstNode::alloc(yypvt[0].str, true); +} break; + +case YYr88: { /* expr : FLTCONST */ +#line 368 "console/gram.y" + yyval.expr = FloatNode::alloc(yypvt[0].f); +} break; + +case YYr89: { /* expr : INTCONST */ +#line 370 "console/gram.y" + yyval.expr = IntNode::alloc(yypvt[0].i); +} break; + +case YYr90: { /* expr : rwBREAK */ +#line 372 "console/gram.y" + yyval.expr = ConstantNode::alloc(StringTable->insert("break")); +} break; + +case YYr91: { /* expr : slot_acc */ +#line 374 "console/gram.y" + yyval.expr = SlotAccessNode::alloc(yypvt[0].slot.object, yypvt[0].slot.array, yypvt[0].slot.slotName); +} break; + +case YYr92: { /* expr : IDENT */ +#line 376 "console/gram.y" + yyval.expr = ConstantNode::alloc(yypvt[0].s); +} break; + +case YYr93: { /* expr : STRATOM */ +#line 378 "console/gram.y" + yyval.expr = StrConstNode::alloc(yypvt[0].str, false); +} break; + +case YYr94: { /* expr : VAR */ +#line 380 "console/gram.y" + yyval.expr = (ExprNode*)VarNode::alloc(yypvt[0].s, NULL); +} break; + +case YYr95: { /* expr : VAR '[' aidx_expr ']' */ +#line 382 "console/gram.y" + yyval.expr = (ExprNode*)VarNode::alloc(yypvt[-3].s, yypvt[-1].expr); +} break; + +case YYr96: { /* slot_acc : expr '.' IDENT */ +#line 387 "console/gram.y" + yyval.slot.object = yypvt[-2].expr; yyval.slot.slotName = yypvt[0].s; yyval.slot.array = NULL; +} break; + +case YYr97: { /* slot_acc : expr '.' IDENT '[' aidx_expr ']' */ +#line 389 "console/gram.y" + yyval.slot.object = yypvt[-5].expr; yyval.slot.slotName = yypvt[-3].s; yyval.slot.array = yypvt[-1].expr; +} break; + +case YYr98: { /* class_name_expr : IDENT */ +#line 394 "console/gram.y" + yyval.expr = ConstantNode::alloc(yypvt[0].s); +} break; + +case YYr99: { /* class_name_expr : '(' expr ')' */ +#line 396 "console/gram.y" + yyval.expr = yypvt[-1].expr; +} break; + +case YYr100: { /* assign_op_struct : opPLUSPLUS */ +#line 401 "console/gram.y" + yyval.asn.token = '+'; yyval.asn.expr = FloatNode::alloc(1); +} break; + +case YYr101: { /* assign_op_struct : opMINUSMINUS */ +#line 403 "console/gram.y" + yyval.asn.token = '-'; yyval.asn.expr = FloatNode::alloc(1); +} break; + +case YYr102: { /* assign_op_struct : opPLASN expr */ +#line 405 "console/gram.y" + yyval.asn.token = '+'; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr103: { /* assign_op_struct : opMIASN expr */ +#line 407 "console/gram.y" + yyval.asn.token = '-'; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr104: { /* assign_op_struct : opMLASN expr */ +#line 409 "console/gram.y" + yyval.asn.token = '*'; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr105: { /* assign_op_struct : opDVASN expr */ +#line 411 "console/gram.y" + yyval.asn.token = '/'; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr106: { /* assign_op_struct : opMODASN expr */ +#line 413 "console/gram.y" + yyval.asn.token = '%'; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr107: { /* assign_op_struct : opANDASN expr */ +#line 415 "console/gram.y" + yyval.asn.token = '&'; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr108: { /* assign_op_struct : opXORASN expr */ +#line 417 "console/gram.y" + yyval.asn.token = '^'; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr109: { /* assign_op_struct : opORASN expr */ +#line 419 "console/gram.y" + yyval.asn.token = '|'; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr110: { /* assign_op_struct : opSLASN expr */ +#line 421 "console/gram.y" + yyval.asn.token = opSHL; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr111: { /* assign_op_struct : opSRASN expr */ +#line 423 "console/gram.y" + yyval.asn.token = opSHR; yyval.asn.expr = yypvt[0].expr; +} break; + +case YYr112: { /* stmt_expr : funcall_expr */ +#line 428 "console/gram.y" + yyval.expr = yypvt[0].expr; +} break; + +case YYr113: { /* stmt_expr : object_decl */ +#line 430 "console/gram.y" + yyval.expr = yypvt[0].od; +} break; + +case YYr114: { /* stmt_expr : VAR '=' expr */ +#line 432 "console/gram.y" + yyval.expr = AssignExprNode::alloc(yypvt[-2].s, NULL, yypvt[0].expr); +} break; + +case YYr115: { /* stmt_expr : VAR '[' aidx_expr ']' '=' expr */ +#line 434 "console/gram.y" + yyval.expr = AssignExprNode::alloc(yypvt[-5].s, yypvt[-3].expr, yypvt[0].expr); +} break; + +case YYr116: { /* stmt_expr : VAR assign_op_struct */ +#line 436 "console/gram.y" + yyval.expr = AssignOpExprNode::alloc(yypvt[-1].s, NULL, yypvt[0].asn.expr, yypvt[0].asn.token); +} break; + +case YYr117: { /* stmt_expr : VAR '[' aidx_expr ']' assign_op_struct */ +#line 438 "console/gram.y" + yyval.expr = AssignOpExprNode::alloc(yypvt[-4].s, yypvt[-2].expr, yypvt[0].asn.expr, yypvt[0].asn.token); +} break; + +case YYr118: { /* stmt_expr : slot_acc assign_op_struct */ +#line 440 "console/gram.y" + yyval.expr = SlotAssignOpNode::alloc(yypvt[-1].slot.object, yypvt[-1].slot.slotName, yypvt[-1].slot.array, yypvt[0].asn.token, yypvt[0].asn.expr); +} break; + +case YYr119: { /* stmt_expr : slot_acc '=' expr */ +#line 442 "console/gram.y" + yyval.expr = SlotAssignNode::alloc(yypvt[-2].slot.object, yypvt[-2].slot.array, yypvt[-2].slot.slotName, yypvt[0].expr); +} break; + +case YYr120: { /* stmt_expr : slot_acc '=' '{' expr_list '}' */ +#line 444 "console/gram.y" + yyval.expr = SlotAssignNode::alloc(yypvt[-4].slot.object, yypvt[-4].slot.array, yypvt[-4].slot.slotName, yypvt[-1].expr); +} break; + +case YYr121: { /* funcall_expr : IDENT '(' expr_list_decl ')' */ +#line 449 "console/gram.y" + yyval.expr = FuncCallExprNode::alloc(yypvt[-3].s, NULL, yypvt[-1].expr, false); +} break; + +case YYr122: { /* funcall_expr : IDENT opCOLONCOLON IDENT '(' expr_list_decl ')' */ +#line 451 "console/gram.y" + yyval.expr = FuncCallExprNode::alloc(yypvt[-3].s, yypvt[-5].s, yypvt[-1].expr, false); +} break; + +case YYr123: { /* funcall_expr : expr '.' IDENT '(' expr_list_decl ')' */ +#line 453 "console/gram.y" + yypvt[-5].expr->append(yypvt[-1].expr); yyval.expr = FuncCallExprNode::alloc(yypvt[-3].s, NULL, yypvt[-5].expr, true); +} break; + +case YYr124: { /* expr_list_decl : */ +#line 458 "console/gram.y" + yyval.expr = NULL; +} break; + +case YYr125: { /* expr_list_decl : expr_list */ +#line 460 "console/gram.y" + yyval.expr = yypvt[0].expr; +} break; + +case YYr126: { /* expr_list : expr */ +#line 465 "console/gram.y" + yyval.expr = yypvt[0].expr; +} break; + +case YYr127: { /* expr_list : expr_list ',' expr */ +#line 467 "console/gram.y" + (yypvt[-2].expr)->append(yypvt[0].expr); yyval.expr = yypvt[-2].expr; +} break; + +case YYr128: { /* slot_assign_list : slot_assign */ +#line 472 "console/gram.y" + yyval.slist = yypvt[0].slist; +} break; + +case YYr129: { /* slot_assign_list : slot_assign_list slot_assign */ +#line 474 "console/gram.y" + yypvt[-1].slist->append(yypvt[0].slist); yyval.slist = yypvt[-1].slist; +} break; + +case YYr130: { /* slot_assign : IDENT '=' expr ';' */ +#line 479 "console/gram.y" + yyval.slist = SlotAssignNode::alloc(NULL, NULL, yypvt[-3].s, yypvt[-1].expr); +} break; + +case YYr131: { /* slot_assign : rwDATABLOCK '=' expr ';' */ +#line 481 "console/gram.y" + yyval.slist = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), yypvt[-1].expr); +} break; + +case YYr132: { /* slot_assign : IDENT '[' aidx_expr ']' '=' expr ';' */ +#line 483 "console/gram.y" + yyval.slist = SlotAssignNode::alloc(NULL, yypvt[-4].expr, yypvt[-6].s, yypvt[-1].expr); +} break; + +case YYr133: { /* aidx_expr : expr */ +#line 488 "console/gram.y" + yyval.expr = yypvt[0].expr; +} break; + +case YYr134: { /* aidx_expr : aidx_expr ',' expr */ +#line 490 "console/gram.y" + yyval.expr = CommaCatExprNode::alloc(yypvt[-2].expr, yypvt[0].expr); +} break; +#line 314 "console/yyparse.c" + case YYrACCEPT: + YYACCEPT; + case YYrERROR: + goto yyError; + } + + /* + * Look up next state in goto table. + */ + + yyp = &yygo[yypgo[yyi]]; + yyq = yyp++; + yyi = *yyps; + while (yyi < *yyp++) + ; + + yystate = yyneg(yyi == *--yyp? YYQYYP: *yyq); +#if YYDEBUG + if (CMDdebug) + YY_TRACE(yyShowGoto) +#endif + goto yyStack; + +yyerrlabel: ; /* come here from YYERROR */ +/* +#pragma used yyerrlabel + */ + yyerrflag = 1; + if (yyi == YYrERROR) { + yyps--; + yypv--; +#if YYDEBUG + yytp--; +#endif + } + +yyError: + switch (yyerrflag) { + + case 0: /* new error */ + yynerrs++; + yyi = CMDchar; + CMDerror(m_textmsg(4969, "Syntax error", "E")); + if (yyi != CMDchar) { + /* user has changed the current token */ + /* try again */ + yyerrflag++; /* avoid loops */ + goto yyEncore; + } + + case 1: /* partially recovered */ + case 2: + yyerrflag = 3; /* need 3 valid shifts to recover */ + + /* + * Pop states, looking for a + * shift on `error'. + */ + + for ( ; yyps > yys; yyps--, yypv-- +#if YYDEBUG + , yytp-- +#endif + ) { +#ifdef YACC_WINDOWS + if (*yyps >= Sizeof_yypact) +#else /* YACC_WINDOWS */ + if (*yyps >= sizeof yypact/sizeof yypact[0]) +#endif /* YACC_WINDOWS */ + continue; + yyp = &yyact[yypact[*yyps]]; + yyq = yyp; + do { + if (YYERRCODE == *yyp) { + yyp++; + yystate = yyneg(YYQYYP); + goto yyStack; + } + } while (*yyp++ > YYTOKEN_BASE); + + /* no shift in this state */ +#if YYDEBUG + if (CMDdebug && yyps > yys+1) + YY_TRACE(yyShowErrRecovery) +#endif + /* pop stacks; try again */ + } + /* no shift on error - abort */ + break; + + case 3: + /* + * Erroneous token after + * an error - discard it. + */ + + if (CMDchar == 0) /* but not EOF */ + break; +#if YYDEBUG + if (CMDdebug) + YY_TRACE(yyShowErrDiscard) +#endif + yyclearin; + goto yyEncore; /* try again in same state */ + } + YYABORT; + +#ifdef YYALLOC +yyReturn: + CMDlval = save_yylval; + yyval = save_yyval; + yypvt = save_yypvt; + CMDchar = save_yychar; + yyerrflag = save_yyerrflag; + yynerrs = save_yynerrs; + free((char *)yys); + free((char *)yyv); +#if YYDEBUG + free((char *)yytypev); +#endif + return(retval); +#endif +} + + +#if YYDEBUG +/* + * Return type of token + */ +int +yyGetType(tok) +int tok; +{ + yyNamedType * tp; + for (tp = &yyTokenTypes[yyntoken-1]; tp > yyTokenTypes; tp--) + if (tp->token == tok) + return tp->type; + return 0; +} +/* + * Print a token legibly. + */ +char * +yyptok(tok) +int tok; +{ + yyNamedType * tp; + for (tp = &yyTokenTypes[yyntoken-1]; tp > yyTokenTypes; tp--) + if (tp->token == tok) + return tp->name; + return ""; +} + +/* + * Read state 'num' from YYStatesFile + */ +#ifdef YYTRACE + +static char * +yygetState(num) +int num; +{ + int size; + static FILE *yyStatesFile = (FILE *) 0; + static char yyReadBuf[YYMAX_READ+1]; + + if (yyStatesFile == (FILE *) 0 + && (yyStatesFile = fopen(YYStatesFile, "r")) == (FILE *) 0) + return "yyExpandName: cannot open states file"; + + if (num < yynstate - 1) + size = (int)(yyStates[num+1] - yyStates[num]); + else { + /* length of last item is length of file - ptr(last-1) */ + if (fseek(yyStatesFile, 0L, 2) < 0) + goto cannot_seek; + size = (int) (ftell(yyStatesFile) - yyStates[num]); + } + if (size < 0 || size > YYMAX_READ) + return "yyExpandName: bad read size"; + if (fseek(yyStatesFile, yyStates[num], 0) < 0) { + cannot_seek: + return "yyExpandName: cannot seek in states file"; + } + + (void) fread(yyReadBuf, 1, size, yyStatesFile); + yyReadBuf[size] = '\0'; + return yyReadBuf; +} +#endif /* YYTRACE */ +/* + * Expand encoded string into printable representation + * Used to decode yyStates and yyRules strings. + * If the expansion of 's' fits in 'buf', return 1; otherwise, 0. + */ +int +yyExpandName(num, isrule, buf, len) +int num, isrule; +char * buf; +int len; +{ + int i, n, cnt, type; + char * endp, * cp; + char *s; + + if (isrule) + s = yyRules[num].name; + else +#ifdef YYTRACE + s = yygetState(num); +#else + s = "*no states*"; +#endif + + for (endp = buf + len - 8; *s; s++) { + if (buf >= endp) { /* too large: return 0 */ + full: (void) strcpy(buf, " ...\n"); + return 0; + } else if (*s == '%') { /* nonterminal */ + type = 0; + cnt = yynvar; + goto getN; + } else if (*s == '&') { /* terminal */ + type = 1; + cnt = yyntoken; + getN: + if (cnt < 100) + i = 2; + else if (cnt < 1000) + i = 3; + else + i = 4; + for (n = 0; i-- > 0; ) + n = (n * 10) + *++s - '0'; + if (type == 0) { + if (n >= yynvar) + goto too_big; + cp = yysvar[n]; + } else if (n >= yyntoken) { + too_big: + cp = ""; + } else + cp = yyTokenTypes[n].name; + + if ((i = strlen(cp)) + buf > endp) + goto full; + (void) strcpy(buf, cp); + buf += i; + } else + *buf++ = *s; + } + *buf = '\0'; + return 1; +} +#ifndef YYTRACE +/* + * Show current state of CMDparse + */ +void +yyShowState(tp) +yyTraceItems * tp; +{ + short * p; + YYSTYPE * q; + + printf( + m_textmsg(2828, "state %d (%d), char %s (%d)\n", "I num1 num2 char num3"), + yysmap[tp->state], tp->state, + yyptok(tp->lookahead), tp->lookahead); +} +/* + * show results of reduction + */ +void +yyShowReduce(tp) +yyTraceItems * tp; +{ + printf("reduce %d (%d), pops %d (%d)\n", + yyrmap[tp->rule], tp->rule, + tp->states[tp->nstates - tp->npop], + yysmap[tp->states[tp->nstates - tp->npop]]); +} +void +yyShowRead(val) +int val; +{ + printf(m_textmsg(2829, "read %s (%d)\n", "I token num"), yyptok(val), val); +} +void +yyShowGoto(tp) +yyTraceItems * tp; +{ + printf(m_textmsg(2830, "goto %d (%d)\n", "I num1 num2"), yysmap[tp->state], tp->state); +} +void +yyShowShift(tp) +yyTraceItems * tp; +{ + printf(m_textmsg(2831, "shift %d (%d)\n", "I num1 num2"), yysmap[tp->state], tp->state); +} +void +yyShowErrRecovery(tp) +yyTraceItems * tp; +{ + short * top = tp->states + tp->nstates - 1; + + printf( + m_textmsg(2832, "Error recovery pops state %d (%d), uncovers %d (%d)\n", "I num1 num2 num3 num4"), + yysmap[*top], *top, yysmap[*(top-1)], *(top-1)); +} +void +yyShowErrDiscard(tp) +yyTraceItems * tp; +{ + printf(m_textmsg(2833, "Error recovery discards %s (%d), ", "I token num"), + yyptok(tp->lookahead), tp->lookahead); +} +#endif /* ! YYTRACE */ +#endif /* YYDEBUG */ diff --git a/console/gram.h b/console/gram.h new file mode 100644 index 0000000..b0c48f3 --- /dev/null +++ b/console/gram.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#define rwDEFINE 257 +#define rwENDDEF 258 +#define rwDECLARE 259 +#define rwBREAK 260 +#define rwELSE 261 +#define rwCONTINUE 262 +#define rwGLOBAL 263 +#define rwIF 264 +#define rwNIL 265 +#define rwRETURN 266 +#define rwWHILE 267 +#define rwENDIF 268 +#define rwENDWHILE 269 +#define rwENDFOR 270 +#define rwDEFAULT 271 +#define rwFOR 272 +#define rwDATABLOCK 273 +#define rwSWITCH 274 +#define rwCASE 275 +#define rwSWITCHSTR 276 +#define rwCASEOR 277 +#define rwPACKAGE 278 +#define ILLEGAL_TOKEN 279 +#define CHRCONST 280 +#define INTCONST 281 +#define TTAG 282 +#define VAR 283 +#define IDENT 284 +#define STRATOM 285 +#define TAGATOM 286 +#define FLTCONST 287 +#define opMINUSMINUS 288 +#define opPLUSPLUS 289 +#define STMT_SEP 290 +#define opSHL 291 +#define opSHR 292 +#define opPLASN 293 +#define opMIASN 294 +#define opMLASN 295 +#define opDVASN 296 +#define opMODASN 297 +#define opANDASN 298 +#define opXORASN 299 +#define opORASN 300 +#define opSLASN 301 +#define opSRASN 302 +#define opCAT 303 +#define opEQ 304 +#define opNE 305 +#define opGE 306 +#define opLE 307 +#define opAND 308 +#define opOR 309 +#define opSTREQ 310 +#define opCOLONCOLON 311 +typedef union { + char c; + int i; + const char *s; + char *str; + double f; + StmtNode *stmt; + ExprNode *expr; + SlotAssignNode *slist; + VarNode *var; + SlotDecl slot; + ObjectBlockDecl odcl; + ObjectDeclNode *od; + AssignDecl asn; + IfStmtNode *ifnode; +} YYSTYPE; +#define opMDASN 312 +#define opNDASN 313 +#define opNTASN 314 +#define opSTRNE 315 +#define UNARY 316 +extern YYSTYPE CMDlval; diff --git a/console/gram.y b/console/gram.y new file mode 100644 index 0000000..3029e12 --- /dev/null +++ b/console/gram.y @@ -0,0 +1,493 @@ +%{ +#include "console.h" +#include "ast.h" +#include +#include "stdio.h" +#include "consoleInternal.h" + +#ifndef YYDEBUG +#define YYDEBUG +#endif + +#define YYSSIZE 350 + +int outtext(char *fmt, ...); +extern int serrors; +#define nil 0 +#undef YY_ARGS +#define YY_ARGS(x) x +%} +%{ + /* Reserved Word Definitions */ +%} +%token rwDEFINE rwENDDEF rwDECLARE +%token rwBREAK rwELSE rwCONTINUE rwGLOBAL +%token rwIF rwNIL rwRETURN rwWHILE +%token rwENDIF rwENDWHILE rwENDFOR rwDEFAULT +%token rwFOR rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR +%token rwCASEOR rwPACKAGE +%token ILLEGAL_TOKEN +%{ + /* Constants and Identifier Definitions */ +%} +%token CHRCONST +%token INTCONST +%token TTAG +%token VAR +%token IDENT +%token STRATOM +%token TAGATOM +%token FLTCONST + +%{ + /* Operator Definitions */ +%} +%token '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%' +%token '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@' +%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; + ObjectBlockDecl odcl; + ObjectDeclNode *od; + AssignDecl asn; + IfStmtNode *ifnode; +} + +%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 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 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 '.' + +%% + +start + : decl_list + { } + ; + +decl_list + : + { $$ = nil; } + | decl_list decl + { if(!statementList) { statementList = $2; } else { statementList->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); } + ; + +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 ')' '{' slot_assign_list '}' ';' + { $$ = ObjectDeclNode::alloc(ConstantNode::alloc($2), ConstantNode::alloc($4), NULL, NULL, $7, NULL, true); } + | rwDATABLOCK IDENT '(' IDENT ')' ':' IDENT '{' slot_assign_list '}' ';' + { $$ = ObjectDeclNode::alloc(ConstantNode::alloc($2), ConstantNode::alloc($4), NULL, $7, $9, NULL, true); } + ; + +object_decl + : rwDECLARE class_name_expr '(' object_name object_args ')' '{' object_declare_block '}' + { $$ = ObjectDeclNode::alloc($2, $4, $5, NULL, $8.slots, $8.decls, false); } + | rwDECLARE class_name_expr '(' object_name object_args ')' + { $$ = ObjectDeclNode::alloc($2, $4, $5, NULL, NULL, NULL, false); } + ; + +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); } + ; + +for_stmt + : rwFOR '(' expr ';' expr ';' expr ')' stmt_block + { $$ = LoopStmtNode::alloc($1, $3, $5, $7, $9, 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); } + | 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; } + ; + +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; } + | 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); } + ; + +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); } + | rwDATABLOCK '=' expr ';' + { $$ = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), $3); } + | IDENT '[' aidx_expr ']' '=' expr ';' + { $$ = SlotAssignNode::alloc(NULL, $3, $1, $6); } + ; + +aidx_expr + : expr + { $$ = $1; } + | aidx_expr ',' expr + { $$ = CommaCatExprNode::alloc($1, $3); } + ; +%% + diff --git a/console/objectTypes.h b/console/objectTypes.h new file mode 100644 index 0000000..d5d95fe --- /dev/null +++ b/console/objectTypes.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _OBJECTTYPES_H_ +#define _OBJECTTYPES_H_ + +// Types used for SimObject type masks (SimObject::mTypeMask) +// + +/* NB! If a new object type is added, don't forget to add it to the + * consoleInit function in simBase.cc + */ + +// SimObjectTypes + +// Types used by the SceneObject class +#define DefaultObjectType 0 +#define StaticObjectType (1<<0) + +// Basic Engine Types +#define EnvironmentObjectType (1<<1) +#define TerrainObjectType (1<<2) +#define InteriorObjectType (1<<3) +#define WaterObjectType (1<<4) +#define TriggerObjectType (1<<5) +#define MarkerObjectType (1<<6) +#define PathedObjectType (1<<7) +#define ForceFieldObjectType (1<<8) +#define DecalManagerObjectType (1<<9) + +#define PlayerObjectType (1<<14) +#define GuiControlObjectType (1<<25) +#define StaticRenderedObjectType (1<<26) + + +// Game Types +#define GameBaseObjectType (1<<10) +#define ShapeBaseObjectType (1<<11) +#define CameraObjectType (1<<12) +#define StaticShapeObjectType (1<<13) +#define ItemObjectType (1<<15) +#define VehicleObjectType (1<<16) +#define MoveableObjectType (1<<17) +#define ProjectileObjectType (1<<18) +#define ExplosionObjectType (1<<19) +#define CorpseObjectType (1<<20) +#define TurretObjectType (1<<21) +#define DebrisObjectType (1<<22) +#define PhysicalZoneObjectType (1<<23) +#define StaticTSObjectType (1<<24) + +// The following are allowed types that can be set on datablocks for static shapes +// +#define DamagableItemObjectType (1<<27) +#define SensorObjectType (1<<28) +#define StationObjectType (1<<29) +#define GeneratorObjectType (1<<30) + + +#define STATIC_COLLISION_MASK ( TerrainObjectType | \ + InteriorObjectType | \ + ForceFieldObjectType | \ + StaticObjectType ) \ + +#define DAMAGEABLE_MASK ( PlayerObjectType | \ + VehicleObjectType | \ + MoveableObjectType | \ + StationObjectType | \ + GeneratorObjectType | \ + SensorObjectType | \ + PathedObjectType | \ + DamagableItemObjectType | \ + TurretObjectType ) \ + +#endif diff --git a/console/scan.cc b/console/scan.cc new file mode 100644 index 0000000..d84396c --- /dev/null +++ b/console/scan.cc @@ -0,0 +1,1560 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/* lex -a -P console\yylex.c -p CMD -o console\scan.cc console\scan.l */ +#define YYNEWLINE 10 +#define INITIAL 0 +#define CMD_endst 180 +#define CMD_nxtmax 2057 +#define YY_LA_SIZE 23 + +static unsigned int CMD_la_act[] = { + 0, 81, 41, 81, 2, 81, 3, 81, 81, 56, 81, 46, 81, 43, 81, 42, + 81, 52, 81, 44, 81, 47, 81, 39, 81, 38, 81, 81, 40, 81, 53, 81, + 54, 81, 76, 81, 76, 81, 76, 81, 32, 81, 33, 81, 34, 81, 35, 81, + 36, 81, 37, 81, 45, 81, 48, 81, 49, 81, 50, 81, 51, 81, 55, 81, + 76, 81, 76, 81, 76, 81, 76, 81, 76, 81, 76, 81, 76, 81, 76, 81, + 76, 81, 76, 81, 76, 81, 76, 81, 76, 81, 76, 81, 78, 81, 78, 81, + 81, 78, 79, 79, 77, 76, 76, 76, 73, 76, 76, 76, 76, 76, 76, 72, + 76, 76, 76, 76, 76, 70, 76, 69, 76, 76, 76, 76, 76, 76, 71, 76, + 76, 76, 76, 76, 76, 76, 67, 76, 76, 66, 76, 76, 76, 76, 68, 76, + 76, 76, 76, 76, 76, 64, 76, 76, 76, 76, 76, 76, 74, 76, 76, 76, + 76, 76, 76, 65, 76, 63, 76, 62, 76, 76, 76, 76, 61, 76, 76, 76, + 60, 76, 76, 76, 76, 76, 59, 76, 76, 76, 76, 58, 76, 57, 76, 76, + 31, 76, 76, 30, 76, 29, 76, 25, 23, 75, 80, 80, 75, 21, 15, 14, + 19, 13, 20, 12, 11, 26, 10, 24, 9, 17, 27, 8, 18, 28, 7, 16, + 6, 5, 4, 1, 22, 1, 0, 0 +}; + +static unsigned char CMD_look[] = { + 0 +}; + +static int CMD_final[] = { + 0, 0, 2, 4, 6, 7, 8, 9, 11, 13, 15, 17, 19, 21, 23, 25, + 27, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, + 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, + 90, 92, 94, 96, 97, 98, 98, 98, 98, 99, 100, 100, 101, 102, 103, 104, + 106, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 119, 120, 121, 122, 123, + 124, 125, 126, 128, 129, 130, 131, 132, 133, 134, 136, 137, 139, 140, 141, 142, + 144, 145, 146, 147, 148, 149, 151, 152, 153, 154, 155, 156, 158, 159, 160, 161, + 162, 163, 165, 167, 169, 170, 171, 172, 174, 175, 176, 178, 179, 180, 181, 182, + 184, 185, 186, 187, 189, 191, 192, 194, 195, 197, 199, 200, 201, 202, 202, 203, + 204, 204, 205, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, + 218, 219, 220, 221, 222, 223, 223, 224, 225, 225, 225, 226, 226, 226, 226, 227, + 227, 228, 229, 230, 231 +}; +#ifndef CMD_state_t +#define CMD_state_t unsigned char +#endif + +static CMD_state_t CMD_begin[] = { + 0, 0, 0 +}; + +static CMD_state_t CMD_next[] = { + 51, 51, 51, 51, 51, 51, 51, 51, 51, 1, 4, 1, 1, 3, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 1, 8, 5, 51, 16, 18, 11, 6, 27, 28, 17, 15, 33, 14, 29, 2, + 49, 50, 50, 50, 50, 50, 50, 50, 50, 50, 13, 30, 10, 7, 9, 24, + 23, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 20, 48, + 48, 48, 48, 22, 21, 48, 48, 48, 48, 48, 48, 25, 51, 26, 19, 48, + 51, 48, 36, 42, 44, 38, 41, 48, 48, 40, 48, 48, 48, 48, 43, 35, + 46, 48, 37, 45, 47, 48, 48, 39, 48, 48, 48, 31, 12, 32, 34, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 53, 61, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 180, 180, 180, 180, + 54, 58, 62, 55, 63, 55, 64, 54, 56, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 65, 66, 67, 68, + 54, 58, 69, 70, 71, 72, 73, 54, 59, 59, 59, 59, 59, 59, 59, 59, + 59, 59, 74, 75, 76, 78, 79, 80, 77, 59, 59, 59, 59, 59, 59, 81, + 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 93, 94, 95, 96, 60, 60, + 60, 60, 60, 60, 60, 60, 60, 60, 92, 59, 59, 59, 59, 59, 59, 60, + 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, + 60, 60, 60, 60, 60, 60, 60, 60, 60, 97, 98, 99, 100, 60, 101, 60, + 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, + 60, 60, 60, 60, 60, 60, 60, 60, 60, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 117, 102, 118, 119, 120, 121, 122, 103, 123, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 141, 141, + 141, 141, 141, 141, 141, 141, 141, 141, 147, 148, 153, 139, 155, 159, 158, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 160, 161, 162, 163, 140, 166, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 180, 167, 154, 180, 142, 142, 142, + 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, + 142, 142, 142, 142, 142, 142, 142, 180, 180, 180, 180, 142, 180, 142, 142, 142, + 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, + 142, 142, 142, 142, 142, 142, 142, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 144, 180, 180, 180, 180, 180, 180, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 143, 180, 180, 180, 180, 143, 180, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 143, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 146, 180, 180, 180, + 180, 180, 180, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, + 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 180, 180, 180, + 180, 145, 180, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, + 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 149, 151, 156, + 165, 179, 176, 179, 179, 180, 180, 180, 180, 180, 180, 180, 180, 180, 152, 150, + 177, 180, 180, 180, 180, 180, 157, 180, 179, 164, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 180, 169, 169, 180, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 170, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 168, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 180, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 180, 173, 173, 180, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 174, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 172, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 180, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, + 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 180, 178, 178, 180, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 0 +}; + +static CMD_state_t CMD_check[] = { + 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, 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, + 50, 47, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 56, 55, 56, 55, + 57, 49, 61, 54, 62, 54, 46, 50, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 64, 65, 66, 67, + 57, 49, 68, 45, 70, 71, 72, 50, 58, 58, 58, 58, 58, 58, 58, 58, + 58, 58, 73, 74, 44, 77, 78, 79, 44, 58, 58, 58, 58, 58, 58, 80, + 81, 76, 83, 84, 85, 86, 87, 88, 43, 90, 42, 93, 94, 92, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 42, 58, 58, 58, 58, 58, 58, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 96, 97, 98, 99, 48, 100, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 41, 104, 105, 106, 103, 108, 109, + 110, 111, 112, 102, 40, 39, 116, 41, 117, 118, 38, 120, 121, 41, 37, 123, + 124, 125, 126, 36, 128, 129, 130, 35, 22, 133, 21, 135, 20, 19, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 17, 16, 13, 18, 12, 10, 10, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 159, 9, 9, 162, 18, 165, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 141, 7, 12, ~0U, 141, 141, 141, + 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, + 141, 141, 141, 141, 141, 141, 141, ~0U, ~0U, ~0U, ~0U, 141, ~0U, 141, 141, 141, + 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 141, + 141, 141, 141, 141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 142, 142, 142, + 142, 142, ~0U, ~0U, ~0U, ~0U, ~0U, ~0U, 142, 142, 142, 142, 142, 142, 142, 142, + 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, + 142, 142, ~0U, ~0U, ~0U, ~0U, 142, ~0U, 142, 142, 142, 142, 142, 142, 142, 142, + 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, + 142, 142, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, ~0U, ~0U, ~0U, + ~0U, ~0U, ~0U, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, ~0U, ~0U, ~0U, + ~0U, 140, ~0U, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 15, 14, 11, + 8, 1, 2, 1, 1, ~0U, ~0U, ~0U, ~0U, ~0U, ~0U, ~0U, ~0U, ~0U, 14, 15, + 2, ~0U, ~0U, ~0U, ~0U, ~0U, 11, ~0U, 1, 8, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, ~0U, 6, 6, ~0U, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, ~0U, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, ~0U, 5, 5, ~0U, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, ~0U, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, ~0U, 176, 176, ~0U, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 0 +}; + +static CMD_state_t CMD_default[] = { + 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, + 18, 180, 180, 180, 48, 48, 48, 180, 180, 180, 180, 180, 180, 180, 180, 180, + 180, 180, 180, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 180, 50, 180, 180, 50, 180, 180, 54, 54, 53, 180, 58, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 180, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 180, 180, 180, 18, 180, 142, + 142, 140, 140, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, + 180, 180, 180, 180, 180, 180, 180, 180, 180, 6, 180, 6, 180, 5, 180, 5, + 180, 180, 176, 1, 0 +}; + +static int CMD_base[] = { + 0, 744, 707, 2058, 2058, 1290, 778, 477, 716, 445, 417, 713, 415, 416, 705, 706, + 412, 411, 414, 400, 384, 393, 376, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, + 2058, 2058, 2058, 341, 337, 345, 334, 333, 334, 328, 249, 243, 227, 188, 181, 143, + 302, 185, 210, 2058, 2058, 242, 232, 226, 225, 203, 264, 2058, 2058, 157, 175, 2058, + 201, 194, 205, 200, 205, 2058, 203, 193, 211, 218, 287, 2058, 221, 223, 229, 210, + 227, 220, 2058, 241, 241, 232, 230, 243, 236, 2058, 226, 2058, 239, 232, 247, 2058, + 277, 289, 285, 279, 297, 2058, 321, 319, 318, 312, 327, 2058, 331, 315, 327, 322, + 324, 2058, 2058, 2058, 333, 332, 340, 2058, 328, 343, 2058, 331, 331, 335, 340, 2058, + 351, 356, 347, 2058, 2058, 390, 2058, 393, 2058, 2058, 2058, 2058, 626, 476, 551, 2058, + 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 2058, 444, + 2058, 2058, 447, 2058, 2058, 449, 2058, 2058, 1034, 2058, 2058, 2058, 1546, 2058, 2058, 2058, + 1802, 2058, 2058, 2058, 2058 +}; + + +#line 1 "console/yylex.c" +/* + * Copyright 1988, 1992 by Mortice Kern Systems Inc. All rights reserved. + * All rights reserved. + * + * $Header: /cvs/torque/torque/engine/console/scan.cc,v 1.1 2001/05/17 02:16:15 timg Exp $ + * + */ +#include +#include +#if __STDC__ +#define YY_ARGS(args) args +#else +#define YY_ARGS(args) () +#endif + +#ifdef LEX_WINDOWS +#include + +/* + * define, if not already defined + * the flag YYEXIT, which will allow + * graceful exits from CMDlex() + * without resorting to calling exit(); + */ + +#ifndef YYEXIT +#define YYEXIT 1 +#endif + +/* + * the following is the handle to the current + * instance of a windows program. The user + * program calling CMDlex must supply this! + */ + +#ifdef STRICT +extern HINSTANCE hInst; +#else +extern HANDLE hInst; +#endif + +#endif /* LEX_WINDOWS */ + +/* + * Define m_textmsg() to an appropriate function for internationalized messages + * or custom processing. + */ +#ifndef I18N +#define m_textmsg(id, str, cls) (str) +#else /*I18N*/ +extern char* m_textmsg YY_ARGS((int id, const char* str, char* cls)); +#endif/*I18N*/ + +/* + * Include string.h to get definition of memmove() and size_t. + * If you do not have string.h or it does not declare memmove + * or size_t, you will have to declare them here. + */ +#include +/* Uncomment next line if memmove() is not declared in string.h */ +/*extern char * memmove();*/ +/* Uncomment next line if size_t is not available in stdio.h or string.h */ +/*typedef unsigned size_t;*/ +/* Drop this when LATTICE provides memmove */ +#ifdef LATTICE +#define memmove memcopy +#endif + +/* + * YY_STATIC determines the scope of variables and functions + * declared by the lex scanner. It must be set with a -DYY_STATIC + * option to the compiler (it cannot be defined in the lex program). + */ +#ifdef YY_STATIC +/* define all variables as static to allow more than one lex scanner */ +#define YY_DECL static +#else +/* define all variables as global to allow other modules to access them */ +#define YY_DECL +#endif + +/* + * You can redefine CMDgetc. For YACC Tracing, compile this code + * with -DYYTRACE to get input from yt_getc + */ +#ifdef YYTRACE +extern int yt_getc YY_ARGS((void)); +#define CMDgetc() yt_getc() +#else +#define CMDgetc() getc(CMDin) /* CMDlex input source */ +#endif + +/* + * the following can be redefined by the user. + */ +#ifdef YYEXIT +#define YY_FATAL(msg) { fprintf(CMDout, "CMDlex: %s\n", msg); CMDLexFatal = 1; } +#else /* YYEXIT */ +#define YY_FATAL(msg) { fprintf(stderr, "CMDlex: %s\n", msg); exit(1); } +#endif /* YYEXIT */ + +#undef ECHO +#define ECHO fputs(CMDtext, CMDout) + +#define output(c) putc((c), CMDout) /* CMDlex sink for unmatched chars */ +#define YY_INTERACTIVE 1 /* save micro-seconds if 0 */ + +#define BEGIN CMD_start = +#define REJECT goto CMD_reject +#define NLSTATE (CMD_lastc = YYNEWLINE) +#define YY_INIT \ + (CMD_start = CMDleng = CMD_end = 0, CMD_lastc = YYNEWLINE) +#define CMDmore() goto CMD_more +#define CMDless(n) if ((n) < 0 || (n) > CMD_end) ; \ + else { YY_SCANNER; CMDleng = (n); YY_USER; } + +YY_DECL void CMD_reset YY_ARGS((void)); +YY_DECL int input YY_ARGS((void)); +YY_DECL int unput YY_ARGS((int c)); + +/* functions defined in libl.lib */ +extern int CMDwrap YY_ARGS((void)); +extern void CMDerror YY_ARGS((char *fmt, ...)); +extern void CMDcomment YY_ARGS((char *term)); +extern int CMDmapch YY_ARGS((int delim, int escape)); + +#line 1 "console/scan.l" + +#define YYLMAX 4096 + +#include "platform/platform.h" +#include "console/console.h" +#include "console/ast.h" +#include "console/gram.h" +#include "core/stringTable.h" + +static int Sc_ScanString(int ret); +static int Sc_ScanNum(); +static int Sc_ScanVar(); +static int Sc_ScanHex(); + +#define FLEX_DEBUG 1 + +//#undef input +//#undef unput +#undef CMDgetc +int CMDgetc(); +static int lineIndex; +extern DataChunker consoleAllocator; + +// Prototypes +void SetScanBuffer(const char *sb, const char *fn); +const char * CMDgetFileLine(int &lineNumber); +void CMDerror(char * s, ...); + +#line 127 "console/yylex.c" + + +#ifndef YYLMAX +#define YYLMAX 100 /* token and pushback buffer size */ +#endif /* YYLMAX */ + +/* + * If %array is used (or defaulted), CMDtext[] contains the token. + * If %pointer is used, CMDtext is a pointer to CMD_tbuf[]. + */ +YY_DECL char CMDtext[YYLMAX+1]; + + + +#ifdef YY_DEBUG +#undef YY_DEBUG +#define YY_DEBUG(fmt, a1, a2) fprintf(stderr, fmt, a1, a2) +#else +#define YY_DEBUG(fmt, a1, a2) +#endif + +/* + * The declaration for the lex scanner can be changed by + * redefining YYLEX or YYDECL. This must be done if you have + * more than one scanner in a program. + */ +#ifndef YYLEX +#define YYLEX CMDlex /* name of lex scanner */ +#endif + +#ifndef YYDECL +#define YYDECL int YYLEX YY_ARGS((void)) /* declaration for lex scanner */ +#endif + +/* + * stdin and stdout may not neccessarily be constants. + * If stdin and stdout are constant, and you want to save a few cycles, then + * #define YY_STATIC_STDIO 1 in this file or on the commandline when + * compiling this file + */ +#ifndef YY_STATIC_STDIO +#define YY_STATIC_STDIO 0 +#endif + +#if YY_STATIC_STDIO +YY_DECL FILE *CMDin = stdin; +YY_DECL FILE *CMDout = stdout; +#else +YY_DECL FILE *CMDin = (FILE *)0; +YY_DECL FILE *CMDout = (FILE *)0; +#endif +YY_DECL int CMDlineno = 1; /* line number */ + +/* CMD_sbuf[0:CMDleng-1] contains the states corresponding to CMDtext. + * CMDtext[0:CMDleng-1] contains the current token. + * CMDtext[CMDleng:CMD_end-1] contains pushed-back characters. + * When the user action routine is active, + * CMD_save contains CMDtext[CMDleng], which is set to '\0'. + * Things are different when YY_PRESERVE is defined. + */ +static CMD_state_t CMD_sbuf [YYLMAX+1]; /* state buffer */ +static int CMD_end = 0; /* end of pushback */ +static int CMD_start = 0; /* start state */ +static int CMD_lastc = YYNEWLINE; /* previous char */ +YY_DECL int CMDleng = 0; /* CMDtext token length */ +#ifdef YYEXIT +static int CMDLexFatal; +#endif /* YYEXIT */ + +#ifndef YY_PRESERVE /* the efficient default push-back scheme */ + +static char CMD_save; /* saved CMDtext[CMDleng] */ + +#define YY_USER { /* set up CMDtext for user */ \ + CMD_save = CMDtext[CMDleng]; \ + CMDtext[CMDleng] = 0; \ + } +#define YY_SCANNER { /* set up CMDtext for scanner */ \ + CMDtext[CMDleng] = CMD_save; \ + } + +#else /* not-so efficient push-back for CMDtext mungers */ + +static char CMD_save [YYLMAX]; +static char *CMD_push = CMD_save+YYLMAX; + +#define YY_USER { \ + size_t n = CMD_end - CMDleng; \ + CMD_push = CMD_save+YYLMAX - n; \ + if (n > 0) \ + memmove(CMD_push, CMDtext+CMDleng, n); \ + CMDtext[CMDleng] = 0; \ + } +#define YY_SCANNER { \ + size_t n = CMD_save+YYLMAX - CMD_push; \ + if (n > 0) \ + memmove(CMDtext+CMDleng, CMD_push, n); \ + CMD_end = CMDleng + n; \ + } + +#endif + + +#ifdef LEX_WINDOWS + +/* + * When using the windows features of lex, + * it is necessary to load in the resources being + * used, and when done with them, the resources must + * be freed up, otherwise we have a windows app that + * is not following the rules. Thus, to make CMDlex() + * behave in a windows environment, create a new + * CMDlex() which will call the original CMDlex() as + * another function call. Observe ... + */ + +/* + * The actual lex scanner (usually CMDlex(void)). + * NOTE: you should invoke CMD_init() if you are calling CMDlex() + * with new input; otherwise old lookaside will get in your way + * and CMDlex() will die horribly. + */ +static int win_CMDlex(); /* prototype for windows CMDlex handler */ + +YYDECL { + int wReturnValue; + HANDLE hRes_table; + unsigned short far *old_CMD_la_act; /* remember previous pointer values */ + short far *old_CMD_final; + CMD_state_t far *old_CMD_begin; + CMD_state_t far *old_CMD_next; + CMD_state_t far *old_CMD_check; + CMD_state_t far *old_CMD_default; + short far *old_CMD_base; + + /* + * the following code will load the required + * resources for a Windows based parser. + */ + + hRes_table = LoadResource (hInst, + FindResource (hInst, "UD_RES_CMDLEX", "CMDLEXTBL")); + + /* + * return an error code if any + * of the resources did not load + */ + + if (hRes_table == NULL) + return (0); + + /* + * the following code will lock the resources + * into fixed memory locations for the scanner + * (and remember previous pointer locations) + */ + + old_CMD_la_act = CMD_la_act; + old_CMD_final = CMD_final; + old_CMD_begin = CMD_begin; + old_CMD_next = CMD_next; + old_CMD_check = CMD_check; + old_CMD_default = CMD_default; + old_CMD_base = CMD_base; + + CMD_la_act = (unsigned short far *)LockResource (hRes_table); + CMD_final = (short far *)(CMD_la_act + Sizeof_CMD_la_act); + CMD_begin = (CMD_state_t far *)(CMD_final + Sizeof_CMD_final); + CMD_next = (CMD_state_t far *)(CMD_begin + Sizeof_CMD_begin); + CMD_check = (CMD_state_t far *)(CMD_next + Sizeof_CMD_next); + CMD_default = (CMD_state_t far *)(CMD_check + Sizeof_CMD_check); + CMD_base = (CMD_state_t far *)(CMD_default + Sizeof_CMD_default); + + + /* + * call the standard CMDlex() code + */ + + wReturnValue = win_CMDlex(); + + /* + * unlock the resources + */ + + UnlockResource (hRes_table); + + /* + * and now free the resource + */ + + FreeResource (hRes_table); + + /* + * restore previously saved pointers + */ + + CMD_la_act = old_CMD_la_act; + CMD_final = old_CMD_final; + CMD_begin = old_CMD_begin; + CMD_next = old_CMD_next; + CMD_check = old_CMD_check; + CMD_default = old_CMD_default; + CMD_base = old_CMD_base; + + return (wReturnValue); +} /* end function */ + +static int win_CMDlex() { + +#else /* LEX_WINDOWS */ + +/* + * The actual lex scanner (usually CMDlex(void)). + * NOTE: you should invoke CMD_init() if you are calling CMDlex() + * with new input; otherwise old lookaside will get in your way + * and CMDlex() will die horribly. + */ +YYDECL { + +#endif /* LEX_WINDOWS */ + + register int c, i, CMDbase; + unsigned CMDst; /* state */ + int CMDfmin, CMDfmax; /* CMD_la_act indices of final states */ + int CMDoldi, CMDoleng; /* base i, CMDleng before look-ahead */ + int CMDeof; /* 1 if eof has already been read */ +#line 47 "console/scan.l" + ; + +#line 350 "console/yylex.c" + + + +#if !YY_STATIC_STDIO + if (CMDin == (FILE *)0) + CMDin = stdin; + if (CMDout == (FILE *)0) + CMDout = stdout; +#endif + +#ifdef YYEXIT + CMDLexFatal = 0; +#endif /* YYEXIT */ + + CMDeof = 0; + i = CMDleng; + YY_SCANNER; + + CMD_again: + CMDleng = i; + /* determine previous char. */ + if (i > 0) + CMD_lastc = CMDtext[i-1]; + /* scan previously accepted token adjusting CMDlineno */ + while (i > 0) + if (CMDtext[--i] == YYNEWLINE) + CMDlineno++; + /* adjust pushback */ + CMD_end -= CMDleng; + if (CMD_end > 0) + memmove(CMDtext, CMDtext+CMDleng, (size_t) CMD_end); + i = 0; + + CMD_contin: + CMDoldi = i; + + /* run the state machine until it jams */ + CMDst = CMD_begin[CMD_start + ((CMD_lastc == YYNEWLINE) ? 1 : 0)]; + CMD_sbuf[i] = (CMD_state_t) CMDst; + do { + YY_DEBUG(m_textmsg(1547, "\n", "I num1 num2"), CMDst, i); + if (i >= YYLMAX) { + YY_FATAL(m_textmsg(1548, "Token buffer overflow", "E")); +#ifdef YYEXIT + if (CMDLexFatal) + return -2; +#endif /* YYEXIT */ + } /* endif */ + + /* get input char */ + if (i < CMD_end) + c = CMDtext[i]; /* get pushback char */ + else if (!CMDeof && (c = CMDgetc()) != EOF) { + CMD_end = i+1; + CMDtext[i] = (char) c; + } else /* c == EOF */ { + c = EOF; /* just to make sure... */ + if (i == CMDoldi) { /* no token */ + CMDeof = 0; + if (CMDwrap()) + return 0; + else + goto CMD_again; + } else { + CMDeof = 1; /* don't re-read EOF */ + break; + } + } + YY_DEBUG(m_textmsg(1549, "\n", "I num hexnum"), c, c); + + /* look up next state */ + while ((CMDbase = CMD_base[CMDst]+(unsigned char)c) > CMD_nxtmax + || CMD_check[CMDbase] != (CMD_state_t) CMDst) { + if (CMDst == CMD_endst) + goto CMD_jammed; + CMDst = CMD_default[CMDst]; + } + CMDst = CMD_next[CMDbase]; + CMD_jammed: ; + CMD_sbuf[++i] = (CMD_state_t) CMDst; + } while (!(CMDst == CMD_endst || YY_INTERACTIVE && CMD_base[CMDst] > CMD_nxtmax && CMD_default[CMDst] == CMD_endst)); + YY_DEBUG(m_textmsg(1550, "\n", "I num1 num2"), CMDst, i); + if (CMDst != CMD_endst) + ++i; + + CMD_search: + /* search backward for a final state */ + while (--i > CMDoldi) { + CMDst = CMD_sbuf[i]; + if ((CMDfmin = CMD_final[CMDst]) < (CMDfmax = CMD_final[CMDst+1])) + goto CMD_found; /* found final state(s) */ + } + /* no match, default action */ + i = CMDoldi + 1; + output(CMDtext[CMDoldi]); + goto CMD_again; + + CMD_found: + YY_DEBUG(m_textmsg(1551, "\n", "I num1 num2"), CMDst, i); + CMDoleng = i; /* save length for REJECT */ + + /* pushback look-ahead RHS */ + if ((c = (int)(CMD_la_act[CMDfmin]>>9) - 1) >= 0) { /* trailing context? */ + unsigned char *bv = CMD_look + c*YY_LA_SIZE; + static unsigned char bits [8] = { + 1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7 + }; + while (1) { + if (--i < CMDoldi) { /* no / */ + i = CMDoleng; + break; + } + CMDst = CMD_sbuf[i]; + if (bv[(unsigned)CMDst/8] & bits[(unsigned)CMDst%8]) + break; + } + } + + /* perform action */ + CMDleng = i; + YY_USER; + switch (CMD_la_act[CMDfmin] & 0777) { + case 0: +#line 48 "console/scan.l" + { } + break; + case 1: +#line 49 "console/scan.l" + ; + break; + case 2: +#line 50 "console/scan.l" + ; + break; + case 3: +#line 51 "console/scan.l" + {lineIndex++;} + break; + case 4: +#line 52 "console/scan.l" + { return(Sc_ScanString(STRATOM)); } + break; + case 5: +#line 53 "console/scan.l" + { return(Sc_ScanString(TAGATOM)); } + break; + case 6: +#line 54 "console/scan.l" + return(CMDlval.i = opEQ); + break; + case 7: +#line 55 "console/scan.l" + return(CMDlval.i = opNE); + break; + case 8: +#line 56 "console/scan.l" + return(CMDlval.i = opGE); + break; + case 9: +#line 57 "console/scan.l" + return(CMDlval.i = opLE); + break; + case 10: +#line 58 "console/scan.l" + return(CMDlval.i = opAND); + break; + case 11: +#line 59 "console/scan.l" + return(CMDlval.i = opOR); + break; + case 12: +#line 60 "console/scan.l" + return(CMDlval.i = opCOLONCOLON); + break; + case 13: +#line 61 "console/scan.l" + return(CMDlval.i = opMINUSMINUS); + break; + case 14: +#line 62 "console/scan.l" + return(CMDlval.i = opPLUSPLUS); + break; + case 15: +#line 63 "console/scan.l" + return(CMDlval.i = opSTREQ); + break; + case 16: +#line 64 "console/scan.l" + return(CMDlval.i = opSTRNE); + break; + case 17: +#line 65 "console/scan.l" + return(CMDlval.i = opSHL); + break; + case 18: +#line 66 "console/scan.l" + return(CMDlval.i = opSHR); + break; + case 19: +#line 67 "console/scan.l" + return(CMDlval.i = opPLASN); + break; + case 20: +#line 68 "console/scan.l" + return(CMDlval.i = opMIASN); + break; + case 21: +#line 69 "console/scan.l" + return(CMDlval.i = opMLASN); + break; + case 22: +#line 70 "console/scan.l" + return(CMDlval.i = opDVASN); + break; + case 23: +#line 71 "console/scan.l" + return(CMDlval.i = opMODASN); + break; + case 24: +#line 72 "console/scan.l" + return(CMDlval.i = opANDASN); + break; + case 25: +#line 73 "console/scan.l" + return(CMDlval.i = opXORASN); + break; + case 26: +#line 74 "console/scan.l" + return(CMDlval.i = opORASN); + break; + case 27: +#line 75 "console/scan.l" + return(CMDlval.i = opSLASN); + break; + case 28: +#line 76 "console/scan.l" + return(CMDlval.i = opSRASN); + break; + case 29: +#line 77 "console/scan.l" + {CMDlval.i = '\n'; return '@'; } + break; + case 30: +#line 78 "console/scan.l" + {CMDlval.i = '\t'; return '@'; } + break; + case 31: +#line 79 "console/scan.l" + {CMDlval.i = ' '; return '@'; } + break; + case 32: +#line 80 "console/scan.l" + {CMDlval.i = 0; return '@'; } + break; + case 33: + case 34: + case 35: + case 36: + case 37: + case 38: + case 39: + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + case 52: + case 53: + case 54: + case 55: + case 56: +#line 104 "console/scan.l" + { return(CMDlval.i = CMDtext[0]); } + break; + case 57: +#line 105 "console/scan.l" + { CMDlval.i = lineIndex; return(rwCASEOR); } + break; + case 58: +#line 106 "console/scan.l" + { CMDlval.i = lineIndex; return(rwBREAK); } + break; + case 59: +#line 107 "console/scan.l" + { CMDlval.i = lineIndex; return(rwRETURN); } + break; + case 60: +#line 108 "console/scan.l" + { CMDlval.i = lineIndex; return(rwELSE); } + break; + case 61: +#line 109 "console/scan.l" + { CMDlval.i = lineIndex; return(rwWHILE); } + break; + case 62: +#line 110 "console/scan.l" + { CMDlval.i = lineIndex; return(rwIF); } + break; + case 63: +#line 111 "console/scan.l" + { CMDlval.i = lineIndex; return(rwFOR); } + break; + case 64: +#line 112 "console/scan.l" + { CMDlval.i = lineIndex; return(rwCONTINUE); } + break; + case 65: +#line 113 "console/scan.l" + { CMDlval.i = lineIndex; return(rwDEFINE); } + break; + case 66: +#line 114 "console/scan.l" + { CMDlval.i = lineIndex; return(rwDECLARE); } + break; + case 67: +#line 115 "console/scan.l" + { CMDlval.i = lineIndex; return(rwDATABLOCK); } + break; + case 68: +#line 116 "console/scan.l" + { CMDlval.i = lineIndex; return(rwCASE); } + break; + case 69: +#line 117 "console/scan.l" + { CMDlval.i = lineIndex; return(rwSWITCHSTR); } + break; + case 70: +#line 118 "console/scan.l" + { CMDlval.i = lineIndex; return(rwSWITCH); } + break; + case 71: +#line 119 "console/scan.l" + { CMDlval.i = lineIndex; return(rwDEFAULT); } + break; + case 72: +#line 120 "console/scan.l" + { CMDlval.i = lineIndex; return(rwPACKAGE); } + break; + case 73: +#line 121 "console/scan.l" + { CMDlval.i = 1; return INTCONST; } + break; + case 74: +#line 122 "console/scan.l" + { CMDlval.i = 0; return INTCONST; } + break; + case 75: +#line 123 "console/scan.l" + return(Sc_ScanVar()); + break; + case 76: +#line 124 "console/scan.l" + { CMDtext[CMDleng] = 0; CMDlval.s = StringTable->insert(CMDtext); return(IDENT); } + break; + case 77: +#line 125 "console/scan.l" + return(Sc_ScanHex()); + break; + case 78: +#line 126 "console/scan.l" + { CMDtext[CMDleng] = 0; CMDlval.i = atoi(CMDtext); return INTCONST; } + break; + case 79: +#line 127 "console/scan.l" + return Sc_ScanNum(); + break; + case 80: +#line 128 "console/scan.l" + return(ILLEGAL_TOKEN); + break; + case 81: +#line 129 "console/scan.l" + return(ILLEGAL_TOKEN); + break; + +#line 472 "console/yylex.c" + + } + YY_SCANNER; + i = CMDleng; + goto CMD_again; /* action fell though */ + + CMD_reject: + YY_SCANNER; + i = CMDoleng; /* restore original CMDtext */ + if (++CMDfmin < CMDfmax) + goto CMD_found; /* another final state, same length */ + else + goto CMD_search; /* try shorter CMDtext */ + + CMD_more: + YY_SCANNER; + i = CMDleng; + if (i > 0) + CMD_lastc = CMDtext[i-1]; + goto CMD_contin; +} +/* + * Safely switch input stream underneath LEX + */ +typedef struct CMD_save_block_tag { + FILE * oldfp; + int oldline; + int oldend; + int oldstart; + int oldlastc; + int oldleng; + char savetext[YYLMAX+1]; + CMD_state_t savestate[YYLMAX+1]; +} YY_SAVED; + +void +CMD_reset() +{ + YY_INIT; + CMDlineno = 1; /* line number */ +} + +#if 0 +YY_SAVED * +CMDSaveScan(fp) +FILE * fp; +{ + YY_SAVED * p; + + if ((p = (YY_SAVED *) malloc(sizeof(*p))) == NULL) + return p; + + p->oldfp = CMDin; + p->oldline = CMDlineno; + p->oldend = CMD_end; + p->oldstart = CMD_start; + p->oldlastc = CMD_lastc; + p->oldleng = CMDleng; + (void) memcpy(p->savetext, CMDtext, sizeof CMDtext); + (void) memcpy((char *) p->savestate, (char *) CMD_sbuf, + sizeof CMD_sbuf); + + CMDin = fp; + CMDlineno = 1; + YY_INIT; + + return p; +} +/*f + * Restore previous LEX state + */ +void +CMDRestoreScan(p) +YY_SAVED * p; +{ + if (p == NULL) + return; + CMDin = p->oldfp; + CMDlineno = p->oldline; + CMD_end = p->oldend; + CMD_start = p->oldstart; + CMD_lastc = p->oldlastc; + CMDleng = p->oldleng; + + (void) memcpy(CMDtext, p->savetext, sizeof CMDtext); + (void) memcpy((char *) CMD_sbuf, (char *) p->savestate, + sizeof CMD_sbuf); + free(p); +} +/* + * User-callable re-initialization of CMDlex() + */ +/* get input char with pushback */ +YY_DECL int +input() +{ + int c; +#ifndef YY_PRESERVE + if (CMD_end > CMDleng) { + CMD_end--; + memmove(CMDtext+CMDleng, CMDtext+CMDleng+1, + (size_t) (CMD_end-CMDleng)); + c = CMD_save; + YY_USER; +#else + if (CMD_push < CMD_save+YYLMAX) { + c = *CMD_push++; +#endif + } else + c = CMDgetc(); + CMD_lastc = c; + if (c == YYNEWLINE) + CMDlineno++; + if (c == EOF) /* CMDgetc() can set c=EOF vsc4 wants c==EOF to return 0 */ + return 0; + else + return c; +} + +/*f + * pushback char + */ +YY_DECL int +unput(c) + int c; +{ +#ifndef YY_PRESERVE + if (CMD_end >= YYLMAX) { + YY_FATAL(m_textmsg(1552, "Push-back buffer overflow", "E")); + } else { + if (CMD_end > CMDleng) { + CMDtext[CMDleng] = CMD_save; + memmove(CMDtext+CMDleng+1, CMDtext+CMDleng, + (size_t) (CMD_end-CMDleng)); + CMDtext[CMDleng] = 0; + } + CMD_end++; + CMD_save = (char) c; +#else + if (CMD_push <= CMD_save) { + YY_FATAL(m_textmsg(1552, "Push-back buffer overflow", "E")); + } else { + *--CMD_push = c; +#endif + if (c == YYNEWLINE) + CMDlineno--; + } /* endif */ + return c; +} + +#endif + +#line 131 "console/scan.l" + +/* + * Scan character constant. + */ + +/* + * Scan identifier. + */ + +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 *, ...) +{ + gConsoleSyntaxError = true; + if(fileName) + { + Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - Syntax error.", + fileName, lineIndex); + char tempBuf[256]; + 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); + } + else + Con::errorf(ConsoleLogEntry::Script, "Syntax error in input."); +} + +void SetScanBuffer(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() +{ + CMDtext[CMDleng] = 0; + CMDlval.s = StringTable->insert(CMDtext); + return(VAR); +} +/* + * Scan string constant. + */ + +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_ScanString(int ret) +{ + CMDtext[CMDleng - 1] = 0; + if(!collapseEscape(CMDtext+1)) + return -1; + CMDlval.str = (char *) consoleAllocator.alloc(dStrlen(CMDtext)); + dStrcpy(CMDlval.str, CMDtext + 1); + return(ret); +} + +void expandEscape(char *dest, const char *src) +{ + unsigned char c; + while((c = (unsigned char) *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 >= 2 && c <= 8) || + (c >= 11 && c <= 12) || + (c >= 14 && c <= 15)) + { + /* Remap around: \t = 0x9, \n = 0xa, \r = 0xd */ + static char expandRemap[15] = { 0x0, + 0x0, + 0x0, + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 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) +{ + int len = dStrlen(buf) + 1; + for(int i = 0; i < len;) + { + if(buf[i] == '\\') + { + if(buf[i+1] == 'x') + { + int dig1 = getHexDigit(buf[i+2]); + if(dig1 == -1) + return false; + + int 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: \t = 0x9, \n = 0xa, \r = 0xd */ + static char collapseRemap[10] = { 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x7, + 0x8, + 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]; + } + 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 = atof(CMDtext); + return(FLTCONST); +} + +static int Sc_ScanHex() +{ + int val = 0; + dSscanf(CMDtext, "%x", &val); + CMDlval.i = val; + return INTCONST; +} + diff --git a/console/scan.l b/console/scan.l new file mode 100644 index 0000000..5fc07d6 --- /dev/null +++ b/console/scan.l @@ -0,0 +1,405 @@ +%{ +#define YYLMAX 4096 + +#include "platform.h" +#include "console.h" +#include "ast.h" +#include "gram.h" +#include "stringTable.h" + +static int Sc_ScanString(int ret); +static int Sc_ScanNum(); +static int Sc_ScanVar(); +static int Sc_ScanHex(); + +#define FLEX_DEBUG 1 + +//#undef input +//#undef unput +#undef CMDgetc +int CMDgetc(); +static int lineIndex; +extern DataChunker consoleAllocator; + +// Prototypes +void SetScanBuffer(const char *sb, const char *fn); +const char * CMDgetFileLine(int &lineNumber); +void CMDerror(char * s, ...); + +%} + +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]* ; +[\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); +"NL" {CMDlval.i = '\n'; return '@'; } +"TAB" {CMDlval.i = '\t'; return '@'; } +"SPC" {CMDlval.i = ' '; return '@'; } +"@" {CMDlval.i = 0; return '@'; } +"?" | +"[" | +"]" | +"(" | +")" | +"+" | +"-" | +"*" | +"/" | +"<" | +">" | +"|" | +"." | +"!" | +":" | +";" | +"{" | +"}" | +"," | +"&" | +"%" | +"^" | +"~" | +"=" { 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); } +"while" { CMDlval.i = lineIndex; return(rwWHILE); } +"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); } +"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); } +"true" { CMDlval.i = 1; return INTCONST; } +"false" { CMDlval.i = 0; return INTCONST; } +{VAR} return(Sc_ScanVar()); +{ID} { CMDtext[CMDleng] = 0; CMDlval.s = StringTable->insert(CMDtext); return(IDENT); } +0[xX]{HEXDIGIT}+ return(Sc_ScanHex()); +{INTEGER} { CMDtext[CMDleng] = 0; CMDlval.i = atoi(CMDtext); return INTCONST; } +{FLOAT} return Sc_ScanNum(); +{ILID} return(ILLEGAL_TOKEN); +. return(ILLEGAL_TOKEN); +%% + +/* + * Scan character constant. + */ + +/* + * Scan identifier. + */ + +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 *, ...) +{ + gConsoleSyntaxError = true; + if(fileName) + Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - Syntax error.", + fileName, lineIndex); + else + Con::errorf(ConsoleLogEntry::Script, "Syntax error in input."); +} + +void SetScanBuffer(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() +{ + CMDtext[CMDleng] = 0; + CMDlval.s = StringTable->insert(CMDtext); + return(VAR); +} +/* + * Scan string constant. + */ + +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_ScanString(int ret) +{ + CMDtext[CMDleng - 1] = 0; + if(!collapseEscape(CMDtext+1)) + return -1; + CMDlval.str = (char *) consoleAllocator.alloc(dStrlen(CMDtext)); + dStrcpy(CMDlval.str, CMDtext + 1); + return(ret); +} + +void expandEscape(char *dest, const char *src) +{ + unsigned char c; + while((c = (unsigned char) *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 >= 2 && c <= 8) || + (c >= 11 && c <= 12) || + (c >= 14 && c <= 15)) + { + /* Remap around: \t = 0x9, \n = 0xa, \r = 0xd */ + static char expandRemap[15] = { 0x0, + 0x0, + 0x0, + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 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) +{ + int len = dStrlen(buf) + 1; + for(int i = 0; i < len;) + { + if(buf[i] == '\\') + { + if(buf[i+1] == 'x') + { + int dig1 = getHexDigit(buf[i+2]); + if(dig1 == -1) + return false; + + int 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: \t = 0x9, \n = 0xa, \r = 0xd */ + static char collapseRemap[10] = { 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x7, + 0x8, + 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]; + } + 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 = atof(CMDtext); + return(FLTCONST); +} + +static int Sc_ScanHex() +{ + int val = 0; + dSscanf(CMDtext, "%x", &val); + CMDlval.i = val; + return INTCONST; +} + diff --git a/console/scriptObject.cc b/console/scriptObject.cc new file mode 100644 index 0000000..ec53cf4 --- /dev/null +++ b/console/scriptObject.cc @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/simBase.h" +#include "console/consoleTypes.h" + +class ScriptObject : public SimObject +{ + typedef SimObject Parent; + StringTableEntry mClassName; + StringTableEntry mSuperClassName; +public: + ScriptObject(); + bool onAdd(); + void onRemove(); + + DECLARE_CONOBJECT(ScriptObject); + + static void initPersistFields(); +}; + +IMPLEMENT_CONOBJECT(ScriptObject); + +void ScriptObject::initPersistFields() +{ + addField("class", TypeString, Offset(mClassName, ScriptObject)); + addField("superClass", TypeString, Offset(mSuperClassName, ScriptObject)); +} + +ScriptObject::ScriptObject() +{ + mClassName = ""; + mSuperClassName = ""; +} + +bool ScriptObject::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if(mClassName[0]) + { + if(mSuperClassName[0]) + { + Con::linkNamespaces(mSuperClassName, mClassName); + Con::linkNamespaces("ScriptObject", mSuperClassName); + } + else + Con::linkNamespaces("ScriptObject", mClassName); + + mNameSpace = Con::lookupNamespace(mClassName); + } + return true; +} + +void ScriptObject::onRemove() +{ + Parent::onRemove(); +} diff --git a/console/simBase.cc b/console/simBase.cc new file mode 100644 index 0000000..c6d7e32 --- /dev/null +++ b/console/simBase.cc @@ -0,0 +1,1527 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simBase.h" +#include "core/stringTable.h" +#include "console/console.h" +#include "core/fileStream.h" +#include "sim/actionMap.h" +#include "core/resManager.h" +#include "core/fileObject.h" +#include "game/objectTypes.h" +#include "console/consoleInternal.h" + + +namespace Sim +{ + // Don't forget to InstantiateNamed* in simManager.cc - DMM + ImplementNamedSet(ActiveActionMapSet) + ImplementNamedSet(GhostAlwaysSet) + ImplementNamedSet(LightSet) + ImplementNamedSet(WayPointSet) + ImplementNamedSet(ClientTargetSet) + ImplementNamedSet(ServerTargetSet) + ImplementNamedSet(FlareSet) + ImplementNamedSet(MissileSet) + ImplementNamedSet(CommandTargetSet) + ImplementNamedSet(ScopeSensorVisibleSet) + ImplementNamedGroup(ActionMapGroup) + ImplementNamedGroup(ClientGroup) + ImplementNamedGroup(GuiGroup) + ImplementNamedGroup(GuiDataGroup) + ImplementNamedGroup(TCPGroup) + + //groups created on the client + ImplementNamedGroup(ClientConnectionGroup) + } + +// for isServer() only... + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +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()) { + iterator last = end() - 1; + *ptr = *last; + erase(last); + } +} + +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); +} + + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +SimFieldDictionary::Entry *SimFieldDictionary::mFreeList = NULL; + +static Chunker fieldChunker; + +SimFieldDictionary::Entry *SimFieldDictionary::allocEntry() +{ + if(mFreeList) + { + Entry *ret = mFreeList; + mFreeList = ret->next; + return ret; + } + else + return fieldChunker.alloc(); +} + +void SimFieldDictionary::freeEntry(SimFieldDictionary::Entry *ent) +{ + ent->next = mFreeList; + mFreeList = ent; +} + +SimFieldDictionary::SimFieldDictionary() +{ + for(U32 i = 0; i < HashTableSize; i++) + mHashTable[i] = 0; +} + +SimFieldDictionary::~SimFieldDictionary() +{ + for(U32 i = 0; i < HashTableSize; i++) + { + for(Entry *walk = mHashTable[i]; walk;) + { + Entry *temp = walk; + walk = temp->next; + + dFree(temp->value); + freeEntry(temp); + } + } +} + +void SimFieldDictionary::setFieldValue(StringTableEntry slotName, const char *value) +{ + U32 bucket = HashPointer(slotName) % HashTableSize; + Entry **walk = &mHashTable[bucket]; + while(*walk && (*walk)->slotName != slotName) + walk = &((*walk)->next); + + Entry *field = *walk; + if(!*value) + { + if(field) + { + dFree(field->value); + *walk = field->next; + freeEntry(field); + } + } + else + { + if(field) + { + dFree(field->value); + field->value = dStrdup(value); + } + else + { + field = allocEntry(); + field->value = dStrdup(value); + field->slotName = slotName; + field->next = NULL; + *walk = field; + } + } +} + +const char *SimFieldDictionary::getFieldValue(StringTableEntry slotName) +{ + U32 bucket = HashPointer(slotName) % HashTableSize; + + for(Entry *walk = mHashTable[bucket];walk;walk = walk->next) + if(walk->slotName == slotName) + return walk->value; + return NULL; +} + +//--------------------------------------------------------------------------- + +SimObject::SimObject() +{ + objectName = NULL; + nextNameObject = (SimObject*)-1; + nextManagerNameObject = (SimObject*)-1; + nextIdObject = NULL; + + mId = 0; + mGroup = 0; + mNameSpace = NULL; + mNotifyList = NULL; + mFlags.set(ModDynamicFields); + mTypeMask = 0; + + mFieldDictionary = NULL; +} + +static void writeTabs(Stream &stream, U32 count) +{ + while(count--) + stream.write(U8('\t')); +} + +void SimFieldDictionary::assignFrom(SimFieldDictionary *dict) +{ + for(U32 i = 0; i < HashTableSize; i++) + for(Entry *walk = dict->mHashTable[i];walk; walk = walk->next) + setFieldValue(walk->slotName, walk->value); +} + +void SimFieldDictionary::writeFields(SimObject *obj, Stream &stream, U32 tabStop) +{ + const AbstractClassRep::FieldList &list = obj->getFieldList(); + char expandedBuffer[1024]; + 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; + writeTabs(stream, tabStop+1); + dSprintf(expandedBuffer, sizeof(expandedBuffer), "%s = \"", walk->slotName); + expandEscape(expandedBuffer + dStrlen(expandedBuffer), walk->value); + dStrcat(expandedBuffer, "\";\r\n"); + stream.write(dStrlen(expandedBuffer),expandedBuffer); + } + } +} + +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::printFields(SimObject *obj) +{ + const AbstractClassRep::FieldList &list = obj->getFieldList(); + char expandedBuffer[1024]; + 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); + expandEscape(expandedBuffer + dStrlen(expandedBuffer), (*itr)->value); + Con::printf("%s\"", expandedBuffer); + } +} + +//------------------------------------------------------------------------------ +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); +} + +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]; + if(f->elementCount == 1) + { + const char *fieldVal = Con::getData(f->type, (void *) (U32(parent) + f->offset), 0, f->table, f->flag); + if(fieldVal) + Con::setData(f->type, (void *) (U32(this) + f->offset), 0, 1, &fieldVal, f->table); + } + else + { + for(U32 j = 0; j < f->elementCount; j++) + { + const char *fieldVal = Con::getData(f->type, (void *) (U32(parent) + f->offset), j, f->table, f->flag); + if(fieldVal) + Con::setData(f->type, (void *) (U32(this) + f->offset), j, 1, &fieldVal, f->table); + } + } + } + } + if(parent->mFieldDictionary) + { + mFieldDictionary = new SimFieldDictionary; + mFieldDictionary->assignFrom(parent->mFieldDictionary); + } +} + +void SimObject::writeFields(Stream &stream, U32 tabStop) +{ + const AbstractClassRep::FieldList &list = getFieldList(); + char expandedBuffer[1024]; + + for(U32 i = 0; i < list.size(); i++) + { + const AbstractClassRep::Field* f = &list[i]; + for(U32 j = 0; S32(j) < f->elementCount; j++) + { + if(f->type == AbstractClassRep::DepricatedFieldType) + continue; + const char *val = Con::getData(f->type, (void *) (U32(this) + f->offset), j, f->table, f->flag); + 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); + dStrcat(expandedBuffer, "\";\r\n"); + + writeTabs(stream, tabStop); + stream.write(dStrlen(expandedBuffer),expandedBuffer); + } + } + if(mFieldDictionary) + mFieldDictionary->writeFields(this, stream, tabStop); +} + +void SimObject::write(Stream &stream, U32 tabStop, U32 flags) +{ + // + if((flags & SelectedOnly) && !isSelected()) + return; + + writeTabs(stream, 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); + writeTabs(stream, tabStop); + stream.write(4, "};\r\n"); +} + +ConsoleFunction(nameToID, S32, 2, 2, "nameToID(object)") +{ + argc; + SimObject *obj = Sim::findObject(argv[1]); + if(obj) + return obj->getId(); + else + return -1; +} + +ConsoleFunction(isObject, bool, 2, 2, "isObject(object)") +{ + argc; + if (!dStrcmp(argv[1], "0") || !dStrcmp(argv[1], "")) + return false; + else + return (Sim::findObject(argv[1]) != NULL); +} + + +ConsoleMethod(SimObject, save, bool, 3, 4, "obj.save(fileName, )") +{ + static const char *beginMessage = "//--- OBJECT WRITE BEGIN ---"; + static const char *endMessage = "//--- OBJECT WRITE END ---"; + FileStream stream; + FileObject f; + f.readMemory(argv[2]); + + // check for flags + U32 writeFlags = 0; + if(argc > 3) + { + if(dAtob(argv[3])) + writeFlags |= SimObject::SelectedOnly; + } + + if(!ResourceManager->openFileForWrite(stream, ResourceManager->getModPathOf(argv[2]), argv[2])) + return false; + + 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"); + object->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"); + } + return true; +} + +ConsoleMethod(SimObject, setName, void, 3, 3, "obj.setName(newName)") +{ + argc; + object->assignName(argv[2]); +} + +ConsoleMethod(SimObject, getName, const char *, 2, 2, "obj.getName()") +{ + argc; argv; + const char *ret = object->getName(); + return ret ? ret : ""; +} + +ConsoleMethod(SimObject, getClassName, const char *, 2, 2, "obj.getClassName()") +{ + argc; argv; + const char *ret = object->getClassName(); + return ret ? ret : ""; +} + +ConsoleMethod(SimObject, getId, S32, 2, 2, "obj.getId()") +{ + argc; argv; + return object->getId(); +} + +ConsoleMethod(SimObject, getGroup, S32, 2, 2, "obj.getGroup()") +{ + argc; argv; + SimGroup *grp = object->getGroup(); + if(!grp) + return -1; + return grp->getId(); +} + +ConsoleMethod(SimObject,delete,void,2,2,"obj.delete()") +{ + argc;argv; + object->deleteObject(); +} + +ConsoleFunction(cancel,void,2,2,"cancel(eventId)") +{ + argc; + Sim::cancelEvent(dAtoi(argv[1])); +} + +ConsoleFunction(isEventPending, bool, 2, 2, "isEventPending(%scheduleId);") +{ + argc; + return Sim::isEventPending(dAtoi(argv[1])); +} + +ConsoleFunction(schedule, S32, 4, 0, "object.schedule(time, 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(1, "backtrace"); +// #endif + return ret; +} + +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(1, "backtrace"); +// #endif + return ret; +} + +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); +} + +ConsoleMethod(SimObject,dump, void, 2, 2, "obj.dump()") +{ + argc; argv; + const AbstractClassRep::FieldList &list = object->getFieldList(); + char expandedBuffer[1024]; + + 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; + for(U32 j = 0; S32(j) < f->elementCount; j++) + { + if(f->type == AbstractClassRep::DepricatedFieldType) + continue; + const char *val = Con::getData(f->type, (void *) (U32(object) + f->offset), j, f->table, f->flag); + 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(); + Vector vec(__FILE__, __LINE__); + if(ns) + ns->getEntryList(&vec); + for(Vector::iterator j = vec.begin(); j != vec.end(); j++) + Con::printf(" %s() - %s", (*j)->mFunctionName, (*j)->mUsage ? (*j)->mUsage : ""); + +} + +ConsoleMethod(SimObject, getType, S32, 2, 2, "obj.getType()") +{ + argc; argv; + return((S32)object->getType()); +} + +void SimObject::consoleInit() +{ + 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::ForceFieldObjectType", ForceFieldObjectType); + 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::TurretObjectType", TurretObjectType); + Con::setIntVariable("$TypeMasks::DebrisObjectType", DebrisObjectType); + Con::setIntVariable("$TypeMasks::PhysicalZoneObjectType", PhysicalZoneObjectType); + Con::setIntVariable("$TypeMasks::StaticTSObjectType", StaticTSObjectType); + Con::setIntVariable("$TypeMasks::GuiControlObjectType", GuiControlObjectType); + Con::setIntVariable("$TypeMasks::StaticRenderedObjectType", StaticRenderedObjectType); + + Con::setIntVariable("$TypeMasks::DamagableItemObjectType", DamagableItemObjectType); + Con::setIntVariable("$TypeMasks::SensorObjectType", SensorObjectType); + Con::setIntVariable("$TypeMasks::StationObjectType", StationObjectType); + Con::setIntVariable("$TypeMasks::GeneratorObjectType", GeneratorObjectType); +} + +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) + { + if(fld->type == AbstractClassRep::DepricatedFieldType) + return; + S32 array1 = array ? dAtoi(array) : 0; + if(array1 >= 0 && array1 < fld->elementCount && fld->elementCount >= 1) + Con::setData(fld->type, (void *) (U32(this) + fld->offset), array1, 1, &value, fld->table); + /*else if(array1 == -1 && fld->elementCount == argc) + { + S32 i; + for(i = 0; i < argc;i++) + Con::setData(fld->type, (void *) (U32(this) + fld->offset), i, 1, argv + i, fld->table); + }*/ + onStaticModified(slotName); + return; + } + } + if(mFlags.test(ModDynamicFields)) + { + if(!mFieldDictionary) + mFieldDictionary = new SimFieldDictionary; + + if(!array) + mFieldDictionary->setFieldValue(slotName, value); + else + { + char buf[256]; + dStrcpy(buf, slotName); + dStrcat(buf, array); + mFieldDictionary->setFieldValue(StringTable->insert(buf), value); + } + } +} + +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 Con::getData(fld->type, (void *) (U32(this) + fld->offset), 0, fld->table, fld->flag); + if(array1 >= 0 && array1 < fld->elementCount) + return Con::getData(fld->type, (void *) (U32(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 ""; +} + +SimObject::~SimObject() +{ + 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() +{ + 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() +{ + 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() +{ + static char IDbuffer[12]; + dSprintf(IDbuffer, sizeof(IDbuffer), "%d", mId); + return IDbuffer; +} +//--------------------------------------------------------------------------- + +bool SimObject::onAdd() +{ + mFlags.set(Added); + + if(getClassRep()) + mNameSpace = getClassRep()->getNameSpace(); + + // onAdd() should return FALSE if there was an error + return true; +} + +void SimObject::onRemove() +{ + mFlags.clear(Added); +} + +void SimObject::onGroupAdd() +{ +} + +void SimObject::onGroupRemove() +{ +} + +void SimObject::onDeleteNotify(SimObject*) +{ +} + +void SimObject::onNameChange(const char*) +{ +} + +void SimObject::onStaticModified(const char*) +{ +} + +bool SimObject::processArguments(S32 argc, const char**) +{ + return argc == 0; +} + +//--------------------------------------------------------------------------- + +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); + freeNotify(note); +} + +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); + freeNotify(note); + } + else + cnote = &(temp->next); + } +} + +//--------------------------------------------------------------------------- + +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() +{ +} + +IMPLEMENT_CONOBJECT(SimObject); + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +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 maxmimum number of data blocks"); + + // add DataBlock to the DataBlockGroup unless it is client side ONLY DataBlock + if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast) + 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 (getId() < DataBlockObjectIdFirst || getId() > DataBlockObjectIdLast) + setId(sNextObjectId++); +} + +void SimDataBlock::onStaticModified(const char*) +{ + modifiedKey = sNextModifiedKey++; + +} + +void SimDataBlock::setLastError(const char*) +{ +} + +void SimDataBlock::packData(BitStream*) +{ +} + +void SimDataBlock::unpackData(BitStream*) +{ +} + +bool SimDataBlock::preload(bool, char[256]) +{ + return true; +} + +ConsoleFunction(deleteDataBlocks, void, 1, 1, "deleteDataBlocks();") +{ + argc; argv; + // delete from last to first: + SimGroup *grp = Sim::getDataBlockGroup(); + for(S32 i = grp->size() - 1; i >= 0; i--) + { + SimObject *obj = (*grp)[i]; + obj->deleteObject(); + } + SimDataBlock::sNextObjectId = DataBlockObjectIdFirst; + SimDataBlock::sNextModifiedKey = 0; +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- + +void SimSet::addObject(SimObject* obj) +{ + objectList.pushBack(obj); + deleteNotify(obj); +} + +void SimSet::removeObject(SimObject* obj) +{ + objectList.remove(obj); + clearNotify(obj); +} + +void SimSet::pushObject(SimObject* pObj) +{ + objectList.pushBackForce(pObj); + deleteNotify(pObj); +} + +void SimSet::popObject() +{ + if (objectList.size() == 0) { + AssertWarn(false, "Stack underflow in SimSet::popObject"); + return; + } + + SimObject* pObject = objectList[objectList.size() - 1]; + + objectList.removeStable(pObject); + clearNotify(pObject); +} + +bool SimSet::reOrder( SimObject *obj, SimObject *target ) +{ + iterator itrS, itrD; + if ( (itrS = find(begin(),end(),obj)) == end() ) + return false; // object must be in list + if ( obj == target ) + return true; // don't reorder same object but don't indicate error + if ( !target ) // if no target, then put to back of list + { + if ( itrS != (end()-1) ) // don't move if already last object + { + objectList.erase(itrS); // remove object from its current location + objectList.push_back(obj); // push it to the back of the list + } + } + else // if target, insert object in front of target + { + if ( (itrD = find(begin(),end(),target)) == end() ) + return false; // target must be in list + objectList.erase(itrS); + + //Tinman - 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() +{ + 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); + } + } + Parent::onRemove(); +} + +void SimSet::write(Stream &stream, U32 tabStop, U32 flags) +{ + // export selected only? + if((flags & SelectedOnly) && !isSelected()) + { + for(U32 i = 0; i < size(); i++) + (*this)[i]->write(stream, tabStop, flags); + return; + + } + + writeTabs(stream, 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); + } + writeTabs(stream, tabStop); + stream.write(4, "};\r\n"); +} + +ConsoleMethod(SimSet, listObjects, void, 2, 2, "set.listObjects();") +{ + argc; argv; + SimSet *set = (SimSet *) object; + SimSet::iterator itr; + for(itr = set->begin(); itr != set->end(); itr++) + { + SimObject *object = *itr; + bool isSet = dynamic_cast(object) != 0; + const char *name = object->getName(); + if(name) + Con::printf(" %d,\"%s\": %s %s", object->getId(), name, + object->getClassName(), isSet ? "(g)":""); + else + Con::printf(" %d: %s %s", object->getId(), object->getClassName(), + isSet ? "(g)" : ""); + } +} + +ConsoleMethod(SimSet, add, void, 3, 0, "set.add(obj1,...)") +{ + SimSet *set = (SimSet *) object; + for(S32 i = 2; i < argc; i++) + { + SimObject *obj = Sim::findObject(argv[i]); + if(obj) + set->addObject(obj); + else + Con::printf("Set::add: Object \"%s\" doesn't exist", argv[i]); + } +} + +ConsoleMethod(SimSet, remove, void, 3, 0, "set.remove(obj1,...)") +{ + SimSet *set = (SimSet *) object; + for(S32 i = 2; i < argc; i++) + { + SimObject *obj = Sim::findObject(argv[i]); + if(obj && set->find(set->begin(),set->end(),obj) != set->end()) + set->removeObject(obj); + else + Con::printf("Set::remove: Object \"%s\" does not exist in set", argv[i]); + } +} + +ConsoleMethod(SimSet, clear, void, 2, 2, "set.clear()") +{ + argc; argv; + SimSet *set = (SimSet *) object; + while (set->size() > 0) + set->removeObject(*(set->begin())); +} + +ConsoleMethod(SimSet, getCount, S32, 2, 2, "set.getCount()") +{ + argc; argv; + return ((SimSet *) object)->size(); +} + +ConsoleMethod(SimSet, getObject, S32, 3, 3, "set.getObject(objIndex)") +{ + argc; + SimSet *set = (SimSet *) object; + S32 objectIndex = dAtoi(argv[2]); + if(objectIndex < 0 || objectIndex >= S32(set->size())) + { + Con::printf("Set::getObject index out of range."); + return -1; + } + return ((*set)[objectIndex])->getId(); +} + +ConsoleMethod(SimSet, isMember, bool, 3, 3, "set.isMember(object)") +{ + argc; + SimSet *set = (SimSet *) object; + SimObject *testObject = Sim::findObject(argv[2]); + if(!testObject) + { + Con::printf("SimSet::isMember: %s is not an object.", argv[2]); + return false; + } + for(SimSet::iterator i = set->begin(); i != set->end(); i++) + if(*i == testObject) + return true; + return false; +} + +ConsoleMethod(SimSet, bringToFront, void, 3, 3, "set.bringToFront(object)") +{ + argc; + SimObject *obj = Sim::findObject(argv[2]); + if(!obj) + return; + ((SimSet *) object)->bringObjectToFront(obj); +} + +ConsoleMethod(SimSet, pushToBack, void, 3, 3, "set.pushToBack(object)") +{ + argc; + SimObject *obj = Sim::findObject(argv[2]); + if(!obj) + return; + ((SimSet *) object)->pushObjectToBack(obj); +} + +//---------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(SimSet); + +//--------------------------------------------------------------------------- + +SimGroup::~SimGroup() +{ + 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(); + } +} + + +//--------------------------------------------------------------------------- + +void SimGroup::addObject(SimObject* obj) +{ + 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(); + } +} + +void SimGroup::removeObject(SimObject* obj) +{ + if (obj->mGroup == this) { + obj->onGroupRemove(); + nameDictionary.remove(obj); + objectList.remove(obj); + obj->mGroup = 0; + } +} + +//--------------------------------------------------------------------------- + +void SimGroup::onRemove() +{ + 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(); +} + +//--------------------------------------------------------------------------- + +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; + + for(SimSet::iterator i = begin(); i != end(); i++) + { + if((*i)->getName() == stName) + { + if(namePath[len] == 0) + return *i; + return (*i)->findObject(namePath + len + 1); + } + } + return NULL; +} + +SimObject* SimObject::findObject(const char* ) +{ + return NULL; +} + +//--------------------------------------------------------------------------- + +bool SimGroup::processArguments(S32, const char **) +{ + return true; +} + +//---------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(SimGroup); + +//------------------------------------------------------------------------------ + +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 + Con::execute(mArgc, const_cast( mArgv )); +} + +//----------------------------------------------------------------------------- + +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; +} + diff --git a/console/simBase.h b/console/simBase.h new file mode 100644 index 0000000..cee62e6 --- /dev/null +++ b/console/simBase.h @@ -0,0 +1,599 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMBASE_H_ +#define _SIMBASE_H_ + +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _TALGORITHM_H_ +#include "Core/tAlgorithm.h" +#endif +#ifndef _BITSET_H_ +#include "Core/bitSet.h" +#endif + +#ifndef _CONSOLEOBJECT_H_ +#include "console/consoleObject.h" +#endif +#ifndef _SIMDICTIONARY_H_ +#include "console/simDictionary.h" +#endif + +//--------------------------------------------------------------------------- + +enum +{ + DataBlockObjectIdFirst = 3, + DataBlockObjectIdBitSize = 10, + DataBlockObjectIdLast = DataBlockObjectIdFirst + (1 << DataBlockObjectIdBitSize) - 1, + + DynamicObjectIdFirst = DataBlockObjectIdLast + 1, + InvalidEventId = 0, + RootGroupId = 0xFFFFFFFF, +}; + +class SimEvent; +class SimObject; +class SimGroup; +class SimManager; +class Namespace; +class BitStream; +class Stream; +class LightManager; + +typedef U32 SimTime; +typedef U32 SimObjectId; + +class SimObjectList: public VectorPtr +{ + static S32 QSORT_CALLBACK compareId(const void* a,const void* b); + public: + void pushBack(SimObject*); + void pushBackForce(SimObject*); + void pushFront(SimObject*); + void remove(SimObject*); + void removeStable(SimObject* pObject) { /* remove is stable at the moment */ remove(pObject); } + void sortId(); +}; + +//--------------------------------------------------------------------------- + +class SimEvent +{ + public: + SimEvent *nextEvent; + SimTime time; + U32 sequenceCount; + SimObject *destObject; + + SimEvent() { destObject = NULL; } + virtual ~SimEvent() {} // dummy virtual destructor is required + // so that subclasses can be deleted properly + virtual void process(SimObject *object)=0; +}; + +class SimConsoleEvent : public SimEvent +{ + S32 mArgc; + char **mArgv; + bool mOnObject; + public: + SimConsoleEvent(S32 argc, const char **argv, bool onObject); + ~SimConsoleEvent(); + virtual void process(SimObject *object); +}; + +//--------------------------------------------------------------------------- +class SimFieldDictionary +{ + friend class SimFieldDictionaryIterator; + + public: + struct Entry + { + StringTableEntry slotName; + char *value; + Entry *next; + }; + private: + enum + { + HashTableSize = 19 + }; + Entry *mHashTable[HashTableSize]; + + static Entry *mFreeList; + static void freeEntry(Entry *entry); + static Entry *allocEntry(); + public: + SimFieldDictionary(); + ~SimFieldDictionary(); + void setFieldValue(StringTableEntry slotName, const char *value); + const char *getFieldValue(StringTableEntry slotName); + void writeFields(SimObject *obj, Stream &strem, U32 tabStop); + void printFields(SimObject *obj); + void assignFrom(SimFieldDictionary *dict); +}; + +class SimFieldDictionaryIterator +{ + SimFieldDictionary * mDictionary; + S32 mHashIndex; + SimFieldDictionary::Entry * mEntry; + + public: + SimFieldDictionaryIterator(SimFieldDictionary*); + SimFieldDictionary::Entry* operator++(); + SimFieldDictionary::Entry* operator*(); +}; + +//--------------------------------------------------------------------------- +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: + enum { + Deleted = 1 << 0, + Removed = 1 << 1, + Added = 1 << 3, + Selected = 1 << 4, + Expanded = 1 << 5, + ModStaticFields = 1 << 6, // can read/modify static fields + ModDynamicFields = 1 << 7 // can read/modify dynamic fields + }; + public: + struct Notify { + enum Type { + ClearNotify, + DeleteNotify, + ObjectRef, + Invalid + } type; + void *ptr; + Notify *next; + }; + enum WriteFlags { + SelectedOnly = BIT(0) + }; + + private: + // dictionary information stored on the object + StringTableEntry objectName; + SimObject* nextNameObject; + SimObject* nextManagerNameObject; + SimObject* nextIdObject; + + SimGroup* mGroup; + BitSet32 mFlags; + Notify* mNotifyList; + + protected: + SimObjectId mId; + Namespace* mNameSpace; + U32 mTypeMask; + + protected: + static SimObject::Notify *mNotifyFreeList; + static SimObject::Notify *allocNotify(); + static void freeNotify(SimObject::Notify*); + private: + SimFieldDictionary *mFieldDictionary; + + public: + const char *getDataField(StringTableEntry slotName, const char *array); + void setDataField(StringTableEntry slotName, const char *array, const char *value); + SimFieldDictionary * getFieldDictionary() {return(mFieldDictionary);} + + SimObject(); + virtual ~SimObject(); + virtual bool processArguments(S32 argc, const char **argv); + + virtual bool onAdd(); + virtual void onRemove(); + virtual void onGroupAdd(); + virtual void onGroupRemove(); + virtual void onNameChange(const char *name); + virtual void onStaticModified(const char* slotName); + virtual void inspectPreApply(); + virtual void inspectPostApply(); + + virtual void onDeleteNotify(SimObject *object); + + virtual void onEditorEnable(){}; + virtual void onEditorDisable(){}; + + virtual SimObject *findObject(const char *name); // find a named sub-object of this object + + Notify *removeNotify(void *ptr, Notify::Type); + void deleteNotify(SimObject* obj); + void clearNotify(SimObject* obj); + void clearAllNotifications(); + void processDeleteNotifies(); + void registerReference(SimObject **obj); + void unregisterReference(SimObject **obj); + + bool registerObject(); + bool registerObject(U32 id); + bool registerObject(const char *name); + bool registerObject(const char *name, U32 id); + + void unregisterObject(); + void deleteObject(); + + SimObjectId getId() const { return mId; } + const char* getIdString(); + U32 getType() const { return mTypeMask; } + const char* getName() const { return objectName; }; + + void setId(SimObjectId id); + void assignName(const char* name); + SimGroup* getGroup() const { return mGroup; } + bool isProperlyAdded() const { return mFlags.test(Added); } + bool isDeleted() const { return mFlags.test(Deleted); } + bool isRemoved() const { return mFlags.test(Deleted | Removed); } + bool isLocked(); + void setLocked( bool b ); + bool isHidden(); + void setHidden(bool b); + + bool addToSet(SimObjectId); + bool addToSet(const char *); + bool removeFromSet(SimObjectId); + bool removeFromSet(const char *); + + virtual void write(Stream &stream, U32 tabStop, U32 flags = 0); + void writeFields(Stream &stream, U32 tabStop); + void assignFieldsFrom(SimObject *obj); + + Namespace* getNamespace() { return mNameSpace; } + const char *tabComplete(const char *prevText, S32 baseLen, bool); + + 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); } + + virtual void registerLights(LightManager *, bool) {}; + + static void consoleInit(); + + DECLARE_CONOBJECT(SimObject); +}; + +//--------------------------------------------------------------------------- + +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; + } + operator bool() const { return mObj != 0; } + bool isNull() const { return mObj == 0; } + T* operator->() { return static_cast(mObj); } + T& operator*() { return *static_cast(mObj); } + operator T*() { return static_cast(mObj)? static_cast(mObj) : 0; } + const T* operator->() const { return static_cast(mObj); } + const T& operator*() const { return *static_cast(mObj); } + operator const T*() const { return static_cast(mObj) ? static_cast(mObj) : 0; } +}; + +//--------------------------------------------------------------------------- +class SimDataBlock: public SimObject +{ + typedef SimObject Parent; + + protected: + S32 modifiedKey; + + public: + static SimObjectId sNextObjectId; + static S32 sNextModifiedKey; + + SimDataBlock(); + DECLARE_CONOBJECT(SimDataBlock); + + static S32 getNextModifiedKey() { return sNextModifiedKey; } + S32 getModifiedKey() const { return modifiedKey; } + + bool onAdd(); + void onStaticModified(const char* slotName); + void setLastError(const char*); + void assignId(); + + virtual bool preload(bool server, char errorBuffer[256]); + + // + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + +//--------------------------------------------------------------------------- +class SimSet: public SimObject +{ + typedef SimObject Parent; + protected: + SimObjectList objectList; + + public: + SimSet() { + VECTOR_SET_ASSOCIATION(objectList); + } + + 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() { 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); } + + bool reOrder( SimObject *obj, SimObject *target=0 ); + + virtual void onRemove(); + virtual void onDeleteNotify(SimObject *object); + + virtual void addObject(SimObject*); + virtual void removeObject(SimObject*); + + virtual void pushObject(SimObject*); + virtual void popObject(); + + void bringObjectToFront(SimObject* obj) { reOrder(obj, front()); } + void pushObjectToBack(SimObject* obj) { reOrder(obj, NULL); } + + void write(Stream &stream, U32 tabStop, U32 flags = 0); + + virtual SimObject *findObject(const char *name); // find a named sub-object of this object + + DECLARE_CONOBJECT(SimSet); +}; + +class SimSetIterator +{ + 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; + } +}; + +//--------------------------------------------------------------------------- +class SimGroup: public SimSet +{ + private: + friend class SimManager; + friend class SimObject; + + typedef SimSet Parent; + SimNameDictionary nameDictionary; + + public: + ~SimGroup(); + + void addObject(SimObject*); + void addObject(SimObject*, SimObjectId); + void addObject(SimObject*, const char *name); + void removeObject(SimObject*); + void onRemove(); + + virtual SimObject* findObject(const char* name); + + bool processArguments(S32 argc, const char **argv); + + DECLARE_CONOBJECT(SimGroup); +}; + +inline void SimGroup::addObject(SimObject* obj, SimObjectId id) +{ + // AddObject will assign it whatever id it already has. + // This should normally be done only with reserved id's. + obj->mId = id; + addObject( obj ); +} + +inline void SimGroup::addObject(SimObject *obj, const char *name) +{ + addObject( obj ); + obj->assignName(name); +} + +//--------------------------------------------------------------------------- + +class SimDataBlockGroup : public SimGroup +{ + private: + S32 mLastModifiedKey; + + public: + static S32 QSORT_CALLBACK compareModifiedKey(const void* a,const void* b); + void sort(); + SimDataBlockGroup(); +}; + +//--------------------------------------------------------------------------- + +// helper macros 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(LightSet) + DeclareNamedSet(WayPointSet) + DeclareNamedSet(ClientTargetSet) + DeclareNamedSet(ServerTargetSet) + DeclareNamedSet(FlareSet) + DeclareNamedSet(MissileSet) + DeclareNamedSet(CommandTargetSet) + DeclareNamedSet(ScopeSensorVisibleSet) + DeclareNamedGroup(ActionMapGroup) + DeclareNamedGroup(ClientGroup) + DeclareNamedGroup(GuiGroup) + DeclareNamedGroup(GuiDataGroup) + DeclareNamedGroup(TCPGroup) + DeclareNamedGroup(ClientConnectionGroup) + + void init(); + void shutdown(); + + SimDataBlockGroup *getDataBlockGroup(); + SimGroup* getRootGroup(); + + SimObject* findObject(SimObjectId); + SimObject* findObject(const char* name); + 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; + } + + 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); +} + +//---------------------------------------------------------------------------- +#define IMPLEMENT_SETDATATYPE(T) \ +void setDataTypeDataBlockPtr##T(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) \ +{ \ + volatile SimDataBlock* pConstraint = static_cast((T*)NULL); \ + \ + 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 expected data block class", argv[0]); \ + } \ + else \ + Con::printf("Cannot set multiple args to a single pointer."); \ +} + +#define IMPLEMENT_GETDATATYPE(T) \ +const char* getDataTypeDataBlockPtr##T(void *dptr, EnumTable *, BitSet32) \ +{ \ + volatile SimDataBlock* pConstraint = static_cast((T*)NULL); \ + \ + T** obj = reinterpret_cast(dptr); \ + return *obj ? (*obj)->getName() : ""; \ +} + +#define REF_SETDATATYPE(T) setDataTypeDataBlockPtr##T +#define REF_GETDATATYPE(T) getDataTypeDataBlockPtr##T + +//--------------------------------------------------------------------------- + +#endif diff --git a/console/simDictionary.cc b/console/simDictionary.cc new file mode 100644 index 0000000..03f531f --- /dev/null +++ b/console/simDictionary.cc @@ -0,0 +1,247 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/simDictionary.h" +#include "console/simBase.h" + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +extern S32 HashPointer(StringTableEntry e); + +SimNameDictionary::SimNameDictionary() +{ + hashTable = NULL; +} + +SimNameDictionary::~SimNameDictionary() +{ + delete[] hashTable; +} + +void SimNameDictionary::insert(SimObject* obj) +{ + if(!obj->objectName) + return; + + 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; + } + } +} + +SimObject* SimNameDictionary::find(StringTableEntry name) +{ + // NULL is a valid lookup - it will always return NULL + if(!hashTable) + return NULL; + + S32 idx = HashPointer(name) % hashTableSize; + SimObject *walk = hashTable[idx]; + while(walk) + { + if(walk->objectName == name) + return walk; + walk = walk->nextNameObject; + } + return NULL; +} + +void SimNameDictionary::remove(SimObject* obj) +{ + if(!obj->objectName) + return; + + SimObject **walk = &hashTable[HashPointer(obj->objectName) % hashTableSize]; + while(*walk) + { + if(*walk == obj) + { + *walk = obj->nextNameObject; + obj->nextNameObject = (SimObject*)-1; + hashEntryCount--; + return; + } + walk = &((*walk)->nextNameObject); + } +} + +//---------------------------------------------------------------------------- + +SimManagerNameDictionary::SimManagerNameDictionary() +{ + hashTable = new SimObject *[DefaultTableSize]; + hashTableSize = DefaultTableSize; + hashEntryCount = 0; + S32 i; + for(i = 0; i < hashTableSize; i++) + hashTable[i] = NULL; +} + +SimManagerNameDictionary::~SimManagerNameDictionary() +{ + delete[] hashTable; +} + +void SimManagerNameDictionary::insert(SimObject* obj) +{ + if(!obj->objectName) + return; + + 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; + } + } +} + +SimObject* SimManagerNameDictionary::find(StringTableEntry name) +{ + // NULL is a valid lookup - it will always return NULL + + S32 idx = HashPointer(name) % hashTableSize; + SimObject *walk = hashTable[idx]; + while(walk) + { + if(walk->objectName == name) + return walk; + walk = walk->nextManagerNameObject; + } + return NULL; +} + +void SimManagerNameDictionary::remove(SimObject* obj) +{ + if(!obj->objectName) + return; + + SimObject **walk = &hashTable[HashPointer(obj->objectName) % hashTableSize]; + while(*walk) + { + if(*walk == obj) + { + *walk = obj->nextManagerNameObject; + obj->nextManagerNameObject = (SimObject*)-1; + hashEntryCount--; + return; + } + walk = &((*walk)->nextManagerNameObject); + } +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +SimIdDictionary::SimIdDictionary() +{ + for(S32 i = 0; i < DefaultTableSize; i++) + table[i] = NULL; +} + +SimIdDictionary::~SimIdDictionary() +{ +} + +void SimIdDictionary::insert(SimObject* obj) +{ + S32 idx = obj->getId() & TableBitMask; + obj->nextIdObject = table[idx]; + table[idx] = obj; +} + +SimObject* SimIdDictionary::find(S32 id) +{ + S32 idx = id & TableBitMask; + SimObject *walk = table[idx]; + while(walk) + { + if(walk->getId() == U32(id)) + return walk; + walk = walk->nextIdObject; + } + return NULL; +} + +void SimIdDictionary::remove(SimObject* obj) +{ + SimObject **walk = &table[obj->getId() & TableBitMask]; + while(*walk && *walk != obj) + walk = &((*walk)->nextIdObject); + if(*walk) + *walk = obj->nextIdObject; +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + diff --git a/console/simDictionary.h b/console/simDictionary.h new file mode 100644 index 0000000..532d43b --- /dev/null +++ b/console/simDictionary.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMDICTIONARY_H_ +#define _SIMDICTIONARY_H_ +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _STRINGTABLE_H_ +#include "Core/stringTable.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; +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; +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]; +public: + void insert(SimObject* obj); + void remove(SimObject* obj); + SimObject* find(S32 id); + + SimIdDictionary(); + ~SimIdDictionary(); +}; + +#endif //_SIMDICTIONARY_H_ diff --git a/console/simManager.cc b/console/simManager.cc new file mode 100644 index 0000000..2d1974d --- /dev/null +++ b/console/simManager.cc @@ -0,0 +1,449 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/simBase.h" +#include "Core/stringTable.h" +#include "console/console.h" +#include "Core/fileStream.h" +#include "Core/resManager.h" +#include "Core/fileObject.h" +#include "console/consoleInternal.h" +#include "Core/idGenerator.h" + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +namespace Sim +{ + + +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// event queue variables: + +SimTime gCurrentTime; +SimTime gTargetTime; + +SimEvent *gEventQueue; +U32 gEventSequence; + +//--------------------------------------------------------------------------- +// event queue init/shutdown + +static void initEventQueue() +{ + gCurrentTime = 0; + gTargetTime = 0; + gEventSequence = 1; + gEventQueue = NULL; +} + +static void shutdownEventQueue() +{ + // Delete all pending events + SimEvent *walk = gEventQueue; + while(walk) + { + SimEvent *temp = walk->nextEvent; + delete walk; + walk = temp; + } +} + +//--------------------------------------------------------------------------- +// event post + +U32 postEvent(SimObject *destObject, SimEvent* event,U32 time) +{ + AssertFatal(time >= gCurrentTime, + "Sim::postEvent: Cannot go back in time."); + AssertFatal(destObject, "Destination object for event doesn't exist."); + + event->time = time; + event->destObject = destObject; + + if(!destObject) + { + delete event; + return InvalidEventId; + } + event->sequenceCount = gEventSequence++; + SimEvent **walk = &gEventQueue; + SimEvent *current; + + while((current = *walk) != NULL && (current->time < event->time)) + walk = &(current->nextEvent); + event->nextEvent = current; + *walk = event; + return event->sequenceCount; +} + +//--------------------------------------------------------------------------- +// event cancellation + +void cancelEvent(U32 eventSequence) +{ + SimEvent **walk = &gEventQueue; + SimEvent *current; + + while((current = *walk) != NULL) + { + if(current->sequenceCount == eventSequence) + { + *walk = current->nextEvent; + delete current; + return; + } + else + walk = &(current->nextEvent); + } +} + +static void cancelPendingEvents(SimObject *obj) +{ + SimEvent **walk = &gEventQueue; + SimEvent *current; + + while((current = *walk) != NULL) + { + if(current->destObject == obj) + { + *walk = current->nextEvent; + delete current; + } + else + walk = &(current->nextEvent); + } +} + +//--------------------------------------------------------------------------- +// event pending test + +bool isEventPending(U32 eventSequence) +{ + for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent) + if(walk->sequenceCount == eventSequence) + return true; + return false; +} + + +//--------------------------------------------------------------------------- +// event timing + +void advanceToTime(SimTime targetTime) +{ + AssertFatal(targetTime >= gCurrentTime, "EventQueue::process: cannot advance to time in the past."); + + gTargetTime = targetTime; + while(gEventQueue && gEventQueue->time <= targetTime) + { + SimEvent *event = gEventQueue; + gEventQueue = gEventQueue->nextEvent; + AssertFatal(event->time >= gCurrentTime, + "SimEventQueue::pop: Cannot go back in time."); + gCurrentTime = event->time; + SimObject *obj = event->destObject; + + if(!obj->isDeleted()) + event->process(obj); + delete event; + } + gCurrentTime = targetTime; +} + +void advanceTime(SimTime delta) +{ + advanceToTime(gCurrentTime + delta); +} + +U32 getCurrentTime() +{ + return gCurrentTime; +} + +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(); + + delete gNameDictionary; + delete gIdDictionary; +} + +//--------------------------------------------------------------------------- + +SimObject* findObject(const char* name) +{ + SimObject *obj; + char c = *name; + 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); +} + +SimGroup *getRootGroup() +{ + return gRootGroup; +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +#define InstantiateNamedSet(set) g##set = new SimSet; g##set->registerObject(#set); gRootGroup->addObject(g##set) +#define InstantiateNamedGroup(set) g##set = new SimGroup; g##set->registerObject(#set); gRootGroup->addObject(g##set) + +SimDataBlockGroup *gDataBlockGroup; +SimDataBlockGroup *getDataBlockGroup() +{ + return gDataBlockGroup; +} + + +void init() +{ + initEventQueue(); + initRoot(); + + InstantiateNamedSet(ActiveActionMapSet); + InstantiateNamedSet(GhostAlwaysSet); + InstantiateNamedSet(LightSet); + InstantiateNamedSet(WayPointSet); + InstantiateNamedSet(ClientTargetSet); + InstantiateNamedSet(ServerTargetSet); + InstantiateNamedSet(FlareSet); + InstantiateNamedSet(MissileSet); + InstantiateNamedSet(CommandTargetSet); + InstantiateNamedSet(ScopeSensorVisibleSet); + InstantiateNamedGroup(ActionMapGroup); + InstantiateNamedGroup(ClientGroup); + InstantiateNamedGroup(GuiGroup); + InstantiateNamedGroup(GuiDataGroup); + InstantiateNamedGroup(TCPGroup); + InstantiateNamedGroup(ClientConnectionGroup); + gDataBlockGroup = new SimDataBlockGroup(); + gDataBlockGroup->registerObject("DataBlockGroup"); + gRootGroup->addObject(gDataBlockGroup); +} + +void shutdown() +{ + shutdownRoot(); + shutdownEventQueue(); +} + +} + +SimDataBlockGroup::SimDataBlockGroup() +{ + mLastModifiedKey = 0; +} + +S32 QSORT_CALLBACK SimDataBlockGroup::compareModifiedKey(const void* a,const void* b) +{ + return (reinterpret_cast(a))->getModifiedKey() - + (reinterpret_cast(b))->getModifiedKey(); +} + + +void SimDataBlockGroup::sort() +{ + if(mLastModifiedKey != SimDataBlock::getNextModifiedKey()) + { + mLastModifiedKey = SimDataBlock::getNextModifiedKey(); + dQsort(objectList.address(),objectList.size(),sizeof(SimObject *),compareModifiedKey); + } +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +bool SimObject::registerObject() +{ + mFlags.clear(Deleted | Removed); + + if(!mId) + mId = Sim::gNextObjectId++; + + 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(); + + // + 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(),"SimManger::deleteObject: " + "Object has already been deleted"); + AssertFatal(!isRemoved(),"SimManger::deleteObject: " + "Object in the process of being removed"); + mFlags.set(Deleted); + + unregisterObject(); + delete this; +} + +//--------------------------------------------------------------------------- + + +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) +{ + 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/telnetConsole.cc b/console/telnetConsole.cc new file mode 100644 index 0000000..80094ce --- /dev/null +++ b/console/telnetConsole.cc @@ -0,0 +1,256 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Platform/event.h" +#include "console/telnetConsole.h" +#include "Platform/gameInterface.h" + +TelnetConsole *TelConsole = NULL; + +void TelnetConsole::create() +{ + TelConsole = new TelnetConsole; +} + +void TelnetConsole::destroy() +{ + delete TelConsole; + TelConsole = NULL; +} + +static void cTelnetSetParams(SimObject *, S32, const char **argv) +{ + TelConsole->setTelnetParameters(dAtoi(argv[1]), argv[2], argv[3]); +} + +static void telnetCallback(ConsoleLogEntry::Level level, const char *consoleLine) +{ + TelConsole->processConsoleLine(consoleLine); +} + +TelnetConsole::TelnetConsole() +{ + Con::addConsumer(telnetCallback); + Con::addCommand("telnetSetParameters", cTelnetSetParams, "telnetSetParameters(port,consolePass,listenPass)", 4, 4); + + mAcceptSocket = InvalidSocket; + mAcceptPort = -1; + mClientList = NULL; +} + +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) +{ + 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(mTelnetPassword, telnetPassword, PasswordMaxLength); + dStrncpy(mListenPassword, listenPassword, PasswordMaxLength); +} + +void TelnetConsole::processConsoleLine(const char *consoleLine) +{ + // 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; + cl->state = PasswordTryOne; + + Net::setBlocking(newConnection, false); + + char *connectMessage = "Tribes 2 Telnet\r\n\r\nEnter 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; + + dStrcpy(mPostEvent.data, client->curLine); + mPostEvent.size = ConsoleEventHeaderSize + dStrlen(client->curLine) + 1; + Game->postEvent(mPostEvent); + + // 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]; + } + } + if(replyPos) + 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..3b5d48e --- /dev/null +++ b/console/telnetConsole.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TELNETCONSOLE_H_ +#define _TELNETCONSOLE_H_ + +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif + +class TelnetConsole +{ + NetSocket mAcceptSocket; + S32 mAcceptPort; + + enum { + PasswordMaxLength = 32 + }; + + char mTelnetPassword[PasswordMaxLength+1]; + char mListenPassword[PasswordMaxLength+1]; + ConsoleEvent mPostEvent; + + enum State + { + PasswordTryOne, + PasswordTryTwo, + PasswordTryThree, + DisconnectThisDude, + FullAccessConnected, + ReadOnlyConnected + }; + + struct TelnetClient + { + NetSocket socket; + char curLine[Con::MaxLineLength]; + S32 curPos; + S32 state; + TelnetClient *nextClient; + }; + TelnetClient *mClientList; + TelnetConsole(); + ~TelnetConsole(); +public: + static void create(); + static void destroy(); + void process(); + void setTelnetParameters(S32 port, const char *telnetPassword, const char *listenPassword); + void processConsoleLine(const char *line); +}; + +extern TelnetConsole *TelConsole; + +#endif + diff --git a/console/telnetDebugger.cc b/console/telnetDebugger.cc new file mode 100644 index 0000000..c3d3a63 --- /dev/null +++ b/console/telnetDebugger.cc @@ -0,0 +1,557 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/console.h" +#include "console/telnetDebugger.h" +#include "Platform/event.h" +#include "Core/stringTable.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "console/compiler.h" +#include "Platform/gameInterface.h" + +//------------------------------------------------------------ + +// debugger commands: +// CEVAL console line - evaluate the console line +// output: none +// BRKVARSET varName passct expr +// output: none +// BRKVARCLR varName +// 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: 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: +// +// when the debugger hits a breakpoint, it lists out: +// BREAK file1 line1 fn1 file2 line2 fn2 file3 line3 fn3 file4 line4 fn4 etc. +// where file1 line1 fn1 ... etc is the current call stack. +// COUT echo out a console line + +static void cDebugSetParams(SimObject *, S32, const char **argv) +{ + TelDebugger->setDebugParameters(dAtoi(argv[1]), argv[2]); +} + +static void debuggerConsumer(ConsoleLogEntry::Level level, const char *line) +{ + TelDebugger->processConsoleLine(line); +} + +TelnetDebugger::TelnetDebugger() +{ + Con::addCommand("dbgSetParameters", cDebugSetParams, "dbgSetParameters(port,pass);", 3, 3); + Con::addConsumer(debuggerConsumer); + + mAcceptPort = -1; + mAcceptSocket = InvalidSocket; + mDebugSocket = InvalidSocket; + + mState = NotConnected; + + mBreakpoints = NULL; + mBreakOnNextStatement = false; + mStackPopBreakIndex = -1; + mProgramPaused = false; +} + +TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber) +{ + Breakpoint **walk = &mBreakpoints; + Breakpoint *cur; + while((cur = *walk) != NULL) + { + if(cur->code->name == fileName && 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; +} + +void TelnetDebugger::destroy() +{ + delete TelDebugger; + TelDebugger = NULL; +} + +void TelnetDebugger::send(const char *str) +{ + Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str)); +} + +void TelnetDebugger::setDebugParameters(S32 port, const char *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); +} + +void TelnetDebugger::processConsoleLine(const char *consoleLine) +{ + if(mState == Connected) + { + 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() +{ + S32 checked = false; + for(;;) { + // check for and recv one command: + for(S32 i = 0; i < mCurPos; i++) + { + if(mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n') + { + if(i == 0) + { + mCurPos--; + dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos); + } + else + { + mLineBuffer[i] = '\n'; + processLineBuffer(i+1); + mCurPos -= i + 1; + dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos); + return; + } + } + else if(mLineBuffer[i] == 0) + mLineBuffer[i] = '_'; + } + // found no or + if(mCurPos == MaxCommandSize) // this shouldn't happen + { + Net::closeSocket(mDebugSocket); + mDebugSocket = InvalidSocket; + mState = NotConnected; + return; + } + if(checked) + return; + checked = false; + + S32 numBytes; + Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes); + + if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0) + { + Net::closeSocket(mDebugSocket); + mDebugSocket = InvalidSocket; + mState = NotConnected; + return; + } + if(err == Net::WouldBlock) + return; + + mCurPos += numBytes; + } +} + +void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber) +{ + if(mProgramPaused) + return; + if(mBreakOnNextStatement) + { + // loop through all the codeblocks clearing the breaks + for(CodeBlock *walk = codeBlockList; walk; walk = walk->nextFile) + walk->clearAllBreaks(); + for(Breakpoint *w = mBreakpoints; w; w = w->next) + w->code->setBreakpoint(w->lineNumber); + breakProcess(); + return; + } + Breakpoint **bp = findBreakpoint(code->name, lineNumber); + if(!bp) + return; + Breakpoint *brk = *bp; + mProgramPaused = true; + Con::evaluatef("$dbgResult = %s;", brk->testExpression); + if(Con::getBoolVariable("$dbgResult")) + { + brk->curCount++; + if(brk->curCount >= brk->passCount) + { + brk->curCount = 0; + if(brk->clearOnHit) + removeBreakpoint(code->name, lineNumber); + breakProcess(); + } + } + mProgramPaused = false; +} + +void TelnetDebugger::popStackFrame() +{ + if(mState == NotConnected) + return; + if(U32(mStackPopBreakIndex) == gEvalState.stack.size()) + breakOnNextStatement(); +} + +void TelnetDebugger::breakProcess() +{ + mProgramPaused = true; + // echo out the break + send("BREAK"); + char buffer[MaxCommandSize]; + + for(S32 i = (S32) gEvalState.stack.size() - 1; i >= 0; i--) + { + CodeBlock *code = gEvalState.stack[i]->code; + U32 ip = gEvalState.stack[i]->ip; + + const char *file = code->name; + const char *scope = gEvalState.stack[i]->scopeName; + if ((! file) || (! file[0])) + file = "N/A"; + if ((! scope) || (! scope[0])) + scope = "N/A"; + U32 line, inst; + code->findBreakLine(ip, line, inst); + dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope); + send(buffer); + } + send("\r\n"); + while(mProgramPaused) + { + checkDebugRecv(); + if(mDebugSocket == InvalidSocket) + { + mProgramPaused = false; + removeAllBreakpoints(); + debugContinue(); + return; + } + } +} + +void TelnetDebugger::processLineBuffer(S32 cmdLen) +{ + if (mState == PasswordTry) + { + if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1)) + { + // failed password: + send("PASS WrongPassword.\r\n"); + Net::closeSocket(mDebugSocket); + mDebugSocket = InvalidSocket; + mState = NotConnected; + } + else + { + send("PASS Connected.\r\n"); + mState = Connected; + } + } + else + { + char evalBuffer[MaxCommandSize]; + char varBuffer[MaxCommandSize]; + char fileBuffer[MaxCommandSize]; + char clear[MaxCommandSize]; + S32 passCount, line, frame; + + if(dSscanf(mLineBuffer, "CEVAL %[^\n]", evalBuffer) == 1) + { + ConsoleEvent postEvent; + dStrcpy(postEvent.data, evalBuffer); + postEvent.size = ConsoleEventHeaderSize + dStrlen(evalBuffer) + 1; + Game->postEvent(postEvent); + } + 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, "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 + { + // invalid stuff. + send("DBGERR Invalid command!\r\n"); + } + } +} + +void TelnetDebugger::addVariableBreakpoint(const char*, S32, const char*) +{ + send("addVariableBreakpoint\r\n"); +} + +void TelnetDebugger::removeVariableBreakpoint(const char*) +{ + send("removeVariableBreakpoint\r\n"); +} + +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 + { + CodeBlock *code = CodeBlock::find(fileName); + if(code) + { + Breakpoint *brk = new Breakpoint; + brk->code = code; + code->setBreakpoint(line); + 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; + brk->code->clearBreakpoint(brk->lineNumber); + dFree(brk->testExpression); + delete brk; + } +} + +void TelnetDebugger::removeAllBreakpoints() +{ + Breakpoint *walk = mBreakpoints; + while(walk) + { + Breakpoint *temp = walk->next; + walk->code->clearBreakpoint(walk->lineNumber); + dFree(walk->testExpression); + delete walk; + walk = temp; + } + mBreakpoints = NULL; +} + +void TelnetDebugger::debugContinue() +{ + mBreakOnNextStatement = false; + mStackPopBreakIndex = -1; + mProgramPaused = false; + send("RUNNING\r\n"); +} + +void TelnetDebugger::breakOnNextStatement() +{ + for(CodeBlock *walk = codeBlockList; walk; walk = walk->nextFile) + walk->setAllBreaks(); + mBreakOnNextStatement = true; +} + +void TelnetDebugger::debugStepIn() +{ + breakOnNextStatement(); + mStackPopBreakIndex = -1; + mProgramPaused = false; + send("RUNNING\r\n"); +} + +void TelnetDebugger::debugStepOver() +{ + mBreakOnNextStatement = false; + mStackPopBreakIndex = gEvalState.stack.size(); + mProgramPaused = false; + send("RUNNING\r\n"); +} + +void TelnetDebugger::debugStepOut() +{ + mBreakOnNextStatement = false; + mStackPopBreakIndex = gEvalState.stack.size() - 1; + mProgramPaused = false; + send("RUNNING\r\n"); +} + +void TelnetDebugger::evaluateExpression(const char *tag, S32, const char *evalBuffer) +{ + char buffer[MaxCommandSize]; + Con::evaluatef("$dbgResult = %s;", evalBuffer); + const char *result = Con::getVariable("$dbgResult"); + dSprintf(buffer, MaxCommandSize, "EVALOUT %s %s\r\n", tag, result[0] ? result : "\"\""); + send(buffer); +} + +void TelnetDebugger::dumpFileList() +{ + send("FILELISTOUT "); + for(CodeBlock *walk = codeBlockList; 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!"); +} diff --git a/console/telnetDebugger.h b/console/telnetDebugger.h new file mode 100644 index 0000000..1406c0a --- /dev/null +++ b/console/telnetDebugger.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TELNETDEBUGGER_H_ +#define _TELNETDEBUGGER_H_ + +class CodeBlock; + +class TelnetDebugger +{ + S32 mAcceptPort; + NetSocket mAcceptSocket; + NetSocket mDebugSocket; + + enum { + PasswordMaxLength = 32, + MaxCommandSize = 2048 + }; + + char mDebuggerPassword[PasswordMaxLength+1]; + enum State + { + NotConnected, + PasswordTry, + Connected + }; + S32 mState; + char mLineBuffer[MaxCommandSize]; + S32 mCurPos; + + TelnetDebugger(); + ~TelnetDebugger(); + + struct Breakpoint + { + 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 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 breakProcess(); + void breakOnNextStatement(); +public: + static void create(); + static void destroy(); + + void process(); + void popStackFrame(); + + virtual void executionStopped(CodeBlock *code, U32 lineNumber); + void send(const char *s); + void setDebugParameters(S32 port, const char *password); + void processConsoleLine(const char *consoleLine); +}; + +extern TelnetDebugger *TelDebugger; + +#endif diff --git a/console/typeValidators.cc b/console/typeValidators.cc new file mode 100644 index 0000000..f60a4a4 --- /dev/null +++ b/console/typeValidators.cc @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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..12d5996 --- /dev/null +++ b/console/typeValidators.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPEVALIDATORS_H_ +#define _TYPEVALIDATORS_H_ + +class TypeValidator +{ + public: + S32 fieldIndex; + + // prints a console error message, prefaces it with: + // className objectName (objectId) - invalid value for fieldName: msg + 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! should +// NOT be used on a field that gets exported - +// field is only converted 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 \ No newline at end of file diff --git a/console/yylex.c b/console/yylex.c new file mode 100644 index 0000000..d9fe44c --- /dev/null +++ b/console/yylex.c @@ -0,0 +1,627 @@ +/* + * Copyright 1988, 1992 by Mortice Kern Systems Inc. All rights reserved. + * All rights reserved. + * + * $Header: /cvs/torque/torque/engine/console/yylex.c,v 1.1 2001/05/17 02:16:20 timg Exp $ + * + */ +#include +#include +#if __STDC__ +#define YY_ARGS(args) args +#else +#define YY_ARGS(args) () +#endif + +#ifdef LEX_WINDOWS +#include + +/* + * define, if not already defined + * the flag YYEXIT, which will allow + * graceful exits from yylex() + * without resorting to calling exit(); + */ + +#ifndef YYEXIT +#define YYEXIT 1 +#endif + +/* + * the following is the handle to the current + * instance of a windows program. The user + * program calling yylex must supply this! + */ + +#ifdef STRICT +extern HINSTANCE hInst; +#else +extern HANDLE hInst; +#endif + +#endif /* LEX_WINDOWS */ + +/* + * Define m_textmsg() to an appropriate function for internationalized messages + * or custom processing. + */ +#ifndef I18N +#define m_textmsg(id, str, cls) (str) +#else /*I18N*/ +extern char* m_textmsg YY_ARGS((int id, const char* str, char* cls)); +#endif/*I18N*/ + +/* + * Include string.h to get definition of memmove() and size_t. + * If you do not have string.h or it does not declare memmove + * or size_t, you will have to declare them here. + */ +#include +/* Uncomment next line if memmove() is not declared in string.h */ +/*extern char * memmove();*/ +/* Uncomment next line if size_t is not available in stdio.h or string.h */ +/*typedef unsigned size_t;*/ +/* Drop this when LATTICE provides memmove */ +#ifdef LATTICE +#define memmove memcopy +#endif + +/* + * YY_STATIC determines the scope of variables and functions + * declared by the lex scanner. It must be set with a -DYY_STATIC + * option to the compiler (it cannot be defined in the lex program). + */ +#ifdef YY_STATIC +/* define all variables as static to allow more than one lex scanner */ +#define YY_DECL static +#else +/* define all variables as global to allow other modules to access them */ +#define YY_DECL +#endif + +/* + * You can redefine yygetc. For YACC Tracing, compile this code + * with -DYYTRACE to get input from yt_getc + */ +#ifdef YYTRACE +extern int yt_getc YY_ARGS((void)); +#define yygetc() yt_getc() +#else +#define yygetc() getc(yyin) /* yylex input source */ +#endif + +/* + * the following can be redefined by the user. + */ +#ifdef YYEXIT +#define YY_FATAL(msg) { fprintf(yyout, "yylex: %s\n", msg); yyLexFatal = 1; } +#else /* YYEXIT */ +#define YY_FATAL(msg) { fprintf(stderr, "yylex: %s\n", msg); exit(1); } +#endif /* YYEXIT */ + +#undef ECHO +#define ECHO fputs(yytext, yyout) + +#define output(c) putc((c), yyout) /* yylex sink for unmatched chars */ +#define YY_INTERACTIVE 1 /* save micro-seconds if 0 */ + +#define BEGIN yy_start = +#define REJECT goto yy_reject +#define NLSTATE (yy_lastc = YYNEWLINE) +#define YY_INIT \ + (yy_start = yyleng = yy_end = 0, yy_lastc = YYNEWLINE) +#define yymore() goto yy_more +#define yyless(n) if ((n) < 0 || (n) > yy_end) ; \ + else { YY_SCANNER; yyleng = (n); YY_USER; } + +YY_DECL void yy_reset YY_ARGS((void)); +YY_DECL int input YY_ARGS((void)); +YY_DECL int unput YY_ARGS((int c)); + +/* functions defined in libl.lib */ +extern int yywrap YY_ARGS((void)); +extern void yyerror YY_ARGS((char *fmt, ...)); +extern void yycomment YY_ARGS((char *term)); +extern int yymapch YY_ARGS((int delim, int escape)); + +@ GLOBAL DECLARATIONS @ + +#ifndef YYLMAX +#define YYLMAX 100 /* token and pushback buffer size */ +#endif /* YYLMAX */ + +/* + * If %array is used (or defaulted), yytext[] contains the token. + * If %pointer is used, yytext is a pointer to yy_tbuf[]. + */ +@aYY_DECL char yytext[YYLMAX+1]; +@pYY_DECL char yy_tbuf[YYLMAX+1]; + +@pYY_DECL char * yytext = yy_tbuf; +#ifdef YY_DEBUG +#undef YY_DEBUG +#define YY_DEBUG(fmt, a1, a2) fprintf(stderr, fmt, a1, a2) +#else +#define YY_DEBUG(fmt, a1, a2) +#endif + +/* + * The declaration for the lex scanner can be changed by + * redefining YYLEX or YYDECL. This must be done if you have + * more than one scanner in a program. + */ +#ifndef YYLEX +#define YYLEX yylex /* name of lex scanner */ +#endif + +#ifndef YYDECL +#define YYDECL int YYLEX YY_ARGS((void)) /* declaration for lex scanner */ +#endif + +/* + * stdin and stdout may not neccessarily be constants. + * If stdin and stdout are constant, and you want to save a few cycles, then + * #define YY_STATIC_STDIO 1 in this file or on the commandline when + * compiling this file + */ +#ifndef YY_STATIC_STDIO +#define YY_STATIC_STDIO 0 +#endif + +#if YY_STATIC_STDIO +YY_DECL FILE *yyin = stdin; +YY_DECL FILE *yyout = stdout; +#else +YY_DECL FILE *yyin = (FILE *)0; +YY_DECL FILE *yyout = (FILE *)0; +#endif +YY_DECL int yylineno = 1; /* line number */ + +/* yy_sbuf[0:yyleng-1] contains the states corresponding to yytext. + * yytext[0:yyleng-1] contains the current token. + * yytext[yyleng:yy_end-1] contains pushed-back characters. + * When the user action routine is active, + * yy_save contains yytext[yyleng], which is set to '\0'. + * Things are different when YY_PRESERVE is defined. + */ +static yy_state_t yy_sbuf [YYLMAX+1]; /* state buffer */ +static int yy_end = 0; /* end of pushback */ +static int yy_start = 0; /* start state */ +static int yy_lastc = YYNEWLINE; /* previous char */ +YY_DECL int yyleng = 0; /* yytext token length */ +#ifdef YYEXIT +static int yyLexFatal; +#endif /* YYEXIT */ + +#ifndef YY_PRESERVE /* the efficient default push-back scheme */ + +static char yy_save; /* saved yytext[yyleng] */ + +#define YY_USER { /* set up yytext for user */ \ + yy_save = yytext[yyleng]; \ + yytext[yyleng] = 0; \ + } +#define YY_SCANNER { /* set up yytext for scanner */ \ + yytext[yyleng] = yy_save; \ + } + +#else /* not-so efficient push-back for yytext mungers */ + +static char yy_save [YYLMAX]; +static char *yy_push = yy_save+YYLMAX; + +#define YY_USER { \ + size_t n = yy_end - yyleng; \ + yy_push = yy_save+YYLMAX - n; \ + if (n > 0) \ + memmove(yy_push, yytext+yyleng, n); \ + yytext[yyleng] = 0; \ + } +#define YY_SCANNER { \ + size_t n = yy_save+YYLMAX - yy_push; \ + if (n > 0) \ + memmove(yytext+yyleng, yy_push, n); \ + yy_end = yyleng + n; \ + } + +#endif + + +#ifdef LEX_WINDOWS + +/* + * When using the windows features of lex, + * it is necessary to load in the resources being + * used, and when done with them, the resources must + * be freed up, otherwise we have a windows app that + * is not following the rules. Thus, to make yylex() + * behave in a windows environment, create a new + * yylex() which will call the original yylex() as + * another function call. Observe ... + */ + +/* + * The actual lex scanner (usually yylex(void)). + * NOTE: you should invoke yy_init() if you are calling yylex() + * with new input; otherwise old lookaside will get in your way + * and yylex() will die horribly. + */ +static int win_yylex(); /* prototype for windows yylex handler */ + +YYDECL { + int wReturnValue; + HANDLE hRes_table; + unsigned short far *old_yy_la_act; /* remember previous pointer values */ + short far *old_yy_final; + yy_state_t far *old_yy_begin; + yy_state_t far *old_yy_next; + yy_state_t far *old_yy_check; + yy_state_t far *old_yy_default; + short far *old_yy_base; + + /* + * the following code will load the required + * resources for a Windows based parser. + */ + + hRes_table = LoadResource (hInst, + FindResource (hInst, "UD_RES_yyLEX", "yyLEXTBL")); + + /* + * return an error code if any + * of the resources did not load + */ + + if (hRes_table == NULL) + return (0); + + /* + * the following code will lock the resources + * into fixed memory locations for the scanner + * (and remember previous pointer locations) + */ + + old_yy_la_act = yy_la_act; + old_yy_final = yy_final; + old_yy_begin = yy_begin; + old_yy_next = yy_next; + old_yy_check = yy_check; + old_yy_default = yy_default; + old_yy_base = yy_base; + + yy_la_act = (unsigned short far *)LockResource (hRes_table); + yy_final = (short far *)(yy_la_act + Sizeof_yy_la_act); + yy_begin = (yy_state_t far *)(yy_final + Sizeof_yy_final); + yy_next = (yy_state_t far *)(yy_begin + Sizeof_yy_begin); + yy_check = (yy_state_t far *)(yy_next + Sizeof_yy_next); + yy_default = (yy_state_t far *)(yy_check + Sizeof_yy_check); + yy_base = (yy_state_t far *)(yy_default + Sizeof_yy_default); + + + /* + * call the standard yylex() code + */ + + wReturnValue = win_yylex(); + + /* + * unlock the resources + */ + + UnlockResource (hRes_table); + + /* + * and now free the resource + */ + + FreeResource (hRes_table); + + /* + * restore previously saved pointers + */ + + yy_la_act = old_yy_la_act; + yy_final = old_yy_final; + yy_begin = old_yy_begin; + yy_next = old_yy_next; + yy_check = old_yy_check; + yy_default = old_yy_default; + yy_base = old_yy_base; + + return (wReturnValue); +} /* end function */ + +static int win_yylex() { + +#else /* LEX_WINDOWS */ + +/* + * The actual lex scanner (usually yylex(void)). + * NOTE: you should invoke yy_init() if you are calling yylex() + * with new input; otherwise old lookaside will get in your way + * and yylex() will die horribly. + */ +YYDECL { + +#endif /* LEX_WINDOWS */ + + register int c, i, yybase; + unsigned yyst; /* state */ + int yyfmin, yyfmax; /* yy_la_act indices of final states */ + int yyoldi, yyoleng; /* base i, yyleng before look-ahead */ + int yyeof; /* 1 if eof has already been read */ +@ LOCAL DECLARATIONS @ + + +#if !YY_STATIC_STDIO + if (yyin == (FILE *)0) + yyin = stdin; + if (yyout == (FILE *)0) + yyout = stdout; +#endif + +#ifdef YYEXIT + yyLexFatal = 0; +#endif /* YYEXIT */ + + yyeof = 0; + i = yyleng; + YY_SCANNER; + + yy_again: + yyleng = i; + /* determine previous char. */ + if (i > 0) + yy_lastc = yytext[i-1]; + /* scan previously accepted token adjusting yylineno */ + while (i > 0) + if (yytext[--i] == YYNEWLINE) + yylineno++; + /* adjust pushback */ + yy_end -= yyleng; + if (yy_end > 0) + memmove(yytext, yytext+yyleng, (size_t) yy_end); + i = 0; + + yy_contin: + yyoldi = i; + + /* run the state machine until it jams */ + yyst = yy_begin[yy_start + ((yy_lastc == YYNEWLINE) ? 1 : 0)]; + yy_sbuf[i] = (yy_state_t) yyst; + do { + YY_DEBUG(m_textmsg(1547, "\n", "I num1 num2"), yyst, i); + if (i >= YYLMAX) { + YY_FATAL(m_textmsg(1548, "Token buffer overflow", "E")); +#ifdef YYEXIT + if (yyLexFatal) + return -2; +#endif /* YYEXIT */ + } /* endif */ + + /* get input char */ + if (i < yy_end) + c = yytext[i]; /* get pushback char */ + else if (!yyeof && (c = yygetc()) != EOF) { + yy_end = i+1; + yytext[i] = (char) c; + } else /* c == EOF */ { + c = EOF; /* just to make sure... */ + if (i == yyoldi) { /* no token */ + yyeof = 0; + if (yywrap()) + return 0; + else + goto yy_again; + } else { + yyeof = 1; /* don't re-read EOF */ + break; + } + } + YY_DEBUG(m_textmsg(1549, "\n", "I num hexnum"), c, c); + + /* look up next state */ + while ((yybase = yy_base[yyst]+(unsigned char)c) > yy_nxtmax + || yy_check[yybase] != (yy_state_t) yyst) { + if (yyst == yy_endst) + goto yy_jammed; + yyst = yy_default[yyst]; + } + yyst = yy_next[yybase]; + yy_jammed: ; + yy_sbuf[++i] = (yy_state_t) yyst; + } while (!(yyst == yy_endst || YY_INTERACTIVE && yy_base[yyst] > yy_nxtmax && yy_default[yyst] == yy_endst)); + YY_DEBUG(m_textmsg(1550, "\n", "I num1 num2"), yyst, i); + if (yyst != yy_endst) + ++i; + + yy_search: + /* search backward for a final state */ + while (--i > yyoldi) { + yyst = yy_sbuf[i]; + if ((yyfmin = yy_final[yyst]) < (yyfmax = yy_final[yyst+1])) + goto yy_found; /* found final state(s) */ + } + /* no match, default action */ + i = yyoldi + 1; + output(yytext[yyoldi]); + goto yy_again; + + yy_found: + YY_DEBUG(m_textmsg(1551, "\n", "I num1 num2"), yyst, i); + yyoleng = i; /* save length for REJECT */ + + /* pushback look-ahead RHS */ + if ((c = (int)(yy_la_act[yyfmin]>>9) - 1) >= 0) { /* trailing context? */ + unsigned char *bv = yy_look + c*YY_LA_SIZE; + static unsigned char bits [8] = { + 1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7 + }; + while (1) { + if (--i < yyoldi) { /* no / */ + i = yyoleng; + break; + } + yyst = yy_sbuf[i]; + if (bv[(unsigned)yyst/8] & bits[(unsigned)yyst%8]) + break; + } + } + + /* perform action */ + yyleng = i; + YY_USER; + switch (yy_la_act[yyfmin] & 0777) { +@ ACTION CODE @ + } + YY_SCANNER; + i = yyleng; + goto yy_again; /* action fell though */ + + yy_reject: + YY_SCANNER; + i = yyoleng; /* restore original yytext */ + if (++yyfmin < yyfmax) + goto yy_found; /* another final state, same length */ + else + goto yy_search; /* try shorter yytext */ + + yy_more: + YY_SCANNER; + i = yyleng; + if (i > 0) + yy_lastc = yytext[i-1]; + goto yy_contin; +} +/* + * Safely switch input stream underneath LEX + */ +typedef struct yy_save_block_tag { + FILE * oldfp; + int oldline; + int oldend; + int oldstart; + int oldlastc; + int oldleng; + char savetext[YYLMAX+1]; + yy_state_t savestate[YYLMAX+1]; +} YY_SAVED; + +void +yy_reset() +{ + YY_INIT; + yylineno = 1; /* line number */ +} + +#if 0 +YY_SAVED * +yySaveScan(fp) +FILE * fp; +{ + YY_SAVED * p; + + if ((p = (YY_SAVED *) malloc(sizeof(*p))) == NULL) + return p; + + p->oldfp = yyin; + p->oldline = yylineno; + p->oldend = yy_end; + p->oldstart = yy_start; + p->oldlastc = yy_lastc; + p->oldleng = yyleng; + (void) memcpy(p->savetext, yytext, sizeof yytext); + (void) memcpy((char *) p->savestate, (char *) yy_sbuf, + sizeof yy_sbuf); + + yyin = fp; + yylineno = 1; + YY_INIT; + + return p; +} +/*f + * Restore previous LEX state + */ +void +yyRestoreScan(p) +YY_SAVED * p; +{ + if (p == NULL) + return; + yyin = p->oldfp; + yylineno = p->oldline; + yy_end = p->oldend; + yy_start = p->oldstart; + yy_lastc = p->oldlastc; + yyleng = p->oldleng; + + (void) memcpy(yytext, p->savetext, sizeof yytext); + (void) memcpy((char *) yy_sbuf, (char *) p->savestate, + sizeof yy_sbuf); + free(p); +} +/* + * User-callable re-initialization of yylex() + */ +/* get input char with pushback */ +YY_DECL int +input() +{ + int c; +#ifndef YY_PRESERVE + if (yy_end > yyleng) { + yy_end--; + memmove(yytext+yyleng, yytext+yyleng+1, + (size_t) (yy_end-yyleng)); + c = yy_save; + YY_USER; +#else + if (yy_push < yy_save+YYLMAX) { + c = *yy_push++; +#endif + } else + c = yygetc(); + yy_lastc = c; + if (c == YYNEWLINE) + yylineno++; + if (c == EOF) /* yygetc() can set c=EOF vsc4 wants c==EOF to return 0 */ + return 0; + else + return c; +} + +/*f + * pushback char + */ +YY_DECL int +unput(c) + int c; +{ +#ifndef YY_PRESERVE + if (yy_end >= YYLMAX) { + YY_FATAL(m_textmsg(1552, "Push-back buffer overflow", "E")); + } else { + if (yy_end > yyleng) { + yytext[yyleng] = yy_save; + memmove(yytext+yyleng+1, yytext+yyleng, + (size_t) (yy_end-yyleng)); + yytext[yyleng] = 0; + } + yy_end++; + yy_save = (char) c; +#else + if (yy_push <= yy_save) { + YY_FATAL(m_textmsg(1552, "Push-back buffer overflow", "E")); + } else { + *--yy_push = c; +#endif + if (c == YYNEWLINE) + yylineno--; + } /* endif */ + return c; +} + +#endif + +@ end of yylex.c @ diff --git a/console/yyparse.c b/console/yyparse.c new file mode 100644 index 0000000..efeab12 --- /dev/null +++ b/console/yyparse.c @@ -0,0 +1,918 @@ +#ifdef YYTRACE +#define YYDEBUG 1 +#else +#ifndef YYDEBUG +#define YYDEBUG $Y +#endif +#endif +/* + * Portable way of defining ANSI C prototypes + */ +#ifndef YY_ARGS +#ifdef __STDC__ +#define YY_ARGS(x) x +#else +#define YY_ARGS(x) () +#endif +#endif + +#ifdef YACC_WINDOWS + +#include + +/* + * the following is the handle to the current + * instance of a windows program. The user + * program calling yyparse must supply this! + */ + +#ifdef STRICT +extern HINSTANCE hInst; +#else +extern HANDLE hInst; +#endif + +#endif /* YACC_WINDOWS */ + +#if YYDEBUG +typedef struct yyNamedType_tag { /* Tokens */ + char * name; /* printable name */ + short token; /* token # */ + short type; /* token type */ +} yyNamedType; +typedef struct yyTypedRules_tag { /* Typed rule table */ + char * name; /* compressed rule string */ + short type; /* rule result type */ +} yyTypedRules; + +#endif + +$@ +#if YYDEBUG +/* + * Package up YACC context for tracing + */ +typedef struct yyTraceItems_tag { + int state, lookahead, errflag, done; + int rule, npop; + short * states; + int nstates; + YYSTYPE * values; + int nvalues; + short * types; +} yyTraceItems; +#endif + +$L#line 2 "$P" + +/* + * Copyright 1985, 1990 by Mortice Kern Systems Inc. All rights reserved. + * + * Automaton to interpret LALR(1) tables. + * + * Macros: + * yyclearin - clear the lookahead token. + * yyerrok - forgive a pending error + * YYERROR - simulate an error + * YYACCEPT - halt and return 0 + * YYABORT - halt and return 1 + * YYRETURN(value) - halt and return value. You should use this + * instead of return(value). + * YYREAD - ensure yychar contains a lookahead token by reading + * one if it does not. See also YYSYNC. + * YYRECOVERING - 1 if syntax error detected and not recovered + * yet; otherwise, 0. + * + * Preprocessor flags: + * YYDEBUG - includes debug code if 1. The parser will print + * a travelogue of the parse if this is defined as 1 + * and yydebug is non-zero. + * yacc -t sets YYDEBUG to 1, but not yydebug. + * YYTRACE - turn on YYDEBUG, and undefine default trace functions + * so that the interactive functions in 'ytrack.c' will + * be used. + * YYSSIZE - size of state and value stacks (default 150). + * YYSTATIC - By default, the state stack is an automatic array. + * If this is defined, the stack will be static. + * In either case, the value stack is static. + * YYALLOC - Dynamically allocate both the state and value stacks + * by calling malloc() and free(). + * YYDYNAMIC - Dynamically allocate (and reallocate, if necessary) + * both the state and value stacks by calling malloc(), + * realloc(), and free(). + * YYSYNC - if defined, yacc guarantees to fetch a lookahead token + * before any action, even if it doesnt need it for a decision. + * If YYSYNC is defined, YYREAD will never be necessary unless + * the user explicitly sets yychar = -1 + * + * Copyright (c) 1983, by the University of Waterloo + */ +/* + * Prototypes + */ + +extern int yylex YY_ARGS((void)); +extern void yyerror YY_ARGS((char *, ...)); + +#if YYDEBUG + +#include /* common prototypes */ +#include + +extern char * yyValue YY_ARGS((YYSTYPE, int)); /* print yylval */ +extern void yyShowState YY_ARGS((yyTraceItems *)); +extern void yyShowReduce YY_ARGS((yyTraceItems *)); +extern void yyShowGoto YY_ARGS((yyTraceItems *)); +extern void yyShowShift YY_ARGS((yyTraceItems *)); +extern void yyShowErrRecovery YY_ARGS((yyTraceItems *)); +extern void yyShowErrDiscard YY_ARGS((yyTraceItems *)); + +extern void yyShowRead YY_ARGS((int)); +#endif + +/* + * If YYDEBUG defined and yydebug set, + * tracing functions will be called at appropriate times in yyparse() + * Pass state of YACC parse, as filled into yyTraceItems yyx + * If yyx.done is set by the tracing function, yyparse() will terminate + * with a return value of -1 + */ +#define YY_TRACE(fn) { \ + yyx.state = yystate; yyx.lookahead = yychar; yyx.errflag =yyerrflag; \ + yyx.states = yys+1; yyx.nstates = yyps-yys; \ + yyx.values = yyv+1; yyx.nvalues = yypv-yyv; \ + yyx.types = yytypev+1; yyx.done = 0; \ + yyx.rule = yyi; yyx.npop = yyj; \ + fn(&yyx); \ + if (yyx.done) YYRETURN(-1); } + +#ifndef I18N +#define m_textmsg(id, str, cls) (str) +#else /*I18N*/ +#include +#endif/*I18N*/ + +#ifndef YYSSIZE +# define YYSSIZE 150 +#endif + +#ifdef YYDYNAMIC +#define YYALLOC +char *getenv(); +int atoi(); +int yysinc = -1; /* stack size increment, <0 = double, 0 = none, >0 = fixed */ +#endif + +#ifdef YYALLOC +int yyssize = YYSSIZE; +#endif + +#define YYERROR goto yyerrlabel +#define yyerrok yyerrflag = 0 +#if YYDEBUG +#define yyclearin { if (yydebug) yyShowRead(-1); yychar = -1; } +#else +#define yyclearin yychar = -1 +#endif +#define YYACCEPT YYRETURN(0) +#define YYABORT YYRETURN(1) +#define YYRECOVERING() (yyerrflag != 0) +#ifdef YYALLOC +#define YYRETURN(val) { retval = (val); goto yyReturn; } +#else +#define YYRETURN(val) return(val); +#endif +#if YYDEBUG +/* The if..else makes this macro behave exactly like a statement */ +# define YYREAD if (yychar < 0) { \ + if ((yychar = yylex()) < 0) { \ + if (yychar == -2) YYABORT; \ + yychar = 0; \ + } /* endif */ \ + if (yydebug) \ + yyShowRead(yychar); \ + } else +#else +# define YYREAD if (yychar < 0) { \ + if ((yychar = yylex()) < 0) { \ + if (yychar == -2) YYABORT; \ + yychar = 0; \ + } /* endif */ \ + } else +#endif + +#define YYERRCODE $e /* value of `error' */ +#define YYTOKEN_BASE 256 +#define YYQYYP yyq[yyq-yyp] + +/* + * Simulate bitwise negation as if was done on a two's complement machine. + * This makes the generated code portable to machines with different + * representations of integers (ie. signed magnitude). + */ +#define yyneg(s) (-((s)+1)) + +YYSTYPE yyval; /* $$ */ +YYSTYPE *yypvt; /* $n */ +YYSTYPE yylval; /* yylex() sets this */ + +int yychar, /* current token */ + yyerrflag, /* error flag */ + yynerrs; /* error count */ + +#if YYDEBUG +int yydebug = 0; /* debug if this flag is set */ +extern char *yysvar[]; /* table of non-terminals (aka 'variables') */ +extern yyNamedType yyTokenTypes[]; /* table of terminals & their types */ +extern short yyrmap[], yysmap[]; /* map internal rule/states */ +extern int yynstate, yynvar, yyntoken, yynrule; + +extern int yyGetType YY_ARGS((int)); /* token type */ +extern char *yyptok YY_ARGS((int)); /* printable token string */ +extern int yyExpandName YY_ARGS((int, int, char *, int)); + /* expand yyRules[] or yyStates[] */ +static char * yygetState YY_ARGS((int)); + +#define yyassert(condition, msg, arg) \ + if (!(condition)) { \ + printf(m_textmsg(2824, "\nyacc bug: ", "E")); \ + printf(msg, arg); \ + YYABORT; } +#else /* !YYDEBUG */ +#define yyassert(condition, msg, arg) +#endif + +$T + +#ifdef YACC_WINDOWS + +/* + * the following is the yyparse() function that will be + * callable by a windows type program. It in turn will + * load all needed resources, obtain pointers to these + * resources, and call a statically defined function + * win_yyparse(), which is the original yyparse() fn + * When win_yyparse() is complete, it will return a + * value to the new yyparse(), where it will be stored + * away temporarily, all resources will be freed, and + * that return value will be given back to the caller + * yyparse(), as expected. + */ + +static int win_yyparse(); /* prototype */ + +int yyparse() +{ + int wReturnValue; + HANDLE hRes_table; /* handle of resource after loading */ + short far *old_yydef; /* the following are used for saving */ + short far *old_yyex; /* the current pointers */ + short far *old_yyact; + short far *old_yypact; + short far *old_yygo; + short far *old_yypgo; + short far *old_yyrlen; + + /* + * the following code will load the required + * resources for a Windows based parser. + */ + + hRes_table = LoadResource (hInst, + FindResource (hInst, "UD_RES_yyYACC", "yyYACCTBL")); + + /* + * return an error code if any + * of the resources did not load + */ + + if (hRes_table == NULL) + return (1); + + /* + * the following code will lock the resources + * into fixed memory locations for the parser + * (also, save the current pointer values first) + */ + + old_yydef = yydef; + old_yyex = yyex; + old_yyact = yyact; + old_yypact = yypact; + old_yygo = yygo; + old_yypgo = yypgo; + old_yyrlen = yyrlen; + + yydef = (short far *)LockResource (hRes_table); + yyex = (short far *)(yydef + Sizeof_yydef); + yyact = (short far *)(yyex + Sizeof_yyex); + yypact = (short far *)(yyact + Sizeof_yyact); + yygo = (short far *)(yypact + Sizeof_yypact); + yypgo = (short far *)(yygo + Sizeof_yygo); + yyrlen = (short far *)(yypgo + Sizeof_yypgo); + + /* + * call the official yyparse() function + */ + + wReturnValue = win_yyparse(); + + /* + * unlock the resources + */ + + UnlockResource (hRes_table); + + /* + * and now free the resource + */ + + FreeResource (hRes_table); + + /* + * restore previous pointer values + */ + + yydef = old_yydef; + yyex = old_yyex; + yyact = old_yyact; + yypact = old_yypact; + yygo = old_yygo; + yypgo = old_yypgo; + yyrlen = old_yyrlen; + + return (wReturnValue); +} /* end yyparse */ + +static int win_yyparse() + +#else /* YACC_WINDOWS */ + +/* + * we are not compiling a windows resource + * based parser, so call yyparse() the old + * standard way. + */ + +int yyparse() + +#endif /* YACC_WINDOWS */ + +{ +#ifdef YACC_WINDOWS + register short far *yyp; /* for table lookup */ + register short far *yyq; +#else + register short *yyp; /* for table lookup */ + register short *yyq; +#endif /* YACC_WINDOWS */ + register short yyi; + register short *yyps; /* top of state stack */ + register short yystate; /* current state */ + register YYSTYPE *yypv; /* top of value stack */ + register int yyj; +#if YYDEBUG + yyTraceItems yyx; /* trace block */ + short * yytp; + int yyruletype = 0; +#endif +#ifdef YYSTATIC + static short yys[YYSSIZE + 1]; + static YYSTYPE yyv[YYSSIZE + 1]; +#if YYDEBUG + static short yytypev[YYSSIZE+1]; /* type assignments */ +#endif +#else /* ! YYSTATIC */ +#ifdef YYALLOC + YYSTYPE *yyv; + short *yys; +#if YYDEBUG + short *yytypev; +#endif + YYSTYPE save_yylval; + YYSTYPE save_yyval; + YYSTYPE *save_yypvt; + int save_yychar, save_yyerrflag, save_yynerrs; + int retval; /* return value holder */ +#else + short yys[YYSSIZE + 1]; + static YYSTYPE yyv[YYSSIZE + 1]; /* historically static */ +#if YYDEBUG + short yytypev[YYSSIZE+1]; /* mirror type table */ +#endif +#endif /* ! YYALLOC */ +#endif /* ! YYSTATIC */ +#ifdef YYDYNAMIC + char *envp; +#endif + +$A +#ifdef YYDYNAMIC + if ((envp = getenv("YYSTACKSIZE")) != (char *)0) { + yyssize = atoi(envp); + if (yyssize <= 0) + yyssize = YYSSIZE; + } + if ((envp = getenv("YYSTACKINC")) != (char *)0) + yysinc = atoi(envp); +#endif +#ifdef YYALLOC + yys = (short *) malloc((yyssize + 1) * sizeof(short)); + yyv = (YYSTYPE *) malloc((yyssize + 1) * sizeof(YYSTYPE)); +#if YYDEBUG + yytypev = (short *) malloc((yyssize + 1) * sizeof(short)); +#endif + if (yys == (short *)0 || yyv == (YYSTYPE *)0 +#if YYDEBUG + || yytypev == (short *) 0 +#endif + ) { + yyerror(m_textmsg(4967, "Not enough space for parser stacks", + "E")); + return 1; + } + save_yylval = yylval; + save_yyval = yyval; + save_yypvt = yypvt; + save_yychar = yychar; + save_yyerrflag = yyerrflag; + save_yynerrs = yynerrs; +#endif + + yynerrs = 0; + yyerrflag = 0; + yyclearin; + yyps = yys; + yypv = yyv; + *yyps = yystate = YYS0; /* start state */ +#if YYDEBUG + yytp = yytypev; + yyi = yyj = 0; /* silence compiler warnings */ +#endif + +yyStack: + yyassert((unsigned)yystate < yynstate, m_textmsg(587, "state %d\n", ""), yystate); +#ifdef YYDYNAMIC + if (++yyps > &yys[yyssize]) { + int yynewsize; + int yysindex = yyps - yys; + int yyvindex = yypv - yyv; +#if YYDEBUG + int yytindex = yytp - yytypev; +#endif + if (yysinc == 0) { /* no increment */ + yyerror(m_textmsg(4968, "Parser stack overflow", "E")); + YYABORT; + } else if (yysinc < 0) /* binary-exponential */ + yynewsize = yyssize * 2; + else /* fixed increment */ + yynewsize = yyssize + yysinc; + if (yynewsize < yyssize) { + yyerror(m_textmsg(4967, + "Not enough space for parser stacks", + "E")); + YYABORT; + } + yyssize = yynewsize; + yys = (short *) realloc(yys, (yyssize + 1) * sizeof(short)); + yyps = yys + yysindex; + yyv = (YYSTYPE *) realloc(yyv, (yyssize + 1) * sizeof(YYSTYPE)); + yypv = yyv + yyvindex; +#if YYDEBUG + yytypev = (short *)realloc(yytypev,(yyssize + 1)*sizeof(short)); + yytp = yytypev + yytindex; +#endif + if (yys == (short *)0 || yyv == (YYSTYPE *)0 +#if YYDEBUG + || yytypev == (short *) 0 +#endif + ) { + yyerror(m_textmsg(4967, + "Not enough space for parser stacks", + "E")); + YYABORT; + } + } +#else + if (++yyps > &yys[YYSSIZE]) { + yyerror(m_textmsg(4968, "Parser stack overflow", "E")); + YYABORT; + } +#endif /* !YYDYNAMIC */ + *yyps = yystate; /* stack current state */ + *++yypv = yyval; /* ... and value */ +#if YYDEBUG + *++yytp = yyruletype; /* ... and type */ + + if (yydebug) + YY_TRACE(yyShowState) +#endif + + /* + * Look up next action in action table. + */ +yyEncore: +#ifdef YYSYNC + YYREAD; +#endif + +#ifdef YACC_WINDOWS + if (yystate >= Sizeof_yypact) /* simple state */ +#else /* YACC_WINDOWS */ + if (yystate >= sizeof yypact/sizeof yypact[0]) /* simple state */ +#endif /* YACC_WINDOWS */ + yyi = yystate - YYDELTA; /* reduce in any case */ + else { + if(*(yyp = &yyact[yypact[yystate]]) >= 0) { + /* Look for a shift on yychar */ +#ifndef YYSYNC + YYREAD; +#endif + yyq = yyp; + yyi = yychar; + while (yyi < *yyp++) + ; + if (yyi == yyp[-1]) { + yystate = yyneg(YYQYYP); +#if YYDEBUG + if (yydebug) { + yyruletype = yyGetType(yychar); + YY_TRACE(yyShowShift) + } +#endif + yyval = yylval; /* stack what yylex() set */ + yyclearin; /* clear token */ + if (yyerrflag) + yyerrflag--; /* successful shift */ + goto yyStack; + } + } + + /* + * Fell through - take default action + */ + +#ifdef YACC_WINDOWS + if (yystate >= Sizeof_yydef) +#else /* YACC_WINDOWS */ + if (yystate >= sizeof yydef /sizeof yydef[0]) +#endif /* YACC_WINDOWS */ + goto yyError; + if ((yyi = yydef[yystate]) < 0) { /* default == reduce? */ + /* Search exception table */ +#ifdef YACC_WINDOWS + yyassert((unsigned)yyneg(yyi) < Sizeof_yyex, + m_textmsg(2825, "exception %d\n", "I num"), yystate); +#else /* YACC_WINDOWS */ + yyassert((unsigned)yyneg(yyi) < sizeof yyex/sizeof yyex[0], + m_textmsg(2825, "exception %d\n", "I num"), yystate); +#endif /* YACC_WINDOWS */ + yyp = &yyex[yyneg(yyi)]; +#ifndef YYSYNC + YYREAD; +#endif + while((yyi = *yyp) >= 0 && yyi != yychar) + yyp += 2; + yyi = yyp[1]; + yyassert(yyi >= 0, + m_textmsg(2826, "Ex table not reduce %d\n", "I num"), yyi); + } + } + + yyassert((unsigned)yyi < yynrule, m_textmsg(2827, "reduce %d\n", "I num"), yyi); + yyj = yyrlen[yyi]; +#if YYDEBUG + if (yydebug) + YY_TRACE(yyShowReduce) + yytp -= yyj; +#endif + yyps -= yyj; /* pop stacks */ + yypvt = yypv; /* save top */ + yypv -= yyj; + yyval = yypv[1]; /* default action $$ = $1 */ +#if YYDEBUG + yyruletype = yyRules[yyrmap[yyi]].type; +#endif + + switch (yyi) { /* perform semantic action */ + $A +$L#line 314 "$P" + case YYrACCEPT: + YYACCEPT; + case YYrERROR: + goto yyError; + } + + /* + * Look up next state in goto table. + */ + + yyp = &yygo[yypgo[yyi]]; + yyq = yyp++; + yyi = *yyps; + while (yyi < *yyp++) + ; + + yystate = yyneg(yyi == *--yyp? YYQYYP: *yyq); +#if YYDEBUG + if (yydebug) + YY_TRACE(yyShowGoto) +#endif + goto yyStack; + +yyerrlabel: ; /* come here from YYERROR */ +/* +#pragma used yyerrlabel + */ + yyerrflag = 1; + if (yyi == YYrERROR) { + yyps--; + yypv--; +#if YYDEBUG + yytp--; +#endif + } + +yyError: + switch (yyerrflag) { + + case 0: /* new error */ + yynerrs++; + yyi = yychar; + yyerror(m_textmsg(4969, "Syntax error", "E")); + if (yyi != yychar) { + /* user has changed the current token */ + /* try again */ + yyerrflag++; /* avoid loops */ + goto yyEncore; + } + + case 1: /* partially recovered */ + case 2: + yyerrflag = 3; /* need 3 valid shifts to recover */ + + /* + * Pop states, looking for a + * shift on `error'. + */ + + for ( ; yyps > yys; yyps--, yypv-- +#if YYDEBUG + , yytp-- +#endif + ) { +#ifdef YACC_WINDOWS + if (*yyps >= Sizeof_yypact) +#else /* YACC_WINDOWS */ + if (*yyps >= sizeof yypact/sizeof yypact[0]) +#endif /* YACC_WINDOWS */ + continue; + yyp = &yyact[yypact[*yyps]]; + yyq = yyp; + do { + if (YYERRCODE == *yyp) { + yyp++; + yystate = yyneg(YYQYYP); + goto yyStack; + } + } while (*yyp++ > YYTOKEN_BASE); + + /* no shift in this state */ +#if YYDEBUG + if (yydebug && yyps > yys+1) + YY_TRACE(yyShowErrRecovery) +#endif + /* pop stacks; try again */ + } + /* no shift on error - abort */ + break; + + case 3: + /* + * Erroneous token after + * an error - discard it. + */ + + if (yychar == 0) /* but not EOF */ + break; +#if YYDEBUG + if (yydebug) + YY_TRACE(yyShowErrDiscard) +#endif + yyclearin; + goto yyEncore; /* try again in same state */ + } + YYABORT; + +#ifdef YYALLOC +yyReturn: + yylval = save_yylval; + yyval = save_yyval; + yypvt = save_yypvt; + yychar = save_yychar; + yyerrflag = save_yyerrflag; + yynerrs = save_yynerrs; + free((char *)yys); + free((char *)yyv); +#if YYDEBUG + free((char *)yytypev); +#endif + return(retval); +#endif +} + + +#if YYDEBUG +/* + * Return type of token + */ +int +yyGetType(tok) +int tok; +{ + yyNamedType * tp; + for (tp = &yyTokenTypes[yyntoken-1]; tp > yyTokenTypes; tp--) + if (tp->token == tok) + return tp->type; + return 0; +} +/* + * Print a token legibly. + */ +char * +yyptok(tok) +int tok; +{ + yyNamedType * tp; + for (tp = &yyTokenTypes[yyntoken-1]; tp > yyTokenTypes; tp--) + if (tp->token == tok) + return tp->name; + return ""; +} + +/* + * Read state 'num' from YYStatesFile + */ +#ifdef YYTRACE + +static char * +yygetState(num) +int num; +{ + int size; + static FILE *yyStatesFile = (FILE *) 0; + static char yyReadBuf[YYMAX_READ+1]; + + if (yyStatesFile == (FILE *) 0 + && (yyStatesFile = fopen(YYStatesFile, "r")) == (FILE *) 0) + return "yyExpandName: cannot open states file"; + + if (num < yynstate - 1) + size = (int)(yyStates[num+1] - yyStates[num]); + else { + /* length of last item is length of file - ptr(last-1) */ + if (fseek(yyStatesFile, 0L, 2) < 0) + goto cannot_seek; + size = (int) (ftell(yyStatesFile) - yyStates[num]); + } + if (size < 0 || size > YYMAX_READ) + return "yyExpandName: bad read size"; + if (fseek(yyStatesFile, yyStates[num], 0) < 0) { + cannot_seek: + return "yyExpandName: cannot seek in states file"; + } + + (void) fread(yyReadBuf, 1, size, yyStatesFile); + yyReadBuf[size] = '\0'; + return yyReadBuf; +} +#endif /* YYTRACE */ +/* + * Expand encoded string into printable representation + * Used to decode yyStates and yyRules strings. + * If the expansion of 's' fits in 'buf', return 1; otherwise, 0. + */ +int +yyExpandName(num, isrule, buf, len) +int num, isrule; +char * buf; +int len; +{ + int i, n, cnt, type; + char * endp, * cp; + char *s; + + if (isrule) + s = yyRules[num].name; + else +#ifdef YYTRACE + s = yygetState(num); +#else + s = "*no states*"; +#endif + + for (endp = buf + len - 8; *s; s++) { + if (buf >= endp) { /* too large: return 0 */ + full: (void) strcpy(buf, " ...\n"); + return 0; + } else if (*s == '%') { /* nonterminal */ + type = 0; + cnt = yynvar; + goto getN; + } else if (*s == '&') { /* terminal */ + type = 1; + cnt = yyntoken; + getN: + if (cnt < 100) + i = 2; + else if (cnt < 1000) + i = 3; + else + i = 4; + for (n = 0; i-- > 0; ) + n = (n * 10) + *++s - '0'; + if (type == 0) { + if (n >= yynvar) + goto too_big; + cp = yysvar[n]; + } else if (n >= yyntoken) { + too_big: + cp = ""; + } else + cp = yyTokenTypes[n].name; + + if ((i = strlen(cp)) + buf > endp) + goto full; + (void) strcpy(buf, cp); + buf += i; + } else + *buf++ = *s; + } + *buf = '\0'; + return 1; +} +#ifndef YYTRACE +/* + * Show current state of yyparse + */ +void +yyShowState(tp) +yyTraceItems * tp; +{ + short * p; + YYSTYPE * q; + + printf( + m_textmsg(2828, "state %d (%d), char %s (%d)\n", "I num1 num2 char num3"), + yysmap[tp->state], tp->state, + yyptok(tp->lookahead), tp->lookahead); +} +/* + * show results of reduction + */ +void +yyShowReduce(tp) +yyTraceItems * tp; +{ + printf("reduce %d (%d), pops %d (%d)\n", + yyrmap[tp->rule], tp->rule, + tp->states[tp->nstates - tp->npop], + yysmap[tp->states[tp->nstates - tp->npop]]); +} +void +yyShowRead(val) +int val; +{ + printf(m_textmsg(2829, "read %s (%d)\n", "I token num"), yyptok(val), val); +} +void +yyShowGoto(tp) +yyTraceItems * tp; +{ + printf(m_textmsg(2830, "goto %d (%d)\n", "I num1 num2"), yysmap[tp->state], tp->state); +} +void +yyShowShift(tp) +yyTraceItems * tp; +{ + printf(m_textmsg(2831, "shift %d (%d)\n", "I num1 num2"), yysmap[tp->state], tp->state); +} +void +yyShowErrRecovery(tp) +yyTraceItems * tp; +{ + short * top = tp->states + tp->nstates - 1; + + printf( + m_textmsg(2832, "Error recovery pops state %d (%d), uncovers %d (%d)\n", "I num1 num2 num3 num4"), + yysmap[*top], *top, yysmap[*(top-1)], *(top-1)); +} +void +yyShowErrDiscard(tp) +yyTraceItems * tp; +{ + printf(m_textmsg(2833, "Error recovery discards %s (%d), ", "I token num"), + yyptok(tp->lookahead), tp->lookahead); +} +#endif /* ! YYTRACE */ +#endif /* YYDEBUG */ diff --git a/core/bitMatrix.h b/core/bitMatrix.h new file mode 100644 index 0000000..34a473c --- /dev/null +++ b/core/bitMatrix.h @@ -0,0 +1,137 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITMATRIX_H_ +#define _BITMATRIX_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _BITVECTOR_H_ +#include "Core/bitVector.h" +#endif + +class BitMatrix +{ + U32 mWidth; + U32 mHeight; + U32 mRowByteWidth; + + U8* mBits; + U32 mSize; + + BitVector mColFlags; + BitVector mRowFlags; + + public: + BitMatrix(const U32 width, const U32 height); + ~BitMatrix(); + + void clearAllBits(); + void setAllBits(); + + void setBit(const U32 x, const U32 y); + void clearBit(const U32 x, const U32 y); + + // Queries + bool isSet(const U32 x, const U32 y) const; + + bool isAnySetCol(const U32 x); + 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.cc b/core/bitRender.cc new file mode 100644 index 0000000..ceecf58 --- /dev/null +++ b/core/bitRender.cc @@ -0,0 +1,834 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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); + 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++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, 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); + 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, 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); + 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++>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] = +{ + { 0, 0, 0, 0}, // 0 + { 0, 0, 0, 0}, // 1 + { 0, 0, 0, 0}, // 2 + { 0, 0, 0, 0}, // 3 + { 0, 0, 0, 0}, // 4 + { 0, 0, 0, 0}, // 5 + { 0, 0, 0, 0}, // 6 + { 0, 0, 0, 0}, // 7 + { 17, 0, 0, 0}, // 8 + { 17, 0, 0, 0}, // 9 + { 17, 0, 0, 0}, // 10 + { 17, 0, 0, 0}, // 11 + { 17, 0, 0, 0}, // 12 + { 17, 0, 0, 0}, // 13 + { 17, 0, 0, 0}, // 14 + { 17, 0, 0, 0}, // 15 +}; + +U8 bitTableB[16][4] = +{ + { 0, 0, 0, 0}, // 0 + { 34, 17, 0, 0}, // 1 + { 17, 34, 17, 0}, // 2 + { 51, 51, 17, 0}, // 3 + { 0, 17, 34, 17}, // 4 + { 34, 34, 34, 17}, // 5 + { 17, 51, 51, 17}, // 6 + { 51, 68, 51, 17}, // 7 + { 0, 0, 17, 34}, // 8 + { 34, 17, 17, 34}, // 9 + { 17, 34, 34, 34}, // 10 + { 51, 51, 34, 34}, // 11 + { 0, 17, 51, 51}, // 12 + { 34, 34, 51, 51}, // 13 + { 17, 51, 68, 51}, // 14 + { 51, 68, 68, 51}, // 15 +}; + + +U8 bitTableC[16][4] = +{ + { 0, 0, 0, 0}, // 0 + { 0, 0, 0, 17}, // 1 + { 0, 0, 0, 0}, // 2 + { 0, 0, 0, 17}, // 3 + { 0, 0, 0, 0}, // 4 + { 0, 0, 0, 17}, // 5 + { 0, 0, 0, 0}, // 6 + { 0, 0, 0, 17}, // 7 + { 0, 0, 0, 0}, // 8 + { 0, 0, 0, 17}, // 9 + { 0, 0, 0, 0}, // 10 + { 0, 0, 0, 17}, // 11 + { 0, 0, 0, 0}, // 12 + { 0, 0, 0, 17}, // 13 + { 0, 0, 0, 0}, // 14 + { 0, 0, 0, 17}, // 15 +}; + +U8 bitTableE[16][4] = +{ + { 0, 0, 0, 0}, // 0 + { 51, 34, 0, 0}, // 1 + { 34, 51, 34, 0}, // 2 + { 85, 85, 34, 0}, // 3 + { 0, 34, 51, 34}, // 4 + { 51, 68, 51, 34}, // 5 + { 34, 85, 85, 34}, // 6 + { 85,119, 85, 34}, // 7 + { 0, 0, 34, 51}, // 8 + { 51, 34, 34, 51}, // 9 + { 34, 51, 68, 51}, // 10 + { 85, 85, 68, 51}, // 11 + { 0, 34, 85, 85}, // 12 + { 51, 68, 85, 85}, // 13 + { 34, 85,119, 85}, // 14 + { 85,119,119, 85}, // 15 +}; + +void BitRender::bitTo8Bit_3(U32 * bits, U32 * eightBits, S32 dim) +{ + // clear out first row of dest + U32 * end32 = eightBits + (dim>>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 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; +} + +U32 BitStream::getPosition() const +{ + return (bitNum + 7) >> 3; +} + + +bool BitStream::setPosition(const U32 pos) +{ + bitNum = pos << 3; + return (true); +} + +U32 BitStream::getStreamSize() +{ + AssertFatal(false, "Ambiguous call on BitStream: bytes or bits?"); + return 0; +} + +U8 *BitStream::getBytePtr() +{ + return dataPtr + getPosition(); +} + + +U32 BitStream::getReadByteSize() +{ + return (maxReadBitNum >> 3) - getPosition(); +} + +void BitStream::clear() +{ + dMemset(dataPtr, 0, bufSize); +} + +void BitStream::writeBits(S32 bitCount, const void *bitPtr) +{ + if(!bitCount) + return; + + if(bitCount + bitNum > maxWriteBitNum) + { + error = true; + AssertFatal(false, "Out of range write"); + return; + } + const U8 *ptr = (U8 *) bitPtr; + U8 *stPtr = dataPtr + (bitNum >> 3); + U8 *endPtr = dataPtr + ((bitCount + bitNum - 1) >> 3); + + S32 upShift = bitNum & 0x7; + S32 downShift= 8 - upShift; + U8 lastMask = 0xFF >> (7 - ((bitNum + bitCount - 1) & 0x7)); + U8 startMask = 0xFF >> downShift; + + U8 curB = *ptr++; + *stPtr = (curB << upShift) | (*stPtr & startMask); + + stPtr++; + while(stPtr <= endPtr) + { + U8 nextB = *ptr++; + *stPtr++ = (curB >> downShift) | (nextB << upShift); + curB = nextB; + } + *endPtr &= lastMask; + + bitNum += bitCount; +} + +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(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( ((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 = mAtan(vec.x, vec.y) / M_PI; + F32 theta = mAtan(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( vec.z, 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( mAtan(vec.x,vec.y) / M_2PI, angleBitCount ); + } +} + +void BitStream::readNormalVector(Point3F * vec, S32 angleBitCount, S32 zBitCount) +{ + vec->z = readSignedFloat(zBitCount); + + // check to see if |z| == 1. If so, then we don't read x and y (they're zero) + if(!IsEqual(mFabs(vec->z), 1.0f)) + { + F32 angle = M_2PI * readSignedFloat(angleBitCount); + + F32 mult = mSqrt(1.0f - vec->z * vec->z); + vec->x = mult * mCos(angle); + vec->y = mult * mSin(angle); + } + else + { + // no need to read, we know + vec->x = 0.0f; + vec->y = 0.0f; + } +} + +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::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); +} + +const static U32 csg_probBoost = 1; + +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] + (dIsalnum(i) ? csg_probBoost : 0) + csg_probBoost; + 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, "um, never."); + + return -((rWrap.pLeaf - m_huffLeaves.address()) + 1); + } else { + AssertFatal(rWrap.pNode != NULL, "um, never."); + + 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/bitStream.h b/core/bitStream.h new file mode 100644 index 0000000..b5f0103 --- /dev/null +++ b/core/bitStream.h @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITSTREAM_H_ +#define _BITSTREAM_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _STREAM_H_ +#include "Core/stream.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 BitStream : public Stream +{ +protected: + U8 *dataPtr; + S32 bitNum; + S32 bufSize; + bool error; + S32 maxReadBitNum; + S32 maxWriteBitNum; + char *stringBuffer; + + 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); + + 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); + + void writeSignedInt(S32 value, S32 bitCount); + S32 readSignedInt(S32 bitCount); + + void writeRangedU32(U32 value, U32 rangeStart, U32 rangeEnd); + U32 readRangedU32(U32 rangeStart, U32 rangeEnd); + + // 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 normalized vector + void writeNormalVector(const Point3F& vec, S32 bitCount); + void readNormalVector(Point3F *vec, S32 bitCount); + + // 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); + + // writes an affine transform (full precision version) + void writeAffineTransform(const MatrixF&); + void readAffineTransform(MatrixF*); + + void writeBits(S32 bitCount, const void *bitPtr); + void readBits(S32 bitCount, void *bitPtr); + bool writeFlag(bool val); + bool readFlag(); + + 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 +{ + U32 mMinSpace; +public: + ResizeBitStream(U32 minSpace = 1500, U32 initialSize = 0); + void validate(); + ~ResizeBitStream(); +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- 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; +} + +#endif //_BITSTREAM_H_ diff --git a/core/bitTables.cc b/core/bitTables.cc new file mode 100644 index 0000000..52b8651 --- /dev/null +++ b/core/bitTables.cc @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/BitTables.h" + +bool BitTables::mTablesBuilt = false; +S8 BitTables::mHighBit[256]; +S8 BitTables::mWhichOn[256][8]; +S8 BitTables::mNumOn[256]; +static BitTables sBuildTheTables; // invoke ctor first-time work + +BitTables::BitTables() +{ + if(! mTablesBuilt){ + // This code only happens once - it relies on the tables being clear. + for( U32 byte = 0; byte < 256; byte++ ) + for( U32 bit = 0; bit < 8; bit++ ) + if( byte & (1 << bit) ) + mHighBit[byte] = (mWhichOn[byte][mNumOn[byte]++] = bit) + 1; + + mTablesBuilt = true; + } +} diff --git a/core/bitTables.h b/core/bitTables.h new file mode 100644 index 0000000..cac644c --- /dev/null +++ b/core/bitTables.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITTABLES_H_ +#define _BITTABLES_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +class BitTables +{ + private: + static bool mTablesBuilt; // For first time build + static S8 mHighBit[256]; // I.e. crude logarithm + static S8 mWhichOn[256][8]; // Unroll a bitset collection (note + static S8 mNumOn[256]; // ptr table wastes same amt.) + + public: + BitTables(); + static const S32 numOn(U8 b) { return mNumOn[b]; } + static const S8 * whichOn(U8 b) { return mWhichOn[b]; } + static const S32 highBit(U8 b) { return mHighBit[b]; } + + static S32 getPower16(U16 x) { return x<256 ? mHighBit[x] : mHighBit[x>>8]+8; } + static S32 getPower32(U32 x){ + if( x < (1<<16) ) + return( x < (1<<8) ? mHighBit[x] : mHighBit[x>>8]+8 ); + else + return( x < (1<<24) ? mHighBit[x>>16]+16 : mHighBit[x>>24]+24 ); + } +}; + +#endif diff --git a/core/bitVector.h b/core/bitVector.h new file mode 100644 index 0000000..74eb9d2 --- /dev/null +++ b/core/bitVector.h @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITVECTOR_H_ +#define _BITVECTOR_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +class BitVector +{ + U8* mBits; + U32 mByteSize; + + U32 mSize; + + static U32 calcByteSize(const U32 numBits); + + public: + BitVector(); + BitVector(const U32 _size); + ~BitVector(); + + void setSize(const U32 _size); + U32 getSize() const; + U32 getByteSize() const; + U32 getAllocatedByteSize() const { return mByteSize; } + + const U8* getBits() const { return mBits; } + U8* getNCBits() { return mBits; } + + bool copy(const BitVector& from); + + void clear(); + void set(); + + void set(U32 bit); + void clear(U32 bit); + bool test(U32 bit) const; +}; + +inline BitVector::BitVector() +{ + mBits = NULL; + mByteSize = 0; + + mSize = 0; +} + + +inline BitVector::BitVector(const U32 _size) +{ + mBits = NULL; + mByteSize = 0; + + mSize = 0; + + setSize(_size); +} + +inline BitVector::~BitVector() +{ + delete [] mBits; + mBits = NULL; + mByteSize = 0; + + mSize = 0; +} + +inline U32 BitVector::calcByteSize(const U32 numBits) +{ + // Make sure that we are 32 bit aligned + // + return (((numBits + 0x7) >> 3) + 0x3) & ~0x3; +} + +inline void BitVector::setSize(const U32 _size) +{ + if (_size != 0) { + U32 newSize = calcByteSize(_size); + if (mByteSize < newSize) { + delete [] mBits; + mBits = new U8[newSize]; + mByteSize = newSize; + } + } else { + delete [] mBits; + mBits = NULL; + mByteSize = 0; + } + + mSize = _size; +} + +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, calcByteSize(mSize)); +} + +inline bool BitVector::copy(const BitVector& from) +{ + U32 sourceSize = from.getSize(); + if (sourceSize) { + setSize(sourceSize); + dMemcpy(mBits, from.getBits(), getByteSize()); + return true; + } + return false; +} + +inline void BitVector::set() +{ + if (mSize != 0) + dMemset(mBits, 0xFF, calcByteSize(mSize)); +} + +inline void BitVector::set(U32 bit) +{ + AssertFatal(bit < mSize, "Error, out of range bit"); + + mBits[bit >> 3] |= U8(1 << (bit & 0x7)); +} + +inline void BitVector::clear(U32 bit) +{ + AssertFatal(bit < mSize, "Error, out of range bit"); + + mBits[bit >> 3] &= U8(~(1 << (bit & 0x7))); +} + +inline bool BitVector::test(U32 bit) const +{ + AssertFatal(bit < mSize, "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..4e0cbdd --- /dev/null +++ b/core/bitVectorW.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITVECTORW_H_ +#define _BITVECTORW_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +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.h b/core/color.h new file mode 100644 index 0000000..a869542 --- /dev/null +++ b/core/color.h @@ -0,0 +1,442 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLOR_H_ +#define _COLOR_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + + +// Forward Declarations... +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; } + + U32 getARGBPack() const; + U32 getRGBAPack() const; + U32 getBGRAPack() 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(); +}; + + +//-------------------------------------- ColorI's are missing some of the operations +// present in ColorF since they cannot recover +// properly from over/underflow. Also, structure +// designed to be castable to PALETTEENTRY's in +// Win32 environments... +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)); + + void set(const U8 in_r, + const U8 in_g, + const U8 in_b, + const U8 in_a = U8(255)); + + ColorI& operator*=(const F32 in_mul); + ColorI operator*(const F32 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; + + U16 get565() const; + U16 get4444() const; + + operator ColorF() const; + + operator const U8*() const { return &red; } +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- 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(alpha * 255.0f + 0.5) << 0) | + (U32(red * 255.0f + 0.5) << 24) | + (U32(green * 255.0f + 0.5) << 16) | + (U32(blue * 255.0f + 0.5) << 8); +} + +inline U32 ColorF::getBGRAPack() const +{ + return (U32(alpha * 255.0f + 0.5) << 0) | + (U32(red * 255.0f + 0.5) << 8) | + (U32(green * 255.0f + 0.5) << 16) | + (U32(blue * 255.0f + 0.5) << 24); +} + +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 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::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 F32 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 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(alpha) << 0) | + (U32(red) << 24) | + (U32(green) << 16) | + (U32(blue) << 8); +} + +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/coreRes.h b/core/coreRes.h new file mode 100644 index 0000000..449a981 --- /dev/null +++ b/core/coreRes.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CORERES_H_ +#define _CORERES_H_ + +#ifndef _RESMANAGER_H_ +#include "Core/resManager.h" +#endif + +class RawData +{ +private: + bool ownMemory; + +public: + char *data; + S32 size; + + RawData() { ownMemory = false; } + RawData(Stream &s, S32 sz) { + ownMemory = true; + size = sz; + data = new char[size]; + if (data) + s.read(size, data); + } + ~RawData() { + if (ownMemory) + delete [] data; + data = NULL; + ownMemory = false; + size = 0; + } +}; + + +//-------------------------------------- RawData type +class ResourceTypeRawData : public ResourceType +{ +public: + ResourceTypeRawData(const char *ext = ".dat"): + ResourceType( ResourceType::typeof(ext) ) { } + void* construct(Stream *stream, S32 size) + { return (void*)new RawData(*stream, size); } + void destruct(void *p) + { delete (RawData*)p; } +}; + +class ResourceTypeStaticRawData : public ResourceType +{ +public: + ResourceTypeStaticRawData(const char *ext = ".sdt"): + ResourceType( ResourceType::typeof(ext) ) { } + void* construct(Stream *stream, S32 size) + { return (void*)new RawData(*stream, size); } + void destruct(void *p) + { } +}; + +#endif //_CORERES_H_ + diff --git a/core/crc.cc b/core/crc.cc new file mode 100644 index 0000000..9889526 --- /dev/null +++ b/core/crc.cc @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/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 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 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 = 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..c432c33 --- /dev/null +++ b/core/crc.h @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CRC_H_ +#define _CRC_H_ + +#define INITIAL_CRC_VALUE 0xffffffff + +class Stream; + +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.cc b/core/dataChunker.cc new file mode 100644 index 0000000..7c48317 --- /dev/null +++ b/core/dataChunker.cc @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Core/dataChunker.h" + + +//---------------------------------------------------------------------------- + +DataChunker::DataChunker(S32 size) +{ + chunkSize = size; + curBlock = new DataBlock(size); + curBlock->next = NULL; + curBlock->curIndex = 0; +} + +DataChunker::~DataChunker() +{ + freeBlocks(); +} + +void *DataChunker::alloc(S32 size) +{ + AssertFatal(size <= chunkSize, "Data chunk too large."); + 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; + } +} + diff --git a/core/dataChunker.h b/core/dataChunker.h new file mode 100644 index 0000000..c97f1d9 --- /dev/null +++ b/core/dataChunker.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DATACHUNKER_H_ +#define _DATACHUNKER_H_ + + +//---------------------------------------------------------------------------- + +class DataChunker +{ + public: + enum { + ChunkSize = 16376 + }; + + 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); + ~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(); }; +}; + + +#endif diff --git a/core/dnet.cc b/core/dnet.cc new file mode 100644 index 0000000..b2e338e --- /dev/null +++ b/core/dnet.cc @@ -0,0 +1,260 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "core/dnet.h" +#include "console/console.h" +#include "core/bitStream.h" +#include "console/consoleTypes.h" + +bool gLogToConsole = false; + +enum NetPacketType +{ + DataPacket, + PingPacket, + AckPacket, + InvalidPacketType, +}; + +static const char *packetTypeNames[] = +{ + "DataPacket", + "PingPacket", + "AckPacket", +}; + +//----------------------------------------------------------------- +//----------------------------------------------------------------- +//----------------------------------------------------------------- +ConsoleFunction(DNetSetLogging, void, 2, 2, "DNetSetLogging(bool);") +{ + 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, "Doh!"); + + S32 headerSize = 3 + ackByteCount; + S32 datalen = 0; + + 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 + + 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 + + if(mLastSeqRecvd != pkSequenceNumber && pkPacketType == DataPacket) + handlePacket(pstream); + + mLastSeqRecvd = pkSequenceNumber; +} + +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); +} + +void 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); +} diff --git a/core/dnet.h b/core/dnet.h new file mode 100644 index 0000000..84b28f8 --- /dev/null +++ b/core/dnet.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DNET_H_ +#define _DNET_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _EVENT_H_ +#include "Platform/event.h" +#endif + +class BitStream; +class ResizeBitStream; + +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 void 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.cc b/core/fileObject.cc new file mode 100644 index 0000000..812bd55 --- /dev/null +++ b/core/fileObject.cc @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/fileObject.h" + +IMPLEMENT_CONOBJECT(FileObject); + +bool FileObject::isEOF() +{ + return mCurPos == mBufferSize; +} + +FileObject::FileObject() +{ + mFileBuffer = NULL; + mBufferSize = 0; + mCurPos = 0; +} + +FileObject::~FileObject() +{ + dFree(mFileBuffer); +} + +void FileObject::close() +{ + stream.close(); + dFree(mFileBuffer); + mFileBuffer = NULL; + mBufferSize = mCurPos = 0; +} + +bool FileObject::openForWrite(const char *fileName, const bool append) +{ + close(); + if ( !append ) + return( ResourceManager->openFileForWrite(stream, NULL, fileName) ); + + // Use the WriteAppend flag so it doesn't clobber the existing file: + if ( !ResourceManager->openFileForWrite(stream, NULL, fileName, File::WriteAppend) ) + 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) +{ + close(); + Stream *s = ResourceManager->openStream(fileName); + if(!s) + return false; + mBufferSize = ResourceManager->getSize(fileName); + mFileBuffer = (U8 *) dMalloc(mBufferSize + 1); + mFileBuffer[mBufferSize] = 0; + s->read(mBufferSize, mFileBuffer); + ResourceManager->closeStream(s); + 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::writeLine(const U8 *line) +{ + stream.write(dStrlen((const char *) line), line); + stream.write(2, "\r\n"); +} + +static bool cFileOpenRead(SimObject *obj, S32, const char **argv) +{ + FileObject *fo = (FileObject *) obj; + return fo->readMemory(argv[2]); +} + +static bool cFileOpenWrite(SimObject *obj, S32, const char **argv) +{ + FileObject *fo = (FileObject *) obj; + return fo->openForWrite(argv[2]); +} + +static bool cFileOpenAppend(SimObject *obj, S32, const char **argv) +{ + FileObject *fo = (FileObject *) obj; + return fo->openForWrite(argv[2], true); +} + +static bool cFileIsEOF(SimObject *obj, S32, const char **) +{ + FileObject *fo = (FileObject *) obj; + return fo->isEOF(); +} + +static const char * cFileReadLine(SimObject *obj, S32, const char **) +{ + FileObject *fo = (FileObject *) obj; + return (const char *) fo->readLine(); +} + +static void cFileWriteLine(SimObject *obj, S32, const char **argv) +{ + FileObject *fo = (FileObject *) obj; + fo->writeLine((const U8 *) argv[2]); +} + +static void cFileClose(SimObject *obj, S32, const char **) +{ + FileObject *fo = (FileObject *) obj; + fo->close(); +} + +void FileObject::consoleInit() +{ + Con::addCommand("FileObject", "openForRead", cFileOpenRead, "file.openForRead(fileName)", 3, 3); + Con::addCommand("FileObject", "openForWrite", cFileOpenWrite, "file.openForWrite(fileName", 3, 3); + Con::addCommand("FileObject", "openForAppend", cFileOpenAppend, "file.openForAppend(fileName)", 3, 3); + Con::addCommand("FileObject", "writeLine", cFileWriteLine, "file.writeLine(text)", 3, 3); + Con::addCommand("FileObject", "isEOF", cFileIsEOF, "file.isEOF()", 2, 2); + Con::addCommand("FileObject", "readLine", cFileReadLine, "file.readLine()", 2, 2); + Con::addCommand("FileObject", "close", cFileClose, "file.close()", 2, 2); +} diff --git a/core/fileObject.h b/core/fileObject.h new file mode 100644 index 0000000..37fdead --- /dev/null +++ b/core/fileObject.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FILEOBJECT_H_ +#define _FILEOBJECT_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _RESMANAGER_H_ +#include "Core/resManager.h" +#endif +#ifndef _FILESTREAM_H_ +#include "Core/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 *readLine(); + bool isEOF(); + void writeLine(const U8 *line); + void close(); + + static void consoleInit(); + + DECLARE_CONOBJECT(FileObject); +}; + +#endif diff --git a/core/fileStream.cc b/core/fileStream.cc new file mode 100644 index 0000000..cbc3e62 --- /dev/null +++ b/core/fileStream.cc @@ -0,0 +1,504 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/******************************************************************************* + ** FILENAME: D:\Tribes\darkstar\Core\fileStream.cc + * + * DESCRIPTION: + * + * CREATED: 07/26/99 08:43:29 + * + * BY: PeteW + */ + +#include "Core/fileStream.h" +#include "Platform/platform.h" + +//----------------------------------------------------------------------------- +// FileStream methods... +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +FileStream::FileStream() +{ + // initialize the file stream + init(); +} + +//----------------------------------------------------------------------------- +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(true == 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; + return(true); + } + // otherwise the new position lies in some block not in memory + else + { + // flush the buffer if its dirty + if (true == mDirty) + flush(); + // and clear out the state of the file stream + clearBuffer(); + + // and set the new position + mFile.setPosition((S32)i_newPosition); + // update the stream to reflect the file's state + setStatus(); + // taking end-of-file into consideration + if ((S32)EOS == (S32)(mFile.getStatus())) + mEOF = true; + // and return good states + 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 (true == mDirty) + return(getMax(mFile.getSize(), mBuffTail + 1)); + // otherwise just get the size on disk... + else + return(mFile.getSize()); +} + +//----------------------------------------------------------------------------- +bool FileStream::open(const char *i_pFilename, AccessMode i_openMode) +{ + AssertWarn(0 == mStreamCaps, "FileStream::setPosition: the stream is already open"); + AssertFatal(NULL != i_pFilename, "FileStream::open: NULL filename"); + + // make sure the file stream's state is clean + clearBuffer(); + + if (File::Ok == mFile.open(i_pFilename, (File::AccessMode)i_openMode)) + { + setStatus(); + switch (i_openMode) + { + case Read: + mStreamCaps = U32(StreamRead) | + U32(StreamPosition); + break; + case Write: + case WriteAppend: + mStreamCaps = U32(StreamWrite) | + U32(StreamPosition); + break; + case ReadWrite: + mStreamCaps = U32(StreamRead) | + U32(StreamWrite) | + U32(StreamPosition); + break; + default: + AssertFatal(false, "FileStream::open: bad access mode"); + } + } + else + { + setStatus(); + return(false); + } + return(true); +} + +//----------------------------------------------------------------------------- +void FileStream::close() +{ + if (Closed == getStatus()) + return; + + // make sure nothing in the buffer differs from what is on disk + if (true == mDirty) + flush(); + // and close the file + File::Status closeResult = mFile.close(); + + AssertFatal(File::Closed == closeResult, "FileStream::close: close failed"); + + // 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 (true == mDirty) + { + AssertFatal(true == 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); + if (File::Ok != mFile.getStatus() && File::EOS != mFile.getStatus()) + return(false); + } + // write contents of the buffer to disk + U32 blockHead; + calcBlockHead(mBuffHead, &blockHead); + mFile.write(mBuffTail - mBuffHead + 1, (char *)mBuffer + (mBuffHead - blockHead)); + // 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 (false == 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 + mFile.read(remaining, (char *)pDst, &bytesRead); + 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 (false == 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 (true == 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 + mFile.write(remaining, (char *)pSrc, &bytesWrit); + 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); + if (File::Ok != mFile.getStatus() && File::EOS != mFile.getStatus()) + { + 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 bytesRead = 0; + U32 blockHead; + // locate bounds of buffer containing current position + calcBlockHead(mBuffPos, &blockHead); + // read as much as possible from input file + mFile.read(BUFFER_SIZE - (i_startPosition - blockHead), (char *)mBuffer + (i_startPosition - blockHead), &bytesRead); + 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 File::Ok: + Stream::setStatus(Ok); + break; + case File::IOError: + Stream::setStatus(IOError); + break; + case File::EOS: + Stream::setStatus(EOS); + break; + case File::IllegalCall: + Stream::setStatus(IllegalCall); + break; + case File::Closed: + Stream::setStatus(Closed); + break; + case File::UnknownError: + Stream::setStatus(UnknownError); + break; + default: + AssertFatal(false, "FileStream::setStatus: invalid error mode"); + } +} diff --git a/core/fileStream.h b/core/fileStream.h new file mode 100644 index 0000000..136d3c4 --- /dev/null +++ b/core/fileStream.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/******************************************************************************* + ** FILENAME: D:\Tribes\darkstar\Core\fileStream.h + * + * DESCRIPTION: buffered file streams. + * read, write, and read-write buffering is supported. + * + * CREATED: 07/26/99 08:24:55 + * + * BY: PeteW + */ + +#ifndef _FILESTREAM_H_ +#define _FILESTREAM_H_ + +#ifndef _FILEIO_H_ +#include "Core/fileio.h" +#endif +#ifndef _STREAM_H_ +#include "Core/stream.h" +#endif + +class FileStream : public Stream +{ +public: + enum AccessMode + { + Read = File::Read, + Write = File::Write, + ReadWrite = File::ReadWrite, + WriteAppend = File::WriteAppend + }; + enum + { + BUFFER_SIZE = 8 * 1024, // this can be changed to anything appropriate + BUFFER_INVALID = 0xffffffff // file offsets must all be less than this + }; + +private: + File mFile; // file being streamed + U32 mStreamCaps; // dependent on access mode + 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 + +public: + FileStream(); // default constructor + virtual ~FileStream(); // destructor + + // 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... + bool open(const char *i_pFilename, AccessMode i_openMode); + void close(); + + bool flush(); + +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(); +}; + +#endif // _FILE_STREAM_H diff --git a/core/fileio.h b/core/fileio.h new file mode 100644 index 0000000..79b9690 --- /dev/null +++ b/core/fileio.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FILEIO_H_ +#define _FILEIO_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +class File +{ +public: + enum Status + { + Ok = 0, // obvious + 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 AccessMode + { + Read = 0, + Write = 1, + ReadWrite = 2, + WriteAppend = 3 + }; + enum Capability + { + FileRead = (1<<0), + FileWrite = (1<<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&); // disable copy constructor + File& operator=(const File&); // disable assignment + +public: + File(); // default constructor + virtual ~File(); // destructor + + Status open(const char *filename, const AccessMode openMode); + U32 getPosition() const; + Status setPosition(S32 position, bool absolutePos = true); + U32 getSize() const; + Status flush(); + Status close(); + Status getStatus() const; + Status read(U32 size, char *dst, U32 *bytesRead = NULL); + Status write(U32 size, const char *src, U32 *bytesWritten = NULL); + bool hasCapability(Capability cap) const; + +protected: + Status setStatus(); // called after error encountered + Status setStatus(Status status); // assign specific value +}; + +#endif // _FILE_IO_H_ diff --git a/core/filterStream.cc b/core/filterStream.cc new file mode 100644 index 0000000..0ab68c4 --- /dev/null +++ b/core/filterStream.cc @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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..b23bbe5 --- /dev/null +++ b/core/filterStream.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FILTERSTREAM_H_ +#define _FILTERSTREAM_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _STREAM_H_ +#include "Core/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/findMatch.cc b/core/findMatch.cc new file mode 100644 index 0000000..cad626c --- /dev/null +++ b/core/findMatch.cc @@ -0,0 +1,114 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Core/findMatch.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( ((s=dStrchr(s,*e)) !=NULL) && !match ) + { + 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 successfull + match = false; + + return ( match ); +} diff --git a/core/findMatch.h b/core/findMatch.h new file mode 100644 index 0000000..b7f9b7e --- /dev/null +++ b/core/findMatch.h @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FINDMATCH_H_ +#define _FINDMATCH_H_ + +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + +class FindMatch +{ + char* expression; + U32 maxMatches; + + public: + static bool isMatch( const char *exp, const char *string, bool caseSensitive = false ); + 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() >= maxMatches); } + void clear() { matchList.clear(); } +}; + +#endif // _FINDMATCH_H_ diff --git a/core/idGenerator.cc b/core/idGenerator.cc new file mode 100644 index 0000000..94f6724 --- /dev/null +++ b/core/idGenerator.cc @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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..5d76da6 --- /dev/null +++ b/core/idGenerator.h @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _IDGENERATOR_H_ +#define _IDGENERATOR_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/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/llist.h b/core/llist.h new file mode 100644 index 0000000..ee647b0 --- /dev/null +++ b/core/llist.h @@ -0,0 +1,308 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef LLIST_H +#define LLIST_H + +//*************************************************************************************** +// Linked List +//*************************************************************************************** +// This template has an overhead of size LListNode for each entry in the linked list - +// be aware of this for very large linked lists. +// +// This template has no destructor! You must explicitly call free() if you want the +// contents of the list to be destroyed. +// +// +// WARNING - this template has not been thoroughly tested so there may be bugs in it! +// +// -Bramage +//--------------------------------------------------------------------------------------- + + +//--------------------------------------------------------------------------------------- +// LListNode template data node +//--------------------------------------------------------------------------------------- +template class LListNode +{ +public: + LListNode * Next; + LListNode * Prev; + T * Data; + + LListNode() + { + Next = NULL; + Prev = NULL; + Data = NULL; + } + +}; + + +//--------------------------------------------------------------------------------------- +// LList template +//--------------------------------------------------------------------------------------- +template class LList +{ +protected: + LListNode< T > *first_entry; + LListNode< T > *last_entry; + int cnt; + +public: + + //--------------------------------------------------------------------------------------- + // Constructor initializes empty list + //--------------------------------------------------------------------------------------- + LList() + { + reset(); + } + + //--------------------------------------------------------------------------------------- + // Reset list to empty state by abandoning contents + //--------------------------------------------------------------------------------------- + void reset(void) + { + first_entry = NULL; + last_entry = NULL; + cnt = 0; + } + + //--------------------------------------------------------------------------------------- + // Return entry count + //--------------------------------------------------------------------------------------- + int size(void) const + { + return cnt; + } + + //--------------------------------------------------------------------------------------- + // Return first list entry (NULL if list empty) + //--------------------------------------------------------------------------------------- + T *first(void) const + { + if( first_entry ){ + return first_entry->Data; + } + else + { + return NULL; + } + } + + //--------------------------------------------------------------------------------------- + // Return last list entry (NULL if list empty) + //--------------------------------------------------------------------------------------- + T *last(void) const + { + if( last_entry ) + { + return last_entry->Data; + } + else + { + return NULL; + } + } + + //--------------------------------------------------------------------------------------- + // Returns next entry - returns first entry if current == NULL + //--------------------------------------------------------------------------------------- + T *next( T* current ) + { + if( current == NULL ) + { + return first(); + } + + LListNode *next = findNode( current )->Next; + if( next ) + { + return next->Data; + } + else + { + return NULL; + } + } + + //--------------------------------------------------------------------------------------- + // Returns prev entry - returns last entry if current == NULL + //--------------------------------------------------------------------------------------- + T *prev( T *current ) + { + if( current == NULL ) + { + return last(); + } + + LListNode *prev = findNode( current )->Prev; + if( prev ) + { + return prev->Data; + } + else + { + return NULL; + } + } + + //--------------------------------------------------------------------------------------- + // Link new item into list before specified entry + // If specified entry==NULL, insert at end of list + //--------------------------------------------------------------------------------------- + T *link(T *entry, T *next = NULL) + { + LListNode *prevNode = NULL; + LListNode *nextNode = findNode( next ); + LListNode *newNode = new LListNode; + + newNode->Data = entry; + + if( nextNode == NULL) + { + prevNode = last_entry; + last_entry = newNode; + } + else + { + prevNode = nextNode->Prev; + nextNode->Prev = newNode; + } + + if( prevNode == NULL ) + { + first_entry = newNode; + } + else + { + prevNode->Next = newNode; + } + + newNode->Next = nextNode; + newNode->Prev = prevNode; + + ++cnt; + + return entry; + } + + //--------------------------------------------------------------------------------------- + // Link new item into list before specified entry + // If specified entry==NULL, insert at end of list + //--------------------------------------------------------------------------------------- + T *link(T &entry, T *next = NULL) + { + T *newEntry = new T; + *newEntry = entry; + + return link( newEntry, next ); + } + + //--------------------------------------------------------------------------------------- + // Unlink item from list (without destroying it) + //--------------------------------------------------------------------------------------- + void unlink(T *entry) + { + LListNode *entryNode = findNode( entry ); + if( !entryNode ) return; + + + if( entryNode->Prev == NULL ) + { + first_entry = entryNode->Next; + } + else + { + entryNode->Prev->Next = entryNode->Next; + } + + if( entryNode->Next == NULL ) + { + last_entry = entryNode->Prev; + } + else + { + entryNode->Next->Prev = entryNode->Prev; + } + + delete entryNode; + + --cnt; + } + + //--------------------------------------------------------------------------------------- + // Allocate entry and insert before specified entry + // If specified entry==NULL, insert at end of list + //--------------------------------------------------------------------------------------- + T * alloc( T *next = NULL ) + { + T *entry = new T; + + if( entry == NULL ) + { + return NULL; + } + + return link( entry, next ); + } + + //--------------------------------------------------------------------------------------- + // Unlink item from list and destroy it + //--------------------------------------------------------------------------------------- + void free(T *entry) + { + unlink(entry); + delete entry; + } + + //--------------------------------------------------------------------------------------- + // Unlink and destroy all list items + //--------------------------------------------------------------------------------------- + void free(void) + { + LListNode *node = NULL; + + while( bool( node = iterate( node ) ) ) + { + LListNode *nodeToKill = node; + node = node->Prev; + free( nodeToKill->Data ); + } + } + + //--------------------------------------------------------------------------------------- + // Find node + //--------------------------------------------------------------------------------------- + LListNode *findNode( T *entry ) + { + LListNode *it = NULL; + while( bool( it = iterate( it ) ) ) + { + if( it->Data == entry ) + { + return it; + } + } + return NULL; + } + + //--------------------------------------------------------------------------------------- + // Returns the next node in list + //--------------------------------------------------------------------------------------- + LListNode *iterate( LListNode *entry = NULL ) + { + if( entry == NULL ) return first_entry; + return entry->Next; + } + +}; + + +#endif diff --git a/core/memStream.cc b/core/memStream.cc new file mode 100644 index 0000000..c84e73a --- /dev/null +++ b/core/memStream.cc @@ -0,0 +1,159 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Core/memstream.h" + +MemStream::MemStream(const U32 in_bufferSize, + void* io_pBuffer, + const bool in_allowRead, + const bool in_allowWrite) + : cm_bufferSize(in_bufferSize), + m_pBufferBase(io_pBuffer), + m_instCaps(0), + m_currentPosition(0) +{ + AssertFatal(io_pBuffer != NULL, "Invalid buffer pointer"); + AssertFatal(in_bufferSize > 0, "Invalid buffer size"); + AssertFatal(in_allowRead || in_allowWrite, "Either write or read must be allowed"); + + if (in_allowRead) + m_instCaps |= Stream::StreamRead; + if (in_allowWrite) + m_instCaps |= Stream::StreamWrite; + + setStatus(Ok); +} + +MemStream::~MemStream() +{ + m_pBufferBase = NULL; + m_currentPosition = 0; + + setStatus(Closed); +} + +U32 MemStream::getStreamSize() +{ + AssertFatal(getStatus() != Closed, "Stream not open, size undefined"); + + return cm_bufferSize; +} + +bool MemStream::hasCapability(const Capability in_cap) const +{ + // Closed streams can't do anything + // + if (getStatus() == Closed) + return false; + + U32 totalCaps = U32(Stream::StreamPosition) | m_instCaps; + + return (U32(in_cap) & totalCaps) != 0; +} + +U32 MemStream::getPosition() const +{ + AssertFatal(getStatus() != Closed, "Position of a closed stream is undefined"); + + return m_currentPosition; +} + +bool MemStream::setPosition(const U32 in_newPosition) +{ + AssertFatal(getStatus() != Closed, "SetPosition of a closed stream is not allowed"); + AssertFatal(in_newPosition <= cm_bufferSize, "Invalid position"); + + m_currentPosition = in_newPosition; + if (m_currentPosition > cm_bufferSize) { + // Never gets here in debug version, this is for the release builds... + // + setStatus(UnknownError); + return false; + } else if (m_currentPosition == cm_bufferSize) { + 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 ((m_currentPosition + in_numBytes) > cm_bufferSize) { + success = false; + actualBytes = cm_bufferSize - m_currentPosition; + } + + // Obtain a current pointer, and do the copy + const void* pCurrent = (const void*)((const U8*)m_pBufferBase + m_currentPosition); + dMemcpy(out_pBuffer, pCurrent, actualBytes); + + // Advance the stream position + m_currentPosition += 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; + + AssertFatal(in_pBuffer != NULL, "Invalid input buffer"); + + if (hasCapability(StreamWrite) == false) { + AssertWarn(0, "Writing is disallowed on this stream"); + setStatus(IllegalCall); + return false; + } + + bool success = true; + U32 actualBytes = in_numBytes; + if ((m_currentPosition + in_numBytes) > cm_bufferSize) { + success = false; + actualBytes = cm_bufferSize - m_currentPosition; + } + + // Obtain a current pointer, and do the copy + void* pCurrent = (void*)((U8*)m_pBufferBase + m_currentPosition); + dMemcpy(pCurrent, in_pBuffer, actualBytes); + + // Advance the stream position + m_currentPosition += actualBytes; + + if (m_currentPosition == cm_bufferSize) + setStatus(EOS); + else + setStatus(Ok); + + return success; +} + diff --git a/core/memstream.h b/core/memstream.h new file mode 100644 index 0000000..45ba797 --- /dev/null +++ b/core/memstream.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MEMSTREAM_H_ +#define _MEMSTREAM_H_ + +//Includes +#ifndef _STREAM_H_ +#include "Core/stream.h" +#endif + +class MemStream : public Stream { + typedef Stream Parent; + + protected: + U32 const cm_bufferSize; + void* m_pBufferBase; + + U32 m_instCaps; + U32 m_currentPosition; + + public: + MemStream(const U32 in_bufferSize, + void* io_pBuffer, + const bool in_allowRead = true, + const bool in_allowWrite = true); + ~MemStream(); + + // Mandatory overrides from Stream + 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); + + // Mandatory overrides from Stream + public: + U32 getStreamSize(); +}; + +#endif //_MEMSTREAM_H_ diff --git a/core/nStream.cc b/core/nStream.cc new file mode 100644 index 0000000..597c284 --- /dev/null +++ b/core/nStream.cc @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Core/stream.h" +#include "Core/stringTable.h" +#include "Core/color.h" + +Stream::Stream() + : m_streamStatus(Closed) +{ + // +} + +Stream::~Stream() +{ + // +} + +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' ) + buff--; + *buff = 0; +} + +void Stream::writeLine(U8 *buffer) +{ + write(dStrlen((StringTableEntry)buffer), buffer); + write(2, "\r\n"); +} + +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; +} + diff --git a/core/nTypes.cc b/core/nTypes.cc new file mode 100644 index 0000000..17de59d --- /dev/null +++ b/core/nTypes.cc @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" + + +//-------------------------------------- +U32 getNextPow2(U32 io_num) +{ + S32 oneCount = 0; + S32 shiftCount = -1; + while (io_num) { + if(io_num & 1) + oneCount++; + shiftCount++; + io_num >>= 1; + } + if(oneCount > 1) + shiftCount++; + + return U32(1 << shiftCount); +} + +//-------------------------------------- +U32 getBinLog2(U32 io_num) +{ + AssertFatal(io_num != 0 && isPow2(io_num) == true, + "Error, this only works on powers of 2 > 0"); + + S32 shiftCount = 0; + while (io_num) { + shiftCount++; + io_num >>= 1; + } + + return U32(shiftCount - 1); +} + +#ifdef linux + +// Linux has no stricmp function, we must provide it. Stolen from +// the MS CRT source. +// +S32 stricmp(const char* in_p1, const char* in_p2) +{ + S32 f, l; + + do { + f = dTolower( (unsigned char)(*(in_p1++)) ); + l = dTolower( (unsigned char)(*(in_p2++)) ); + } while ( f && (f == l) ); + + return f - l; +} + +#endif diff --git a/core/polyList.h b/core/polyList.h new file mode 100644 index 0000000..1278394 --- /dev/null +++ b/core/polyList.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POLYLIST_H_ +#define _POLYLIST_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MPLANE_H_ +#include "Math/mPlane.h" +#endif + +class PolyList +{ + public: + struct Poly { + public: + PlaneF plane; + U32 material; + + U16 vStart; + U16 vCount; + + // NOTE: USE THESE FUNCTIONS! The above exposed implementation is likely + // to change soon. + public: + U16 getVIndex(U16 n) const { return U16(vStart + n); } + U16 getVCount() const { return vCount; } + + const PlaneF& getPlane() const { return plane; } + U32 getMaterial() const { return material; } + }; + + public: + PolyList() { + VECTOR_SET_ASSOCIATION(mPolys); + VECTOR_SET_ASSOCIATION(mVertices); + } + + Vector mPolys; + Vector mVertices; +}; + +#endif //_POLYLIST_H_ diff --git a/core/realComp.h b/core/realComp.h new file mode 100644 index 0000000..8b5e81c --- /dev/null +++ b/core/realComp.h @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _REALCOMP_H_ +#define _REALCOMP_H_ + +//Includes +#ifndef _MMATHFN_H_ +#include "Math/mMathFn.h" +#endif +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +inline bool isEqual(F32 a, F32 b) +{ + return mFabs(a - b) < __EQUAL_CONST_F; +} + +inline bool isZero(F32 a) +{ + return mFabs(a) < __EQUAL_CONST_F; +} + +#endif //_REALCOMP_H_ diff --git a/core/resDictionary.cc b/core/resDictionary.cc new file mode 100644 index 0000000..ff444a6 --- /dev/null +++ b/core/resDictionary.cc @@ -0,0 +1,130 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Core/resManager.h" +#include "Core/tAlgorithm.h" + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +ResDictionary::ResDictionary() +{ + entryCount = 0; + hashTableSize = 1023; //DefaultTableSize; + hashTable = new ResourceObject *[hashTableSize]; + S32 i; + for(i = 0; i < hashTableSize; i++) + hashTable[i] = NULL; +} + +ResDictionary::~ResDictionary() +{ + // we assume the resources are purged before we destroy + // the dictionary + + delete[] hashTable; +} + +S32 ResDictionary::hash(StringTableEntry path, StringTableEntry file) +{ + return ((U32(path) >> 2) + (U32(file) >> 2) ) % hashTableSize; +} + +void ResDictionary::insert(ResourceObject *obj, StringTableEntry path, StringTableEntry file) +{ + obj->name = file; + obj->path = path; + + S32 idx = hash(path, file); + obj->nextEntry = hashTable[idx]; + hashTable[idx] = obj; + entryCount++; + + if(entryCount > hashTableSize) { + ResourceObject *head = NULL, *temp, *walk; + for(idx = 0; idx < hashTableSize;idx++) { + walk = hashTable[idx]; + while(walk) + { + temp = walk->nextEntry; + walk->nextEntry = head; + head = walk; + walk = temp; + } + } + delete[] hashTable; + hashTableSize = 2 * hashTableSize - 1; + hashTable = new ResourceObject *[hashTableSize]; + for(idx = 0; idx < hashTableSize; idx++) + hashTable[idx] = NULL; + walk = head; + while(walk) + { + temp = walk->nextEntry; + idx = hash(walk); + walk->nextEntry = hashTable[idx]; + hashTable[idx] = walk; + walk = temp; + } + } +} + +ResourceObject* ResDictionary::find(StringTableEntry path, StringTableEntry name) +{ + for(ResourceObject *walk = hashTable[hash(path, name)]; walk; walk = walk->nextEntry) + if(walk->name == name && walk->path == path) + return walk; + return NULL; +} + +ResourceObject* ResDictionary::find(StringTableEntry path, StringTableEntry name, StringTableEntry filePath, StringTableEntry fileName) +{ + for(ResourceObject *walk = hashTable[hash(path, name)]; walk; walk = walk->nextEntry) + if(walk->name == name && walk->path == path && walk->fileName == fileName && walk->filePath == filePath) + return walk; + return NULL; +} + +ResourceObject* ResDictionary::find(StringTableEntry path, StringTableEntry name, U32 flags) +{ + for(ResourceObject *walk = hashTable[hash(path, name)]; walk; walk = walk->nextEntry) + if(walk->name == name && walk->path == path && U32(walk->flags) == flags) + return walk; + return NULL; +} + +void ResDictionary::pushBehind(ResourceObject *resObj, S32 flagMask) +{ + remove(resObj); + entryCount++; + ResourceObject **walk = &hashTable[hash(resObj)]; + for(; *walk; walk = &(*walk)->nextEntry) + { + if(!((*walk)->flags & flagMask)) + { + resObj->nextEntry = *walk; + *walk = resObj; + return; + } + } + resObj->nextEntry = NULL; + *walk = resObj; +} + +void ResDictionary::remove(ResourceObject *resObj) +{ + for(ResourceObject **walk = &hashTable[hash(resObj)]; *walk; walk = &(*walk)->nextEntry) + { + if(*walk == resObj) + { + entryCount--; + *walk = resObj->nextEntry; + return; + } + } +} diff --git a/core/resManager.cc b/core/resManager.cc new file mode 100644 index 0000000..cd6b265 --- /dev/null +++ b/core/resManager.cc @@ -0,0 +1,970 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/tVector.h" +#include "core/stream.h" + +#include "core/fileStream.h" +#include "core/zipSubStream.h" +#include "core/zipAggregate.h" +#include "core/zipHeaders.h" +#include "core/resizeStream.h" +#include "sim/frameAllocator.h" + +#include "core/resManager.h" +#include "core/findMatch.h" + +#include "console/console.h" + +ResManager* ResourceManager = NULL; +static char sgCurExeDir[1024]; +static S32 sgCurExeDirStrLen; + +//------------------------------------------------------------------------------ +ResourceObject::ResourceObject() +{ + next = NULL; + prev = NULL; + lockCount = 0; + mInstance = NULL; +} + +void ResourceObject::destruct() +{ + // If the resource was not loaded because of an error, the resource + // pointer will be NULL + if (mInstance) { + delete mInstance; + mInstance = NULL; + } +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +ResManager::ResManager() +{ + echoFileNames = 0; + primaryPath[0] = 0; + writeablePath[0] = 0; + pathList = NULL; + resourceList.nextResource = NULL; + resourceList.next = NULL; + resourceList.prev = NULL; + timeoutList.nextResource = NULL; + timeoutList.next = NULL; + timeoutList.prev = NULL; + sgCurExeDir[0] = '\0'; + Platform::getCurrentDirectory(sgCurExeDir, 1024); + sgCurExeDirStrLen = dStrlen(sgCurExeDir); + registeredList = NULL; +} + +void ResourceObject::getFileTimes(FileTime *createTime, FileTime *modifyTime) +{ + char buffer[1024]; +#ifdef __linux + dSprintf( buffer, sizeof( buffer ), "%s/%s", filePath, fileName ); +#else + dSprintf(buffer, sizeof(buffer), "%s/%s/%s", sgCurExeDir, filePath, fileName); +#endif + Platform::getFileTimes(buffer, createTime, modifyTime); +} + +//------------------------------------------------------------------------------ +ResManager::~ResManager() +{ + purge(); + // volume list should be gone. + + if ( pathList ) + dFree( pathList ); + + for(ResourceObject *walk = resourceList.nextResource; walk; walk = walk->nextResource) + walk->destruct(); + + while(resourceList.nextResource) + freeResource(resourceList.nextResource); + + while(registeredList) + { + RegisteredExtension *temp = registeredList->next; + delete registeredList; + registeredList = temp; + } +} + +#ifdef DEBUG +void ResManager::dumpLoadedResources() +{ + ResourceObject* walk = resourceList.nextResource; + while (walk != NULL) + { + if (walk->mInstance != NULL) + { + Con::errorf("LoadedRes: %s/%s (%d)", walk->path, walk->name, walk->lockCount); + } + walk = walk->nextResource; + } +} +#endif + +//------------------------------------------------------------------------------ +void ResManager::create() +{ + AssertFatal(ResourceManager == NULL, "ResourceManager::create: manager already exists."); + ResourceManager = new ResManager; +} + + +//------------------------------------------------------------------------------ +void ResManager::destroy() +{ + AssertFatal(ResourceManager != NULL, "ResourceManager::destroy: manager does not exist."); + delete ResourceManager; + ResourceManager = NULL; +} + +//------------------------------------------------------------------------------ + +void ResManager::setFileNameEcho(bool on) +{ + echoFileNames = on; +} + +//------------------------------------------------------------------------------ + +bool ResManager::isValidWriteFileName(const char *fn) +{ + // all files must be based off the VFS + if(fn[0] == '/' || dStrchr(fn, ':')) + return false; + + if(!writeablePath[0]) + return true; + + // get the path to the file + const char *path = dStrrchr(fn, '/'); + if(!path) + path = fn; + else + { + if(!dStrchr(path, '.')) + return false; + } + // now loop through the writeable path. + const char *start = writeablePath; + S32 pathLen = path - fn; + for(;;) + { + const char *end = dStrchr(writeablePath, ';'); + if(!end) + end = writeablePath + dStrlen(writeablePath); + + if(end - start == pathLen && !dStrnicmp(start, path, pathLen)) + return true; + if(end[0]) + start = end + 1; + else + break; + } + return false; +} + +void ResManager::setWriteablePath(const char *path) +{ + dStrcpy(writeablePath, path); +} + +//------------------------------------------------------------------------------ +static const char *buildPath(StringTableEntry path, StringTableEntry file) +{ + static char buf[1024]; + if(path) + dSprintf(buf, sizeof(buf), "%s/%s", path, file); + else + dStrcpy(buf, file); + return buf; +} + +//------------------------------------------------------------------------------ +static void getPaths(const char *fullPath, StringTableEntry &path, StringTableEntry &fileName) +{ + static char buf[1024]; + char *ptr = (char *) dStrrchr(fullPath, '/'); + if(!ptr) + { + path = NULL; + fileName = StringTable->insert(fullPath); + } + else + { + S32 len = ptr - fullPath; + dStrncpy(buf, fullPath, len); + buf[len] = 0; + fileName = StringTable->insert(ptr + 1); + path = StringTable->insert(buf); + } +} + +//------------------------------------------------------------------------------ +bool ResManager::scanZip(ResourceObject *zipObject) +{ + // now open the volume and add all its resources to the dictionary + ZipAggregate zipAggregate; + if (zipAggregate.openAggregate(buildPath(zipObject->filePath, zipObject->fileName)) == false) { + AssertFatal(false, "Error opening zip, need to handle this better..."); + return false; + } + ZipAggregate::iterator itr; + for (itr = zipAggregate.begin(); itr != zipAggregate.end(); itr++) { + const ZipAggregate::FileEntry& rEntry = *itr; + + ResourceObject* ro = createResource(rEntry.pPath, rEntry.pFileName, + zipObject->filePath, zipObject->fileName); + + ro->flags = ResourceObject::VolumeBlock; + ro->fileSize = rEntry.fileSize; + ro->compressedFileSize = rEntry.compressedFileSize; + ro->fileOffset = rEntry.fileOffset; + + dictionary.pushBehind(ro, ResourceObject::File); + } + zipAggregate.closeAggregate(); + + return true; +} + +//------------------------------------------------------------------------------ +void ResManager::searchPath(const char* basePath) +{ + AssertFatal(basePath != NULL, "No path to dump?"); + + Vector fileInfoVec; + Platform::dumpPath(basePath, fileInfoVec); + + for (U32 i = 0; i < fileInfoVec.size(); i++) { + Platform::FileInfo& rInfo = fileInfoVec[i]; + + // Create a resource for this file... + // + ResourceObject *ro = createResource(rInfo.pVirtPath, rInfo.pFileName, rInfo.pFullPath, rInfo.pFileName); + dictionary.pushBehind(ro, ResourceObject::File); + + ro->flags = ResourceObject::File; + ro->fileOffset = 0; + ro->fileSize = rInfo.fileSize; + ro->compressedFileSize = rInfo.fileSize; + + // see if it's a zip + const char *extension = dStrrchr(ro->fileName, '.'); + if(extension && !dStricmp(extension, ".zip")) + scanZip(ro); + } +} + +//------------------------------------------------------------------------------ +void ResManager::setModPaths(U32 numPaths, const char **paths) +{ + // detach all the files. + for(ResourceObject *pwalk = resourceList.nextResource; pwalk; pwalk = pwalk->nextResource) + pwalk->flags = ResourceObject::Added; + + bool primaryWriteSet = false; + primaryPath[0] = 0; + U32 pathLen = 0; + + U32 i; + for(i = 0; i < numPaths; i++) + { + // silent fail on any invalid paths + if(dStrchr(paths[i], '/') || dStrchr(paths[i], '.') || dStrchr(paths[i], ':') || dStrlen(paths[i]) == 0) + continue; + if(!primaryWriteSet) + { + dStrcpy(primaryPath, paths[i]); + primaryWriteSet = true; + } + pathLen += ( dStrlen( paths[i] ) + 1 ); + searchPath(paths[i]); +#ifdef __linux + // FIXME: Add this to Platform:: and implement this for Windows/PPC. + extern bool platformGetUserPath( const char*, char*, U32 ); + char user[256]; + + if( platformGetUserPath( paths[i], user, 256 ) ) { + searchPath( user ); + } +#endif + } + + pathList = (char*) dRealloc( pathList, pathLen ); + dStrcpy( pathList, paths[0] ); + U32 strlen; + for ( i = 1; i < numPaths; i++ ) + { + strlen = dStrlen( pathList ); + dSprintf( pathList + strlen, pathLen - strlen, ";%s", paths[i] ); + } + + // unlink all the added baddies that aren't loaded. + ResourceObject *rwalk = resourceList.nextResource, *rtemp; + while(rwalk != NULL) + { + if((rwalk->flags & ResourceObject::Added) && !rwalk->mInstance) + { + rwalk->unlink(); + dictionary.remove(rwalk); + rtemp = rwalk->nextResource; + freeResource(rwalk); + rwalk = rtemp; + } + else + rwalk = rwalk->nextResource; + } +} + +//------------------------------------------------------------------------------ +const char* ResManager::getModPaths() +{ + return( (const char*) pathList ); +} + +//------------------------------------------------------------------------------ + +S32 ResManager::getSize(const char *fileName) +{ + ResourceObject *ro = find(fileName); + if(!ro) + return 0; + else + return ro->fileSize; +} + +//------------------------------------------------------------------------------ +const char* ResManager::getFullPath(const char* fileName, char *path, U32 pathlen) +{ + AssertFatal(fileName, "ResourceManager::getFullPath: fileName is NULL"); + AssertFatal(path, "ResourceManager::getFullPath: path is NULL"); + ResourceObject *obj = find(fileName); + if(!obj) + dStrcpy(path, fileName); + else + dSprintf(path, pathlen, "%s/%s", obj->filePath, fileName); + return path; +} + +//------------------------------------------------------------------------------ +const char* ResManager::getPathOf(const char* fileName) +{ + AssertFatal(fileName, "ResourceManager::getPathOf: fileName is NULL"); + ResourceObject *obj = find(fileName); + if(!obj) + return NULL; + else + return obj->filePath; +} + +//------------------------------------------------------------------------------ +const char* ResManager::getModPathOf(const char* fileName) +{ + AssertFatal(fileName, "ResourceManager::getModPathOf: fileName is NULL"); + + if (!pathList) + return NULL; + + ResourceObject *obj = find(fileName); + if(!obj) + return NULL; + + char buffer[256]; + char *base; + const char *list = pathList; + do + { + base = buffer; + *base = 0; + while (*list && *list != ';') + { + *base++ = *list++; + } + if (*list == ';') + ++list; + + *base = 0; + + if (dStrncmp(buffer, obj->filePath, (base-buffer)) == 0) + return StringTable->insert(buffer); + + }while(*list); + + return NULL; +} + +//------------------------------------------------------------------------------ +const char* ResManager::getBasePath() +{ + if (!pathList) + return NULL; + const char *base = dStrrchr(pathList, ';'); + return base ? (base+1) : pathList; +} + + +//------------------------------------------------------------------------------ + +void ResManager::registerExtension(const char *name, RESOURCE_CREATE_FN create_fn) +{ + AssertFatal(!getCreateFunction(name), "ResourceManager::registerExtension: file extension already registered."); + + const char *extension = dStrrchr( name, '.' ); + AssertFatal(extension, "ResourceManager::registerExtension: file has no extension."); + + RegisteredExtension *add = new RegisteredExtension; + add->mExtension = StringTable->insert(extension); + add->mCreateFn = create_fn; + add->next = registeredList; + registeredList = add; +} + +//------------------------------------------------------------------------------ +RESOURCE_CREATE_FN ResManager::getCreateFunction( const char *name ) +{ + const char *s = dStrrchr( name, '.' ); + if (!s) return (NULL); + + RegisteredExtension *itr = registeredList; + while (itr) + { + if (dStricmp(s, itr->mExtension) == 0) + return (itr->mCreateFn); + itr = itr->next; + } + return (NULL); +} + + +//------------------------------------------------------------------------------ +void ResManager::unlock(ResourceObject *obj) +{ + if (!obj) return; + AssertFatal(obj->lockCount > 0, "ResourceManager::unlock: lock count is zero."); + //set the timeout to the max requested + if (--obj->lockCount == 0) + obj->linkAfter(&timeoutList); +} + +//------------------------------------------------------------------------------ +// gets the crc of the file, ignores the stream type +bool ResManager::getCrc(const char * fileName, U32 & crcVal, const U32 crcInitialVal ) +{ + ResourceObject * obj = find(fileName); + if(!obj) + return(false); + + // check if in a volume + if(obj->flags & (ResourceObject::VolumeBlock | ResourceObject::File)) + { + // can't crc locked resources... + if(obj->lockCount) + return false; + + // get rid of the resource + // have to make sure user can't have it sitting around in the resource cache + + obj->unlink(); + obj->destruct(); + + Stream *stream = openStream(obj); + + U32 waterMark = 0xFFFFFFFF; + + U8 *buffer; + U32 maxSize = FrameAllocator::getHighWaterMark() - FrameAllocator::getWaterMark(); + if(maxSize < obj->fileSize) + buffer = new U8[obj->fileSize]; + else + { + waterMark = FrameAllocator::getWaterMark(); + buffer = (U8*) FrameAllocator::alloc(obj->fileSize); + } + stream->read(obj->fileSize, buffer); + // get the crc value + crcVal = calculateCRC(buffer, obj->fileSize, crcInitialVal); + if(waterMark == 0xFFFFFFFF) + delete [] buffer; + else + FrameAllocator::setWaterMark(waterMark); + + closeStream(stream); + + return(true); + } + + return(false); +} + +//------------------------------------------------------------------------------ + +ResourceObject* ResManager::load(const char *fileName, bool computeCRC) +{ + // if filename is not known, exit now + ResourceObject *obj = find(fileName); + if(!obj) + return NULL; + + // if noone has a lock on this, but it's loaded and it needs to + // be CRC'd, delete it and reload it. + if(!obj->lockCount && computeCRC && obj->mInstance) + obj->destruct(); + + obj->lockCount++; + obj->unlink(); // remove from purge list + if(!obj->mInstance) + { + obj->mInstance = loadInstance(obj, computeCRC); + if(!obj->mInstance) + { + obj->lockCount--; + return NULL; + } + } + return obj; +} + +//------------------------------------------------------------------------------ +ResourceInstance *ResManager::loadInstance(const char *fileName, bool computeCRC) +{ + // if filename is not known, exit now + ResourceObject *obj = find(fileName); + if(!obj) + return NULL; + + return loadInstance(obj, computeCRC); +} + +//------------------------------------------------------------------------------ + +static const char *alwaysCRCList = ".ter.dif.dts"; + +ResourceInstance *ResManager::loadInstance(ResourceObject *obj, bool computeCRC) +{ + Stream *stream = openStream(obj); + if(!stream) + return NULL; + + if(!computeCRC) + { + const char *x = dStrrchr(obj->name, '.'); + if(x && dStrstr(alwaysCRCList, x)) + computeCRC = true; + } + + if(computeCRC) + obj->crc = calculateCRCStream(stream, InvalidCRC); + else + obj->crc = InvalidCRC; + + RESOURCE_CREATE_FN createFunction = ResourceManager->getCreateFunction(obj->name); + AssertFatal(createFunction, "ResourceObject::construct: NULL resource create function."); + ResourceInstance *ret = createFunction( *stream ); + closeStream(stream); + return ret; +} + +//------------------------------------------------------------------------------ +Stream* ResManager::openStream(const char * fileName) +{ + ResourceObject *obj = find(fileName); + if(!obj) + return NULL; + return openStream(obj); +} + +//------------------------------------------------------------------------------ +Stream* ResManager::openStream(ResourceObject *obj) +{ + // if filename is not known, exit now + if(!obj) + return NULL; + + if(echoFileNames) + Con::printf("FILE ACCESS: %s/%s", obj->path, obj->name); + + // used for openStream stream access + FileStream* diskStream = NULL; + + if(obj->flags & (ResourceObject::File | ResourceObject::VolumeBlock)) + { + diskStream = new FileStream; + diskStream->open(buildPath(obj->filePath, obj->fileName), FileStream::Read); + + if(obj->flags & ResourceObject::File) { + obj->fileSize = diskStream->getStreamSize(); + } + diskStream->setPosition(obj->fileOffset); + + if (obj->flags & ResourceObject::VolumeBlock) + { + ZipLocalFileHeader zlfHeader; + + if (zlfHeader.readFromStream(*diskStream) == false) + { + AssertFatal(false, avar("ResourceManager::loadStream: '%s' Not in the zip! (%s)", obj->name, obj->fileName)); + diskStream->close(); + return NULL; + } + + if (zlfHeader.m_header.compressionMethod == ZipLocalFileHeader::Stored || obj->fileSize == 0) + { + // Just read straight from the stream... + ResizeFilterStream *strm = new ResizeFilterStream; + strm->attachStream(diskStream); + strm->setStreamOffset(diskStream->getPosition(), obj->fileSize); + return strm; + } + else + { + if (zlfHeader.m_header.compressionMethod == ZipLocalFileHeader::Deflated) + { + ZipSubRStream* zipStream = new ZipSubRStream; + zipStream->attachStream(diskStream); + zipStream->setUncompressedSize(obj->fileSize); + return zipStream; + } + else + { + AssertFatal(false, avar("ResourceManager::loadStream: '%s' Compressed inappropriately in the zip! (%s)", obj->name, obj->fileName)); + diskStream->close(); + return NULL; + } + } + } + else + { + return diskStream; + } + } + return NULL; +} + + +//------------------------------------------------------------------------------ +void ResManager::closeStream(Stream *stream) +{ + FilterStream *subStream = dynamic_cast(stream); + if(subStream) + { + stream = subStream->getStream(); + subStream->detachStream(); + delete subStream; + } + delete stream; +} + + +//------------------------------------------------------------------------------ +ResourceObject* ResManager::find(const char *fileName) +{ + if(!fileName) + return NULL; + StringTableEntry path, file; + getPaths(fileName, path, file); + return dictionary.find(path, file); +} + +//------------------------------------------------------------------------------ +ResourceObject* ResManager::find(const char *fileName, U32 flags) +{ + if(!fileName) + return NULL; + StringTableEntry path, file; + getPaths(fileName, path, file); + return dictionary.find(path, file, flags); +} + + +//------------------------------------------------------------------------------ +// Add resource constructed outside the manager + +bool ResManager::add(const char* name, ResourceInstance *addInstance, bool extraLock) +{ + StringTableEntry path, file; + getPaths(name, path, file); + + ResourceObject* obj = dictionary.find(path, file); + if (obj && obj->mInstance) + // Resource already exists? + return false; + + if (!obj) + obj = createResource(path, file, NULL, NULL); + + dictionary.pushBehind(obj, ResourceObject::File | ResourceObject::VolumeBlock); + obj->mInstance = addInstance; + obj->lockCount = extraLock ? 2 : 1; + unlock(obj); + return true; +} + +//------------------------------------------------------------------------------ + +void ResManager::purge() +{ + bool found; + do { + ResourceObject *obj = timeoutList.getNext(); + found = false; + while(obj) + { + ResourceObject *temp = obj; + obj = obj->next; + temp->unlink(); + temp->destruct(); + found = true; + if(temp->flags & ResourceObject::Added) + freeResource(temp); + } + } while(found); +} + +//------------------------------------------------------------------------------ + +void ResManager::purge( ResourceObject *obj ) +{ + AssertFatal(obj->lockCount == 0, "ResourceManager::purge: handle lock count is not ZERO.") + obj->unlink(); + obj->destruct(); +} + +//------------------------------------------------------------------------------ +// serialize sorts a list of files by .zip and position within the zip +// it allows an aggregate (material list, etc) to find the preffered +// loading order for a set of files. +//------------------------------------------------------------------------------ + +struct ResourceObjectIndex +{ + ResourceObject *ro; + const char *fileName; + + static S32 QSORT_CALLBACK compare(const void *s1, const void *s2) + { + const ResourceObjectIndex *r1 = (ResourceObjectIndex *) s1; + const ResourceObjectIndex *r2 = (ResourceObjectIndex *) s2; + + if(r1->ro->filePath != r2->ro->filePath) + return r1->ro->filePath - r2->ro->filePath; + if(r1->ro->fileName != r2->ro->fileName) + return r1->ro->fileName - r2->ro->fileName; + return r1->ro->fileOffset - r2->ro->fileOffset; + } +}; + +//------------------------------------------------------------------------------ + +void ResManager::serialize(VectorPtr &filenames) +{ + Vector sortVector; + + sortVector.reserve(filenames.size()); + + U32 i; + for(i = 0; i < filenames.size(); i++) + { + ResourceObjectIndex roi; + roi.ro = find(filenames[i]); + roi.fileName = filenames[i]; + sortVector.push_back(roi); + } + + dQsort((void *) &sortVector[0], sortVector.size(), sizeof(ResourceObjectIndex), ResourceObjectIndex::compare); + for(i = 0; i < filenames.size(); i++) + filenames[i] = sortVector[i].fileName; +} + +//------------------------------------------------------------------------------ +ResourceObject* ResManager::findMatch(const char *expression, const char **fn, ResourceObject *start) +{ + if(!start) + start = resourceList.nextResource; + else + start = start->nextResource; + while(start) + { + const char *fname = buildPath(start->path, start->name); + if(FindMatch::isMatch(expression, fname, false)) + { + *fn = fname; + return start; + } + start = start->nextResource; + } + return NULL; +} + +S32 ResManager::findMatches( FindMatch *pFM ) +{ + static char buffer[16384]; + S32 bufl = 0; + ResourceObject *walk; + for(walk = resourceList.nextResource; walk && !pFM->isFull(); walk = walk->nextResource) + { + const char *fpath = buildPath(walk->path, walk->name); + if(bufl + dStrlen(fpath) >= 16380) + return pFM->numMatches(); + dStrcpy(buffer + bufl, fpath); + if(pFM->findMatch(buffer + bufl)) + bufl += dStrlen(fpath) + 1; + } + return ( pFM->numMatches() ); +} + +//------------------------------------------------------------------------------ +bool ResManager::findFile(const char *name) +{ + return (bool) find(name); +} + +//------------------------------------------------------------------------------ +ResourceObject *ResManager::createResource(StringTableEntry path, StringTableEntry file, StringTableEntry filePath, StringTableEntry fileName) +{ + ResourceObject *newRO = dictionary.find(path, file, filePath, fileName); + if(newRO) + return newRO; + + newRO = new ResourceObject; + newRO->path = path; + newRO->name = file; + newRO->lockCount = 0; + newRO->mInstance = NULL; +// newRO->lockedData = NULL; + newRO->flags = ResourceObject::Added; + newRO->next = newRO->prev = NULL; + newRO->nextResource = resourceList.nextResource; + resourceList.nextResource = newRO; + newRO->prevResource = &resourceList; + if(newRO->nextResource) + newRO->nextResource->prevResource = newRO; + dictionary.insert(newRO, path, file); + newRO->fileSize = newRO->fileOffset = newRO->compressedFileSize = 0; + newRO->filePath = filePath; + newRO->fileName = fileName; + newRO->crc = InvalidCRC; + + return newRO; +} + +//------------------------------------------------------------------------------ +void ResManager::freeResource(ResourceObject *ro) +{ + ro->destruct(); + ro->unlink(); + +// if((ro->flags & ResourceObject::File) && ro->lockedData) +// delete[] ro->lockedData; + + if(ro->prevResource) + ro->prevResource->nextResource = ro->nextResource; + if(ro->nextResource) + ro->nextResource->prevResource = ro->prevResource; + dictionary.remove(ro); + delete ro; +} + + +//------------------------------------------------------------------------------ +// 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 calculateCRC(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 calculateCRCStream(Stream *stream, U32 crcVal ) +{ + stream->setPosition(0); + // check if need to generate the crc table + if(!crcTableValid) + calculateCRCTable(); + + // now calculate the crc + 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 = calculateCRC(buf, slen, crcVal); + } + stream->setPosition(0); + return(crcVal); +} + +bool ResManager::openFileForWrite(FileStream &stream, const char *modPath, const char *fileName, U32 accessMode) +{ + if(!primaryPath[0]) + return false; + if(!isValidWriteFileName(fileName)) + return false; + // tag it on to the first directory + char fnBuf[1024]; + dSprintf(fnBuf, sizeof(fnBuf), "%s/%s", modPath ? modPath : primaryPath, fileName); + if(!Platform::createPath(fnBuf)) // create directory tree + return false; + if(!stream.open(fnBuf, (FileStream::AccessMode) accessMode)) + return false; + + // create a resource for the file. + StringTableEntry rPath, rFileName, vPath, vFileName; + getPaths(fnBuf, rPath, rFileName); + getPaths(fileName, vPath, vFileName); + + ResourceObject *ro = createResource(vPath, vFileName, rPath, rFileName); + ro->flags = ResourceObject::File; + ro->fileOffset = 0; + ro->fileSize = 0; + ro->compressedFileSize = 0; + return true; +} diff --git a/core/resManager.h b/core/resManager.h new file mode 100644 index 0000000..095b46e --- /dev/null +++ b/core/resManager.h @@ -0,0 +1,323 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RESMANAGER_H_ +#define _RESMANAGER_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif + +#ifndef _FILESTREAM_H_ +#include "core/fileStream.h" +#endif +#ifndef _ZIPSUBSTREAM_H_ +#include "core/zipSubStream.h" +#endif +#ifndef _ZIPAGGREGATE_H_ +#include "core/zipAggregate.h" +#endif +#ifndef _ZIPHEADERS_H_ +#include "core/zipHeaders.h" +#endif + + +#define RES_DEFAULT_TIMEOUT (5*60*1000) //5-minutes + +class Stream; +class FileStream; +class ZipSubRStream; +class ResManager; +class FindMatch; + + +extern ResManager *ResourceManager; + +//------------------------------------------------------------------------------ +// Basic resource manager behavior +// -set the mod path +// ResManager scans directory tree under listed base directories +// Any .rpk (.zip) file in the root directory of a mod is added (scanned) +// for resources +// Any files currently in the resource manager become memory resources +// They can be "reattached" to the first file that matches the file name +// +//------------------------------------------------------------------------------ +// all classes which wish to be handled by the resource manager +// need to be +// 1) derrived from ResourceInstance +// 2) register a creation function and file extension with the manager + +class ResourceInstance +{ +private: +public: + virtual ~ResourceInstance() {} +}; + + +typedef ResourceInstance* (*RESOURCE_CREATE_FN)(Stream &stream); + + +//------------------------------------------------------------------------------ +#define InvalidCRC 0xFFFFFFFF + +class ResourceObject +{ + friend class ResDictionary; + friend class ResManager; + + ResourceObject *prev, *next; // timeout list + ResourceObject *nextEntry; // objects are inserted by id or name + ResourceObject *nextResource; // in linked list of all resources + ResourceObject *prevResource; +public: + enum + { + VolumeBlock = 1 << 0, + File = 1 << 1, + Added = 1 << 2, + }; + S32 flags; + + StringTableEntry path; // resource path + StringTableEntry name; // resource name + + StringTableEntry filePath; // path/name of file of volume if in volume + StringTableEntry fileName; + + S32 fileOffset; // offset on disk in fileName file of resource + S32 fileSize; // size on disk of resource block + S32 compressedFileSize; // Actual size of resource data. + + ResourceInstance *mInstance; // ptr ot actual object instance + S32 lockCount; + U32 crc; + + ResourceObject(); + ~ResourceObject() { unlink(); } + + void destruct(); + + ResourceObject* getNext() const { return next; } + void unlink(); + void linkAfter(ResourceObject* res); + void getFileTimes(FileTime *createTime, FileTime *modifyTime); +}; + + +inline void ResourceObject::unlink() +{ + if (next) + next->prev = prev; + if (prev) + prev->next = next; + next = prev = 0; +} + +inline void ResourceObject::linkAfter(ResourceObject* res) +{ + unlink(); + prev = res; + if ((next = res->next) != 0) + next->prev = this; + res->next = this; +} + + +//------------------------------------------------------------------------------ +template class Resource +{ +private: + ResourceObject *obj; + // ***WARNING*** + // Using a faster lock that bypasses the resource manger. + // void _lock() { if (obj) obj->rm->lockResource( obj ); } + void _lock(); + void _unlock(); + +public: + // If assigned a ResourceObject, it's assumed to already have + // been locked, lock count is incremented only for copies or + // assignment from another Resource. + Resource() : obj(NULL) { ; } + Resource(ResourceObject *p) : obj(p) { ; } + Resource(const Resource &res) : obj(res.obj) { _lock(); } + ~Resource() { unlock(); } + + const char *getFilePath() const { return (obj ? obj->path : NULL); } + const char *getFileName() const { return (obj ? obj->name : NULL); } + + Resource& operator= (ResourceObject *p) { _unlock(); obj = p; return *this; } + Resource& operator= (const Resource &r) { _unlock(); obj = r.obj; _lock(); return *this; } + + U32 getCRC() { return (obj ? obj->crc : 0); } + operator bool() const { return ((obj != NULL) && (obj->mInstance != NULL)); } + T* operator->() { return (T*)obj->mInstance; } + T& operator*() { return *((T*)obj->mInstance); } + operator T*() { return (obj) ? (T*)obj->mInstance : (T*)NULL; } + const T* operator->() const { return (const T*)obj->mInstance; } + const T& operator*() const { return *((const T*)obj->mInstance); } + operator const T*() const { return (obj) ? (const T*)obj->mInstance : (const T*)NULL; } + void unlock(); + void purge(); +}; + +template inline void Resource::unlock() +{ + if (obj) { + ResourceManager->unlock( obj ); + obj=NULL; + } +} + +template inline void Resource::purge() +{ + if (obj) { + ResourceManager->unlock( obj ); + if (obj->lockCount == 0) + ResourceManager->purge(obj); + obj = NULL; + } +} +template inline void Resource::_lock() +{ + if (obj) + obj->lockCount++; +} + +template inline void Resource::_unlock() +{ + if (obj) + ResourceManager->unlock( obj ); +} + +#define INVALID_ID ((U32)(~0)) + +//---------------------------------------------------------------------------- +// Map of Names and Object IDs to objects +// Provides fast lookup for name->object, id->object and +// for fast removal of an object given object* +// +class ResDictionary +{ + enum { DefaultTableSize = 1029 }; + + ResourceObject **hashTable; + S32 entryCount; + S32 hashTableSize; + DataChunker memPool; + S32 hash(StringTableEntry path, StringTableEntry name); + S32 hash(ResourceObject *obj) { return hash(obj->path, obj->name); } +public: + ResDictionary(); + ~ResDictionary(); + + void insert(ResourceObject *obj, StringTableEntry path, StringTableEntry file); + ResourceObject* find(StringTableEntry path, StringTableEntry file); + ResourceObject* find(StringTableEntry path, StringTableEntry file, StringTableEntry filePath, StringTableEntry fileName); + ResourceObject* find(StringTableEntry path, StringTableEntry file, U32 flags); + void pushBehind(ResourceObject *obj, S32 mask); + void remove(ResourceObject *obj); +}; + + +//------------------------------------------------------------------------------ +class ResManager +{ +private: + char writeablePath[1024]; + char primaryPath[1024]; + char* pathList; + + ResourceObject timeoutList; + ResourceObject resourceList; + + ResDictionary dictionary; + bool echoFileNames; + + bool scanZip(ResourceObject *zipObject); + + ResourceObject* createResource(StringTableEntry path, StringTableEntry file, StringTableEntry filePath, StringTableEntry fileName); + void freeResource(ResourceObject *resObject); + void searchPath(const char *pathStart); + + struct RegisteredExtension + { + StringTableEntry mExtension; + RESOURCE_CREATE_FN mCreateFn; + RegisteredExtension *next; + }; + + RegisteredExtension *registeredList; + + ResManager(); +public: + RESOURCE_CREATE_FN getCreateFunction( const char *name ); + + ~ResManager(); + static void create(); + static void destroy(); + + void setFileNameEcho(bool on); + void setModPaths(U32 numPaths, const char **dirs); + const char* getModPaths(); + + void registerExtension(const char *extension, RESOURCE_CREATE_FN create_fn); + + S32 getSize(const char* filename); + const char* getFullPath(const char * filename, char * path, U32 pathLen); + const char* getModPathOf(const char* fileName); + const char* getPathOf(const char * filename); + const char* getBasePath(); + + ResourceObject* load(const char * fileName, bool computeCRC = false); + Stream* openStream(const char * fileName); + Stream* openStream(ResourceObject *object); + void closeStream(Stream *stream); + + void unlock( ResourceObject* ); + bool add(const char* name, ResourceInstance *addInstance, bool extraLock = false); + + ResourceObject* find(const char * fileName); + ResourceInstance* loadInstance(const char *fileName, bool computeCRC = false); + ResourceInstance* loadInstance(ResourceObject *object, bool computeCRC = false); + + ResourceObject* find(const char * fileName, U32 flags); + ResourceObject* findMatch(const char *expression, const char **fn, ResourceObject *start = NULL); + + void purge(); + void purge( ResourceObject *obj ); + void serialize(VectorPtr &filenames); + + S32 findMatches( FindMatch *pFM ); + bool findFile( const char *name ); + + bool getCrc(const char * fileName, U32 & crcVal, const U32 crcInitialVal = 0xffffffff ); + + void setWriteablePath(const char *path); + bool isValidWriteFileName(const char *fn); + + bool openFileForWrite(FileStream &fs, const char *modPath, const char *fileName, U32 accessMode = 1); + +#ifdef DEBUG + void dumpLoadedResources(); +#endif +}; + +// simple crc - may need to move somewhere else someday +// will generate the table on the first call +U32 calculateCRC(void * buffer, S32 len, U32 crcVal = 0xffffffff); +U32 calculateCRCStream(Stream *stream, U32 crcVal = 0xffffffff); + +#endif //_RESMANAGER_H_ diff --git a/core/resizeStream.cc b/core/resizeStream.cc new file mode 100644 index 0000000..fe6ff1b --- /dev/null +++ b/core/resizeStream.cc @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/resizeStream.h" + +ResizeFilterStream::ResizeFilterStream() + : m_pStream(NULL), + m_startOffset(0), + m_streamLen(0), + m_currOffset(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"); + + 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; + + 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..69aff72 --- /dev/null +++ b/core/resizeStream.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 +{ + typedef FilterStream Parent; + + Stream* m_pStream; + U32 m_startOffset; + U32 m_streamLen; + U32 m_currOffset; + + 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(); +}; + +#endif //_RESIZESTREAM_H_ diff --git a/core/stream.h b/core/stream.h new file mode 100644 index 0000000..d9e80fd --- /dev/null +++ b/core/stream.h @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STREAM_H_ +#define _STREAM_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + + +// This should ideally be done with templates... +// +#define DECLARE_OVERLOADED_READ(type) \ + bool read(type* out_read) { \ + return read(sizeof(type), out_read); \ + } +#define DECLARE_OVERLOADED_WRITE(type) \ + bool write(type in_write) { \ + return write(sizeof(type), &in_write); \ + } + +#define DECLARE_ENDIAN_OVERLOADED_READ(type) \ + bool read(type* out_read) { \ + type temp; \ + bool success = read(sizeof(type), &temp); \ + *out_read = convertLEndianToHost(temp); \ + return success; \ + } +#define DECLARE_ENDIAN_OVERLOADED_WRITE(type) \ + bool write(type in_write) { \ + type temp = convertHostToLEndian(in_write); \ + return write(sizeof(type), &temp); \ + } + + +class ColorI; +class ColorF; + +//------------------------------------------------------------------------------ +//-------------------------------------- Base Stream class +// +class Stream { + // Public structs and enumerations... + public: + enum Status { + Ok = 0, // obv. + 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 = U32(1 << 0), + StreamRead = U32(1 << 1), + StreamPosition = U32(1 << 2) + }; + + // 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(); + + Status getStatus() const { return m_streamStatus; } + 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; + public: + virtual bool hasCapability(const Capability) const = 0; + + virtual U32 getPosition() const = 0; + virtual bool setPosition(const U32 in_newPosition) = 0; + virtual U32 getStreamSize() = 0; + + void readLine(U8 *buffer, U32 bufferSize); + void writeLine(U8 *buffer); + + const char *readSTString(bool casesens = false); + virtual void readString(char stringBuf[256]); + void readLongString(U32 maxStringLen, char *stringBuf); + void writeLongString(U32 maxStringLen, const char *string); + + virtual void writeString(const char *stringBuf, S32 maxLen=255); + + bool write(const ColorI&); + bool write(const ColorF&); + bool read(ColorI*); + bool read(ColorF*); + + + // 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(F64) + + DECLARE_ENDIAN_OVERLOADED_WRITE(S16) + DECLARE_ENDIAN_OVERLOADED_WRITE(S32) + DECLARE_ENDIAN_OVERLOADED_WRITE(U16) + DECLARE_ENDIAN_OVERLOADED_WRITE(U32) + DECLARE_ENDIAN_OVERLOADED_WRITE(F32) + + DECLARE_OVERLOADED_READ(S8) + DECLARE_OVERLOADED_READ(U8) + DECLARE_OVERLOADED_READ(F64) + + DECLARE_ENDIAN_OVERLOADED_READ(S16) + DECLARE_ENDIAN_OVERLOADED_READ(S32) + DECLARE_ENDIAN_OVERLOADED_READ(U16) + DECLARE_ENDIAN_OVERLOADED_READ(U32) + DECLARE_ENDIAN_OVERLOADED_READ(F32) + + // 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); + } +}; + +#endif //_STREAM_H_ diff --git a/core/stringTable.cc b/core/stringTable.cc new file mode 100644 index 0000000..a25f520 --- /dev/null +++ b/core/stringTable.cc @@ -0,0 +1,206 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Core/stringTable.h" + +_StringTable *StringTable = 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(); + + U32 ret = 0; + char c; + while((c = *str++) != 0) { + ret <<= 1; + ret ^= sgHashTable[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[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(StringTable == NULL, "StringTable::create: StringTable all ready exists."); + StringTable = new _StringTable; +} + + +//-------------------------------------- +void _StringTable::destroy() +{ + AssertFatal(StringTable != NULL, "StringTable::destroy: StringTable does not exist."); + delete StringTable; + StringTable = NULL; +} + + +//-------------------------------------- +StringTableEntry _StringTable::insert(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); + } + 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)) + return temp->val; + else if(!caseSens && !dStrnicmp(temp->val, val, len)) + 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..1435a46 --- /dev/null +++ b/core/stringTable.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STRINGTABLE_H_ +#define _STRINGTABLE_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _DATACHUNKER_H_ +#include "Core/dataChunker.h" +#endif + + +//-------------------------------------- +class _StringTable +{ +private: + struct Node + { + char *val; + Node *next; + }; + + Node** buckets; + U32 numBuckets; + U32 itemCount; + DataChunker mempool; + + protected: + static const U32 csm_stInitSize; + + _StringTable(); + ~_StringTable(); + + public: + static void create(); + static void destroy(); + + StringTableEntry insert(const char *string, bool caseSens = false); + StringTableEntry insertn(const char *string, S32 len, bool caseSens = false); + StringTableEntry lookup(const char *string, bool caseSens = false); + StringTableEntry lookupn(const char *string, S32 len, bool caseSens = false); + void resize(const U32 newSize); + + static U32 hashString(const char* in_pString); + static U32 hashStringn(const char* in_pString, S32 len); +}; + + +extern _StringTable *StringTable; + + +#endif //_STRINGTABLE_H_ + diff --git a/core/tAlgorithm.h b/core/tAlgorithm.h new file mode 100644 index 0000000..c821f5b --- /dev/null +++ b/core/tAlgorithm.h @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TALGORITHM_H_ +#define _TALGORITHM_H_ + +//Includes + +template +Iterator find(Iterator first, Iterator last, Value value) +{ + while (first != last && *first != value) + ++first; + return first; +} + +#endif //_TALGORITHM_H_ diff --git a/core/tSortedSceneObjectList.cc b/core/tSortedSceneObjectList.cc new file mode 100644 index 0000000..9d62bde --- /dev/null +++ b/core/tSortedSceneObjectList.cc @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "core/tSortedSceneObjectList.h" + +/** + * Constructor, initalize pointers to null, counter to 0 + */ +SortedSceneObjectList::SortedSceneObjectList() { + mHead = NULL; +} + +SortedSceneObjectList::~SortedSceneObjectList() { + + while( mHead != NULL ) { + SceneObjectNode *temp = mHead; + mHead = mHead->next; + + delete temp; + } +} + +/** + * Adds an object, no duplicates, adds it in order + */ +void SortedSceneObjectList::addObject( SceneObject *toAdd, Point3F &tag ) { + + if( contains( toAdd ) ) { + // Just update the tag + SceneObjectNode *i = mHead; + + while( i != NULL ) { + if( i->object->getId() == toAdd->getId() ) { + i->tag = tag; + return; + } + + i = i->next; + } + } + + SceneObjectNode *previous = mHead; + SceneObjectNode *current = mHead; + + while( current != NULL && toAdd->getId() > current->object->getId() ) { + previous = current; + current = current -> next; + } + + if( previous == current ) + mHead = new SceneObjectNode( toAdd, tag, mHead ); + else + previous->next = new SceneObjectNode( toAdd, tag, current ); +} + +/** + * Test to see if this list contains the test object + */ +bool SortedSceneObjectList::contains( const SceneObject *test ) const { + + SceneObjectNode *i = mHead; + + while( i != NULL ) { + if( i->object->getId() == test->getId() ) + return true; + + i = i->next; + } + + return false; +} + +/** + * Remove an object from the list + */ +void SortedSceneObjectList::removeObject( const SceneObject *toRemove ) { + + SceneObjectNode *previous = mHead; + SceneObjectNode *current = mHead; + + while( current != NULL && toRemove->getId() > current->object->getId() ) { + previous = current; + current = current->next; + } + + if( current == NULL ) + return; + + if( previous == current) + mHead = mHead->next; + else + previous->next = current->next; + + delete current; +} + +/** + * Gets the tag on an object + */ +Point3F SortedSceneObjectList::getTag( const SceneObject *test ) const { + + SceneObjectNode *current = mHead; + SceneObjectNode *previous = mHead; + + while( test != NULL && current != NULL && test->getId() > current->object->getId() ) { + previous = current; + current = current->next; + } + + if( current == NULL ) + return Point3F( 42.42f, 42.42f, 42.42f ); // Eh, it's easy to test for + else + return current->tag; +} + +/** + * Returns this as a vector + */ +Vector SortedSceneObjectList::toVector() const { + + Vector retVector; + + SceneObjectNode *i = mHead; + + while( i != NULL ) { + retVector.push_back( i->object ); + + i = i->next; + } + + return retVector; +} \ No newline at end of file diff --git a/core/tSortedSceneObjectList.h b/core/tSortedSceneObjectList.h new file mode 100644 index 0000000..4e7a7de --- /dev/null +++ b/core/tSortedSceneObjectList.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSORTEDSCENEOBJECTLIST_H_ +#define _TSORTEDSCENEOBJECTLIST_H_ + +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif + +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif + +class SortedSceneObjectList { + + private: + + class SceneObjectNode { + public: + SceneObjectNode *next; + + SceneObject *object; + Point3F tag; + + SceneObjectNode( SceneObject *obj, Point3F &tg, SceneObjectNode *nxt ) { + object = obj; + next = nxt; + tag = tg; + } + }; + + SceneObjectNode *mHead; + + public: + + SortedSceneObjectList(); + ~SortedSceneObjectList(); + + void addObject( SceneObject *toAdd, Point3F &tag ); + void removeObject( const SceneObject *toRemove ); + bool contains( const SceneObject *test ) const; + + Point3F getTag( const SceneObject *test ) const; + + Vector toVector() const; +}; + +#endif \ No newline at end of file diff --git a/core/tSparseArray.h b/core/tSparseArray.h new file mode 100644 index 0000000..28978ea --- /dev/null +++ b/core/tSparseArray.h @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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; + + void clearTables(); // Note: _deletes_ the objects! + + public: + SparseArray(const U32 modulusSize = 64); + ~SparseArray(); + + void insert(T* pObject, U32 key); + T* remove(U32 key); + T* retreive(U32 key); +}; + +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 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; + } + + 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; + } + + AssertFatal(false, "Key didn't exist in the array!"); + return NULL; +} + +#endif //_TSPARSEARRAY_H_ + diff --git a/core/tVector.cc b/core/tVector.cc new file mode 100644 index 0000000..d3fab68 --- /dev/null +++ b/core/tVector.cc @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/tVector.h" + +#ifdef 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; + + if (*arrayPtr != NULL) + { + if (fileName == NULL && mem_size <= 64) + U32 test = 0; + *arrayPtr = dRealloc(*arrayPtr,mem_size); + } + else + { + if (fileName == NULL && mem_size <= 64) + U32 test = 0; + const char* pUseFileName = fileName != NULL ? fileName : __FILE__; + U32 useLineNum = fileName != NULL ? lineNum : __LINE__; + *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/tVector.h b/core/tVector.h new file mode 100644 index 0000000..028d4f4 --- /dev/null +++ b/core/tVector.h @@ -0,0 +1,642 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TVECTOR_H_ +#define _TVECTOR_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +#if defined(__BORLANDC__) && (__BORLANDC__ >= 0x500) +#pragma warn -inl +#endif + +#define VectorBlockSize 16 + +//----------------------------------------------------------------------------- +// A dynamic array 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. + +// ***WARNING*** +// This template does not initialize, construct or destruct any of +// it's elements. This means don't use this template for elements +// (classes) that need these operations. This template is intended +// to be used for simple structures that have no constructors or +// destructors. + +#ifdef 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 DEBUG_GUARD +#define VECTOR_SET_ASSOCIATION(x) x.setFileAssociation(__FILE__, __LINE__) +#else +#define VECTOR_SET_ASSOCIATION(x) x +#endif + +// ============================================================================= +template +class Vector +{ + protected: + U32 mElementCount; + U32 mArraySize; + T* mArray; + +#ifdef DEBUG_GUARD + const char* mFileAssociation; + U32 mLineAssociation; +#endif + + bool resize(U32); + 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 DEBUG_GUARD + void setFileAssociation(const char* file, const U32 line); +#endif + + // 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; + + Vector& operator=(const Vector& p); + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + + S32 size() const; + bool empty() 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&); + 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; + + // Extended interface + U32 memSize() const; + T* address() const; + U32 setSize(U32); + void increment(U32 = 1); + void decrement(U32 = 1); + void insert(U32); + void erase(U32); + void erase_fast(U32); + void erase_fast(iterator); + void clear(); + void compact(); + + T& first(); + T& last(); + const T& first() const; + const T& last() const; + + void set(void * addr, S32 sz); + + // BJW 8/20/97 + // merge another vector into this one + void merge(const Vector& p); +}; + +template inline Vector::~Vector() +{ + dFree(mArray); +} + +template inline Vector::Vector(const U32 initialSize) +{ +#ifdef 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 DEBUG_GUARD + mFileAssociation = fileName; + mLineAssociation = lineNum; +#else + fileName; + lineNum; +#endif + + mArray = 0; + mElementCount = 0; + mArraySize = 0; + if(initialSize) + reserve(initialSize); +} + +template inline Vector::Vector(const char* fileName, + const U32 lineNum) +{ +#ifdef DEBUG_GUARD + mFileAssociation = fileName; + mLineAssociation = lineNum; +#else + fileName; + lineNum; +#endif + + mArray = 0; + mElementCount = 0; + mArraySize = 0; +} + +template inline Vector::Vector(const Vector& p) +{ +#ifdef DEBUG_GUARD + mFileAssociation = p.mFileAssociation; + mLineAssociation = p.mLineAssociation; +#endif + + mArray = 0; + resize(p.mElementCount); + if (p.mElementCount) + dMemcpy(mArray,p.mArray,mElementCount * sizeof(value_type)); +} + + +#ifdef DEBUG_GUARD +template inline void Vector::setFileAssociation(const char* file, + const U32 line) +{ + mFileAssociation = file; + mLineAssociation = line; +} +#endif + +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) +{ + if (size > mArraySize) + resize(size); + else + mElementCount = size; + return mElementCount; +} + +template inline void Vector::increment(U32 delta) +{ + if ((mElementCount += delta) > mArraySize) + resize(mElementCount); +} + +template inline void Vector::decrement(U32 delta) +{ + if (mElementCount > delta) + mElementCount -= delta; + else + mElementCount = 0; +} + +template inline void Vector::insert(U32 index) +{ + // Assert: index >= 0 && index < mElementCount + increment(); + dMemmove(&mArray[index + 1], + &mArray[index], + (mElementCount - index - 1) * sizeof(value_type)); +} + +template inline void Vector::erase(U32 index) +{ + // Assert: index >= 0 && index < mElementCount + dMemmove(&mArray[index], + &mArray[index + 1], + (mElementCount - index - 1) * sizeof(value_type)); + decrement(); +} + +template inline void Vector::erase_fast(U32 index) +{ + // CAUTION: this operator does 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)) + dMemmove(&mArray[index], &mArray[mElementCount - 1], sizeof(value_type)); + decrement(); +} + +template inline T& Vector::first() +{ + return mArray[0]; +} + +template inline const T& Vector::first() const +{ + return mArray[0]; +} + +template inline T& Vector::last() +{ + AssertFatal(mElementCount != 0, "Error, no last element of a zero sized array!"); + return mArray[mElementCount - 1]; +} + +template inline const T& Vector::last() const +{ + return mArray[mElementCount - 1]; +} + +template inline void Vector::clear() +{ + mElementCount = 0; +} + +template inline void Vector::compact() +{ + resize(mElementCount); +} + + +//----------------------------------------------------------------------------- + +template inline Vector& Vector::operator=(const Vector& p) +{ + resize(p.mElementCount); + if (p.mElementCount) + dMemcpy(mArray,p.mArray,mElementCount * sizeof(value_type)); + + 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) +{ + S32 index = p - mArray; + insert(U32(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 void Vector::pop_front() +{ + erase(U32(0)); +} + +template inline void Vector::pop_back() +{ + decrement(); +} + +template inline T& Vector::operator[](U32 index) +{ + return mArray[index]; +} + +template inline const T& Vector::operator[](U32 index) const +{ + return mArray[index]; +} + +template inline void Vector::reserve(U32 size) +{ + if (size > mArraySize) { + S32 ec = S32(mElementCount); + if (resize(size)) + mElementCount = U32(ec); + } +} + +template inline U32 Vector::capacity() const +{ + return mArraySize; +} + +template inline void Vector::set(void * addr, S32 sz) +{ + setSize(sz); + if (addr) + dMemcpy(address(),addr,sz*sizeof(T)); +} + +//----------------------------------------------------------------------------- + +template inline bool Vector::resize(U32 ecount) +{ +#ifdef DEBUG_GUARD + return VectorResize(&mArraySize, &mElementCount, (void**) &mArray, ecount, sizeof(T), + mFileAssociation, mLineAssociation); +#else + return VectorResize(&mArraySize, &mElementCount, (void**) &mArray, ecount, sizeof(T)); +#endif +} + +// BJW 8/20/97 +// code to merge a vector into this one +template inline void Vector::merge(const Vector& p) +{ + if (p.size()) { + S32 oldsize = size(); + resize(oldsize + p.size()); + dMemcpy( &mArray[oldsize], p.address(), p.size() * sizeof(T) ); + } +} + +//----------------------------------------------------------------------------- +// Template for vectors of pointers. +//----------------------------------------------------------------------------- + +template +class VectorPtr : public Vector +{ + + VectorPtr(const VectorPtr&); // disallowed + public: + VectorPtr(); + VectorPtr(const char* fileName, const U32 lineNum); + + // 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 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; + + // 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) +{ + // CAUTION: this operator does 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); +} + +#endif //_TVECTOR_H_ + diff --git a/core/tagDictionary.cc b/core/tagDictionary.cc new file mode 100644 index 0000000..6b5ed49 --- /dev/null +++ b/core/tagDictionary.cc @@ -0,0 +1,305 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Core/tagDictionary.h" +#include "Core/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(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..5f51212 --- /dev/null +++ b/core/tagDictionary.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TAGDICTIONARY_H_ +#define _TAGDICTIONARY_H_ + +#ifndef _STRINGTABLE_H_ +#include "Core/stringTable.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/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/zipAggregate.cc b/core/zipAggregate.cc new file mode 100644 index 0000000..273e8b8 --- /dev/null +++ b/core/zipAggregate.cc @@ -0,0 +1,212 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Core/stringTable.h" + +#include "Core/fileStream.h" // Streams + +#include "Core/zipAggregate.h" // Own header, and private includes +#include "Core/zipHeaders.h" + +ZipAggregate::ZipAggregate() + : m_pZipFileName(NULL) +{ + VECTOR_SET_ASSOCIATION(m_fileList); +} + +ZipAggregate::~ZipAggregate() +{ + closeAggregate(); +} + +bool +ZipAggregate::refreshAggregate() +{ + AssertFatal(m_pZipFileName != NULL, "No filename? Must not be open. Disallowed"); + + char tmpBuff[512]; + dStrcpy(tmpBuff, m_pZipFileName); + + return openAggregate(tmpBuff); +} + +bool +ZipAggregate::openAggregate(const char* in_pFileName) +{ + closeAggregate(); + + AssertFatal(in_pFileName != NULL, "No filename to open!"); + + m_pZipFileName = new char[dStrlen(in_pFileName) + 1]; + dStrcpy(m_pZipFileName, in_pFileName); + + FileStream* pStream = new FileStream; + if (pStream->open(m_pZipFileName, FileStream::Read) == false || + createZipDirectory(pStream) == false) { + // Failure, abort the open... + // + delete pStream; + + delete [] m_pZipFileName; + m_pZipFileName = NULL; + return false; + } + + // Finished! Open for business + delete pStream; + return true; +} + +void +ZipAggregate::closeAggregate() +{ + destroyZipDirectory(); + + delete [] m_pZipFileName; + m_pZipFileName = NULL; +} + +void +ZipAggregate::destroyZipDirectory() +{ + m_fileList.clear(); +} + +bool +ZipAggregate::createZipDirectory(Stream* io_pStream) +{ + AssertFatal(io_pStream != NULL, "Error, stream not open."); + + U32 streamSize = io_pStream->getStreamSize(); + U32 initialPosition = io_pStream->getPosition(); + + // We assume that the CD is 22 bytes from the end. This will be invalid + // in the case that the zip file has comments. Perhaps test the quick + // way, then degrade to seaching the final 64k+22b (!) of the stream? + // + bool posSuccess = io_pStream->setPosition(streamSize - sizeof(ZipEOCDRecord::EOCDRecord)); + if (posSuccess == false) { + AssertWarn(false, "Unable to position stream to start of EOCDRecord"); + return false; + } + + ZipEOCDRecord* pEOCDRecord = new ZipEOCDRecord; + if (pEOCDRecord->readFromStream(*io_pStream) == false) { + // This is where we would try to degrade to general case... + // + AssertWarn(false, "Unable to locate central directory. " + "Zip File might have comments"); + delete pEOCDRecord; + return false; + } + + // Check the consistency of the zipFile. + // + if ((pEOCDRecord->m_record.diskNumber != pEOCDRecord->m_record.eocdDiskNumber) || + (pEOCDRecord->m_record.numCDEntriesDisk != pEOCDRecord->m_record.numCDEntriesTotal)) { + AssertWarn(false, "Zipfile appears to be part of a " + "multi-zip disk span set, unsupported"); + delete pEOCDRecord; + return false; + } + + // If we're here, we're good! Scan to the start of the CDirectory, and + // start scanning the entries into our directory structure... + // + U32 startCDPosition = pEOCDRecord->m_record.cdOffset; + U32 endCDPosition = pEOCDRecord->m_record.cdOffset + + pEOCDRecord->m_record.cdSize; + + posSuccess = io_pStream->setPosition(startCDPosition); + if (posSuccess == false) { + AssertWarn(false, "Unable to position to CD entries."); + delete pEOCDRecord; + return false; + } + + bool dirReadSuccess = true; + for (U16 i = 0; i < pEOCDRecord->m_record.numCDEntriesTotal; i++) { + ZipDirFileHeader zdfHeader; + + bool hrSuccess = zdfHeader.readFromStream(*io_pStream); + if (hrSuccess == false) { + AssertWarn(false, "Error reading a CD Entry in zip aggregate"); + dirReadSuccess = false; + break; + } + + enterZipDirRecord(zdfHeader); + } + + delete pEOCDRecord; + if (dirReadSuccess == true) { + // Every thing went well, we're done, position the stream to the end of the + // CD... + // + io_pStream->setPosition(endCDPosition); + return true; + } else { + // Oh, crap. + io_pStream->setPosition(initialPosition); + destroyZipDirectory(); + return false; + } +} + +void +ZipAggregate::enterZipDirRecord(const ZipDirFileHeader& in_rHeader) +{ + // Ok, the first thing to do is figure out whether this is + // a directory or a file. Directories have a trailing / + // in the file name, and a filelength (comp/uncomp) of 0 + // Note: this is not specified in + // the file format spec I have, but seems fairly likely to + // be correct. + // + if (in_rHeader.m_pFileName[dStrlen(in_rHeader.m_pFileName) - 1] == '/' && + (in_rHeader.m_header.compressedSize == 0 && + in_rHeader.m_header.uncompressedSize == 0)) + return; + + // It's a file. Enter it into the directory... + m_fileList.increment(); + FileEntry& rEntry = m_fileList.last(); + + char tempString[1024]; + dStrcpy(tempString, in_rHeader.m_pFileName); + char* scan = tempString; + while (*scan != '\0') { + if (*scan == '\\') + *scan = '/'; + scan++; + } + char* pPathEnd = dStrrchr(tempString, '/'); + if (pPathEnd != NULL) { + pPathEnd[0] = '\0'; + rEntry.pPath = StringTable->insert(tempString); + rEntry.pFileName = StringTable->insert(pPathEnd + 1); + } else { + rEntry.pPath = NULL; + rEntry.pFileName = StringTable->insert(in_rHeader.m_pFileName); + } + + rEntry.fileSize = in_rHeader.m_header.uncompressedSize; + rEntry.compressedFileSize = in_rHeader.m_header.compressedSize; + rEntry.fileOffset = in_rHeader.m_header.relativeOffsetOfLocalHeader; + + if (in_rHeader.m_header.compressionMethod == ZipDirFileHeader::Deflated) { + rEntry.flags = FileEntry::Compressed; + } else if (in_rHeader.m_header.compressionMethod == ZipDirFileHeader::Stored) { + rEntry.flags = FileEntry::Uncompressed; + } else { + AssertWarn(0, avar("Warning, non-stored or deflated resource in %s", + m_pZipFileName)); + m_fileList.decrement(); + } +} + diff --git a/core/zipAggregate.h b/core/zipAggregate.h new file mode 100644 index 0000000..2c14966 --- /dev/null +++ b/core/zipAggregate.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ZIPAGGREGATE_H_ +#define _ZIPAGGREGATE_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + +class Stream; +class ZipDirFileHeader; + +class ZipAggregate +{ + public: + struct FileEntry { + enum { + Uncompressed = 0, + Compressed = 1 << 0 + }; + + const char* pPath; + const char* pFileName; + U32 fileOffset; + U32 fileSize; + U32 compressedFileSize; + U32 flags; + }; + + //-------------------------------------- Instance scope members and decls. + private: + char* m_pZipFileName; + Vector m_fileList; + + void enterZipDirRecord(const ZipDirFileHeader& in_rHeader); + bool createZipDirectory(Stream*); + void destroyZipDirectory(); + + ZipAggregate(const ZipAggregate&); // disallowed + public: + ZipAggregate(); + ~ZipAggregate(); + + // Opening/Manipulation interface... + public: + bool openAggregate(const char* in_pFileName); + void closeAggregate(); + bool refreshAggregate(); + + // Entry iteration interface... + public: + typedef Vector::const_iterator iterator; + + U32 numEntries() const { return m_fileList.size(); } + const FileEntry& operator[](const U32 idx) const { return m_fileList[idx]; } + iterator begin() const { return m_fileList.begin(); } + iterator end() const { return m_fileList.end(); } +}; + +#endif //_ZIPAGGREGATE_H_ diff --git a/core/zipHeaders.cc b/core/zipHeaders.cc new file mode 100644 index 0000000..ea8fcad --- /dev/null +++ b/core/zipHeaders.cc @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Core/stream.h" +#include "Core/zipHeaders.h" + +const U32 ZipLocalFileHeader::csm_localFileHeaderSig = 0x04034b50; +const U32 ZipDirFileHeader::csm_dirFileHeaderSig = 0x02014b50; +const U32 ZipEOCDRecord::csm_eocdRecordSig = 0x06054b50; + +bool +ZipLocalFileHeader::readFromStream(Stream& io_rStream) +{ + AssertFatal(io_rStream.getStatus() == Stream::Ok, + "Error, stream is closed or has an uncleared error."); + AssertFatal(io_rStream.hasCapability(Stream::StreamPosition), + "Must be positionable stream to read zip headers..."); + + // Read the initial header fields, marking the initial position... + // + U32 initialPosition = io_rStream.getPosition(); + bool success = io_rStream.read(sizeof(m_header), &m_header); + + if (success == false || m_header.headerSig != csm_localFileHeaderSig) { + AssertWarn(0, "Unable to retrieve local file header from stream position..."); + io_rStream.setPosition(initialPosition); + return false; + } + + // Read the variable length file name from the stream... + // + AssertFatal(m_header.fileNameLength < (MaxFileNameLength - 1), + "Filename too long, increase structure size"); + success = io_rStream.read(m_header.fileNameLength, m_pFileName); + m_pFileName[m_header.fileNameLength] = '\0'; + if (success == false) { + AssertWarn(0, "Unable to read file name from stream position..."); + io_rStream.setPosition(initialPosition); + return false; + } + + + // And seek to the end of the header, ignoring the extra field. + io_rStream.setPosition(initialPosition + + (sizeof(m_header) + + m_header.fileNameLength + + m_header.extraFieldLength)); + return true; +} + +bool +ZipDirFileHeader::readFromStream(Stream& io_rStream) +{ + AssertFatal(io_rStream.getStatus() == Stream::Ok, + "Error, stream is closed or has an uncleared error."); + AssertFatal(io_rStream.hasCapability(Stream::StreamPosition), + "Must be positionable stream to read zip headers..."); + + // Read the initial header fields, marking the initial position... + // + U32 initialPosition = io_rStream.getPosition(); + bool success = io_rStream.read(sizeof(m_header), &m_header); + + if (success == false || m_header.headerSig != csm_dirFileHeaderSig) { + AssertWarn(0, "Unable to retrieve local file header from stream position..."); + io_rStream.setPosition(initialPosition); + return false; + } + + // Read the variable length file name from the stream... + // + AssertFatal(m_header.fileNameLength < (MaxFileNameLength - 1), + "Filename too long, increase structure size"); + success = io_rStream.read(m_header.fileNameLength, m_pFileName); + m_pFileName[m_header.fileNameLength] = '\0'; + if (success == false) { + AssertWarn(0, "Unable to read file name from stream position..."); + io_rStream.setPosition(initialPosition); + return false; + } + + + // And seek to the end of the header, ignoring the extra field. + io_rStream.setPosition(initialPosition + + (sizeof(m_header) + + m_header.fileNameLength + + m_header.extraFieldLength)); + return true; +} + +bool +ZipEOCDRecord::readFromStream(Stream& io_rStream) +{ + AssertFatal(io_rStream.getStatus() == Stream::Ok, + "Error, stream is closed or has an uncleared error."); + AssertFatal(io_rStream.hasCapability(Stream::StreamPosition), + "Must be positionable stream to read zip headers..."); + + // Read the initial header fields, marking the initial position... + // + U32 initialPosition = io_rStream.getPosition(); + bool success = io_rStream.read(sizeof(m_record), &m_record); + + if (success == false || m_record.eocdSig != csm_eocdRecordSig) { + AssertWarn(0, "Unable to retrieve EOCD header from stream position..."); + io_rStream.setPosition(initialPosition); + return false; + } + + // And seek to the end of the header, ignoring the extra field. + io_rStream.setPosition(initialPosition + + (sizeof(m_record) + m_record.zipFileCommentLength)); + return true; +} + diff --git a/core/zipHeaders.h b/core/zipHeaders.h new file mode 100644 index 0000000..dd32fca --- /dev/null +++ b/core/zipHeaders.h @@ -0,0 +1,211 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ZIPHEADERS_H_ +#define _ZIPHEADERS_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +//-------------------------------------- NB: Structures in this header are BYTE +// aligned! +#ifdef __BORLANDC__ +# pragma option -a1 +#endif + +#ifdef _MSC_VER +# pragma pack(push,1) +#endif + +#ifdef __MWERKS__ +# pragma options align=packed +#endif + + +class Stream; + +//-------------------------------------- Structure designed to fit exactly 256 bytes. +class ZipLocalFileHeader +{ + // NB: Extra field in the header is ignored, but the stream read seeks + // past it... + // + private: + static const U32 csm_localFileHeaderSig; + + public: + enum { + MaxFileNameLength = 211 + }; + enum CompressionMethod { + Stored = 0, + Shrunk = 1, + ReducedL1 = 2, + ReducedL2 = 3, + ReducedL3 = 4, + ReducedL4 = 5, + Imploded = 6, + ReservedTokenized = 7, + Deflated = 8, + EnhDefalted = 9, + DateCompression = 10 + }; + + struct LocalFileHeader { + U32 headerSig; + U16 versionToDecompress; + U16 bitFlags; + U16 compressionMethod; + U16 lastModTime; + U16 lastModDate; + U32 crc32; + U32 compressedSize; + U32 uncompressedSize; + + U16 fileNameLength; + U16 extraFieldLength; +#ifndef linux + }; +#else + } __attribute__ ((packed)); +#endif + + LocalFileHeader m_header; // Fixed size header + char m_pFileName[226]; // Variable size: FileName. Note that the + // number of chars here is more than the + // max allowed filename for alignment + // purposes + + // Stream read routines + public: + bool readFromStream(Stream& io_rStream); +#ifndef linux +}; +#else +} __attribute__ ((packed)); +#endif + + +//-------------------------------------- Also designed to fit into 256 bytes, note +// that we ignore the extra and file comment +// fields. +class ZipDirFileHeader +{ + private: + static const U32 csm_dirFileHeaderSig; + + public: + enum { + MaxFileNameLength = 211 + }; + enum CompressionMethod { + Stored = 0, + Shrunk = 1, + ReducedL1 = 2, + ReducedL2 = 3, + ReducedL3 = 4, + ReducedL4 = 5, + Imploded = 6, + ReservedTokenized = 7, + Deflated = 8, + EnhDefalted = 9, + DateCompression = 10 + }; + + struct DirFileHeader { + U32 headerSig; + U16 versionMadeBy; + U16 versionToDecompress; + U16 bitFlags; + U16 compressionMethod; + U16 lastModTime; + U16 lastModDate; + U32 crc32; + U32 compressedSize; + U32 uncompressedSize; + U16 fileNameLength; + U16 extraFieldLength; + U16 fileCommentLength; + U16 diskNumberStart; + U16 internalFileAttributes; + U32 externalFileAttributes; + U32 relativeOffsetOfLocalHeader; +#ifndef linux + }; +#else + } __attribute__ ((packed)); +#endif + + DirFileHeader m_header; + char m_pFileName[212]; + + // Stream read routines + public: + bool readFromStream(Stream& io_rStream); +#ifndef linux +}; +#else +} __attribute__ ((packed)); +#endif + + +//-------------------------------------- Padded to 32 bytes. Note that we completely +// ignore any zip file comments. +class ZipEOCDRecord +{ + private: + static const U32 csm_eocdRecordSig; + + public: + enum { + ProperRecordSize = 22 + }; + + struct EOCDRecord { + U32 eocdSig; + U16 diskNumber; + U16 eocdDiskNumber; + U16 numCDEntriesDisk; + U16 numCDEntriesTotal; + U32 cdSize; + U32 cdOffset; + U16 zipFileCommentLength; +#ifndef linux + }; +#else + } __attribute__ ((packed)); +#endif + + EOCDRecord m_record; + char __padding[10]; + // Stream read routines + public: + bool readFromStream(Stream& io_rStream); +#ifndef linux +}; +#else +} __attribute__ ((packed)); +#endif + + + +#ifdef __BORLANDC__ +# pragma option -a. +#endif + +#ifdef _MSC_VER +# pragma pack(pop) +#endif + +#ifdef __MWERKS__ +# pragma options align=reset +#endif + + +#endif //_NZIPHEADERS_H_ diff --git a/core/zipSubStream.cc b/core/zipSubStream.cc new file mode 100644 index 0000000..b22f9f8 --- /dev/null +++ b/core/zipSubStream.cc @@ -0,0 +1,401 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "zlib.h" +#include "core/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_pZipStream(NULL), + m_originalSlavePosition(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; + + // 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; + 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) +{ + 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; + } + + // 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"); + + if (retVal == Z_STREAM_END) + { + if (m_pZipStream->avail_out != 0) + setStatus(EOS); + else + setStatus(Ok); + m_currentPosition += m_pZipStream->total_out; + return getStatus() == Ok; + } + } + 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 + { + AssertFatal(false, "Not implemented!"); + // Erk. How do we do this. + return false; + } +} + +//-------------------------------------- +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_currPosition(0), + + m_pZipStream(NULL) +{ + // +} + +//-------------------------------------- +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) +{ + 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; + + 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/zipSubStream.h b/core/zipSubStream.h new file mode 100644 index 0000000..f773024 --- /dev/null +++ b/core/zipSubStream.h @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ZIPSUBSTREAM_H_ +#define _ZIPSUBSTREAM_H_ + +//Includes +#ifndef _FILTERSTREAM_H_ +#include "Core/filterStream.h" +#endif + +struct z_stream_s; + +class ZipSubRStream : public FilterStream +{ + typedef FilterStream Parent; + static const U32 csm_streamCaps; + static const U32 csm_inputBufferSize; + + Stream* m_pStream; + U32 m_uncompressedSize; + U32 m_currentPosition; + + z_stream_s* m_pZipStream; + U8* m_pInputBuffer; + + U32 m_originalSlavePosition; + + U32 fillBuffer(const U32 in_attemptSize); + + 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 +{ + 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; + + 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/crypt/cryptMGF.cc b/crypt/cryptMGF.cc new file mode 100644 index 0000000..65a7b73 --- /dev/null +++ b/crypt/cryptMGF.cc @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "crypt/cryptMGF.h" +#include "crypt/cryptSHA1.h" + +bool MGF1(const U8* seed, const U32 seedLen, + U8* mask, const U32 maskLen) +{ + AssertFatal(seed != NULL, "Error, no seed!"); + AssertFatal(mask != NULL && maskLen != 0, "No mask pointer or maskLen == 0"); + + U32 upper = (maskLen + SHA1Context::csmHashLenBytes - 1) / SHA1Context::csmHashLenBytes; + U8* maskBuffer = new U8[upper * SHA1Context::csmHashLenBytes]; + + U8* catBuffer = new U8[seedLen + 4]; + dMemcpy(catBuffer, seed, seedLen); + + SHA1Context hashContext; + for (U32 i = 0; i < upper; i++) { + catBuffer[seedLen + 0] = (i >> 24) & 0xFF; + catBuffer[seedLen + 1] = (i >> 16) & 0xFF; + catBuffer[seedLen + 2] = (i >> 8) & 0xFF; + catBuffer[seedLen + 3] = (i >> 0) & 0xFF; + + hashContext.init(); + hashContext.hashBytes(catBuffer, seedLen + 4); + hashContext.finalize(); + + hashContext.getHash(maskBuffer + (SHA1Context::csmHashLenBytes * i)); + } + + dMemcpy(mask, maskBuffer, maskLen); + + delete [] catBuffer; + delete [] maskBuffer; + + return true; +} + diff --git a/crypt/cryptMGF.h b/crypt/cryptMGF.h new file mode 100644 index 0000000..6fc9601 --- /dev/null +++ b/crypt/cryptMGF.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CRYPTMGF_H_ +#define _CRYPTMGF_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +bool MGF1(const U8* seed, const U32 seedLen, + U8* mask, const U32 maskLen); + +#endif // _H_CRYPTMGF_ diff --git a/crypt/cryptRandPool.cc b/crypt/cryptRandPool.cc new file mode 100644 index 0000000..488f639 --- /dev/null +++ b/crypt/cryptRandPool.cc @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "crypt/cryptRandPool.h" +#include "Core/bitStream.h" +#include "crypt/cryptSHA1.h" + +namespace { + +CryptRandomPool* sgRandPool = NULL; + +} // namespace {} + + +void CryptRandomPool::init() +{ + AssertFatal(sgRandPool == NULL, "RandPool already initialized"); + + sgRandPool = new CryptRandomPool; +} + +void CryptRandomPool::destroy() +{ + AssertFatal(sgRandPool != NULL, "RandPool not initialized"); + delete sgRandPool; + sgRandPool = NULL; +} + + +CryptRandomPool::CryptRandomPool() +{ + mPoolPos = 0; + mAvailableBytes = 0; +} + +CryptRandomPool::~CryptRandomPool() +{ + +} + +void CryptRandomPool::submitEntropy(U32 value, U32 entropicBits) +{ + AssertFatal(sgRandPool != NULL, "No Random pool!"); + + for (U32 i = 0; i < entropicBits; i++) { + U8 bit = value & 0x1; + value >>= 1; + + // Write this bit to the current pool pos + U32 byte = sgRandPool->mPoolPos / 8; + U32 mask = sgRandPool->mPoolPos % 8; + sgRandPool->mPool[byte] ^= bit << mask; + sgRandPool->mPoolPos = (sgRandPool->mPoolPos + 1) % (55 * 8); + } +} + +void CryptRandomPool::extractRandomBytes(U8* pOutput, U32 bytesNeeded) +{ + AssertFatal(sgRandPool != NULL, "No Random pool!"); + + U32 currOutput = 0; + + while (sgRandPool->mAvailableBytes != 0 && bytesNeeded != 0) { + sgRandPool->mAvailableBytes--; + bytesNeeded--; + pOutput[currOutput++] = sgRandPool->mRandomBytes[sgRandPool->mAvailableBytes]; + } + + while (bytesNeeded != 0) { + sgRandPool->churnPool(); + + while (sgRandPool->mAvailableBytes != 0 && bytesNeeded != 0) { + sgRandPool->mAvailableBytes--; + bytesNeeded--; + pOutput[currOutput++] = sgRandPool->mRandomBytes[sgRandPool->mAvailableBytes]; + } + } +} + + +void CryptRandomPool::churnPool() +{ + SHA1Context hashCTX; + hashCTX.init(); + hashCTX.hashBytes(mPool, 55); + hashCTX.finalize(); + + hashCTX.getHash(mRandomBytes); + mAvailableBytes = 20; + + const U32* pChurn = (const U32*)mRandomBytes; + submitEntropy(pChurn[0], 32); + submitEntropy(pChurn[1], 32); + submitEntropy(pChurn[2], 32); + submitEntropy(pChurn[3], 32); + submitEntropy(pChurn[4], 32); + +} diff --git a/crypt/cryptRandPool.h b/crypt/cryptRandPool.h new file mode 100644 index 0000000..1ba381a --- /dev/null +++ b/crypt/cryptRandPool.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CRYPTRANDPOOL_H_ +#define _CRYPTRANDPOOL_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +class BitStream; + +class CryptRandomPool +{ + U8 mPool[55]; + U32 mPoolPos; + + U8 mRandomBytes[20]; + U32 mAvailableBytes; + + void churnPool(); + + CryptRandomPool(); + ~CryptRandomPool(); + public: + + static void init(); + static void destroy(); + + static void submitEntropy(U32 value, U32 entropicBits); + static void extractRandomBytes(U8* pOutput, + U32 bytesNeeded); +}; + +#endif // _H_CRYPTRANDPOOL_ diff --git a/crypt/cryptSHA1.cc b/crypt/cryptSHA1.cc new file mode 100644 index 0000000..ab10208 --- /dev/null +++ b/crypt/cryptSHA1.cc @@ -0,0 +1,229 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "crypt/cryptSHA1.h" + + +namespace { + +inline U32 rollLeft(U32 val, U32 rol) +{ + return (val << rol) | (val >> (32 - rol)); +} + +} // namespace {} + + +//-------------------------------------------------------------------------- +//-------------------------------------- SHA1Context +// +const U32 SHA1Context::csmHashInitialValues[5] = { 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0 }; + +const U32 SHA1Context::csmRoundConstants[4] = { 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 }; +const U32 SHA1Context::csmHashLenBits = 160; +const U32 SHA1Context::csmHashLenBytes = 20; + +SHA1Context::SHA1Context() +{ + U32 mBytesWritten = 0; + bool mInitialized = false; + bool mHashValid = false; + + U32 mBufferLen = 0; +} + +SHA1Context::~SHA1Context() +{ + U32 mBytesWritten = 0; + bool mInitialized = false; + bool mHashValid = false; + + U32 mBufferLen = 0; +} + +void SHA1Context::init() +{ + for (U32 i = 0; i < 5; i++) + mHashVals[i] = csmHashInitialValues[i]; + + mBufferLen = 0; + mBytesWritten = 0; + mInitialized = true; + mHashValid = false; +} + +void SHA1Context::hashBytes(const void* input, const U32 inputLen) +{ + AssertFatal(input != NULL, "Error, invalid input pointer"); + AssertFatal(mInitialized == true, "Error, SHA1Context not initialized. Must init() before writing bytes"); + AssertFatal(mHashValid == false, "Error, SHA1Context already finalized. Must reinit() before writing more bytes"); + + if (inputLen == 0) + return; + + const U8* pByteInput = reinterpret_cast(input); + U32 currInputPos = 0; + + if (mBufferLen != 0) { + // Copy out enough bytes to finish off the buffer, if enough exist + while (mBufferLen < 64 && currInputPos < inputLen) { + mBuffer[mBufferLen++] = pByteInput[currInputPos++]; + mBytesWritten++; + } + + if (mBufferLen == 64) { + hashBlock(mBuffer); + mBufferLen = 0; + } + } + AssertFatal(mBufferLen == 0 || currInputPos == inputLen, "Hm, something goofed"); + + if (currInputPos < inputLen) { + // More bytes to hash... + U32 bytesLeft = inputLen - currInputPos; + + while (bytesLeft >= 64) { + hashBlock(&pByteInput[currInputPos]); + currInputPos += 64; + bytesLeft -= 64; + mBytesWritten += 64; + } + + if (bytesLeft != 0) { + dMemcpy(mBuffer, &pByteInput[currInputPos], bytesLeft); + mBytesWritten += bytesLeft; + mBufferLen = bytesLeft; + } + } +} + +void SHA1Context::finalize() +{ + // We assume that bytes written is less than 256 megs. Seems safe for now. + AssertFatal(mInitialized == true, "Error, hash context not initialized."); + AssertFatal(mBytesWritten < (1 << 27), "Error, too many bytes written to SHAContext"); + AssertFatal(mBufferLen < 64, "Error, unflushed buffer, or invalid bufferlen"); + + if (mBufferLen < (64 - 8 - 1)) { + // We have enough room in this buffer for the padding structure... + // + mBuffer[mBufferLen++] = 0x80; + for (U32 i = mBufferLen; i < (64 - 8); i++) + mBuffer[mBufferLen++] = 0x0; + } else { + // We have to create a new block to finalize this one... + + mBuffer[mBufferLen++] = 0x80; + for (U32 i = mBufferLen; i < 64; i++) + mBuffer[i] = 0x00; + + hashBlock(mBuffer); + mBufferLen = 0; + + for (U32 i = 0; i < (64 - 8); i++) + mBuffer[mBufferLen++] = 0x00; + } + AssertFatal(mBufferLen == (64 - 8), "Error in one of the above loops"); + + U32* pLenVals = reinterpret_cast(&mBuffer[(64 - 8)]); + pLenVals[0] = 0; + pLenVals[1] = convertHostToBEndian(mBytesWritten * 8); + + hashBlock(mBuffer); + mBufferLen = 0; + mHashValid = true; + mBytesWritten = 0; +} + +bool SHA1Context::getHash(U8* pHash) +{ + if (mHashValid == true) { + U32* pWordHash = reinterpret_cast(pHash); + + pWordHash[0] = convertHostToBEndian(mHashVals[0]); + pWordHash[1] = convertHostToBEndian(mHashVals[1]); + pWordHash[2] = convertHostToBEndian(mHashVals[2]); + pWordHash[3] = convertHostToBEndian(mHashVals[3]); + pWordHash[4] = convertHostToBEndian(mHashVals[4]); + + return true; + } + + return false; +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- Note that this isn't the most optimal +// implementation, but it's easy, and since +// we're not overly concerned with speed +// (right now), we're fine +// +inline U32 SHA1Context::sha1Nonlinear(U32 x, U32 y, U32 z, U32 t) +{ + AssertFatal(t < 80, "Error, invalid round"); + + if (t <= 19) { + return ((x & y) | ((~x) & z)) + csmRoundConstants[0]; + } else if (t <= 39) { + return (x ^ y ^ z) + csmRoundConstants[1]; + } else if (t <= 59) { + return ((x & y) | (x & z) | (y & z)) + csmRoundConstants[2]; + } else { + return (x ^ y ^ z) + csmRoundConstants[3]; + } +} + +void SHA1Context::hashBlock(const void* pInput) +{ + const U32* pWordInput = reinterpret_cast(pInput); + + // First expand the message to 80 words + // + U32 expandedBlock[80]; + for (U32 i = 0; i < 16; i++) + expandedBlock[i] = convertBEndianToHost(pWordInput[i]); + for (U32 i = 16; i < 80; i++) + expandedBlock[i] = rollLeft(expandedBlock[i - 3] ^ expandedBlock[i - 8] ^ + expandedBlock[i - 14] ^ expandedBlock[i - 16], 1); + + register U32 a = mHashVals[0]; + register U32 b = mHashVals[1]; + register U32 c = mHashVals[2]; + register U32 d = mHashVals[3]; + register U32 e = mHashVals[4]; + U32 tmp; + + for (U32 i = 0; i < 80; i++) { + // In this implementation, the round constants are in the nonlinear function + // + tmp = rollLeft(a, 5) + + sha1Nonlinear(b, c, d, i) + + e + + expandedBlock[i]; + + e = d; + d = c; + c = rollLeft(b, 30); + b = a; + a = tmp; + } + + mHashVals[0] += a; + mHashVals[1] += b; + mHashVals[2] += c; + mHashVals[3] += d; + mHashVals[4] += e; +} + diff --git a/crypt/cryptSHA1.h b/crypt/cryptSHA1.h new file mode 100644 index 0000000..18bfa36 --- /dev/null +++ b/crypt/cryptSHA1.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CRYPTSHA1_H_ +#define _CRYPTSHA1_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +class SHA1Context +{ + static const U32 csmHashInitialValues[5]; + static const U32 csmRoundConstants[4]; + + U32 mHashVals[5]; + + U32 mBytesWritten; + bool mInitialized; + bool mHashValid; + + U8 mBuffer[64]; + U32 mBufferLen; + + private: + void hashBlock(const void*); + U32 sha1Nonlinear(U32, U32, U32, U32); + + public: + SHA1Context(); + ~SHA1Context(); + + static const U32 csmHashLenBits; + static const U32 csmHashLenBytes; + + void init(); + void hashBytes(const void* input, const U32 inputLen); + void finalize(); + + bool getHash(U8* pHash); +}; + +#endif // _H_CRYPTSHA1_ diff --git a/dgl/bitmapBm8.cc b/dgl/bitmapBm8.cc new file mode 100644 index 0000000..bfb2c9f --- /dev/null +++ b/dgl/bitmapBm8.cc @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/gBitmap.h" +#include "dgl/gPalette.h" +#include "Core/stream.h" +#include "Platform/platform.h" + + +bool GBitmap::readBmp8(Stream& stream) +{ + stream.read(&byteSize); + stream.read(&width); + stream.read(&height); + stream.read(&bytesPerPixel); + stream.read(&numMipLevels); + U32 i; + for (i = 0; i < numMipLevels; i++) + stream.read(&mipLevelOffsets[i]); + internalFormat = GBitmap::Palettized; + + pPalette = new GPalette; + pPalette->read(stream); + + pBits = new U8[byteSize]; + stream.read(byteSize, pBits); + + return true; +} + +bool GBitmap::writeBmp8(Stream& stream) +{ + AssertFatal(pPalette != NULL, "Error, must have a palette to write the bmp!"); + + stream.write(byteSize); + stream.write(width); + stream.write(height); + stream.write(bytesPerPixel); + stream.write(numMipLevels); + U32 i; + for (i = 0; i < numMipLevels; i++) + stream.write(mipLevelOffsets[i]); + + pPalette->write(stream); + stream.write(byteSize, pBits); + + return true; +} diff --git a/dgl/bitmapBmp.cc b/dgl/bitmapBmp.cc new file mode 100644 index 0000000..53e4ed9 --- /dev/null +++ b/dgl/bitmapBmp.cc @@ -0,0 +1,205 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/gBitmap.h" +#include "dgl/gPalette.h" +#include "Core/stream.h" +#include "Platform/platform.h" + +// 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 + + +//------------------------------------------------------------------------------ +//-------------------------------------- Supplimentary I/O (Partially located in +// bitmapPng.cc) +// + +bool GBitmap::readMSBmp(Stream& stream) +{ + 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); + + BitmapFormat fmt = RGB; + if(bi.biBitCount == 8) + { + fmt = Palettized; + if(!bi.biClrUsed) + bi.biClrUsed = 256; + stream.read(sizeof(RGBQUAD) * bi.biClrUsed, rgb); + + pPalette = new GPalette; + for (U32 i = 0; i < 256; i++) + { + (pPalette->getColors())[i].red = rgb[i].rgbRed; + (pPalette->getColors())[i].green = rgb[i].rgbGreen; + (pPalette->getColors())[i].blue = rgb[i].rgbBlue; + (pPalette->getColors())[i].alpha = 255; + } + } + U8 *rowBuffer = new U8[bi.biWidth * 4]; + allocateBitmap(bi.biWidth, bi.biHeight, false, fmt); + S32 width = getWidth(); + S32 height = getHeight(); + for(int i = 0; i < bi.biHeight; i++) + { + U8 *rowDest = getAddress(0, height - i - 1); + stream.read(bytesPerPixel * width, rowDest); + } + + if(bytesPerPixel == 3) // do BGR swap + { + U8 *ptr = getAddress(0,0); + for(int i = 0; i < width * height; i++) + { + U8 tmp = ptr[0]; + ptr[0] = ptr[2]; + ptr[2] = tmp; + ptr += 3; + } + } + delete[] rowBuffer; + return true; +} + +bool GBitmap::writeMSBmp(Stream& io_rStream) const +{ + + RGBQUAD rgb[256]; + BITMAPINFOHEADER bi; + BITMAPFILEHEADER bf; + + bi.biSize = sizeof(BITMAPINFOHEADER); + bi.biWidth = getWidth(); + bi.biHeight = getHeight(); //our data is top-down + bi.biPlanes = 1; + + if(getFormat() == Palettized) + { + bi.biBitCount = 8; + bi.biCompression = BI_RGB; + bi.biClrUsed = 256; + AssertFatal(pPalette != NULL, "Error, must have a palette"); + } + else if(getFormat() == RGB) + { + bi.biBitCount = 24; + bi.biCompression = BI_RGB; + bi.biClrUsed = 0; + } + + U32 bytesPP = bi.biBitCount >> 3; + bi.biSizeImage = getWidth() * getHeight() * 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; + + io_rStream.write(bf.bfType); + io_rStream.write(bf.bfSize); + io_rStream.write(bf.bfReserved1); + io_rStream.write(bf.bfReserved2); + io_rStream.write(bf.bfOffBits); + + io_rStream.write(bi.biSize); + io_rStream.write(bi.biWidth); + io_rStream.write(bi.biHeight); + io_rStream.write(bi.biPlanes); + io_rStream.write(bi.biBitCount); + io_rStream.write(bi.biCompression); + io_rStream.write(bi.biSizeImage); + io_rStream.write(bi.biXPelsPerMeter); + io_rStream.write(bi.biYPelsPerMeter); + io_rStream.write(bi.biClrUsed); + io_rStream.write(bi.biClrImportant); + + if(getFormat() == Palettized) + { + for (S32 ndx=0; ndx<256; ndx++) + { + rgb[ndx].rgbRed = pPalette->getColor(ndx).red; + rgb[ndx].rgbGreen = pPalette->getColor(ndx).green; + rgb[ndx].rgbBlue = pPalette->getColor(ndx).blue; + rgb[ndx].rgbReserved = 0; + } + io_rStream.write(sizeof(RGBQUAD)*256, (U8*)&rgb); + } + + //write the bitmap bits + U8* pMSUpsideDownBits = new U8[bi.biSizeImage]; + for (U32 i = 0; i < getHeight(); i++) { + const U8* pSrc = getAddress(0, i); + U8* pDst = pMSUpsideDownBits + (getHeight() - i - 1) * getWidth() * bytesPP; + + dMemcpy(pDst, pSrc, getWidth() * bytesPP); + } + io_rStream.write(bi.biSizeImage, pMSUpsideDownBits); + delete [] pMSUpsideDownBits; + + return io_rStream.getStatus() == Stream::Ok; +} diff --git a/dgl/bitmapGif.cc b/dgl/bitmapGif.cc new file mode 100644 index 0000000..dc9f757 --- /dev/null +++ b/dgl/bitmapGif.cc @@ -0,0 +1,172 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/stream.h" +#include "Core/fileStream.h" +#include "Core/memstream.h" +#include "dgl/gPalette.h" +#include "dgl/gBitmap.h" + +#include "gif_lib.h" + + + +//-------------------------------------- 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, "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 gifWriteDataFn(GifFileType *gifinfo, GifByteType *data, int length) +{ + Stream *stream = (Stream*)gifinfo->UserData; + AssertFatal(stream != NULL, "jpegWriteDataFn::No stream."); + if (stream->write(length, data)) + return length; + else + return 0; +} + + +//-------------------------------------- +bool GBitmap::readGIF(Stream &stream) +{ + 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; + + BitmapFormat format = (gifinfo->SBackGroundColor == 0 ) ? RGB : RGBA; + allocateBitmap(gifinfo->SWidth, gifinfo->SHeight, false, format); + + U32 gwidth = gifinfo->Image.Width ? gifinfo->Image.Width : width; + U32 gheight= gifinfo->Image.Height ? gifinfo->Image.Height : height; + 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 = gifinfo->SColorMap->Colors; + if (gifinfo->Image.ColorMap) + color = gifinfo->Image.ColorMap->Colors; + + if (color) + { + U8 *dst = getAddress(gifinfo->Image.Left, gifinfo->Image.Top); + U8 *src = data; + U32 right = gifinfo->Image.Left + gwidth; + U32 bottom = gifinfo->Image.Top + gheight; + U32 next = (width - gwidth) * bytesPerPixel; + + if (format == RGBA) + { + 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 + } + else + { + dst[0] = color[*src].Red; + dst[1] = color[*src].Green; + dst[2] = color[*src].Blue; + dst[3] = 0; // alpha + } + dst += bytesPerPixel; + } + dst += next; + } + } + else + { + for (U32 y=gifinfo->Image.Top; yImage.Left; xgetPosition(); + 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 void jpegFlushDataFn(void *) +{ + // +} + + +//-------------------------------------- +static int jpegErrorFn(void *client_data) +{ + Stream *stream = (Stream*)client_data; + AssertFatal(stream != NULL, "jpegErrorFn::No stream."); + return (stream->getStatus() != Stream::Ok); +} + + +//-------------------------------------- +bool GBitmap::readJPEG(Stream &stream) +{ + 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); + + BitmapFormat format; + switch (cinfo.out_color_space) + { + case JCS_GRAYSCALE: format = Alpha; break; + case JCS_RGB: format = RGB; break; + default: + jpeg_destroy_decompress(&cinfo); + return false; + } + + // Start decompressor + jpeg_start_decompress(&cinfo); + + // allocate the bitmap space and init internal variables... + 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*)getBits(); + for (U32 i = 0; i < height; 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); + + return true; +} + + +//-------------------------------------------------------------------------- +bool GBitmap::writeJPEG(Stream&) const +{ + return false; +/* + if (compressHard == false) { + return _writePNG(stream, 6, 0, PNG_ALL_FILTERS); + } else { + U8* buffer = new U8[1 << 22]; // 4 Megs. Should be enough... + MemStream* pMemStream = new MemStream(1 << 22, buffer, false, true); + + // We have to try the potentially useful compression methods here. + + 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); + + if (_writePNG(*pMemStream, cl, zStrategies[zs], pngFilters[pf]) == false) + AssertFatal(false, "Handle this error!"); + + if (pMemStream->getPosition() < minSize) { + minSize = pMemStream->getPosition(); + bestStrategy = zs; + bestFilter = pf; + bestCLevel = cl; + } + } + } + } + AssertFatal(minSize != 0xFFFFFFFF, "Error, no best found?"); + + delete pMemStream; + delete [] buffer; + + + return _writePNG(stream, + bestCLevel, + zStrategies[bestStrategy], + pngFilters[bestFilter]); + } +*/ +} diff --git a/dgl/bitmapPng.cc b/dgl/bitmapPng.cc new file mode 100644 index 0000000..9a7ea71 --- /dev/null +++ b/dgl/bitmapPng.cc @@ -0,0 +1,415 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/stream.h" +#include "Core/fileStream.h" +#include "Core/memstream.h" +#include "dgl/gPalette.h" +#include "dgl/gBitmap.h" +#include "Sim/frameAllocator.h" + +#define PNG_INTERNAL 1 +#include "time.h" +#include "png.h" +#include "zlib.h" + +#ifdef NULL +#undef NULL +#define NULL 0 +#endif + +// Our chunk signatures... +static png_byte DGL_CHUNK_dcCf[5] = { 100, 99, 67, 102, '\0' }; +static png_byte DGL_CHUNK_dcCs[5] = { 100, 99, 67, 115, '\0' }; + +static const U32 csgMaxRowPointers = 1024; +static png_bytep sRowPointers[1024]; + +//-------------------------------------- Instead of using the user_ptr, +// we use a global pointer, we +// need to ensure that only one thread +// at once may be using the variable. +// NOTE: Removed mutex for g_varAccess. +// may have to re-thread safe this. +static Stream* sg_pStream = NULL; + +//-------------------------------------- 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(sg_pStream != NULL, "No stream?"); + + bool success = sg_pStream->read(length, data); + AssertFatal(success, "Png Read catastrofic error!"); +} + + +//-------------------------------------- +static void pngWriteDataFn(png_structp /*png_ptr*/, + png_bytep data, + png_size_t length) +{ + AssertFatal(sg_pStream != NULL, "No stream?"); + + sg_pStream->write(length, data); +} + + +//-------------------------------------- +static void pngFlushDataFn(png_structp /*png_ptr*/) +{ + // +} + +static png_voidp pngMallocFn(png_structp /*png_ptr*/, png_size_t size) +{ + return FrameAllocator::alloc(size); +// return (png_voidp)dMalloc(size); +} + +static void pngFreeFn(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)); +} + + +//-------------------------------------- +bool GBitmap::readPNG(Stream& io_rStream) +{ + static const U32 cs_headerBytesChecked = 8; + + U8 header[cs_headerBytesChecked]; + io_rStream.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, + pngMallocFn, + pngFreeFn); + + 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; + } + + sg_pStream = &io_rStream; + png_set_read_fn(png_ptr, NULL, 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... + // + 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; + BitmapFormat format = RGB; + + // Strip off any 16 bit info + // + if (bit_depth == 16) { + 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 ? RGBA : RGB; + } else if (color_type == PNG_COLOR_TYPE_GRAY) { + png_set_expand(png_ptr); + //png_set_gray_to_rgb(png_ptr); + format = Alpha; //transAlpha ? RGBA : RGB; + } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_expand(png_ptr); + png_set_gray_to_rgb(png_ptr); + format = RGBA; + } else if (color_type == PNG_COLOR_TYPE_RGB) { + format = transAlpha ? RGBA : RGB; + png_set_expand(png_ptr); + } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + png_set_expand(png_ptr); + format = RGBA; + } + + // 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 == RGB) { + AssertFatal(rowBytes == width * 3, + "Error, our rowbytes are incorrect for this transform... (3)"); + } else if (format == RGBA) { + AssertFatal(rowBytes == width * 4, + "Error, our rowbytes are incorrect for this transform... (4)"); + } + + // actually allocate the bitmap space... + allocateBitmap(width, height, + false, // don't extrude miplevels... + format); // use determined format... + + // Set up the row pointers... + AssertISV(height <= csgMaxRowPointers, "Error, cannot load pngs taller than 1024 pixels!"); + png_bytep* rowPointers = sRowPointers; + U8* pBase = (U8*)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); + png_read_end(png_ptr, NULL); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + + sg_pStream = NULL; + + // 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 + // + FrameAllocator::setWaterMark(prevWaterMark); + return true; +} + + +//-------------------------------------------------------------------------- +bool GBitmap::_writePNG(Stream& stream, + const U32 compressionLevel, + const U32 strategy, + const U32 filter) const +{ + // ONLY RGB bitmap writing supported at this time! + AssertFatal(getFormat() == RGB || getFormat() == RGBA || getFormat() == Alpha, "GBitmap::writePNG: ONLY RGB bitmap writing supported at this time."); + if (internalFormat != RGB && internalFormat != RGBA && internalFormat != Alpha) + return (false); + + #define MAX_HEIGHT 4096 + + if (height >= MAX_HEIGHT) + 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; + } + + sg_pStream = &stream; + png_set_write_fn(png_ptr, NULL, 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 + + if (getFormat() == RGB) { + 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 (getFormat() == RGBA) { + 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 (getFormat() == Alpha) { + 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 + } + + png_write_info(png_ptr, info_ptr); + png_bytep row_pointers[MAX_HEIGHT]; + for (U32 i=0; i(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; +} + + +//-------------------------------------------------------------------------- +bool GBitmap::writePNG(Stream& stream, const bool compressHard) const +{ + U32 waterMark = FrameAllocator::getWaterMark(); + + if (compressHard == false) { + bool retVal = _writePNG(stream, 6, 0, PNG_ALL_FILTERS); + FrameAllocator::setWaterMark(waterMark); + return retVal; + } else { + U8* buffer = new U8[1 << 22]; // 4 Megs. Should be enough... + MemStream* pMemStream = new MemStream(1 << 22, buffer, false, true); + + // We have to try the potentially useful compression methods here. + + 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); + + if (_writePNG(*pMemStream, cl, zStrategies[zs], pngFilters[pf]) == false) + AssertFatal(false, "Handle this error!"); + + 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(stream, + bestCLevel, + zStrategies[bestStrategy], + pngFilters[bestFilter]); + FrameAllocator::setWaterMark(waterMark); + return retVal; + } +} + +//-------------------------------------------------------------------------- +bool GBitmap::writePNGUncompressed(Stream& stream) const +{ + U32 waterMark = FrameAllocator::getWaterMark(); + + bool retVal = _writePNG(stream, 0, 0, PNG_FILTER_NONE); + FrameAllocator::setWaterMark(waterMark); + return retVal; +} diff --git a/dgl/dgl.cc b/dgl/dgl.cc new file mode 100644 index 0000000..7ea9268 --- /dev/null +++ b/dgl/dgl.cc @@ -0,0 +1,746 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "math/mPoint.h" +#include "dgl/gTexManager.h" +#include "dgl/dgl.h" +#include "core/color.h" +#include "math/mPoint.h" +#include "math/mRect.h" +#include "dgl/gFont.h" +#include "console/console.h" +#include "math/mMatrix.h" +#include "sim/frameAllocator.h" +#include "platform/profiler.h" + +namespace { + +ColorI sg_bitmapModulation(255, 255, 255, 255); +ColorI sg_textAnchorColor(255, 255, 255, 255); +ColorI sg_stackColor(255, 255, 255, 255); +RectI sgCurrentClipRect; + +} // namespace {} + + +//-------------------------------------------------------------------------- +void dglSetBitmapModulation(const ColorF& in_rColor) +{ + ColorF c = in_rColor; + c.clamp(); + sg_bitmapModulation = c; + sg_textAnchorColor = sg_bitmapModulation; +} + +void dglGetBitmapModulation(ColorF* color) +{ + *color = sg_bitmapModulation; +} + +void dglGetBitmapModulation(ColorI* color) +{ + *color = sg_bitmapModulation; +} + +void dglClearBitmapModulation() +{ + sg_bitmapModulation.set(255, 255, 255, 255); +} + +void dglSetTextAnchorColor(const ColorF& in_rColor) +{ + ColorF c = in_rColor; + c.clamp(); + sg_textAnchorColor = c; +} + + +//-------------------------------------------------------------------------- +void dglDrawBitmapStretchSR(TextureObject* texture, + const RectI& dstRect, + const RectI& srcRect, + const U32 in_flip) +{ + AssertFatal(texture != NULL, "GSurface::drawBitmapStretchSR: NULL Handle"); + if(!dstRect.isValidRect()) + return; + AssertFatal(srcRect.isValidRect() == true, + "GSurface::drawBitmapStretchSR: routines assume normal rects"); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texture->texGLName); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + F32 texLeft = F32(srcRect.point.x) / F32(texture->texWidth); + F32 texRight = F32(srcRect.point.x + srcRect.extent.x) / F32(texture->texWidth); + F32 texTop = F32(srcRect.point.y) / F32(texture->texHeight); + F32 texBottom = F32(srcRect.point.y + srcRect.extent.y) / F32(texture->texHeight); + 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 & GFlip_X) + { + F32 temp = texLeft; + texLeft = texRight; + texRight = temp; + } + if(in_flip & GFlip_Y) + { + F32 temp = texTop; + texTop = texBottom; + texBottom = temp; + } + + glColor4ub(sg_bitmapModulation.red, + sg_bitmapModulation.green, + sg_bitmapModulation.blue, + sg_bitmapModulation.alpha); + + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f(texLeft, texBottom); + glVertex2f(screenLeft, screenBottom); + + glTexCoord2f(texRight, texBottom); + glVertex2f(screenRight, screenBottom); + + glTexCoord2f(texRight, texTop); + glVertex2f(screenRight, screenTop); + + glTexCoord2f(texLeft, texTop); + glVertex2f(screenLeft, screenTop); + glEnd(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); +} + +void dglDrawBitmap(TextureObject* texture, const Point2I& in_rAt, const U32 in_flip) +{ + AssertFatal(texture != NULL, "GSurface::drawBitmap: NULL Handle"); + + // All non-StretchSR bitmaps are transformed into StretchSR calls... + // + RectI subRegion(0, 0, + texture->bitmapWidth, + texture->bitmapHeight); + RectI stretch(in_rAt.x, in_rAt.y, + texture->bitmapWidth, + texture->bitmapHeight); + dglDrawBitmapStretchSR(texture, + stretch, + subRegion, + in_flip); +} + +void dglDrawBitmapStretch(TextureObject* texture, const RectI& dstRect, const U32 in_flip) +{ + AssertFatal(texture != NULL, "GSurface::drawBitmapStretch: NULL Handle"); + AssertFatal(dstRect.isValidRect() == true, + "GSurface::drawBitmapStretch: routines assume normal rects"); + + RectI subRegion(0, 0, + texture->bitmapWidth, + texture->bitmapHeight); + dglDrawBitmapStretchSR(texture, + dstRect, + subRegion, + in_flip); +} + +void dglDrawBitmapSR(TextureObject *texture, const Point2I& in_rAt, const RectI& srcRect, const U32 in_flip) +{ + AssertFatal(texture != NULL, "GSurface::drawBitmapSR: NULL Handle"); + AssertFatal(srcRect.isValidRect() == true, + "GSurface::drawBitmapSR: routines assume normal rects"); + + RectI stretch(in_rAt.x, in_rAt.y, + srcRect.len_x(), + srcRect.len_y()); + dglDrawBitmapStretchSR(texture, + stretch, + srcRect, + in_flip); +} + +U32 dglDrawText(GFont* font, + const Point2I& ptDraw, + const void* in_string, + const ColorI* colorTable, + const U32 maxColorIndex) +{ + return dglDrawTextN(font, ptDraw, in_string, dStrlen((const char *) in_string), colorTable, maxColorIndex); +} + +struct TextVertex +{ + Point3F p; + Point2F t; + ColorI c; + void set(F32 x, F32 y, F32 tx, F32 ty, ColorI color) + { + p.x = x; + p.y = y; + p.z = 0; + t.x = tx; + t.y = ty; + c = color; + } +}; + +U32 dglDrawTextN(GFont* font, + const Point2I& ptDraw, + const void* in_string, + U32 n, + const ColorI* colorTable, + const U32 maxColorIndex) +{ + // return on zero length strings + if( n < 1 ) + return ptDraw.x; + PROFILE_START(DrawText); + + Point2I pt; + U8 c; + const U8 *str = (const U8*)in_string; + const U8 *endStr = str + n; + pt.x = ptDraw.x; + + ColorI currentColor; + S32 currentPt = 0; + U32 storedWaterMark; + + TextureObject *lastTexture = NULL; + + storedWaterMark = FrameAllocator::getWaterMark(); + currentColor = sg_bitmapModulation; + + TextVertex *vert = (TextVertex *) FrameAllocator::alloc(4 * n * sizeof(TextVertex)); + + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + glEnableClientState ( GL_VERTEX_ARRAY ); + glVertexPointer ( 3, GL_FLOAT, sizeof(TextVertex), &(vert[0].p) ); + + glEnableClientState ( GL_COLOR_ARRAY ); + glColorPointer ( 4, GL_UNSIGNED_BYTE, sizeof(TextVertex), &(vert[0].c) ); + + glEnableClientState ( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[0].t) ); + + // first build the point, color, and coord arrays + for (c = *str; str < endStr; c = *(++str)) + { + // We have to do a little dance here since \t = 0x9, \n = 0xa, and \r = 0xd + if ((c >= 2 && c <= 8) || + (c >= 11 && c <= 12) || + (c == 14)) + { + // Color code + if (colorTable) + { + static U8 remap[15] = + { + 0x0, + 0x0, + 0x0, + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x0, + 0x0, + 0x7, + 0x8, + 0x0, + 0x9 + }; + + U8 remapped = remap[c]; + // Ignore if the color is greater than the specified max index: + if ( remapped <= maxColorIndex ) + { + const ColorI &clr = colorTable[remapped]; + currentColor = sg_bitmapModulation = clr; + } + } + continue; + } + + // reset color? + if ( c == 15 ) + { + currentColor = sg_textAnchorColor; + sg_bitmapModulation = sg_textAnchorColor; + continue; + } + + // push color: + if ( c == 16 ) + { + sg_stackColor = sg_bitmapModulation; + continue; + } + + // pop color: + if ( c == 17 ) + { + currentColor = sg_stackColor; + sg_bitmapModulation = sg_stackColor; + continue; + } + + // Tab character + if( !font->isValidChar( c ) ) + { + if ( c == '\t' ) + { + const GFont::CharInfo &ci = font->getCharInfo( ' ' ); + pt.x += ci.xIncrement * GFont::TabWidthInSpaces; + } + continue; + } + + const GFont::CharInfo &ci = font->getCharInfo(c); + TextureObject *newObj = font->getTextureHandle(ci.bitmapIndex); + if(newObj != lastTexture) + { + if(currentPt) + { + glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName); + glDrawArrays( GL_QUADS, 0, currentPt ); + currentPt = 0; + } + lastTexture = newObj; + } + if(ci.width != 0 && ci.height != 0) + { + pt.y = ptDraw.y + font->getBaseline() - ci.yOrigin; + pt.x += ci.xOrigin; + + F32 texLeft = F32(ci.xOffset) / F32(lastTexture->texWidth); + F32 texRight = F32(ci.xOffset + ci.width) / F32(lastTexture->texWidth); + F32 texTop = F32(ci.yOffset) / F32(lastTexture->texHeight); + F32 texBottom = F32(ci.yOffset + ci.height) / F32(lastTexture->texHeight); + + F32 screenLeft = pt.x; + F32 screenRight = pt.x + ci.width; + F32 screenTop = pt.y; + F32 screenBottom = pt.y + ci.height; + vert[currentPt++].set(screenLeft, screenBottom, texLeft, texBottom, currentColor); + vert[currentPt++].set(screenRight, screenBottom, texRight, texBottom, currentColor); + vert[currentPt++].set(screenRight, screenTop, texRight, texTop, currentColor); + vert[currentPt++].set(screenLeft, screenTop, texLeft, texTop, currentColor); + pt.x += ci.xIncrement - ci.xOrigin; + } + else + pt.x += ci.xIncrement; + } +/* if (gOpenGLNoDrawArraysAlpha) + { + glBegin(GL_QUADS); + for (S32 i = 0; i < rd[page].count; ++i) + { + glColor4fv((float *) &colArray[rd[page].start+i]); + glTexCoord2f(texArray[rd[page].start+i].x, texArray[rd[page].start+i].y); + glVertex2f(ptArray[rd[page].start+i].x, ptArray[rd[page].start+i].y); + } + glEnd(); + } + else*/ + if(currentPt) + { + glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName); + glDrawArrays( GL_QUADS, 0, currentPt ); + } + + glDisableClientState ( GL_VERTEX_ARRAY ); + glDisableClientState ( GL_COLOR_ARRAY ); + glDisableClientState ( GL_TEXTURE_COORD_ARRAY ); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + // restore the FrameAllocator + FrameAllocator::setWaterMark(storedWaterMark); + + AssertFatal(pt.x >= ptDraw.x, "How did this happen?"); + PROFILE_END(); + return pt.x - ptDraw.x; +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// Drawing primitives + +void dglDrawLine(S32 x1, S32 y1, S32 x2, S32 y2, const ColorI &color) +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_TEXTURE_2D); + + glColor4ub(color.red, color.green, color.blue, color.alpha); + glBegin(GL_LINES); + glVertex2f((F32)x1 + 0.5, (F32)y1 + 0.5); + glVertex2f((F32)x2 + 0.5, (F32)y2 + 0.5); + glEnd(); +} + +void dglDrawLine(const Point2I &startPt, const Point2I &endPt, const ColorI &color) +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_TEXTURE_2D); + + glColor4ub(color.red, color.green, color.blue, color.alpha); + glBegin(GL_LINES); + glVertex2f((F32)startPt.x + 0.5, (F32)startPt.y + 0.5); + glVertex2f((F32)endPt.x + 0.5, (F32)endPt.y + 0.5); + glEnd(); +} + +void dglDrawRect(const Point2I &upperL, const Point2I &lowerR, const ColorI &color) +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_TEXTURE_2D); + + glColor4ub(color.red, color.green, color.blue, color.alpha); + glBegin(GL_LINE_LOOP); + glVertex2f((F32)upperL.x + 0.5, (F32)upperL.y + 0.5); + glVertex2f((F32)lowerR.x + 0.5, (F32)upperL.y + 0.5); + glVertex2f((F32)lowerR.x + 0.5, (F32)lowerR.y + 0.5); + glVertex2f((F32)upperL.x + 0.5, (F32)lowerR.y + 0.5); + glEnd(); +} + +void dglDrawRect(const RectI &rect, const ColorI &color) +{ + Point2I lowerR(rect.point.x + rect.extent.x - 1, rect.point.y + rect.extent.y - 1); + dglDrawRect(rect.point, lowerR, color); +} + +void dglDrawRectFill(const Point2I &upperL, const Point2I &lowerR, const ColorI &color) +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_TEXTURE_2D); + + glColor4ub(color.red, color.green, color.blue, color.alpha); + glRecti((F32)upperL.x, (F32)upperL.y, (F32)lowerR.x, (F32)lowerR.y); +} +void dglDrawRectFill(const RectI &rect, const ColorI &color) +{ + Point2I lowerR(rect.point.x + rect.extent.x - 1, rect.point.y + rect.extent.y - 1); + dglDrawRectFill(rect.point, lowerR, color); +} + +void dglDraw2DSquare( const Point2F &screenPoint, F32 width, F32 spinAngle ) +{ + width *= 0.5; + + MatrixF rotMatrix( EulerF( 0.0, 0.0, spinAngle ) ); + + Point3F offset( screenPoint.x, screenPoint.y, 0.0 ); + Point3F points[4]; + + points[0] = Point3F(-width, -width, 0.0); + points[1] = Point3F(-width, width, 0.0); + points[2] = Point3F( width, width, 0.0); + points[3] = Point3F( width, -width, 0.0); + + for( int i=0; i<4; i++ ) + { + rotMatrix.mulP( points[i] ); + points[i] += offset; + } + + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f(0.0, 0.0); + glVertex2fv(points[0]); + + glTexCoord2f(0.0, 1.0); + glVertex2fv(points[1]); + + glTexCoord2f(1.0, 1.0); + glVertex2fv(points[2]); + + glTexCoord2f(1.0, 0.0); + glVertex2fv(points[3]); + glEnd(); +} + +void dglDrawBillboard( const Point3F &position, F32 width, F32 spinAngle ) +{ + MatrixF modelview; + dglGetModelview( &modelview ); + modelview.transpose(); + + + width *= 0.5; + Point3F points[4]; + points[0] = Point3F(-width, 0.0, -width); + points[1] = Point3F(-width, 0.0, width); + points[2] = Point3F( width, 0.0, width); + points[3] = Point3F( width, 0.0, -width); + + + MatrixF rotMatrix( EulerF( 0.0, spinAngle, 0.0 ) ); + + for( int i=0; i<4; i++ ) + { + rotMatrix.mulP( points[i] ); + modelview.mulP( points[i] ); + points[i] += position; + } + + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f(0.0, 0.0); + glVertex3fv(points[0]); + + glTexCoord2f(0.0, 1.0); + glVertex3fv(points[1]); + + glTexCoord2f(1.0, 1.0); + glVertex3fv(points[2]); + + glTexCoord2f(1.0, 0.0); + glVertex3fv(points[3]); + glEnd(); + + +} + + + +void dglSetClipRect(const RectI &clipRect) +{ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + U32 screenWidth = Platform::getWindowSize().x; + U32 screenHeight = Platform::getWindowSize().y; + + glOrtho(clipRect.point.x, clipRect.point.x + clipRect.extent.x, + clipRect.extent.y, 0, + 0, 1); + glTranslatef(0, -clipRect.point.y, 0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glViewport(clipRect.point.x, screenHeight - (clipRect.point.y + clipRect.extent.y), + clipRect.extent.x, clipRect.extent.y); + + sgCurrentClipRect = clipRect; +} + +const RectI& dglGetClipRect() +{ + return sgCurrentClipRect; +} + +bool dglPointToScreen( Point3F &point3D, Point3F &screenPoint ) +{ + GLdouble glMV[16]; + GLdouble glPR[16]; + GLint glVP[4]; + + + glGetDoublev(GL_PROJECTION_MATRIX, glPR); + glGetDoublev(GL_MODELVIEW_MATRIX, glMV); + + RectI viewport; + dglGetViewport(&viewport); + + glVP[0] = viewport.point.x; + glVP[1] = viewport.point.y + viewport.extent.y; + glVP[2] = viewport.extent.x; + glVP[3] = -viewport.extent.y; + + MatrixF mv; + dglGetModelview(&mv); + MatrixF pr; + dglGetProjection(&pr); + + F64 x, y, z; + int result = gluProject( point3D.x, point3D.y, point3D.z, (const F64 *)&glMV, (const F64 *)&glPR, (const S32 *)&glVP, &x, &y, &z ); + screenPoint.x = x; + screenPoint.y = y; + screenPoint.z = z; + + + return (result == GL_TRUE); + +} + + + +bool dglIsInCanonicalState() +{ + bool ret = true; + + // Canonical state: + // BLEND disabled + // TEXTURE_2D disabled on both texture units. + // ActiveTexture set to 0 + // LIGHTING off + // winding : clockwise ? + // cullface : disabled + + ret &= glIsEnabled(GL_BLEND) == GL_FALSE; + ret &= glIsEnabled(GL_CULL_FACE) == GL_FALSE; + GLint temp; + + if (dglDoesSupportARBMultitexture() == true) { + glActiveTextureARB(GL_TEXTURE1_ARB); + ret &= glIsEnabled(GL_TEXTURE_2D) == GL_FALSE; + glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &temp); + ret &= temp == GL_REPLACE; + + glActiveTextureARB(GL_TEXTURE0_ARB); + ret &= glIsEnabled(GL_TEXTURE_2D) == GL_FALSE; + glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &temp); + ret &= temp == GL_REPLACE; + + glClientActiveTextureARB(GL_TEXTURE1_ARB); + ret &= glIsEnabled(GL_TEXTURE_COORD_ARRAY) == GL_FALSE; + glClientActiveTextureARB(GL_TEXTURE0_ARB); + ret &= glIsEnabled(GL_TEXTURE_COORD_ARRAY) == GL_FALSE; + } else { + ret &= glIsEnabled(GL_TEXTURE_2D) == GL_FALSE; + glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &temp); + ret &= temp == GL_REPLACE; + + ret &= glIsEnabled(GL_TEXTURE_COORD_ARRAY) == GL_FALSE; + } + + ret &= glIsEnabled(GL_LIGHTING) == GL_FALSE; + + ret &= glIsEnabled(GL_COLOR_ARRAY) == GL_FALSE; + ret &= glIsEnabled(GL_VERTEX_ARRAY) == GL_FALSE; + ret &= glIsEnabled(GL_NORMAL_ARRAY) == GL_FALSE; + if (dglDoesSupportFogCoord()) + ret &= glIsEnabled(GL_FOG_COORDINATE_ARRAY_EXT) == GL_FALSE; + + return ret; +} + + +void dglSetCanonicalState() +{ + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glBlendFunc(GL_ONE, GL_ZERO); + glDisable(GL_LIGHTING); + if (dglDoesSupportARBMultitexture() == true) { + glActiveTextureARB(GL_TEXTURE1_ARB); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glActiveTextureARB(GL_TEXTURE0_ARB); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } else { + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + if (dglDoesSupportFogCoord()) + glDisableClientState(GL_FOG_COORDINATE_ARRAY_EXT); +} + +void dglGetTransformState(S32* mvDepth, + S32* pDepth, + S32* t0Depth, + F32* t0Matrix, + S32* t1Depth, + F32* t1Matrix, + S32* vp) +{ + glGetIntegerv(GL_MODELVIEW_STACK_DEPTH, mvDepth); + glGetIntegerv(GL_PROJECTION_STACK_DEPTH, pDepth); + + glGetIntegerv(GL_TEXTURE_STACK_DEPTH, t0Depth); + glGetFloatv(GL_TEXTURE_MATRIX, t0Matrix); + if (dglDoesSupportARBMultitexture()) + { + glActiveTextureARB(GL_TEXTURE1_ARB); + glGetIntegerv(GL_TEXTURE_STACK_DEPTH, t1Depth); + glGetFloatv(GL_TEXTURE_MATRIX, t1Matrix); + glActiveTextureARB(GL_TEXTURE0_ARB); + } + else + { + *t1Depth = 0; + for (U32 i = 0; i < 16; i++) + t1Matrix[i] = 0; + } + + RectI v; + dglGetViewport(&v); + vp[0] = v.point.x; + vp[1] = v.point.y; + vp[2] = v.extent.x; + vp[3] = v.extent.y; +} + + +bool dglCheckState(const S32 mvDepth, const S32 pDepth, + const S32 t0Depth, const F32* t0Matrix, + const S32 t1Depth, const F32* t1Matrix, + const S32* vp) +{ + GLint md, pd; + RectI v; + + glGetIntegerv(GL_MODELVIEW_STACK_DEPTH, &md); + glGetIntegerv(GL_PROJECTION_STACK_DEPTH, &pd); + + GLint t0d, t1d; + GLfloat t0m[16], t1m[16]; + glGetIntegerv(GL_TEXTURE_STACK_DEPTH, &t0d); + glGetFloatv(GL_TEXTURE_MATRIX, t0m); + if (dglDoesSupportARBMultitexture()) + { + glActiveTextureARB(GL_TEXTURE1_ARB); + glGetIntegerv(GL_TEXTURE_STACK_DEPTH, &t1d); + glGetFloatv(GL_TEXTURE_MATRIX, t1m); + glActiveTextureARB(GL_TEXTURE0_ARB); + } + else + { + t1d = 0; + for (U32 i = 0; i < 16; i++) + t1m[i] = 0; + } + + dglGetViewport(&v); + + return ((md == mvDepth) && + (pd == pDepth) && + (t0d == t0Depth) && + (dMemcmp(t0m, t0Matrix, sizeof(F32) * 16) == 0) && + (t1d == t1Depth) && + (dMemcmp(t1m, t1Matrix, sizeof(F32) * 16) == 0) && + ((v.point.x == vp[0]) && + (v.point.y == vp[1]) && + (v.extent.x == vp[2]) && + (v.extent.y == vp[3]))); +} + diff --git a/dgl/dgl.h b/dgl/dgl.h new file mode 100644 index 0000000..96733e6 --- /dev/null +++ b/dgl/dgl.h @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DGL_H_ +#define _DGL_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _PLATFORMGL_H_ +#include "PlatformWin32/platformGL.h" +#endif + +class TextureObject; +class GFont; +class MatrixF; +class RectI; +class ColorI; +class ColorF; +class Point2I; +class Point2F; +class Point3F; + +//------------------------------------------------------------------------------ +//-------------------------------------- Bitmap Drawing +// +static const U32 GFlip_None = 0; +static const U32 GFlip_X = 1 << 0; +static const U32 GFlip_Y = 1 << 1; +static const U32 GFlip_XY = GFlip_X | GFlip_Y; + +void dglSetBitmapModulation(const ColorF& in_rColor); +void dglGetBitmapModulation(ColorF* color); +void dglGetBitmapModulation(ColorI* color); +void dglClearBitmapModulation(); + +// Note that you must call this _after_ SetBitmapModulation if the two are different +// SetBMod sets the text anchor to the modulation color +void dglSetTextAnchorColor(const ColorF&); + +void dglDrawBitmap(TextureObject* texObject, + const Point2I& in_rAt, + const U32 in_flip = GFlip_None); +void dglDrawBitmapStretch(TextureObject* texObject, + const RectI& in_rStretch, + const U32 in_flip = GFlip_None); +void dglDrawBitmapSR(TextureObject* texObject, + const Point2I& in_rAt, + const RectI& in_rSubRegion, + const U32 in_flip = GFlip_None); +void dglDrawBitmapStretchSR(TextureObject* texObject, + const RectI& in_rStretch, + const RectI& in_rSubRegion, + const U32 in_flip = GFlip_None); + +// Returns the number of x pixels traversed +U32 dglDrawText(GFont *font, const Point2I &ptDraw, const void *in_string, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9); +U32 dglDrawTextN(GFont *font, const Point2I &ptDraw, const void *in_string, U32 n, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9); + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// Drawing primitives + +void dglDrawLine(S32 x1, S32 y1, S32 x2, S32 y2, const ColorI &color); +void dglDrawLine(const Point2I &startPt, const Point2I &endPt, const ColorI &color); +void dglDrawRect(const Point2I &upperL, const Point2I &lowerR, const ColorI &color); +void dglDrawRect(const RectI &rect, const ColorI &color); +void dglDrawRectFill(const Point2I &upperL, const Point2I &lowerR, const ColorI &color); +void dglDrawRectFill(const RectI &rect, const ColorI &color); +void dglDraw2DSquare( const Point2F &screenPoint, F32 width, F32 spinAngle ); +void dglDrawBillboard( const Point3F &position, F32 width, F32 spinAngle ); + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// Matrix functions + +void dglLoadMatrix(const MatrixF *m); +void dglMultMatrix(const MatrixF *m); +void dglGetModelview(MatrixF *m); +void dglGetProjection(MatrixF *m); + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// Camera functions + +F32 dglGetPixelScale(); +F32 dglGetWorldToScreenScale(); +F32 dglProjectRadius(F32 dist, F32 radius); + +void dglSetViewport(const RectI &aViewPort); +void dglGetViewport(RectI* outViewport); +void dglSetFrustum(F64 left, F64 right, F64 bottom, F64 top, F64 nearDist, F64 farDist, bool ortho = false); +void dglGetFrustum(F64 *left, F64 *right, F64 *bottom, F64 *top, F64 *nearDist, F64 *farDist); +bool dglIsOrtho(); +void dglSetClipRect(const RectI &clipRect); +const RectI& dglGetClipRect(); + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// Misc +bool dglPointToScreen( Point3F &point3D, Point3F &screenPoint ); + +//-------------------------------------------------------------------------- +// Debug stuff +bool dglIsInCanonicalState(); +void dglSetCanonicalState(); + +void dglGetTransformState(S32* mvDepth, + S32* pDepth, + S32* t0Depth, + F32* t0Matrix, + S32* t1Depth, + F32* t1Matrix, + S32* vp); +bool dglCheckState(const S32 mvDepth, const S32 pDepth, + const S32 t0Depth, const F32* t0Matrix, + const S32 t1Depth, const F32* t1Matrix, + const S32* vp); + +#endif // _H_DGL diff --git a/dgl/dglMatrix.cc b/dgl/dglMatrix.cc new file mode 100644 index 0000000..2f2f82c --- /dev/null +++ b/dgl/dglMatrix.cc @@ -0,0 +1,146 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Math/mMatrix.h" +#include "dgl/dgl.h" +#include "console/console.h" + +void dglLoadMatrix(const MatrixF *m) +{ + //F32 mat[16]; + //m->transposeTo(mat); + const_cast(m)->transpose(); + glLoadMatrixf(*m); + const_cast(m)->transpose(); +} + +void dglMultMatrix(const MatrixF *m) +{ + //F32 mat[16]; + //m->transposeTo(mat); +// const F32* mp = *m; +// Con::errorf(ConsoleLogEntry::General, "Mult: %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g", +// mp[0], +// mp[1], +// mp[2], +// mp[3], +// mp[4], +// mp[5], +// mp[6], +// mp[7], +// mp[8], +// mp[9], +// mp[10], +// mp[11], +// mp[12], +// mp[13], +// mp[14], +// mp[15]); + + + const_cast(m)->transpose(); + glMultMatrixf(*m); + const_cast(m)->transpose(); +} + +void dglGetModelview(MatrixF *m) +{ + glGetFloatv(GL_MODELVIEW_MATRIX, *m); + m->transpose(); +} + +void dglGetProjection(MatrixF *m) +{ + glGetFloatv(GL_PROJECTION_MATRIX, *m); + m->transpose(); +} + +static F64 frustLeft = 0, frustRight = 1, frustBottom, frustTop, frustNear, frustFar; +static RectI viewPort; +static F32 pixelScale; +static F32 worldToScreenScale; +static bool isOrtho; + +void dglSetFrustum(F64 left, F64 right, F64 bottom, F64 top, F64 nearPlane, F64 farPlane, bool ortho) +{ + // this converts from a coord system looking down the pos-y axis + // to ogl's down neg z axis. + // it's stored in OGL matrix form + static F32 darkToOGLCoord[16] = { 1, 0, 0, 0, + 0, 0, -1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 }; + + frustLeft = left; + frustRight = right; + frustBottom = bottom; + frustTop = top; + frustNear = nearPlane; + frustFar = farPlane; + isOrtho = ortho; + if (ortho) + { + glOrtho(left, right, bottom, top, nearPlane, farPlane); + worldToScreenScale = viewPort.extent.x / (frustRight - frustLeft); + } + else + { + glFrustum(left, right, bottom, top, nearPlane, farPlane); + worldToScreenScale = (frustNear * viewPort.extent.x) / (frustRight - frustLeft); + } + glMultMatrixf(darkToOGLCoord); +} + +void dglGetFrustum(F64 *left, F64 *right, F64 *bottom, F64 *top, F64 *nearPlane, F64 *farPlane) +{ + *left = frustLeft; + *right = frustRight; + *bottom = frustBottom; + *top = frustTop; + *nearPlane = frustNear; + *farPlane = frustFar; +} + +bool dglIsOrtho() +{ + return isOrtho; +} + +void dglSetViewport(const RectI &aViewPort) +{ + viewPort = aViewPort; + U32 screenHeight = Platform::getWindowSize().y; + //glViewport(viewPort.point.x, viewPort.point.y + viewPort.extent.y, + // viewPort.extent.x, -viewPort.extent.y); + + glViewport(viewPort.point.x, screenHeight - (viewPort.point.y + viewPort.extent.y), + viewPort.extent.x, viewPort.extent.y); + pixelScale = viewPort.extent.x / 640.0; + worldToScreenScale = (frustNear * viewPort.extent.x) / (frustRight - frustLeft); +} + +void dglGetViewport(RectI* outViewport) +{ + AssertFatal(outViewport != NULL, "Error, bad point in GetViewport"); + *outViewport = viewPort; +} + +F32 dglGetPixelScale() +{ + return pixelScale; +} + +F32 dglGetWorldToScreenScale() +{ + return worldToScreenScale; +} + +F32 dglProjectRadius(F32 dist, F32 radius) +{ + return (radius / dist) * worldToScreenScale; +} + diff --git a/dgl/gBitmap.cc b/dgl/gBitmap.cc new file mode 100644 index 0000000..209d804 --- /dev/null +++ b/dgl/gBitmap.cc @@ -0,0 +1,771 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/stream.h" +#include "Core/fileStream.h" +#include "dgl/gBitmap.h" +#include "dgl/gPalette.h" +#include "Core/resManager.h" +#include "Platform/platform.h" + +#include "console/console.h" + +const U32 GBitmap::csFileVersion = 3; +U32 GBitmap::sBitmapIdSource = 0; + + +GBitmap::GBitmap() + : internalFormat(RGB), + pBits(NULL), + byteSize(0), + width(0), + height(0), + numMipLevels(0), + bytesPerPixel(0), + pPalette(NULL) +{ + for (U32 i = 0; i < c_maxMipLevels; i++) + mipLevelOffsets[i] = 0xffffffff; +} + +GBitmap::GBitmap(const GBitmap& rCopy) +{ + AssertFatal(rCopy.pPalette == NULL, "Cant copy bitmaps with palettes"); + + internalFormat = rCopy.internalFormat; + + byteSize = rCopy.byteSize; + pBits = new U8[byteSize]; + dMemcpy(pBits, rCopy.pBits, byteSize); + + width = rCopy.width; + height = rCopy.height; + bytesPerPixel = rCopy.bytesPerPixel; + numMipLevels = rCopy.numMipLevels; + dMemcpy(mipLevelOffsets, rCopy.mipLevelOffsets, sizeof(mipLevelOffsets)); + + pPalette = NULL; +} + + +GBitmap::GBitmap(const U32 in_width, + const U32 in_height, + const bool in_extrudeMipLevels, + const BitmapFormat in_format) + : pBits(NULL), + byteSize(0), + pPalette(NULL) +{ + for (U32 i = 0; i < c_maxMipLevels; i++) + mipLevelOffsets[i] = 0xffffffff; + + allocateBitmap(in_width, in_height, in_extrudeMipLevels, in_format); +} + + +//-------------------------------------------------------------------------- +GBitmap::~GBitmap() +{ + deleteImage(); +} + + +//-------------------------------------------------------------------------- +void GBitmap::deleteImage() +{ + delete [] pBits; + pBits = NULL; + byteSize = 0; + + width = 0; + height = 0; + numMipLevels = 0; + + delete pPalette; + pPalette = NULL; +} + + +//-------------------------------------------------------------------------- +void GBitmap::setPalette(GPalette* in_pPalette) +{ + delete pPalette; + + pPalette = in_pPalette; +} + + +//-------------------------------------------------------------------------- +void GBitmap::allocateBitmap(const U32 in_width, const U32 in_height, const bool in_extrudeMipLevels, const BitmapFormat in_format) +{ + //-------------------------------------- Some debug checks... + U32 svByteSize = byteSize; + U8 *svBits = pBits; + + AssertFatal(in_width != 0 && in_height != 0, "GBitmap::allocateBitmap: width or height is 0"); + + if (in_extrudeMipLevels == true) { + //AssertFatal(in_width <= 256 && in_height <= 256, "GBitmap::allocateBitmap: width or height is too large"); + AssertFatal(isPow2(in_width) == true && isPow2(in_height) == true, "GBitmap::GBitmap: in order to extrude miplevels, bitmap w/h must be pow2"); + } + + internalFormat = in_format; + width = in_width; + height = in_height; + + bytesPerPixel = 1; + switch (internalFormat) { + case Alpha: + case Palettized: + case Luminance: + case Intensity: bytesPerPixel = 1; + break; + case RGB: bytesPerPixel = 3; + break; + case RGBA: bytesPerPixel = 4; + break; + case RGB565: + case RGB5551: bytesPerPixel = 2; + break; + default: + AssertFatal(false, "GBitmap::GBitmap: misunderstood format specifier"); + break; + } + + // Set up the mip levels, if necessary... + numMipLevels = 1; + U32 allocPixels = in_width * in_height * bytesPerPixel; + mipLevelOffsets[0] = 0; + + + if (in_extrudeMipLevels == true) { + U32 currWidth = in_width; + U32 currHeight = in_height; + + do { + mipLevelOffsets[numMipLevels] = mipLevelOffsets[numMipLevels - 1] + + (currWidth * currHeight * bytesPerPixel); + currWidth >>= 1; + currHeight >>= 1; + if (currWidth == 0) currWidth = 1; + if (currHeight == 0) currHeight = 1; + + numMipLevels++; + allocPixels += currWidth * currHeight * bytesPerPixel; + } while (currWidth != 1 || currHeight != 1); + } + AssertFatal(numMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels"); + + // Set up the memory... + byteSize = allocPixels; + pBits = new U8[byteSize]; + dMemset(pBits, 0xFF, byteSize); + + if(svBits != NULL) + { + dMemcpy(pBits, svBits, getMin(byteSize, svByteSize)); + delete[] svBits; + } +} + + +//-------------------------------------------------------------------------- +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]; + 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; + } + } else { + for(U32 y = 0; y < height; y++) + { + U32 a = src[0]; + U32 c = src[stride]; + dst[y] = ((( (a >> 11) + (c >> 11)) >> 1) << 11) | + ((( ((a >> 6) & 0x1f) + ((c >> 6) & 0x1f)) >> 1) << 6) | + ((( ((a >> 1) & 0x1F) + ((c >> 1) & 0x1f)) >> 1) << 1); + 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; + src++; + *dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3])) >> 2; + src++; + *dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3])) >> 2; + src += 4; + } + src += stride; // skip + } + } else { + for(U32 y = 0; y < height; y++) + { + *dst++ = (U32(*src) + U32(src[stride])) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride])) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride])) >> 1; + src += 4; + + src += stride; // skip + } + } +} + +//-------------------------------------------------------------------------- +void bitmapExtrudePaletted_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; + + dMemset(mip, 0, width * height); + +// 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; +// src++; +// *dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3])) >> 2; +// src++; +// *dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3])) >> 2; +// src += 4; +// } +// src += stride; // skip +// } +// } else { +// for(U32 y = 0; y < height; y++) +// { +// *dst++ = (U32(*src) + U32(src[stride])) >> 1; +// src++; +// *dst++ = (U32(*src) + U32(src[stride])) >> 1; +// src++; +// *dst++ = (U32(*src) + U32(src[stride])) >> 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; + src++; + *dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4])) >> 2; + src++; + *dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4])) >> 2; + src++; + *dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4])) >> 2; + src += 5; + } + src += stride; // skip + } + } else { + for(U32 y = 0; y < height; y++) + { + *dst++ = (U32(*src) + U32(src[stride])) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride])) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride])) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride])) >> 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 (*bitmapExtrudePaletted)(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth) = bitmapExtrudePaletted_c; + + +//-------------------------------------------------------------------------- +void GBitmap::extrudeMipLevels(bool clearBorders) +{ + if(numMipLevels == 1) + allocateBitmap(getWidth(), getHeight(), true, getFormat()); + +// AssertFatal(getFormat() != Palettized, "Cannot calc miplevels for palettized bitmaps yet"); + + switch (getFormat()) + { + case RGB5551: + { + for(U32 i = 1; i < numMipLevels; i++) + bitmapExtrude5551(getBits(i - 1), getWritableBits(i), getHeight(i), getWidth(i)); + break; + } + + case RGB: + { + for(U32 i = 1; i < numMipLevels; i++) + bitmapExtrudeRGB(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1)); + break; + } + + case RGBA: + { + for(U32 i = 1; i < numMipLevels; i++) + bitmapExtrudeRGBA(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1)); + break; + } + + case Palettized: + { + for(U32 i = 1; i < numMipLevels; i++) + bitmapExtrudePaletted(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1)); + break; + } + } + if (clearBorders) + { + for (U32 i = 1; i> shift; + AssertFatal(newVal <= 255, "Error, oob"); + pMipBits[j] = U8(newVal); + } + } + AssertFatal(getWidth(numMipLevels - 1) == 1 && getHeight(numMipLevels - 1) == 1, + "Error, last miplevel should be 1x1!"); + ((U8*)getWritableBits(numMipLevels - 1))[0] = 0x80; + ((U8*)getWritableBits(numMipLevels - 1))[1] = 0x80; + ((U8*)getWritableBits(numMipLevels - 1))[2] = 0x80; +} + +//-------------------------------------------------------------------------- +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; + + *dst++ = (b << 1) | (g << 6) | (r << 11) | 1; + src += 3; + } +} + + + +void (*bitmapConvertRGB_to_5551)(U8 *src, U32 pixels) = bitmapConvertRGB_to_5551_c; + + +//-------------------------------------------------------------------------- +bool GBitmap::setFormat(BitmapFormat 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 < numMipLevels; i++) + pixels += getHeight(i) * getWidth(i); + + switch (getFormat()) + { + case RGB: + switch (fmt) + { + case RGB5551: + bitmapConvertRGB_to_5551(pBits, pixels); + internalFormat = RGB5551; + bytesPerPixel = 2; + break; + } + break; + + default: + AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); + return false; + } + + U32 offset = 0; + for (U32 j=0; j < numMipLevels; j++) + { + mipLevelOffsets[j] = offset; + offset += getHeight(j) * getWidth(j) * bytesPerPixel; + } + + return true; +} + + +//-------------------------------------------------------------------------- +bool GBitmap::getColor(const U32 x, const U32 y, ColorI& rColor) const +{ + if (x >= width || y >= height) + return false; + if (internalFormat == Palettized && pPalette == NULL) + return false; + + const U8* pLoc = getAddress(x, y); + + switch (internalFormat) { + case Palettized: + rColor = pPalette->getColor(*pLoc); + break; + + case Alpha: + case Intensity: + case Luminance: + rColor.red = *pLoc; + rColor.green = *pLoc; + rColor.blue = *pLoc; + rColor.alpha = *pLoc; + break; + + case RGB: + rColor.red = pLoc[0]; + rColor.green = pLoc[1]; + rColor.blue = pLoc[2]; + rColor.alpha = 255; + break; + + case RGBA: + rColor.red = pLoc[0]; + rColor.green = pLoc[1]; + rColor.blue = pLoc[2]; + rColor.alpha = pLoc[3]; + break; + + case RGB5551: + rColor.red = *((U16*)pLoc) >> 11; + rColor.green = (*((U16*)pLoc) >> 6) & 0x1f; + rColor.blue = (*((U16*)pLoc) >> 1) & 0x1f; + rColor.alpha = (*((U16*)pLoc) & 1) ? 255 : 0; + break; + + default: + AssertFatal(false, "Bad internal format"); + return false; + } + + return true; +} + + +//-------------------------------------------------------------------------- +bool GBitmap::setColor(const U32 x, const U32 y, ColorI& rColor) +{ + if (x >= width || y >= height) + return false; + if (internalFormat == Palettized && pPalette == NULL) + return false; + + U8* pLoc = getAddress(x, y); + + switch (internalFormat) { + case Palettized: + rColor = pPalette->getColor(*pLoc); + break; + + case Alpha: + case Intensity: + case Luminance: + *pLoc = rColor.alpha; + break; + + case RGB: + pLoc[0] = rColor.red; + pLoc[1] = rColor.green; + pLoc[2] = rColor.blue; + break; + + case RGBA: + pLoc[0] = rColor.red; + pLoc[1] = rColor.green; + pLoc[2] = rColor.blue; + pLoc[3] = rColor.alpha; + break; + + case RGB5551: + *((U16*)pLoc) = (rColor.blue << 1) | (rColor.green << 6) | (rColor.red << 11) | ((rColor.alpha>0) ? 1 : 0); + break; + + default: + AssertFatal(false, "Bad internal format"); + return false; + } + + return true; +} + + +//------------------------------------------------------------------------------ +//-------------------------------------- Persistent I/O +// +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); + internalFormat = BitmapFormat(fmt); + bytesPerPixel = 1; + switch (internalFormat) { + case Alpha: + case Palettized: + case Luminance: + case Intensity: bytesPerPixel = 1; + break; + case RGB: bytesPerPixel = 3; + break; + case RGBA: bytesPerPixel = 4; + break; + case RGB565: + case RGB5551: bytesPerPixel = 2; + break; + default: + AssertFatal(false, "GBitmap::GBitmap: misunderstood format specifier"); + break; + } + + io_rStream.read(&byteSize); + + pBits = new U8[byteSize]; + io_rStream.read(byteSize, pBits); + + io_rStream.read(&width); + io_rStream.read(&height); + + io_rStream.read(&numMipLevels); + for (U32 i = 0; i < c_maxMipLevels; i++) + io_rStream.read(&mipLevelOffsets[i]); + + if (internalFormat == Palettized) { + pPalette = new GPalette; + pPalette->read(io_rStream); + } + + 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(internalFormat)); + + io_rStream.write(byteSize); + io_rStream.write(byteSize, pBits); + + io_rStream.write(width); + io_rStream.write(height); + + io_rStream.write(numMipLevels); + for (U32 i = 0; i < c_maxMipLevels; i++) + io_rStream.write(mipLevelOffsets[i]); + + if (internalFormat == Palettized) { + AssertFatal(pPalette != NULL, + "GBitmap::write: cannot write a palettized bitmap wo/ a palette"); + pPalette->write(io_rStream); + } + + return (io_rStream.getStatus() == Stream::Ok); +} + + +//-------------------------------------- GFXBitmap +ResourceInstance* constructBitmapJPEG(Stream &stream) +{ + GBitmap* bmp = new GBitmap; + if (bmp->readJPEG(stream)) + return bmp; + else + { + delete bmp; + return NULL; + } +} + +ResourceInstance* constructBitmapPNG(Stream &stream) +{ + GBitmap* bmp = new GBitmap; + if (bmp->readPNG(stream)) + return bmp; + else + { + delete bmp; + return NULL; + } +} + +ResourceInstance* constructBitmapBM8(Stream &stream) +{ + GBitmap* bmp = new GBitmap; + if (bmp->readBmp8(stream)) + return bmp; + else + { + delete bmp; + return NULL; + } +} + +ResourceInstance* constructBitmapBMP(Stream &stream) +{ + GBitmap *bmp = new GBitmap; + if(bmp->readMSBmp(stream)) + return bmp; + else + { + delete bmp; + return NULL; + } +} + +ResourceInstance* constructBitmapGIF(Stream &stream) +{ + GBitmap *bmp = new GBitmap; + if(bmp->readGIF(stream)) + return bmp; + else + { + delete bmp; + return NULL; + } +} +ResourceInstance* constructBitmapDBM(Stream &stream) +{ + GBitmap* bmp = new GBitmap; + if (bmp->read(stream)) + return bmp; + else + { + delete bmp; + return NULL; + } +} + diff --git a/dgl/gBitmap.h b/dgl/gBitmap.h new file mode 100644 index 0000000..73673e7 --- /dev/null +++ b/dgl/gBitmap.h @@ -0,0 +1,221 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GBITMAP_H_ +#define _GBITMAP_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _RESMANAGER_H_ +#include "Core/resManager.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +//-------------------------------------- Forward decls. +class Stream; +class GPalette; + +extern ResourceInstance* constructBitmapBM8(Stream& stream); +extern ResourceInstance* constructBitmapBMP(Stream& stream); +extern ResourceInstance* constructBitmapPNG(Stream& stream); +extern ResourceInstance* constructBitmapJPEG(Stream& stream); +extern ResourceInstance* constructBitmapGIF(Stream& stream); +extern ResourceInstance* constructBitmapDBM(Stream& stream); + + +//------------------------------------------------------------------------------ +//-------------------------------------- GBitmap +// +class GBitmap: public ResourceInstance +{ + //-------------------------------------- public enumerants and structures + public: + enum BitmapFormat { // BitmapFormat and UsageHint are + Palettized = 0, // written to the stream in write(...), + Intensity = 1, // be sure to maintain compatability + RGB = 2, // if they are changed... + RGBA = 3, + Alpha = 4, + RGB565 = 5, + RGB5551 = 6, + Luminance = 7 + }; + + enum Constants { + c_maxMipLevels = 10 + }; + + public: + GBitmap(); + GBitmap(const GBitmap&); + GBitmap(const U32 in_width, + const U32 in_height, + const bool in_extrudeMipLevels = false, + const BitmapFormat in_format = RGB); + virtual ~GBitmap(); + + void allocateBitmap(const U32 in_width, + const U32 in_height, + const bool in_extrudeMipLevels = false, + const BitmapFormat in_format = RGB); + + void extrudeMipLevels(bool clearBorders = false); + void extrudeMipLevelsDetail(); + + BitmapFormat getFormat() const; + bool setFormat(BitmapFormat fmt); + U32 getNumMipLevels() const; + U32 getWidth(const U32 in_mipLevel = 0) const; + U32 getHeight(const U32 in_mipLevel = 0) const; + + U8* getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel = U32(0)); + const U8* getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel = U32(0)) const; + + const U8* getBits(const U32 in_mipLevel = 0) const; + U8* getWritableBits(const U32 in_mipLevel = 0); + + bool getColor(const U32 x, const U32 y, ColorI& rColor) const; + bool setColor(const U32 x, const U32 y, ColorI& rColor); + + // Note that on set palette, the bitmap delete's its palette. + GPalette const* getPalette() const; + void setPalette(GPalette* in_pPalette); + + //-------------------------------------- Internal data/operators + static U32 sBitmapIdSource; + + void deleteImage(); + + BitmapFormat internalFormat; + public: + + U8* pBits; // Master bytes + U32 byteSize; + U32 width; // Top level w/h + U32 height; + U32 bytesPerPixel; + + U32 numMipLevels; + U32 mipLevelOffsets[c_maxMipLevels]; + + GPalette* pPalette; // Note that this palette pointer is ALWAYS + // owned by the bitmap, and will be + // deleted on exit, or written out on a + // write. + + //-------------------------------------- Input/Output interface + public: + bool readJPEG(Stream& io_rStream); // located in bitmapJpeg.cc + bool writeJPEG(Stream& io_rStream) const; + + bool readPNG(Stream& io_rStream); // located in bitmapPng.cc + bool writePNG(Stream& io_rStream, const bool compressHard = false) const; + bool writePNGUncompressed(Stream& io_rStream) const; + + bool readBmp8(Stream& io_rStream); // located in bitmapMS.cc + bool writeBmp8(Stream& io_rStream); // located in bitmapMS.cc + + bool readMSBmp(Stream& io_rStream); // located in bitmapMS.cc + bool writeMSBmp(Stream& io_rStream) const; // located in bitmapMS.cc + + bool readGIF(Stream& io_rStream); // located in bitmapGIF.cc + bool writeGIF(Stream& io_rStream) const; // located in bitmapGIF.cc + + bool read(Stream& io_rStream); + bool write(Stream& io_rStream) const; + + private: + bool _writePNG(Stream& stream, const U32, const U32, const U32) const; + + static const U32 csFileVersion; +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- Inlines +// + +inline GBitmap::BitmapFormat GBitmap::getFormat() const +{ + return internalFormat; +} + +inline U32 GBitmap::getNumMipLevels() const +{ + return numMipLevels; +} + +inline U32 GBitmap::getWidth(const U32 in_mipLevel) const +{ + AssertFatal(in_mipLevel < numMipLevels, + avar("GBitmap::getWidth: mip level out of range: (%d, %d)", + in_mipLevel, numMipLevels)); + + U32 retVal = width >> in_mipLevel; + + return (retVal != 0) ? retVal : 1; +} + +inline U32 GBitmap::getHeight(const U32 in_mipLevel) const +{ + AssertFatal(in_mipLevel < numMipLevels, + avar("Bitmap::getHeight: mip level out of range: (%d, %d)", + in_mipLevel, numMipLevels)); + + U32 retVal = height >> in_mipLevel; + + return (retVal != 0) ? retVal : 1; +} + +inline const GPalette* GBitmap::getPalette() const +{ + AssertFatal(getFormat() == Palettized, + "Error, incorrect internal format to return a palette"); + + return pPalette; +} + +inline const U8* GBitmap::getBits(const U32 in_mipLevel) const +{ + AssertFatal(in_mipLevel < numMipLevels, + avar("GBitmap::getBits: mip level out of range: (%d, %d)", + in_mipLevel, numMipLevels)); + + return &pBits[mipLevelOffsets[in_mipLevel]]; +} + +inline U8* GBitmap::getWritableBits(const U32 in_mipLevel) +{ + AssertFatal(in_mipLevel < numMipLevels, + avar("GBitmap::getWritableBits: mip level out of range: (%d, %d)", + in_mipLevel, numMipLevels)); + + return &pBits[mipLevelOffsets[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) * bytesPerPixel); +} + +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) * bytesPerPixel); +} + + +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 (*bitmapConvertRGB_to_5551)(U8 *src, U32 pixels); +extern void (*bitmapExtrudePaletted)(const void *srcMip, void *mip, U32 height, U32 width); + +void bitmapExtrudeRGB_c(const void *srcMip, void *mip, U32 height, U32 width); + +#endif //_GBITMAP_H_ diff --git a/dgl/gChunkedTexManager.h b/dgl/gChunkedTexManager.h new file mode 100644 index 0000000..66172a3 --- /dev/null +++ b/dgl/gChunkedTexManager.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GCHUNKEDTEXMANAGER_H_ +#define _GCHUNKEDTEXMANAGER_H_ + +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif + +class ChunkedTextureObject +{ +public: + ChunkedTextureObject *next; + StringTableEntry texFileName; + U32 texWidthCount; + U32 texHeightCount; + U32 width; + U32 height; + TextureHandle *textureHandles; + S32 refCount; + GBitmap *bitmap; +}; + +class ChunkedTextureManager +{ + friend class ChunkedTextureHandle; + static ChunkedTextureObject* loadTexture(const char *textureName); + static ChunkedTextureObject* registerTexture(const char *textureName, GBitmap *data, bool keep); + static void freeTexture(ChunkedTextureObject *to); + static void refresh(ChunkedTextureObject *to); +public: + static void makeZombie(); + static void resurrect(); +}; + +class ChunkedTextureHandle +{ + ChunkedTextureObject *object; + void lock(); + void unlock(); +public: + ChunkedTextureHandle() { object = NULL; } + ChunkedTextureHandle(const ChunkedTextureHandle &th) { + object = th.object; + lock(); + } + ChunkedTextureHandle(const char *textureName) + { + object = ChunkedTextureManager::loadTexture(textureName); + lock(); + } + ChunkedTextureHandle(const char *textureName, GBitmap *bmp) + { + object = ChunkedTextureManager::registerTexture(textureName, bmp, true); + lock(); + } + ~ChunkedTextureHandle() { unlock(); } + + ChunkedTextureHandle& operator=(const ChunkedTextureHandle &t) { unlock(); object = t.object; lock(); return *this; } + void refresh() { if(object) ChunkedTextureManager::refresh(object); } + operator ChunkedTextureObject*() { return object; } + const char* getName() const { return (object ? object->texFileName : NULL); } + U32 getWidth() const { return (object ? object->width : 0UL); } + U32 getHeight() const { return (object ? object->height : 0UL); } + GBitmap* getBitmap() { return (object ? object->bitmap : NULL); } + TextureHandle getSubTexture(U32 x, U32 y); + U32 getTextureCountWidth() { return (object ? object->texWidthCount : 0UL); }; + U32 getTextureCountHeight(){ return (object ? object->texHeightCount : 0UL); }; +}; + +#endif diff --git a/dgl/gFont.cc b/dgl/gFont.cc new file mode 100644 index 0000000..2475237 --- /dev/null +++ b/dgl/gFont.cc @@ -0,0 +1,470 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/stream.h" +#include "dgl/gFont.h" +#include "dgl/gBitmap.h" +#include "Core/fileStream.h" +#include "dgl/gTexManager.h" + +S32 GFont::smSheetIdCount = 0; + +ResourceInstance* constructFont(Stream& stream) +{ + GFont *ret = new GFont; + if(!ret->read(stream)) + { + delete ret; + return NULL; + } + + return ret; +} +const U32 GFont::csm_fileVersion = 1; + +Resource GFont::create(const char *faceName, S32 size) +{ + char buf[256]; + dSprintf(buf, sizeof(buf), "fonts/%s_%d.gft", faceName, size); + + Resource ret = ResourceManager->load(buf); + if(bool(ret)) + return ret; + + GFont *resFont = createFont(faceName, size); + if (resFont == NULL) { + AssertISV(dStricmp(faceName, "Arial") != 0, "Error, The Arial Font must always be available!"); + + // Need to handle this case better. For now, let's just return a font that we're + // positive exists, in the correct size... + return create("Arial", size); + } + + FileStream stream; + if(ResourceManager->openFileForWrite(stream, ResourceManager->getBasePath(), buf)) + { + resFont->write(stream); + stream.close(); + } + ResourceManager->add(buf, resFont, false); + return ResourceManager->load(buf); +} + +GFont::GFont() +{ + VECTOR_SET_ASSOCIATION(mCharInfoList); + + for (U32 i = 0; i < 256; i++) + mRemapTable[i] = -1; + + mTextureSheets = NULL; +} + +GFont::~GFont() +{ + delete [] mTextureSheets; + mTextureSheets = NULL; +} + +void GFont::insertBitmap(U16 index, U8 *src, U32 stride, U32 width, U32 height, S32 xOrigin, S32 yOrigin, S32 xIncrement) +{ + CharInfo c; + c.bitmapIndex = -1; + c.xOffset = 0; + c.yOffset = 0; + c.width = width; + c.height = height; + + c.xOrigin = xOrigin; + c.yOrigin = yOrigin; + c.xIncrement = xIncrement; + + c.bitmapData = new U8[c.width * c.height]; + + for(U32 y = 0; S32(y) < c.height; y++) + { + U32 x; + for(x = 0; x < width; x++) + c.bitmapData[y * c.width + x] = src[y * stride + x]; + } + mRemapTable[index] = mCharInfoList.size(); + mCharInfoList.push_back(c); +} + +static S32 QSORT_CALLBACK CharInfoCompare(const void *a, const void *b) +{ + S32 ha = (*((GFont::CharInfo **) a))->height; + S32 hb = (*((GFont::CharInfo **) b))->height; + + return hb - ha; +} + +void GFont::pack(U32 inFontHeight, U32 inBaseLine) +{ + mFontHeight = inFontHeight; + mBaseLine = inBaseLine; + + // pack all the bitmap data into sheets. + Vector vec; + + U32 size = mCharInfoList.size(); + U32 i; + + for(i = 0; i < size; i++) + { + CharInfo *ch = &mCharInfoList[i]; + vec.push_back(ch); + } + + dQsort(vec.address(), size, sizeof(CharInfo *), CharInfoCompare); + // sorted by height + + Vector sheetSizes; + Point2I curSheetSize(256, 256); + + S32 curY = 0; + S32 curX = 0; + S32 curLnHeight = 0; + for(i = 0; i < size; i++) + { + CharInfo *ci = vec[i]; + + if(curX + ci->width > curSheetSize.x) + { + curY += curLnHeight; + curX = 0; + curLnHeight = 0; + } + if(curY + ci->height > curSheetSize.y) + { + sheetSizes.push_back(curSheetSize); + 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; + } + curY += curLnHeight; + + if(curY < 64) + curSheetSize.y = 64; + else if(curY < 128) + curSheetSize.y = 128; + + sheetSizes.push_back(curSheetSize); + + Vector bitmapArray; + + mNumSheets = sheetSizes.size(); + mTextureSheets = new TextureHandle[mNumSheets]; + + for(i = 0; i < mNumSheets; i++) + bitmapArray.push_back(new GBitmap(sheetSizes[i].x, sheetSizes[i].y, false, GBitmap::Alpha)); + + for(i = 0; i < size; i++) + { + CharInfo *ci = vec[i]; + GBitmap *bmp = bitmapArray[ci->bitmapIndex]; + S32 x, y; + for(y = 0; y < ci->height; y++) + for(x = 0; x < ci->width; x++) + *bmp->getAddress(x + ci->xOffset, y + ci->yOffset) = + ci->bitmapData[y * ci->width + x]; + delete[] ci->bitmapData; + } + for(i = 0; i < mNumSheets; i++) + assignSheet(i, bitmapArray[i]); +} + +TextureHandle GFont::getTextureHandle(S32 index) +{ + return mTextureSheets[index]; +} + +void GFont::assignSheet(S32 sheetNum, GBitmap *bmp) +{ + char buf[30]; + dSprintf(buf, sizeof(buf), "font_%d", smSheetIdCount++); + mTextureSheets[sheetNum] = TextureHandle(buf, bmp); +} + +U32 GFont::getStrWidth(const char* in_pString) const +{ + 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 getStrNWidth(in_pString, dStrlen(in_pString)); +} + +U32 GFont::getStrWidthPrecise(const char* in_pString) const +{ + 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 char *str, U32 n) const +{ + AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL"); + + if (str == NULL) + return(0); + + U32 totWidth = 0; + const char *curChar; + const char *endStr; + for (curChar = str, endStr = str + n; curChar < endStr; curChar++) + { + if(isValidChar(*curChar)) + { + const CharInfo& rChar = getCharInfo(*curChar); + totWidth += rChar.xIncrement; + } + else if (*curChar == '\t') + { + const CharInfo& rChar = getCharInfo(' '); + totWidth += rChar.xIncrement * TabWidthInSpaces; + } + } + + return(totWidth); +} + +U32 GFont::getStrNWidthPrecise(const char *str, U32 n) const +{ + AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL"); + + if (str == NULL) + return(0); + + U32 totWidth = 0; + const char *curChar; + const char *endStr; + for (curChar = str, endStr = str + n; curChar < endStr; curChar++) + { + if(isValidChar(*curChar)) + { + const CharInfo& rChar = getCharInfo(*curChar); + totWidth += rChar.xIncrement; + } + else if (*curChar == '\t') + { + const CharInfo& rChar = getCharInfo(' '); + totWidth += rChar.xIncrement * TabWidthInSpaces; + } + } + + if (n != 0) { + // Need to check the last char to see if it has some slop... + char endChar = str[n-1]; + if (isValidChar(endChar)) { + const CharInfo& rChar = getCharInfo(endChar); + if (rChar.width > rChar.xIncrement) + totWidth += (rChar.width - rChar.xIncrement); + } + } + + return(totWidth); +} + +U32 GFont::getBreakPos(const char *string, U32 slen, U32 width, bool breakOnWhitespace) +{ + U32 ret = 0; + U32 lastws = 0; + while(ret < slen) + { + char c = string[ret]; + if(c == '\t') + c = ' '; + if(!isValidChar(c)) + { + ret++; + continue; + } + if(c == ' ') + lastws = ret+1; + const 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 char *txt, U32 lineWidth, Vector &startLineOffset, Vector &lineLen) +{ + 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(isValidChar(txt[i])) + { + lineStrWidth += getCharInfo(txt[i]).xIncrement; + if ( txt[i] == '\n' || lineStrWidth > lineWidth ) + { + needsNewLine = true; + break; + } + } + } + + if (!needsNewLine) + { + // we are done! + lineLen.push_back(i - startLine); + return; + } + + // now determine where to put the newline + // else we need to backtrack until we find a either space character + // or \\ character to break up the line. + S32 j; + for (j = i - 1; j >= startLine; j--) + { + if (dIsspace(txt[j])) + 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 + for (i++; i < len; i++) + { + if (!dIsspace(txt[i]) || txt[i] == '\n') + break; + } + } +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Persist functionality +// +static const U32 csm_fileVersion = 1; + +bool GFont::read(Stream& io_rStream) +{ + // Handle versioning + U32 version; + io_rStream.read(&version); + if(version != csm_fileVersion) + return false; + + // Read Font Information + io_rStream.read(&mFontHeight); + io_rStream.read(&mBaseLine); + + U32 size = 0; + io_rStream.read(&size); + mCharInfoList.setSize(size); + U32 i; + for(i = 0; i < size; i++) + { + 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); + } + io_rStream.read(&mNumSheets); + + mTextureSheets = new TextureHandle[mNumSheets]; + for(i = 0; i < mNumSheets; i++) + { + GBitmap *bmp = new GBitmap; + if(!bmp->readPNG(io_rStream)) + { + delete bmp; + return false; + } + assignSheet(i, bmp); + } + + // Read character remap table + for(i = 0; i < 256; i++) + io_rStream.read(&mRemapTable[i]); + + return (io_rStream.getStatus() == Stream::Ok); +} + +bool +GFont::write(Stream& stream) const +{ + // Handle versioning + stream.write(csm_fileVersion); + + // Write Font Information + stream.write(mFontHeight); + stream.write(mBaseLine); + + stream.write(U32(mCharInfoList.size())); + U32 i; + for(i = 0; i < mCharInfoList.size(); i++) + { + const 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(mNumSheets); + for(i = 0; i < mNumSheets; i++) + mTextureSheets[i].getBitmap()->writePNG(stream); + + for(i = 0; i < 256; i++) + stream.write(mRemapTable[i]); + + return (stream.getStatus() == Stream::Ok); +} diff --git a/dgl/gFont.h b/dgl/gFont.h new file mode 100644 index 0000000..2bfa54d --- /dev/null +++ b/dgl/gFont.h @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFONT_H_ +#define _GFONT_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _GBITMAP_H_ +#include "dgl/gBitmap.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _MRECT_H_ +#include "Math/mRect.h" +#endif +#ifndef _RESMANAGER_H_ +#include "Core/resManager.h" +#endif + +extern ResourceInstance* constructFont(Stream& stream); + +class TextureHandle; + +class GFont : public ResourceInstance +{ + static const U32 csm_fileVersion; + static S32 smSheetIdCount; + + // Enumerations and structs available to everyone... +public: + // A justification consists of a horizontal type | a vertical type. + // Note that a justification of 0 evalutes to left/top, the default. + // The robustness of the rendering functions should be considered + // suspect for a while, especially the justified versions... + // + struct CharInfo { + S16 bitmapIndex; // Note: -1 indicates character is NOT to be + // rendered, i.e., \n, \r, etc. + U8 xOffset; // x offset into bitmap sheet + U8 yOffset; // y offset into bitmap sheet + U8 width; // width of character (pixels) + U8 height; // height of character (pixels) + S8 xOrigin; + S8 yOrigin; + S8 xIncrement; + U8 *bitmapData; // temp storage for bitmap data + }; + enum Constants { + TabWidthInSpaces = 3 + }; + + + // Enumerations and structures available to derived classes +private: + U32 mNumSheets; + TextureHandle *mTextureSheets; + + U32 mFontHeight; // ascent + descent of the font + U32 mBaseLine; // ascent of the font (pixels above the baseline of any character in the font) + + Vector mCharInfoList; // - List of character info structures, must + // be accessed through the getCharInfo(U32) + // function to account for remapping... + S16 mRemapTable[256]; // - Index remapping + + S16 getActualIndex(const U8 in_charIndex) const; + void assignSheet(S32 sheetNum, GBitmap *bmp); + +public: + GFont(); + virtual ~GFont(); + + // Queries about this font +public: + TextureHandle getTextureHandle(S32 index); + U32 getCharHeight(const U8 in_charIndex) const; + U32 getCharWidth(const U8 in_charIndex) const; + U32 getCharXIncrement(const U8 in_charIndex) const; + + bool isValidChar(const U8 in_charIndex) const; + const CharInfo& getCharInfo(const U8 in_charIndex) const; + + + // Rendering assistance functions... +public: + U32 getBreakPos(const char *string, U32 strlen, U32 width, bool breakOnWhitespace); + + U32 getStrWidth(const char*) const; // Note: ignores c/r + U32 getStrNWidth(const char*, U32 n) const; + U32 getStrWidthPrecise(const char*) const; // Note: ignores c/r + U32 getStrNWidthPrecise(const char*, U32 n) const; + void wrapString(const char *string, U32 width, Vector &startLineOffset, Vector &lineLen); + + bool read(Stream& io_rStream); + bool write(Stream& io_rStream) const; + + U32 getHeight() { return mFontHeight; } + U32 getBaseline() { return mBaseLine; } + U32 getAscent() { return mBaseLine; } + U32 getDescent() { return mFontHeight - mBaseLine; } + + void insertBitmap(U16 index, U8 *src, U32 stride, U32 width, U32 height, S32 xOrigin, S32 yOrigin, S32 xIncrement); + void pack(U32 fontHeight, U32 baseLine); + + static Resource create(const char *face, S32 size); +}; + +inline bool GFont::isValidChar(const U8 in_charIndex) const +{ + return mRemapTable[in_charIndex] != -1; +} + +inline S16 GFont::getActualIndex(const U8 in_charIndex) const +{ + AssertFatal(isValidChar(in_charIndex) == true, + avar("GFont::getActualIndex: invalid character: 0x%x", + in_charIndex)); + + return mRemapTable[in_charIndex]; +} + +inline const GFont::CharInfo& GFont::getCharInfo(const U8 in_charIndex) const +{ + S16 remap = getActualIndex(in_charIndex); + AssertFatal(remap != -1, "No remap info for this character"); + + return mCharInfoList[remap]; +} + +inline U32 GFont::getCharXIncrement(const U8 in_charIndex) const +{ + const CharInfo& rChar = getCharInfo(in_charIndex); + return rChar.xIncrement; +} + +inline U32 GFont::getCharWidth(const U8 in_charIndex) const +{ + const CharInfo& rChar = getCharInfo(in_charIndex); + return rChar.width; +} + +inline U32 GFont::getCharHeight(const U8 in_charIndex) const +{ + const CharInfo& rChar = getCharInfo(in_charIndex); + return rChar.height; +} + +#endif //_GFONT_H_ diff --git a/dgl/gPalette.cc b/dgl/gPalette.cc new file mode 100644 index 0000000..ab8e2e9 --- /dev/null +++ b/dgl/gPalette.cc @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/stream.h" +#include "Core/fileStream.h" +#include "dgl/gPalette.h" + +const U32 GPalette::csm_fileVersion = 1; + +GPalette::GPalette() +: m_paletteType(RGB) +{ + // +} + +GPalette::~GPalette() +{ + // +} + +//-------------------------------------- Supplimentary I/O +bool +GPalette::readMSPalette(const char* in_pFileName) +{ + AssertFatal(in_pFileName != NULL, "GPalette::readMSPalette: NULL FileName"); + + FileStream frs; + if (frs.open(in_pFileName, FileStream::Read) == false) { + return false; + } else { + bool success = readMSPalette(frs); + frs.close(); + + return success; + } +} + +bool +GPalette::writeMSPalette(const char* in_pFileName) const +{ + AssertFatal(in_pFileName != NULL, "GPalette::writeMSPalette: NULL FileName"); + + FileStream fws; + if (fws.open(in_pFileName, FileStream::Write) == false) { + return false; + } else { + bool success = writeMSPalette(fws); + fws.close(); + + return success; + } +} + +bool +GPalette::readMSPalette(Stream& io_rStream) +{ + AssertFatal(io_rStream.getStatus() != Stream::Closed, + "GPalette::writeMSPalette: can't write to a closed stream!"); + + U32 data; + U32 size; + + io_rStream.read(&data); + io_rStream.read(&size); + + if (data == makeFourCCTag('R', 'I', 'F', 'F')) { + io_rStream.read(&data); + io_rStream.read(&size); + } + + if (data == makeFourCCTag('P', 'A', 'L', ' ')) { + io_rStream.read(&data); // get number of colors (ignored) + io_rStream.read(&data); // skip the version number. + + // Read the colors... + io_rStream.read(256 * sizeof(ColorI), m_pColors); + + // With MS Pals, we assume that the type is RGB, clear out all the alpha + // members so the palette keys are consistent across multiple palettes + // + for (U32 i = 0; i < 256; i++) + m_pColors[i].alpha = 0; + + m_paletteType = RGB; + + return (io_rStream.getStatus() == Stream::Ok); + } + + AssertWarn(false, "GPalette::readMSPalette: not a MS Palette"); + return false; +} + +bool +GPalette::writeMSPalette(Stream& io_rStream) const +{ + AssertFatal(io_rStream.getStatus() != Stream::Closed, + "GPalette::writeMSPalette: can't write to a closed stream!"); + + io_rStream.write(U32(makeFourCCTag('R', 'I', 'F', 'F'))); + io_rStream.write(U32((256 * sizeof(ColorI)) + 8 + 4 + 4)); + + io_rStream.write(U32(makeFourCCTag('P', 'A', 'L', ' '))); + io_rStream.write(U32(makeFourCCTag('d', 'a', 't', 'a'))); + + io_rStream.write(U32(0x0404)); // Number of colors + 4 + + io_rStream.write(U16(0x300)); // version + io_rStream.write(U16(256)); // num colors... + + io_rStream.write(256 * sizeof(ColorI), m_pColors); + + return (io_rStream.getStatus() == Stream::Ok); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Persistent I/O +// +bool +GPalette::read(Stream& io_rStream) +{ + // Handle versioning + U32 version; + io_rStream.read(&version); + AssertFatal(version == csm_fileVersion, "Palette::read: wrong file version..."); + + U32 type; + io_rStream.read(&type); + m_paletteType = PaletteType(type); + io_rStream.read(256 * sizeof(ColorI), m_pColors); + + return (io_rStream.getStatus() == Stream::Ok); +} + +bool +GPalette::write(Stream& io_rStream) const +{ + // Handle versioning... + io_rStream.write(csm_fileVersion); + + io_rStream.write(U32(m_paletteType)); + io_rStream.write(256 * sizeof(ColorI), m_pColors); + + return (io_rStream.getStatus() == Stream::Ok); +} diff --git a/dgl/gPalette.h b/dgl/gPalette.h new file mode 100644 index 0000000..24d66c2 --- /dev/null +++ b/dgl/gPalette.h @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GPALETTE_H_ +#define _GPALETTE_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +//-------------------------------------- Forward decls. +class Stream; + +//------------------------------------------------------------------------------ +//-------------------------------------- GPalette +// +class GPalette +{ + public: + enum PaletteType { + RGB, + RGBA + }; + + protected: + PaletteType m_paletteType; + ColorI m_pColors[256]; + + public: + GPalette(); + virtual ~GPalette(); + + PaletteType getPaletteType() const; + void setPaletteType(const PaletteType pt) { m_paletteType = pt; } + + const ColorI* getColors() const; + ColorI* getColors(); + const ColorI& getColor(const U32 in_index) const; + ColorI& getColor(const U32 in_index); + + //-------------------------------------- Supplimentary output members + public: + bool readMSPalette(Stream& io_rStream); + bool readMSPalette(const char* in_pFileName); + bool writeMSPalette(Stream& io_rStream) const; + bool writeMSPalette(const char* in_pFileName) const; + + //-------------------------------------- Persistent members + public: + bool read(Stream& io_rStream); + bool write(Stream& io_rStream) const; + private: + static const U32 csm_fileVersion; +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- Inlines (Trust) +// +inline GPalette::PaletteType +GPalette::getPaletteType() const +{ + return m_paletteType; +} + +inline const ColorI* +GPalette::getColors() const +{ + return m_pColors; +} + +inline ColorI* +GPalette::getColors() +{ + return m_pColors; +} + +inline const ColorI& +GPalette::getColor(const U32 in_index) const +{ + AssertFatal(in_index < 256, "Out of range index"); + + return m_pColors[in_index]; +} + +inline ColorI& +GPalette::getColor(const U32 in_index) +{ + AssertFatal(in_index < 256, "Out of range index"); + + return m_pColors[in_index]; +} + +#endif //_GPALETTE_H_ diff --git a/dgl/gTexManager.cc b/dgl/gTexManager.cc new file mode 100644 index 0000000..2cf7db5 --- /dev/null +++ b/dgl/gTexManager.cc @@ -0,0 +1,1468 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platformAssert.h" +#include "platformWIN32/platformGL.h" +#include "platform/platform.h" +#include "core/tVector.h" +#include "core/resManager.h" +#include "dgl/gBitmap.h" +#include "dgl/gPalette.h" +#include "dgl/gTexManager.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/consoleTypes.h" +#include "dgl/gChunkedTexManager.h" + +//------------------------------------------------------------------------------ + +bool gDGLRender = true; + +bool sgResurrect = false; +bool sgForcePalettedTexture = false; +bool sgForce16BitTexture = false; + + +#define ENABLE_HOLDING 1 + +#ifdef GATHER_METRICS +U32 TextureManager::smTextureSpaceLoaded = 0; +U32 TextureManager::smTextureCacheMisses = 0; +#endif + +bool TextureManager::smUseSmallTextures = false; + +bool TextureManager::smIsZombie = false; + +//-------------------------------------------------------------------------- +//-------------------------------------- Texture detailing control variables +// 0: Highest +// 1: ... +// 2: ... +// 3: Lowest + +namespace { + +struct Forced16BitMapping +{ + GLenum wanted; + GLenum forced; + bool end; +}; + +Forced16BitMapping sg16BitMappings[] = +{ + { GL_RGB, GL_RGB5, false }, + { GL_RGBA, GL_RGBA4, false }, + { 0, 0, true } +}; + + +U32 sgTextureDetailLevel = 0; +U32 sgSkyTextureDetailLevel = 0; +U32 sgInteriorTextureDetailLevel = 0; +bool sgAllowTexCompression = false; +GLenum sgCompressionHint = GL_FASTEST; +F32 sgTextureAnisotropy = 0.0; +bool sgDisableSubImage = false; +// valid texture extensions +#define EXT_ARRAY_SIZE 6 +static const char* extArray[EXT_ARRAY_SIZE] = { "", ".jpg", ".png", ".gif", ".bmp", "" }; +static const char* extArray_8[EXT_ARRAY_SIZE] = { "", ".bm8", ".bmp", ".jpg", ".png", ".gif" }; + +ConsoleFunction(setOpenGLMipReduction, void, 2, 2, "setOpenGLMipReduction(0-5);") +{ + argc; + S32 val = dAtoi(argv[1]); + if (val < 0) + val = 0; + else if (val > 5) + val = 5; + + sgTextureDetailLevel = val; +} + +ConsoleFunction(setOpenGLSkyMipReduction, void, 2, 2, "setOpenGLSkyMipReduction(0-5);") +{ + argc; + S32 val = dAtoi(argv[1]); + if (val < 0) + val = 0; + else if (val > 5) + val = 5; + + sgSkyTextureDetailLevel = val; +} + +ConsoleFunction(setOpenGLInteriorMipReduction, void, 2, 2, "setOpenGLInteriorMipReduction(0-5);") +{ + argc; + S32 val = dAtoi(argv[1]); + if (val < 0) + val = 0; + else if (val > 5) + val = 5; + + sgInteriorTextureDetailLevel = val; +} + +ConsoleFunction(setOpenGLTextureCompressionHint, void, 2, 2, "setTextureCompressionHint(GL_DONT_CARE|GL_FASTEST|GL_NICEST);") +{ + argc; + + GLenum newHint = GL_DONT_CARE; + const char* newString = "GL_DONT_CARE"; + + if (dStricmp(argv[1], "GL_FASTEST") == 0) { + newHint = GL_FASTEST; + newString = "GL_FASTEST"; + } else if (dStricmp(argv[1], "GL_NICEST") == 0) { + GLenum newHint = GL_NICEST; + newString = "GL_NICEST"; + } + + sgCompressionHint = newHint; + + if (dglDoesSupportTextureCompression()) + glHint(GL_TEXTURE_COMPRESSION_HINT_ARB, sgCompressionHint); +} + +ConsoleFunction(setOpenGLAnisotropy, void, 2, 2, "setOpenGLAnisotropy(0-1);") +{ + argc; + F32 val = dAtof(argv[1]); + if (val < 0.0) + val = 0.0; + if (val > 1.0) + val = 1.0; + sgTextureAnisotropy = val; +} + +} // namespace {} + + +//-------------------------------------- +struct TextureDictionary +{ + static TextureObject **smTable; + static TextureObject *smTOList; + static U32 smHashTableSize; + + static void create(); + static void preDestroy(); + static void destroy(); + + static void insert(TextureObject *object); + static TextureObject *find(StringTableEntry name, TextureHandleType type, bool clamp); + static void remove(TextureObject *object); + static S32 clearHolds(); +}; + +TextureObject **TextureDictionary::smTable = NULL; +TextureObject *TextureDictionary::smTOList = NULL; +U32 TextureDictionary::smHashTableSize = 0; + +//-------------------------------------- +void TextureDictionary::create() +{ + smTOList = NULL; + smHashTableSize = 1023; + smTable = new TextureObject *[smHashTableSize]; + for(U32 i = 0; i < smHashTableSize; i++) + smTable[i] = NULL; + + //Con::addVariable("$pref::OpenGL::mipReduction", texDetailLevelCB, "0"); + //Con::addVariable("$pref::OpenGL::anisotropy", anisotropyCB, "0"); + + Con::addVariable("$pref::OpenGL::force16BitTexture", TypeBool, &sgForce16BitTexture); + Con::addVariable("$pref::OpenGL::forcePalettedTexture", TypeBool, &sgForcePalettedTexture); + Con::addVariable("$pref::OpenGL::allowCompression", TypeBool, &sgAllowTexCompression); + Con::addVariable("$pref::OpenGL::disableSubImage", TypeBool, &sgDisableSubImage); +} + + +//-------------------------------------- +TextureObject *TextureDictionary::find(StringTableEntry name, TextureHandleType type, bool clamp) +{ + U32 key = HashPointer(name) % smHashTableSize; + TextureObject *walk = smTable[key]; + for(; walk; walk = walk->hashNext) + if(walk->texFileName == name && walk->type == type && walk->clamp == clamp) + break; + return walk; +} + + +//-------------------------------------- +void TextureDictionary::remove(TextureObject *object) +{ + if(object->next) + object->next->prev = object->prev; + + if(object->prev) + object->prev->next = object->next; + else + smTOList = object->next; + + if(!object->texFileName) + return; + + U32 key = HashPointer(object->texFileName) % smHashTableSize; + TextureObject **walk = &smTable[key]; + while(*walk) + { + if(*walk == object) + { + *walk = object->hashNext; + break; + } + walk = &((*walk)->hashNext); + } +} + + +//-------------------------------------- +void TextureDictionary::insert(TextureObject *object) +{ + object->next = smTOList; + object->prev = NULL; + if(smTOList) + smTOList->prev = object; + smTOList = object; + + if(object->texFileName) + { + U32 key = HashPointer(object->texFileName) % smHashTableSize; + + object->hashNext = smTable[key]; + smTable[key] = object; + } +} + +//-------------------------------------- +void TextureDictionary::preDestroy() +{ + // This is a horrid hack, but it will have to do for now. (DMM, aided + // and abetted by MF.) + TextureObject* walk = smTOList; + while (walk) + { + if((gDGLRender || sgResurrect) && walk->texGLName) + glDeleteTextures(1, (const GLuint*)&walk->texGLName); + if((gDGLRender || sgResurrect) && walk->smallTexGLName) + glDeleteTextures(1, (const GLuint*)&walk->smallTexGLName); + delete walk->bitmap; + walk->texGLName = 0; + walk->smallTexGLName = 0; + walk->bitmap = NULL; + + walk = walk->next; + } +} + +//-------------------------------------- +void TextureDictionary::destroy() +{ + // This is a horrid hack, but it will have to do for now. (DMM, aided + // and abetted by MF.) + while(smTOList) + TextureManager::freeTexture(smTOList); + delete[] smTable; +} + +//-------------------------------------- +S32 TextureDictionary::clearHolds() +{ + Vector holds; + + // Find held textures to delete. Clear holding flag too so they're free + // to go away. + for (TextureObject * walk = smTOList; walk; walk = walk->next) + if (walk->holding) + if (!walk->refCount) + holds.push_back(walk); + else + walk->holding = false; + + // Remove them- + for (S32 i = 0; i < holds.size(); i++) + TextureManager::freeTexture(holds[i]); + + return holds.size(); +} + +namespace { +ConsoleFunction(clearTextureHolds, S32, 1, 1, "clearTextureHolds();") +{ + argc; argv; + return TextureDictionary::clearHolds(); +} +}//namespace + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +struct EventCallbackEntry +{ + TextureEventCallback callback; + U32 userData; + U32 key; +}; +static U32 sgCurrCallbackKey = 0; +static Vector sgEventCallbacks(__FILE__, __LINE__); + +U32 TextureManager::registerEventCallback(TextureEventCallback callback, const U32 userData) +{ + sgEventCallbacks.increment(); + sgEventCallbacks.last().callback = callback; + sgEventCallbacks.last().userData = userData; + sgEventCallbacks.last().key = sgCurrCallbackKey++; + + return sgEventCallbacks.last().key; +} + +void TextureManager::unregisterEventCallback(const U32 callbackKey) +{ + for (U32 i = 0; i < sgEventCallbacks.size(); i++) + if (sgEventCallbacks[i].key == callbackKey) { + sgEventCallbacks.erase(i); + return; + } +} + +void TextureManager::postTextureEvent(const U32 eventCode) +{ + for (U32 i = 0; i < sgEventCallbacks.size(); i++) + (sgEventCallbacks[i].callback)(eventCode, sgEventCallbacks[i].userData); +} + + +void TextureManager::create() +{ + TextureDictionary::create(); +} + +void TextureManager::preDestroy() +{ + TextureDictionary::preDestroy(); +} + +void TextureManager::destroy() +{ + TextureDictionary::destroy(); + + AssertFatal(sgEventCallbacks.size() == 0, + "Error, some object didn't unregister it's texture event callback function!"); +} + + +//-------------------------------------- +void TextureManager::makeZombie() +{ + if (smIsZombie == true) + return; + smIsZombie = true; + + postTextureEvent(BeginZombification); + ChunkedTextureManager::makeZombie(); + // Publish flush event? + + Vector deleteNames(4096); + + TextureObject* probe = TextureDictionary::smTOList; + while (probe) { + AssertFatal(probe->type != TerrainTexture, "Error, all the terrain textureobjects should be gone by now!"); + if (probe->type == BitmapNoDownloadTexture) + { + probe = probe->next; + continue; + } + if (probe->texGLName != 0) + deleteNames.push_back(probe->texGLName); + if (probe->smallTexGLName != 0) + deleteNames.push_back(probe->smallTexGLName); + +#ifdef GATHER_METRICS + AssertFatal(probe->textureSpace <= smTextureSpaceLoaded, "Error, that shouldn't happen!"); + smTextureSpaceLoaded -= probe->textureSpace; + probe->textureSpace = 0; +#endif + + probe->texGLName = 0; + probe->smallTexGLName = 0; + + probe = probe->next; + } + + glDeleteTextures(deleteNames.size(), deleteNames.address()); +} + +void TextureManager::resurrect() +{ + if (smIsZombie == false) + return; + smIsZombie = false; + + sgResurrect = true; + + TextureObject* probe = TextureDictionary::smTOList; + + while (probe) { + // reload texture... + AssertFatal(probe->type != TerrainTexture, "Error, all the terrain textureobjects should be gone by now!"); + if (probe->type == BitmapNoDownloadTexture) + { + probe = probe->next; + continue; + } + if (probe->bitmap != NULL) { + if(probe->type == BitmapKeepTexture) + { + delete probe->bitmap; + probe->bitmap = NULL; + } + else + { + if (probe->type == RegisteredTexture) { + createGLName(probe->bitmap, probe->clamp, 0, probe->type, probe); + } else { + TextureObject* refreshed = registerTexture(probe->texFileName, probe->bitmap, + probe->type, probe->clamp); + AssertFatal(refreshed == probe, "Error, new texture object returned. This should not happen in resurrect"); + } + probe = probe->next; + continue; + } + } + + // Ok, what we have here is the object, with the right name, we need to load the + // bitmap, and register the texture + GBitmap *bmp = NULL; + for (U32 i = 0; i < EXT_ARRAY_SIZE && bmp == NULL; i++) { + char extendedBuffer[260]; + dStrcpy(extendedBuffer, probe->texFileName); + + if (sgForcePalettedTexture == true && dglDoesSupportPalettedTexture()) + dStrcat(extendedBuffer, extArray_8[i]); + else + dStrcat(extendedBuffer, extArray[i]); + + bmp = (GBitmap*)ResourceManager->loadInstance(extendedBuffer); + } + AssertISV(bmp != NULL, "Error resurrecting the texture cache.\n" + "Possible cause: a bitmap was deleted during the course of gameplay."); + + TextureObject* refreshed = registerTexture(probe->texFileName, bmp, + probe->type, probe->clamp); + AssertFatal(refreshed == probe, "Error, new texture object returned. This should not happen in resurrect"); + + probe = probe->next; + } + + ChunkedTextureManager::resurrect(); + postTextureEvent(CacheResurrected); + + sgResurrect = false; +} + +void TextureManager::flush() +{ + makeZombie(); + resurrect(); +} + + +#ifdef GATHER_METRICS +void TextureManager::dumpStats() +{ + TextureObject* probe = TextureDictionary::smTOList; + + Con::errorf("aaa Texture dump"); + while (probe) + { + Con::errorf("aaa %d: (%d, %s) %d (%s)", probe->type, probe->refCount, probe->holding ? "yes" : "no", probe->textureSpace, probe->texFileName ? probe->texFileName : "nil"); + probe = probe->next; + } +} +#endif + + +//------------------------------------------------------------------------------ +GBitmap* TextureManager::createPaddedBitmap(GBitmap* pBitmap) +{ + if (isPow2(pBitmap->getWidth()) && isPow2(pBitmap->getHeight())) + return pBitmap; + + AssertFatal(pBitmap->getNumMipLevels() == 1, + "Cannot have non-pow2 bitmap with miplevels"); + + U32 newWidth = getNextPow2(pBitmap->getWidth()); + U32 newHeight = getNextPow2(pBitmap->getHeight()); + + GBitmap* pReturn = new GBitmap(newWidth, newHeight, false, pBitmap->getFormat()); + + for (U32 i = 0; i < pBitmap->getHeight(); i++) { + U8* pDest = (U8*)pReturn->getAddress(0, i); + const U8* pSrc = (const U8*)pBitmap->getAddress(0, i); + + dMemcpy(pDest, pSrc, pBitmap->getWidth() * pBitmap->bytesPerPixel); + } + if (pBitmap->getFormat() == GBitmap::Palettized) + { + pReturn->pPalette = new GPalette; + dMemcpy(pReturn->pPalette->getColors(), pBitmap->pPalette->getColors(), sizeof(ColorI) * 256); + pReturn->pPalette->setPaletteType(pBitmap->pPalette->getPaletteType()); + } + return pReturn; +} + + +//------------------------------------------------------------------------------ +GBitmap* TextureManager::createMipBitmap(const GBitmap* pBitmap) +{ + AssertFatal(pBitmap != NULL, "Error, no bitmap"); + AssertFatal(pBitmap->getNumMipLevels() != 1, "Error, no mips to maintain"); + + GBitmap* pRetBitmap = new GBitmap(pBitmap->getWidth(1), + pBitmap->getHeight(1), + true, + pBitmap->getFormat()); + + for (U32 i = 1; i < pBitmap->getNumMipLevels(); i++) { + void* pDest = pRetBitmap->getWritableBits(i - 1); + const void* pSrc = pBitmap->getBits(i); + + dMemcpy(pDest, pSrc, (pBitmap->getWidth(i) * + pBitmap->getHeight(i) * + pBitmap->bytesPerPixel)); + } + + return pRetBitmap; +} + + +//------------------------------------------------------------------------------ +void TextureManager::freeTexture(TextureObject *to) +{ +#ifdef GATHER_METRICS + AssertFatal(to->textureSpace <= smTextureSpaceLoaded, "Error, that shouldn't happen!"); + smTextureSpaceLoaded -= to->textureSpace; +#endif + + if((gDGLRender || sgResurrect) && to->texGLName) + glDeleteTextures(1, (const GLuint*)&to->texGLName); + if((gDGLRender || sgResurrect) && to->smallTexGLName) + glDeleteTextures(1, (const GLuint*)&to->smallTexGLName); + + delete to->bitmap; + TextureDictionary::remove(to); + delete to; +} + + +//------------------------------------------------------------------------------ +static void getSourceDestByteFormat(GBitmap *pBitmap, U32 *sourceFormat, U32 *destFormat, U32 *byteFormat) +{ + *byteFormat = GL_UNSIGNED_BYTE; + + switch(pBitmap->getFormat()) { + case GBitmap::Intensity: + *sourceFormat = GL_INTENSITY; + break; + + case GBitmap::Palettized: + *sourceFormat = GL_COLOR_INDEX; + break; + + case GBitmap::Luminance: + *sourceFormat = GL_LUMINANCE; + break; + case GBitmap::RGB: + *sourceFormat = GL_RGB; + break; + case GBitmap::RGBA: + *sourceFormat = GL_RGBA; + break; + case GBitmap::Alpha: + *sourceFormat = GL_ALPHA; + break; + case GBitmap::RGB565: + case GBitmap::RGB5551: + *sourceFormat = GL_RGBA; + *byteFormat = 0x8034; + break; + }; + + if(*byteFormat == GL_UNSIGNED_BYTE) + { + if (*sourceFormat != GL_COLOR_INDEX) + *destFormat = *sourceFormat; + else + *destFormat = GL_COLOR_INDEX8_EXT; + + if (pBitmap->getNumMipLevels() > 1 && + pBitmap->getFormat() != GBitmap::Palettized && + (sgAllowTexCompression && dglDoesSupportTextureCompression())) + { + if (*sourceFormat == GL_RGB) + *destFormat = GL_COMPRESSED_RGB_ARB; + else if (*sourceFormat == GL_RGBA) + *destFormat = GL_COMPRESSED_RGBA_ARB; + } + } else + { + *destFormat = GL_RGB5_A1; + } + + if (sgForce16BitTexture) + { + for (U32 i = 0; sg16BitMappings[i].end != true; i++) + { + if (*destFormat == sg16BitMappings[i].wanted) + { + *destFormat = sg16BitMappings[i].forced; + return; + } + } + } +} + + +//-------------------------------------- +void TextureManager::refresh(TextureObject *to) +{ + if (!(gDGLRender || sgResurrect)) + return; + + U32 sourceFormat, destFormat, byteFormat; + GBitmap *pBitmap = to->bitmap; + + getSourceDestByteFormat(pBitmap, &sourceFormat, &destFormat, &byteFormat); + + if (!to->texGLName) + glGenTextures(1,&to->texGLName); + + glBindTexture(GL_TEXTURE_2D, to->texGLName); + GBitmap *pDL = createPaddedBitmap(pBitmap); + + U32 maxDownloadMip = pDL->getNumMipLevels(); + if (to->type == BitmapTexture || + to->type == BitmapKeepTexture || + to->type == BitmapNoDownloadTexture) + { + maxDownloadMip = 1; + } + + if (pDL->getFormat() == GBitmap::Palettized) + { + glColorTableEXT(GL_TEXTURE_2D, + pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA, + 256, + GL_RGBA, + GL_UNSIGNED_BYTE, + pDL->getPalette()->getColors()); + } + if (sgDisableSubImage) + { + for (U32 i = 0; i < maxDownloadMip; i++) + { + glTexImage2D(GL_TEXTURE_2D, + i, + destFormat, + pDL->getWidth(i), pDL->getHeight(i), + 0, + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + } + else + { + for (U32 i = 0; i < maxDownloadMip; i++) + { + glTexSubImage2D(GL_TEXTURE_2D, + i, + 0, 0, + pDL->getWidth(i), pDL->getHeight(i), + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + } + + if ((to->type == InteriorTexture || to->type == MeshTexture) && + pDL->getNumMipLevels() > 4) + { + // + if (!to->smallTexGLName) + glGenTextures(1,&to->smallTexGLName); + + glBindTexture(GL_TEXTURE_2D, to->smallTexGLName); + if (pDL->getFormat() == GBitmap::Palettized) + { + glColorTableEXT(GL_TEXTURE_2D, + pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA, + 256, + GL_RGBA, + GL_UNSIGNED_BYTE, + pDL->getPalette()->getColors()); + } + if (sgDisableSubImage) + { + for (U32 i = 4; i < maxDownloadMip; i++) + { + glTexImage2D(GL_TEXTURE_2D, + i - 4, + destFormat, + pDL->getWidth(i), pDL->getHeight(i), + 0, + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + } + else + { + for (U32 i = 4; i < maxDownloadMip; i++) + { + glTexSubImage2D(GL_TEXTURE_2D, + i - 4, + 0, 0, + pDL->getWidth(i), pDL->getHeight(i), + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + } + } + else + { + if (to->smallTexGLName != 0) + glDeleteTextures(1, &to->smallTexGLName); + to->smallTexGLName = 0; + } + + if(pDL != pBitmap) + delete pDL; +} + +void TextureManager::refresh(TextureObject *to, GBitmap* bmp) +{ + if (!(gDGLRender || sgResurrect)) return; + + U32 sourceFormat, destFormat, byteFormat; + GBitmap* pBitmap = bmp; + + getSourceDestByteFormat(pBitmap, &sourceFormat, &destFormat, &byteFormat); + + if (!to->texGLName) + glGenTextures(1,&to->texGLName); + + glBindTexture(GL_TEXTURE_2D, to->texGLName); + GBitmap* pDL = createPaddedBitmap(pBitmap); + + U32 maxDownloadMip = pDL->getNumMipLevels(); + if (to->type == BitmapTexture || + to->type == BitmapKeepTexture || + to->type == BitmapNoDownloadTexture) + { + maxDownloadMip = 1; + } + + if (pDL->getFormat() == GBitmap::Palettized) + { + glColorTableEXT(GL_TEXTURE_2D, + pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA, + 256, + GL_RGBA, + GL_UNSIGNED_BYTE, + pDL->getPalette()->getColors()); + } + if (sgDisableSubImage) + { + for (U32 i = 0; i < maxDownloadMip; i++) + { + glTexImage2D(GL_TEXTURE_2D, + i, + destFormat, + pDL->getWidth(i), pDL->getHeight(i), + 0, + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + } + else + { + for (U32 i = 0; i < maxDownloadMip; i++) + { + glTexSubImage2D(GL_TEXTURE_2D, + i, + 0, 0, + pDL->getWidth(i), pDL->getHeight(i), + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + } + + if ((to->type == InteriorTexture || to->type == MeshTexture) && + pDL->getNumMipLevels() > 4) + { + // + if (!to->smallTexGLName) + glGenTextures(1,&to->smallTexGLName); + + glBindTexture(GL_TEXTURE_2D, to->smallTexGLName); + glBindTexture(GL_TEXTURE_2D, to->smallTexGLName); + if (pDL->getFormat() == GBitmap::Palettized) + { + glColorTableEXT(GL_TEXTURE_2D, + pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA, + 256, + GL_RGBA, + GL_UNSIGNED_BYTE, + pDL->getPalette()->getColors()); + } + if (sgDisableSubImage) + { + for (U32 i = 4; i < maxDownloadMip; i++) + { + glTexImage2D(GL_TEXTURE_2D, + i - 4, + destFormat, + pDL->getWidth(i), pDL->getHeight(i), + 0, + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + } + else + { + for (U32 i = 4; i < maxDownloadMip; i++) + { + glTexSubImage2D(GL_TEXTURE_2D, + i - 4, + 0, 0, + pDL->getWidth(i), pDL->getHeight(i), + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + } + } + else + { + if (to->smallTexGLName != 0) + glDeleteTextures(1, &to->smallTexGLName); + to->smallTexGLName = 0; + } + + if(pDL != pBitmap) + delete pDL; +} + +//-------------------------------------- +bool TextureManager::createGLName(GBitmap* pBitmap, + bool clampToEdge, + U32 firstMip, + TextureHandleType type, + TextureObject* to) +{ + if (!(gDGLRender || sgResurrect)) + return 0; + + glGenTextures(1, &to->texGLName); + glBindTexture(GL_TEXTURE_2D, to->texGLName); + + U32 sourceFormat, destFormat, byteFormat; + + getSourceDestByteFormat(pBitmap, &sourceFormat, &destFormat, &byteFormat); + + GBitmap *pDL = createPaddedBitmap(pBitmap); + + U32 maxDownloadMip = pDL->getNumMipLevels(); + if (type == BitmapTexture || + type == BitmapKeepTexture || + type == BitmapNoDownloadTexture) + { + maxDownloadMip = firstMip + 1; + } + + if (pDL->getFormat() == GBitmap::Palettized) + { + glColorTableEXT(GL_TEXTURE_2D, + pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA, + 256, + GL_RGBA, + GL_UNSIGNED_BYTE, + pDL->getPalette()->getColors()); + } + for (U32 i = firstMip; i < maxDownloadMip; i++) + { + glTexImage2D(GL_TEXTURE_2D, + i - firstMip, + destFormat, + pDL->getWidth(i), pDL->getHeight(i), + 0, + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if(pBitmap->getNumMipLevels() != 1 && + type != BitmapTexture && + type != BitmapKeepTexture && + type != BitmapNoDownloadTexture) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + if (dglDoesSupportTexAnisotropy()) { + F32 val = 1.0 + sgTextureAnisotropy * dglGetMaxAnisotropy(); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, val); + } + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + U32 clamp = GL_REPEAT; + if (clampToEdge) + clamp = dglDoesSupportEdgeClamp() ? GL_CLAMP_TO_EDGE_EXT : GL_CLAMP; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp); + + + if ((type == InteriorTexture || type == MeshTexture) && + (pDL->getNumMipLevels() - firstMip) > 4) + { + glGenTextures(1, &to->smallTexGLName); + glBindTexture(GL_TEXTURE_2D, to->smallTexGLName); + + if (pDL->getFormat() == GBitmap::Palettized) + { + glColorTableEXT(GL_TEXTURE_2D, + pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA, + 256, + GL_RGBA, + GL_UNSIGNED_BYTE, + pDL->getPalette()->getColors()); + } + for (U32 i = firstMip + 4; i < maxDownloadMip; i++) + { + glTexImage2D(GL_TEXTURE_2D, + i - (firstMip + 4), + destFormat, + pDL->getWidth(i), pDL->getHeight(i), + 0, + sourceFormat, + byteFormat, + pDL->getBits(i)); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + if (dglDoesSupportTexAnisotropy()) { + F32 val = 1.0 + sgTextureAnisotropy * dglGetMaxAnisotropy(); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, val); + } + + U32 clamp = GL_REPEAT; + if (clampToEdge) + clamp = dglDoesSupportEdgeClamp() ? GL_CLAMP_TO_EDGE_EXT : GL_CLAMP; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp); + } + + if(pDL != pBitmap) + delete pDL; + + return to->texGLName != 0; +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +TextureObject* TextureManager::registerTexture(const char* textureName, const GBitmap* data, bool clampToEdge) +{ + // if there is no textureName, it isn't inserted into the hash + // table... merely tracked by the texture manager + + TextureObject *ret = NULL; + if(textureName) + { + textureName = StringTable->insert(textureName); + ret = TextureDictionary::find(textureName, RegisteredTexture, clampToEdge); + } + if(ret) + { + // Crucial conditionals for the flush case... + if (ret->bitmap != data) + delete ret->bitmap; + if (ret->texGLName) + glDeleteTextures(1, (const GLuint*)&ret->texGLName); + if (ret->smallTexGLName) + glDeleteTextures(1, (const GLuint*)&ret->smallTexGLName); + ret->texGLName = 0; + ret->smallTexGLName = 0; + +#ifdef GATHER_METRICS + AssertFatal(ret->textureSpace <= smTextureSpaceLoaded, "Error, that shouldn't happen!"); + smTextureSpaceLoaded -= ret->textureSpace; + ret->textureSpace = 0; +#endif + + } + else + { + ret = new TextureObject; + ret->texFileName = textureName; + ret->texGLName = 0; + ret->smallTexGLName = 0; + ret->refCount = 0; + ret->type = RegisteredTexture; + ret->holding = false; + + TextureDictionary::insert(ret); + } + ret->bitmap = (GBitmap *) data; + ret->bitmapWidth = data->getWidth(); + ret->bitmapHeight = data->getHeight(); + ret->texWidth = getNextPow2(ret->bitmapWidth); + ret->texHeight = getNextPow2(ret->bitmapHeight); + ret->downloadedWidth = ret->texWidth; + ret->downloadedHeight = ret->texHeight; + ret->clamp = clampToEdge; + +#ifdef GATHER_METRICS + ret->textureSpace = ret->downloadedWidth * ret->downloadedHeight; + smTextureSpaceLoaded += ret->textureSpace; +#endif + + createGLName(ret->bitmap, clampToEdge, 0, ret->type, ret); + + return ret; +} + + +//-------------------------------------- +TextureObject* TextureManager::registerTexture(const char* textureName, GBitmap* bmp, TextureHandleType type, bool clampToEdge) +{ + TextureObject *ret = NULL; + if(textureName) + { + textureName = StringTable->insert(textureName); + ret = TextureDictionary::find(textureName, type, clampToEdge); + } + + if(ret) + { + // Crucial conditionals for the flush case... + if (ret->bitmap != bmp) + delete ret->bitmap; + if (ret->texGLName) + glDeleteTextures(1, (const GLuint*)&ret->texGLName); + if (ret->smallTexGLName) + glDeleteTextures(1, (const GLuint*)&ret->smallTexGLName); + ret->texGLName = 0; + ret->smallTexGLName = 0; + +#ifdef GATHER_METRICS + AssertFatal(ret->textureSpace <= smTextureSpaceLoaded, "Error, that shouldn't happen!"); + smTextureSpaceLoaded -= ret->textureSpace; + ret->textureSpace = 0; +#endif + } + else + { + ret = new TextureObject; + ret->texFileName = textureName; + ret->texGLName = 0; + ret->smallTexGLName = 0; + ret->refCount = 0; + ret->type = type; + + TextureDictionary::insert(ret); + } + + ret->bitmap = bmp; + ret->bitmapWidth = bmp->getWidth(); + ret->bitmapHeight = bmp->getHeight(); + ret->texWidth = getNextPow2(ret->bitmapWidth); + ret->texHeight = getNextPow2(ret->bitmapHeight); + ret->clamp = clampToEdge; + ret->holding = (type == MeshTexture) && ENABLE_HOLDING; + + if (ret->type == DetailTexture && + bmp->getFormat() != GBitmap::Palettized) + bmp->extrudeMipLevels(); + else if (ret->type != TerrainTexture && + ret->type != BitmapTexture && + ret->type != BitmapKeepTexture && + ret->type != BitmapNoDownloadTexture && + bmp->getFormat() != GBitmap::Palettized) + bmp->extrudeMipLevels(ret->type==ZeroBorderTexture); + + if(!ret->texGLName) { + U32 firstMip = 0; + if (ret->bitmap->getNumMipLevels() > 1 && + type != DetailTexture && + type != TerrainTexture && + type != BitmapTexture && + type != BitmapKeepTexture && + type != BitmapNoDownloadTexture) + { + if (type == SkyTexture) + { + firstMip = getMin(sgSkyTextureDetailLevel, ret->bitmap->getNumMipLevels() - 1); + } + else if (type == InteriorTexture) + { + firstMip = getMin(sgInteriorTextureDetailLevel, ret->bitmap->getNumMipLevels() - 1); + } + else + { + firstMip = getMin(sgTextureDetailLevel, ret->bitmap->getNumMipLevels() - 1); + } + } + + ret->downloadedWidth = ret->bitmapWidth >> firstMip; + ret->downloadedHeight = ret->bitmapHeight >> firstMip; + if (ret->downloadedWidth == 0) ret->downloadedWidth = 1; + if (ret->downloadedHeight == 0) ret->downloadedHeight = 1; + +#ifdef GATHER_METRICS + ret->textureSpace = 0; + for (U32 i = firstMip; i < ret->bitmap->getNumMipLevels(); i++) + ret->textureSpace += ret->bitmap->getWidth(i) * ret->bitmap->getHeight(i); + smTextureSpaceLoaded += ret->textureSpace; +#endif + + if(ret->type != BitmapNoDownloadTexture) + createGLName(bmp, clampToEdge, firstMip, ret->type, ret); + } + + if (ret->type == BitmapKeepTexture || ret->type == BitmapNoDownloadTexture) { + // do nothing + } else if (ret->type == TerrainTexture) { + // Don't delete the bitmap + ret->bitmap = NULL; + } else { + delete ret->bitmap; + ret->bitmap = NULL; + } + + return ret; +} + + +//-------------------------------------- +static GBitmap *loadBitmapInstance(const char *textureName) +{ + char fileNameBuffer[512]; + dStrcpy(fileNameBuffer, textureName); + + GBitmap *bmp = NULL; + + U32 len = dStrlen(fileNameBuffer); + // DMM: Major hack here until we decide what do to about this problem. + for (U32 i = 0; i < EXT_ARRAY_SIZE && bmp == NULL; i++) { + if (sgForcePalettedTexture == true && dglDoesSupportPalettedTexture()) + dStrcpy(fileNameBuffer + len, extArray_8[i]); + else + dStrcpy(fileNameBuffer + len, extArray[i]); + bmp = (GBitmap*)ResourceManager->loadInstance(fileNameBuffer); + } + return bmp; +} + +//-------------------------------------- + +TextureObject *TextureManager::loadTexture(const char* textureName, TextureHandleType type, bool clampToEdge) +{ + // case of assigning texture to NULL + if(!textureName) + return NULL; + + textureName = StringTable->insert(textureName); + + TextureObject *ret = TextureDictionary::find(textureName, type, clampToEdge); + + if(ret) + return ret; + + GBitmap *bmp = loadBitmapInstance(textureName); + if(!bmp) + return NULL; + + return registerTexture(textureName, bmp, type, clampToEdge); +} + + +//-------------------------------------- +void TextureHandle::setClamp(const bool c) +{ + if (object) + { + object->clamp = c; + if (object->texGLName != 0) + { + glBindTexture(GL_TEXTURE_2D, object->texGLName); + GLenum clamp; + if (c) + clamp = dglDoesSupportEdgeClamp() ? GL_CLAMP_TO_EDGE_EXT : GL_CLAMP; + else + clamp = GL_REPEAT; + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp); + } + if (object->smallTexGLName != 0) + { + glBindTexture(GL_TEXTURE_2D, object->smallTexGLName); + GLenum clamp; + if (c) + clamp = dglDoesSupportEdgeClamp() ? GL_CLAMP_TO_EDGE_EXT : GL_CLAMP; + else + clamp = GL_REPEAT; + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp); + } + } +} + +void TextureHandle::lock() +{ + if(object) + object->refCount++; +} + +void TextureHandle::unlock() +{ + if(object) + { + object->refCount--; + if (object->holding == false) + { + if(!object->refCount) + TextureManager::freeTexture(object); + } + else + { + AssertISV(object->refCount >= 0, avar("Texture holding out of balance: %d (0x%x)", + object->refCount, object->refCount)); + } + + object = NULL; + } +} + +void TextureHandle::refresh() +{ + TextureManager::refresh(object); +} + +void TextureHandle::refresh(GBitmap* bmp) +{ + AssertFatal(object->type == TerrainTexture, "Error, only terrain textures may be refreshed in this manner!"); + TextureManager::refresh(object, bmp); +} + + +#ifdef GATHER_METRICS +F32 TextureManager::getResidentFraction() +{ + U32 resident = 0; + U32 total = 0; + + Vector names; + + TextureObject* pProbe = TextureDictionary::smTOList; + while (pProbe != NULL) { + if (pProbe->texGLName != 0) { + total++; + names.push_back(pProbe->texGLName); + } + + pProbe = pProbe->next; + } + + if (total == 0) + return 1.0f; + + Vector isResident; + isResident.setSize(names.size()); + + glAreTexturesResident(names.size(), names.address(), isResident.address()); + for (U32 i = 0; i < names.size(); i++) + if (isResident[i] == GL_TRUE) + resident++; + + return (F32(resident) / F32(total)); +} +#endif + + +ChunkedTextureObject *gChunkedTextureList = NULL; + +ChunkedTextureObject* ChunkedTextureManager::loadTexture(const char *textureName) +{ + if(!textureName) + return NULL; + StringTableEntry tName = StringTable->insert(textureName); + + for(ChunkedTextureObject *walk = gChunkedTextureList; walk; walk = walk->next) + if(walk->texFileName == tName) + return walk; + GBitmap *bmp = loadBitmapInstance(textureName); + if(!bmp) + return NULL; + return registerTexture(textureName, bmp, false); +} + +ChunkedTextureObject* ChunkedTextureManager::registerTexture(const char *textureName, GBitmap *data, bool keep) +{ + ChunkedTextureObject *ret = NULL; + StringTableEntry tName = NULL; + + if(textureName) + { + tName = StringTable->insert(textureName); + for(ChunkedTextureObject *walk = gChunkedTextureList; walk; walk = walk->next) + { + if(walk->texFileName == tName) + { + ret = walk; + break; + } + } + } + if(ret && ret->bitmap) + { + delete ret->bitmap; + ret->bitmap = data; + } + else + { + ret = new ChunkedTextureObject; + ret->bitmap = data; + ret->texFileName = tName; + ret->next = gChunkedTextureList; + gChunkedTextureList = ret; + ret->texWidthCount = (data->getWidth() + 255) >> 8; + ret->texHeightCount = (data->getHeight() + 255) >> 8; + ret->width = data->getWidth(); + ret->height = data->getHeight(); + ret->textureHandles = NULL; + ret->refCount = 0; + } + refresh(ret); + if(!keep) + { + delete ret->bitmap; + ret->bitmap = NULL; + } + return ret; +} + +void ChunkedTextureManager::freeTexture(ChunkedTextureObject *to) +{ + // remove it from the linked list + + for(ChunkedTextureObject **walk = &gChunkedTextureList; *walk; walk = &((*walk)->next)) + { + if(*walk == to) + { + *walk = to->next; + delete[] to->textureHandles; + delete to->bitmap; + return; + } + } +} + +void ChunkedTextureManager::refresh(ChunkedTextureObject *to) +{ + if(!to->bitmap) + return; + + if(to->textureHandles) + { + delete[] to->textureHandles; + to->textureHandles = NULL; + } + to->textureHandles = new TextureHandle[to->texWidthCount * to->texHeightCount]; + for(U32 j = 0; j < to->texHeightCount; j++) + { + U32 y = j * 256; + U32 height = getMin(to->bitmap->getHeight() - y, U32(256)); + + for(U32 i = 0; i < to->texWidthCount; i++) + { + U32 index = j * to->texWidthCount + i; + U32 x = i * 256; + U32 width = getMin(to->bitmap->getWidth() - x, U32(256)); + GBitmap *tempBitmap = new GBitmap(width, height, false, to->bitmap->getFormat()); + for(U32 lp = 0; lp < height; lp++) + { + const U8 *src = to->bitmap->getAddress(x, y + lp); + U8 *dest = tempBitmap->getAddress(0, lp); + dMemcpy(dest, src, width * to->bitmap->bytesPerPixel); + } + to->textureHandles[index] = TextureHandle(NULL, tempBitmap, BitmapTexture, true); + } + } +} + +void ChunkedTextureManager::makeZombie() +{ + for(ChunkedTextureObject *walk = gChunkedTextureList; walk; walk = walk->next) + { + delete[] walk->textureHandles; + walk->textureHandles = NULL; + } +} + +void ChunkedTextureManager::resurrect() +{ + for(ChunkedTextureObject *walk = gChunkedTextureList; walk; walk = walk->next) + { + GBitmap *bmp = walk->bitmap; + if(!bmp) + walk->bitmap = loadBitmapInstance(walk->texFileName); + refresh(walk); + if(!bmp) + { + delete walk->bitmap; + walk->bitmap = NULL; + } + } +} + +TextureHandle ChunkedTextureHandle::getSubTexture(U32 x, U32 y) +{ + if(!object || !object->textureHandles) + return NULL; + return object->textureHandles[x + y * object->texWidthCount]; +} + + +void ChunkedTextureHandle::lock() +{ + if(object) + object->refCount++; +} + +void ChunkedTextureHandle::unlock() +{ + if(object) + { + object->refCount--; + if(object->refCount == 0) + ChunkedTextureManager::freeTexture(object); + } +} + diff --git a/dgl/gTexManager.h b/dgl/gTexManager.h new file mode 100644 index 0000000..ec9205d --- /dev/null +++ b/dgl/gTexManager.h @@ -0,0 +1,285 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GTEXMANAGER_H_ +#define _GTEXMANAGER_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +//-------------------------------------- Forward Decls. +class GBitmap; + +//------------------------------------------------------------------------------ +//-------------------------------------- TextureHandle +// + +enum TextureHandleType +{ + BitmapTexture = 0, + BitmapKeepTexture, + BitmapNoDownloadTexture, + RegisteredTexture, + MeshTexture, + TerrainTexture, + SkyTexture, + InteriorTexture, + + DetailTexture, + ZeroBorderTexture +}; + +class TextureObject +{ + public: + TextureObject *next; + TextureObject *prev; + TextureObject *hashNext; + + U32 texGLName; + U32 smallTexGLName; + +#ifdef GATHER_METRICS + U32 textureSpace; +#endif + + StringTableEntry texFileName; + GBitmap * bitmap; + + U32 texWidth; + U32 texHeight; + + U32 bitmapWidth; + U32 bitmapHeight; + + U32 downloadedWidth; + U32 downloadedHeight; + + TextureHandleType type; + bool clamp; + bool holding; + S32 refCount; +}; + +typedef void (*TextureEventCallback)(const U32 eventCode, const U32 userData); + +struct TextureManager +{ + // additional functions for refreshing the textures, reloading larger + // mip levels, etc, will go in here, as well as delay-load functions. + friend class TextureHandle; + friend class InteriorLMManager; + friend struct TextureDictionary; + + private: + static TextureObject* loadTexture(const char *textureName, TextureHandleType type, bool clampToEdge); + static TextureObject* registerTexture(const char *textureName, const GBitmap *data, bool clampToEdge); + static TextureObject* registerTexture(const char *textureName, GBitmap *data, TextureHandleType type, bool clampToEdge); + static void freeTexture(TextureObject *to); + static bool createGLName(GBitmap *pb, bool clampToEdge, U32 firstMip, TextureHandleType type, TextureObject* obj); + static void refresh(TextureObject *to); + static void refresh(TextureObject *to, GBitmap*); + static GBitmap* createMipBitmap(const GBitmap* pBitmap); + static GBitmap* createPaddedBitmap(GBitmap* pBitmap); + + + public: + static void create(); + static void preDestroy(); + static void destroy(); + + static void makeZombie(); // This pair of functions is a flush() equivalent. To flush + static void resurrect(); // the cache, call: + // makeZombie(); /* blah blah blah */ resurrect(); + // Note that NO drawing must take place until resurrect is + // called. The manager is a stinking corpse at this point. + // The split is necessary to support changing the OpenGL + // device in the "right way". This way glDeleteTexture is + // called on the original device rather than on the new + // device, as a flush() call would necessitate. + static void flush(); // Added for convenience when you don't need to worry about + // the above problems. + static bool smIsZombie; + +#ifdef GATHER_METRICS + static void dumpStats(); +#endif + + enum EventCodes { + BeginZombification = 0, + CacheResurrected = 1 + }; + static U32 registerEventCallback(TextureEventCallback, const U32 userData); + static void unregisterEventCallback(const U32 callbackKey); + + private: + static void postTextureEvent(const U32); + static bool smUseSmallTextures; + + public: + static const char * csmTexturePrefix; + + static void setSmallTexturesActive(const bool t) { smUseSmallTextures = t; } + static bool areSmallTexturesActive() { return smUseSmallTextures; } + +#ifdef GATHER_METRICS + static U32 smTextureSpaceLoaded; + static U32 smTextureCacheMisses; + + static F32 getResidentFraction(); +#endif +}; + +//------------------------------------------------------------------ +// +// TextureHandle - this is how you access a bitmap, etc. +// +// Texture handles can be allocated in 2 ways - by name to be loaded +// from disk, or by name to a dynamically generated texture +// +// If you create a GBitmap and register it, the Texture manager +// owns the pointer - so if you re-register a texture with the same +// name, the texture manager will delete the second copy. +// +//------------------------------------------------------------------ + +class TextureHandle +{ + TextureObject *object; + void lock(); + void unlock(); + public: + TextureHandle() { object = NULL; } + TextureHandle(const TextureHandle &th) { + object = th.object; + lock(); + } + + TextureHandle(const char* textureName, + TextureHandleType type=BitmapTexture, + bool clampToEdge = false) { + object = TextureManager::loadTexture(textureName, type, clampToEdge); + lock(); + } + + TextureHandle(const char* textureName, + const GBitmap* bmp, + bool clampToEdge = false) { + object = TextureManager::registerTexture(textureName, bmp, clampToEdge); + lock(); + } + + TextureHandle(const char* textureName, + GBitmap* bmp, + TextureHandleType type, + bool clampToEdge = false) { + object = TextureManager::registerTexture(textureName, bmp, type, clampToEdge); + lock(); + } + + ~TextureHandle() { unlock(); } + + TextureHandle& operator=(const TextureHandle &t) { + unlock(); + object = t.object; + lock(); + return *this; + } + void set(const char *textureName, + TextureHandleType type=BitmapTexture, + bool clampToEdge = false) { + TextureObject* newObject = TextureManager::loadTexture(textureName, type, clampToEdge);; + if (newObject != object) + { + unlock(); + object = newObject; + lock(); + } + } + void set(const char *textureName, + const GBitmap *data, + bool clampToEdge = false) { + TextureObject* newObject = TextureManager::registerTexture(textureName, data, clampToEdge); + if (newObject != object) + { + unlock(); + object = newObject; + lock(); + } + } + void set(const char *textureName, + GBitmap *bmp, + TextureHandleType type, + bool clampToEdge = false) { + TextureObject* newObject = TextureManager::registerTexture(textureName, bmp, type, clampToEdge); + if (newObject != object) + { + unlock(); + object = newObject; + lock(); + } + } + + bool operator==(const TextureHandle &t) const { return t.object == object; } + bool operator!=(const TextureHandle &t) const { return t.object != object; } + + void setClamp(const bool); + + void refresh(); + void refresh(GBitmap*); + operator TextureObject*() { return object; } + const char* getName() const { return (object ? object->texFileName : NULL); } + U32 getWidth() const { return (object ? object->bitmapWidth : 0UL); } + U32 getHeight() const { return (object ? object->bitmapHeight : 0UL); } + U32 getDownloadedWidth() const { return (object ? object->downloadedWidth : 0UL); } + U32 getDownloadedHeight() const { return (object ? object->downloadedHeight : 0UL); } + GBitmap* getBitmap() { return (object ? object->bitmap : NULL); } + U32 getGLName() const; +}; + +#if defined(GATHER_METRICS) && GATHER_METRICS > 1 +#ifndef _PLATFORMGL_H_ +#include "engine/platformWIN32/platformGL.h" +#endif + +inline U32 TextureHandle::getGLName() const +{ + if (!object) + return 0; + + U32 useName = tex->texGLName; + if (TextureManager::areSmallTexturesActive() && object->smallTexGLName != 0) + useName = object->smallTexGLName; + + if (useName != 0) { + GLboolean res; + glAreTexturesResident(1, &useName, &res); + if (res == GL_FALSE) + TextureManager::smTextureCacheMisses++; + } + + return useName; +} + +#else + +inline U32 TextureHandle::getGLName() const +{ + if (!object) + return 0; + + U32 useName = object->texGLName; + if (TextureManager::areSmallTexturesActive() && object->smallTexGLName != 0) + useName = object->smallTexGLName; + + return useName; +} + +#endif + +#endif // _GTEXMANAGER_H_ diff --git a/dgl/lensFlare.cc b/dgl/lensFlare.cc new file mode 100644 index 0000000..bbffb32 --- /dev/null +++ b/dgl/lensFlare.cc @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/lensFlare.h" +#include "dgl/dgl.h" + + +//************************************************************************** +// Lens Flare +//************************************************************************** + +//-------------------------------------------------------------------------- +// Clean up +//-------------------------------------------------------------------------- +LensFlare::~LensFlare() +{ + for( int i=0; ioffset; + renderFlare( flarePos, *flare ); + } + +} + +//-------------------------------------------------------------------------- +// Render flare +//-------------------------------------------------------------------------- +void LensFlare::renderFlare( Point3F &pos, const LFlare &flare ) +{ + + Point3F screenPoint; + if( !dglPointToScreen( pos, screenPoint ) ) + { + return; + } + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + // set ortho mode + RectI viewport; + dglGetViewport(&viewport); + dglSetClipRect( viewport ); + + glColor4fv( flare.color ); + glBindTexture(GL_TEXTURE_2D, flare.tex.getGLName()); + + dglDraw2DSquare( Point2F( screenPoint.x, screenPoint.y ), flare.size, 0 ); + + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); +} diff --git a/dgl/lensFlare.h b/dgl/lensFlare.h new file mode 100644 index 0000000..04f9e44 --- /dev/null +++ b/dgl/lensFlare.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LENSFLARE_H_ +#define _LENSFLARE_H_ + +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +//************************************************************************** +// Lens flare data +//************************************************************************** +struct LFlare +{ + ColorF color; + TextureHandle tex; + F32 size; // size in screen pixels (scaled to 640x480) + F32 offset; // offset of flare along flare line values around 0.0-1.0 are good + + + LFlare() + { + dMemset( this, 0, sizeof( LFlare ) ); + color.set( 1.0, 1.0, 1.0, 1.0 ); + } +}; + +//************************************************************************** +// Lens Flare +//************************************************************************** +class LensFlare +{ +private: + Vector mFlareList; + + void renderFlare( Point3F &pos, const LFlare &flare ); + +public: + ~LensFlare(); + void addFlare( LFlare &flare ); + void render( const MatrixF &camTrans, const Point3F &lightPos ); + + + +}; + + + + +#endif diff --git a/dgl/materialList.cc b/dgl/materialList.cc new file mode 100644 index 0000000..4c31050 --- /dev/null +++ b/dgl/materialList.cc @@ -0,0 +1,305 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "dgl/gTexManager.h" +#include "Core/resManager.h" +#include "Core/stream.h" +#include "dgl/materialList.h" + +//-------------------------------------- +MaterialList::MaterialList() +{ + mTextureType = BitmapTexture; + mClampToEdge = false; + + VECTOR_SET_ASSOCIATION(mMaterialNames); + VECTOR_SET_ASSOCIATION(mMaterials); +} + +MaterialList::MaterialList(const MaterialList* pCopy) +{ + VECTOR_SET_ASSOCIATION(mMaterialNames); + VECTOR_SET_ASSOCIATION(mMaterials); + + mClampToEdge = pCopy->mClampToEdge; + mTextureType = pCopy->mTextureType; + + mMaterialNames.setSize(pCopy->mMaterialNames.size()); + U32 i; + for (i = 0; i < mMaterialNames.size(); i++) { + if (pCopy->mMaterialNames[i]) { + mMaterialNames[i] = new char[dStrlen(pCopy->mMaterialNames[i]) + 1]; + dStrcpy(mMaterialNames[i], pCopy->mMaterialNames[i]); + } else { + mMaterialNames[i] = NULL; + } + } + + mMaterials.setSize(pCopy->mMaterials.size()); + for (i = 0; i < mMaterials.size(); i++) { + constructInPlace(&mMaterials[i]); + mMaterials[i] = pCopy->mMaterials[i]; + } +} + + + +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); + for(U32 i = 0; i < materialCount; i++) + { + // vectors DO NOT initialize classes so manually call the constructor + constructInPlace(&mMaterials[i]); + mMaterialNames[i] = new char[dStrlen(materialNames[i]) + 1]; + dStrcpy(mMaterialNames[i], materialNames[i]); + } +} + + +//-------------------------------------- +MaterialList::~MaterialList() +{ + free(); +} + + +//-------------------------------------- +void MaterialList::load(U32 index) +{ + AssertFatal(index < size(), "MaterialList:: index out of range."); + if (index < size()) + { + TextureHandle &handle = mMaterials[index]; + if (handle.getBitmap() == NULL) + { + const char *name = mMaterialNames[index]; + if (name && *name) + handle.set(name, mTextureType, mClampToEdge); + } + } +} + + +//-------------------------------------- +bool MaterialList::load() +{ + AssertFatal(mMaterials.size() == mMaterials.size(), "MaterialList::load: internal vectors out of sync."); + + for(U32 i=0; i < mMaterials.size(); i++) + load(i); + return true; +} + + +//-------------------------------------- +void MaterialList::unload() +{ + AssertFatal(mMaterials.size() == mMaterials.size(), "MaterialList::unload: internal vectors out of sync."); + for(U32 i=0; i < mMaterials.size(); i++) + mMaterials[i].~TextureHandle(); +} + + +//-------------------------------------- +void MaterialList::free() +{ + AssertFatal(mMaterials.size() == mMaterials.size(), "MaterialList::free: internal vectors out of sync."); + for(U32 i=0; i < mMaterials.size(); i++) + { + if(mMaterialNames[i]) + delete [] mMaterialNames[i]; + mMaterials[i].~TextureHandle(); + } + mMaterialNames.setSize(0); + mMaterials.setSize(0); +} + + +//-------------------------------------- +U32 MaterialList::push_back(TextureHandle textureHandle, const char * filename) +{ + mMaterials.increment(); + mMaterialNames.increment(); + + // vectors DO NOT initialize classes so manually call the constructor + constructInPlace(&mMaterials.last()); + mMaterials.last() = textureHandle; + mMaterialNames.last() = new char[dStrlen(filename) + 1]; + dStrcpy(mMaterialNames.last(), filename); + + // return the index + return mMaterials.size()-1; +} + +//-------------------------------------- +U32 MaterialList::push_back(const char *filename) +{ + mMaterials.increment(); + mMaterialNames.increment(); + + // vectors DO NOT initialize classes so manually call the constructor + constructInPlace(&mMaterials.last()); + mMaterialNames.last() = new char[dStrlen(filename) + 1]; + dStrcpy(mMaterialNames.last(), filename); + + // return the index + return mMaterials.size()-1; +} + + +//-------------------------------------- +U32 MaterialList::push_back(const char *filename, GBitmap *bmp, TextureHandleType type, bool clampToEdge) +{ + mMaterials.increment(); + mMaterialNames.increment(); + + // vectors DO NOT initialize classes so manually call the constructor + constructInPlace(&mMaterials.last()); + mMaterials.last().set(filename, bmp, type, clampToEdge); + mMaterialNames.last() = new char[dStrlen(filename) + 1]; + dStrcpy(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; ireadText(stream)) + return matList; + else + { + delete matList; + return NULL; + } +} diff --git a/dgl/materialList.h b/dgl/materialList.h new file mode 100644 index 0000000..fb5de87 --- /dev/null +++ b/dgl/materialList.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATERIALLIST_H_ +#define _MATERIALLIST_H_ + +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _RESMANAGER_H_ +#include "Core/resManager.h" +#endif +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif + + +//-------------------------------------- +class MaterialList : public ResourceInstance +{ +private: + friend class TSMaterialList; + + enum Constants { BINARY_FILE_VERSION = 1 }; + +public: + VectorPtr mMaterialNames; + Vector mMaterials; +protected: + bool mClampToEdge; + TextureHandleType mTextureType; + +public: + MaterialList(); + MaterialList(U32 materialCount, const char **materialNames); + ~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 retricting the copy syntax to + // ML* pML = new ML(©); + explicit MaterialList(const MaterialList*); + + S32 getMaterialCount() { return mMaterials.size(); } + const char * getMaterialName(U32 index) { return mMaterialNames[index]; } + TextureHandle &getMaterial(U32 index) + { + AssertFatal(index < mMaterials.size(), "MaterialList::getMaterial: index lookup out of range."); + return mMaterials[index]; + } + + // material properties + void setTextureType(TextureHandleType type) { mTextureType = type; } + void setClampToEdge(bool tf) { mClampToEdge = tf; } + + void set(U32 materialCount, const char **materialNames); + U32 push_back(TextureHandle textureHandle, const char *filename); + U32 push_back(const char *filename); + U32 push_back(const char *filename, GBitmap *bmp, TextureHandleType type, bool clampToEdge = false); + + virtual void load(U32 index); + bool load(); + bool load(TextureHandleType type, bool clampToEdge = false); + void unload(); + virtual void free(); + + typedef Vector::iterator iterator; + typedef Vector::value_type value; + TextureHandle& front() { return mMaterials.front(); } + TextureHandle& first() { return mMaterials.first(); } + TextureHandle& last() { return mMaterials.last(); } + bool empty() { return mMaterials.empty(); } + S32 size() { return 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); +}; + + +//-------------------------------------- +inline bool MaterialList::load(TextureHandleType type, bool clampToEdge) +{ + mTextureType = type; + mClampToEdge = clampToEdge; + return load(); +} + + +extern ResourceInstance* constructMaterialList(Stream &stream); + + +#endif diff --git a/dgl/materialPropertyMap.cc b/dgl/materialPropertyMap.cc new file mode 100644 index 0000000..7c4ee16 --- /dev/null +++ b/dgl/materialPropertyMap.cc @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/materialPropertyMap.h" + +namespace { + + +bool cMatPropMapAddMapping(SimObject*, S32 argc, const char** argv) +{ + MaterialPropertyMap* pMap = static_cast(Sim::findObject("MaterialPropertyMap")); + if (pMap == NULL) { + Con::errorf(ConsoleLogEntry::General, "Error, cannot find the global material map object"); + return false; + } + + return pMap->addMapping(argc - 1, argv + 1); +} + +} // namespace {} + + +IMPLEMENT_CONOBJECT(MaterialPropertyMap); +MaterialPropertyMap::MaterialPropertyMap() +{ + VECTOR_SET_ASSOCIATION(mMapEntries); +} + +MaterialPropertyMap::~MaterialPropertyMap() +{ + +} + +const MaterialPropertyMap::MapEntry* MaterialPropertyMap::getMapEntry(StringTableEntry name) const +{ + // DMMNOTE: Really slow. Shouldn't be a problem since these are one time scans + // for each object, but might want to replace this with a hash table + // + const MapEntry* ret = NULL; + for (U32 i = 0; i < mMapEntries.size(); i++) { + if (dStricmp(mMapEntries[i].name, name) == 0) { + ret = &mMapEntries[i]; + break; + } + } + + return ret; +} + +const MaterialPropertyMap::MapEntry* MaterialPropertyMap::getMapEntryFromIndex(S32 index) const +{ + const MapEntry* ret = NULL; + if(index < mMapEntries.size()) + ret = &mMapEntries[index]; + return ret; +} + +S32 MaterialPropertyMap::getIndexFromName(StringTableEntry name) const +{ + S32 ret = -1; + for (U32 i = 0; i < mMapEntries.size(); i++) { + if (dStricmp(mMapEntries[i].name, name) == 0) { + ret = i; + break; + } + } + return ret; +} + +MaterialPropertyMap::MapEntry* MaterialPropertyMap::getNCMapEntry(StringTableEntry name) +{ + return const_cast(getMapEntry(name)); +} + +bool MaterialPropertyMap::addMapping(const S32 argc, const char** argv) +{ + const char* matName = StringTable->insert(argv[0]); + + MapEntry* pEntry = getNCMapEntry(matName); + if (pEntry != NULL) { + Con::warnf(ConsoleLogEntry::General, "Warning, overwriting material properties for: %s", matName); + } else { + mMapEntries.increment(); + pEntry = &mMapEntries.last(); + pEntry->sound = -1; + pEntry->puffColor[0].set(0.0f, 0.0f, 0.0f); + pEntry->puffColor[1].set(0.0f, 0.0f, 0.0f); + } + + pEntry->name = matName; + pEntry->detailMapName = NULL; + pEntry->environMapName = NULL; + pEntry->matType = Default; + pEntry->matFlags = 0; + + for (U32 i = 1; S32(i) < argc; i++) { + const char* param = argv[i]; + + if (dStrnicmp(param, "detail:", dStrlen("detail:")) == 0) { + // Set the detail map + const char* pColon = dStrchr(param, ':'); + pColon++; + while (*pColon == ' ' || *pColon == '\t') + pColon++; + + pEntry->detailMapName = StringTable->insert(pColon); + } + else if (dStrnicmp(param, "environment:", dStrlen("environment:")) == 0) { + // Set the detail map + const char* pColon = dStrchr(param, ':'); + pColon++; + while (*pColon == ' ' || *pColon == '\t') + pColon++; + + const char* start = pColon; + while (*pColon != ' ') + pColon++; + const char* end = pColon; + pColon++; + + char buffer[256]; + dStrncpy(buffer, start, end - start); + buffer[end - start] = '\0'; + + pEntry->environMapName = StringTable->insert(buffer); + pEntry->environMapFactor = dAtof(pColon); + } + else if (dStrnicmp(param, "color:", dStrlen("color:")) == 0) { + const char* curChar = dStrchr(param, ':'); + curChar++; + while (*curChar == ' ' || *curChar == '\t') + curChar++; + + char buffer[5][256]; + S32 index = 0; + for(S32 x = 0; x < 5; ++x, index = 0) + { + while(*curChar != ' ' && *curChar != '\0') + buffer[x][index++] = *curChar++; + buffer[x][index++] = '\0'; + while(*curChar == ' ') + ++curChar; + } + pEntry->puffColor[0].set(dAtof(buffer[0]), dAtof(buffer[1]), dAtof(buffer[2]), dAtof(buffer[3])); + pEntry->puffColor[1].set(dAtof(buffer[0]), dAtof(buffer[1]), dAtof(buffer[2]), dAtof(buffer[4])); + } + else if (dStrnicmp(param, "sound:", dStrlen("sound:")) == 0) { + // Set the detail map + const char* pColon = dStrchr(param, ':'); + pColon++; + while (*pColon == ' ' || *pColon == '\t') + pColon++; + + const char* start = pColon; + while(*pColon != ' ' && *pColon != '\0') + pColon++; + const char* end = pColon; + pColon++; + + char buffer[256]; + dStrncpy(buffer, start, end - start); + buffer[end - start] = '\0'; + + pEntry->sound = dAtoi(buffer); + } + else if (param[0] == '\0') { + // Empty statement allowed, does nothing + } + else { + Con::warnf(ConsoleLogEntry::General, "Warning, misunderstood material parameter: %s in materialEntry %s", param, matName); + } + } + + return true; +} + + +void MaterialPropertyMap::consoleInit() +{ + //-------------------------------------- Class level variables +// Con::addVariable("pref::Interior::LightUpdatePeriod", TypeS32, &smLightUpdatePeriod); + + //-------------------------------------- Class level commands + Con::addCommand("addMaterialMapping", cMatPropMapAddMapping, "addMaterialMapping(\"matName\", ...)", 2, 99); +} diff --git a/dgl/materialPropertyMap.h b/dgl/materialPropertyMap.h new file mode 100644 index 0000000..16074cd --- /dev/null +++ b/dgl/materialPropertyMap.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATERIALPROPERTYMAP_H_ +#define _MATERIALPROPERTYMAP_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +class MaterialPropertyMap : public SimObject +{ + typedef SimObject Parent; + + public: + enum MaterialType { + Default + }; + + enum MaterialFlags { + None = 0 << 0 + }; + + struct MapEntry { + StringTableEntry name; + StringTableEntry detailMapName; + StringTableEntry environMapName; + + MaterialType matType; + U32 matFlags; + + float environMapFactor; + + S32 sound; + ColorF puffColor[2]; + }; + + public: + MaterialPropertyMap(); + ~MaterialPropertyMap(); + + const MapEntry* getMapEntry(StringTableEntry) const; + const MapEntry* getMapEntryFromIndex(S32 index) const; + S32 getIndexFromName(StringTableEntry name) const; + + DECLARE_CONOBJECT(MaterialPropertyMap); + static void consoleInit(); + + // Should only be used by console functions + public: + bool addMapping(const S32, const char**); + + //-------------------------------------- Internal interface + private: + MapEntry* getNCMapEntry(StringTableEntry); + + //-------------------------------------- Data + private: + Vector mMapEntries; +}; + +#endif // _H_MATERIALPROPERTYMAPPING_ diff --git a/dgl/rectClipper.cc b/dgl/rectClipper.cc new file mode 100644 index 0000000..5526c63 --- /dev/null +++ b/dgl/rectClipper.cc @@ -0,0 +1,147 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/rectClipper.h" + +//#pragma message "Check to make sure new RectI semantics followed" + +namespace { + +inline void +swap(F32& in_one, F32& in_two) +{ + F32 temp = in_one; + in_one = in_two; + in_two = temp; +} + +} + +bool +RectClipper::clipLine(const Point2I& in_rStart, + const Point2I& in_rEnd, + Point2I& out_rStart, + Point2I& out_rEnd) const +{ + // Check for trivial rejection + if ((in_rStart.x < m_clipRect.point.x && in_rEnd.x < m_clipRect.point.x) || + (in_rStart.x >= m_clipRect.point.x + m_clipRect.extent.x && + in_rEnd.x >= m_clipRect.point.x + m_clipRect.extent.x)) + return false; + if ((in_rStart.y < m_clipRect.point.y && in_rEnd.y < m_clipRect.point.y) || + (in_rStart.y >= m_clipRect.point.y + m_clipRect.extent.y && + in_rEnd.y >= m_clipRect.point.y + m_clipRect.extent.y)) + return false; + + F32 x1 = F32(in_rStart.x); + F32 y1 = F32(in_rStart.y); + F32 x2 = F32(in_rEnd.x); + F32 y2 = F32(in_rEnd.y); + + // I'm using essentially what's in the Phoenix libs, Liang-Biarsky based, but + // converted to FP math for greater precision on the back end... + // + bool flipped = false; + if (x1 > x2) { + swap(x1, x2); + swap(y1, y2); + flipped = !flipped; + } + + F32 dx = x2 - x1; + F32 dy = y2 - y1; + + // Clip x coord + F32 t; + if (x1 < F32(m_clipRect.point.x)) { + t = (F32(m_clipRect.point.x) - x1) / F32(dx); + x1 = F32(m_clipRect.point.x); + y1 += t * dy; + dx = x2 - x1; + dy = y2 - y1; + } + if (x2 >= F32(m_clipRect.point.x + m_clipRect.extent.x)) + { + t = (F32(m_clipRect.point.x + m_clipRect.extent.x - 1) - x1) / F32(dx); + x2 = F32(m_clipRect.point.x + m_clipRect.extent.x - 1); + y2 = y1 + (t * dy); + dx = x2 - x1; + dy = y2 - y1; + } + + // Recheck trivial rejection condition... + if((y1 > F32(m_clipRect.point.y + m_clipRect.extent.y - 1) && + y2 > F32(m_clipRect.point.y + m_clipRect.extent.y - 1)) || + (y1 < F32(m_clipRect.point.y) && y2 < F32(m_clipRect.point.y))) + return false; + + if (y1 > y2) { + swap(x1, x2); + swap(y1, y2); + flipped = !flipped; + } + + if (y1 < F32(m_clipRect.point.y)) { + t = (F32(m_clipRect.point.y) - y1) / F32(dy); + y1 = F32(m_clipRect.point.y); + x1 += t * dx; + dx = x2 - x1; + dy = y2 - y1; + } + if (y2 > F32(m_clipRect.point.y + m_clipRect.extent.y - 1)) + { + t = (F32(m_clipRect.point.y + m_clipRect.extent.y - 1) - y1) / F32(dy); + y2 = F32(m_clipRect.point.y + m_clipRect.extent.y - 1); + x2 = x1 + (t * dx); + } + + if (flipped == true) { + out_rEnd.x = S32(x1 + 0.5f); + out_rEnd.y = S32(y1 + 0.5f); + out_rStart.x = S32(x2 + 0.5f); + out_rStart.y = S32(y2 + 0.5f); + } else { + out_rStart.x = S32(x1 + 0.5f); + out_rStart.y = S32(y1 + 0.5f); + out_rEnd.x = S32(x2 + 0.5f); + out_rEnd.y = S32(y2 + 0.5f); + } + + return true; +} + + +bool +RectClipper::clipRect(const RectI& in_rRect, + RectI& out_rRect) const +{ + AssertFatal(in_rRect.isValidRect(), "Inappropriate min/max coords for rectangle"); + + if (in_rRect.point.x + in_rRect.extent.x - 1 < m_clipRect.point.x || + in_rRect.point.x > m_clipRect.point.x + m_clipRect.extent.x - 1) + return false; + if (in_rRect.point.y + in_rRect.extent.y - 1 < m_clipRect.point.y || + in_rRect.point.y > m_clipRect.point.y + m_clipRect.extent.y - 1) + return false; + + if (in_rRect.point.x < m_clipRect.point.x) out_rRect.point.x = m_clipRect.point.x; + else out_rRect.point.x = in_rRect.point.x; + + if (in_rRect.point.y < m_clipRect.point.y) out_rRect.point.y = m_clipRect.point.y; + else out_rRect.point.y = in_rRect.point.y; + + Point2I bottomR; + bottomR.x = getMin(in_rRect.point.x + in_rRect.extent.x - 1, + m_clipRect.point.x + m_clipRect.extent.x - 1); + bottomR.y = getMin(in_rRect.point.y + in_rRect.extent.y - 1, + m_clipRect.point.y + m_clipRect.extent.y - 1); + + out_rRect.extent.x = bottomR.x - out_rRect.point.x + 1; + out_rRect.extent.x = bottomR.y - out_rRect.point.y + 1; + + return true; +} diff --git a/dgl/rectClipper.h b/dgl/rectClipper.h new file mode 100644 index 0000000..ed134ab --- /dev/null +++ b/dgl/rectClipper.h @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RECTCLIPPER_H_ +#define _RECTCLIPPER_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MRECT_H_ +#include "Math/mRect.h" +#endif + + +class RectClipper +{ + RectI m_clipRect; + + public: + RectClipper(const RectI& in_rRect); + + bool clipPoint(const Point2I& in_rPoint) const; + bool clipLine(const Point2I& in_rStart, + const Point2I& in_rEnd, + Point2I& out_rStart, + Point2I& out_rEnd) const; + bool clipRect(const RectI& in_rRect, + RectI& out_rRect) const; +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- INLINES +// +inline +RectClipper::RectClipper(const RectI& in_rRect) + : m_clipRect(in_rRect) +{ + // +} + +inline bool +RectClipper::clipPoint(const Point2I& in_rPoint) const +{ + if ((in_rPoint.x < m_clipRect.point.x) || + (in_rPoint.y < m_clipRect.point.y) || + (in_rPoint.x >= m_clipRect.point.x + m_clipRect.extent.x) || + (in_rPoint.y >= m_clipRect.point.y + m_clipRect.extent.y)) + return false; + return true; +} + +#endif //_RECTCLIPPER_H_ diff --git a/dgl/splineUtil.cc b/dgl/splineUtil.cc new file mode 100644 index 0000000..0586767 --- /dev/null +++ b/dgl/splineUtil.cc @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/splineUtil.h" + +#include "PlatformWin32/platformGL.h" + +namespace SplineUtil{ + + +//------------------------------------------------------------------------------ +// Draws strip of specified width along spline. Polys on strip segments are +// front-facing (billboarded) +//------------------------------------------------------------------------------ +void drawSplineBeam( const Point3F& camPos, U32 numSegments, + F32 width, SplinePatch &spline, F32 uvOffset, F32 numTexRep ) +{ + + Point3F beginPoint, endPoint; + spline.calc( 0.0, beginPoint ); + spline.calc( 1.0, endPoint ); + + F32 approxBeamLength = (beginPoint - endPoint).len(); + F32 texRepFactor = approxBeamLength * numTexRep; + + + glBegin(GL_TRIANGLE_STRIP); + + + for( int i=0; icalc( 0.0, beginPoint ); + sbi.spline->calc( 1.0, endPoint ); + + F32 approxBeamLength = (beginPoint - endPoint).len(); + F32 texRepFactor = approxBeamLength * sbi.numTexRep; + + + glBegin(GL_TRIANGLE_STRIP); + + + for( int i=0; icalc( t, curPoint ); + + Point3F segmentDir; + + // handle last segment case + Point3F nextPoint; + if( i == (sbi.numSegments - 1) ) + { + F32 modT = ((F32)(sbi.numSegments - 1)) / ((F32)sbi.numSegments); + sbi.spline->calc( modT, nextPoint ); + segmentDir = curPoint - nextPoint; + } + else + { + F32 modT = t + (1.0 / sbi.numSegments); + sbi.spline->calc( modT, nextPoint ); + segmentDir = nextPoint - curPoint; + } + + if( segmentDir.isZero() ) continue; + segmentDir.normalize(); + + + Point3F dirFromCam = curPoint - *sbi.camPos; + Point3F crossVec; + mCross(dirFromCam, segmentDir, &crossVec); + crossVec.normalize(); + + crossVec *= sbi.width * 0.5; + + F32 u = sbi.uvOffset + texRepFactor * t; + + if( i== 0 && sbi.zeroAlphaStart ) + { + glColor4f( sbi.color.red, sbi.color.green, sbi.color.blue, 0.0 ); + } + else + { + glColor4fv( sbi.color ); + } + + glTexCoord2f( u, 0.0 ); + glVertex3fv( curPoint + crossVec ); + + glTexCoord2f( u, 1.0 ); + glVertex3fv( curPoint - crossVec ); + + } + + glEnd(); + +} + + + + + +} // end SplineUtil namespace diff --git a/dgl/splineUtil.h b/dgl/splineUtil.h new file mode 100644 index 0000000..1d71cf8 --- /dev/null +++ b/dgl/splineUtil.h @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SPLINEUTIL_H_ +#define _SPLINEUTIL_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MSPLINEPATCH_H_ +#include "Math/mSplinePatch.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +namespace SplineUtil +{ + + struct SplineBeamInfo + { + Point3F * camPos; + U32 numSegments; + F32 width; + SplinePatch * spline; + F32 uvOffset; + F32 numTexRep; + ColorF color; + bool zeroAlphaStart; // first part of first segment has 0 alpha value + + SplineBeamInfo() + { + dMemset( this, 0, sizeof( SplineBeamInfo ) ); + numTexRep = 1.0; + } + + }; + + //------------------------------------------------------------------------------ + // Draws strip of specified width along spline. Polys on strip segments are front-facing (billboarded) + //------------------------------------------------------------------------------ + void drawSplineBeam( const Point3F& camPos, U32 numSegments, F32 width, + SplinePatch &spline, F32 uvOffset = 0.0, F32 numTexRep = 1.0 ); + + void drawSplineBeam( SplineBeamInfo &sbi ); + + +} + + + +#endif diff --git a/dgl/stripCache.cc b/dgl/stripCache.cc new file mode 100644 index 0000000..df6dba6 --- /dev/null +++ b/dgl/stripCache.cc @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/stripCache.h" +#include "PlatformWin32/platformGL.h" + +void StripCache::emitStrip(const U32 start, const U32 count, const ColorI& color) +{ + if (count < 3) { + AssertFatal(false, "Strip count < 3"); + return; + } + + if (currIndex + count >= 1024) + flushCache(); + + // Contiguous index enforcement + if (currIndex != 0 && start != (stripIndices[currIndex - 1]+1)) + flushCache(); + + stripStarts[currStrip] = currIndex; + stripColors[currStrip++] = color; + for (U32 i = start; i < start+count; i++) + stripIndices[currIndex++] = i; +} + +void StripCache::flushCache() +{ + if (currIndex == 0) + return; + + // We know that (for right now) the first index is the least, and the + // last is the greatest. The commented condition in the emitStrip + // call makes sure this range is contiguous... + U32 first = stripIndices[0]; + U32 last = stripIndices[currIndex-1]; + + stripStarts[currStrip] = currIndex; + + if (dglDoesSupportCompiledVertexArray()) + glLockArraysEXT(first, last - first + 1); + + for (U32 i = 0; i < currStrip; i++) { + glColor4ubv(stripColors[i]); + glDrawElements(GL_TRIANGLE_STRIP, stripStarts[i+1] - stripStarts[i], + GL_UNSIGNED_INT, &stripIndices[stripStarts[i]]); + } + + if (dglDoesSupportCompiledVertexArray()) + glUnlockArraysEXT(); + + currIndex = 0; + currStrip = 0; +} + diff --git a/dgl/stripCache.h b/dgl/stripCache.h new file mode 100644 index 0000000..0d21f02 --- /dev/null +++ b/dgl/stripCache.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STRIPCACHE_H_ +#define _STRIPCACHE_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + + +class StripCache +{ + U32 stripIndices[1024]; + U32 stripStarts[512]; + ColorI stripColors[512]; + U32 currIndex; + U32 currStrip; + + public: + StripCache() { currIndex = 0; currStrip = 0; } + + // Cache manages locking + void emitStrip(const U32 start, const U32 end, const ColorI& color); + void flushCache(); +}; + +#endif // _H_STRIPCACHE_ diff --git a/editor/compTest.cc b/editor/compTest.cc new file mode 100644 index 0000000..ea1d3f1 --- /dev/null +++ b/editor/compTest.cc @@ -0,0 +1,537 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiControl.h" +#include "console/consoleTypes.h" +#include "Core/fileStream.h" +#include "dgl/dgl.h" +#include "GUI/guiScrollCtrl.h" +#include "terrain/terrData.h" +#include + +//----------------------------------------------------------------------------- + +class CompTest : public GuiScrollContentCtrl +{ + private: + typedef GuiControl Parent; + S32 mHeights[65536]; + S32 mHeightMin; + S32 mHeightMax; + void calcMinMax(); + + U16 mTerrainHeights[65536]; + U32 mCurrentIndex; + Point2I mHilbertPos; + + public: + + enum { + UP, + LEFT, + DOWN, + RIGHT + }; + + DECLARE_CONOBJECT(CompTest); + + static S32 smShift; + static S32 smHisto; + static S32 smSaveHiLo; + + static void consoleInit(); + void onRender(Point2I offset, const RectI & updateRect, GuiControl * firstResponder); + void openFile(StringTableEntry fileName); + void saveFile(StringTableEntry fileName); + void buildRep(StringTableEntry type); + void windowCompress(); + + TerrainBlock * getTerrainObj(); + + // hilbert curve stuff + void hilbertFill(S32 level, S32 direction = UP); + void move(S32 direction); +}; + +S32 CompTest::smShift; +S32 CompTest::smHisto; +S32 CompTest::smSaveHiLo; + +IMPLEMENT_CONOBJECT(CompTest); + +//----------------------------------------------------------------------------- + +static void cCompTestOpenFile(SimObject * obj, S32, const char ** argv) +{ + static_cast(obj)->openFile(argv[2]); +} + +static void cCompTestSaveFile(SimObject * obj, S32, const char ** argv) +{ + static_cast(obj)->saveFile(argv[2]); +} + +static void cCompTestBuildRep(SimObject * obj, S32, const char ** argv) +{ + static_cast(obj)->buildRep(argv[2]); +} + +void CompTest::consoleInit() +{ + Con::addCommand("CompTest", "buildRep", cCompTestBuildRep, "compTest.buildRep(type)", 3, 3); + Con::addCommand("CompTest", "openFile", cCompTestOpenFile, "compTest.openFile(filename)", 3, 3); + Con::addCommand("CompTest", "saveFile", cCompTestSaveFile, "compTest.saveFile(filename)", 3, 3); + + Con::addVariable("CompTestShift", TypeS32, &smShift); + Con::addVariable("CompTestHisto", TypeS32, &smHisto); + Con::addVariable("CompTestSaveHiLo", TypeS32, &smSaveHiLo); +} + +//----------------------------------------------------------------------------- +// BEGIN Hilbert curve stuff +void CompTest::move(S32 direction) +{ + switch(direction) + { + case LEFT: + mHilbertPos.x--; + break; + case RIGHT: + mHilbertPos.x++; + break; + case UP: + mHilbertPos.y--; + break; + case DOWN: + mHilbertPos.y++; + break; + } + + AssertFatal(mCurrentIndex < 256 * 256, "Doh!"); + AssertFatal(mHilbertPos.x + 256 * mHilbertPos.y < 256 * 256, "Blah!"); + + // copy at the hilbert position + mHeights[mCurrentIndex++] = mTerrainHeights[mHilbertPos.x + 256 * mHilbertPos.y]; +} + +void CompTest::hilbertFill(S32 level, S32 direction) +{ + if (level==1) { + switch (direction) { + case LEFT: + move(RIGHT); /* move() could draw a line in... */ + move(DOWN); /* ...the indicated direction */ + move(LEFT); + break; + case RIGHT: + move(LEFT); + move(UP); + move(RIGHT); + break; + case UP: + move(DOWN); + move(RIGHT); + move(UP); + break; + case DOWN: + move(UP); + move(LEFT); + move(DOWN); + break; + } /* switch */ + } else { + switch (direction) { + case LEFT: + hilbertFill(level-1,UP); + move(RIGHT); + hilbertFill(level-1,LEFT); + move(DOWN); + hilbertFill(level-1,LEFT); + move(LEFT); + hilbertFill(level-1,DOWN); + break; + case RIGHT: + hilbertFill(level-1,DOWN); + move(LEFT); + hilbertFill(level-1,RIGHT); + move(UP); + hilbertFill(level-1,RIGHT); + move(RIGHT); + hilbertFill(level-1,UP); + break; + case UP: + hilbertFill(level-1,LEFT); + move(DOWN); + hilbertFill(level-1,UP); + move(RIGHT); + hilbertFill(level-1,UP); + move(UP); + hilbertFill(level-1,RIGHT); + break; + case DOWN: + hilbertFill(level-1,RIGHT); + move(UP); + hilbertFill(level-1,DOWN); + move(LEFT); + hilbertFill(level-1,DOWN); + move(DOWN); + hilbertFill(level-1,LEFT); + break; + } + } +} + +// END Hilbert curve stuff +//------------------------------------------------------------------------------ + +static S32 QSORT_CALLBACK sortHisto(const void * a, const void * b) +{ + S16 intA = *((S16*)a); + S16 intB = *((S16*)b); + return(intA == intB ? 0 : intA > intB ? -1 : 1); +} + +//----------------------------------------------------------------------------- + +TerrainBlock * CompTest::getTerrainObj() +{ + // get the terrain obj + SimObject* obj = Sim::findObject("Terrain"); + if(!obj) + return(0); + TerrainBlock * terrain = static_cast(obj); + return(terrain); +} + +//----------------------------------------------------------------------------- + +void CompTest::windowCompress() +{ + U16 window[32]; + S32 size = 32 * 16; + S32 windowCount = 0; + S32 i; + for(i = 0; i < 65536 && windowCount < 32; i++) + { + U16 h = mHeights[i]; + S32 j; + for(j = 0; j < windowCount;j++) + if(window[j] == h) + break; + if(j == windowCount) + window[windowCount++] = h; + } + S32 runStart = 0; + S32 runCount = 0; + S32 winPos = 0; + for(i = 0; i < 65536; i++) + { + U16 h = mHeights[i]; + S32 j; + for(j = 0; j < windowCount; j++) + if(h == window[j]) + break; + if(j == windowCount) + { + size += runCount * 5 + 32; + window[winPos++] = h; + if(winPos == windowCount) + winPos = 0; + runStart = i+1; + runCount = 0; + } + else + runCount++; + } + Con::printf("Compressed size: %d", size >> 3); +} + +void CompTest::buildRep(StringTableEntry type) +{ + TerrainBlock * terrain = getTerrainObj(); + if(!terrain) + return; + + U16 *htcpy = terrain->heightMap; + + for(S32 y = 0; y < 256; y++) + for(S32 x = 0; x < 256; x++) + mTerrainHeights[y * 256 + x] = htcpy[y * 257 + x]; + + // check for shift + if(smShift) + for(U32 i = 0; i < 256<<8; i++) + mTerrainHeights[i] >>= smShift; + + // just copy + if(!dStricmp(type, "baseline")) + { + for(U32 i = 0; i < 256 * 256; i++) + mHeights[i] = mTerrainHeights[i]; + } + // hilbert curve + else if(!dStricmp(type, "hilbert")) + { + mHilbertPos.x = mHilbertPos.y = 0; + mHeights[0] = mTerrainHeights[0]; + mCurrentIndex = 1; + hilbertFill(8,UP); + } + // MarkF delta type + else if(!dStricmp(type, "delta")) + { + for(U32 y = 0; y < 256; y++) + for(U32 x = 0; x < 256; x++) + { + U16 h1 = mTerrainHeights[x + (y<<8)]; + U16 h2 = mTerrainHeights[((x+1) % 256) + (y << 8)]; + U16 h3 = mTerrainHeights[x + (((y+1) % 256) << 8)]; + U16 h4 = mTerrainHeights[((x+1) % 256) + (((y+1) % 256) << 8)]; + mHeights[x + (y<<8)] = h4 - h1 + (h2 - h1) + (h3 - h1); + } + } + else if(!dStricmp(type, "delta2")) + { + U16 prevHeight = 0; + for(U32 i = 0; i < 65536; i++) + { + mHeights[i] = mTerrainHeights[i] - prevHeight; + prevHeight = mTerrainHeights[i]; + } + } + else if(!dStricmp(type, "delta3")) + { + mHeights[0] = mTerrainHeights[0]; + S32 delta = mTerrainHeights[0] / 2; + S32 deltaPrev = mTerrainHeights[0] - delta; + for(U32 i = 0; i < 65536; i++) + { + S32 ph = mTerrainHeights[i-1]; + S32 h = mTerrainHeights[i]; + + mHeights[i] = h - ph - ((delta + deltaPrev) >> 1); + deltaPrev = delta; + delta = h - ph; + } + /*for(U32 y = 0; y < 256; y++) + { + U32 prevy = (y - 1) & 0xFF; + + U16 *prevRow = mTerrainHeights + prevy * 256; + U16 *curRow = mTerrainHeights + y * 256; + S16 *dest = mHeights + y *256; + for(U32 x = 0; x < 256; x++) + dest[x] = curRow[x] - prevRow[x]; + }*/ +/* U16 prevDelta = 0; + U16 prevHeight = 0; + for(U32 i = 0; i < 65536; i++) + { + U16 h = mTerrainHeights[i]; + mHeights[i] = h - (prevHeight + prevDelta); + prevHeight = h; + prevDelta = mHeights[i]; + } + prevHeight = 0; + prevDelta = 0; + for(U32 i = 0; i < 65536; i++) + { + U16 newDelta = mHeights[i]; + mHeights[i] = prevHeight + prevDelta + newDelta; + prevDelta = newDelta; + prevHeight = mHeights[i]; + }*/ + } + else + return; + + // check for histo display + windowCompress(); + if(smHisto) + { + S16 *hist = new S16[256*256]; + U32 i; + for(i = 0; i < 256*256; i++) + hist[i] = 0; + for(i = 0; i < 256*256; i++) + hist[U16(mHeights[i])]++; + + dQsort(hist, 256*256, 2, sortHisto); + for(i = 0; i < 256*256; i++) + mHeights[i] = hist[i]; + + U32 used = 0; + for(i = 0; i < 256*256; i++) + if(mHeights[i]) + used++; + delete [] hist; + + Con::printf("compTest => number of unique histogram entries: %d", used); + F32 sum = 0; + for(i = 0; i < 65536; i++) + { + if(hist[i] == 0) + continue; + F32 p = hist[i] / 65536.0f; + sum += p * log(p) / log(2.0); + } + Con::printf("Compressability sum: %f", -sum); + } + calcMinMax(); + + Con::printf("compTest => min/max terrain height: %f / %f", + F32(mHeightMin) * 0.03125, F32(mHeightMax) * 0.03125); +} + + +//----------------------------------------------------------------------------- + +void CompTest::calcMinMax() +{ + mHeightMin = 32000; + mHeightMax = -32000; + + for(U32 i = 0; i < (256*256); i++) + { + if(mHeights[i] < mHeightMin) + mHeightMin = mHeights[i]; + if(mHeights[i] > mHeightMax) + mHeightMax = mHeights[i]; + } +} + +//----------------------------------------------------------------------------- + +void CompTest::openFile(StringTableEntry fileName) +{ + if(!fileName || !dStrlen(fileName)) + return; + + FileStream file; + if(!file.open(fileName, FileStream::Read)) + return; + + for(U32 i = 0; i < (256*256); i++) + { + if(!file.read(&mHeights[i])) + return; + } + calcMinMax(); +} + +void CompTest::saveFile(StringTableEntry fileName) +{ + if(!fileName || !dStrlen(fileName) || !mHeights) + return; + + FileStream file; + file.open(fileName, FileStream::ReadWrite); + U32 i; + for(i = 0; i < (256*256); i++) + file.write(mHeights[i]); + file.close(); + + if(smSaveHiLo) + { + // save lo data + file.open("lo_byte.out", FileStream::ReadWrite); + for(i = 0; i < (256*256); i++) + { + U8 out = mHeights[i]; + file.write(out); + } + // save high data + file.open("hi_byte.out", FileStream::ReadWrite); + for(i = 0; i < (256*256); i++) + { + U8 out = mHeights[i] >> 8; + file.write(out); + } + } +} + +//----------------------------------------------------------------------------- + +void CompTest::onRender(Point2I offset, const RectI & updateRect, GuiControl * firstResponder) +{ + firstResponder; + if(!mHeights) + return; + + dglSetViewport(updateRect); + + GuiControl * parent = getParent(); + if(!parent) + return; + + Point2I parentOff = parent->localToGlobalCoord(Point2I(0,0)); + offset -= parentOff; + + // clear the background only +// glClear(GL_COLOR_BUFFER_BIT); + glBegin(GL_QUADS); + glColor3f(0.2, 0.2, 0.2); + glVertex2f(updateRect.point.x, updateRect.point.y); + glVertex2f(updateRect.point.x + updateRect.extent.x, updateRect.point.y); + glVertex2f(updateRect.point.x + updateRect.extent.x, updateRect.point.y + updateRect.extent.y); + glVertex2f(updateRect.point.x, updateRect.point.y + updateRect.extent.y); + glEnd(); + + // draw forest, draw! + glBegin(GL_LINES); + glColor3f(0.8,0,0); + + // check for neg vals + if(mHeightMin < 0) + { + U16 range = mHeightMax > -mHeightMin ? mHeightMax : -mHeightMin; + + for(U32 i = 0; i < updateRect.extent.x; i++) + { + U32 index = i + -offset.x; + if(i >= 256 * 256) + break; + + S16 curHeight = mHeights[index]; + + F32 bottom = updateRect.point.y + updateRect.extent.y / 2; + glVertex2f(i, bottom); + + F32 scale = F32(curHeight) / F32(range); + + F32 top = bottom - F32(updateRect.extent.y / 2) * scale; + glVertex2f(i, top); + } + } + else + { + U16 range = mHeightMax - mHeightMin; + + F32 maxScale = 0.f; + + for(U32 i = 0; i < updateRect.extent.x; i++) + { + U32 index = i + -offset.x; + if(index >= 256 * 256) + break; + + U16 curHeight = mHeights[index]; + + F32 bottom = updateRect.point.y + updateRect.extent.y; + glVertex2f(updateRect.point.x + i, bottom); + + F32 scale = (F32(curHeight) - F32(mHeightMin)) / F32(range); + + if(scale > maxScale) + maxScale = scale; + + F32 top = bottom - F32(updateRect.extent.y) * scale; + glVertex2f(updateRect.point.x + i, top); + } + } + glEnd(); +} diff --git a/editor/creator.cc b/editor/creator.cc new file mode 100644 index 0000000..8c77354 --- /dev/null +++ b/editor/creator.cc @@ -0,0 +1,465 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Editor/creator.h" +#include "dgl/dgl.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 +} + +//------------------------------------------------------------------------------ + +static S32 cAddGroup(SimObject * obj, S32, const char ** argv) +{ + CreatorTree * tree = dynamic_cast(obj); + CreatorTree::Node * grp = tree->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 = tree->createNode(argv[3], 0, true, grp); + tree->build(); + return(node ? node->getId() : -1); +} + +static S32 cAddItem(SimObject * obj, S32, const char ** argv) +{ + CreatorTree * tree = dynamic_cast(obj); + CreatorTree::Node * grp = tree->findNode(dAtoi(argv[2])); + + if(!grp || !grp->isGroup()) + return -1; + + CreatorTree::Node * node = tree->createNode(argv[3], argv[4], false, grp); + tree->build(); + return(node ? node->getId() : -1); +} + +//------------------------------------------------------------------------------ + +static bool cFileNameMatch(SimObject *, S32, const char ** argv) +{ + // 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)); +} + +static S32 cGetSelected(SimObject * obj, S32, const char **) +{ + CreatorTree * tree = dynamic_cast(obj); + return(tree->getSelected()); +} + +static bool cIsGroup(SimObject * obj, S32, const char ** argv) +{ + CreatorTree * tree = dynamic_cast(obj); + CreatorTree::Node * node = tree->findNode(dAtoi(argv[2])); + if(node && node->isGroup()) + return(true); + return(false); +} + +static const char * cGetName(SimObject * obj, S32, const char ** argv) +{ + CreatorTree * tree = dynamic_cast(obj); + CreatorTree::Node * node = tree->findNode(dAtoi(argv[2])); + return(node ? node->mName : 0); +} + +static const char * cGetValue(SimObject * obj, S32, const char ** argv) +{ + CreatorTree * tree = dynamic_cast(obj); + CreatorTree::Node * node = tree->findNode(dAtoi(argv[2])); + return(node ? node->mValue : 0); +} + +static void cClear(SimObject * obj, S32, const char **) +{ + CreatorTree * tree = dynamic_cast(obj); + tree->clear(); +} + +static S32 cGetParent(SimObject * obj, S32, const char ** argv) +{ + CreatorTree * tree = dynamic_cast(obj); + CreatorTree::Node * node = tree->findNode(dAtoi(argv[2])); + if(node && node->mParent) + return(node->mParent->getId()); + else + return(-1); +} +//------------------------------------------------------------------------------ + +void CreatorTree::consoleInit() +{ + Con::addCommand("CreatorTree", "fileNameMatch", cFileNameMatch, "creator.fileNameMatch(world, type, filename);", 5, 5); + Con::addCommand("CreatorTree", "addGroup", cAddGroup, "creator.addGroup(parent, group);", 4, 4); + Con::addCommand("CreatorTree", "addItem", cAddItem, "creator.addItem(group, name, value);", 5, 5); + Con::addCommand("CreatorTree", "getSelected", cGetSelected, "creator.getSelected();", 2, 2); + Con::addCommand("CreatorTree", "isGroup", cIsGroup, "creator.isGroup(id);", 3, 3); + Con::addCommand("CreatorTree", "getName", cGetName, "creator.getName(id);", 3, 3); + Con::addCommand("CreatorTree", "getValue", cGetValue, "creator.getValue(id);", 3, 3); + Con::addCommand("CreatorTree", "clear", cClear, "creator.clear();", 2, 2); + Con::addCommand("CreatorTree", "getParent", cGetParent, "creator.getParent(id);", 3, 3); +} + +//------------------------------------------------------------------------------ + +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, mBitmapBounds[BmpParentContinue].extent.y); + setSize(Point2I(1, mNodeList.size())); +} + +//------------------------------------------------------------------------------ +bool CreatorTree::onWake() +{ + if(!Parent::onWake()) + return(false); + + if(!mProfile->mTextureHandle.getBitmap()) + return(false); + + if(!createBitmapArray(mProfile->mTextureHandle.getBitmap(), mBitmapBounds, 1, BmpCount)) + return(false); + + mTabSize = mBitmapBounds[BmpParentContinue].extent.x; + + // + build(); + mCellSize.set(mMaxWidth + 1, mBitmapBounds[BmpParentContinue].extent.y); + setSize(Point2I(1, mNodeList.size())); + return true; +} + +//------------------------------------------------------------------------------ + +void CreatorTree::onMouseDown(const GuiEvent & event) +{ + if(!mActive) + { + Parent::onMouseDown(event); + return; + } + + 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) +{ + event; +} + +//------------------------------------------------------------------------------ + +void CreatorTree::onRenderCell(Point2I offset, Point2I cell, bool, bool) +{ + Node * node = mNodeList[cell.y]; + + // get the bitmap index... + S32 bmpIndex = BmpNone; + if(node->isGroup()) + { + if(node->isExpanded() || !node->mChildren.size()) + bmpIndex = BmpParentOpen; + else bmpIndex = BmpParentClosed; + } + + // + if(!node->isFirst()) + bmpIndex += 2; + if(!node->isLast()) + bmpIndex += 1; + + // + if(bmpIndex != BmpNone) + { + Point2I pos(offset.x + mTabSize * (mNodeList[cell.y]->mTab), offset.y); + dglClearBitmapModulation(); + dglDrawBitmapSR(mProfile->mTextureHandle, pos, mBitmapBounds[bmpIndex]); + } + + // draw the vertical line thingies + Node * parent = node->mParent; + while(!parent->isRoot()) + { + if(!parent->isLast()) + { + Point2I pos(offset.x + mTabSize * parent->mTab, offset.y); + dglClearBitmapModulation(); + dglDrawBitmapSR(mProfile->mTextureHandle, pos, mBitmapBounds[BmpParentContinue]); + } + parent = parent->mParent; + } + + // set the color + 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); + + dglSetBitmapModulation(fontColor); //node->isSelected() ? mProfile->mFontColorHL : mProfile->mFontColor); + dglDrawText(mProfile->mFont, Point2I(offset.x + mTxtOffset + mTabSize * (mNodeList[cell.y]->mTab + 1), offset.y), mNodeList[cell.y]->mName); +} diff --git a/editor/creator.h b/editor/creator.h new file mode 100644 index 0000000..91d4ff8 --- /dev/null +++ b/editor/creator.h @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CREATOR_H_ +#define _CREATOR_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GUIARRAYCTRL_H_ +#include "GUI/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(); + + // bitmap stuff taken from guiinspector.. + enum BitmapIndices + { + BmpNone = -1, + BmpChildAbove, + BmpChildBelow, + BmpChildBetween, + + BmpParentOpen, + BmpParentOpenAbove, + BmpParentOpenBelow, + BmpParentOpenBetween, + + BmpParentClosed, + BmpParentClosedAbove, + BmpParentClosedBelow, + BmpParentClosedBetween, + + BmpParentContinue, + + BmpCount + }; + + RectI mBitmapBounds[BmpCount]; + S32 mTabSize; + S32 mMaxWidth; + S32 mTxtOffset; + + // SimObject + static void consoleInit(); + + // GuiControl + void onMouseDown(const GuiEvent & event); + void onMouseDragged(const GuiEvent & event); + bool onWake(); + + // GuiArrayCtrl + void onRenderCell(Point2I offset, Point2I cell, bool, bool); + + DECLARE_CONOBJECT(CreatorTree); +}; + +#endif diff --git a/editor/editTSCtrl.cc b/editor/editTSCtrl.cc new file mode 100644 index 0000000..309ad1e --- /dev/null +++ b/editor/editTSCtrl.cc @@ -0,0 +1,583 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "editor/editTSCtrl.h" +#include "sceneGraph/sceneGraph.h" +#include "editor/editor.h" +#include "game/gameConnection.h" +#include "game/gameBase.h" +#include "game/missionArea.h" +#include "console/consoleTypes.h" +#include "terrain/terrData.h" +#include "game/game.h" +#include "game/sphere.h" + +IMPLEMENT_CONOBJECT(EditTSCtrl); + +//------------------------------------------------------------------------------ + +Point3F EditTSCtrl::smCamPos; +EulerF EditTSCtrl::smCamRot; +MatrixF EditTSCtrl::smCamMatrix; +F32 EditTSCtrl::smVisibleDistance = 2100.f; + +EditTSCtrl::EditTSCtrl() : + mEditManager(0) +{ + 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; + + mConsoleRendering = false; +} + +EditTSCtrl::~EditTSCtrl() +{ +} + +//------------------------------------------------------------------------------ + +bool EditTSCtrl::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + // give all derived access to the fields + setModStaticFields(true); + return true; +} + +void EditTSCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + updateGuiInfo(); + Parent::onRender(offset, updateRect, firstResponder); +} + +//------------------------------------------------------------------------------ + +void EditTSCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("renderMissionArea", TypeBool, Offset(mRenderMissionArea, EditTSCtrl)); + addField("missionAreaFillColor", TypeColorI, Offset(mMissionAreaFillColor, EditTSCtrl)); + addField("missionAreaFrameColor", TypeColorI, Offset(mMissionAreaFrameColor, EditTSCtrl)); + + 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)); + + Con::addVariable("pref::Editor::visibleDistance", TypeF32, &EditTSCtrl::smVisibleDistance); +} + +//------------------------------------------------------------------------------ + +void EditTSCtrl::make3DMouseEvent(Gui3DMouseEvent & gui3DMouseEvent, const GuiEvent & event) +{ + (GuiEvent&)(gui3DMouseEvent) = event; + + // get the eye pos and the mouse vec from that... + Point3F sp(event.mousePoint.x, event.mousePoint.y, 1); + + Point3F wp; + unproject(sp, &wp); + + gui3DMouseEvent.pos = smCamPos; + gui3DMouseEvent.vec = wp - smCamPos; + gui3DMouseEvent.vec.normalize(); +} + +//------------------------------------------------------------------------------ + +void EditTSCtrl::onMouseUp(const GuiEvent & event) +{ + Gui3DMouseEvent gui3DMouseEvent; + make3DMouseEvent(gui3DMouseEvent, event); + on3DMouseUp(gui3DMouseEvent); +} + +void EditTSCtrl::onMouseDown(const GuiEvent & event) +{ + Gui3DMouseEvent gui3DMouseEvent; + make3DMouseEvent(gui3DMouseEvent, event); + on3DMouseDown(gui3DMouseEvent); +} + +void EditTSCtrl::onMouseMove(const GuiEvent & event) +{ + Gui3DMouseEvent gui3DMouseEvent; + make3DMouseEvent(gui3DMouseEvent, event); + on3DMouseMove(gui3DMouseEvent); +} + +void EditTSCtrl::onMouseDragged(const GuiEvent & event) +{ + Gui3DMouseEvent gui3DMouseEvent; + make3DMouseEvent(gui3DMouseEvent, event); + on3DMouseDragged(gui3DMouseEvent); + +} + +void EditTSCtrl::onMouseEnter(const GuiEvent & event) +{ + Gui3DMouseEvent gui3DMouseEvent; + make3DMouseEvent(gui3DMouseEvent, event); + on3DMouseEnter(gui3DMouseEvent); +} + +void EditTSCtrl::onMouseLeave(const GuiEvent & event) +{ + Gui3DMouseEvent gui3DMouseEvent; + make3DMouseEvent(gui3DMouseEvent, event); + on3DMouseLeave(gui3DMouseEvent); +} + +void EditTSCtrl::onRightMouseDown(const GuiEvent & event) +{ + Gui3DMouseEvent gui3DMouseEvent; + make3DMouseEvent(gui3DMouseEvent, event); + on3DRightMouseDown(gui3DMouseEvent); +} + +void EditTSCtrl::onRightMouseUp(const GuiEvent & event) +{ + Gui3DMouseEvent gui3DMouseEvent; + make3DMouseEvent(gui3DMouseEvent, event); + on3DRightMouseUp(gui3DMouseEvent); +} + +void EditTSCtrl::onRightMouseDragged(const GuiEvent & event) +{ + Gui3DMouseEvent gui3DMouseEvent; + make3DMouseEvent(gui3DMouseEvent, event); + on3DRightMouseDragged(gui3DMouseEvent); +} + +//------------------------------------------------------------------------------ + +void EditTSCtrl::renderWorld(const RectI & updateRect) +{ + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glClear(GL_DEPTH_BUFFER_BIT); + glDisable(GL_CULL_FACE); + glMatrixMode(GL_MODELVIEW); + + dglSetCanonicalState(); + gClientSceneGraph->renderScene(); + + // render the mission area... + if(mRenderMissionArea) + renderMissionArea(); + + glDisable(GL_DEPTH_TEST); + + // render through console callbacks + SimSet * missionGroup = static_cast(Sim::findObject("MissionGroup")); + if(missionGroup) + { + mConsoleRendering = true; + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + + for(SimSetIterator itr(missionGroup); *itr; ++itr) + { + char buf[2][16]; + dSprintf(buf[0], 16, (*itr)->isSelected() ? "true" : "false"); + dSprintf(buf[1], 16, (*itr)->isExpanded() ? "true" : "false"); + Con::executef(*itr, 4, "onEditorRender", getIdString(), buf[0], buf[1]); + } + + glDisable(GL_DEPTH_TEST); + mConsoleRendering = false; + } + + // render the editor stuff + renderScene(updateRect); + + dglSetClipRect(updateRect); +} + +void EditTSCtrl::renderMissionArea() +{ + MissionArea * obj = dynamic_cast(Sim::findObject("MissionArea")); + TerrainBlock * terrain = dynamic_cast(Sim::findObject("Terrain")); + if(!terrain) + return; + + GridSquare * gs = terrain->findSquare(TerrainBlock::BlockShift, Point2I(0,0)); + F32 height = F32(gs->maxHeight) * 0.03125f + 10.f; + + // + const RectI &area = obj->getArea(); + Point2F min(area.point.x, area.point.y); + Point2F max(area.point.x + area.extent.x, area.point.y + area.extent.y); + + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + ColorI & a = mMissionAreaFillColor; + ColorI & b = mMissionAreaFrameColor; + for(U32 i = 0; i < 2; i++) + { + // + if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);} + glVertex3f(min.x, min.y, 0); + glVertex3f(max.x, min.y, 0); + glVertex3f(max.x, min.y, height); + glVertex3f(min.x, min.y, height); + glEnd(); + + // + if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);} + glVertex3f(min.x, max.y, 0); + glVertex3f(max.x, max.y, 0); + glVertex3f(max.x, max.y, height); + glVertex3f(min.x, max.y, height); + glEnd(); + + // + if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);} + glVertex3f(min.x, min.y, 0); + glVertex3f(min.x, max.y, 0); + glVertex3f(min.x, max.y, height); + glVertex3f(min.x, min.y, height); + glEnd(); + + // + if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);} + glVertex3f(max.x, min.y, 0); + glVertex3f(max.x, max.y, 0); + glVertex3f(max.x, max.y, height); + glVertex3f(max.x, min.y, height); + glEnd(); + } + + glDisable(GL_BLEND); +} + +//------------------------------------------------------------------------------ + +bool EditTSCtrl::processCameraQuery(CameraQuery * query) +{ + GameConnection* connection = dynamic_cast(NetConnection::getServerConnection()); + if (connection) + { + if (connection->getControlCameraTransform(0.032,&query->cameraMatrix)) { + query->nearPlane = 0.1; + query->farPlane = getMax(smVisibleDistance, 50.f); + query->fov = 3.1415 / 2; + + smCamMatrix = query->cameraMatrix; + smCamMatrix.getColumn(3,&smCamPos); + smCamRot.set(0,0,0); + return(true); + } + } + return(false); +} + +//------------------------------------------------------------------------------ +// sort the surfaces: not correct when camera is inside sphere but not +// inside tesselated representation of sphere.... +struct SortInfo { + U32 idx; + F32 dot; +}; + +static int QSORT_CALLBACK alphaSort(const void* p1, const void* p2) +{ + const SortInfo* ip1 = (const SortInfo*)p1; + const SortInfo* ip2 = (const SortInfo*)p2; + + if(ip1->dot > ip2->dot) + return(1); + if(ip1->dot == ip2->dot) + return(0); + return(-1); +} + +//------------------------------------------------------------------------------ +static void cRenderSphere(SimObject * obj, S32 argc, const char ** argv) +{ + EditTSCtrl * ctrl = static_cast(obj); + if(!ctrl->mConsoleRendering) + return; + + static Sphere sphere(Sphere::Icosahedron); + + if(!ctrl->mConsoleFrameColor.alpha && !ctrl->mConsoleFillColor.alpha) + return; + + S32 sphereLevel = ctrl->mConsoleSphereLevel; + if(argc == 5) + sphereLevel = dAtoi(argv[4]); + + const Sphere::TriangleMesh * mesh = sphere.getMesh(sphereLevel); + + Point3F pos; + dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z); + + F32 radius = dAtoi(argv[3]); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // sort the surfaces back->front + Vector sortInfos; + + Point3F camNormal = ctrl->smCamPos - pos; + camNormal.normalize(); + + sortInfos.setSize(mesh->numPoly); + for(U32 i = 0; i < mesh->numPoly; i++) + { + sortInfos[i].idx = i; + sortInfos[i].dot = mDot(camNormal, mesh->poly[i].normal); + } + dQsort(sortInfos.address(), sortInfos.size(), sizeof(SortInfo), alphaSort); + + // frame + if(ctrl->mConsoleFrameColor.alpha) + { + glColor4ub(ctrl->mConsoleFrameColor.red, + ctrl->mConsoleFrameColor.green, + ctrl->mConsoleFrameColor.blue, + ctrl->mConsoleFrameColor.alpha); + + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + glLineWidth(ctrl->mConsoleLineWidth); + glBegin(GL_TRIANGLES); + for(U32 i = 0; i < mesh->numPoly; i++) + { + Sphere::Triangle & tri = mesh->poly[sortInfos[i].idx]; + for(S32 j = 2; j >= 0; j--) + glVertex3f(tri.pnt[j].x * radius + pos.x, + tri.pnt[j].y * radius + pos.y, + tri.pnt[j].z * radius + pos.z); + } + glEnd(); + + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glLineWidth(1); + } + + // fill + if(ctrl->mConsoleFillColor.alpha) + { + glColor4ub(ctrl->mConsoleFillColor.red, + ctrl->mConsoleFillColor.green, + ctrl->mConsoleFillColor.blue, + ctrl->mConsoleFillColor.alpha); + + glBegin(GL_TRIANGLES); + for(U32 i = 0; i < mesh->numPoly; i++) + { + Sphere::Triangle & tri = mesh->poly[sortInfos[i].idx]; + for(S32 j = 2; j >= 0; j--) + glVertex3f(tri.pnt[j].x * radius + pos.x, + tri.pnt[j].y * radius + pos.y, + tri.pnt[j].z * radius + pos.z); + } + glEnd(); + } + glDisable(GL_BLEND); +} + +static void cRenderCircle(SimObject * obj, S32 argc, const char ** argv) +{ + EditTSCtrl * ctrl = static_cast(obj); + if(!ctrl->mConsoleRendering) + return; + + if(!ctrl->mConsoleFrameColor.alpha && !ctrl->mConsoleFillColor.alpha) + return; + + Point3F pos, normal; + dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z); + dSscanf(argv[3], "%f %f %f", &normal.x, &normal.y, &normal.z); + + F32 radius = dAtoi(argv[4]); + + S32 segments = ctrl->mConsoleCircleSegments; + if(argc == 6) + segments = dAtoi(argv[5]); + + normal.normalize(); + + AngAxisF aa; + mCross(normal, Point3F(0,0,1), &aa.axis); + 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; + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + 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; + } + + // framed + if(ctrl->mConsoleFrameColor.alpha) + { + glColor4ub(ctrl->mConsoleFrameColor.red, + ctrl->mConsoleFrameColor.green, + ctrl->mConsoleFrameColor.blue, + ctrl->mConsoleFrameColor.alpha); + glLineWidth(ctrl->mConsoleLineWidth); + glBegin(GL_LINE_LOOP); + for(U32 i = 0; i < points.size(); i++) + glVertex3f(points[i].x, points[i].y, points[i].z); + glEnd(); + glLineWidth(1); + } + + // filled + if(ctrl->mConsoleFillColor.alpha) + { + glColor4ub(ctrl->mConsoleFillColor.red, + ctrl->mConsoleFillColor.green, + ctrl->mConsoleFillColor.blue, + ctrl->mConsoleFillColor.alpha); + + glBegin(GL_TRIANGLES); + + for(S32 i = 0; i < points.size(); i++) + { + S32 j = (i + 1) % points.size(); + glVertex3f(points[i].x, points[i].y, points[i].z); + glVertex3f(points[j].x, points[j].y, points[j].z); + glVertex3f(pos.x, pos.y, pos.z); + } + + glEnd(); + } + + glDisable(GL_BLEND); +} + +static void cRenderTriangle(SimObject * obj, S32, const char ** argv) +{ + EditTSCtrl * ctrl = static_cast(obj); + if(!ctrl->mConsoleRendering) + return; + + if(!ctrl->mConsoleFrameColor.alpha && !ctrl->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); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // frame + if(ctrl->mConsoleFrameColor.alpha) + { + glColor4ub(ctrl->mConsoleFrameColor.red, + ctrl->mConsoleFrameColor.green, + ctrl->mConsoleFrameColor.blue, + ctrl->mConsoleFrameColor.alpha); + glLineWidth(ctrl->mConsoleLineWidth); + glBegin(GL_LINE_LOOP); + for(U32 i = 0; i < 3; i++) + glVertex3f(pnts[i].x, pnts[i].y, pnts[i].z); + glEnd(); + glLineWidth(1); + } + + // fill + if(ctrl->mConsoleFillColor.alpha) + { + glColor4ub(ctrl->mConsoleFillColor.red, + ctrl->mConsoleFillColor.green, + ctrl->mConsoleFillColor.blue, + ctrl->mConsoleFillColor.alpha); + + glBegin(GL_TRIANGLES); + for(U32 i = 0; i < 3; i++) + glVertex3f(pnts[i].x, pnts[i].y, pnts[i].z); + glEnd(); + } + glDisable(GL_BLEND); +} + +static void cRenderLine(SimObject * obj, S32 argc, const char ** argv) +{ + EditTSCtrl * ctrl = static_cast(obj); + if(!ctrl->mConsoleRendering) + return; + + if(!ctrl->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); + + S32 width = ctrl->mConsoleLineWidth; + if(argc == 5) + width = dAtoi(argv[4]); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glColor4ub(ctrl->mConsoleFrameColor.red, + ctrl->mConsoleFrameColor.green, + ctrl->mConsoleFrameColor.blue, + ctrl->mConsoleFrameColor.alpha); + + glLineWidth(width); + + glBegin(GL_LINES); + glVertex3f(start.x, start.y, start.z); + glVertex3f(end.x, end.y, end.z); + glEnd(); + + glLineWidth(1); + glDisable(GL_BLEND); +} + +//------------------------------------------------------------------------------ +void EditTSCtrl::consoleInit() +{ + Con::addCommand("EditTSCtrl", "renderSphere", cRenderSphere, "EditTSCtrl.renderSphere(pos, radius, ", 4, 5); + Con::addCommand("EditTSCtrl", "renderCircle", cRenderCircle, "EditTSCtrl.renderCircle(pos, normal, radius, ", 5, 6); + Con::addCommand("EditTSCtrl", "renderTriangle", cRenderTriangle, "EditTSCtrl.renderTriangle(pnt, pnt, pnt)", 5, 5); + Con::addCommand("EditTSCtrl", "renderLine", cRenderLine, "EditTSCtrl.renderLine(start, end, ", 4, 5); +} + diff --git a/editor/editTSCtrl.h b/editor/editTSCtrl.h new file mode 100644 index 0000000..29c9817 --- /dev/null +++ b/editor/editTSCtrl.h @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EDITTSCTRL_H_ +#define _EDITTSCTRL_H_ + +#ifndef _GUITSCONTROL_H_ +#include "GUI/guiTSControl.h" +#endif + +struct Gui3DMouseEvent : public GuiEvent +{ + Point3F vec; + Point3F pos; +}; + +class EditManager; + +class EditTSCtrl : public GuiTSCtrl +{ + private: + typedef GuiTSCtrl Parent; + + // EditTSCtrl + void make3DMouseEvent(Gui3DMouseEvent & gui3Devent, const GuiEvent & event); + 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); + void onRightMouseDown(const GuiEvent & event); + void onRightMouseUp(const GuiEvent & event); + void onRightMouseDragged(const GuiEvent & event); + + virtual void updateGuiInfo() {}; + virtual void renderScene(const RectI &){}; + void renderMissionArea(); + + // GuiTSCtrl + void renderWorld(const RectI & updateRect); + + protected: + EditManager * mEditManager; + + public: + + EditTSCtrl(); + ~EditTSCtrl(); + + // SimObject + bool onAdd(); + + // + bool mRenderMissionArea; + ColorI mMissionAreaFillColor; + ColorI mMissionAreaFrameColor; + + // + ColorI mConsoleFrameColor; + ColorI mConsoleFillColor; + S32 mConsoleSphereLevel; + S32 mConsoleCircleSegments; + S32 mConsoleLineWidth; + + static void initPersistFields(); + static void consoleInit(); + + // + bool mConsoleRendering; + + // all editors will share a camera + static Point3F smCamPos; + static EulerF smCamRot; + static MatrixF smCamMatrix; + static F32 smVisibleDistance; + + // GuiTSCtrl + bool processCameraQuery(CameraQuery * query); + + // guiControl + virtual void onRender(Point2I, const RectI &, GuiControl *); + 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 &){}; + + DECLARE_CONOBJECT(EditTSCtrl); +}; + +#endif diff --git a/editor/editor.cc b/editor/editor.cc new file mode 100644 index 0000000..6b4c04f --- /dev/null +++ b/editor/editor.cc @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "editor/editor.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "gui/guiTextListCtrl.h" +#include "platform/event.h" +#include "game/shapeBase.h" +#include "game/gameConnection.h" + +bool gEditingMission = false; + +//------------------------------------------------------------------------------ +// 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); + + for(SimSetIterator itr(Sim::getRootGroup()); *itr; ++itr) + (*itr)->onEditorEnable(); + + gEditingMission = true; + + return(true); +} + +void EditManager::onSleep() +{ + for(SimSetIterator itr(Sim::getRootGroup()); *itr; ++itr) + (*itr)->onEditorDisable(); + + gEditingMission = false; + 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); +} + +//------------------------------------------------------------------------------ + +static GameBase * getControlObj() +{ + GameConnection * connection = GameConnection::getLocalClientConnection(); + ShapeBase* control = 0; + if(connection) + control = connection->getControlObject(); + return(control); +} + +static void cSetBookmark(SimObject * obj, S32, const char ** argv) +{ + S32 val = dAtoi(argv[2]); + if(val < 0 || val > 9) + return; + + EditManager * editor = static_cast(obj); + GameBase * control = getControlObj(); + if(control) + editor->mBookmarks[val] = control->getTransform(); +} + +static void cGotoBookmark(SimObject * obj, S32, const char ** argv) +{ + S32 val = dAtoi(argv[2]); + if(val < 0 || val > 9) + return; + + EditManager * editor = static_cast(obj); + GameBase * control = getControlObj(); + if(control) + control->setTransform(editor->mBookmarks[val]); +} + +void EditManager::consoleInit() +{ + Con::addCommand("EditManager", "setBookmark", cSetBookmark, "editor.setBookmark(<1-0>);", 3, 3); + Con::addCommand("EditManager", "gotoBookmark", cGotoBookmark, "editor.gotoBookmark(<1-0>);", 3, 3); +} diff --git a/editor/editor.h b/editor/editor.h new file mode 100644 index 0000000..85ed1ae --- /dev/null +++ b/editor/editor.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EDITOR_H_ +#define _EDITOR_H_ + +#ifndef _MMATRIX_H_ +#include "Math/mMatrix.h" +#endif +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +class GameBase; + +//------------------------------------------------------------------------------ + +class EditManager : public GuiControl +{ + private: + typedef GuiControl Parent; + + public: + EditManager(); + ~EditManager(); + + bool onWake(); + void onSleep(); + + // SimObject + bool onAdd(); + static void consoleInit(); + + MatrixF mBookmarks[10]; + DECLARE_CONOBJECT(EditManager); +}; + +extern bool gEditingMission; + +//------------------------------------------------------------------------------ + +#endif diff --git a/editor/editorButtonCtrl.cc b/editor/editorButtonCtrl.cc new file mode 100644 index 0000000..ea17beb --- /dev/null +++ b/editor/editorButtonCtrl.cc @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Editor/editorButtonCtrl.h" +#include "console/consoleTypes.h" +#include "GUI/guiCanvas.h" +#include "dgl/dgl.h" + +IMPLEMENT_CONOBJECT(EditorButtonCtrl); + +EditorButtonCtrl::EditorButtonCtrl() +{ + mBitmapName = StringTable->insert(""); + mTextureHandle = 0; + mMouseOverColor.set(200,0,0); + mDepressedAlpha = 0.2; +} + +//------------------------------------------------------------------------------ + +void EditorButtonCtrl::onRender(Point2I offset, const RectI & updateRect, + GuiControl * firstResponder) +{ + GuiCanvas * root = getRoot(); + bool stateOver = cursorInControl(); + bool stateDepressed = (stateOver && root && root->mouseButtonDown()); + + if(mTextureHandle) + { + dglClearBitmapModulation(); + dglDrawBitmapStretch(mTextureHandle, updateRect); + } + else + { + glColor4f(0, 0, 0, 1); + glBegin(GL_LINE_LOOP); + glVertex2i(updateRect.point.x, updateRect.point.y); + glVertex2i(updateRect.point.x+updateRect.extent.x-1, updateRect.point.y); + glVertex2i(updateRect.point.x+updateRect.extent.x-1, updateRect.point.y+updateRect.extent.y-1); + glVertex2i(updateRect.point.x, updateRect.point.y+updateRect.extent.y-1); + glEnd(); + } + + // + if(stateOver) + dglDrawRect(updateRect, mMouseOverColor); + + // + if(stateDepressed) + { + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA); + glBegin(GL_QUADS); + glColor4f(1,1,1,mDepressedAlpha); + glVertex2i(updateRect.point.x, updateRect.point.y); + glVertex2i(updateRect.point.x+updateRect.extent.x-1, updateRect.point.y); + glVertex2i(updateRect.point.x+updateRect.extent.x-1, updateRect.point.y+updateRect.extent.y-1); + glVertex2i(updateRect.point.x, updateRect.point.y+updateRect.extent.y-1); + glEnd(); + glDisable(GL_BLEND); + } + renderChildControls(offset, updateRect, firstResponder); +} + +//------------------------------------------------------------------------------ + +void EditorButtonCtrl::setBitmap(const char * name) +{ + mBitmapName = StringTable->insert(name); + if(mBitmapName[0]) + mTextureHandle = TextureHandle(mBitmapName, BitmapTexture); + else + mTextureHandle = 0; + setUpdate(); +} + +//------------------------------------------------------------------------------ + +bool EditorButtonCtrl::onWake() +{ + if(!Parent::onWake()) + return false; + setActive(true); + setBitmap(mBitmapName); + return(true); +} + +void EditorButtonCtrl::onSleep() +{ + mTextureHandle = 0; + Parent::onSleep(); +} + +//------------------------------------------------------------------------------ + +void EditorButtonCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("bitmap", TypeString, Offset(mBitmapName, EditorButtonCtrl)); + addField("mouseOverColor", TypeColorI, Offset(mMouseOverColor, EditorButtonCtrl)); + addField("depressedAlpha", TypeF32, Offset(mDepressedAlpha, EditorButtonCtrl)); +} + +static void cSetBitmap(SimObject * obj, S32, const char ** argv) +{ + EditorButtonCtrl * ctrl = static_cast(obj); + ctrl->setBitmap(argv[2]); +} + +void EditorButtonCtrl::consoleInit() +{ + Con::addCommand("EditorButtonCtrl", "setBitmap", cSetBitmap, "editorButtonCtrl.setBitmap(name);", 3, 3); +} + diff --git a/editor/editorButtonCtrl.h b/editor/editorButtonCtrl.h new file mode 100644 index 0000000..335dbd4 --- /dev/null +++ b/editor/editorButtonCtrl.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EDITORBUTTONCTRL_H_ +#define _EDITORBUTTONCTRL_H_ + +#ifndef _GUIBUTTONCTRL_H_ +#include "GUI/guiButtonCtrl.h" +#endif + +// JFF - move into gui once finished + +class EditorButtonCtrl : public GuiButtonCtrl +{ + private: + typedef GuiButtonCtrl Parent; + + protected: + TextureHandle mTextureHandle; + StringTableEntry mBitmapName; + ColorI mMouseOverColor; + F32 mDepressedAlpha; + + public: + DECLARE_CONOBJECT(EditorButtonCtrl); + + EditorButtonCtrl(); + + // SimObject + static void initPersistFields(); + static void consoleInit(); + + // GuiControl + bool onWake(); + void onSleep(); + + void setBitmap(const char *name); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); +}; + +#endif diff --git a/editor/editorCheckboxCtrl.cc b/editor/editorCheckboxCtrl.cc new file mode 100644 index 0000000..bbac5b6 --- /dev/null +++ b/editor/editorCheckboxCtrl.cc @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Editor/editorCheckboxCtrl.h" +#include "console/consoleTypes.h" +#include "GUI/guiCanvas.h" +#include "dgl/dgl.h" + +IMPLEMENT_CONOBJECT(EditorCheckBoxCtrl); + +EditorCheckBoxCtrl::EditorCheckBoxCtrl() +{ + mBitmapName = StringTable->insert(""); + mTextureHandle = 0; + mMouseOverColor.set(200,0,0); + mDepressedAlpha = 0.2; +} + +//------------------------------------------------------------------------------ + +void EditorCheckBoxCtrl::onRender(Point2I offset, const RectI & updateRect, + GuiControl * firstResponder) +{ + GuiCanvas * root = getRoot(); + bool stateOver = cursorInControl(); + bool stateDepressed = (stateOver && root && root->mouseButtonDown()); + + if(mTextureHandle) + { + dglClearBitmapModulation(); + dglDrawBitmapStretch(mTextureHandle, updateRect); + } + else + { + glColor4f(0, 0, 0, 1); + glBegin(GL_LINE_LOOP); + glVertex2i(updateRect.point.x, updateRect.point.y); + glVertex2i(updateRect.point.x+updateRect.extent.x-1, updateRect.point.y); + glVertex2i(updateRect.point.x+updateRect.extent.x-1, updateRect.point.y+updateRect.extent.y-1); + glVertex2i(updateRect.point.x, updateRect.point.y+updateRect.extent.y-1); + glEnd(); + } + + // + if(stateOver) + Parent::drawBorder(updateRect, mMouseOverColor); + + // + if(!mStateOn) + { + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA); + glBegin(GL_QUADS); + glColor4f(1,1,1,mDepressedAlpha); + glVertex2i(updateRect.point.x, updateRect.point.y); + glVertex2i(updateRect.point.x+updateRect.extent.x-1, updateRect.point.y); + glVertex2i(updateRect.point.x+updateRect.extent.x-1, updateRect.point.y+updateRect.extent.y-1); + glVertex2i(updateRect.point.x, updateRect.point.y+updateRect.extent.y-1); + glEnd(); + glDisable(GL_BLEND); + } + renderChildControls(offset, updateRect, firstResponder); +} + +//------------------------------------------------------------------------------ + +void EditorCheckBoxCtrl::setBitmap(const char * name) +{ + mBitmapName = StringTable->insert(name); + if(mBitmapName[0]) + mTextureHandle = TextureHandle(mBitmapName, BitmapTexture); + else + mTextureHandle = 0; + setUpdate(); +} + +//------------------------------------------------------------------------------ + +bool EditorCheckBoxCtrl::onWake() +{ + if(!Parent::onWake()) + return false; + setActive(true); + setBitmap(mBitmapName); + return(true); +} + +void EditorCheckBoxCtrl::onSleep() +{ + mTextureHandle = 0; + Parent::onSleep(); +} + +//------------------------------------------------------------------------------ + +void EditorCheckBoxCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("bitmap", TypeString, Offset(mBitmapName, EditorCheckBoxCtrl)); + addField("mouseOverColor", TypeColorI, Offset(mMouseOverColor, EditorCheckBoxCtrl)); + addField("depressedAlpha", TypeF32, Offset(mDepressedAlpha, EditorCheckBoxCtrl)); +} + +static void cSetBitmap(SimObject * obj, S32, const char ** argv) +{ + EditorCheckBoxCtrl * ctrl = static_cast(obj); + ctrl->setBitmap(argv[2]); +} + +void EditorCheckBoxCtrl::consoleInit() +{ + Con::addCommand("EditorCheckBoxCtrl", "setBitmap", cSetBitmap, "editorButtonCtrl.setBitmap(name);", 3, 3); +} diff --git a/editor/editorCheckboxCtrl.h b/editor/editorCheckboxCtrl.h new file mode 100644 index 0000000..aaec70d --- /dev/null +++ b/editor/editorCheckboxCtrl.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EDITORCHECKBOXCTRL_H_ +#define _EDITORCHECKBOXCTRL_H_ + +#ifndef _GUICHECKBOXCTRL_H_ +#include "GUI/guiCheckBoxCtrl.h" +#endif + +// JFF - move into gui once finished + +class EditorCheckBoxCtrl : public GuiCheckBoxCtrl +{ + private: + typedef GuiCheckBoxCtrl Parent; + + protected: + TextureHandle mTextureHandle; + StringTableEntry mBitmapName; + ColorI mMouseOverColor; + F32 mDepressedAlpha; + + public: + DECLARE_CONOBJECT(EditorCheckBoxCtrl); + + EditorCheckBoxCtrl(); + + // SimObject + static void initPersistFields(); + static void consoleInit(); + + // GuiControl + bool onWake(); + void onSleep(); + + void setBitmap(const char *name); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); +}; +#endif diff --git a/editor/guiTerrPreviewCtrl.cc b/editor/guiTerrPreviewCtrl.cc new file mode 100644 index 0000000..15578b0 --- /dev/null +++ b/editor/guiTerrPreviewCtrl.cc @@ -0,0 +1,324 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "platformWin32/platformGL.h" +#include "game/game.h" +#include "terrain/terrData.h" +#include "editor/guiTerrPreviewCtrl.h" + +IMPLEMENT_CONOBJECT(GuiTerrPreviewCtrl); + +GuiTerrPreviewCtrl::GuiTerrPreviewCtrl(void) +{ + mTerrainSize = 2048.0f; + mRoot.set( 0, 0 ); + mOrigin.set( 0, 0 ); + mWorldScreenCenter.set( mTerrainSize*0.5f, mTerrainSize*0.5f ); +} + +void GuiTerrPreviewCtrl::initPersistFields() +{ + Parent::initPersistFields(); +} + + +static void cTerrPreview_Reset(SimObject *obj, S32, const char **) +{ + static_cast(obj)->reset(); +} + +static void cTerrPreview_SetRoot(SimObject *obj, S32, const char **) +{ + static_cast(obj)->setRoot(); +} + +static const char* cTerrPreview_GetRoot(SimObject *obj, S32, const char **) +{ + GuiTerrPreviewCtrl *ctrl = static_cast(obj); + Point2F p = ctrl->getRoot(); + + static char buf[32]; + dSprintf(buf,sizeof(buf),"%f %f", p.x, -p.y); + return buf; +} + +static void cTerrPreview_SetOrigin(SimObject *obj, S32, const char **argv) +{ + GuiTerrPreviewCtrl *ctrl = static_cast(obj); + ctrl->setOrigin( Point2F( dAtof(argv[2]), -dAtof(argv[3]) ) ); +} + +static const char* cTerrPreview_GetOrigin(SimObject *obj, S32, const char **) +{ + GuiTerrPreviewCtrl *ctrl = static_cast(obj); + Point2F p = ctrl->getOrigin(); + + static char buf[32]; + dSprintf(buf,sizeof(buf),"%f %f", p.x, -p.y); + return buf; +} + +static const char* cTerrPreview_GetValue(SimObject *obj, S32, const char **) +{ + GuiTerrPreviewCtrl *ctrl = static_cast(obj); + Point2F r = ctrl->getRoot(); + Point2F o = ctrl->getOrigin(); + + static char buf[64]; + dSprintf(buf,sizeof(buf),"%f %f %f %f", r.x, -r.y, o.x, -o.y); + return buf; +} + +static void cTerrPreview_SetValue(SimObject *obj, S32, const char **argv) +{ + Point2F r,o; + GuiTerrPreviewCtrl *ctrl = static_cast(obj); + dSscanf(argv[1],"%f %f %f %f", &r.x, &r.y, &o.x, &o.y); + r.y = -r.y; + o.y = -o.y; + ctrl->reset(); + ctrl->setRoot(r); + ctrl->setOrigin(o); +} + + +void GuiTerrPreviewCtrl::consoleInit() +{ + Con::addCommand("GuiTerrPreviewCtrl", "reset", cTerrPreview_Reset, "guiTerrPreviewCtrl.reset()", 2, 2); + Con::addCommand("GuiTerrPreviewCtrl", "setRoot", cTerrPreview_SetRoot, "guiTerrPreviewCtrl.setRoot()", 2, 2); + Con::addCommand("GuiTerrPreviewCtrl", "getRoot", cTerrPreview_GetRoot, "guiTerrPreviewCtrl.getRoot()", 2, 2); + Con::addCommand("GuiTerrPreviewCtrl", "setOrigin", cTerrPreview_SetOrigin, "guiTerrPreviewCtrl.setOrigin(x,y)", 4, 4); + Con::addCommand("GuiTerrPreviewCtrl", "getOrigin", cTerrPreview_GetOrigin, "guiTerrPreviewCtrl.getOrigin()", 2, 2); + Con::addCommand("GuiTerrPreviewCtrl", "getValue", cTerrPreview_GetValue, "guiTerrPreviewCtrl.getValue()", 2, 2); + Con::addCommand("GuiTerrPreviewCtrl", "setValue", cTerrPreview_SetValue, "guiTerrPreviewCtrl.getValue(t)", 3, 3); +} + + +bool GuiTerrPreviewCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + return true; +} + +void GuiTerrPreviewCtrl::onSleep() +{ + Parent::onSleep(); +} + + +void GuiTerrPreviewCtrl::setBitmap(const TextureHandle &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 + mOrigin - mWorldScreenCenter ); + result *= mBounds.extent.x / mTerrainSize; + return result; +} + + +void GuiTerrPreviewCtrl::onPreRender() +{ + setUpdate(); +} + +void GuiTerrPreviewCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + struct CameraQuery query; + GameProcessCameraQuery(&query); + Point3F cameraRot; + + MatrixF matrix = query.cameraMatrix; + matrix.getColumn(3,&cameraRot); // get Camera translation + mCamera.set(cameraRot.x, -cameraRot.y); + matrix.getRow(1,&cameraRot); // get camera rotation + + mTerrainSize = 8*256; + TerrainBlock *terrBlock = dynamic_cast(Sim::findObject("Terrain")); + if (terrBlock) + mTerrainSize = terrBlock->getSquareSize()*TerrainBlock::BlockSize; + + + //----------------------------------------- RENDER the Terrain Bitmap + if (mTextureHandle) + { + TextureObject *texture = (TextureObject*)mTextureHandle; + if (texture) + { + glEnable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, texture->texGLName); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + Point2F screenP1(offset.x - 0.5f, offset.y + 0.5f); + Point2F screenP2(offset.x + mBounds.extent.x - 0.5f, offset.y + mBounds.extent.x + 0.5f); + Point2F textureP1( worldToTexture( mCamera ) ); + Point2F textureP2(textureP1 + Point2F(1.0f, 1.0f)); + + // the texture if flipped horz to reflect how the terrain is really drawn + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f(textureP1.x, textureP2.y); + glVertex2f(screenP1.x, screenP2.y); // left bottom + + glTexCoord2f(textureP2.x, textureP2.y); + glVertex2f(screenP2.x, screenP2.y); // right bottom + + glTexCoord2f(textureP2.x, textureP1.y); + glVertex2f(screenP2.x, screenP1.y); // right top + + glTexCoord2f(textureP1.x, textureP1.y); + glVertex2f(screenP1.x, screenP1.y); // left top + glEnd(); + + glDisable(GL_TEXTURE_2D); + } + } + else + { + RectI rect(offset.x, offset.y, mBounds.extent.x, mBounds.extent.y); + dglDrawRectFill(rect, ColorI(0,0,0)); + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //----------------------------------------- RENDER the '+' at the center of the Block + + glColor4f(1.0f, 1.0f, 1.0f, 0.7f); + Point2F center( worldToCtrl(Point2F(0,0)) ); + 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; + glBegin(GL_LINES); + glVertex2f(xoffset + center.x, yoffset + center.y-5); + glVertex2f(xoffset + center.x, yoffset + center.y+6); + glVertex2f(xoffset + center.x-5, yoffset + center.y); + glVertex2f(xoffset + center.x+6, yoffset + center.y); + glEnd(); + } + } + + //----------------------------------------- RENDER the Block Corners + Point2F corner( worldToCtrl(Point2F(-mTerrainSize/2.0f, -mTerrainSize/2.0f)) ); + 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; + glBegin(GL_LINE_STRIP); + glColor4f(1.0f, 1.0f, 1.0f, 0.0f); + glVertex2i(xoffset + corner.x, yoffset + corner.y-128); + glColor4f(1.0f, 1.0f, 1.0f, 0.7f); + glVertex2i(xoffset + corner.x, yoffset + corner.y); + glColor4f(1.0f, 1.0f, 1.0f, 0.0f); + glVertex2i(xoffset + corner.x+128, yoffset + corner.y); + glEnd(); + glBegin(GL_LINE_STRIP); + glColor4f(1.0f, 1.0f, 1.0f, 0.0f); + glVertex2i(xoffset + corner.x, yoffset + corner.y+128); + glColor4f(1.0f, 1.0f, 1.0f, 0.7f); + glVertex2i(xoffset + corner.x, yoffset + corner.y); + glColor4f(1.0f, 1.0f, 1.0f, 0.0f); + glVertex2i(xoffset + corner.x-128, yoffset + corner.y); + glEnd(); + } + } + + + //----------------------------------------- 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(offset.x + mBounds.extent.x / 2, offset.y + mBounds.extent.y / 2 ); + glBegin(GL_LINE_STRIP); + glColor4f(1.0f, 0.0f, 0.0f, 0.7f); + glVertex2i(center.x + point1.x, center.y + point1.y); + glColor4f(1.0f, 0.0f, 0.0f, 1.0f); + glVertex2i(center.x, center.y); + glColor4f(1.0f, 0.0f, 0.0f, 0.7f); + glVertex2i(center.x + point2.x, center.y + point2.y); + glEnd(); + + glDisable(GL_BLEND); + + /* debuging stuff + Point2I loc(offset.x +5, offset.y+10); + dglSetBitmapModulation(mProfile->mFontColor); + dglDrawText(mProfile->mFont, loc, avar("mCamera(%3.2f, %3.2f)", mCamera.x, mCamera.y)); loc.y += 10; + dglDrawText(mProfile->mFont, loc, avar("mRoot(%3.2f, %3.2f)", mRoot.x, mRoot.y)); loc.y += 10; + dglDrawText(mProfile->mFont, loc, avar("mOrigin(%3.2f, %3.2f)", mOrigin.x, mOrigin.y)); loc.y += 10; + */ + + renderChildControls(offset, updateRect, firstResponder); +} + + diff --git a/editor/guiTerrPreviewCtrl.h b/editor/guiTerrPreviewCtrl.h new file mode 100644 index 0000000..dadaf17 --- /dev/null +++ b/editor/guiTerrPreviewCtrl.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITERRPREVIEWCTRL_H_ +#define _GUITERRPREVIEWCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif +#ifndef _GUITSCONTROL_H_ +#include "GUI/guiTSControl.h" +#endif + +class GuiTerrPreviewCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + TextureHandle mTextureHandle; + Point2F mRoot; + Point2F mOrigin; + Point2F mWorldScreenCenter; + Point2F mCamera; + F32 mTerrainSize; + + Point2F& wrap(const Point2F &p); + Point2F& worldToTexture(const Point2F &p); + Point2F& worldToCtrl(const Point2F &p); + + +public: + //creation methods + DECLARE_CONOBJECT(GuiTerrPreviewCtrl); + GuiTerrPreviewCtrl(); + static void initPersistFields(); + static void consoleInit(); + + //Parental methods + bool onWake(); + void onSleep(); + + void setBitmap(const TextureHandle &handle); + + 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, GuiControl *firstResponder); +}; + + +#endif diff --git a/editor/missionAreaEditor.cc b/editor/missionAreaEditor.cc new file mode 100644 index 0000000..28b045c --- /dev/null +++ b/editor/missionAreaEditor.cc @@ -0,0 +1,1103 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "editor/missionAreaEditor.h" +#include "dgl/gBitmap.h" +#include "terrain/terrData.h" +#include "sim/sceneObject.h" +#include "dgl/dgl.h" +#include "console/consoleTypes.h" +#include "console/objectTypes.h" +#include "gui/guiCanvas.h" +#include "gui/guiTSControl.h" +#include "game/game.h" +#include "game/shapeBase.h" +#include "game/gameConnection.h" +#include "core/bitMatrix.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 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 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); +} + +//------------------------------------------------------------------------------ + +const RectI & MissionAreaEditor::getArea() +{ + AssertFatal(bool(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(bool(mMissionArea), "MissionAreaEditor::setArea: no MissionArea obj!"); + if(bool(mMissionArea)) + { + RectI clamped = area; + if(clampArea(clamped)) + { + mMissionArea->setArea(clamped); + onUpdate(); + } + } +} + +//------------------------------------------------------------------------------ + +GuiCursor * MissionAreaEditor::getCursor() +{ + AssertFatal(mCurrentCursor, "MissionAreaEditor::getCursor: no cursor to get!"); + return(mCurrentCursor); +} + +void MissionAreaEditor::setCursor(U32 cursor) +{ + AssertFatal(cursor < NumCursors, "MissionAreaEditor::setCursor: invalid cursor"); + mCurrentCursor = mCursors[cursor]; + Canvas->setCursor(mCurrentCursor); +} + +//------------------------------------------------------------------------------ + +bool MissionAreaEditor::grabCursors() +{ + struct { + U32 index; + const char * name; + } infos[] = { + {DefaultCursor, "DefaultCursor" }, + {HandCursor, "Editor_HandCursor" }, + {GrabCursor, "Editor_MoveCursor" }, + {VertResizeCursor, "UpDownCursor" }, + {HorizResizeCursor, "LeftRightCursor" }, + {DiagRightResizeCursor, "DiagRightCursor" }, + {DiagLeftResizeCursor, "DiagLeftCursor" } + }; + + 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() +{ + GBitmap * bitmap = new GBitmap(TerrainBlock::BlockSize, TerrainBlock::BlockSize, false, GBitmap::RGB); + 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; + + 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); +} + +//------------------------------------------------------------------------------ + +bool MissionAreaEditor::onAdd() +{ + if(!Parent::onAdd()) + return(false); + if(!grabCursors()) + return(false); + return(true); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::updateTerrainBitmap() +{ + const GBitmap * bitmap = createTerrainBitmap(); + if(bitmap) + setBitmap(TextureHandle("maTerrain", bitmap, true)); +} + +bool MissionAreaEditor::onWake() +{ + if(!Parent::onWake()) + return(false); + + mMissionArea = const_cast(MissionArea::getServerObject()); + if(!bool(mMissionArea)) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::onWake: no MissionArea object."); + return(false); + } + + mTerrainBlock = getTerrainObj(); + if(!bool(mTerrainBlock)) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::onWake: no TerrainBlock object."); + return(false); + } + + updateTerrainBitmap(); + + // make sure mission area is clamped + setArea(getArea()); + + onUpdate(); + setActive(true); + + return(true); +} + +void MissionAreaEditor::onSleep() +{ + mTextureHandle = 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, 2, "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))); +} + +Point2F MissionAreaEditor::screenToWorld(const Point2F & pos) +{ + return(Point2F((pos.x - mCenterPos.x) / mScale.x, (pos.y - mCenterPos.y) / mScale.y)); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::getScreenMissionArea(RectI & rect) +{ + RectI area = mMissionArea->getArea(); + Point2F pos = worldToScreen(Point2F(area.point.x, area.point.y)); + Point2F end = worldToScreen(Point2F(area.point.x + area.extent.x, 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(area.point.x, area.point.y)); + Point2F end = worldToScreen(Point2F(area.point.x + area.extent.x, 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 = F32(mTerrainBlock->getSquareSize() * TerrainBlock::BlockSize); + + 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, S32 val) +{ + Vector * list = (Vector*)val; + list->push_back(obj); +} + +void MissionAreaEditor::onRender(Point2I offset, const RectI & updateRect, GuiControl * firstResponder) +{ + RectI rect = updateRect; + + setUpdate(); + + // draw an x + if(!bool(mMissionArea) || !bool(mTerrainBlock)) + { + glBegin(GL_LINES); + glColor3f(0,0,0); + glVertex2f(rect.point.x, updateRect.point.y); + glVertex2f(rect.point.x + updateRect.extent.x, updateRect.point.y + updateRect.extent.y); + glVertex2f(rect.point.x, updateRect.point.y + updateRect.extent.y); + glVertex2f(rect.point.x + updateRect.extent.x, updateRect.point.y); + 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; + dglSetClipRect(rect); + + dglClearBitmapModulation(); + dglDrawBitmapStretch(mTextureHandle, rect); + + // draw all the objects + Vector objects; + U32 mask = InteriorObjectType | PlayerObjectType | VehicleObjectType | StaticShapeObjectType | WaterObjectType | TriggerObjectType; + gServerContainer.findObjects(mask, findObjectsCallback, (S32)&objects); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin(GL_QUADS); + + // project 'em + for(U32 i = 0; i < objects.size(); i++) + { + // get the color + if(objects[i]->getTypeMask() & WaterObjectType) + glColor4ub(mWaterObjectColor.red, mWaterObjectColor.green, mWaterObjectColor.blue, mWaterObjectColor.alpha); + else + glColor4ub(mDefaultObjectColor.red, mDefaultObjectColor.green, mDefaultObjectColor.blue, mDefaultObjectColor.alpha); + + const Box3F & objBox = objects[i]->getObjBox(); + const MatrixF & objTransform = objects[i]->getTransform(); + const VectorF & objScale = objects[i]->getScale(); + + U32 numPlanes = 0; + PlaneF testPlanes[3]; + U32 planeIndices[3]; + + U32 j; + for(j = 0; (j < 6) && (numPlanes < 3); j++) + { + PlaneF plane; + plane.x = BoxNormals[j].x; + plane.y = BoxNormals[j].y; + plane.z = BoxNormals[j].z; + + if(j&1) + plane.d = (((const F32 *)objBox.min)[(j-1)>>1]); + else + plane.d = -(((const F32 *)objBox.max)[j>>1]); + + // + mTransformPlane(objTransform, objScale, plane, &testPlanes[numPlanes]); + + planeIndices[numPlanes] = j; + + if(mDot(testPlanes[numPlanes], Point3F(0,0,1)) > 0.f) + numPlanes++; + } + + // dump the polys + for(j = 0; j < numPlanes; j++) + { + for(U32 k = 0; k < 4; k++) + { + U32 vertIndex = BoxVerts[planeIndices[j]][k]; + + Point3F pnt; + pnt.set(BoxPnts[vertIndex].x ? objBox.max.x : objBox.min.x, + BoxPnts[vertIndex].y ? objBox.max.y : objBox.min.y, + BoxPnts[vertIndex].z ? objBox.max.z : objBox.min.z); + + // scale it + pnt.convolve(objScale); + + Point3F proj; + objTransform.mulP(pnt, &proj); + + Point2F pos = worldToScreen(Point2F(proj.x, proj.y)); + glVertex2f(pos.x, pos.y); + } + } + } + + glEnd(); + glDisable(GL_BLEND); + + RectF area; + getScreenMissionArea(area); + + // render the mission area box + glColor4ub(mMissionBoundsColor.red, mMissionBoundsColor.green, mMissionBoundsColor.blue, mMissionBoundsColor.alpha); + glBegin(GL_LINE_LOOP); + glVertex2f(area.point.x, area.point.y); + glVertex2f(area.point.x + area.extent.x, area.point.y); + glVertex2f(area.point.x + area.extent.x, area.point.y + area.extent.y); + glVertex2f(area.point.x, area.point.y + area.extent.y); + glEnd(); + + // 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)); + + glColor4ub(mCameraColor.red, mCameraColor.green, mCameraColor.blue, mCameraColor.alpha); + glBegin(GL_LINES); + glVertex2f(s.x, s.y); + glVertex2f(e1.x, e1.y); + glVertex2f(s.x, s.y); + glVertex2f(e2.x, e2.y); + glEnd(); + } + + // 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 + glColor4ub(mMirrorLineColor.red, mMirrorLineColor.green, mMirrorLineColor.blue, mMirrorLineColor.alpha); + glBegin(GL_LINES); + glVertex2f(rect.point.x + octPoints[(mMirrorIndex+6)%8].x * rect.extent.x, + rect.point.y + octPoints[(mMirrorIndex+6)%8].y * rect.extent.y); + glVertex2f(rect.point.x + octPoints[(mMirrorIndex+2)%8].x * rect.extent.x, + rect.point.y + octPoints[(mMirrorIndex+2)%8].y * rect.extent.y); + glEnd(); + + // 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 + glColor4ub(mMirrorArrowColor.red, mMirrorArrowColor.green, mMirrorArrowColor.blue, mMirrorArrowColor.alpha); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBegin(GL_TRIANGLES); + for(i = 0; i < 15; i++) + glVertex2f(rect.point.x + pnts[arrow_tri[i]].x * rect.extent.x + (rect.extent.x / 2), + rect.point.y + pnts[arrow_tri[i]].y * rect.extent.y + (rect.extent.y / 2)); + glEnd(); + + // opaque + glColor4ub(mMirrorArrowColor.red, mMirrorArrowColor.green, mMirrorArrowColor.blue, 0xff); + glBegin(GL_LINE_STRIP); + for(i = 0; i < 8; i++) + glVertex2f(rect.point.x + pnts[i].x * rect.extent.x + (rect.extent.x / 2), + rect.point.y + pnts[i].y * rect.extent.y + (rect.extent.y / 2)); + glEnd(); + glDisable(GL_BLEND); + } + + renderChildControls(offset, updateRect, firstResponder); +} + +//------------------------------------------------------------------------------ +// 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); + dglDrawRect(r, mHandleFrameColor); + r.point += Point2I(1, 1); + r.extent -= Point2I(1, 1); + dglDrawRectFill(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(event.mousePoint.x, 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; + + RectF box; + getScreenMissionArea(box); + Point2F 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; + } + } + + // + Point2F min = screenToWorld(box.point); + Point2F max = screenToWorld(box.point + box.extent); + + RectI iBox(round_local(min.x), round_local(min.y), round_local(max.x - min.x), round_local(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 = 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); +} + +//------------------------------------------------------------------------------ + +static void cCenterWorld(SimObject * obj, S32, const char **) +{ + MissionAreaEditor * editor = static_cast(obj); + if(!editor->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 area is clamped to terrain square size! + editor->setArea(editor->getArea()); + RectI area = editor->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(offset.x, offset.y, 0.f); + + // 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 / terrain->getSquareSize()) & TerrainBlock::BlockMask, + (offset.y / terrain->getSquareSize()) & TerrainBlock::BlockMask); + + // update the grid block + if(start.x || start.y) + { + U16 heights[TerrainBlock::BlockSize * TerrainBlock::BlockSize]; + U8 baseMaterials[TerrainBlock::BlockSize * TerrainBlock::BlockSize]; + TerrainBlock::Material materials[TerrainBlock::BlockSize * TerrainBlock::BlockSize]; + + 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(); + } + + editor->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), "%f %f %f", -offset3F.x, -offset3F.y, -offset3F.z); + Con::executef(editor, 2, "onWorldOffset", buf); + + // move the mission area + area.point.x -= offset.x; + area.point.y -= offset.y; + + editor->setArea(area); +} + +//------------------------------------------------------------------------------ + +static const char * cGetArea(SimObject * obj, S32, const char **) +{ + MissionAreaEditor * editor = static_cast(obj); + if(!editor->missionAreaObjValid()) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cGetArea: no MissionArea obj!"); + return(""); + } + + // + RectI area = editor->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); +} + +static void cSetArea(SimObject * obj, S32 argc, const char ** argv) +{ + MissionAreaEditor * editor = static_cast(obj); + if(!editor->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!"); + + // + editor->setArea(area); +} + +static void cUpdateTerrain(SimObject * obj, S32, const char **) +{ + MissionAreaEditor * editor = static_cast(obj); + + // + if(!editor->getTerrainObj()) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cUpdateTerrain: no terrain found!"); + return; + } + + // + editor->updateTerrainBitmap(); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::consoleInit() +{ + Con::addCommand("MissionAreaEditor", "centerWorld", cCenterWorld, "missionAreaEditor.centerWorld();", 2, 2); + Con::addCommand("MissionAreaEditor", "getArea", cGetArea, "missionAreaEditor.getArea();", 2, 2); + Con::addCommand("MissionAreaEditor", "setArea", cSetArea, "missionAreaEditor.setArea(x, y, w, h);", 3, 6); + Con::addCommand("MissionAreaEditor", "updateTerrain", cUpdateTerrain, "missionAreaEditor.updateTerrain();", 2, 2); +} + +void MissionAreaEditor::initPersistFields() +{ + Parent::initPersistFields(); + addField("squareBitmap", TypeBool, Offset(mSquareBitmap, MissionAreaEditor)); + addField("enableEditing", TypeBool, Offset(mEnableEditing, MissionAreaEditor)); + addField("renderCamera", TypeBool, Offset(mRenderCamera, MissionAreaEditor)); + + 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("enableMirroring", TypeBool, Offset(mEnableMirroring, MissionAreaEditor)); + addField("mirrorIndex", TypeS32, Offset(mMirrorIndex, MissionAreaEditor)); + addField("mirrorLineColor", TypeColorI, Offset(mMirrorLineColor, MissionAreaEditor)); + addField("mirrorArrowColor", TypeColorI, Offset(mMirrorArrowColor, MissionAreaEditor)); +} diff --git a/editor/missionAreaEditor.h b/editor/missionAreaEditor.h new file mode 100644 index 0000000..5390f2f --- /dev/null +++ b/editor/missionAreaEditor.h @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MISSIONAREAEDITOR_H_ +#define _MISSIONAREAEDITOR_H_ + +#ifndef _GUIBITMAPCTRL_H_ +#include "gui/guiBitmapCtrl.h" +#endif +#ifndef _GUITYPES_H_ +#include "gui/guiTypes.h" +#endif +#ifndef _MISSIONAREA_H_ +#include "game/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 &); + + 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]; + GuiCursor * getCursor(); + 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) + }; + + 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(bool(mMissionArea)); } + bool terrainObjValid() { return( mTerrainBlock.operator bool () ); } + + TerrainBlock * getTerrainObj(); + + const RectI & getArea(); + void setArea(const RectI & area); + + void updateTerrainBitmap(); + + // GuiControl + void parentResized(const Point2I &, const Point2I &); + void onRender(Point2I offset, const RectI & updateRect, GuiControl * firstResponder); + 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 consoleInit(); + static void initPersistFields(); + + DECLARE_CONOBJECT(MissionAreaEditor); +}; + +#endif diff --git a/editor/terraformer.cc b/editor/terraformer.cc new file mode 100644 index 0000000..066e53c --- /dev/null +++ b/editor/terraformer.cc @@ -0,0 +1,1371 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "terrain/terrData.h" +#include "core/fileStream.h" +#include "dgl/gBitmap.h" +#include "math/mRandom.h" +#include "editor/terraformer.h" +#include "dgl/dgl.h" + +#include "core/dnet.h" +#include "game/shapeBase.h" +#include "game/gameConnection.h" + +#include "editor/guiTerrPreviewCtrl.h" + + + +S32* Heightfield::zoneOffset = NULL; +U32 Heightfield::instance = 0; + +//------------------------------------------------------------------------------ +Heightfield::Heightfield(U32 r, U32 sz) +{ + registerNumber = r; + mask = sz-1; + shift = getBinLog2(sz); + data = new F32[sz*sz]; + + // initialize zone offsets if it has not been done + if (instance++ == 0) + { + zoneOffset = new S32[sz*sz]; + + // initialize Zone Offset lookup Table + S32 array[3]; + array[0] = 0; + array[1] = 1; + array[2] = mask; + + S32 *o = zoneOffset; + for (S32 y=0; y<3; y++) + { + S32 y1 = array[y]; + S32 y1plus = (y1+1)&mask; + S32 y1minus= (y1-1)&mask; + for (S32 x=0; x<3; x++) + { + S32 x1 = array[x]; + S32 x1plus = (x1+1)&mask; + S32 x1minus= (x1-1)&mask; + S32 root = offset(x1,y1); + + *o++ = offset(x1minus, y1minus) - root; + *o++ = offset(x1, y1minus) - root; + *o++ = offset(x1plus, y1minus) - root; + + *o++ = offset(x1minus, y1) - root; + *o++ = 0; + *o++ = offset(x1plus, y1) - root; + + *o++ = offset(x1minus, y1plus) - root; + *o++ = offset(x1, y1plus) - root; + *o++ = offset(x1plus, y1plus) - root; + } + } + } + +} + + +Heightfield::~Heightfield() +{ + if (data) + delete [] data; + + if (instance) + { + instance--; + if (!instance) + delete [] zoneOffset; + } +} + + +Heightfield& Heightfield::operator=(const Heightfield &src) +{ + if (data != src.data) + dMemcpy(data, src.data, sizeof(F32) * (1<::iterator i; + for (i = registerList.begin(); i != registerList.end(); i++) + delete *i; + for (i = scratchList.begin(); i != scratchList.end(); i++) + delete *i; +} + +//------------------------------------------------------------------------------ +Heightfield* Terraformer::getRegister(U32 r) +{ + VectorPtr::iterator i; + for (i = registerList.begin(); i != registerList.end(); i++) + if ((*i)->registerNumber == r) + break; + if (i == registerList.end()) + { + registerList.push_back( new Heightfield(r, blockSize) ); + i = ®isterList.last(); + } + return *i; +} + + +Heightfield* Terraformer::getScratch(U32 r) +{ + VectorPtr::iterator i; + for (i = scratchList.begin(); i != scratchList.end(); i++) + if ((*i)->registerNumber == r) + break; + if (i == scratchList.end()) + { + scratchList.push_back( new Heightfield(r, blockSize) ); + i = &scratchList.last(); + } + return *i; +} + + +//-------------------------------------- +void Terraformer::setTerrainInfo(U32 size, F32 tileSize, F32 minHeight, F32 heightRange, F32 water) +{ + blockSize = getNextPow2(size); // just to make sure clamp to power of 2 + blockMask = size-1; + worldTileSize = tileSize; + worldHeight = heightRange; + worldBaseHeight = minHeight; + worldWater = water; +} + + +void Terraformer::setShift(const Point2F &shift) +{ + mShift.set(shift.x, shift.y); +} + + +//------------------------------------------------------------------------------ +void Terraformer::getMinMax(U32 r, F32 *fmin, F32 *fmax) +{ + Heightfield *src = getRegister(r); + if (!src) + return; + + F32 *p = src->data; + *fmin = *p; + *fmax = *p; + for (S32 i=0; i < (blockSize*blockSize); i++, p++) + { + if (*fmin > *p) *fmin = *p; + if (*fmax < *p) *fmax = *p; + } +} + +//------------------------------------------------------------------------------ +void Terraformer::clearRegister(U32 r) +{ + Heightfield *dst = getRegister(r); + + for (S32 y=0; yval(x,y) = 0.0f; +} + +//------------------------------------------------------------------------------ +GBitmap* Terraformer::getScaledGreyscale(U32 r) +{ + enum { NUM_COLORS = 2 }; + ColorI land[NUM_COLORS] = + { + ColorI(0,0,0), + ColorI(20,255,20) + }; + + ColorI water[NUM_COLORS] = + { + ColorI(0,0,0), + ColorI(20,20,255) + }; + + + Heightfield *src = getRegister(r); + + F32 fmin, fmax; + getMinMax(r, &fmin, &fmax); + F32 scale = (NUM_COLORS-1)/(fmax-fmin); + + GBitmap* bitmap = new GBitmap(blockSize, blockSize, false, GBitmap::RGB); + + S32 y, x; + U8 *rgb = bitmap->getAddress(0,0); + for (y=blockSize-1; y >= 0; y--) + { + for (x=0; x < blockSize; x++) + { + ColorI c; + F32 index = (src->val(wrap(x), wrap(y))-fmin) * scale; + if (index > worldWater) + { // above "water" + S32 indexLo = (S32)mFloor(index); + S32 indexHi = (S32)mCeil(index); + index -= indexLo; + c.interpolate(land[indexLo], land[indexHi], index); + } + else + { // below "water" + index /= worldWater; + S32 indexLo = (S32)mFloor(index); + S32 indexHi = (S32)mCeil(index); + index -= indexLo; + c.interpolate(water[indexLo], water[indexHi], index); + } + + *rgb++ = c.red; + *rgb++ = c.green; + *rgb++ = c.blue; + } + } + return(bitmap); +} + + +//------------------------------------------------------------------------------ +GBitmap* Terraformer::getGreyscale(U32 r) +{ + Heightfield *src = getRegister(r); + + GBitmap* bitmap = new GBitmap(blockSize, blockSize, false, GBitmap::RGB); + + S32 y, x; + U8 *rgb = bitmap->getAddress(0,0); + for (y=blockSize-1; y >= 0; y--) + { + for (x=0; x < blockSize; x++) + { + S32 index = (S32)(src->val(wrap(x), wrap(y)) * 255.0f); + index = getMax(getMin(index, 255), 0); + + *rgb++ = index; + *rgb++ = index; + *rgb++ = index; + } + } + + return(bitmap); +} + + + +//------------------------------------------------------------------------------ +bool Terraformer::saveGreyscale(U32 r, const char *filename) +{ + GBitmap *bitmap = getGreyscale(r); + if (!bitmap) + return false; + + FileStream sio; + if (sio.open(filename, FileStream::Write)) + { + bitmap->writePNG(sio); + sio.close(); + + } + delete bitmap; + return true; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::loadGreyscale(U32 r, const char *filename) +{ + Heightfield *dst = getRegister(r); + +// return ResourceManager->findFile(filename); + GBitmap *bmp = (GBitmap *) ResourceManager->loadInstance(filename); + if (!bmp) + return false; + + for (S32 y=0; ygetAddress(x,y); + // compute the luminance of each RGB + dst->val(x, y) = ((F32)rgb[0]) * (0.299f/256.0f) + + ((F32)rgb[1]) * (0.587f/256.0f) + + ((F32)rgb[2]) * (0.114f/256.0f); + } + + delete bmp; + return true; +} + +//------------------------------------------------------------------------------ +bool Terraformer::saveHeightField(U32 r, const char *filename) +{ + FileStream stream; + F32 fmin, fmax, f; + Heightfield *dst = getRegister(r); + + getMinMax(r, &fmin, &fmax); + + F32 scale = blockSize/(fmax-fmin); + if(ResourceManager->openFileForWrite(stream, ResourceManager->getModPathOf(filename), filename)) + { + for (S32 y=0; yval(wrap(x+mShift.x),wrap(y+mShift.y)) - fmin) * scale; + U16 test = floatToFixed( f ); + stream.write(test); + } + stream.close(); + } + + return true; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::setTerrain(U32 r) +{ + Heightfield *src = getRegister(r); + + TerrainBlock *terrBlock = dynamic_cast(Sim::findObject("Terrain")); + if (!terrBlock) + return false; + + F32 omin, omax; + getMinMax(r, &omin, &omax); + + F32 scale = worldHeight/(omax-omin); + S32 sz = TerrainBlock::BlockSize; + + Point2F shift(mShift); + shift *= 256.0/(TerrainBlock::BlockSize*terrBlock->getSquareSize()); + + S32 y; + for (y=sz; y>=0; y--) + { + S32 x; + for (x=0; x<=sz; x++) + { + F32 f = (src->val(wrap(x+shift.x),wrap(y+shift.y)) - omin) * scale + worldBaseHeight; + *(terrBlock->getHeightAddress(x,y)) = floatToFixed( f ); + } + } + { + Point3F a(0.5, 0.5, -0.5); + terrBlock->buildGridMap(); + terrBlock->relight(ColorF(0.8,0.8,0.8), ColorF(0.35,0.35,0.35), a); + } + + Point3F pos = getCameraPosition(); + pos.z = 0.0f; + setCameraPosition(pos); + + return (true); +} + + +//------------------------------------------------------------------------------ +void Terraformer::setCameraPosition(const Point3F &pos) +{ + GameConnection *connection = GameConnection::getLocalClientConnection(); + Point3F position( pos ); + + ShapeBase *camera = NULL; + if(connection) + camera = connection->getControlObject(); + + if(!camera) + { + Con::warnf(ConsoleLogEntry::General, "Terraformer::setCameraPosition: could not get camera."); + return; + } + + // move it + MatrixF mat = camera->getTransform(); + + + TerrainBlock * terrain = dynamic_cast(Sim::findObject("Terrain")); + if(terrain) + { + F32 height; + Point2F xy(position.x, position.y); + + terrain->getHeight(xy, &height); + if((position.z - height) < 2.0f) + position.z = height + 10.0f; + } + + mat.setColumn(3, position); + camera->setTransform(mat); +} + + +//------------------------------------------------------------------------------ +Point3F Terraformer::getCameraPosition() +{ + GameConnection *connection = GameConnection::getLocalClientConnection(); + Point3F current; + current.set(0.0f,0.0f,0.0f); + + ShapeBase *camera = NULL; + if(connection) + camera = connection->getControlObject(); + + if(!camera) + { + Con::warnf(ConsoleLogEntry::General, "Terraformer::getCameraPosition: could not get camera."); + return current; + } + + // move it + MatrixF mat = camera->getTransform(); + mat.getColumn(3, ¤t); + return current; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::scale(U32 r_src, U32 r_dst, F32 fmin, F32 fmax) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + F32 omin, omax; + getMinMax(r_src, &omin, &omax); + + F32 scale = (fmax-fmin)/(omax-omin); + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) = ((src->val(i) - omin) * scale ) + fmin; + + return true; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::smooth(U32 r_src, U32 r_dst, F32 factor, U32 iterations) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + // early out if there is nothing to do + if (iterations == 0 ) + { + *dst = *src; + return true; + } + + Heightfield *a = getScratch(0); + Heightfield *b = getScratch(1); + *a = *src; + + // factor of 0.0 = NO Smoothing + // factor of 1.0 = MAX Smoothing + F32 matrixM = 1.0f - getMax(0.0f, getMin(1.0f, factor)); + F32 matrixE = (1.0f-matrixM) * (1.0f/12.0f) * 2.0f; + F32 matrixC = matrixE * 0.5f; + + *a = *src; + for (U32 i=0; iblock(x,y,array); + // 0 1 2 + // 3 x,y 5 + // 6 7 8 + + b->val( x, y ) = + ((array[0]+array[2]+array[6]+array[8]) * matrixC) + + ((array[1]+array[3]+array[5]+array[7]) * matrixE) + + (array[4] * matrixM); + } + } + Heightfield *tmp = a; + a = b; + b = tmp; + } + *dst = *a; + + return true; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::smoothWater(U32 r_src, U32 r_dst, F32 factor, U32 iterations) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + // early out if there is nothing to do + if (iterations == 0 ) + { + *dst = *src; + return true; + } + + Heightfield *a = getScratch(0); + Heightfield *b = getScratch(1); + *a = *src; + + F32 fmin, fmax; + getMinMax(r_src, &fmin, &fmax); + F32 water = (worldWater*(fmax-fmin))+fmin; + + // factor of 0.0 = NO Smoothing + // factor of 1.0 = MAX Smoothing + F32 matrixM = 1.0f - getMax(0.0f, getMin(1.0f, factor)); + F32 matrixE = (1.0f-matrixM) * (1.0f/12.0f) * 2.0f; + F32 matrixC = matrixE * 0.5f; + + + for (U32 i=0; ival( x, y); + if (value <= water) + { + F32 array[9]; + a->block(x,y,array); + // 0 1 2 + // 3 x,y 5 + // 6 7 8 + + b->val( x, y ) = + ((array[0]+array[2]+array[6]+array[8]) * matrixC) + + ((array[1]+array[3]+array[5]+array[7]) * matrixE) + + (array[4] * matrixM); + } + else + b->val( x, y ) = value; + } + } + Heightfield *tmp = a; + a = b; + b = tmp; + } + *dst = *a; + return true; +} + + +#define ridgeMask(m0, m1, m2, m3, m4, m5, m6, m7, m8) (m8 | (m7<<2) | (m6<<4) | (m5<<6) | (m4<<8) | (m3<<10) | (m2<<12) | (m1<<14) | (m0<<16) ) + +//------------------------------------------------------------------------------ +bool Terraformer::smoothRidges(U32 r_src, U32 r_dst, F32 factor, U32 iterations, F32 threshold) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + // early out if there is nothing to do + if (iterations == 0 ) + { + *dst = *src; + return true; + } + + Heightfield *a = getScratch(0); + Heightfield *b = getScratch(1); + *a = *src; + + F32 fmin, fmax; + getMinMax(r_src, &fmin, &fmax); + threshold = mClampF(threshold, 0.0f, 1.0f); + threshold = (fmax-fmin) * threshold; + + + // factor of 0.0 = NO Smoothing + // factor of 1.0 = MAX Smoothing + F32 matrixM = 1.0f - getMax(0.0f, getMin(1.0f, factor)); + F32 matrixE = (1.0f-matrixM) * (1.0f/12.0f) * 2.0f; + F32 matrixC = matrixE * 0.5f; + + *a = *src; + for (U32 i=0; iblock(x,y,array); + // 0 1 2 + // 3 x,y 5 + // 6 7 8 + + F32 center = array[4]; + F32 ave = (array[0]+array[1]+array[2]+ + array[3]+ array[5]+ + array[6]+array[7]+array[8]) / 8.0f; + + // if this height deviates too much from its neighboors smooth it! + if (mFabs(ave-center) > threshold) + { + b->val( x, y ) = + ((array[0]+array[2]+array[6]+array[8]) * matrixC) + + ((array[1]+array[3]+array[5]+array[7]) * matrixE) + + (center * matrixM); + } + else + b->val( x, y ) = center; + } + } + Heightfield *tmp = a; + a = b; + b = tmp; + } + *dst = *a; + + return true; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::blend(U32 r_srcA, U32 r_srcB, U32 r_dst, F32 factor, BlendOperation operation) +{ + Heightfield *srcA = getRegister(r_srcA); + Heightfield *srcB = getRegister(r_srcB); + Heightfield *dst = getRegister(r_dst); + + F32 fminA, fmaxA, fminB, fmaxB; + getMinMax(r_srcA, &fminA, &fmaxA); + getMinMax(r_srcB, &fminB, &fmaxB); + + F32 scaleA = blockSize/(fmaxA-fminA)*factor; + F32 scaleB = blockSize/(fmaxB-fminB)*(1.0f-factor); + + for (S32 i=0; i<(blockSize*blockSize); i++) + { + F32 a = (srcA->val(i) - fminA) * scaleA; + F32 b = (srcB->val(i) - fminB) * scaleB; + switch (operation) + { + case OperationSubtract: + dst->val(i) = a - b; + break; + case OperationMax: + dst->val(i) = getMax(a, b); + break; + case OperationMin: + dst->val(i) = getMin(a, b); + default: + case OperationAdd: + dst->val(i) = a + b; + break; + case OperationMultiply: + dst->val(i) = a * b; + } + } + return true; + +} + + +//------------------------------------------------------------------------------ +bool Terraformer::filter(U32 r_src, U32 r_dst, const Filter &filter) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + F32 omin, omax; + getMinMax(r_src, &omin, &omax); + scale(r_src, r_dst, 0.0f, 1.0f); + + for (S32 i=0; i < (blockSize*blockSize); i++) + { + F32 h = dst->val(i); + dst->val(i) = filter.getValue(h) * omax + omin; + } + return true; +} + + + +//-------------------------------------------------------------------------- +bool Terraformer::shift(U32 r_src ) +{ + Heightfield *src = getScratch(0); + Heightfield *dst = getRegister(r_src); + + // early out if there is nothing to do + if (mShift.x == 0 && mShift.y == 0) + return true; + + *src = *dst; + + for (S32 y=0; yval(x, y) = src->val(wrap(x+mShift.x), wrap(y+mShift.y)); + + return true; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::sinus(U32 r, const Filter &filter, U32 seed) +{ + F32 invBlockSize = 1 / F32(blockSize); + Heightfield *dst = getRegister(r); + + random.setSeed(seed); + noise.setSeed(seed); + + U32 iterations = 31; + for(U32 k = 0; k < blockSize * blockSize; k++) + dst->val(k) = 0; + + for(S32 i = 0; i < iterations; i += 2) + { + F32 period = M_2PI * (i + 1) * invBlockSize; + F32 scale = filter.getValue(i / F32(iterations - 1)); + F32 xOffset = random.randF() * M_2PI; + F32 yOffset = random.randF() * M_2PI; + + F32 interval = i + 2; + F32 sqInterval = invBlockSize * interval; + + for(S32 y = 0; y < blockSize; y++) + { + F32 cosy = mCos(y * period + yOffset); + for(S32 x = 0; x < blockSize; x++) + { + F32 sinx = mSin(x * period + xOffset); + dst->val(x, y) += scale * (sinx + cosy) * noise.getValue(x * sqInterval, y * sqInterval, interval); + } + } + } + return true; +} + +bool Terraformer::setHeight(U32 r, F32 height) +{ + Heightfield * dst = getRegister(r); + for(U32 i = 0; i < (blockSize * blockSize); i++) + dst->val(i) = height; + return(true); +} + +bool Terraformer::terrainData(U32 r) +{ + if(blockSize != TerrainBlock::BlockSize) + { + Con::errorf(ConsoleLogEntry::General, "Terraformer::terrainData - invalid blocksize."); + return(setHeight(r, 0.f)); + } + + Heightfield * dst = getRegister(r); + + TerrainBlock * terrBlock = dynamic_cast(Sim::findObject("Terrain")); + if(!terrBlock) + { + Con::warnf(ConsoleLogEntry::General, "Terraformer::terrainData - TerrainBlock 'terrain' not found."); + return(setHeight(r, 0.f)); + } + + // + for(U32 y = 0; y < TerrainBlock::BlockSize; y++) + for(U32 x = 0; x < TerrainBlock::BlockSize; x++) + dst->val(x, y) = fixedToFloat(terrBlock->getHeight(x,y)); + + return(true); +} + +bool Terraformer::terrainFile(U32 r, const char * fileName) +{ + if(blockSize != TerrainBlock::BlockSize) + { + Con::errorf(ConsoleLogEntry::General, "Terraformer::terrainFile - invalid blocksize."); + return(setHeight(r, 0.f)); + } + + Heightfield * dst = getRegister(r); + + Resource terrRes; + + terrRes = ResourceManager->load(fileName); + if(!bool(terrRes)) + { + Con::errorf(ConsoleLogEntry::General, "Terraformer::terrainFile - invalid terrain file '%s'.", fileName); + return(setHeight(r, 0.f)); + } + + // + for(U32 y = 0; y < TerrainBlock::BlockSize; y++) + for(U32 x = 0; x < TerrainBlock::BlockSize; x++) + dst->val(x, y) = fixedToFloat(terrRes->getHeight(x,y)); + + return(true); +} + +//------------------------------------------------------------------------------ +bool Terraformer::fBm(U32 r, U32 interval, F32 roughness, F32 octave, U32 seed) +{ + Heightfield *dst = getRegister(r); + + noise.setSeed(seed); + noise.fBm(dst, blockSize, interval, 1.0-roughness, octave); + + return true; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::rigidMultiFractal(U32 r, U32 interval, F32 roughness, F32 octave, U32 seed) +{ + + Heightfield *dst = getRegister(r); + Heightfield *a = getScratch(0); + + noise.setSeed(seed); + noise.rigidMultiFractal(dst, a, blockSize, interval, 1.0-roughness, octave); + + return true; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::canyonFractal(U32 r, U32 f, F32 v, U32 seed) +{ + Heightfield *dst = getRegister(r); + noise.setSeed(seed); + + v *= 40; // just a magic number + for (S32 y=0; yval(x, y) = mCos( fx*M_2PI*f + t ); + } + } + return true; +} + + +//-------------------------------------------------------------------------- +bool Terraformer::turbulence(U32 r_src, U32 r_dst, F32 v, F32 r) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + // early out if there is nothing to do + if (v == 0.0f || r == 0.0f) + { + *dst = *src; + return true; + } + + v *= 20; // just a magic number + F32 scale = 1.0f/(F32)blockSize; + for (S32 y=0; yval(x, y) = src->val(wrap(x + dx), wrap(y + dy)); + } + } + return true; +} + + +//------------------------------------------------------------------------------ +bool Terraformer::erodeThermal(U32 r_src, U32 r_dst, F32 slope, F32 materialLoss, U32 iterations) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + // early out if there is nothing to do + if (iterations == 0 ) + { + *dst = *src; + return true; + } + + + F32 fmin, fmax; + getMinMax(r_src, &fmin, &fmax); + + Heightfield *a = getScratch(0); + Heightfield *b = getScratch(1); + Heightfield *r = getScratch(2); + *a = *src; + + F32 conservation = 1.0f - mClampF(materialLoss, 0.0f, 100.0f)/100.0f; + slope = mClampF(conservation, 0.0f, 89.0f); // clamp to 0-89 degrees + + F32 talusConst = mTan(mDegToRad(slope)) * worldTileSize; // in world units + talusConst = talusConst * (fmax-fmin) / worldHeight; // scale to current height units + F32 p = 0.1f; + + for (U32 i=0; ival(i) = 0.0f; + + for (S32 y=0; yval( x, y ); + F32 *dstHeight = &r->val( x, y ); + + // for each height look at the immediate surrounding heights + // if any are higher than talusConst erode on me + for (S32 y1=y-1; y1 <= y+1; y1++) + { + S32 ywrap = wrap(y1); + for (S32 x1=x-1; x1 <= x+1; x1++) + { + if (x1 != x && y1 != y) + { + S32 adjOffset = a->offset(wrap(x1), ywrap); + F32 adjHeight = a->val(adjOffset); + F32 delta = adjHeight - *height; + if (delta > talusConst) + { + F32 rubble = p * (delta - talusConst); + r->val(adjOffset) -= rubble; + *dstHeight += rubble * conservation; + } + } + } + } + } + } + for (S32 k=0; k < (blockSize*blockSize); k++) + a->val(k) += r->val(k); + } + *dst = *a; + return true; +} + + + +//------------------------------------------------------------------------------ +bool Terraformer::erodeHydraulic(U32 r_src, U32 r_dst, U32 iterations, const Filter &filter) +{ + filter; + + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + // early out if there is nothing to do + if (iterations == 0 ) + { + *dst = *src; + return true; + } + + F32 fmin, fmax; + getMinMax(r_src, &fmin, &fmax); + + +// currently using SCRATCH_3 for debugging -- Rick + U32 *o = (U32*)getScratch(0)->data; + Heightfield *a = getScratch(1); + Heightfield *b = getScratch(2); + Heightfield *c = getScratch(3); + + *a = *src; + for (S32 k=0; k < (blockSize*blockSize); k++) + c->val(k) = 0.0f; + + for (int i=0; ioffset(x,y); + F32 height = a->val(srcOffset); + o[srcOffset] = srcOffset; + for (S32 y1=y-1; y1 <= y+1; y1++) + { + F32 maxDelta = 0.0f; + S32 ywrap = wrap(y1); + for (S32 x1=x-1; x1 <= x+1; x1++) + { + if (x1 != x && y1 != y) + { + U32 adjOffset = a->offset(wrap(x1), ywrap); + F32 &adjHeight = a->val(adjOffset); + F32 delta = height - adjHeight; + if (x1 != x || y1 != y) + delta *= 1.414213562f; // compensate for diagonals + if (delta > maxDelta) + { + maxDelta = delta; + o[srcOffset] = adjOffset; + } + } + } + } + } + } + for (S32 j=0; j < (blockSize*blockSize); j++) + { + F32 &s = a->val(j); + F32 &d = b->val(o[j]); + F32 delta = s - d; + if (delta > 0.0f) + { + F32 alt = (s-fmin) / (fmax-fmin); + F32 amt = delta * (0.1f * (1.0f-alt)); + s -= amt; + d += amt; + } + } +// debug only + for (S32 k=0; k < (blockSize*blockSize); k++) + c->val(k) += b->val(k) - a->val(k); + + Heightfield *tmp = a; + a = b; + b = tmp; + } + *dst = *b; + //*dst = *c; + + return true; +} + + + +// CONSOLE FN's +//------------------------------------------------------------------------------ +static void cTerraformer_SetTerrainInfo(SimObject *obj, S32, const char *argv[]) +{ + static_cast(obj)->setTerrainInfo( dAtoi(argv[2]), dAtof(argv[3]), dAtof(argv[4]), dAtof(argv[5]), dAtof(argv[6])); +} + +//------------------------------------------------------------------------------ +static void cTerraformer_SetShift(SimObject *obj, S32, const char *argv[]) +{ + Point2F shift( dAtof(argv[2]), dAtof(argv[3]) ); + static_cast(obj)->setShift( shift ); +} + + +//------------------------------------------------------------------------------ +static S32 cTerraformer_GenerateSeed(SimObject *, S32, const char **) +{ + S32 n = Platform::getVirtualMilliseconds() * 57; + n = (n<<13) ^ n; + n = (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff; + return n; +} + + +//------------------------------------------------------------------------------ +static bool cTerraformer_SaveGreyscale(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->saveGreyscale( dAtoi(argv[2]), argv[3] ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_LoadGreyscale(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->loadGreyscale( dAtoi(argv[2]), argv[3] ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_SaveHeightField(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->saveHeightField(dAtoi(argv[2]), argv[3]); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_SetTerrain(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->setTerrain(dAtoi(argv[2])); +} + +//------------------------------------------------------------------------------ +static const char* cTerraformer_GetCameraPosition(SimObject *obj, S32, const char *argv[]) +{ argv; + static char buffer[64]; + Point3F position = static_cast(obj)->getCameraPosition(); + dSprintf(buffer, sizeof(buffer), "%f %f %f", position.x, position.y, position.z); + return buffer; +} + +//------------------------------------------------------------------------------ +static void cTerraformer_SetCameraPosition(SimObject *obj, S32 argc, const char *argv[]) +{ + F32 z = 0.0f; + if (argc == 5) + z = dAtof(argv[4]); + Point3F pos( dAtof(argv[2]), dAtof(argv[3]), z ); + static_cast(obj)->setCameraPosition( pos ); +} + + +static bool cTerraformer_TerrainData(SimObject * obj, S32, const char ** argv) +{ + return static_cast(obj)->terrainData(dAtoi(argv[2])); +} + +static bool cTerraformer_TerrainFile(SimObject * obj, S32, const char ** argv) +{ + return static_cast(obj)->terrainFile(dAtoi(argv[2]), argv[3]); +} +//------------------------------------------------------------------------------ +static bool cTerraformer_Scale(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->scale( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), dAtof(argv[5]) ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_Smooth(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->smooth( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), dAtoi(argv[5]) ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_SmoothWater(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->smoothWater( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), dAtoi(argv[5]) ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_SmoothRidges(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->smoothRidges( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), dAtoi(argv[5]), 0.01f ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_Filter(SimObject *obj, S32 argc, const char *argv[]) +{ + Filter filter; + filter.set(argc-4, &argv[4]); + return static_cast(obj)->filter( dAtoi(argv[2]), dAtoi(argv[3]), filter ); +} + + +//------------------------------------------------------------------------------ +static bool cTerraformer_Blend(SimObject *obj, S32, const char *argv[]) +{ + Terraformer::BlendOperation op = Terraformer::OperationAdd; + if (dStricmp(argv[6],"add") == 0) op = Terraformer::OperationAdd; + if (dStricmp(argv[6],"subtract") == 0) op = Terraformer::OperationSubtract; + if (dStricmp(argv[6],"max") == 0) op = Terraformer::OperationMax; + if (dStricmp(argv[6],"min") == 0) op = Terraformer::OperationMin; + if (dStricmp(argv[6],"multiply") == 0) op = Terraformer::OperationMultiply; + return static_cast(obj)->blend( dAtoi(argv[2]), dAtoi(argv[3]), dAtoi(argv[4]), dAtof(argv[5]), op); +} + + +//------------------------------------------------------------------------------ +static bool cTerraformer_Turbulence(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->turbulence( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), dAtof(argv[5]) ); +} + + +//------------------------------------------------------------------------------ +static void cTerraformer_MaskFBm(SimObject *obj, S32, const char *argv[]) +{ + Filter filter; + filter.set(1, &argv[6]); + static_cast(obj)->maskFBm( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), dAtoi(argv[5]), filter, dAtob(argv[7]), dAtoi(argv[8]) ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_MaskHeight(SimObject *obj, S32, const char *argv[]) +{ + Filter filter; + filter.set(1, &argv[4]); + return static_cast(obj)->maskHeight( dAtoi(argv[2]), dAtoi(argv[3]), filter, dAtob(argv[5]), dAtoi(argv[6]) ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_MaskSlope(SimObject *obj, S32, const char *argv[]) +{ + Filter filter; + filter.set(1, &argv[4]); + return static_cast(obj)->maskSlope( dAtoi(argv[2]), dAtoi(argv[3]), filter, dAtob(argv[5]), dAtoi(argv[6]) ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_MaskWater(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->maskWater( dAtoi(argv[2]), dAtoi(argv[3]), dAtob(argv[4]), dAtoi(argv[5]) ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_MergeMasks(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->mergeMasks( argv[2], dAtoi(argv[3]) ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_SetMaterials(SimObject *obj, S32, const char *argv[]) +{ + return (static_cast(obj)->setMaterials( argv[2], argv[3] )); +} + + +//------------------------------------------------------------------------------ +static bool cTerraformer_ErodeHydraulic(SimObject *obj, S32 argc, const char *argv[]) +{ + Filter filter; + filter.set(argc-5, &argv[5]); + return static_cast(obj)->erodeHydraulic( dAtoi(argv[2]), dAtoi(argv[3]), dAtoi(argv[4]), filter ); +} + +//------------------------------------------------------------------------------ +static bool cTerraformer_ErodeThermal(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->erodeThermal( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), dAtof(argv[5]), dAtoi(argv[6]) ); +} + + +//-------------------------------------------------------------------------- +static bool cTerraformer_CanyonFractal(SimObject *obj, S32, const char *argv[]) +{ + return static_cast(obj)->canyonFractal( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), dAtoi(argv[5]) ); +} + +//------------------------------------------------------------------------------ +static void cTerraformer_PreviewScaled(SimObject *obj, S32, const char *argv[]) +{ + GuiTerrPreviewCtrl *dcc = dynamic_cast(Sim::findObject(argv[2])); + const GBitmap* bmp = static_cast(obj)->getScaledGreyscale(dAtoi(argv[3])); + + dcc->setBitmap(TextureHandle("tfImage", bmp, true)); +} + +//------------------------------------------------------------------------------ +static void cTerraformer_Preview(SimObject *obj, S32, const char *argv[]) +{ + GuiTerrPreviewCtrl *bmc = dynamic_cast(Sim::findObject(argv[2])); + const GBitmap* bmp = static_cast(obj)->getGreyscale( dAtoi(argv[3]) ); + + bmc->setBitmap(TextureHandle("tfImage", bmp, true)); +} + +//------------------------------------------------------------------------------ +static void cTerraformer_ClearReg(SimObject *obj, S32, const char *argv[]) +{ + static_cast(obj)->clearRegister(dAtoi(argv[2])); +} + +//------------------------------------------------------------------------------ +static void cTerraformer_FBm(SimObject *obj, S32, const char *argv[]) +{ + F32 octave = 3.0f; + if (!dStricmp(argv[5],"Very Low")) octave = 1.0f; + else if (!dStricmp(argv[5],"Low")) octave = 2.0f; + else if (!dStricmp(argv[5],"Normal")) octave = 3.0f; + else if (!dStricmp(argv[5],"High")) octave = 4.0f; + else if (!dStricmp(argv[5],"Very High")) octave = 5.0f; + static_cast(obj)->fBm( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), octave, dAtoi(argv[6]) ); +} + +//------------------------------------------------------------------------------ +static void cTerraformer_Sinus(SimObject *obj, S32, const char *argv[]) +{ + Filter filter; + filter.set(1, &argv[3]); + static_cast(obj)->sinus( dAtoi(argv[2]), filter, dAtoi(argv[4]) ); +} + +//------------------------------------------------------------------------------ +static void cTerraformer_RigidMultiFractal(SimObject *obj, S32, const char *argv[]) +{ + F32 octave = 3.0f; + if (!dStricmp(argv[5],"Very Low")) octave = 1.0f; + else if (!dStricmp(argv[5],"Low")) octave = 2.0f; + else if (!dStricmp(argv[5],"Normal")) octave = 3.0f; + else if (!dStricmp(argv[5],"High")) octave = 4.0f; + else if (!dStricmp(argv[5],"Very High")) octave = 5.0f; + static_cast(obj)->rigidMultiFractal( dAtoi(argv[2]), dAtoi(argv[3]), dAtof(argv[4]), octave, dAtoi(argv[6]) ); +} + + +//------------------------------------------------------------------------------ +void Terraformer::consoleInit() +{ + Con::addCommand("Terraformer", "setTerrainInfo", cTerraformer_SetTerrainInfo, "Terraformer.setTerrainInfo( blockSize, tileSize, minHeight, heightRange, water% )", 7, 7); + Con::addCommand("Terraformer", "setShift", cTerraformer_SetShift, "Terraformer.setShift( x, y )", 4, 4); + Con::addCommand("Terraformer", "generateSeed", cTerraformer_GenerateSeed, "Terraformer.generateSeed()", 2, 2); + + Con::addCommand("Terraformer", "saveGreyscale", cTerraformer_SaveGreyscale, "Terraformer.saveGreyscale(r, filename)", 4, 4); + Con::addCommand("Terraformer", "loadGreyscale", cTerraformer_LoadGreyscale, "Terraformer.loadGreyscale(r, filename)", 4, 4); + + Con::addCommand("Terraformer", "saveHeightField", cTerraformer_SaveHeightField, "Terraformer.saveHeightField(r, filename)", 4, 4); + Con::addCommand("Terraformer", "setTerrain", cTerraformer_SetTerrain, "Terraformer.setTerrain(r)", 3, 3); + Con::addCommand("Terraformer", "getCameraPosition",cTerraformer_GetCameraPosition,"Terraformer.getCameraPosition()", 2, 2); + Con::addCommand("Terraformer", "setCameraPosition",cTerraformer_SetCameraPosition,"Terraformer.setCameraPosition(x,y {,z})", 4, 5); + + Con::addCommand("Terraformer", "terrainData", cTerraformer_TerrainData, "Terraformer.terrainData(reg)", 3, 3); + Con::addCommand("Terraformer", "terrainFile", cTerraformer_TerrainFile, "Terraformer.terrainFile(reg, file)", 4, 4); + Con::addCommand("Terraformer", "scale", cTerraformer_Scale, "Terraformer.scale(src, dst, min, max)", 6, 6); + Con::addCommand("Terraformer", "smooth", cTerraformer_Smooth, "Terraformer.smooth(src, dst, 0-1{factor}, iterations)", 6, 6); + Con::addCommand("Terraformer", "smoothWater", cTerraformer_SmoothWater, "Terraformer.smoothWater(src, dst, 0-1{factor}, iterations)", 6, 6); + Con::addCommand("Terraformer", "smoothRidges", cTerraformer_SmoothRidges, "Terraformer.smoothRidges(src, dst, 0-1{factor}, iterations)", 6, 6); + Con::addCommand("Terraformer", "filter", cTerraformer_Filter, "Terraformer.filter(src, dst, \"filter array\")", 5, 5); + Con::addCommand("Terraformer", "blend", cTerraformer_Blend, "Terraformer.blend(srcA, srcB, dst, factor, operation)", 7, 7); + Con::addCommand("Terraformer", "turbulence", cTerraformer_Turbulence, "Terraformer.turbulence(src, dst, factor, radius)", 6, 6); + + Con::addCommand("Terraformer", "maskFBm", cTerraformer_MaskFBm, "Terraformer.maskFBm(dst, freq, 0.0-1.0{roughness}, seed, \"filter array\", distort_factor, distort_reg)", 9, 9); + Con::addCommand("Terraformer", "maskHeight", cTerraformer_MaskHeight, "Terraformer.maskHeight(src, dst, \"filter array\", distort_factor, distort_reg)", 7, 7); + Con::addCommand("Terraformer", "maskSlope", cTerraformer_MaskSlope, "Terraformer.maskSlope(src, dst, \"filter array\", distort_factor, distort_reg)", 7, 7); + Con::addCommand("Terraformer", "maskWater", cTerraformer_MaskWater, "Terraformer.maskWater(src, dst, distort_factor, distort_reg)", 6, 6); + Con::addCommand("Terraformer", "mergeMasks", cTerraformer_MergeMasks, "Terraformer.mergeMasks(\"src array\", dst)", 4, 4); + Con::addCommand("Terraformer", "setMaterials", cTerraformer_SetMaterials, "Terraformer.setMaterials(\"src array\", \"material array\")", 4, 4); + + Con::addCommand("Terraformer", "erodeHydraulic", cTerraformer_ErodeHydraulic, "Terraformer.erodeHydraulic(src, dst, iterations, \"filter array\" )", 6, 6); + Con::addCommand("Terraformer", "erodeThermal", cTerraformer_ErodeThermal, "Terraformer.erodeThermal(src, dst, slope, materialLoss, iterations )", 7, 7); + + Con::addCommand("Terraformer", "preview", cTerraformer_Preview, "Terraformer.preview(dst_gui, src)", 4, 4); + Con::addCommand("Terraformer", "previewScaled", cTerraformer_PreviewScaled, "Terraformer.previewScaled(dst_gui, src)", 4, 4); + Con::addCommand("Terraformer", "clearRegister", cTerraformer_ClearReg, "Terraformer.clearRegister(r)", 3, 3); + + Con::addCommand("Terraformer", "fBm", cTerraformer_FBm, "Terraformer.fBm(r, freq, 0.0-1.0{roughness}, detail, seed)", 7, 7); + Con::addCommand("Terraformer", "rigidMultiFractal",cTerraformer_RigidMultiFractal,"Terraformer.rigidMultiFractal(r, freq, 0.0-1.0{roughness}, detail, seed)", 7, 7); + Con::addCommand("Terraformer", "canyon", cTerraformer_CanyonFractal, "Terraformer.canyon(dst, freq, turb, seed)", 6, 6); + Con::addCommand("Terraformer", "sinus", cTerraformer_Sinus, "Terraformer.sinus(r, \"filter array\", seed)", 5, 5); +} + + + diff --git a/editor/terraformer.h b/editor/terraformer.h new file mode 100644 index 0000000..2404c64 --- /dev/null +++ b/editor/terraformer.h @@ -0,0 +1,232 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRAFORMER_H_ +#define _TERRAFORMER_H_ + +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _TERRDATA_H_ +#include "terrain/terrData.h" +#endif +#ifndef _GBITMAP_H_ +#include "dgl/gBitmap.h" +#endif +#ifndef _MRANDOM_H_ +#include "Math/mRandom.h" +#endif +#ifndef _TERRAFORMER_NOISE_H_ +#include "Editor/terraformer_noise.h" +#endif +#ifndef _GUIFILTERCTRL_H_ +#include "GUI/guiFilterCtrl.h" +#endif + + +struct Heightfield +{ + static S32 *zoneOffset; + static U32 instance; + + U32 registerNumber; + U32 mask; + U32 shift; + union + { + F32 *data; + S32 *dataS32; + }; + Heightfield(U32 r, U32 sz); + ~Heightfield(); + + Heightfield& operator=(const Heightfield &src); + S32 offset(S32 x, S32 y); + F32& val(S32 x, S32 y); + F32& val(S32 index); + void block(S32 x, S32 y, F32 *a ); + + S32& valS32(S32 x, S32 y); + S32& valS32(S32 index); + +}; + + + +class Terraformer: public SimObject +{ +public: + enum BlendOperation + { + OperationAdd, + OperationSubtract, + OperationMax, + OperationMin, + OperationMultiply, + }; + +private: + VectorPtr registerList; + VectorPtr scratchList; + MRandomR250 random; + + Noise2D noise; + + // used by wrap, val inlines + U32 blockMask; + U32 blockSize; + F32 worldTileSize; + F32 worldBaseHeight; + F32 worldHeight; + F32 worldWater; + Point2F mShift; + + S32 wrap(S32 p); + + Heightfield* getRegister(U32 r); + Heightfield* getScratch(U32 r); + void getMinMax(U32 r, F32 *fmin, F32 *fmax); + +public: + Terraformer(); + ~Terraformer(); + static void consoleInit(); + DECLARE_CONOBJECT(Terraformer); + + void setSize(U32 n); + void setTerrainInfo(U32 blockSize, F32 tileSize, F32 minHeight, F32 heightRange, F32 worldWater); + void setShift( const Point2F &shift ); + void clearRegister(U32 r); + + // I/O operations + GBitmap* getScaledGreyscale(U32 r); + GBitmap* getGreyscale(U32 r); + bool saveGreyscale(U32 r, const char *filename); + bool loadGreyscale(U32 r, const char *filename); + bool saveHeightField(U32 r, const char *filename); + bool setTerrain(U32 r); + void setCameraPosition(const Point3F & pos); + Point3F getCameraPosition(); + + + // Mathmatical operations + bool scale(U32 r_src, U32 r_dst, F32 fmin, F32 fmax); + bool smooth(U32 r_src, U32 r_dst, F32 factor, U32 iterations); + bool smoothWater(U32 r_src, U32 r_dst, F32 factor, U32 iterations); + bool smoothRidges(U32 r_src, U32 r_dst, F32 factor, U32 iterations, F32 threshold); + bool blend(U32 r_srcA, U32 r_srcB, U32 r_dst, F32 factor, BlendOperation operation); + bool filter(U32 r_src, U32 r_dst, const Filter &filter); + bool turbulence(U32 r_src, U32 r_dst, F32 v, F32 r); + bool shift(U32 r_src); + + // Generation operations + bool setHeight(U32 r, F32 height); + bool terrainData(U32 r); + bool terrainFile(U32 r, const char * terrFile); + bool fBm(U32 r, U32 interval, F32 roughness, F32 detail, U32 seed); + bool rigidMultiFractal(U32 r, U32 interval, F32 roughness, F32 detail, U32 seed); + bool canyonFractal(U32 r, U32 f, F32 v, U32 seed); + bool sinus(U32 r, const Filter &filter, U32 seed); + + // effects + bool erodeHydraulic(U32 r_src, U32 r_dst, U32 iterations, const Filter &filter); + bool erodeThermal(U32 r_src, U32 r_dst, F32 slope, F32 materialLoss, U32 iterations); + + // Texturing operations + bool maskFBm(U32 r_dst, U32 interval, F32 roughness, U32 seed, const Filter &filter, bool distort, U32 r_distort); + bool maskHeight(U32 r_src, U32 r_dst, const Filter &filter, bool distort, U32 r_distort); + bool maskSlope(U32 r_src, U32 r_dst, const Filter &filter, bool distort, U32 r_distort); + bool maskWater(U32 r_src, U32 r_dst, bool distort, U32 r_distort); + + bool mergeMasks(const char *r_src, U32 r_dst); + bool setMaterials(const char *r_src, const char *materials); +}; + + +//-------------------------------------- +inline S32 Heightfield::offset(S32 x, S32 y) +{ + return (y< 0) + { + zone++; + if (x >= mask) + zone++; + } + if (y > 0) + { + zone += 3; + if (y >= mask) + zone += 3; + } + + F32 *d = &data[ (y< m) + f -= m; + while (f < 0.0f) + f += m; + return f; +} + + +//-------------------------------------- +void Noise2D::fBm(Heightfield *dst, U32 size, U32 interval, F32 h, F32 octaves) +{ + interval = getMin(U32(128), getMax(U32(1), interval)); + F32 H = getMin(1.0f, getMax(0.0f, h)); + octaves = getMin(5.0f, getMax(1.0f, octaves)); + F32 lacunarity = 2.0f; + + F32 exponent_array[32]; + + // precompute and store spectral weights + // seize required memory for exponent_array + F32 frequency = 1.0; + for (U32 i=0; i<=octaves; i++) + { + // compute weight for each frequency + exponent_array[i] = mPow( frequency, -H ); + frequency *= lacunarity; + } + + // initialize dst + for (S32 k=0; k < (size*size); k++) + dst->val(k) = 0.0f; + + F32 scale = 1.0f / (F32)size * interval; + for (S32 o=0; oval(x, y) += noise * exp; + } + } + scale *= lacunarity; + interval = (U32)(interval * lacunarity); + } +} + + +//-------------------------------------- +void Noise2D::rigidMultiFractal(Heightfield *dst, Heightfield *sig, U32 size, U32 interval, F32 h, F32 octaves) +{ + interval = getMin(U32(128), getMax(U32(1), interval)); + F32 H = getMin(1.0f, getMax(0.0f, h)); + octaves = getMin(5.0f, getMax(1.0f, octaves)); + F32 lacunarity = 2.0f; + F32 offset = 1.0f; + F32 gain = 2.0f; + + F32 exponent_array[32]; + + // precompute and store spectral weights + // seize required memory for exponent_array + F32 frequency = 1.0; + for (U32 i=0; i<=octaves; i++) + { + // compute weight for each frequency + exponent_array[i] = mPow( frequency, -H ); + frequency *= lacunarity; + } + + F32 scale = 1.0f / (F32)size * interval; + + //-------------------------------------- + // compute first octave + for (S32 y=0; yval(x, y) = signal; + sig->val(x, y) = signal; + } + } + + //-------------------------------------- + // compute remaining octaves + for (S32 o=1; ooffset(x,y); + F32 result = dst->val(index); + F32 signal = sig->val(index); + + // weight successive contributions by previous signal + F32 weight = mClampF(signal * gain, 0.0f, 1.0f); + + signal = mFabs(getValue( fx, fy, interval )); + + signal = offset - signal; + signal *= signal + 0.2; + // weight the contribution + signal *= weight; + result += signal * exp; + + dst->val(index) = result; + sig->val(index) = signal; + } + } + } + for (S32 k=0; k < (size*size); k++) + dst->val(k) = (dst->val(k)-1.0f)/2.0f; +} + + +//-------------------------------------- +F32 Noise2D::turbulence(F32 x, F32 y, F32 freq) +{ + F32 t, x2, y2; + + for ( t = 0.0f ; freq >= 3.0f ; freq /= 2.0f) + { + x2 = freq * x; + y2 = freq * y; + t += mFabs(getValue(x2, y2, freq)) / freq; + } + return t; +} + + +//-------------------------------------- +inline void Noise2D::setup(F32 t, S32 &b0, S32 &b1, F32 &r0, F32 &r1) +{ + // find the bounding integers of u + b0 = S32(t) & SIZE_MASK; + b1 = (b0+1) & SIZE_MASK; + + // seperate the fractional components + r0 = t - (S32)t; + r1 = r0 - 1.0f; +} + +inline F32 Noise2D::dot(const F32 *q, F32 rx, F32 ry) +{ + return (rx * q[0] + ry * q[1] ); +} + + + +//-------------------------------------- +F32 Noise2D::getValue(F32 x, F32 y, S32 interval) +{ + S32 bx0, bx1, by0, by1; + F32 rx0, rx1, ry0, ry1; + + // Imagine having a square of the type + // p0---p1 Where p0 = (bx0, by0) +----> U + // |(u,v)| p1 = (bx1, by0) | + // | | p2 = (bx0, by1) | Coordinate System + // p2---p3 p3 = (bx1, by1) V + // The u, v point in 2D texture space is bounded by this rectangle. + + // Goal, determine the scalar at the points p0, p1, p2, p3. + // Then the scalar of the point (u, v) will be found by linear interpolation. + + // First step: Get the 2D coordinates of the points p0, p1, p2, p3. + // We also need vectors pointing from each point in the square above and + // ending at the (u,v) coordinate located inside the square. + // The vector (rx0, ry0) goes from P0 to the (u,v) coordinate. + // The vector (rx1, ry0) goes from P1 to the (u,v) coordinate. + // The vector (rx0, ry1) goes from P2 to the (u,v) coordinate. + // The vector (rx1, ry1) goes from P3 to the (u,v) coordinate. + + setup(x, bx0, bx1, rx0, rx1); + setup(y, by0, by1, ry0, ry1); + + // Make sure the box corners fall within the interval + // so that the final output will wrap on itself + bx0 = bx0 % interval; + bx1 = bx1 % interval; + by0 = by0 % interval; + by1 = by1 % interval; + + S32 i = mPermutation[ bx0 ]; + S32 j = mPermutation[ bx1 ]; + + S32 b00 = mPermutation[ i + by0 ]; + S32 b10 = mPermutation[ j + by0 ]; + S32 b01 = mPermutation[ i + by1 ]; + S32 b11 = mPermutation[ j + by1 ]; + + // Next, calculate the dropoff component about the point p0. + F32 sx = curve(rx0); + F32 sy = curve(ry0); + + // Now, for each point in the square shown above, calculate the dot + // product of the gradiant vector and the vector going from each square + // corner point to the (u,v) point inside the square. + F32 u = dot(mGradient[ b00 ], rx0,ry0); + F32 v = dot(mGradient[ b10 ], rx1,ry0); + + // Interpolation along the X axis. + F32 a = lerp(sx, u, v); + + u = dot(mGradient[ b01 ], rx0,ry1); + v = dot(mGradient[ b11 ], rx1,ry1); + + // Interpolation along the Y axis. + F32 b = lerp(sx, u, v); + + // Final Interpolation + return lerp(sy, a, b); +} + + diff --git a/editor/terraformerNoise.h b/editor/terraformerNoise.h new file mode 100644 index 0000000..d3ccc97 --- /dev/null +++ b/editor/terraformerNoise.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRAFORMER_NOISE_H_ +#define _TERRAFORMER_NOISE_H_ + + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _MRANDOM_H_ +#include "Math/mRandom.h" +#endif + +struct Heightfield; + +class Noise2D +{ +private: + enum Constants { + SIZE = 0x100, + SIZE_MASK = 0x0ff + }; + S32 mPermutation[SIZE + SIZE + 2]; + F32 mGradient[SIZE + SIZE + 2][2]; + + U32 mSeed; + + MRandom mRandom; + + F32 lerp(F32 t, F32 a, F32 b); + F32 curve(F32 t); + void setup(F32 t, S32 &b0, S32 &b1, F32 &r0, F32 &r1); + F32 dot(const F32 *q, F32 rx, F32 ry); + void normalize(F32 v[2]); + + +public: + Noise2D(); + ~Noise2D(); + + void setSeed(U32 seed); + U32 getSeed(); + + F32 getValue(F32 u, F32 v, S32 interval); + void fBm(Heightfield *dst, U32 size, U32 interval, F32 h, F32 octave=5.0f); + void rigidMultiFractal(Heightfield *dst, Heightfield *signal, U32 size, U32 interval, F32 h, F32 octave=5.0f); + F32 turbulence(F32 x, F32 y, F32 freq); +}; + + + +#endif // _H_TERRAFORMER_NOISE_ diff --git a/editor/terraformerTexture.cc b/editor/terraformerTexture.cc new file mode 100644 index 0000000..0f946c4 --- /dev/null +++ b/editor/terraformerTexture.cc @@ -0,0 +1,415 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "terrain/terrData.h" +#include "editor/terraformer.h" +#include "gui/guiFilterCtrl.h" +#include "editor/editor.h" +#include "platform/event.h" +#include "game/gameConnection.h" + +#include "core/fileStream.h" + + + +inline F32 lerp(F32 t, F32 a, F32 b) +{ + return a + t * (b - a); +} + + +inline F32 curve(F32 t) +{ + return t * t * (3.0f - 2.0f * t); +} + + +F32 getAlpha(U32 x, U32 y, Heightfield *alpha) +{ + F32 xFactor = F32(x & 7) * (1.0f/8.0f); + F32 yFactor = F32(y & 7) * (1.0f/8.0f); + U32 xi = x >> 3; + U32 yi = y >> 3; + + F32 a0 = alpha->val(xi, yi); + F32 a1 = alpha->val(xi+1, yi); + F32 a2 = alpha->val(xi+1, yi+1); + F32 a3 = alpha->val(xi, yi+1); + +// F32 ah0 = (a0 * (1.0f-xFactor)) + (a1 * xFactor); +// F32 ah1 = (a3 * (1.0f-xFactor)) + (a2 * xFactor); +// +// F32 a = (ah0 * (1.0f-yFactor)) + (ah1 * yFactor); + + //xFactor = curve(xFactor); + //yFactor = curve(yFactor); + + F32 ah0 = lerp(xFactor, a0, a1); + F32 ah1 = lerp(xFactor, a3, a2); + F32 a = lerp(yFactor, ah0, ah1); + + return (a*a); +} + + +GBitmap* merge(VectorPtr &alpha, VectorPtr &material) +{ + // due to memory constraints we build the the output bitmap one scan-line at a time. + F32 sum[2048]; + GBitmap *bitmap = new GBitmap(2048, 2048, false, GBitmap::RGB); + VectorPtr::iterator itrA; + VectorPtr::iterator itrM; + + for (S32 y = 0; y<2048; y++) + { + // first compute the sum of the alphas at each pixel + S32 x; + for (x = 0; x<2048; x++) + { + sum[x] = 0.0f; + for (itrA = alpha.begin(); itrA != alpha.end(); itrA++) + sum[x] += getAlpha(x,y,*itrA); + } + + // blend the pixels + for (x = 0; x<2048; x++) + { + ColorI blend(0,0,0,0); + if (sum[x] > 0.0f) + { + F32 fsum = sum[x]; + F32 scaleFactor = (1.0f/fsum); + for (itrA = alpha.begin(), itrM = material.begin(); itrM != material.end(); itrM++, itrA++) + { + ColorI color; + GBitmap *bmp = *itrM; + bmp->getColor(x % bmp->getWidth(), y % bmp->getHeight(), color); + color *= getAlpha(x,y,*itrA) * scaleFactor; + blend.red += color.red; + blend.green += color.green; + blend.blue += color.blue; + } + } + else + { + GBitmap *mat = *material.begin(); + mat->getColor(x % mat->getWidth(), y % mat->getHeight(), blend); + } + bitmap->setColor(x,y,blend); + } + } + return bitmap; +} + + +//-------------------------------------- +bool Terraformer::setMaterials(const char *r_src, const char *materials ) +{ + TerrainBlock *serverTerrBlock = dynamic_cast(Sim::findObject("Terrain")); + if (!serverTerrBlock) + return false; + + NetConnection* toServer = NetConnection::getServerConnection(); + NetConnection* toClient = NetConnection::getLocalClientConnection(); + + S32 index = toClient->getGhostIndex(serverTerrBlock); + + TerrainBlock *clientTerrBlock = dynamic_cast(toServer->resolveGhost(index)); + if (!clientTerrBlock) + return false; + + VectorPtr src; + VectorPtr dml; + Vector dmlIndex; + + //-------------------------------------- + // extract the source registers + char buffer[1024]; + dStrcpy(buffer, r_src); + char *str = dStrtok(buffer, " \0"); + while (str) + { + src.push_back( getRegister(dAtof(str)) ); + str = dStrtok(NULL, " \0"); + } + + //-------------------------------------- + // extract the materials + dStrcpy(buffer, materials); + str = dStrtok(buffer, " \0"); + while (str) + { + S32 i; + for (i=0; i TerrainBlock::MaterialGroups) + { + Con::printf("maximum number of DML Material Exceeded"); + return false; + } + + // install the new DMLs + clientTerrBlock->setBaseMaterials(dml.size(), (const char**)dml.address()); + + //-------------------------------------- + // build alpha masks for each material type + + for (S32 y=0; yval(x,y); + total += src[i]->val(x,y); + } + + if(total == 0) + { + matVals[0] = 1; + total = 1; + } + + // axe out any amount that is less than the threshold + F32 threshold = 0.15 * total; + for(i = 0; i < TerrainBlock::MaterialGroups; i++) + if(matVals[i] < threshold) + matVals[i] = 0; + + total = 0; + for(i = 0; i < TerrainBlock::MaterialGroups; i++) + total += matVals[i]; + + for(i = 0; i < TerrainBlock::MaterialGroups; i++) + { + U8 *map = clientTerrBlock->getMaterialAlphaMap(i); + map[x + (y << TerrainBlock::BlockShift)] = (U8)(255 * matVals[i] / total); + } + + S32 material = 0; + F32 best = 0.0f; + for (i=0; ival(x,y); + if ( value > best) + { + material = dmlIndex[i]; + best = value; + } + } + // place the material + *clientTerrBlock->getBaseMaterialAddress(x, y) = material; + } + } + + // make it so! + clientTerrBlock->buildGridMap(); + clientTerrBlock->buildMaterialMap(); + + // reload the material lists? + if(gEditingMission) + clientTerrBlock->refreshMaterialLists(); + + //-------------------------------------------------------------------------- + // for mow steal the first bitmap out of each dml + + if (Con::getBoolVariable("$terrainTestBmp", false) == true) + { + VectorPtr mats; + for (S32 i=0; i mlist = ResourceManager->load(buf); + mlist->load(); + GBitmap *bmp = mlist->getMaterial(0).getBitmap(); + mats.push_back(bmp); + } + GBitmap *texture = merge(src,mats); + + FileStream stream; + stream.open("terrain.png", FileStream::Write); + texture->writePNG(stream); + stream.close(); + delete texture; + } + + return true; +} + + +//-------------------------------------- +bool Terraformer::mergeMasks(const char *r_src, U32 r_dst) +{ + Heightfield *dst = getRegister(r_dst); + VectorPtr src; + + // extract the source registers + char buffer[1024]; + dStrcpy(buffer, r_src); + char *reg = dStrtok(buffer, " \0"); + while (reg) + { + src.push_back( getRegister(dAtoi(reg)) ); + reg = dStrtok(NULL, " \0"); + } + + // if no masks set the destination to Zero + if (src.size() == 0) + { + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) = 0.0f; + return true; + } + + if (src.size() == 1) + { + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) = src[0]->val(i); + return true; + } + + // store the MAX of the masks into dst + for (S32 i=0; i < (blockSize*blockSize); i++) + { + F32 value = src[0]->val(i); + for (S32 j=1; jval(i); + dst->val(i) = value; + } + + return true; +} + + +//-------------------------------------- +bool Terraformer::maskFBm(U32 r_dst, U32 interval, F32 roughness, U32 seed, const Filter &filter, bool distort, U32 r_distort) +{ + Heightfield *dst = getRegister(r_dst); + noise.setSeed(seed); + noise.fBm(dst, blockSize, interval, 1.0-roughness, 3.0f); + + scale(r_dst, r_dst, 0.0f, 1.0f); + + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) = filter.getValue( dst->val(i) ); + + if (distort) + { + Heightfield *d = getRegister(r_distort); + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) *= d->val(i); + } + return true; +} + + +//-------------------------------------- +bool Terraformer::maskHeight(U32 r_src, U32 r_dst, const Filter &filter, bool distort, U32 r_distort) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + scale(r_src, r_dst, 0.0f, 1.0f); + + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) = filter.getValue(dst->val(i)); + + if (distort) + { + Heightfield *d = getRegister(r_distort); + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) *= d->val(i); + } + return true; +} + + +//-------------------------------------- +bool Terraformer::maskSlope(U32 r_src, U32 r_dst, const Filter &filter, bool distort, U32 r_distort) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + F32 fmin, fmax; + getMinMax(r_src, &fmin, &fmax); + F32 scale = worldHeight / (fmax-fmin); + + for (S32 y=0; yblock(x,y,array); + F32 height = array[4]; + + for (S32 i=0; i<9; i++) + { + F32 delta = mFabs(array[i] - height); + if ( (i&1) == 0) + delta *= 0.70711f; // compensate for diagonals + + if (delta > maxDelta) + maxDelta = delta; + } + F32 slopeVal = mAtan( maxDelta * scale, worldTileSize ) * (2.0f/M_PI); + dst->val(x, y) = filter.getValue( mPow(slopeVal, 1.5f) ); + } + } + + if (distort) + { + Heightfield *d = getRegister(r_distort); + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) *= d->val(i); + } + return true; +} + + +//-------------------------------------- +bool Terraformer::maskWater(U32 r_src, U32 r_dst, bool distort, U32 r_distort) +{ + Heightfield *src = getRegister(r_src); + Heightfield *dst = getRegister(r_dst); + + scale(r_src, r_dst, 0.0f, 1.0f); + + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) = (dst->val(i) > worldWater) ? 0.0f : 1.0f; + + if (distort) + { + Heightfield *d = getRegister(r_distort); + for (S32 i=0; i < (blockSize*blockSize); i++) + dst->val(i) *= d->val(i); + } + return true; +} diff --git a/editor/terrainActions.cc b/editor/terrainActions.cc new file mode 100644 index 0000000..e286989 --- /dev/null +++ b/editor/terrainActions.cc @@ -0,0 +1,454 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Editor/terrainActions.h" +#include "Platform/event.h" +#include "GUI/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_CTRL) + { + 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].mGridPos, 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 SoftSelectAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type type) +{ + // 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 = mTerrainEditor->getTerrainBlock()->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(info.mGridPos.x, info.mGridPos.y); + + // + for(S32 x = info.mGridPos.x - offset; x < info.mGridPos.x + (offset << 1); x++) + for(S32 y = info.mGridPos.y - offset; y < info.mGridPos.y + (offset << 1); y++) + { + // + Point2F pos(x, y); + + F32 dist = Point2F(pos - infoPos).len() * F32(squareSize); + + if(dist > radius) + continue; + + F32 weight = mFilter.getValue(dist / radius); + + // + GridInfo gInfo; + if(mTerrainEditor->getCurrentSel()->getInfo(Point2I(x, y), gInfo)) + { + if(gInfo.mPrimarySelect) + continue; + + if(gInfo.mWeight < weight) + { + gInfo.mWeight = weight; + mTerrainEditor->getCurrentSel()->setInfo(gInfo); + } + } + else + { + mTerrainEditor->getGridInfo(Point2I(x, y), gInfo); + gInfo.mWeight = weight; + gInfo.mPrimarySelect = false; + mTerrainEditor->getCurrentSel()->add(gInfo); + } + } + } + } +} + +//------------------------------------------------------------------------------ + +void OutlineSelectAction::process(Selection * sel, const Gui3DMouseEvent & event, bool, Type type) +{ + sel;event;type; + switch(type) + { + case Begin: + if(event.modifier & SI_SHIFT) + break; + + mTerrainEditor->getCurrentSel()->reset(); + break; + + case End: + case Update: + + default: + return; + } + + mLastEvent = event; +} + +//------------------------------------------------------------------------------ + +void RaiseHeightAction::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->mAdjustHeightVal * (*sel)[i].mWeight; + mTerrainEditor->setGridInfo((*sel)[i]); + } +} + +//------------------------------------------------------------------------------ + +void LowerHeightAction::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->mAdjustHeightVal * (*sel)[i].mWeight; + mTerrainEditor->setGridInfo((*sel)[i]); + } +} + +//------------------------------------------------------------------------------ + +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]); + } +} + +//------------------------------------------------------------------------------ + +void SetEmptyAction::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].mMaterial.flags |= TerrainBlock::Material::Empty; + mTerrainEditor->setGridInfo((*sel)[i]); + } +} + +//------------------------------------------------------------------------------ + +void ClearEmptyAction::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].mMaterial.flags &= ~TerrainBlock::Material::Empty; + mTerrainEditor->setGridInfo((*sel)[i]); + } +} + +//------------------------------------------------------------------------------ + +void SetModifiedAction::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].mMaterial.flags |= TerrainBlock::Material::Modified; + mTerrainEditor->setGridInfo((*sel)[i]); + } +} + +//------------------------------------------------------------------------------ + +void ClearModifiedAction::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].mMaterial.flags &= ~TerrainBlock::Material::Modified; + mTerrainEditor->setGridInfo((*sel)[i]); + } +} + +//------------------------------------------------------------------------------ + +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]); + } +} + +void BrushAdjustHeightAction::process(Selection * sel, const Gui3DMouseEvent & event, bool, Type type) +{ + if(type == Process) + return; + + // + if(type == Begin) + { + mTerrainEditor->lockSelection(true); + + mFirstPos = mLastPos = event.mousePoint; + Canvas->mouseLock(mTerrainEditor); + + // add to undo + for(U32 i = 0; i < sel->size(); i++) + mTerrainEditor->getUndoSel()->add((*sel)[i]); + } + else if(type == Update) + { + // + F32 diff = (event.mousePoint.x - mLastPos.x) * mTerrainEditor->mAdjustHeightMouseScale; + + for(U32 i = 0; i < sel->size(); i++) + { + (*sel)[i].mHeight += 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->setGridInfo((*sel)[i]); + } + + mLastPos = event.mousePoint; + } + else if(type == End) + { + Canvas->mouseUnlock(mTerrainEditor); + Canvas->setCursorPos(mFirstPos); + } +} + +//------------------------------------------------------------------------------ + +AdjustHeightAction::AdjustHeightAction(TerrainEditor * editor) : + TerrainAction(editor) +{ + mCursor = 0; +} + +void AdjustHeightAction::process(Selection *, const Gui3DMouseEvent & event, bool, Type type) +{ + if(type == Process) + return; + + Selection * curSel = mTerrainEditor->getCurrentSel(); + + // + if(type == Begin) + { + mTerrainEditor->lockSelection(true); + + if(!mTerrainEditor->collide(event, mHitPos)) + return; + + if(!bool(mCursor)) + mCursor = dynamic_cast(Sim::findObject("Editor_HandCursor")); + + if(bool(mCursor)) + mTerrainEditor->setCursor(mCursor); + + mLastPos = mHitPos; + + // add to undo + for(U32 i = 0; i < curSel->size(); i++) + mTerrainEditor->getUndoSel()->add((*curSel)[i]); + } + else if(type == Update) + { + // do a projection onto the z axis + F64 dist = mSqrt((event.pos.x - mHitPos.x) * (event.pos.x - mHitPos.x) + + (event.pos.y - mHitPos.y) * (event.pos.y - mHitPos.y)); + + Point3F vec(mHitPos.x - event.pos.x, mHitPos.y - event.pos.y, 0.f); + vec.normalize(); + + F64 projDist = mDot(event.vec, vec); + if(projDist == 0.f) + return; + + F64 scale = dist / projDist; + vec = event.pos + (event.vec * scale); + + for(U32 i = 0; i < curSel->size(); i++) + { + F32 diff = (vec.z - mLastPos.z) * (*curSel)[i].mWeight; + (*curSel)[i].mHeight += diff; + + // clamp it + if((*curSel)[i].mHeight < 0.f) + (*curSel)[i].mHeight = 0.f; + if((*curSel)[i].mHeight > 2047.f) + (*curSel)[i].mHeight = 2047.f; + + mTerrainEditor->setGridInfo((*curSel)[i]); + } + + mLastPos = vec; + } +} + +//------------------------------------------------------------------------------ +// 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]); + } + } +} + +//------------------------------------------------------------------------------ + +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]); + } + } +} + +void SetMaterialGroupAction::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].mMaterial.flags |= TerrainBlock::Material::Modified; + (*sel)[i].mMaterialGroup = mTerrainEditor->mMaterialGroup; + mTerrainEditor->setGridInfo((*sel)[i]); + } +} diff --git a/editor/terrainActions.h b/editor/terrainActions.h new file mode 100644 index 0000000..0475d87 --- /dev/null +++ b/editor/terrainActions.h @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRAINACTIONS_H_ +#define _TERRAINACTIONS_H_ + +#ifndef _TERRAINEDITOR_H_ +#include "Editor/terrainEditor.h" +#endif +#ifndef _GUIFILTERCTRL_H_ +#include "GUI/guiFilterCtrl.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 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 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 SetModifiedAction : public TerrainAction +{ + public: + SetModifiedAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("setModified");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +//------------------------------------------------------------------------------ + +class ClearModifiedAction : public TerrainAction +{ + public: + ClearModifiedAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("clearModified");} + + 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: + Point2I mFirstPos; + Point2I mLastPos; + +// private: +// // +// Point3F mHitPos; +// Point3F mLastPos; +}; + +class AdjustHeightAction : public TerrainAction +{ + 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 SetMaterialGroupAction : public TerrainAction +{ + public: + SetMaterialGroupAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("setMaterialGroup");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +#endif diff --git a/editor/terrainEditor.cc b/editor/terrainEditor.cc new file mode 100644 index 0000000..991c6e7 --- /dev/null +++ b/editor/terrainEditor.cc @@ -0,0 +1,1703 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "editor/terrainEditor.h" +#include "game/collisionTest.h" +#include "terrain/terrData.h" +#include "gui/guiCanvas.h" +#include "console/consoleTypes.h" +#include "editor/terrainActions.h" +#include "interior/interiorInstance.h" +#include "interior/interior.h" +#include "game/gameConnection.h" +#include "sim/netObject.h" +#include "sim/frameAllocator.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(); +} + +void Selection::reset() +{ + for(U32 i = 0; i < mHashListSize; i++) + mHashLists[i] = -1; + clear(); +} + +U32 Selection::getHashIndex(const Point2I & pos) +{ + Point2F pnt = Point2F(pos.x, pos.y) + Point2F(1.3f,3.5f); + return( (U32)(mFloor(mHashLists.size() * mFmod(pnt.len() * 0.618f, 1))) ); +} + +S32 Selection::lookup(const Point2I & pos) +{ + U32 index = getHashIndex(pos); + + S32 entry = mHashLists[index]; + + while(entry != -1) + { + if((*this)[entry].mGridPos == pos) + return(entry); + + entry = (*this)[entry].mNext; + } + + return(-1); +} + +void Selection::insert(GridInfo & info) +{ + U32 index = getHashIndex(info.mGridPos); + + info.mNext = mHashLists[index]; + info.mPrev = -1; + + if(info.mNext != -1) + (*this)[info.mNext].mPrev = size(); + + mHashLists[index] = size(); + + push_back(info); +} + +bool Selection::remove(const GridInfo & info) +{ + U32 index = getHashIndex(info.mGridPos); + + S32 entry = mHashLists[index]; + + if(entry == -1) + return(false); + + // front? + if((*this)[entry].mGridPos == info.mGridPos) + mHashLists[index] = (*this)[entry].mNext; + + while(entry != -1) + { + if((*this)[entry].mGridPos == info.mGridPos) + { + if((*this)[entry].mPrev != -1) + (*this)[(*this)[entry].mPrev].mNext = (*this)[entry].mNext; + if((*this)[entry].mNext != -1) + (*this)[(*this)[entry].mNext].mPrev = (*this)[entry].mPrev; + + // swap? + if(entry != (size() - 1)) + { + U32 last = size() - 1; + + (*this)[entry] = (*this)[size()-1]; + + if((*this)[entry].mPrev != -1) + (*this)[(*this)[entry].mPrev].mNext = entry; + else + { + U32 idx = getHashIndex((*this)[entry].mGridPos); + AssertFatal(mHashLists[idx] == ((*this).size() - 1), "doh"); + mHashLists[idx] = entry; + } + if((*this)[entry].mNext != -1) + (*this)[(*this)[entry].mNext].mPrev = entry; + } + + pop_back(); + return(true); + } + + entry = (*this)[entry].mNext; + } + return(false); +} + +// add unique grid info into the selection - test uniqueness by grid position +bool Selection::add(GridInfo & info) +{ + S32 index = lookup(info.mGridPos); + 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.mGridPos); + 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()); +} + +//------------------------------------------------------------------------------ + +Brush::Brush(TerrainEditor * editor) : + mTerrainEditor(editor) +{ + mSize = mTerrainEditor->getBrushSize(); +} + +const Point2I & Brush::getPosition() +{ + return(mGridPos); +} + +void Brush::setPosition(const Point3F & pos) +{ + Point2I gPos; + mTerrainEditor->worldToGrid(pos, gPos); + setPosition(gPos); +} + +void Brush::setPosition(const Point2I & pos) +{ + mGridPos = pos; + update(); +} + +//------------------------------------------------------------------------------ + +void Brush::update() +{ + rebuild(); + + // soft selection? + if(mTerrainEditor->mEnableSoftBrushes) + { + Gui3DMouseEvent event; + TerrainAction * action = mTerrainEditor->lookupAction("softSelect"); + AssertFatal(action, "Brush::update: no 'softSelect' action found!"); + + // + mTerrainEditor->setCurrentSel(this); + action->process(this, event, true, TerrainAction::Process); + mTerrainEditor->resetCurrentSel(); + } +} + +//------------------------------------------------------------------------------ + +void BoxBrush::rebuild() +{ + reset(); + + // + for(U32 x = 0; x < mSize.x; x++) + for(U32 y = 0; y < mSize.y; y++) + { + GridInfo info; + mTerrainEditor->getGridInfo(Point2I(mGridPos.x + x - (mSize.x / 2), mGridPos.y + y - (mSize.y / 2)), info); + push_back(info); + } +} + +//------------------------------------------------------------------------------ + +void EllipseBrush::rebuild() +{ + reset(); + Point3F center(F32(mSize.x) / 2, F32(mSize.y) / 2, 0); + + for(U32 x = 0; x < mSize.x; x++) + for(U32 y = 0; y < mSize.y; y++) + { + F32 a = mSize.x >= mSize.y ? F32(mSize.x) / 2 : F32(mSize.y) / 2; + F32 b = mSize.x >= mSize.y ? F32(mSize.y) / 2 : F32(mSize.x) / 2; + Point3F dir(mSize.x >= mSize.y ? 1 : 0, mSize.x >= mSize.y ? 0 : 1, 0); + + // first do quick check on minor + Point3F pos(F32(x) + 0.5, F32(y) + 0.5, 0); + Point3F vec = pos - center; + F32 len = vec.len(); + + bool addPoint = false; + if(len <= b) + addPoint = true; + else + { + F32 theta = mAcos(mDot(vec, dir)); + F32 as = a*a; + F32 bs = b*b; + + F32 r = mSqrt((bs * as) / ((bs * (1+2*mCos(theta))) + (as * (1-2*mCos(theta))))); + if(len <= r) + addPoint = true; + } + + if(addPoint) + { + GridInfo info; + mTerrainEditor->getGridInfo(Point2I(mGridPos.x + x, mGridPos.y + y), info); + push_back(info); + } + } +} + +//------------------------------------------------------------------------------ + +SelectionBrush::SelectionBrush(TerrainEditor * editor) : + Brush(editor) +{ + //... grab the current selection +} + +void SelectionBrush::rebuild() +{ + reset(); + //... move the selection +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +TerrainEditor::TerrainEditor() : + mTerrainBlock(0), + mMousePos(0,0,0), + mMouseBrush(0), + mInAction(false), + mUndoLimit(20), + mUndoSel(0), + mRebuildEmpty(false), + mRebuildTextures(false) +{ + VECTOR_SET_ASSOCIATION(mActions); + VECTOR_SET_ASSOCIATION(mUndoList); + VECTOR_SET_ASSOCIATION(mRedoList); + VECTOR_SET_ASSOCIATION(mBaseMaterialInfos); + + // + resetCurrentSel(); + + // + mBrushSize.set(2,2); + mMouseBrush = new BoxBrush(this); + + // add in all the actions here.. + mActions.push_back(new SelectAction(this)); + mActions.push_back(new SoftSelectAction(this)); + mActions.push_back(new OutlineSelectAction(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 SetMaterialGroupAction(this)); + mActions.push_back(new SetModifiedAction(this)); + mActions.push_back(new ClearModifiedAction(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; + mEnableSoftBrushes = false; + mRenderVertexSelection = false; + mProcessUsesBrush = false; + + // + mAdjustHeightVal = 10; + mSetHeightVal = 100; + mScaleVal = 1; + mSmoothFactor = 0.1f; + 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;; +} + +TerrainEditor::~TerrainEditor() +{ + // mouse + delete mMouseBrush; + + // terrain actions + U32 i; + for(i = 0; i < mActions.size(); i++) + delete mActions[i]; + + // undo stuff + clearUndo(mUndoList); + clearUndo(mRedoList); + delete mUndoSel; + + // base material infos + for(i = 0; i < mBaseMaterialInfos.size(); i++) + delete mBaseMaterialInfos[i]; +} + +//------------------------------------------------------------------------------ + +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("Editor_ArrowCursor"); + if(!obj) + { + Con::errorf(ConsoleLogEntry::General, "TerrainEditor::onAdd: failed to load cursor"); + return(false); + } + + mDefaultCursor = dynamic_cast(obj); + + return(true); +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::onDeleteNotify(SimObject * object) +{ + Parent::onDeleteNotify(object); + + if(mTerrainBlock != dynamic_cast(object)) + return; + + mTerrainBlock = 0; +} + +void TerrainEditor::setCursor(GuiCursor * cursor) +{ + Canvas->setCursor(cursor ? cursor : mDefaultCursor); +} + +//------------------------------------------------------------------------------ + +TerrainBlock * TerrainEditor::getClientTerrain() +{ + // do the client.. + + NetConnection * toServer = NetConnection::getServerConnection(); + NetConnection * toClient = NetConnection::getLocalClientConnection(); + + S32 index = toClient->getGhostIndex(mTerrainBlock); + + return(dynamic_cast(toServer->resolveGhost(index))); +} + +//------------------------------------------------------------------------------ + +bool TerrainEditor::gridToWorld(const Point2I & gPos, Point3F & wPos) +{ + const MatrixF & mat = mTerrainBlock->getTransform(); + Point3F origin; + mat.getColumn(3, &origin); + + wPos.x = gPos.x * (float)mTerrainBlock->getSquareSize() + origin.x; + wPos.y = gPos.y * (float)mTerrainBlock->getSquareSize() + origin.y; + wPos.z = getGridHeight(gPos); + + return(!(gPos.x >> TerrainBlock::BlockShift || gPos.y >> TerrainBlock::BlockShift)); +} + +bool TerrainEditor::worldToGrid(const Point3F & wPos, Point2I & gPos) +{ + const MatrixF & mat = mTerrainBlock->getTransform(); + Point3F origin; + mat.getColumn(3, &origin); + + float x = (wPos.x - origin.x) / (float)mTerrainBlock->getSquareSize(); + float y = (wPos.y - origin.y) / (float)mTerrainBlock->getSquareSize(); + + gPos.x = (S32)mFloor(x); + gPos.y = (S32)mFloor(y); + + return(!(gPos.x >> TerrainBlock::BlockShift || gPos.y >> TerrainBlock::BlockShift)); +} + +bool TerrainEditor::gridToCenter(const Point2I & gPos, Point2I & cPos) +{ + cPos.x = gPos.x & TerrainBlock::BlockMask; + cPos.y = gPos.y & TerrainBlock::BlockMask; + + return(!(gPos.x >> TerrainBlock::BlockShift || gPos.y >> TerrainBlock::BlockShift)); +} + +//------------------------------------------------------------------------------ + +bool TerrainEditor::getGridInfo(const Point3F & wPos, GridInfo & info) +{ + Point2I gPos; + bool center = worldToGrid(wPos, gPos); + + // + info.mGridPos = gPos; + info.mMaterial = getGridMaterial(gPos); + info.mHeight = getGridHeight(gPos); + info.mMaterialGroup = getGridMaterialGroup(gPos); + info.mWeight = 1.f; + info.mPrimarySelect = true; + + return(center); +} + +bool TerrainEditor::getGridInfo(const Point2I & gPos, GridInfo & info) +{ + // + info.mGridPos = gPos; + info.mMaterial = getGridMaterial(gPos); + info.mHeight = getGridHeight(gPos); + info.mMaterialGroup = getGridMaterialGroup(gPos); + info.mWeight = 1.f; + info.mPrimarySelect = true; + + return(!(gPos.x >> TerrainBlock::BlockShift || gPos.y >> TerrainBlock::BlockShift)); +} + +void TerrainEditor::setGridInfo(const GridInfo & info) +{ + setGridHeight(info.mGridPos, info.mHeight); + setGridMaterial(info.mGridPos, info.mMaterial); + setGridMaterialGroup(info.mGridPos, info.mMaterialGroup); +} + +//------------------------------------------------------------------------------ + +F32 TerrainEditor::getGridHeight(const Point2I & gPos) +{ + Point2I cPos; + gridToCenter(gPos, cPos); + return(fixedToFloat(mTerrainBlock->getHeight(cPos.x, cPos.y))); +} + +void TerrainEditor::setGridHeight(const Point2I & gPos, const F32 height) +{ + Point2I cPos; + gridToCenter(gPos, cPos); + mTerrainBlock->setHeight(cPos, height); +} + +TerrainBlock::Material TerrainEditor::getGridMaterial(const Point2I & gPos) +{ + Point2I cPos; + gridToCenter(gPos, cPos); + return(*mTerrainBlock->getMaterial(cPos.x, cPos.y)); +} + +void TerrainEditor::setGridMaterial(const Point2I & gPos, const TerrainBlock::Material & material) +{ + Point2I cPos; + gridToCenter(gPos, cPos); + + // check if empty has been altered... + TerrainBlock::Material * mat = mTerrainBlock->getMaterial(cPos.x, cPos.y); + + if((mat->flags & TerrainBlock::Material::Empty) ^ (material.flags & TerrainBlock::Material::Empty)) + mRebuildEmpty = true; + + *mat = material; +} + +U8 TerrainEditor::getGridMaterialGroup(const Point2I & gPos) +{ + Point2I cPos; + gridToCenter(gPos, cPos); + + return(mTerrainBlock->getBaseMaterial(cPos.x, cPos.y)); +} + +// basematerials are shared through a resource... so work on client object +// so wont need to load textures.... + +void TerrainEditor::setGridMaterialGroup(const Point2I & gPos, const U8 group) +{ + Point2I cPos; + gridToCenter(gPos, cPos); + + TerrainBlock * clientTerrain = getClientTerrain(); + + clientTerrain->setBaseMaterial(cPos.x, cPos.y, group); +} + +//------------------------------------------------------------------------------ + +bool TerrainEditor::collide(const Gui3DMouseEvent & event, Point3F & pos) +{ + if(!mTerrainBlock) + return(false); + + // call the terrain block's ray collision routine directly + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000; + Point3F tStartPnt, tEndPnt; + + mTerrainBlock->getTransform().mulP(startPnt, &tStartPnt); + mTerrainBlock->getTransform().mulP(endPnt, &tEndPnt); + + RayInfo ri; + if(mTerrainBlock->castRayI(tStartPnt, tEndPnt, &ri, true)) + { + ri.point.interpolate(startPnt, endPnt, ri.t); + pos = ri.point; + return(true); + } + return(false); +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::updateGuiInfo() +{ + char buf[128]; + + // mouse num grids + // mouse avg height + // selection num grids + // selection avg height + dSprintf(buf, sizeof(buf), "%d %f %d %f", + mMouseBrush->size(), mMouseBrush->getAvgHeight(), + mDefaultSel.size(), mDefaultSel.getAvgHeight()); + Con::executef(this, 2, "onGuiUpdate", buf); +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::renderScene(const RectI &) +{ + if(!mTerrainBlock) + 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) + renderSelection(*mMouseBrush, ColorF(1,0,0), ColorF(0,1,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 ) +{ + for(U32 i = 0; i < sel.size(); i++) + { + Point3F wPos; + bool center = gridToWorld(sel[i].mGridPos, wPos); + + ColorF color; + if(center) + { + if(sel[i].mWeight < 0.f || sel[i].mWeight > 1.f) + color = inColorFull; + else + { + Point3F pnt; + pnt.interpolate(Point3F(inColorNone.red, inColorNone.green, inColorNone.blue), + Point3F(inColorFull.red, inColorFull.green, inColorFull.blue), + sel[i].mWeight); + color.set(pnt.x, pnt.y, pnt.z); + } + } + else + { + if(sel[i].mWeight < 0.f || sel[i].mWeight > 1.f) + color = outColorFull; + else + { + Point3F pnt; + pnt.interpolate(Point3F(outColorFull.red, outColorFull.green, outColorFull.blue), + Point3F(outColorNone.red, outColorNone.green, outColorNone.blue), sel[i].mWeight); + color.set(pnt.x, pnt.y, pnt.z); + } + } + + if(mRenderVertexSelection) + { + // + if(renderFill) + { + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA); + + glBegin(GL_QUADS); + glColor3f(color.red, color.green, color.blue); + glVertex3f(wPos.x - 1, wPos.y - 1, wPos.z); + glVertex3f(wPos.x + 1, wPos.y - 1, wPos.z); + glVertex3f(wPos.x + 1, wPos.y + 1, wPos.z); + glVertex3f(wPos.x - 1, wPos.y + 1, wPos.z); + glEnd(); + + glDisable(GL_BLEND); + } + + if(renderFrame) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glBegin(GL_QUADS); + glColor3f(color.red, color.green, color.blue); + glVertex3f(wPos.x - 1, wPos.y - 1, wPos.z); + glVertex3f(wPos.x + 1, wPos.y - 1, wPos.z); + glVertex3f(wPos.x + 1, wPos.y + 1, wPos.z); + glVertex3f(wPos.x - 1, wPos.y + 1, wPos.z); + glEnd(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + } + else + { + // walk the points in the selection + for(U32 i = 0; i < sel.size(); i++) + { + Point3F wPos[4]; + Point2I gPos = sel[i].mGridPos; + + bool center = gridToWorld(gPos, wPos[0]); + gridToWorld(Point2I(gPos.x + 1, gPos.y), wPos[1]); + gridToWorld(Point2I(gPos.x + 1, gPos.y + 1), wPos[2]); + gridToWorld(Point2I(gPos.x, gPos.y + 1), wPos[3]); + + // + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA); + + if(renderFill) + { + glBegin(GL_QUADS); + glColor3f(color.red, color.green, color.blue); + for(U32 i = 0; i < 4; i++) + glVertex3f(wPos[i].x, wPos[i].y, wPos[i].z); + glEnd(); + } + + glDisable(GL_BLEND); + + if(renderFrame) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glBegin(GL_QUADS); + glColor3f(color.red, color.green, color.blue); + for(U32 k = 0; k < 4; k++) + glVertex3f(wPos[k].x, wPos[k].y, wPos[k].z); + glEnd(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + } + } + } +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::renderBorder() +{ + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + Point2I pos(0,0); + Point2I dir[4] = { + Point2I(1,0), + Point2I(0,1), + Point2I(-1,0), + Point2I(0,-1) + }; + + // + if(mBorderLineMode) + { + glColor4ub(mBorderFrameColor.red, mBorderFrameColor.green, mBorderFrameColor.blue, mBorderFrameColor.alpha); + glBegin(GL_LINE_STRIP); + for(U32 i = 0; i < 4; i++) + { + for(U32 j = 0; j < TerrainBlock::BlockSize; j++) + { + Point3F wPos; + gridToWorld(pos, wPos); + glVertex3f(wPos.x, wPos.y, wPos.z); + pos += dir[i]; + } + } + + Point3F wPos; + gridToWorld(Point2I(0,0), wPos); + glVertex3f(wPos.x, wPos.y, wPos.z); + glEnd(); + } + else + { + glEnable(GL_DEPTH_TEST); + GridSquare * gs = mTerrainBlock->findSquare(TerrainBlock::BlockShift, Point2I(0,0)); + F32 height = F32(gs->maxHeight) * 0.03125f + mBorderHeight; + + const MatrixF & mat = mTerrainBlock->getTransform(); + Point3F pos; + mat.getColumn(3, &pos); + + Point2F min(pos.x, pos.y); + Point2F max(pos.x + TerrainBlock::BlockSize * mTerrainBlock->getSquareSize(), + pos.y + TerrainBlock::BlockSize * mTerrainBlock->getSquareSize()); + + ColorI & a = mBorderFillColor; + ColorI & b = mBorderFrameColor; + + for(U32 i = 0; i < 2; i++) + { + // + if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);} + glVertex3f(min.x, min.y, 0); + glVertex3f(max.x, min.y, 0); + glVertex3f(max.x, min.y, height); + glVertex3f(min.x, min.y, height); + glEnd(); + + // + if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);} + glVertex3f(min.x, max.y, 0); + glVertex3f(max.x, max.y, 0); + glVertex3f(max.x, max.y, height); + glVertex3f(min.x, max.y, height); + glEnd(); + + // + if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);} + glVertex3f(min.x, min.y, 0); + glVertex3f(min.x, max.y, 0); + glVertex3f(min.x, max.y, height); + glVertex3f(min.x, min.y, height); + glEnd(); + + // + if(i){glColor4ub(a.red,a.green,a.blue,a.alpha);glBegin(GL_QUADS);} else {glColor4f(b.red,b.green,b.blue,b.alpha); glBegin(GL_LINE_LOOP);} + glVertex3f(max.x, min.y, 0); + glVertex3f(max.x, max.y, 0); + glVertex3f(max.x, max.y, height); + glVertex3f(max.x, min.y, height); + glEnd(); + } + glDisable(GL_DEPTH_TEST); + } + + glDisable(GL_BLEND); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +void TerrainEditor::addUndo(Vector & list, Selection * sel) +{ + AssertFatal(sel, "TerrainEditor::addUndo - invalid selection"); + list.push_front(sel); + if(list.size() == mUndoLimit) + { + Selection * undo = list[list.size()-1]; + delete undo; + list.pop_back(); + } +} + +void TerrainEditor::clearUndo(Vector & list) +{ + for(U32 i = 0; i < list.size(); i++) + delete list[i]; + list.clear(); +} + +bool TerrainEditor::processUndo(Vector & src, Vector & dest) +{ + if(!src.size()) + return(false); + + Selection * task = src.front(); + src.pop_front(); + + Selection * save = new Selection; + for(U32 i = 0; i < task->size(); i++) + { + GridInfo info; + getGridInfo((*task)[i].mGridPos, info); + save->add(info); + setGridInfo((*task)[i]); + } + + delete task; + addUndo(dest, save); + + rebuild(); + + return(true); +} + +//------------------------------------------------------------------------------ +// +void TerrainEditor::rebuild() +{ + // empty + if(mRebuildEmpty) + { + mTerrainBlock->rebuildEmptyFlags(); + mTerrainBlock->packEmptySquares(); + mRebuildEmpty = false; + } + + // base texture gruop + if(mRebuildTextures) + { + mRebuildTextures = false; + + TerrainBlock * clientTerrain = getClientTerrain(); + if(clientTerrain) + clientTerrain->buildMipMap(); + } +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::on3DMouseUp(const Gui3DMouseEvent & event) +{ + if(!mTerrainBlock) + return; + + if(mInAction) + { + mouseUnlock(); + mCurrentAction->process(mMouseBrush, event, false, TerrainAction::End); + setCursor(0); + + if(mUndoSel->size()) + { + addUndo(mUndoList, mUndoSel); + clearUndo(mRedoList); + } + else + delete mUndoSel; + + mUndoSel = 0; + mInAction = false; + + rebuild(); + } +} + +void TerrainEditor::on3DMouseDown(const Gui3DMouseEvent & event) +{ + if(!mTerrainBlock) + return; + + if(mInAction || mUndoSel) + return; + + mSelectionLocked = false; + + mouseLock(); + mInAction = true; + mUndoSel = new Selection; + mCurrentAction->process(mMouseBrush, event, true, TerrainAction::Begin); +} + +void TerrainEditor::on3DMouseMove(const Gui3DMouseEvent & event) +{ + if(!mTerrainBlock) + return; + + Point3F pos; + if(!collide(event, pos)) + { + mInAction = false; + mMouseBrush->reset(); + Canvas->showCursor(true); + } + else + { + // + if(mRenderBrush) + Canvas->showCursor(false); + mMousePos = pos; + + mMouseBrush->setPosition(mMousePos); + } +} + +void TerrainEditor::on3DMouseDragged(const Gui3DMouseEvent & event) +{ + if(!mTerrainBlock) + return; + + if(!mInAction) + return; + + Point3F pos; + if(!mSelectionLocked) + { + if(!collide(event, pos)) + { + mMouseBrush->reset(); + Canvas->showCursor(true); + return; + } + } + + if(mRenderBrush) + Canvas->showCursor(false); + + // 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; + } + + mCurrentAction->process(mMouseBrush, event, selChanged, TerrainAction::Update); +} + +void TerrainEditor::on3DMouseEnter(const Gui3DMouseEvent &) +{ + if(!mTerrainBlock) + return; + + if(mRenderBrush) + Canvas->showCursor(false); +} + +void TerrainEditor::on3DMouseLeave(const Gui3DMouseEvent &) +{ + if(!mTerrainBlock) + return; + + mInAction = false; + Canvas->showCursor(true); +} + +//------------------------------------------------------------------------------ +// any console function which depends on a terrainBlock attached to the editor +// should call this +bool checkTerrainBlock(TerrainEditor * terrainEditor, const char * funcName) +{ + if(!terrainEditor->terrainBlockValid()) + { + Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::%s: not attached to a terrain block!", funcName); + return(false); + } + return(true); +} + +//------------------------------------------------------------------------------ + +static void cAttachTerrain(SimObject * obj, S32 argc, const char ** argv) +{ + TerrainEditor * terrainEditor = static_cast(obj); + + TerrainBlock * terrBlock = 0; + + SimSet * missionGroup = dynamic_cast(Sim::findObject("MissionGroup")); + if(!missionGroup) + { + Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no mission group found"); + return; + } + + // attach to first found terrainBlock + if(argc == 2) + { + for(SimSetIterator itr(missionGroup); *itr; ++itr) + { + terrBlock = dynamic_cast(*itr); + if(terrBlock) + break; + } + + if(!terrBlock) + Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no TerrainBlock objects found!"); + } + else // attach to named object + { + terrBlock = dynamic_cast(Sim::findObject(argv[2])); + + if(!terrBlock) + Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: failed to attach to object '%s'", argv[2]); + } + + if(terrBlock && !terrBlock->isServerObject()) + { + Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: cannot attach to client TerrainBlock"); + terrBlock = 0; + } + + // + terrainEditor->mTerrainBlock = terrBlock; +} + +//------------------------------------------------------------------------------ + +static void cSetBrushType(SimObject * obj, S32, const char **argv) +{ + TerrainEditor * terrainEditor = static_cast(obj); + + if(!dStricmp(argv[2], "box")) + { + delete terrainEditor->mMouseBrush; + terrainEditor->mMouseBrush = new BoxBrush(terrainEditor); + } + else if(!dStricmp(argv[2], "ellipse")) + { + delete terrainEditor->mMouseBrush; + terrainEditor->mMouseBrush = new EllipseBrush(terrainEditor); + } + else if(!dStricmp(argv[2], "selection")) + { + delete terrainEditor->mMouseBrush; + terrainEditor->mMouseBrush = new SelectionBrush(terrainEditor); + } + else {} +} + +//------------------------------------------------------------------------------ + +static void cSetBrushSize(SimObject * obj, S32, const char **argv) +{ + TerrainEditor * terrainEditor = static_cast(obj); + + S32 w = dAtoi(argv[2]); + S32 h = dAtoi(argv[3]); + + // + if(w < 1 || w > Brush::MaxBrushDim || h < 1 || h > Brush::MaxBrushDim) + { + Con::errorf(ConsoleLogEntry::General, "TerrainEditor::cSetBrushSize: invalid brush dimension. [1-%d].", Brush::MaxBrushDim); + return; + } + + terrainEditor->mBrushSize.set(w, h); + terrainEditor->mMouseBrush->setSize(terrainEditor->mBrushSize); +} + +static const char * cGetBrushPos(SimObject * obj, S32, const char **) +{ + TerrainEditor * tEditor = static_cast(obj); + AssertFatal(tEditor->mMouseBrush, "TerrainEditor::cGetBrushPos: no mouse brush!"); + + Point2I pos = tEditor->mMouseBrush->getPosition(); + char * ret = Con::getReturnBuffer(32); + dSprintf(ret, sizeof(ret), "%d %d", pos.x, pos.y); + return(ret); +} + +static void cSetBrushPos(SimObject * obj, S32 argc, const char ** argv) +{ + TerrainEditor * tEditor = static_cast(obj); + + // + 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]); + } + + AssertFatal(tEditor->mMouseBrush, "TerrainEditor::cSetBrushPos: no mouse brush!"); + tEditor->mMouseBrush->setPosition(pos); +} + +//------------------------------------------------------------------------------ + +static void cSetAction(SimObject * obj, S32, const char **argv) +{ + TerrainEditor * terrainEditor = static_cast(obj); + + for(U32 i = 0; i < terrainEditor->mActions.size(); i++) + { + if(!dStricmp(terrainEditor->mActions[i]->getName(), argv[2])) + { + terrainEditor->mCurrentAction = terrainEditor->mActions[i]; + + // + terrainEditor->mRenderBrush = terrainEditor->mCurrentAction->useMouseBrush(); + return; + } + } +} + +//------------------------------------------------------------------------------ + +static const char * cGetActionName(SimObject * obj, S32, const char ** argv) +{ + TerrainEditor * terrainEditor = static_cast(obj); + U32 index = dAtoi(argv[2]); + if(index >= terrainEditor->mActions.size()) + return(""); + return(terrainEditor->mActions[index]->getName()); +} + +//------------------------------------------------------------------------------ + +static S32 cGetNumActions(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + return terrainEditor->mActions.size(); +} + +//------------------------------------------------------------------------------ + +static const char * cGetCurrentAction(SimObject * obj, S32, const char **) +{ + TerrainEditor *terrainEditor = static_cast(obj); + return(terrainEditor->mCurrentAction->getName()); +} + +static void cResetSelWeights(SimObject * obj, S32, const char ** argv) +{ + TerrainEditor * tEditor = static_cast(obj); + + bool clear = dAtob(argv[2]); + + // + if(!clear) + { + for(U32 i = 0; i < tEditor->mDefaultSel.size(); i++) + { + tEditor->mDefaultSel[i].mPrimarySelect = false; + tEditor->mDefaultSel[i].mWeight = 1.f; + } + return; + } + + Selection sel; + + U32 i; + for(i = 0; i < tEditor->mDefaultSel.size(); i++) + { + if(tEditor->mDefaultSel[i].mPrimarySelect) + { + tEditor->mDefaultSel[i].mWeight = 1.f; + sel.add(tEditor->mDefaultSel[i]); + } + } + + tEditor->mDefaultSel.reset(); + + for(i = 0; i < sel.size(); i++) + tEditor->mDefaultSel.add(sel[i]); +} + +//------------------------------------------------------------------------------ + +static void cUndoAction(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "undoAction")) + return; + + terrainEditor->processUndo(terrainEditor->mUndoList, terrainEditor->mRedoList); +} + +//------------------------------------------------------------------------------ + +static void cRedoAction(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "redoAction")) + return; + + terrainEditor->processUndo(terrainEditor->mRedoList, terrainEditor->mUndoList); +} + +//------------------------------------------------------------------------------ + +static void cClearSelection(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + terrainEditor->mDefaultSel.reset(); +} + +//------------------------------------------------------------------------------ + +static void cProcessAction(SimObject * obj, S32 argc, const char ** argv) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "processAction")) + return; + + TerrainAction * action = terrainEditor->mCurrentAction; + if(argc == 3) + { + action = terrainEditor->lookupAction(argv[2]); + + if(!action) + { + Con::errorf(ConsoleLogEntry::General, "TerrainEditor::cProcessAction: invalid action name '%s'.", argv[2]); + return; + } + } + + if(!terrainEditor->getCurrentSel()->size() && !terrainEditor->mProcessUsesBrush) + return; + + terrainEditor->mUndoSel = new Selection; + + Gui3DMouseEvent event; + if(terrainEditor->mProcessUsesBrush) + action->process(terrainEditor->mMouseBrush, event, true, TerrainAction::Process); + else + action->process(terrainEditor->getCurrentSel(), event, true, TerrainAction::Process); + + terrainEditor->rebuild(); + + // check if should delete the undo + if(terrainEditor->mUndoSel->size()) + { + terrainEditor->addUndo(terrainEditor->mUndoList, terrainEditor->mUndoSel); + terrainEditor->clearUndo(terrainEditor->mRedoList); + } + else + delete terrainEditor->mUndoSel; + + terrainEditor->mUndoSel = 0; +} + +static void cBuildMaterialMap(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "buildMaterialMap")) + return; + terrainEditor->mTerrainBlock->buildMaterialMap(); +} + +static S32 cGetNumTextures(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "getNumTextures")) + return(0); + + // walk all the possible material lists and count them.. + U32 count = 0; + for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++) + if(terrainEditor->mTerrainBlock->mMaterialFileName[i] && + *terrainEditor->mTerrainBlock->mMaterialFileName[i]) + count++; + return count; +} + +static const char * cGetTextureName(SimObject * obj, S32, const char ** argv) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "getTextureName")) + return(""); + + // textures only exist on the client.. + NetConnection * toServer = NetConnection::getServerConnection(); + NetConnection * toClient = NetConnection::getLocalClientConnection(); + + S32 index = toClient->getGhostIndex(terrainEditor->mTerrainBlock); + + TerrainBlock * terrBlock = dynamic_cast(toServer->resolveGhost(index)); + if(!terrBlock) + return(""); + + // possibly in range? + S32 group = dAtoi(argv[2]); + if(group < 0 || group >= TerrainBlock::MaterialGroups) + return(""); + + // now find the i-th group + U32 count = 0; + bool found = false; + for(U32 i = 0; !found && (i < TerrainBlock::MaterialGroups); i++) + { + // count it + if(terrBlock->mMaterialFileName[i] && + *terrBlock->mMaterialFileName[i]) + count++; + + if((group + 1) == count) + { + group = i; + found = true; + } + } + + if(!found) + return(""); + + char *retBuffer = Con::getReturnBuffer(256); + dSprintf(retBuffer, 256, "terrain/%s", terrBlock->mMaterialFileName[group]); + return retBuffer; +} + +static void findObjectsCallback(SceneObject* obj, S32 val) +{ + Vector * list = (Vector*)val; + list->push_back(obj); +} + +static void cMarkEmptySquares(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "markEmptySquares")) + return; + + // build a list of all the marked interiors + Vector interiors; + U32 mask = InteriorObjectType; + gServerContainer.findObjects(mask, findObjectsCallback, (S32)&interiors); + + // walk the terrain and empty any grid which clips to an interior + for(U32 x = 0; x < TerrainBlock::BlockSize; x++) + for(U32 y = 0; y < TerrainBlock::BlockSize; y++) + { + TerrainBlock::Material * material = terrainEditor->mTerrainBlock->getMaterial(x,y); + material->flags |= ~(TerrainBlock::Material::Empty); + + Point3F a, b; + terrainEditor->gridToWorld(Point2I(x,y), a); + terrainEditor->gridToWorld(Point2I(x+1,y+1), b); + + Box3F box; + box.min = a; + box.max = b; + + box.min.setMin(b); + box.max.setMax(a); + + const MatrixF & terrOMat = terrainEditor->mTerrainBlock->getTransform(); + const MatrixF & terrWMat = terrainEditor->mTerrainBlock->getWorldTransform(); + + terrWMat.mulP(box.min); + terrWMat.mulP(box.max); + + 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.. + terrainEditor->mTerrainBlock->buildGridMap(); + terrainEditor->mTerrainBlock->rebuildEmptyFlags(); + terrainEditor->mTerrainBlock->packEmptySquares(); +} + +static void cClearModifiedFlags(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "clearModifiedFlags")) + return; + + // + for(U32 i = 0; i < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); i++) + terrainEditor->mTerrainBlock->materialMap[i].flags &= ~TerrainBlock::Material::Modified; +} + +static void cMirrorTerrain(SimObject * obj, S32, const char ** argv) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "mirrorTerrain")) + return; + + TerrainBlock * terrain = terrainEditor->mTerrainBlock; + S32 mirrorIndex = dAtoi(argv[2]); + + // + 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; + terrainEditor->getGridInfo(dest, info); + 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 to undo list and clear redo + terrainEditor->addUndo(terrainEditor->mUndoList, undo); + terrainEditor->clearUndo(terrainEditor->mRedoList); +} + +static void cPushBaseMaterialInfo(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "pushMaterialInfo")) + return; + + TerrainBlock * terrain = terrainEditor->mTerrainBlock; + + BaseMaterialInfo * info = new BaseMaterialInfo; + + // copy the material list names + for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++) + info->mMaterialNames[i] = terrain->mMaterialFileName[i]; + + // copy the base materials + dMemcpy(info->mBaseMaterials, terrain->mBaseMaterialMap, + TerrainBlock::BlockSize * TerrainBlock::BlockSize); + + terrainEditor->mBaseMaterialInfos.push_front(info); +} + +static void cPopBaseMaterialInfo(SimObject * obj, S32, const char **) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "popMaterialInfo")) + return; + + if(!terrainEditor->mBaseMaterialInfos.size()) + return; + + TerrainBlock * terrain = terrainEditor->mTerrainBlock; + + BaseMaterialInfo * info = terrainEditor->mBaseMaterialInfos.front(); + + // names + for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++) + terrain->mMaterialFileName[i] = info->mMaterialNames[i]; + + // base materials + dMemcpy(terrain->mBaseMaterialMap, info->mBaseMaterials, + TerrainBlock::BlockSize * TerrainBlock::BlockSize); + + // kill it.. + delete info; + terrainEditor->mBaseMaterialInfos.pop_front(); + + // rebuild + terrain->refreshMaterialLists(); + terrain->buildGridMap(); +} + +static void cSetLoneBaseMaterial(SimObject * obj, S32, const char ** argv) +{ + TerrainEditor * terrainEditor = static_cast(obj); + if(!checkTerrainBlock(terrainEditor, "setLoneBaseMaterial")) + return; + + TerrainBlock * terrain = terrainEditor->mTerrainBlock; + + // force the material group + terrain->mMaterialFileName[0] = StringTable->insert(argv[2]); + dMemset(terrain->getBaseMaterialAddress(0,0), + TerrainBlock::BlockSize * TerrainBlock::BlockSize, 0); + + terrain->refreshMaterialLists(); + terrain->buildGridMap(); +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::consoleInit() +{ + Con::addCommand("TerrainEditor", "attachTerrain", cAttachTerrain, "terrainEditor.attachTerrain();", 2, 3); + Con::addCommand("TerrainEditor", "setBrushType", cSetBrushType, "terrainEditor.setBrushType(box | ellipse | ...);", 3, 3); + Con::addCommand("TerrainEditor", "setBrushSize", cSetBrushSize, "terrainEditor.setBrushSize(x, y);", 4, 4); + Con::addCommand("TerrainEditor", "getBrushPos", cGetBrushPos, "terrainEditor.getBrushPos();", 2, 2); + Con::addCommand("TerrainEditor", "setBrushPos", cSetBrushPos, "terrainEditor.setBrushPos(x, y);", 3, 4); + Con::addCommand("TerrainEditor", "setAction", cSetAction, "terrainEditor.setAction(action_name);", 3, 3); + Con::addCommand("TerrainEditor", "getNumActions", cGetNumActions, "terrainEditor.getNumActions();", 2, 2); + Con::addCommand("TerrainEditor", "getActionName", cGetActionName, "terrainEditor.getActionName(num);", 3, 3); + Con::addCommand("TerrainEditor", "getCurrentAction", cGetCurrentAction, "terrainEditor.getCurrentAction();", 2, 2); + Con::addCommand("TerrainEditor", "resetSelWeights", cResetSelWeights, "terrainEditor.resetSelWeights(clear);", 3, 3); + Con::addCommand("TerrainEditor", "undo", cUndoAction, "terrainEditor.undo();", 2, 2); + Con::addCommand("TerrainEditor", "redo", cRedoAction, "terrainEditor.redo();", 2, 2); + Con::addCommand("TerrainEditor", "clearSelection", cClearSelection, "terrainEditor.clearSelection();", 2, 2); + Con::addCommand("TerrainEditor", "processAction", cProcessAction, "terrainEditor.processAction();", 2, 3); + Con::addCommand("TerrainEditor", "buildMaterialMap", cBuildMaterialMap, "terrainEditor.buildMaterialMap();", 2, 2); + Con::addCommand("TerrainEditor", "getNumTextures", cGetNumTextures, "terrainEditor.getNumTextures();", 2, 2); + Con::addCommand("TerrainEditor", "getTextureName", cGetTextureName, "terrainEditor.getTextureName(index);", 3, 3); + Con::addCommand("TerrainEditor", "markEmptySquares", cMarkEmptySquares, "terrainEditor.markEmptySquares();", 2, 2); + Con::addCommand("TerrainEditor", "clearModifiedFlags", cClearModifiedFlags, "terrainEditor.clearModifiedFlags();", 2, 2); + Con::addCommand("TerrainEditor", "mirrorTerrain", cMirrorTerrain, "terrainEditor.mirrorTerrain(dest octant index);", 3, 3); + Con::addCommand("TerrainEditor", "pushBaseMaterialInfo", cPushBaseMaterialInfo, "terrainEditor.pushBaseMaterialInfo();", 2, 2); + Con::addCommand("TerrainEditor", "popBaseMaterialInfo", cPopBaseMaterialInfo, "terrainEditor.popBaseMaterialInfo();", 2, 2); + Con::addCommand("TerrainEditor", "setLoneBaseMaterial", cSetLoneBaseMaterial, "terrainEditor.setLoneBaseMaterial(material list base name);", 3, 3); +} + +void TerrainEditor::initPersistFields() +{ + Parent::initPersistFields(); + addField("renderBorder", TypeBool, Offset(mRenderBorder, TerrainEditor)); + addField("borderHeight", TypeF32, Offset(mBorderHeight, TerrainEditor)); + addField("borderFillColor", TypeColorI, Offset(mBorderFillColor, TerrainEditor)); + addField("borderFrameColor", TypeColorI, Offset(mBorderFrameColor, TerrainEditor)); + addField("borderLineMode", TypeBool, Offset(mBorderLineMode, TerrainEditor)); + addField("selectionHidden", TypeBool, Offset(mSelectionHidden, TerrainEditor)); + addField("enableSoftBrushes", TypeBool, Offset(mEnableSoftBrushes, TerrainEditor)); + addField("renderVertexSelection", TypeBool, Offset(mRenderVertexSelection, TerrainEditor)); + addField("processUsesBrush", TypeBool, Offset(mProcessUsesBrush, TerrainEditor)); + + // action values... + addField("adjustHeightVal", TypeF32, Offset(mAdjustHeightVal, TerrainEditor)); + addField("setHeightVal", TypeF32, Offset(mSetHeightVal, TerrainEditor)); + addField("scaleVal", TypeF32, Offset(mScaleVal, TerrainEditor)); + addField("smoothFactor", TypeF32, Offset(mSmoothFactor, TerrainEditor)); + addField("materialGroup", TypeS32, Offset(mMaterialGroup, TerrainEditor)); + addField("softSelectRadius", TypeF32, Offset(mSoftSelectRadius, TerrainEditor)); + addField("softSelectFilter", TypeString, Offset(mSoftSelectFilter, TerrainEditor)); + addField("softSelectDefaultFilter", TypeString, Offset(mSoftSelectDefaultFilter, TerrainEditor)); + addField("adjustHeightMouseScale", TypeF32, Offset(mAdjustHeightMouseScale, TerrainEditor)); +} diff --git a/editor/terrainEditor.h b/editor/terrainEditor.h new file mode 100644 index 0000000..8e8512c --- /dev/null +++ b/editor/terrainEditor.h @@ -0,0 +1,313 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRAINEDITOR_H_ +#define _TERRAINEDITOR_H_ + +#ifndef _EDITTSCTRL_H_ +#include "Editor/editTSCtrl.h" +#endif +#ifndef _TERRDATA_H_ +#include "terrain/terrData.h" +#endif + +//------------------------------------------------------------------------------ + +class GridInfo +{ + public: + Point2I mGridPos; + TerrainBlock::Material mMaterial; + F32 mHeight; + U8 mMaterialGroup; + F32 mWeight; + + bool mPrimarySelect; + + // 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); + + Vector mHashLists; + U32 mHashListSize; + + public: + + Selection(); + + void reset(); + bool add(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(); +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class TerrainEditor; +class Brush : public Selection +{ + protected: + TerrainEditor * mTerrainEditor; + Point2I mSize; + Point2I mGridPos; + + public: + + enum { + MaxBrushDim = 40 + }; + + Brush(TerrainEditor * editor); + virtual ~Brush(){}; + + // + void setPosition(const Point3F & pos); + void setPosition(const Point2I & pos); + const Point2I & getPosition(); + + void update(); + virtual void rebuild() = 0; + + Point2I getSize(){return(mSize);} + virtual void setSize(const Point2I & size){mSize = size;} +}; + +class BoxBrush : public Brush +{ + public: + BoxBrush(TerrainEditor * editor) : Brush(editor){} + void rebuild(); +}; + +class EllipseBrush : public Brush +{ + public: + EllipseBrush(TerrainEditor * editor) : Brush(editor){} + void rebuild(); +}; + +class SelectionBrush : public Brush +{ + public: + SelectionBrush(TerrainEditor * editor); + void rebuild(); + void setSize(const Point2I &){} +}; +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +// must declare the console functions before making them a friend of TerrainEditor +static void cAttachTerrain(SimObject *, S32, const char **); +static void cSetBrushType(SimObject *, S32, const char **); +static void cSetBrushSize(SimObject *, S32, const char **); +static const char * cGetBrushPos(SimObject *, S32, const char **); +static void cSetBrushPos(SimObject *, S32, const char **); +static void cSetAction(SimObject *, S32, const char **); +static S32 cGetNumActions(SimObject *, S32, const char **); +static const char * cGetActionName(SimObject *, S32, const char **); +static const char * cGetCurrentAction(SimObject *, S32, const char **); +static void cResetSelWeights(SimObject *, S32, const char **); +static void cUndoAction(SimObject *, S32, const char **); +static void cRedoAction(SimObject *, S32, const char **); +static void cClearSelection(SimObject *, S32, const char **); +static void cProcessAction(SimObject *, S32, const char **); +static void cBuildMaterialMap(SimObject * obj, S32, const char **); +static S32 cGetNumTextures(SimObject * obj, S32, const char **); +static const char * cGetTextureName(SimObject * obj, S32, const char ** argv); +static void cMarkEmptySquares(SimObject * obj, S32, const char **); +static void cClearModifiedFlags(SimObject * obj, S32, const char **); +static void cMirrorTerrain(SimObject * obj, S32, const char **); +static void cPushBaseMaterialInfo(SimObject * obj, S32, const char **); +static void cPopBaseMaterialInfo(SimObject * obj, S32, const char **); +static void cSetLoneBaseMaterial(SimObject * obj, S32, const char ** argv); + +struct BaseMaterialInfo { + StringTableEntry mMaterialNames[TerrainBlock::MaterialGroups]; + U8 mBaseMaterials[TerrainBlock::BlockSize * TerrainBlock::BlockSize]; +}; + +class TerrainAction; +class TerrainEditor : public EditTSCtrl +{ + private: + + // give console functions full access + friend void cAttachTerrain(SimObject *, S32, const char **); + friend void cSetBrushType(SimObject *, S32, const char **); + friend void cSetBrushSize(SimObject *, S32, const char **); + friend const char * cGetBrushPos(SimObject *, S32, const char **); + friend void cSetBrushPos(SimObject *, S32, const char **); + friend void cSetAction(SimObject *, S32, const char **); + friend S32 cGetNumActions(SimObject *, S32, const char **); + friend const char * cGetActionName(SimObject *, S32, const char **); + friend const char * cGetCurrentAction(SimObject *, S32, const char **); + friend void cResetSelWeights(SimObject *, S32, const char **); + friend void cUndoAction(SimObject *, S32, const char **); + friend void cRedoAction(SimObject *, S32, const char **); + friend void cClearSelection(SimObject *, S32, const char**); + friend void cProcessAction(SimObject *, S32, const char **); + friend void cBuildMaterialMap(SimObject * obj, S32, const char **); + friend S32 cGetNumTextures(SimObject * obj, S32, const char **); + friend const char * cGetTextureName(SimObject * obj, S32, const char ** argv); + friend void cMarkEmptySquares(SimObject * obj, S32, const char **); + friend void cClearModifiedFlags(SimObject * obj, S32, const char **); + friend void cMirrorTerrain(SimObject * obj, S32, const char **); + friend void cPushBaseMaterialInfo(SimObject * obj, S32, const char **); + friend void cPopBaseMaterialInfo(SimObject * obj, S32, const char **); + friend void cSetLoneBaseMaterial(SimObject * obj, S32, const char ** argv); + + typedef EditTSCtrl Parent; + TerrainBlock * mTerrainBlock; + + Point3F mMousePos; + Brush * mMouseBrush; + bool mRenderBrush; + Point2I mBrushSize; + Vector mActions; + TerrainAction * mCurrentAction; + bool mInAction; + Selection mDefaultSel; + bool mSelectionLocked; + GuiCursor * mDefaultCursor; + Selection * mCurrentSel; + + // + bool mRebuildEmpty; + bool mRebuildTextures; + void rebuild(); + + void addUndo(Vector & list, Selection * sel); + bool processUndo(Vector & src, Vector & dest); + void clearUndo(Vector & list); + + U32 mUndoLimit; + Selection * mUndoSel; + + Vector mUndoList; + Vector mRedoList; + + Vector mBaseMaterialInfos; + public: + + TerrainEditor(); + ~TerrainEditor(); + + // conversion functions + bool gridToWorld(const Point2I & gPos, Point3F & wPos); + bool worldToGrid(const Point3F & wPos, Point2I & gPos); + bool gridToCenter(const Point2I & gPos, Point2I & cPos); + + bool getGridInfo(const Point3F & wPos, GridInfo & info); + bool getGridInfo(const Point2I & gPos, GridInfo & info); + void setGridInfo(const GridInfo & info); + + bool 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; } + + Point2I getBrushSize() { return(mBrushSize); } + TerrainBlock * getTerrainBlock() { AssertFatal(mTerrainBlock, "No terrain block"); return(mTerrainBlock); } + bool terrainBlockValid() { return(mTerrainBlock ? true : false); } + void setCursor(GuiCursor * cursor); + + TerrainAction * lookupAction(const char * name); + + private: + + TerrainBlock * getClientTerrain(); + + // terrain interface functions + F32 getGridHeight(const Point2I & gPos); + void setGridHeight(const Point2I & gPos, const F32 height); + + TerrainBlock::Material getGridMaterial(const Point2I & gPos); + void setGridMaterial(const Point2I & gPos, const TerrainBlock::Material & material); + + U8 getGridMaterialGroup(const Point2I & gPos); + void setGridMaterialGroup(const Point2I & gPos, 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 renderBorder(); + + public: + + // persist field data - these are dynamic + bool mRenderBorder; + F32 mBorderHeight; + ColorI mBorderFillColor; + ColorI mBorderFrameColor; + bool mBorderLineMode; + bool mSelectionHidden; + bool mEnableSoftBrushes; + bool mRenderVertexSelection; + bool mProcessUsesBrush; + + // + F32 mAdjustHeightVal; + F32 mSetHeightVal; + F32 mScaleVal; + F32 mSmoothFactor; + S32 mMaterialGroup; + F32 mSoftSelectRadius; + StringTableEntry mSoftSelectFilter; + StringTableEntry mSoftSelectDefaultFilter; + F32 mAdjustHeightMouseScale; + + public: + + // SimObject + bool onAdd(); + void onDeleteNotify(SimObject * object); + + static void consoleInit(); + static void initPersistFields(); + + // EditTSCtrl + void on3DMouseUp(const Gui3DMouseEvent & event); + void on3DMouseDown(const Gui3DMouseEvent & event); + void on3DMouseMove(const Gui3DMouseEvent & event); + void on3DMouseDragged(const Gui3DMouseEvent & event); + void on3DMouseEnter(const Gui3DMouseEvent & event); + void on3DMouseLeave(const Gui3DMouseEvent & event); + void updateGuiInfo(); + void renderScene(const RectI & updateRect); + + DECLARE_CONOBJECT(TerrainEditor); +}; + +#endif diff --git a/editor/worldEditor.cc b/editor/worldEditor.cc new file mode 100644 index 0000000..b0b1e49 --- /dev/null +++ b/editor/worldEditor.cc @@ -0,0 +1,2479 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "editor/worldEditor.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "sim/sceneObject.h" +#include "platform/event.h" +#include "gui/guiCanvas.h" +#include "game/gameConnection.h" +#include "core/memstream.h" +#include "collision/clippedPolyList.h" +#include "game/shapeBase.h" +#include "console/consoleInternal.h" +#include "game/sphere.h" +#include "ai/graph.h" + +IMPLEMENT_CONOBJECT(WorldEditor); + +// unnamed namespace for static data +namespace { + + 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 + }; + + 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) + }; + + // + 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.max.x : box.min.x, + BoxPnts[i].y ? box.max.y : box.min.y, + BoxPnts[i].z ? box.max.z : box.min.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")) + 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 = mAtan(-mat[MatrixF::idx(2,0)], mat[MatrixF::idx(2,2)]); + r.z = mAtan(-mat[MatrixF::idx(0,1)], mat[MatrixF::idx(1,1)]); + } + else + { + r.y = 0.f; + r.z = mAtan(mat[MatrixF::idx(1,0)], mat[MatrixF::idx(0,0)]); + } + + return(r); + } +} + +//------------------------------------------------------------------------------ +// Class WorldEditor::Selection +//------------------------------------------------------------------------------ + +WorldEditor::Selection::Selection() : + mCentroidValid(false), + mAutoSelect(false) +{ + registerObject(); +} + +WorldEditor::Selection::~Selection() +{ + unregisterObject(); +} + +bool WorldEditor::Selection::objInSet(SceneObject * obj) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + if(mObjectList[i] == (SimObject*)obj) + return(true); + return(false); +} + +bool WorldEditor::Selection::addObject(SceneObject * obj) +{ + if(objInSet(obj)) + return(false); + + mCentroidValid = false; + + mObjectList.pushBack(obj); + deleteNotify(obj); + + if(mAutoSelect) + { + obj->setSelected(true); + SceneObject * clientObj = WorldEditor::getClientObj(obj); + if(clientObj) + clientObj->setSelected(true); + } + + return(true); +} + +bool WorldEditor::Selection::removeObject(SceneObject * obj) +{ + if(!objInSet(obj)) + return(false); + + mCentroidValid = false; + + mObjectList.remove(obj); + clearNotify(obj); + + if(mAutoSelect) + { + obj->setSelected(false); + + SceneObject * clientObj = WorldEditor::getClientObj(obj); + if(clientObj) + clientObj->setSelected(false); + } + + return(true); +} + +void WorldEditor::Selection::clear() +{ + while(mObjectList.size()) + removeObject((SceneObject*)mObjectList[0]); +} + +void WorldEditor::Selection::onDeleteNotify(SimObject * obj) +{ + removeObject((SceneObject*)obj); +} + +void WorldEditor::Selection::updateCentroid() +{ + if(mCentroidValid) + return; + + mCentroidValid = true; + + // + mCentroid.set(0,0,0); + mBoxCentroid = mCentroid; + + if(!mObjectList.size()) + return; + + // + for(U32 i = 0; i < mObjectList.size(); i++) + { + const MatrixF & mat = ((SceneObject*)mObjectList[i])->getTransform(); + Point3F wPos; + mat.getColumn(3, &wPos); + + // + mBoxCentroid += getBoundingBoxCenter((SceneObject*)mObjectList[i]); + mCentroid += wPos; + } + + mCentroid /= mObjectList.size(); + mBoxCentroid /= mObjectList.size(); +} + +const Point3F & WorldEditor::Selection::getCentroid() +{ + updateCentroid(); + return(mCentroid); +} + +const Point3F & WorldEditor::Selection::getBoxCentroid() +{ + updateCentroid(); + return(mBoxCentroid); +} + +void WorldEditor::Selection::enableCollision() +{ + for(U32 i = 0; i < mObjectList.size(); i++) + ((SceneObject*)mObjectList[i])->enableCollision(); +} + +void WorldEditor::Selection::disableCollision() +{ + for(U32 i = 0; i < mObjectList.size(); i++) + ((SceneObject*)mObjectList[i])->disableCollision(); +} + +//------------------------------------------------------------------------------ + +void WorldEditor::Selection::offset(const Point3F & offset) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + MatrixF mat = ((SceneObject*)mObjectList[i])->getTransform(); + Point3F wPos; + mat.getColumn(3, &wPos); + + // adjust + wPos += offset; + mat.setColumn(3, wPos); + ((SceneObject*)mObjectList[i])->setTransform(mat); + } + + mCentroidValid = false; +} + +void WorldEditor::Selection::rotate(const EulerF & rot, const Point3F & center) +{ + // single selections will rotate around own axis, multiple about world + if(mObjectList.size() == 1) + { + MatrixF mat = ((SceneObject*)mObjectList[0])->getTransform(); + + Point3F pos; + mat.getColumn(3, &pos); + + // get offset in obj space + Point3F offset = pos - center; + MatrixF wMat = ((SceneObject*)mObjectList[0])->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); + + ((SceneObject*)mObjectList[0])->setTransform(mat); + } + else + { + for(U32 i = 0; i < size(); i++) + { + MatrixF mat = ((SceneObject*)mObjectList[i])->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 = ((SceneObject*)mObjectList[i])->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); + + ((SceneObject*)mObjectList[i])->setTransform(mat); + } + } + + mCentroidValid = false; +} + +void WorldEditor::Selection::scale(const VectorF & scale) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + VectorF current = ((SceneObject*)mObjectList[i])->getScale(); + current.convolve(scale); + ((SceneObject*)mObjectList[i])->setScale(current); + } + + mCentroidValid = false; +} + +//------------------------------------------------------------------------------ + +SceneObject * WorldEditor::getClientObj(SceneObject * obj) +{ + AssertFatal(obj->isServerObject(), "WorldEditor::getClientObj: not a server object!"); + + NetConnection * toServer = NetConnection::getServerConnection(); + NetConnection * toClient = NetConnection::getLocalClientConnection(); + + 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 * clientObj = getClientObj(sel[i]); + if(!clientObj) + continue; + + // + clientObj->setTransform(sel[i]->getTransform()); + clientObj->setScale(sel[i]->getScale()); + } +} + +//------------------------------------------------------------------------------ +// simple undo mechanism: bascially maintains stacks of state information +// from the selections (transform info only) +WorldEditor::SelectionState * WorldEditor::createUndo(Selection & sel) +{ + SelectionState * sState = new SelectionState; + for(U32 i = 0; i < sel.size(); i++) + { + SelectionState::Entry entry; + + entry.mMatrix = sel[i]->getTransform(); + entry.mScale = sel[i]->getScale(); + entry.mObjId = sel[i]->getId(); + sState->mEntries.push_back(entry); + } + return(sState); +} + +void WorldEditor::addUndo(Vector & list, SelectionState * sel) +{ + AssertFatal(sel, "WorldEditor::addUndo - invalid selection"); + list.push_front(sel); + if(list.size() == mUndoLimit) + { + SelectionState * ss = list[list.size()-1]; + delete ss; + list.pop_back(); + } +} + +bool WorldEditor::processUndo(Vector & src, Vector & dest) +{ + if(!src.size()) + return(false); + + SelectionState * task = src.front(); + src.pop_front(); + + for(U32 i = 0; i < task->mEntries.size(); i++) + { + SceneObject * obj = static_cast(Sim::findObject(task->mEntries[i].mObjId)); + if(obj) + { + setClientObjInfo(obj, task->mEntries[i].mMatrix, task->mEntries[i].mScale); + obj->setTransform(task->mEntries[i].mMatrix); + obj->setScale(task->mEntries[i].mScale); + } + } + + addUndo(dest, task); + mSelected.invalidateCentroid(); + + return(true); +} + +void WorldEditor::clearUndo(Vector & list) +{ + for(U32 i = 0; i < list.size(); i++) + delete list[i]; + list.clear(); +} + +//------------------------------------------------------------------------------ +// edit stuff +bool WorldEditor::deleteSelection(Selection & sel) +{ + while(sel.size()) + sel[0]->deleteObject(); + return(true); +} + +bool WorldEditor::copySelection(Selection & sel) +{ + mStreamBufs.clear(); + + for(U32 i = 0; i < sel.size(); i++) + { + // um.. can we say lame? + mStreamBufs.increment(); + MemStream stream(2048, mStreamBufs.last(), false, true); + stream.write(7, (void*)"return "); + sel[i]->write(stream, 0); + + if(stream.getStatus() != Stream::Ok) + { + mStreamBufs.clear(); + return(false); + } + } + return(true); +} + +bool WorldEditor::pasteSelection() +{ + if(!mSelectionLocked) + mSelected.clear(); + for(U32 i = 0; i < mStreamBufs.size(); i++) + { + const char * eval = Con::evaluate((const char *)mStreamBufs[i]); + SceneObject * obj = dynamic_cast(Sim::findObject(eval)); + if(obj && !mSelectionLocked) + mSelected.addObject(obj); + } + + // drop it ... + dropSelection(mSelected); + return(true); +} + +//------------------------------------------------------------------------------ + +void WorldEditor::hideSelection(bool hide) +{ + // set server/client objects hide field + for(U32 i = 0; i < mSelected.size(); i++) + { + // client + SceneObject * clientObj = getClientObj(mSelected[i]); + if(!clientObj) + continue; + + clientObj->setHidden(hide); + + // server + mSelected[i]->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; + + Point3F centroid = mObjectsUseBoxCenter ? sel.getBoxCentroid() : sel.getCentroid(); + + switch(mDropType) + { + case DropAtCentroid: + // already there + break; + + case DropAtOrigin: + { + sel.offset(Point3F(-centroid)); + break; + } + + case DropAtCameraWithRot: + { + sel.offset(Point3F(smCamPos - centroid)); + sel.rotate(smCamRot, centroid); + break; + } + + case DropAtCamera: + { + sel.offset(Point3F(smCamPos - centroid)); + break; + } + + case DropBelowCamera: + { + Point3F offset = smCamPos - centroid; + offset.z -= 15.f; + sel.offset(offset); + break; + } + + case DropAtScreenCenter: + { + Gui3DMouseEvent event; + event.pos = smCamPos; + + Point2I offset = localToGlobalCoord(Point2I(0,0)); + + Point3F sp(offset.x + (getExtent().x / 2), offset.y + (getExtent().y / 2), 1); + + Point3F wp; + unproject(sp, &wp); + event.vec = wp - smCamPos; + event.vec.normalizeSafe(); + + // if fails to hit screen, then place at camera + CollisionInfo info; + if(collide(event, info)) + sel.offset(Point3F(info.pos - centroid)); + else + sel.offset(Point3F(event.pos - centroid)); + + break; + } + + case DropToGround: + { + for(U32 i = 0; i < sel.size(); i++) + { + Point3F start; + MatrixF mat = sel[i]->getTransform(); + mat.getColumn(3, &start); + Point3F end = start; + start.z = -2000.f; + end.z = 2000.f; + + RayInfo ri; + bool hit; + if(mBoundingBoxCollision) + hit = gServerContainer.collideBox(start, end, TerrainObjectType, &ri); + else + hit = gServerContainer.castRay(start, end, TerrainObjectType, &ri); + + if(hit) + { + mat.setColumn(3, ri.point); + sel[i]->setTransform(mat); + } + else + sel.offset(Point3F(smCamPos - centroid)); + } + break; + } + } + + // + updateClientTransforms(sel); +} + +//------------------------------------------------------------------------------ + +SceneObject * WorldEditor::getControlObject() +{ + GameConnection * connection = GameConnection::getLocalClientConnection(); + if(connection) + return(dynamic_cast(connection->getControlObject())); + return(0); +} + +bool WorldEditor::collide(const Gui3DMouseEvent & event, CollisionInfo & info) +{ + // turn off the collsion with the control object + SceneObject * controlObj = getControlObject(); + if(controlObj) + controlObj->disableCollision(); + + // + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * mProjectDistance; + + // + RayInfo ri; + bool hit; + if(mBoundingBoxCollision) + hit = gServerContainer.collideBox(startPnt, endPnt, -1, &ri); + else + hit = gServerContainer.castRay(startPnt, endPnt, -1, &ri); + + // + if(hit) + { + info.pos = ri.point; + info.obj = ri.object; + info.normal = ri.normal; + AssertFatal(info.obj, "WorldEditor::collide - client container returned non SceneObject"); + } + + if(controlObj) + controlObj->enableCollision(); + return(hit); +} + +//------------------------------------------------------------------------------ +// main render functions + +void WorldEditor::renderSelectionWorldBox(Selection & sel) +{ + if(!mRenderSelectionBox) + return; + + // + if(!sel.size()) + return; + + // build the world bounds + Box3F selBox = sel[0]->getWorldBox(); + + U32 i; + for(i = 1; i < sel.size(); i++) + { + const Box3F & wBox = sel[i]->getWorldBox(); + selBox.min.setMin(wBox.min); + selBox.max.setMax(wBox.max); + } + + // + glDisable(GL_CULL_FACE); + glColor4ub(mSelectionBoxColor.red, mSelectionBoxColor.green, + mSelectionBoxColor.blue, mSelectionBoxColor.alpha); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // create the box points + Point3F projPnts[8]; + for(i = 0; i < 8; i++) + { + Point3F pnt; + pnt.set(BoxPnts[i].x ? selBox.max.x : selBox.min.x, + BoxPnts[i].y ? selBox.max.y : selBox.min.y, + BoxPnts[i].z ? selBox.max.z : selBox.min.z); + projPnts[i] = pnt; + } + + // do the box + for(U32 j = 0; j < 6; j++) + { + glBegin(GL_LINE_LOOP); + for(U32 k = 0; k < 4; k++) + glVertex3f(projPnts[BoxVerts[j][k]].x, + projPnts[BoxVerts[j][k]].y, + projPnts[BoxVerts[j][k]].z); + glEnd(); + } +} + +void WorldEditor::renderObjectBox(SceneObject * obj, const ColorI & col) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + glDisable(GL_CULL_FACE); + glColor4ub(col.red, col.green, col.blue, col.alpha); + + // project the points... + Box3F box = obj->getObjBox(); + MatrixF mat = obj->getTransform(); + VectorF scale = obj->getScale(); + + Point3F projPnts[8]; + for(U32 i = 0; i < 8; i++) + { + Point3F pnt; + pnt.set(BoxPnts[i].x ? box.max.x : box.min.x, + BoxPnts[i].y ? box.max.y : box.min.y, + BoxPnts[i].z ? box.max.z : box.min.z); + + // scale it + pnt.convolve(scale); + mat.mulP(pnt, &projPnts[i]); + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // do the box + for(U32 j = 0; j < 6; j++) + { + glBegin(GL_LINE_LOOP); + for(U32 k = 0; k < 4; k++) + glVertex3f(projPnts[BoxVerts[j][k]].x, + projPnts[BoxVerts[j][k]].y, + projPnts[BoxVerts[j][k]].z); + glEnd(); + } + +// // render the collision polys? +// // jff - this is using the worldbox... use obj box? which sides? +// if(mRenderBoxIntersect) +// { +// obj->disableCollision(); +// Box3F bBox = obj->getWorldBox(); +// +// bBox.min.z -= mProjectDistance; +// bBox.max.z += mProjectDistance; +// +// ClippedPolyList polyList; +// polyList.mPlaneList.clear(); +// polyList.mNormal.set(0,0,0); +// polyList.mPlaneList.setSize(4); +// polyList.mPlaneList[0].set(bBox.min, VectorF(-1,0,0)); +// polyList.mPlaneList[1].set(bBox.max, VectorF(0,1,0)); +// polyList.mPlaneList[2].set(bBox.max, VectorF(1,0,0)); +// polyList.mPlaneList[3].set(bBox.min, VectorF(0,-1,0)); +// +// // build the poly list +// if(gServerContainer.buildPolyList(bBox, -1, &polyList)) +// { +// glEnable(GL_CULL_FACE); +// glDisable(GL_DEPTH_TEST); +// glVertexPointer(3,GL_FLOAT,sizeof(ClippedPolyList::Vertex), polyList.mVertexList.address()); +// glEnableClientState(GL_VERTEX_ARRAY); +// +// glColor4ub(mBoxIntersectColor.red, mBoxIntersectColor.green, mBoxIntersectColor.blue, mBoxIntersectColor.alpha); +// +// glEnable(GL_BLEND); +// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +// +// // render em... +// ClippedPolyList::Poly * p; +// for (p = polyList.mPolyList.begin(); p < polyList.mPolyList.end(); p++) { +// glDrawElements(GL_POLYGON,p->vertexCount, +// GL_UNSIGNED_INT,&polyList.mIndexList[p->vertexStart]); +// } +// +// glDisableClientState(GL_VERTEX_ARRAY); +// glEnable(GL_DEPTH_TEST); +// glDisable(GL_CULL_FACE); +// } +// obj->enableCollision(); +// } + + dglSetCanonicalState(); + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//------------------------------------------------------------------------------ + +void WorldEditor::renderObjectFace(SceneObject * obj, const VectorF & normal, const ColorI & col) +{ + // 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.max.x : box.min.x, + BoxPnts[BoxVerts[normI][i]].y ? box.max.y : box.min.y, + BoxPnts[BoxVerts[normI][i]].z ? box.max.z : box.min.z); + + // scale it + pnt.convolve(scale); + mat.mulP(pnt, &projPnts[i]); + } + + glDisable(GL_CULL_FACE); + glColor4ub(col.red, col.green, col.blue, col.alpha); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); + + // + glBegin(GL_QUADS); + for(U32 k = 0; k < 4; k++) + glVertex3f(projPnts[k].x, projPnts[k].y, projPnts[k].z); + glEnd(); + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); +} + +//------------------------------------------------------------------------------ + +void WorldEditor::renderPlane(const Point3F & origin) +{ + if(!(mRenderPlane || mRenderPlaneHashes)) + return; + + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glColor4ub(mGridColor.red, mGridColor.green, mGridColor.blue, mGridColor.alpha); + Point2F start(origin.x - mPlaneDim / 2, origin.y - mPlaneDim / 2); + + // + if(mRenderPlane) + { + // draw the plane + glBegin(GL_QUADS); + glVertex3f(start.x, start.y, origin.z); + glVertex3f(start.x, start.y + mPlaneDim, origin.z); + glVertex3f(start.x + mPlaneDim, start.y + mPlaneDim, origin.z); + glVertex3f(start.x + mPlaneDim, start.y, origin.z); + glEnd(); + } + + // + if(mRenderPlaneHashes) + { + if(mGridSize.x > 0) + { + U32 xSteps = (U32)(mPlaneDim / mGridSize.x); + F32 hashStart = mCeil(start.x / mGridSize.x) * mGridSize.x; + for(U32 i = 0; i < xSteps; i++) + { + glBegin(GL_LINE_LOOP); + glVertex3f(hashStart + mGridSize.x * i, start.y, origin.z + 0.001f); + glVertex3f(hashStart + mGridSize.x * i, start.y + mPlaneDim, origin.z + 0.001f); + glEnd(); + } + } + + if(mGridSize.y > 0) + { + U32 ySteps = (U32)(mPlaneDim / mGridSize.y); + F32 hashStart = mCeil(start.y / mGridSize.y) * mGridSize.y; + for(U32 i = 0; i < ySteps; i++) + { + glBegin(GL_LINE_LOOP); + glVertex3f(start.x, hashStart + mGridSize.y * i, origin.z + 0.001f); + glVertex3f(start.x + mPlaneDim, hashStart + mGridSize.y * i, origin.z + 0.001f); + glEnd(); + } + } + } + + glDisable(GL_BLEND); +} + +//------------------------------------------------------------------------------ + +void WorldEditor::renderMousePopupInfo() +{ + Point2I pos(mLastMouseEvent.mousePoint.x, + mLastMouseEvent.mousePoint.y + getCursor()->getExtent().y - getCursor()->getHotSpot().y); + + if(mCurrentMode == mDefaultMode && !mMouseDragged) + return; + + char buf[256]; + switch(mCurrentMode) + { + case Move: + { + if(!mSelected.size()) + return; + + Point3F pos = mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid(); + dSprintf(buf, sizeof(buf), "x: %0.3f, y: %0.3f, z: %0.3f", pos.x, pos.y, pos.z); + break; + } + + case Rotate: { + + if(!bool(mHitObject) || (mSelected.size() != 1)) + return; + + // print out the angle-axis 'fo + 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 Scale: { + + if(!bool(mHitObject) || (mSelected.size() != 1)) + return; + + VectorF scale = mHitObject->getScale(); + + Box3F box = mHitObject->getObjBox(); + box.min.convolve(scale); + box.max.convolve(scale); + + box.max -= box.min; + dSprintf(buf, sizeof(buf), "w: %0.3f, h: %0.3f, d: %0.3f", box.max.x, box.max.y, box.max.z); + break; + } + + default: + return; + } + + U32 width = mProfile->mFont->getStrWidth(buf); + + if(mRenderPopupBackground) + { + Point2I min(pos.x - width / 2 - 2, pos.y - 1); + Point2I max(pos.x + width / 2 + 2, pos.y + mProfile->mFont->getHeight() + 1); + + dglDrawRectFill(min, max, mPopupBackgroundColor); + } + + dglSetBitmapModulation(mPopupTextColor); + dglDrawText(mProfile->mFont, Point2I(pos.x - width / 2, pos.y), buf); +} + +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// render the handle/text/... +void WorldEditor::renderScreenObj(SceneObject * obj, Point2I sPos) +{ + // do not render control object stuff + if(obj == getControlObject() || obj->isHidden()) + return; + + ClassInfo::Entry * entry = getClassEntry(obj); + if(!entry) + entry = &mDefaultClassEntry; + + TextureObject * bitmap; + if(mRenderObjHandle) + { + // offset + if(obj->isLocked()) + bitmap = entry->mLockedHandle ? entry->mLockedHandle : mDefaultClassEntry.mLockedHandle; + else + { + if(mSelected.objInSet(obj)) + bitmap = entry->mSelectHandle ? entry->mSelectHandle : mDefaultClassEntry.mSelectHandle; + else + bitmap = entry->mDefaultHandle ? entry->mDefaultHandle : mDefaultClassEntry.mDefaultHandle; + } + + sPos.x -= (bitmap->bitmapWidth / 2); + sPos.y -= (bitmap->bitmapHeight / 2); + dglClearBitmapModulation(); + dglDrawBitmap(bitmap, sPos); + } + + // + if(mRenderObjText) + { + + const char * str = parseObjectFormat(obj, mObjTextFormat); + + Point2I extent(mProfile->mFont->getStrWidth(str), mProfile->mFont->getHeight()); + + Point2I pos(sPos); + + if(mRenderObjHandle) + { + pos.x += (bitmap->bitmapWidth / 2) - (extent.x / 2); + pos.y += (bitmap->bitmapHeight / 2) + 3; + } + dglSetBitmapModulation(mObjectTextColor); + dglDrawText(mProfile->mFont, pos, str); + } +} + +//------------------------------------------------------------------------------ +// axis gizmo stuff + +void WorldEditor::calcAxisInfo() +{ + if(!mSelected.size()) + return; + + // get the centroid.. + mSelected.invalidateCentroid(); + Point3F centroid = mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid(); + mAxisGizmoCenter = centroid; + + VectorF axisVector[3] = { + VectorF(1,0,0), + VectorF(0,1,0), + VectorF(0,0,1) + }; + + // adjust to object space if just one object... + if((mSelected.size() == 1) && !((mLastMouseEvent.modifier & SI_SHIFT) && (mCurrentMode == Move))) + { + const MatrixF & mat = mSelected[0]->getTransform(); + for(U32 i = 0; i < 3; i++) + { + VectorF tmp; + mat.mulV(axisVector[i], &tmp); + mAxisGizmoVector[i] = tmp; + mAxisGizmoVector[i].normalizeSafe(); + } + } + else + for(U32 i = 0; i < 3; i++) + mAxisGizmoVector[i] = axisVector[i]; + + // get the projected size... + SceneObject * obj = getControlObject(); + if(!obj) + return; + + // + Point3F camPos; + obj->getTransform().getColumn(3, &camPos); + + // assumes a 90deg FOV + Point3F dir = mAxisGizmoCenter - camPos; + mAxisGizmoProjLen = (F32(mAxisGizmoMaxScreenLen) / F32(getExtent().x)) * dir.magnitudeSafe() * mTan(mDegToRad(45.0)); +} + +bool WorldEditor::collideAxisGizmo(const Gui3DMouseEvent & event) +{ + if(!mAxisGizmoActive || !mSelected.size()) + return(false); + + // get the projected size... + SceneObject * obj = getControlObject(); + if(!obj) + return(false); + + // + Point3F camPos; + obj->getTransform().getColumn(3, &camPos); + + // assumes a 90deg FOV + Point3F dir = mAxisGizmoCenter - camPos; + mAxisGizmoProjLen = (F32(mAxisGizmoMaxScreenLen) / F32(getExtent().x)) * dir.magnitudeSafe() * mTan(mDegToRad(45.0)); + + dir.normalizeSafe(); + + mAxisGizmoSelAxis = -1; + + // find axis to use... + for(U32 i = 0; i < 3; i++) + { + VectorF up, normal; + mCross(dir, mAxisGizmoVector[i], &up); + mCross(up, mAxisGizmoVector[i], &normal); + + if(normal.isZero()) + break; + + PlaneF plane(mAxisGizmoCenter, normal); + + // width of the axis poly is 1/10 the run + Point3F a = up * mAxisGizmoProjLen / 10; + Point3F b = mAxisGizmoVector[i] * mAxisGizmoProjLen; + + Point3F poly [] = { + Point3F(mAxisGizmoCenter + a), + Point3F(mAxisGizmoCenter + a + b), + Point3F(mAxisGizmoCenter - a + b), + Point3F(mAxisGizmoCenter - a) + }; + + Point3F end = camPos + event.vec * mProjectDistance; + F32 t = plane.intersect(camPos, end); + if(t >= 0 && t <= 1) + { + Point3F pos; + pos.interpolate(camPos, end, t); + + // check if inside our 'poly' of this axis 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) + { + mAxisGizmoSelAxis = i; + return(true); + } + } + } + + // default to Z + mAxisGizmoSelAxis = 2; + return(false); +} + +//------------------------------------------------------------------------------ + +void WorldEditor::renderAxisGizmo() +{ + calcAxisInfo(); + + ColorI axisColors[3] = { + ColorI(0xff, 0x00, 0x00), + ColorI(0x00, 0xff, 0x00), + ColorI(0x00, 0x00, 0xff) + }; + + ColorI selectColor(0xff, 0xff, 0x00); + + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glBegin(GL_LINES); + + // render each of them... + for(U32 i = 0; i < 3; i++) + { + Point3F & centroid = mAxisGizmoCenter; + if(i == mAxisGizmoSelAxis) + glColor3ub(selectColor.red, selectColor.green, selectColor.blue); + else + glColor3ub(axisColors[i].red, axisColors[i].green, axisColors[i].blue); + glVertex3f(centroid.x, centroid.y, centroid.z); + glVertex3f(centroid.x + mAxisGizmoVector[i].x * mAxisGizmoProjLen, + centroid.y + mAxisGizmoVector[i].y * mAxisGizmoProjLen, + centroid.z + mAxisGizmoVector[i].z * mAxisGizmoProjLen); + } + + glEnd(); + glEnable(GL_DEPTH_TEST); +} + +void WorldEditor::renderAxisGizmoText() +{ + char axisText[] = "xyz"; + + for(U32 i = 0; i < 3; i++) + { + const Point3F & centroid = mAxisGizmoCenter; + Point3F pos(centroid.x + mAxisGizmoVector[i].x * mAxisGizmoProjLen, + centroid.y + mAxisGizmoVector[i].y * mAxisGizmoProjLen, + centroid.z + mAxisGizmoVector[i].z * mAxisGizmoProjLen); + + Point3F sPos; + if(project(pos, &sPos)) + { + char buf[2]; + buf[0] = axisText[i]; buf[1] = '\0'; + dglSetBitmapModulation(mObjectTextColor); + dglDrawText(mProfile->mFont, Point2I(sPos.x, sPos.y), buf); + } + } +} + +//------------------------------------------------------------------------------ + +Point3F WorldEditor::snapPoint(const Point3F & pnt) +{ + if(!mSnapToGrid) + return(pnt); + + + Point3F snap; + snap.x = snapFloat(pnt.x, mGridSize.x); + snap.y = snapFloat(pnt.y, mGridSize.y); + snap.z = snapFloat(pnt.z, mGridSize.z); + + return(snap); +} + +//------------------------------------------------------------------------------ +// ClassInfo stuff + +WorldEditor::ClassInfo::~ClassInfo() +{ + for(U32 i = 0; i < mEntries.size(); i++) + delete mEntries[i]; +} + +bool WorldEditor::objClassIgnored(const SceneObject * 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 SceneObject * 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 + +bool WorldEditor::grabCursors() +{ + struct _cursorInfo { + U32 index; + const char * name; + } infos[] = { + {HandCursor, "Editor_HandCursor"}, + {RotateCursor, "Editor_RotateCursor"}, + {ScaleCursor, "Editor_RotateCursor"}, + {MoveCursor, "Editor_MoveCursor"}, + {ArrowCursor, "Editor_ArrowCursor"}, + {DefaultCursor, "DefaultCursor"}, + }; + + // + for(U32 i = 0; i < (sizeof(infos) / sizeof(infos[0])); i++) + { + SimObject * obj = Sim::findObject(infos[i].name); + if(!obj) + { + Con::errorf(ConsoleLogEntry::Script, "WorldEditor::grabCursors: failed to find cursor '%s'.", infos[i].name); + return(false); + } + + GuiCursor *cursor = dynamic_cast(obj); + if(!cursor) + { + Con::errorf(ConsoleLogEntry::Script, "WorldEditor::grabCursors: object is not a cursor '%s'.", infos[i].name); + return(false); + } + + // + mCursors[infos[i].index] = cursor; + } + + // + mCurrentCursor = mCursors[DefaultCursor]; + return(true); +} + +void WorldEditor::setCursor(U32 cursor) +{ + AssertFatal(cursor < NumCursors, "WorldEditor::setCursor: invalid cursor"); + + mCurrentCursor = mCursors[cursor]; + Canvas->setCursor(mCurrentCursor); +} + +GuiCursor * WorldEditor::getCursor() +{ + AssertFatal(mCurrentCursor, "WorldEditor::getCursor: no cursor to get!"); + return(mCurrentCursor); +} + +//------------------------------------------------------------------------------ + +WorldEditor::WorldEditor() +{ + // init the field data + mPlanarMovement = true; + mUndoLimit = 40; + mDropType = DropAtScreenCenter; + mProjectDistance = 2000.f; + mBoundingBoxCollision = true; + mRenderPlane = true; + mRenderPlaneHashes = true; + mGridColor.set(255,255,255,20); + mPlaneDim = 500; + mGridSize.set(10,10,10); + mRenderPopupBackground = true; + mPopupBackgroundColor.set(100,100,100); + mPopupTextColor.set(255,255,0); + mSelectHandle = StringTable->insert("gui/Editor_SelectHandle.png"); + mDefaultHandle = StringTable->insert("gui/Editor_DefaultHandle.png"); + mLockedHandle = StringTable->insert("gui/Editor_LockedHandle.png"); + mObjectTextColor.set(255,255,255); + mObjectsUseBoxCenter = true; + mAxisGizmoMaxScreenLen = 200; + mAxisGizmoActive = true; + mMouseMoveScale = 0.2f; + mMouseRotateScale = 0.01f; + mMouseScaleScale = 0.01f; + mMinScaleFactor = 0.1f; + mMaxScaleFactor = 4000.f; + 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$"); + mFaceSelectColor.set(0,0,100,100); + mRenderSelectionBox = true; + mSelectionBoxColor.set(255,255,0); + mSelectionLocked = false; + mSnapToGrid = false; + mSnapRotations = false; + mRotationSnap = 15.f; + mToggleIgnoreList = false; + mRenderNav = false; + + mRedirectID = 0; + + // + mHitInfo.obj = 0; + mHitObject = mHitInfo.obj; + + // + mDefaultMode = mCurrentMode = Move; + mCurrentCursor = 0; + mMouseDown = false; + mDragSelect = false; + + // + mSelected.autoSelect(true); + mDragSelected.autoSelect(false); +} + +WorldEditor::~WorldEditor() +{ + clearUndo(mUndoList); + clearUndo(mRedoList); +} + +//------------------------------------------------------------------------------ + +bool WorldEditor::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + // grab all the cursors + if(!grabCursors()) + return(false); + + // create the default class entry + mDefaultClassEntry.mName = 0; + mDefaultClassEntry.mIgnoreCollision = false; + mDefaultClassEntry.mDefaultHandle = TextureHandle(mDefaultHandle); + mDefaultClassEntry.mSelectHandle = TextureHandle(mSelectHandle); + mDefaultClassEntry.mLockedHandle = TextureHandle(mLockedHandle); + + if(!(mDefaultClassEntry.mDefaultHandle && mDefaultClassEntry.mSelectHandle && mDefaultClassEntry.mLockedHandle)) + return(false); + + 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::on3DMouseMove(const Gui3DMouseEvent & event) +{ + setCursor(ArrowCursor); + mHitInfo.obj = 0; + + // + if(collideAxisGizmo(event)) + setCursor(HandCursor); + else + { + CollisionInfo info; + if(collide(event, info) && !objClassIgnored(info.obj)) + { + setCursor(HandCursor); + mHitInfo = info; + } + } + + // + mHitObject = mHitInfo.obj; + + mLastMouseEvent = event; +} + +void WorldEditor::on3DMouseDown(const Gui3DMouseEvent & event) +{ + mMouseDown = true; + mMouseDragged = false; + mLastRotation = 0.f; + + mouseLock(); + + // use ctrl to toggle vertical movement + mUseVertMove = (event.modifier & SI_CTRL); + + // check gizmo first + mUsingAxisGizmo = false; + if(collideAxisGizmo(event)) + { + mUsingAxisGizmo = true; + mHitMousePos = event.mousePoint; + mHitCentroid = mSelected.getCentroid(); + mHitRotation = extractEuler(mSelected[0]->getTransform()); + mHitObject = mSelected[0]; + } + else + { + CollisionInfo info; + if(collide(event, info) && !objClassIgnored(info.obj)) + { + if(!mSelectionLocked) + { + if(!(event.modifier & SI_CTRL)) + { + if(!(event.modifier & SI_SHIFT) && !mSelected.objInSet(info.obj)) + mSelected.clear(); + mSelected.addObject(info.obj); + } + } + + if(event.mouseClickCount > 1) + { + // + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", info.obj->getId()); + + SimObject * obj = 0; + if(mRedirectID) + obj = Sim::findObject(mRedirectID); + Con::executef(obj ? obj : this, 2, "onDblClick", buf); + } + + mHitInfo = info; + mHitObject = mHitInfo.obj; + mHitOffset = info.pos - mSelected.getCentroid(); + mHitRotation = extractEuler(mHitObject->getTransform()); + mHitMousePos = event.mousePoint; + mHitCentroid = mSelected.getCentroid(); + } + else if(!mSelectionLocked) + { + if(!(event.modifier & SI_SHIFT)) + mSelected.clear(); + + 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; + mUsingAxisGizmo = false; + + // check if selecting objects.... + if(mDragSelect) + { + mDragSelect = false; + + // add all the objects from the drag selection into the normal selection + for(U32 i = 0; i < mDragSelected.size(); i++) + mSelected.addObject(mDragSelected[i]); + mDragSelected.clear(); + + mouseUnlock(); + return; + } + + mHitInfo.obj = 0; + + // + if(collideAxisGizmo(event)) + setCursor(HandCursor); + else + { + CollisionInfo info; + if(collide(event, info) && !objClassIgnored(info.obj)) + { + setCursor(HandCursor); + + if(!mMouseDragged && !mSelectionLocked) + { + if(event.modifier & SI_CTRL) + { + if(mSelected.objInSet(info.obj)) + mSelected.removeObject(info.obj); + else + mSelected.addObject(info.obj); + } + else if(!(event.modifier & SI_SHIFT)) + { + mSelected.clear(); + mSelected.addObject(info.obj); + } + } + + mHitInfo = info; + } + else + setCursor(ArrowCursor); + } + + // + mHitObject = mHitInfo.obj; + mouseUnlock(); +} + +void WorldEditor::on3DMouseDragged(const Gui3DMouseEvent & event) +{ + if(!mMouseDown) + return; + + // + if(!mMouseDragged) + { + if(!mUsingAxisGizmo) + { + // vert drag on new object.. reset hit offset + if(bool(mHitObject) && !mSelected.objInSet(mHitObject)) + { + if(!mSelectionLocked) + mSelected.addObject(mHitObject); + + mHitOffset = mHitInfo.pos - mSelected.getCentroid(); + } + } + + // create and add an undo state + if(!mDragSelect) + { + addUndo(mUndoList, createUndo(mSelected)); + clearUndo(mRedoList); + } + + 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 && (!bool(mHitObject) || !mSelected.objInSet(mHitObject))) + return; + + // anything locked? + for(U32 i = 0; i < mSelected.size(); i++) + if(mSelected[i]->isLocked()) + return; + + // do stuff + switch(mCurrentMode) + { + case Move: + { + setCursor(MoveCursor); + + // grabbed axis gizmo? + if(mUsingAxisGizmo) + { + F32 offset = (event.mousePoint.x - mHitMousePos.x) * mMouseMoveScale; + Point3F projPnt = mHitCentroid; + + for(S32 i = 0; i < 3; i++) + if(i == mAxisGizmoSelAxis) + ((F32*)projPnt)[i] += offset; + + // + if((mSelected.size() == 1) && !(event.modifier & SI_SHIFT)) + { + MatrixF mat = mSelected[0]->getTransform(); + Point3F offset; + mat.mulV(projPnt - mHitCentroid, &offset); + + mSelected.offset(offset + mHitCentroid - mSelected.getCentroid()); + } + else + { + // snap to the selected axis + Point3F snap = snapPoint(projPnt); + ((F32*)projPnt)[mAxisGizmoSelAxis] = ((F32*)snap)[mAxisGizmoSelAxis]; + mSelected.offset(projPnt - mSelected.getCentroid()); + } + + updateClientTransforms(mSelected); + } + else + { + // ctrl modifier movement? + if(mUseVertMove) + { + if(mPlanarMovement) + { + // do a projection onto the z axis + F64 pDist = mSqrt((event.pos.x - mHitInfo.pos.x) * (event.pos.x - mHitInfo.pos.x) + + (event.pos.y - mHitInfo.pos.y) * (event.pos.y - mHitInfo.pos.y)); + + Point3F vec(mHitInfo.pos.x - event.pos.x, mHitInfo.pos.y - event.pos.y, 0.f); + vec.normalizeSafe(); + + F64 projDist = mDot(event.vec, vec); + if(projDist == 0.f) + return; + + F64 scale = pDist / projDist; + vec = event.pos + (event.vec * scale); + + vec.x = mHitInfo.pos.x; + vec.y = mHitInfo.pos.y; + mSelected.offset(vec - mSelected.getCentroid() - mHitOffset); + updateClientTransforms(mSelected); + } + else + { + // do a move on the z axis + F32 diff = mLastMouseEvent.mousePoint.x - event.mousePoint.x; + F32 offset = diff * mMouseMoveScale; + Point3F projPnt = mSelected.getCentroid(); + projPnt.z += offset; + + // snap just to z axis + Point3F snapped = snapPoint(projPnt); + projPnt.z = snapped.z; + + mSelected.offset(projPnt - mSelected.getCentroid()); + updateClientTransforms(mSelected); + } + } + else + { + // move on XY plane + if(mPlanarMovement) + { + // on z + F32 cos = mDot(event.vec, Point3F(0,0,-1)); + F32 a = event.pos.z - mHitInfo.pos.z; + if(cos != 0.f) + { + F32 c = a / cos; + + Point3F projPnt = event.vec * c; + projPnt += event.pos; + projPnt -= mHitOffset; + + // + F32 z = projPnt.z; + projPnt = snapPoint(projPnt); + projPnt.z = z; + + mSelected.offset(projPnt - mSelected.getCentroid()); + updateClientTransforms(mSelected); + } + } + else + { + // offset the pnt of collision - no snapping involved + mSelected.disableCollision(); + + CollisionInfo info; + if(collide(event, info)) + { + mSelected.offset(info.pos - mSelected.getCentroid() - mHitOffset); + updateClientTransforms(mSelected); + } + + mSelected.enableCollision(); + } + } + } + break; + } + + case Scale: + { + setCursor(ScaleCursor); + + // can scale only single selections + if(mSelected.size() > 1) + break; + + if(mUsingAxisGizmo) + { + // + F32 diff = mLastMouseEvent.mousePoint.x - event.mousePoint.x; + + // offset the correct axis + EulerF curScale = ((SceneObject*)mSelected[0])->getScale(); + EulerF scale; + + F32 * pCurScale = ((F32*)curScale); + F32 * pScale = ((F32*)scale); + + for(S32 i = 0; i < 3; i++) + { + if(i == mAxisGizmoSelAxis) + pScale[i] = 1.f + (diff * mMouseScaleScale); + else + pScale[i] = 1.f; + + // clamp + if(pCurScale[i] * pScale[i] < mMinScaleFactor) + pScale[i] = mMinScaleFactor / pCurScale[i]; + if(pCurScale[i] * pScale[i] > mMaxScaleFactor) + pScale[i] = mMaxScaleFactor / pCurScale[i]; + } + + mSelected.scale(scale); + updateClientTransforms(mSelected); + } + else + { + if(!mBoundingBoxCollision) + { + // the hit normal is not useful, enable bounding box + // collision and get the collided face/normal + CollisionInfo info; + + mBoundingBoxCollision = true; + bool hit = collide(event, info); + mBoundingBoxCollision = false; + + // hit and the hit object? + if(!hit || (info.obj != (SceneObject*)mHitObject)) + break; + + mHitInfo = info; + } + + VectorF normal; + mCross(mHitInfo.normal, event.vec, &normal); + + VectorF planeNormal; + mCross(mHitInfo.normal, normal, &planeNormal); + + PlaneF plane; + plane.set(mHitInfo.pos, planeNormal); + + Point3F vecProj = event.pos + event.vec * mProjectDistance; + F32 t = plane.intersect(event.pos, vecProj); + if(t < 0.f || t > 1.f) + break; + + Point3F pnt; + pnt.interpolate(event.pos, vecProj, t); + + // figure out which axis we are working with, then + // find the distance to the correct face on the bbox + VectorF objNorm; + const MatrixF & worldMat = mHitObject->getWorldTransform(); + worldMat.mulV(mHitInfo.normal, &objNorm); + + U32 normIndex = getBoxNormalIndex(objNorm); + + Box3F box = mHitObject->getObjBox(); + VectorF curScale = mHitObject->getScale(); + + // scale and transform the bbox + Point3F size = box.max - box.min; + + box.min.convolve(curScale); + box.max.convolve(curScale); + + Box3F projBox; + const MatrixF & objMat = mHitObject->getTransform(); + objMat.mulP(box.min, &projBox.min); + objMat.mulP(box.max, &projBox.max); + + // if positive normal then grab max, else min + Point3F boxPnt; + Point3F offset; + if(normIndex & 0x01) + { + boxPnt = projBox.max; + worldMat.mulV(mSelected.getCentroid() - projBox.min, &offset); + } + else + { + boxPnt = projBox.min; + worldMat.mulV(mSelected.getCentroid() - projBox.max, &offset); + } + + plane.set(boxPnt, mHitInfo.normal); + F32 dist = plane.distToPlane(pnt); + + // set the scale for the correct axis + Point3F scale; + F32 * pScale = ((F32*)scale); + F32 * pSize = ((F32*)size); + F32 * pCurScale = ((F32*)curScale); + F32 * pOffset = ((F32*)offset); + + for(U32 i = 0; i < 3; i++) + { + if((normIndex >> 1) == i) + { + // get the new scale + pScale[i] = (pSize[i] * pCurScale[i] + dist) / pSize[i]; + + // clamp + if(pScale[i] < mMinScaleFactor) + pScale[i] = mMinScaleFactor; + if(pScale[i] > mMaxScaleFactor) + pScale[i] = mMaxScaleFactor; + + pOffset[i] = pScale[i] / pCurScale[i] * pOffset[i] - pOffset[i]; + } + else + { + pScale[i] = pCurScale[i]; + pOffset[i] = 0; + } + } + + objMat.mulV(offset, &pnt); + mSelected.offset(pnt); + mHitObject->setScale(scale); + updateClientTransforms(mSelected); + } + + break; + } + + case Rotate: + { + setCursor(RotateCursor); + + // default to z axis + S32 axis = 2; + if(mUsingAxisGizmo) + axis = mAxisGizmoSelAxis; + + F32 angle = (event.mousePoint.x - mHitMousePos.x) * mMouseRotateScale; + + // + if(mSnapRotations) + { + angle = mDegToRad(snapFloat(mRadToDeg(angle), mRotationSnap)); + if(mSelected.size() == 1) + angle -= ((F32*)mHitRotation)[axis]; + } + + EulerF rot(0.f, 0.f, 0.f); + ((F32*)rot)[axis] = (angle - mLastRotation); + + Point3F centroid = mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid(); + + mSelected.rotate(rot, centroid); + updateClientTransforms(mSelected); + + mLastRotation = angle; + + break; + } + } + mLastMouseEvent = event; +} + +void WorldEditor::on3DMouseEnter(const Gui3DMouseEvent &) +{ + mMouseDown = false; +} + +void WorldEditor::on3DMouseLeave(const Gui3DMouseEvent &) +{ + mMouseDown = false; + mDragSelect = false; + mDragSelected.clear(); + mMouseDragged = false; + mUsingAxisGizmo = false; + + mHitInfo.obj = 0; + mHitObject = mHitInfo.obj; + + setCursor(DefaultCursor); +} + +void WorldEditor::on3DRightMouseDown(const Gui3DMouseEvent & event) +{ + CollisionInfo info; + if(collide(event, info) && !objClassIgnored(info.obj)) + { + if(!mSelected.objInSet(info.obj) && !mSelectionLocked) + { + mSelected.clear(); + mSelected.addObject(info.obj); + } + + mHitInfo = info; + mHitObject = mHitInfo.obj; + } + else + { + if(!mSelectionLocked) + mSelected.clear(); + + mHitInfo.obj = 0; + mHitObject = mHitInfo.obj; + } +} + +void WorldEditor::on3DRightMouseUp(const Gui3DMouseEvent & event) +{ + char buf[32]; + dSprintf(buf, sizeof(buf), "%d %d", event.mousePoint.x, event.mousePoint.y); + + SimObject * obj = 0; + if(mRedirectID) + obj = Sim::findObject(mRedirectID); + Con::executef(obj ? obj : this, 2, "onContextMenu", buf); +} + +//------------------------------------------------------------------------------ + +void WorldEditor::updateGuiInfo() +{ + SimObject * obj = 0; + if(mRedirectID) + obj = Sim::findObject(mRedirectID); + + char buf[] = ""; + Con::executef(obj ? obj : this, 2, "onGuiUpdate", buf); +} + +//------------------------------------------------------------------------------ + +static void findObjectsCallback(SceneObject* obj, S32 val) +{ + Vector * list = (Vector*)val; + list->push_back(obj); +} + +void WorldEditor::renderScene(const RectI & updateRect) +{ + glEnable(GL_DEPTH_TEST); + + // navGraph + if(mRenderNav) + { + NavigationGraph *ng = dynamic_cast(Sim::findObject("NavGraph")); + if(ng) + ng->render(smCamPos, true); + } + + glDepthFunc(GL_LEQUAL); + + // just walk the selected + U32 i; + for(i = 0; i < mSelected.size(); i++) + { + if((const SceneObject *)mHitObject == mSelected[i]) + continue; + renderObjectBox(mSelected[i], mObjSelectColor); + } + + // do the drag selection + for(i = 0; i < mDragSelected.size(); i++) + renderObjectBox(mDragSelected[i], mObjSelectColor); + + // draw the mouse over obj + if(bool(mHitObject)) + { + ColorI & col = mSelected.objInSet(mHitObject) ? mObjMouseOverSelectColor : mObjMouseOverColor; + renderObjectBox(mHitObject, col); + + if(mCurrentMode == Scale && !mUsingAxisGizmo && mSelected.size() == 1) + renderObjectFace(mHitObject, mHitInfo.normal, mFaceSelectColor); + } + + // stuff to do if there is a selection + if(mSelected.size()) + { + if(mAxisGizmoActive) + renderAxisGizmo(); + + renderSelectionWorldBox(mSelected); + renderPlane(mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid()); + } + + // draw the handles and text's now... + dglSetClipRect(updateRect); + + if(mSelected.size() && mAxisGizmoActive) + renderAxisGizmoText(); + + // update what is in the selction + if(mDragSelect) + mDragSelected.clear(); + + // + Vector objects; + gServerContainer.findObjects(-1, findObjectsCallback, (S32)&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)) + { + // check if object needs to be added into the regions select + if(mDragSelect) + if(mDragRect.pointInRect(Point2I(sPos.x, sPos.y)) && !mSelected.objInSet(obj)) + mDragSelected.addObject(obj); + + // + renderScreenObj(obj, Point2I(sPos.x, sPos.y)); + } + } + + // + if(mShowMousePopupInfo && mMouseDown) + renderMousePopupInfo(); + + // seletion box + if(mDragSelect) + dglDrawRect(mDragRect, mDragRectColor); + +} + +//------------------------------------------------------------------------------ +// 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::DropToGround, "toGround" } +}; +static EnumTable gEditorDropTable(7, &dropEnums[0]); + +void WorldEditor::initPersistFields() +{ + Parent::initPersistFields(); + addField("planarMovement", TypeBool, Offset(mPlanarMovement, WorldEditor)); + addField("undoLimit", TypeS32, Offset(mUndoLimit, WorldEditor)); + addField("dropType", TypeEnum, Offset(mDropType, WorldEditor), 1, &gEditorDropTable); + addField("projectDistance", TypeF32, Offset(mProjectDistance, WorldEditor)); + addField("boundingBoxCollision", TypeBool, Offset(mBoundingBoxCollision, WorldEditor)); + addField("renderPlane", TypeBool, Offset(mRenderPlane, WorldEditor)); + addField("renderPlaneHashes", TypeBool, Offset(mRenderPlaneHashes, WorldEditor)); + addField("gridColor", TypeColorI, Offset(mGridColor, WorldEditor)); + addField("planeDim", TypeF32, Offset(mPlaneDim, WorldEditor)); + addField("gridSize", TypePoint3F, Offset(mGridSize, WorldEditor)); + addField("renderPopupBackground", TypeBool, Offset(mRenderPopupBackground, WorldEditor)); + addField("popupBackgroundColor", TypeColorI, Offset(mPopupBackgroundColor, WorldEditor)); + addField("popupTextColor", TypeColorI, Offset(mPopupTextColor, WorldEditor)); + addField("selectHandle", TypeString, Offset(mSelectHandle, WorldEditor)); + addField("defaultHandle", TypeString, Offset(mDefaultHandle, WorldEditor)); + addField("lockedHandle", TypeString, Offset(mLockedHandle, WorldEditor)); + addField("objectTextColor", TypeColorI, Offset(mObjectTextColor, WorldEditor)); + addField("objectsUseBoxCenter", TypeBool, Offset(mObjectsUseBoxCenter, WorldEditor)); + addField("axisGizmoMaxScreenLen", TypeS32, Offset(mAxisGizmoMaxScreenLen, WorldEditor)); + addField("axisGizmoActive", TypeBool, Offset(mAxisGizmoActive, WorldEditor)); + addField("mouseMoveScale", TypeF32, Offset(mMouseMoveScale, WorldEditor)); + addField("mouseRotateScale", TypeF32, Offset(mMouseRotateScale, WorldEditor)); + addField("mouseScaleScale", TypeF32, Offset(mMouseScaleScale, WorldEditor)); + addField("minScaleFactor", TypeF32, Offset(mMinScaleFactor, WorldEditor)); + addField("maxScaleFactor", TypeF32, Offset(mMaxScaleFactor, WorldEditor)); + 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("snapToGrid", TypeBool, Offset(mSnapToGrid, WorldEditor)); + addField("snapRotations", TypeBool, Offset(mSnapRotations, WorldEditor)); + addField("rotationSnap", TypeF32, Offset(mRotationSnap, WorldEditor)); + addField("toggleIgnoreList", TypeBool, Offset(mToggleIgnoreList, WorldEditor)); + addField("renderNav", TypeBool, Offset(mRenderNav, WorldEditor)); +} + +//------------------------------------------------------------------------------ + +void WorldEditor::consoleInit() +{ + Con::addCommand("WorldEditor", "ignoreObjClass", cIgnoreObjClass, "worldEditor.ignoreObjectClass(class_name, ...);", 3, 0); + Con::addCommand("WorldEditor", "clearIgnoreList", cClearIgnoreList, "worldEditor.clearIgnoreList();", 2, 2); + Con::addCommand("WorldEditor", "undo", cUndoAction, "worldEditor.undo();", 2, 2); + Con::addCommand("WorldEditor", "redo", cRedoAction, "worldEditor.redo();", 2, 2); + Con::addCommand("WorldEditor", "clearSelection", cClearSelection, "worldEditor.clearSelection();", 2, 2); + Con::addCommand("WorldEditor", "selectObject", cSelectObject, "worldEditor.selectObject(object);", 3, 3); + Con::addCommand("WorldEditor", "unselectObject", cUnselectObject, "worldEditor.unselectObject(object);", 3, 3); + Con::addCommand("WorldEditor", "getSelectionSize", cGetSelectionSize, "worldEditor.getSelectionSize();", 2, 2); + Con::addCommand("WorldEditor", "getSelectedObject", cGetSelectedObject, "worldEditor.getSelectedObject(index);", 3, 3); + Con::addCommand("WorldEditor", "getSelectionCentroid", cGetSelectionCentroid, "worldEditor.getSelectionCentroid();", 2, 2); + Con::addCommand("WorldEditor", "dropSelection", cDropSelection, "worldEditor.dropSelection();", 2, 2); + Con::addCommand("WorldEditor", "deleteSelection", cDeleteSelection, "worldEditor.deleteSelection();", 2, 2); + Con::addCommand("WorldEditor", "copySelection", cCopySelection, "worldEditor.copySelection();", 2, 2); + Con::addCommand("WorldEditor", "pasteSelection", cPasteSelection, "worldEditor.pasteSelection();", 2, 2); + Con::addCommand("WorldEditor", "canPasteSelection", cCanPasteSelection, "worldEditor.canPasteSelection();", 2, 2); + Con::addCommand("WorldEditor", "getMode", cGetMode, "worldEditor.getMode();", 2, 2); + Con::addCommand("WorldEditor", "setMode", cSetMode, "worldEditor.setMode(move|rotate|scale);", 3, 3); + Con::addCommand("WorldEditor", "hideSelection", cHideSelection, "worldEditor.hideSelection(bool);", 3, 3); + Con::addCommand("WorldEditor", "lockSelection", cLockSelection, "worldEditor.lockSelection(bool);", 3, 3); + Con::addCommand("WorldEditor", "addUndoState", cAddUndoState, "worldEditor.addUndoState();", 2, 2); + Con::addCommand("WorldEditor", "redirectConsole", cRedirectConsole, "worldEditor.redirectConsole(objID)", 3, 3); +} + +//------------------------------------------------------------------------------ + +static void cIgnoreObjClass(SimObject * obj, S32 argc, const char ** argv) +{ + WorldEditor * wEditor = static_cast(obj); + + for(S32 i = 2; i < argc; i++) + { + WorldEditor::ClassInfo::Entry * entry = wEditor->getClassEntry(argv[i]); + if(entry) + entry->mIgnoreCollision = true; + else + { + entry = new WorldEditor::ClassInfo::Entry; + entry->mName = StringTable->insert(argv[i]); + entry->mIgnoreCollision = true; + if(!wEditor->addClassEntry(entry)) + delete entry; + } + } +} + +static void cClearIgnoreList(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + for(U32 i = 0; i < wEditor->mClassInfo.mEntries.size(); i++) + wEditor->mClassInfo.mEntries[i]->mIgnoreCollision = false; +} + +static void cUndoAction(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->processUndo(wEditor->mUndoList, wEditor->mRedoList); +} + +static void cRedoAction(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->processUndo(wEditor->mRedoList, wEditor->mUndoList); +} + +static void cClearSelection(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + if(wEditor->mSelectionLocked) + return; + + wEditor->mSelected.clear(); +} + +static void cSelectObject(SimObject * obj, S32, const char ** argv) +{ + WorldEditor * wEditor = static_cast(obj); + if(wEditor->mSelectionLocked) + return; + + SceneObject * select = dynamic_cast(Sim::findObject(argv[2])); + if(select && !wEditor->objClassIgnored(select)) + wEditor->mSelected.addObject(select); +} + +static void cUnselectObject(SimObject * obj, S32, const char ** argv) +{ + WorldEditor * wEditor = static_cast(obj); + if(wEditor->mSelectionLocked) + return; + + SceneObject * select = dynamic_cast(Sim::findObject(argv[2])); + if(select && !wEditor->objClassIgnored(select)) + wEditor->mSelected.removeObject(select); +} + +static S32 cGetSelectionSize(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + return(wEditor->mSelected.size()); +} + +static S32 cGetSelectedObject(SimObject * obj, S32, const char ** argv) +{ + WorldEditor * wEditor = static_cast(obj); + + S32 index = dAtoi(argv[2]); + if(index < 0 || index >= wEditor->mSelected.size()) + { + Con::errorf(ConsoleLogEntry::General, "WorldEditor::getSelectedObject: invalid object index"); + return(-1); + } + + return(wEditor->mSelected[index]->getId()); +} + +static const char * cGetSelectionCentroid(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + + const Point3F & centroid = wEditor->mSelected.getCentroid(); + char * ret = Con::getReturnBuffer(100); + dSprintf(ret, 100, "%f %f %f", centroid.x, centroid.y, centroid.z); + return(ret); +} + +static void cDropSelection(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->dropSelection(wEditor->mSelected); +} + +static void cDeleteSelection(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->deleteSelection(wEditor->mSelected); +} + +static void cCopySelection(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->copySelection(wEditor->mSelected); +} + +static void cPasteSelection(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->pasteSelection(); +} +static bool cCanPasteSelection(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + return(wEditor->mStreamBufs.size() != 0); +} + +static void cHideSelection(SimObject * obj, S32, const char ** argv) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->hideSelection(dAtob(argv[2])); +} + +static void cLockSelection(SimObject * obj, S32, const char ** argv) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->lockSelection(dAtob(argv[2])); +} + +static void cRedirectConsole(SimObject * obj, S32, const char ** argv) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->mRedirectID = dAtoi(argv[2]); +} + +static const char * cGetMode(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + + if(wEditor->mCurrentMode == WorldEditor::Move) + return("move"); + else if(wEditor->mCurrentMode == WorldEditor::Rotate) + return("rotate"); + else if(wEditor->mCurrentMode == WorldEditor::Scale) + return("scale"); + else + { + Con::warnf(ConsoleLogEntry::General, avar("worldEditor.getMode: unknown mode")); + return(""); + } +} + +static void cSetMode(SimObject * obj, S32, const char ** argv) +{ + WorldEditor * wEditor = static_cast(obj); + + if(!dStricmp(argv[2], "move")) + wEditor->mCurrentMode = WorldEditor::Move; + else if(!dStricmp(argv[2], "rotate")) + wEditor->mCurrentMode = WorldEditor::Rotate; + else if(!dStricmp(argv[2], "scale")) + wEditor->mCurrentMode = WorldEditor::Scale; + else + Con::warnf(ConsoleLogEntry::General, avar("worldEditor.setMode: invalid mode '%s'", argv[2])); +} + +static void cAddUndoState(SimObject * obj, S32, const char **) +{ + WorldEditor * wEditor = static_cast(obj); + wEditor->addUndo(wEditor->mUndoList, wEditor->createUndo(wEditor->mSelected)); + wEditor->clearUndo(wEditor->mRedoList); +} + diff --git a/editor/worldEditor.h b/editor/worldEditor.h new file mode 100644 index 0000000..1b5c161 --- /dev/null +++ b/editor/worldEditor.h @@ -0,0 +1,375 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WORLDEDITOR_H_ +#define _WORLDEDITOR_H_ + +#ifndef _EDITTSCTRL_H_ +#include "Editor/editTSCtrl.h" +#endif +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif + + +// declare the console functions and then make them friends of the class - +// hopefully this will reduce the number of functions needed in this class +static void cIgnoreObjClass(SimObject *, S32, const char **); +static void cClearIgnoreList(SimObject *, S32, const char**); +static void cUndoAction(SimObject *, S32, const char **); +static void cRedoAction(SimObject *, S32, const char **); +static void cClearSelection(SimObject *, S32, const char **); +static void cSelectObject(SimObject *, S32, const char **); +static void cUnselectObject(SimObject *, S32, const char **); +static S32 cGetSelectionSize(SimObject *, S32, const char **); +static S32 cGetSelectedObject(SimObject *, S32, const char **); +static const char * cGetSelectionCentroid(SimObject *, S32, const char **); +static void cDropSelection(SimObject *, S32, const char **); +static void cDeleteSelection(SimObject *, S32, const char **); +static void cCopySelection(SimObject *, S32, const char **); +static void cPasteSelection(SimObject *, S32, const char **); +static bool cCanPasteSelection(SimObject *, S32, const char **); +static void cHideSelection(SimObject *, S32, const char **); +static void cLockSelection(SimObject *, S32, const char **); +static const char * cGetMode(SimObject *, S32, const char **); +static void cSetMode(SimObject *, S32, const char **); +static void cAddUndoState(SimObject *, S32, const char **); +static void cRedirectConsole(SimObject * obj, S32, const char ** argv); + +class SceneObject; +class WorldEditor : public EditTSCtrl +{ + //... + friend void cIgnoreObjClass(SimObject *, S32, const char **); + friend void cClearIgnoreList(SimObject *, S32, const char **); + friend void cUndoAction(SimObject *, S32, const char **); + friend void cRedoAction(SimObject *, S32, const char **); + friend void cClearSelection(SimObject *, S32, const char **); + friend void cSelectObject(SimObject *, S32, const char **); + friend void cUnselectObject(SimObject *, S32, const char **); + friend S32 cGetSelectionSize(SimObject *, S32, const char **); + friend S32 cGetSelectedObject(SimObject *, S32, const char **); + friend const char * cGetSelectionCentroid(SimObject *, S32, const char **); + friend void cDropSelection(SimObject *, S32, const char **); + friend void cDeleteSelection(SimObject *, S32, const char **); + friend void cCopySelection(SimObject *, S32, const char **); + friend void cPasteSelection(SimObject *, S32, const char **); + friend bool cCanPasteSelection(SimObject *, S32, const char **); + friend void cHideSelection(SimObject *, S32, const char **); + friend void cLockSelection(SimObject *, S32, const char **); + friend const char * cGetMode(SimObject *, S32, const char **); + friend void cSetMode(SimObject *, S32, const char **); + friend void cAddUndoState(SimObject *, S32, const char **); + friend void cRedirectConsole(SimObject * obj, S32, const char ** argv); + + public: + + struct CollisionInfo + { + SceneObject * obj; + Point3F pos; + VectorF normal; + }; + + class Selection : public SimObject + { + typedef SimObject Parent; + + private: + + Point3F mCentroid; + Point3F mBoxCentroid; + bool mCentroidValid; + SimObjectList mObjectList; + bool mAutoSelect; + + void updateCentroid(); + + public: + + Selection(); + ~Selection(); + + // + U32 size() { return(mObjectList.size()); } + SceneObject * operator[] (S32 index) { return((SceneObject*)mObjectList[index]); } + + bool objInSet(SceneObject *); + + bool addObject(SceneObject *); + bool removeObject(SceneObject *); + void clear(); + + void onDeleteNotify(SimObject *); + + const Point3F & getCentroid(); + const Point3F & getBoxCentroid(); + + void enableCollision(); + void disableCollision(); + + // + void autoSelect(bool b) { mAutoSelect = b; } + void invalidateCentroid() { mCentroidValid = false; } + + // + void offset(const Point3F &); + void rotate(const EulerF &, const Point3F &); + void scale(const VectorF &); + }; + + // + static SceneObject * getClientObj(SceneObject *); + static void setClientObjInfo(SceneObject *, const MatrixF &, const VectorF &); + static void updateClientTransforms(Selection &); + + // VERY basic undo stuff - only concerned with transform/scale/... + private: + + struct SelectionState + { + struct Entry + { + MatrixF mMatrix; + VectorF mScale; + + // validation + U32 mObjId; + U32 mObjNumber; + }; + + Vector mEntries; + + SelectionState() { + VECTOR_SET_ASSOCIATION(mEntries); + } + }; + + SelectionState * createUndo(Selection &); + void addUndo(Vector & list, SelectionState * sel); + bool processUndo(Vector & src, Vector & dest); + void clearUndo(Vector & list); + + Vector mUndoList; + Vector mRedoList; + + // someday get around to creating a growing memory stream... + Vector mStreamBufs; + + bool deleteSelection(Selection & sel); + bool copySelection(Selection & sel); + bool pasteSelection(); + void dropSelection(Selection & sel); + + // work off of mSelected + void hideSelection(bool hide); + void lockSelection(bool lock); + + private: + typedef EditTSCtrl Parent; + + SceneObject * getControlObject(); + bool collide(const Gui3DMouseEvent & event, CollisionInfo & info); + + // gfx stuff + 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, Point2I sPos); + + // axis gizmo stuff... + void calcAxisInfo(); + bool collideAxisGizmo(const Gui3DMouseEvent & event); + void renderAxisGizmo(); + void renderAxisGizmoText(); + + // axis gizmo stuff... + Point3F mAxisGizmoCenter; + VectorF mAxisGizmoVector[3]; + F32 mAxisGizmoProjLen; + S32 mAxisGizmoSelAxis; + bool mUsingAxisGizmo; + + // + Point3F snapPoint(const Point3F & pnt); + + // + bool mMouseDown; + Selection mSelected; + bool mUseVertMove; + + 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; + + CollisionInfo mHitInfo; + Point3F mHitOffset; + SimObjectPtr mHitObject; + Point2I mHitMousePos; + Point3F mHitCentroid; + EulerF mHitRotation; + bool mMouseDragged; + Gui3DMouseEvent mLastMouseEvent; + F32 mLastRotation; + + // + class ClassInfo + { + public: + ~ClassInfo(); + + struct Entry + { + StringTableEntry mName; + bool mIgnoreCollision; + TextureHandle mDefaultHandle; + TextureHandle mSelectHandle; + TextureHandle mLockedHandle; + }; + + Vector mEntries; + }; + + + ClassInfo mClassInfo; + ClassInfo::Entry mDefaultClassEntry; + + bool objClassIgnored(const SceneObject * obj); + ClassInfo::Entry * getClassEntry(StringTableEntry name); + ClassInfo::Entry * getClassEntry(const SceneObject * obj); + bool addClassEntry(ClassInfo::Entry * entry); + + // persist field data + public: + + enum { + DropAtOrigin = 0, + DropAtCamera, + DropAtCameraWithRot, + DropBelowCamera, + DropAtScreenCenter, + DropAtCentroid, + DropToGround + }; + + bool mPlanarMovement; + S32 mUndoLimit; + S32 mDropType; + F32 mProjectDistance; + bool mBoundingBoxCollision; + bool mRenderPlane; + bool mRenderPlaneHashes; + ColorI mGridColor; + F32 mPlaneDim; + Point3F mGridSize; + bool mRenderPopupBackground; + ColorI mPopupBackgroundColor; + ColorI mPopupTextColor; + StringTableEntry mSelectHandle; + StringTableEntry mDefaultHandle; + StringTableEntry mLockedHandle; + ColorI mObjectTextColor; + bool mObjectsUseBoxCenter; + S32 mAxisGizmoMaxScreenLen; + bool mAxisGizmoActive; + F32 mMouseMoveScale; + F32 mMouseRotateScale; + F32 mMouseScaleScale; + F32 mMinScaleFactor; + F32 mMaxScaleFactor; + 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 mSnapToGrid; + bool mSnapRotations; + F32 mRotationSnap; + bool mToggleIgnoreList; + bool mRenderNav; + + private: + // cursor stuff + enum { + HandCursor = 0, + RotateCursor, + ScaleCursor, + MoveCursor, + ArrowCursor, + DefaultCursor, + + // + NumCursors + }; + + GuiCursor * mCursors[NumCursors]; + GuiCursor * mCurrentCursor; + bool grabCursors(); + void setCursor(U32 cursor); + GuiCursor * getCursor(); + + public: + + WorldEditor(); + ~WorldEditor(); + + // SimObject + bool onAdd(); + void onEditorEnable(); + + // 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); + + static void consoleInit(); + static void initPersistFields(); + + DECLARE_CONOBJECT(WorldEditor); +}; + +#endif + + + diff --git a/engine.overview.txt b/engine.overview.txt new file mode 100644 index 0000000..ceee295 --- /dev/null +++ b/engine.overview.txt @@ -0,0 +1,891 @@ +//---------------------------------------------------------------------------- +/*! \mainpage V12 Engine Documentation + +\section EngOverview Engine Overview +
    +
  • \ref ControlFlow +
  • \ref PlatformLayer +
      +
    • \ref PlatformOverview +
    • \ref PlatformEvent +
    • \ref PlatformDevice +
    • \ref PlatformUtil +
    • \ref PlatformNetwork +
    • \ref PlatformGame +
    +
  • \ref Console +
      +
    • \ref ConOverview +
    • \ref ConRef +
    • \ref ConFunctions +
    • \ref ConClasses +
    • \ref ConFields +
    • \ref ConDynamic +
    • \ref ConNamespace +
    • \ref ConObject +
    • \ref ConPackage +
    • \ref ConVariable +
    • \ref ConArray +
    • \ref ConStringOp +
    • \ref ConCompiler +
    • \ref ConDebugger +
    • \ref ConInterface +
    +
  • \ref Simulation +
      +
    • \ref SimOver +
    • \ref SimBase +
    • \ref SimManager +
    • \ref SimDB +
    +
  • \ref FileIO +
      +
    • \ref FileOverview +
    • \ref FilePaths +
    • \ref FileVolumes +
    • \ref FileResourceObject +
    + +
  • \ref InputModel +
      +
    • \ref InputPlatform +
    • \ref InputAction +
    • \ref InputBind +
    • \ref InputBindCmd +
    • \ref InputGlobalMap +
    • \ref InputMapStack +
    • \ref InputModKeys +
    • \ref InputGui +
    + +
  • \ref Graphics +
      +
    • \ref GraphicsOverview +
    • \ref GraphicsInit +
    • \ref GraphicsDgl +
    • \ref GraphicsTexture +
    • \ref GraphicsPrimitive +
    • \ref GraphicsPrimRender +
    • \ref Graphics3DRender +
    + +
  • \ref GUI +
      +
    • \ref GUIOver +
    • \ref GuiInput +
    • \ref GuiRender +
    • \ref GuiConsole +
    + +
  • \ref Render3D +
      +
    • \ref Render3DOver +
    • \ref Render3DSceneGraph +
    • \ref Render3DTerain +
    • \ref RenderInterior +
    • \ref Render3Space +
    + +
  • \ref Networking +
      +
    • \ref NetOver +
    • \ref NetPlatform +
    • \ref NetProtocol +
    • \ref NetConnection +
    • \ref NetBitstream +
    • \ref NetEvents +
    • \ref NetGhost +
    • \ref NetGameConnection +
    • \ref NetDatablocks +
    • \ref NetStringTable +
    • \ref NetConsole +
    +
+*/ +//--------------------------------------------------------- +/*! \page ControlFlow Basic Control Flow +Overview +Because different platforms can have different main() entry points for applications, the V12 Engine main() function resides in the target OS platform library. In the case of Windows, this is in file engine/platformWin32/winWindow.cc, where both main() (for console apps) and WinMain() are defined. These in turn call run() which calls Game->main(int argc, const char **argv). Game is a global object pointer referencing an instance of the GameInterface class that can be overridden for specific game behavior. + +The V12 example program's main initialization occurs in engine/game/main.cc in V12Game::main(). This function initializes libraries, initializes game functions and then cycles in the main game loop until the program is terminated. The main loop basically calls platform library functions (engine/platform/platform.h) to produce platform events, which then drive the main simulation. + +The main.cc file also has V12Game function overrides for some of the basic event procession functions: processMouseMoveEvent (which dispatches Windows mouse movements to the GUI), processInputEvent (which processes other input related events), and processTimeEvent which computes an elapsed time value based on the time scale setting of the simulation and then: + +1. processes time for server objects (serverProcess() in engine/game/game.cc) +2. checks for server network packet sends (serverNetProcess() in engine/game/netDispatch.cc) +3. advances simulation event time (Sim::advanceTime() in engine/console/simManager.cc) +4. processes time for client objects (clientProcess() in engine/game/game.cc) +5. checks for client network packet sends (clientNetProcess() in engine/game/netDispatch.cc) +6. renders the current frame (GuiCanvas::render() in engine/gui/guiCanvas.cc) +7. checks for network timeouts (dispatchCheckTimeouts() in engine/game/netDispatch.cc) + +Incoming UDP network packets are processed in V12Game::processPacketReceiveEvent (defined in engine/game/netDispatch.cc), and incoming TCP connection data or information is processed in V12Game::processConnected*Event (defined in engine/game/TCPObject.cc). +*/ + +//--------------------------------------------------- +/*! \page PlatformLayer Platform layer +\subsection PlatformOverview Overview +The platform layer is the foundation of the game - as the lowest level it provides a common cross platform/architecture interface to the game. The platform layer is responsible for handling file and network IO, graphics initialization, device input and generation of time events. Standard library calls are replicated in the platform layer and system header files are (pretty much) only included from the platform library. The platform layer is broken up into several sections - the cross-platform definition library (engine/platform), and the platform specific interface libraries (engine/platformWin32, engine/platformMacOS, engine/platformX86UNIX). + +\subsection PlatformEvent Event model +The game, as much as possible, is driven by a stream of events from the platform library. The following events are defined in engine/platform/event.h: InputEvent, MouseMoveEvent, PacketReceiveEvent, TimeEvent, QuitEvent, ConsoleEvent, ConnectedReceiveEvent, ConnectedAcceptEvent, and ConnectedNotifyEvent. By journaling the stream of events from the platform layer, the game portion of the simulation session can deterministically replayed for debugging purposes. +InputEvent - input from a physical input device such as a keyboard, mouse or joystick. +MouseMoveEvent - special screen-space mouse movement event for when the game is running in a window. +PacketReceiveEvent - unguaranteed data network packet event from some other host on the network. +TimeEvent - elapsed time event is passed in when no other events are pending. This allows the simulation to move forward and frames to be rendered. +QuitEvent - platform specific method for notifying the game that the host wishes to exit the game. +ConsoleEvent - command typed into a platform specific console (like the Win32 console for a dedicated server). +ConnectedReceiveEvent - Message data received on a TCP network connection. +ConnectedAcceptEvent - Notification event when a connection has been accepted from a remote client. +ConnectedNotifyEvent - Update event on the status of a connection with another host - DNSResolved, DNSFailed, Connected, ConnectFailed, Disconnected. Note that these events are only received for TCP connections. + +\subsection PlatformDevice Device management +The platform layer is responsible for initializing all devices used by the game - sound, OpenGL graphics, input devices and network interfaces. + +\subsection PlatformUtil Platform Utilities +Memory Manager - the V12 engine has its own memory management library (engine/platform/platformMemory.cc) designed to quickly allocate memory on cache aligned boundaries and perform debugging checks for overruns, multiple block frees and memory leaks. +Profiler - the profiler is a hierarchal time analysis toolkit that allows specific portions of the game to be timed - useful for finding performance bottlenecks. +Thread management - utility classes for creating threads, mutexes and semaphores. +Standard library functions - all standard library functions are redefined as dFuncname, for example sprintf becomes dSprintf, strcpy becomes dStrcpy, etc. + +\subsection PlatformNetwork Network functionality +The platform library has a common interface for opening and closing network ports, connecting to other hosts, translating network addresses and sending and receiving network data. Guaranteed and unguaranteed protocols are supported. + +\subsection PlatformGame GameInterface +The GameInterface class, defined in engine/platform/gameInterface.* is the interface through which the platform and libraries communicate with the game, as well as the main handler for journaling. All platform events are passed through GameInterface::processEvent, which in turn feeds them to separate virtual event handlers. One instance of this class (or a subclass) should exist in the program and a single global pointer named Game points to it. +*/ + +*/ +//------------------------------------------------------ +/*! \page Simulation Simulation + +\section SimOver Overview +The simulation of objects is handled almost entirely in the game portion of the engine example. All simulation object classes are derived from GameBase, which is a subclass of SceneObject. GameBase objects that wish to be notified of the passage of time can be added to one of the two process lists - the global server or global client process list, depending on whether the object is a server object or a client ghost. All objects in the process list are "ticked" once every 32 milliseconds. The ordering of the objects is determined by the GameBase::processAfter method - called if an object must be specifically processed at some time after another object (not necessarily immediately afterward). For example, a player mounted to a vehicle would be set to processAfter the vehicle, so that after the vehicle moved the player's position could be updated to the correct position on the vehicles new position. + +Game objects are updated in three separate functions, overridden from GameBase: GameBase::processTick, which takes a single Move structure as an argument (can be NULL) and advances the object in time by one 32 ms tick, GameBase::interpolateTick, which interpolates a client object backwards from the end of its current tick to the present time, and GameBase::advanceTime, which allows a client object to advance animations and effects by the full duration of the time event. + +Server side objects are only simulated on even tick boundaries, but client objects, in order to present a smooth view when the frame rate is high, are simulated after each time event. The processTick method is still only invoked on even tick boundaries, but at the end of the time advance, objects are essentially rewound by the time difference to the end of the tick. Also, client objects that need to animate only by the total elapsed time can do so in the GameBase::advanceTime function, which is only called once per time advancement. + +One item to note: if the control object is sent new object state from the server, it will be advanced by every Move the client has recorded that the server has not yet acknowledged - this is because the server has old data as far as the client is concerned, so the client skips forward through all the new data it knows about. + +\section SimDB Container database +The Container class maintains a database on client and server for objects positioned in the simulation. It supports a set of functions for quickly inserting, removing and moving objects in the world (Container::addObject, Container::removeObject, Container::checkBins), as well as query functions for line, box and polyhedron intersection tests (Container::castRay, Container::collideBox, Container::findObjects). +*/ + +//------------------------------------------- +/*! \page Console Console + +\section ConOverview Overview +The console library (engine/console) is a combined compiler and interpreter runtime that serves as the foundation for V12 applications. All GUIs, game objects and interface and game logic are handled through the console. The language itself is syntactically similar to a typeless C++, with some additional features that allow for easier mod development. Console scripts can be loaded via the exec() console command from the console window (brought up using the ~ key) or they can be loaded automatically from a mod via that mod's main.cs. + +\section ConRef Console language ref +The V12 console language scanner and parser were built using the tools lex and yacc. The scan and grammar files are engine/console/scan.l and engine/console/gram.y respectively. The grammar is shown in a somewhat more understandable way in the attached document. + +\subsection ConFunctions Functions +Console functions can be declared in either console scripts or in the C++ game/engine code. A simple script function declaration for example: + +Example.cs: + +\code +function helloWorld () +{ + echo("Hello World!"); +} +helloWorld(); +\endcode + +or, for a method on a class: + +\code +function SimObject::helloWorld(%this) +{ + echo("Hello World!"); + echo("Called on object: " @ %this); +} +$object = new SimObject(); +$object.helloWorld(); +\endcode + +Unlike in C++, the this variable is not implicit in the function declaration. To declare console functions in the game or engine code, the ConsoleFunction macro (declared in engine/console/console.h) should be used. + +Example.cc: + +\code +ConsoleFunction(helloWorld, void, 1, 1, "helloWorld()") +{ + Con::printf("Hello World!"); +} +\endcode + +The first argument to the macro is the name of the function as it will appear to scripts. Second is the return type, followed by the minimum and maximum number of arguments allowed to the function. Since the function name is always argument 0, 1 is minimum argument count allowed. Passing a 0 for max arguments will allow any number of arguments to the function. The last parameter is a usage string, which is displayed as an error message if the function is called with an incorrect number of arguments. + +Methods for classes can also be declared in C++, using the ConsoleMethod macro: + +\code +ConsoleMethod(SimObject, helloWorld, void, 2, 2, "object.helloWorld()") +{ + Con::printf("Hello World!"); + Con::printf("Called on object: %s", argv[1]); + Con::printf("Also called on object: %s", object->getName()); +} +\endcode + +In this case, the first argument is the name of the class, followed by the arguments to ConsoleFunction. These macros create a function declaration in C++ with several arguments defined: argc (integer), argv (array of strings), and, in the case of ConsoleMethod, object (SimObject *). + +\subsection ConClasses Classes, Objects and Namespaces +Declaring Console Classes +Objects in the scripting language are simply instances of C++ classes (derived from SimObject) declared in the game engine and processed with a special set of macros (declared in engine/console/consoleObject.h). The DECLARE_CONOBJECT(class_name) macro is placed inside the class definition and the IMPLEMENT_CONOBJECT (class_name) macro is placed in a linked source file. IMPLEMENT_CONOBJECT has several versions, depending on the type of class: + +IMPLEMENT_CONOBJECT - simple console object - no special network attributes. + +IMPLEMENT_CO_NETOBJECT - ghostable network object classs. Any object that will be ghosted from server to client needs to be declared as a NETOBJECT. See the section on the network layer for more details about NetObjects. + +IMPLEMENT_CO_DATABLOCK - The class is a datablock class. +There are 3 more IMPLEMENT_CONOBJECTs that deal specifically with network events. + +Every class declared as a Console class MUST declare a Parent typedef in its private member section referencing its parent SimObject class. This allows the console to properly determine the console object class hierarchy for method dispatch in the console. + +Example: + +\code +class SampleObject : public SimObject +{ + typedef SimObject Parent; +public: + DECLARE_CONOBJECT(SampleObject); + S32 someVariable; // signed integer variable +}; +IMPLEMENT_CONOBJECT(SampleObject); +\endcode + +Then in script: + +\code +function foo() +{ + %obj = new SampleObject(MySampleObject); + echo(%obj.getName()); +} +\endcode + +\subsection ConFields Adding class member fields +C++ data members of classes can be accessed from within the scripting language. When the game starts the Console calls AbstractClassRep::initialize() (defined in engine/console/consoleObject.cc), which assigns network ids to classes, links class hierarchies, and initializes field data for each class. To do this, each class with accessible data members declares a static member function called initPersistFields(). This function calls the addField static member function for each data member of the class: + +\code +void SampleObject::initPersistFields() +{ + Parent::initPersistFields(); // adds the parent class's fields as well + addField("someVariable", TypeS32, Offset(someVariable, SampleObject)); +} +\endcode + +The type must be properly specified, and the Offset macro can be used to determine the relative address of the data member in the class. Once this is defined, scripts can use the member field of the object directly: + +\code +function bar() +{ + %obj = new SampleObject(MySampleObject); + %obj.someVariable = 100; + echo(%obj.someVariable); +} +\endcode + +Field types are declared in engine/console/consoleTypes.h. New console data types can be added by adding a type to consoleTypes.h and calling Con::registerType(typeId, typeSize, getDataFunc, setDataFunc). See engine/console/consoleTypes.cc for examples of how types are defined. + +\subsection ConDynamic Dynamically defined fields +Member fields of objects can also be defined from within the script itself. For example: + +\code +function foo() +{ + %object = new SampleObject(); + %object.scriptVariable = "Hello World!"; + echo(%object.scriptVariable); +} +\endcode + +This script field is applicable only to that object instance. + +\subsection ConNamespace Namespaces +Namespaces (declared in engine/console/consoleInternal.h) are collections of class member functions. Every SimObject belongs to exactly one namespace. By default, an object belongs to the namespace that corresponds to its class - so an instance of GameBase will have the GameBase namespace. Each namespace has a parent, so methods can invoke parent class methods by calling Parent::function(args). New namespaces (not class-based) can be added in the engine via the Con::linkNamespaces(parentNamespace, newNamespace) function. Assigning the mNameSpace field of SimObject then assigns the new namespace to an object. For example, in GuiControl::onAdd() (engine/gui/guiControl.cc), if a control has a name its name is used as its namespace. This allows a named GUI control to have special behavior for clicks or actions. + +The ScriptObject class (defined in engine/console/scriptObject.cc) allows for the creation of "classes" within the scripting language: + +\code +new ScriptObject(MyObject) { + class = Bar; + superClass = Foo; +}; + +function Bar::doSomething(%this) +{ + echo("Hi!"); +} + +MyObject.doSomething(); +> Hi! + +function Foo::doSomething(%this) +{ + echo("Hi! Foo"); +} + +function Bar::go(%this) +{ + %this.doSomething(); + Parent::doSomething(%this); +} + +MyObject.go(); +> Hi! +> Hi! Foo +\endcode + +\subsection ConObject Objects +Every SimObject in the system can be addressed either by name or by id. So in the example above, MyObject.go() searches the object dictionary (engine/console/simBase.cc, engine/console/simManager.cc) for an object named MyObject and calls the go() method on that object. + +\subsection ConDatablock DataBlocks +Datablocks are special objects that are used to transmit static data from server to client. Datablocks are declared as followed: + +\code +datablock datablock_class (datablock_name) +{ + field1 = value; +}; +\endcode + +More information about datablocks can be found in the network programming section . + +\subsection ConPackage Packages +Packages are collections of functions that can be enabled and disabled at runtime. Package functions can override (redefine) the behavior of existing functions in the global state or in packages that have been activated earlier. Prior versions of a function can then be accessed using the Parent call. For example: + +\code +function foo() +{ + echo("foo!"); +} + +package SamplePackage +{ + +function foo() +{ + echo("Haha!"); + Parent::foo(); +} + +} + +foo(); +> foo! +ActivatePackage(SamplePackage); +foo(); +> Haha! +> foo! +\endcode + +Packages are useful for creating mods to games or specific game modes. + +\subsection ConVariable Variables +The console language supports global variables and local (function scoped) variables. Global variables are specified by a preceding $, and local variables by a % sign. Example: + +\code +$someGlobal = "This is some global."; + +function foo(%local1, %local2) +{ + %local3 = $someGlobal; +} +\endcode + +\subsection ConArray Arrays +The console language supports associative single- and multi-dimensional arrays. Arrays actually construct new variables with the names concatenated - so for example $array[10] is the same as $array10. Strings can be used as array indexes as well: $array["foo"] = 100;. Array dimensions are separated with commas inside the brackets - $array[1, 0] = 10; + +\subsection ConStringOp Special String Operators +There are several special operators for strings in the scripting language: + +$= Case insensitive string comparison. True if strings are equal. +!$= Negative case insensitive string comparison. True if strings are not equal +@ String concatenation operator: "Hello " @ "World!" == "Hello World!" +TAB String concatenation with a tab. "Hello" TAB "World!" == "Hello\tWorld!" +NL String concatenation with a newline. "Hello" NL "World!" == "Hello\nWorld!" +SPC String concatenation with a space. "Hello" SPC "World!" == "Hello World!" + +\section ConCompiler Compiler +Scripts are executed in a two step process: First the script is compiled into a tokenized instruction stream (engine/console/compiler.*), then the instruction stream is processed using the compiled evaluator (engine/console/compiledEval.cc). + +\section ConDebugger Debugger +The console supports remote debugging via another instance of the V12 example program. In the game instance to be debugged, debugger port and password must be set using the dbgSetParameters(port, password); Then, in the instance to be used as the debugger, the guis and scripts in base/debugger/ must be loaded. More info soon + +\section ConInterface Interfacing with C++ code +The C++ game and engine code can be called from the scripts as described above, and the game code can also call into script using the console execute and evaluate functions: + +\code +// simple execute of a console function using argv array: +const char *execute(S32 argc, const char* argv[]); + +// simple execute of a console function, without stuffing an array +const char *executef(S32 argc, ...); + +// execution of a method on a SimObject using argv array: +// first param is func name, second param MUST be empty +// also, MUST have at least those two params +const char *execute(SimObject *, S32 argc, const char *argv[]); + +// execution of a method on a SimObject without stuffing an array +// first param is funcName, remaining params are args +const char *executef(SimObject *, S32 argc, ...); + +// evaluation of an arbitrary console command script: +const char *evaluate(const char* string, bool echo, const char *fileName); + +// evaluation of a formatted (ala printf) command string: +const char *evaluatef(const char* string, ...); +\endcode + +Examples: + +\code +SimObject *mySimObject = new SimObject; + +Con::executef(mySimObject, 4, "doSomething", Con::getIntArg(20), "Bye", "Hi"); +Con::evaluatef("mySimObject.doSomething(%d,\"%s\",\"%s\");", 20, "Bye", "Hi"); + +char *argv[5]; +argv[0] = "doSomething"; +argv[1] = NULL; +argv[2] = Con::getIntArg(20); +argv[3] = "Bye"; +argv[4] = "Hi"; + +Con::execute(mySimObject, 5, argv); +\endcode + +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. + +\section SimBase SimBase +SimBase (engine/console/simBase.*) defines the foundation SimObject classes that form the basis of the simulation engine. + +SimObject is the base class for all objects that the console language can create and manipulate. All game classes (Player, InteriorInstance, Terrain, etc.) and GUI classes (GuiCanvas, GuiControl, etc). are all derived from SimObject. SimObject maintains the list of dynamic fields, has name and id properties, and can register itself with a global object manager. + +SimSet is a simple collection of SimObjects. The set has console methods for adding and removing objects and iterating through the set. + +SimGroup is a derivative of SimSet that "owns" the objects in its collection. When a SimGroup object is destroyed, it destroys all of its members. GuiControl is derived from SimGroup - thus making the Gui a hierarchal set of objects. + +SimEvent is a special class objects can use to send time-delayed messages to objects. + +\section SimManager SimManager +SimManager (engine/console/simManager.cc) is a collection of functions for managing all of the objects and events in the simulation. Objects are collected in a hierarchy of SimGroups and can be searched for by name or by object id. +Object Persistence and Inspection +Objects in the V12 can be saved to a script file using the SimObject::save() method. This basically dumps the current state of all the object's registered fields and dynamic fields, as well as, in the case of a SimGroup, all of that object's sub objects. GUIs and missions are examples of how the engine's editors save objects. + +Objects' fields can also be modified in-game using the inspector, a GUI window that lists out the fields of an object and allows those fields to be changed and saved. Before new field values are to be saved to an object its onPreApply method will be called, then the fields will be changed, then the object's onPostApply method will be invoked. +Useful Console Commands +There are several console commands that are useful to get an idea of what's going on in the system: trace(true); turns on console trace output, trace(false); to disable it. Tree() displays a graphical hierarchy and object inspector for all objects currently registered in the system. SimObject::dump() dumps a list of all console commands and instance variables on an object, and SimSet::listObjects() lists all of the objects in a set. +*/ + +//----------------------------------------------------------- +/*! \page FileIO Files, Streams and the Resource Manager + +\subsection FileOverview FileIO Files, Streams and the Resource Manager +The V12 engine uses many game resources - terrain files, bitmaps, shapes, material lists, fonts and interiors are all examples of game resources. In order to manage the large number of game resources effectively and provide a common interface for loading and saving resources, the V12 uses the ResourceManager (engine/core/resManager.*). Resources have the special property that only one instance of a resource will ever be loaded at a time. Resource objects are reference counted so that when a second request is made for the same resource, the original loaded instance is returned. The resource manager also defines a resource template class that acts as a transparent pointer to various types of game resources. + +\subsection FilePaths Searchable Paths +Documentation in developement + +\subsection FileVolumes Volume Files +Documentation in developement +\subsection FileResourceObject ResourceObject and template class Resource +Documentation in developement +*/ + +//------------------------------------------------------------- +/*! \page InputModel Input Model + +\section InputOverview Overview +Input Events come from the host OS/platform, are translated in the platform layer and then posted to the game. By default the game checks the input event against a global action map (which supercedes all other action handlers). If there is no action specified for the event, it is passed on to the GUI system. If the GUI does not handle the input event it is passed to the currently active (non-global) action map stack. +Example: the user presses the ~ (tilde) key, which is bound in the global action map (in example/client/scripts/default.bind.cs) to toggleConsole. This causes the console function associated with the bind to be executed, which in this case is toggleConsole, resulting in the console output window being shown. If the key had not been bound in the global map, it would have passed to the first gui that could have handled it, and if none did, it would pass to any game actions that were bound to that key. + +\section InputPlatform Platform Input +Platform specific code translates Win32/Xwindows/Mac events into uniform V12 input events. These events are posted into the main application event queue via Game->processEvent. The default behavior for the GameInterface class (engine/platform/gameInterface.*) is to pass all input events to Game->processInputEvent, which in the example V12Game (engine/game/V12Game.h, engine/game/main.cc) calls ActionMap::handleEventGlobal, followed by Canvas->processInputEvent (if not handled by the global map), and if neither of those handles it, passes it to ActionMap::handleEvent. + +\section InputAction Action Maps +Action maps map platform input events to console commands. Any platform input event can be bound in a single generic way - so in theory the game doesn't need to know if the event came from the keyboard, mouse, joystick or some other input device. This allows users of the game to map keys and actions according to their own preferences. + +\subsection InputBind ActionMap::bind(device, inputName, functionName) +Bind - calls the specified function name when the named input for the particular device changes state. The function named should take a single argument, which is the current numeric state value of the named input. For button inputs (keys, mouse and joystick buttons), a state value of 1 indicates that the key is depressed. So if the user presses and releases the w key (which in the example is bound to moveforward), the moveforward function will be called twice, first with a 1.0 as the value and then, upon key release with a value of 0. Non-binary devices (joystick and mouse axis) are called with values depending on the device type. + +\subsection InputBindCmd ActionMap::bindCmd(device, inputName, downScript, upScript) +BindCmd - executes the console script downScript when the specified button input is depressed and upScript when it is released. Somewhat more useful than bind() in some cases. + +\subsection InputGlobalMap Global Action Map +There is one defined ActionMap object that is processed first for all events called GlobalActionMap. + +\subsection InputMapStack Action Map Stack +Game action maps are arranged in a stack for processing - so individual parts of the game can define specific actions - for example when the player jumps into a vehicle it could push a vehicle action map and pop the default player action map. + +\subsection InputModKeys Modifier Keys +The inputName of an action can be modified by one of the three modifier keys - alt, shift and control. For example, the fullscreen/window toggle is bound to "alt enter". If an action is called with a modifier, releasing the modifier key(s) will not cause the break event to fire - only when the key itself is released. + +\section InputGui GUI Event passing +See the section below on the GUI system +*/ +//---------------------------------------------------- +/*! \page Graphics Graphics + +\section GraphicsOverview Overview +The V12 Engine does not implement its own graphics rasterization layer. OpenGL was chosen as the graphics API for the V12 to primarily for its cross-platform nature and ease-of-use. The V12 includes a utility library called dgl (engine/dgl) that extends OpenGL to support higher level primitives and resources. + +\section GraphicsInit Platform initialization +The platform layer is responsible for initializing the OpenGL state. For PlatformWin32 this can include loading a DLL that converts OpenGL calls to Direct3D (OpenGL2D3D.DLL). + +\section GraphicsDgl Dgl +Dgl is a collection of utility classes and functions that add support for complex primitives/resources like fonts and bitmaps as well as add simple functions for more easily managing textures and 2D rasterization. + +\subsection GraphicsTexture The Texture Manager +Dgl includes a texture manager (engine/dgl/gTexManager.*) that tracks the loading and unloading of all textures in the game. When the game requests a texture, it uses the TextureHandle class - which acts as a sort of special resource handle for textures in the game. Only one instance of a texture is ever loaded at once, and after load is handed off to OpenGL. When the game switches graphics modes or video devices, the Texture Manager can transparently reload and re-download all the game's textures. + +\subsection GraphicsPrimitive Primitive support +GFont - fonts in the V12 are alpha textures created by the platform layer from OS dependent outline fonts. The font class is defined in engine/dgl/gFont.* + +GBitmap - the V12 supports several bitmap file types - PNG, JPEG, GIF, BMP and the custom Bm8 format - an 8-bit color quantized texture format used to cut texture memory overhead. The Bitmap class is defined in engine/dgl/gBitmap.* + +MaterialList - a material list is a resource that manages a list of bitmaps. It is used for shapes and interiors that have more than one texture. + +\subsection GraphicsPrimRender Primitive rendering +The dgl 2D render support functions (declared in engine/dgl/dgl.h) support a wide variety of common 2D rendering primitives. Bitmaps (loaded as textures) can be rendered via the dglDrawBitmap, dglDrawBitmapStretch, dglDrawBitmapSR (sub-region), and dglDrawBitmapStretchSR functions. Text fonts can be rendered using dglDrawText and dglDrawTextN. Dgl also supports drawing of lines, rectangles and filled rectangles. + +Unlike default OpenGL, the screen coordinate space set up for 2D rendering in the V12 is the traditional 2D scheme whereby the coordinate 0,0 is in the upper left corner of the screen and +Y goes down the screen. This requires (in the case of 3D) calling dglSetViewport rather than glViewport. + +For 2D rendering, dgl viewport management is simplified by the dglSetClipRect function (defined in engine/dgl/dgl.cc). + +\section Graphics3DRender 3D Rendering +There are several key differences in how the V12 does rendering from the default OpenGL. First, the coordinate system is set up to look down the +Y axis instead of -Z. This means dgl replaces the call to glFrustum with the dglSetFrustum function (defined in engine/dgl/dglMatrix.cc). Also, all V12 matrices are organized in standard C array form - with the second element in the array corresponding to the first row, second column. This is the opposite of OpenGL, so dgl supplies alternates to glLoadMatrix and glMultMatrix, appropriately named dglLoadMatrix and dglMultMatrix respectively. + +3D points can be converted to 2D screen points using the dglPointToScreen function, and a measure of projected screen size of an object can be determined using the dglProjectRadius function. +*/ + +//---------------------------------------------------------------- +/*! \page GUI The Graphical User Interface (GUI) + +\section GUIOver Overview +The GUI library manages the user interface of V12 applications. Based very loosely on the NEXTSTEP interface class library, the GUI library is designed specifically for the needs of game UI development. Some important classes are: + +GuiCanvas (engine/gui/guiCanvas.*) - The Canvas object, a singleton instance of the GuiCanvas class, is the root of the currently active GUI hierarchy. It is responsible for processing and dispatching mouse and keyboard events, managing update regions and cursor handling, and calling the GuiControl render methods when it is time to draw the next frame. The GuiCanvas keeps track of a stack of content controls - separate hierarchies of GuiControls that render from bottom to top. The main content control is a screen in the shell, and can be covered by any number of floating windows or dialogs. The root of each content control hierarchy is sized to cover the entire canvas, so generally dialogs are composed of a content control containing a dialog control. + +GuiControlProfile (engine/gui/guiTypes.*) - The GuiControlProfile class instances maintain common instance data across a set of controls. Common information such as font face, colors, bitmaps and sound data are all stored in instances of GuiControlProfile, so that they don't need to be replicated on each control. + +GuiControl (engine/gui/guiControl.*) - The GuiControl class is the root class for all the GUI controls in the system. GuiControl is derived from SimGroup and can contain any number of child GUI controls. Each GuiControl maintains a bounding rectangle in the coordinate system of its parent control. GuiControl has virtual methods for processing input events (onMouseDown, onMouseUp, onMouseEnter, onMouseLeave, onMouseMove, onMouseDragged, onRightMouseDown, onKeyDown, onKeyUp,onKeyRepeat), methods called by the Canvas for rendering (onPreRender, onRender), methods to control the lock focus of the mouse (mouseLock, mouseUnlock), coordinate conversion methods (localToGlobalCoord, globalToLocalCoord), and automatic sizing behavior when its parent control is resized (for example, when the screen resolution changes). When a control is made visible, it (if not already loaded) loads the data associated with its GuiControlProfile. + +\section GuiInput Input Event Processing in the GUI system +Input events are first processed for dispatch in GuiCanvas::processInputEvent. Depending on the type of input event (keyboard or mouse), the Canvas processes the event in different ways. + +For keyboard events, the Canvas maintains an instance variable call the first responder (mFirstResponder). The first responder is essentially the control that has keyboard focus - for example a text field that the user is typing into or a button that the user has tabbed to. For any keyboard event, the first responder has the first chance to process it. If it does not process the event it passes it up to its parent class and then in GuiControl finally to its parent control. If the keyboard event is not handled by any control in the responder chain (from first responder up to the root content control), the Canvas checks to see if it is a special key - tab and shift-tab set the first responder to the next and previous controls in the tab list respectively. If it is not tab or shift-tab, the Canvas checks to see if the key is in the accelerator map for any of the controls visible (for example an OK button mapped to the enter key). If it is not mapped, the Canvas passes back false, signifying that the event should be processed by the action map handler. + +If the event is a mouse event and the cursor is on, it is handled by the GUI system. Each mouse event can potentially generate several virtual method calls to controls. For example, moving the mouse cursor from one control into another will cause the first control to receive an onMouseLeave method call, followed by an onMouseEnter method call to the new control and finally an onMouseMove to that control as well. GuiControls can lock the mouse focus on the canvas so that only that control receives mouse method invocations - for example the GuiButtonCtrl control locks the mouse on mouse down and unlocks it on mouse up so that even if the user moves the cursor outside the control, the button still maintains focus and can re-highlight when the cursor is moved back inside. The GuiControl virtual method pointInControl by default returns true if the cursor is inside the bounding rectangle of the control. Control subclasses with non-rectangular shapes can override this method to provide controls with irregular mouse boundaries. + +\section GuiRender Control Rendering in the GUI system +The Canvas object is the root of the control rendering hierarchy and is responsible for the rendering process. When the game receives a time event from the platform layer it advances the state of the simulation and then instructs the canvas to render itself. + +Before actually doing any rendering the Canvas instructs each control on the screen to onPreRender() itself, allowing each control to determine what if anything needs to be rendered on that control. The canvas, in order to render more quickly, maintains a set of "dirty" bounding boxes signifying areas of the screen that need to be repainted - a button may change, for example, if the mouse has just been pressed inside it, causing it to glow in the depressed state. During or before the onPreRender, a control can call the setUpdate() method on itself signifying that it needs to be repainted. Because the V12 engine works with page flipping devices and there may be as many as three separate buffers with old screen data, the Canvas maintains three dirty rectangles representing the dirty state of each of the (up to) three buffers. + +After onPreRender, the Canvas renders the hierarchy of controls by telling the root of each layer (content control) to onRender() itself, and passing in a rectangle of the visible area of that control, which is initially set to the dirty area of the screen. When a control renders it can optionally call renderChildControls with this visible rectangle which will clip the rectangle to the bounds of each of its child controls, and if there is any area visible, will call onRender() on the child with the clipped rectangle as the visible area. + +\section GuiConsole The GUI system and the Console +The GUI system is designed to work tightly with the Console language. The GuiControl class has an instance variable called mConsoleVariable which is the name of a global console variable that will, if set, reflect the state of that control in a control-dependant way. For example, a variable that is mapped to a checkbox control will have the value of 1 when the control is checked and 0 when it is not. Each GuiControl also has an instance variable called mConsoleCommand and mAltConsoleCommand that get executed in different cases depending on the control - in the case of a GuiButton the mConsoleCommand script gets evaluated when the button is clicked (in GuiControl::onAction()). + +Another way the GUI system interacts with the console is via Console namespaces - when you name a control (like MainMenuQuitButton) that control name is registered as a namespace whose parent is the control's class, and special console methods can then be invoked from code on that control - in the case of a button the buttons onAction console method will be called when the button is pressed. Custom controls can be built in this way to execute several different commands with custom arguments. +*/ + +//------------------------------------------------------- +/*! \page Render3D 3D Rendering + +\section Render3DOver Overview +The V12 library has a modular, extensible 3D world rendering system. Game subclass(es) of the GuiTSCtrl (defined in gui/guiTSControl.*) override the GuiTSCtrl::processCameraQuery and GuiTSCtrl::renderWorld methods to define the camera orientation/fov and draw the 3D scene using OpenGL drawing commands respectively. The GuiTSCtrl class manages the setting up the viewport, modelview matrix and projection matrix. The V12 example code GameTSCtrl class calls the global functions GameProcessCameraQuery and GameRenderWorld functions (defined in engine/game/game.cc). The GameProcessCameraQuery function returns the viewing camera of the current control object (the object in the simulation that the player is currently controlling) and then GameRenderWorld calls the client scene graph object to render the world. + +\section Render3DSceneGraph SceneGraph +The scene graph library (engine/sceneGraph) is, on the client, responsible for traversing the world scene and determining which objects in the world should be rendered given the current camera position, and on the server, determines what objects should be sent to each client based on that client's position in the world. + +The world in the SceneGraph is divided into zones - volumes of space bounded by solid areas and portals. The outside world is a single zone, and interior objects can have multiple interior zones. The SceneGraph::findZone() function finds the zone of a given 3D point and which SceneObject owns that zone. The SceneGraph::rezoneObject() function determines which zone or zones contain a SceneObject instance. At render time - SceneGraph::renderScene - the scene is traversed starting from the zone that contains the camera, clipping each zone's objects to the visible portal set from the zones before it. Scoping of network objects is performed in SceneGraph::scopeScene. + +The scene graph traversal is complicated by transform portals. Transform portals are objects like mirrors or teleporters through which the world can be viewed using a different transform than the normal camera transform. When SceneGraph::buildSceneTree() encounters an object with a transform portal, it constructs a new SceneState object for rendering that portal's contents. + +Every renderable world object in the scene derives from the SceneObject base class. As the world is traversed, visible objects are asked to prepare one or more SceneRenderImage objects (in SceneObject::prepRenderImage) that are then inserted into the current SceneState via SceneState::insertRenderImage. Render images are then sorted based on translucency and rendered from SceneObject::renderObject. This system allows, for example, an interior object with multiple translucent windows to render the building first, followed by other objects, followed by the building's windows. Objects can insert any number of images for rendering. + +\section Render3DTerain Terrain +The terrain library (engine/terrain) is the home for objects that render the outside world, including instances of the Sky, TerrainBlock and WaterBlock classes. The Sky object renders the outside sky and cloud layers and maintains the visible distance and fog distance settings for the world. The sky also tracks vertical fog layers and installs them into the SceneGraph for rendering. + +The TerrainBlock class (declared in engine/terrain/terrData.h) manages a single 256x256 infinitely repeating block of heightfield terrain. Terrain heightfield data is stored and loaded using the TerrainFile resource class (Resource) so that a single terrain data file can be shared between server and client, when both are on the same execution instance. The TerrainRender static class is used by TerrainBlock instances for rendering. The TerrainRender::renderBlock function renders the current repeating block of terrain. + +The terrain is textured by software blending base material textures into new material textures and then mapping those across 16 or more terrain squares based on the distance from the square. The Blender class (engine/terrain/blender.*) performs the blending of terrain textures and includes a MMX assembly version to speed the process (x86 architectures only). + +The WaterBlock class manages a single block of water, which may or may not be infinitely repeating. Water is dynamically detailed based on distance, so nearby water is more highly tessellated. Though the surface of a water block is rectangular, the actual coverage of the water area can be set to seed fill from a point on the surface, allowing the water to fill a mountain crater, for example, without leaking outside the corner edges. + +\section RenderInterior Interior +The Interior library (engine/interior) manages the rendering, collision and IO for interior objects. The InteriorInstance SceneObject class manages a single interior. The InteriorResource class manages the data associated with one definition of an interior, multiple instances of which may exist at any one time. Interiors manage zones for the scene graph, and may have subobjects that, for example, render a mirrored view (MirrorSubObject). The InteriorLMManager class manages lightmaps for all currently loaded interiors - sharing lightmaps among instances where possible. + +Interior resources are built and lit by the morian interior importer. The source files are just Quake-style .map files - lists of convex physical "brushes" that define the solid areas of the interior. Special brushes are used to define zone portal boundaries and objects such as doors and platforms. + +\section Render3Space 3Space (TS) +The 3Space library (engine/ts) manages the display and animation of shape models in the world. The 3Space shape resource class TSShape can be shared between multiple TSShapeInstance instances. The TSShape class manages all the static data for a shape - mesh data, animation keyframes, material lists, decal information, triggers and detail levels (for dynamically detailed shapes). + +The TSShapeInstance class manages animation, rendering and detail selection for an instance of a shape. The TSShapeInstance class uses the TSThread class to manage one of the concurrently running animations on an instance. The TSShapeInstance::addThread() method initializes a new thread on a shape instance, and TSShapeInstance::setSequence() sets an animation sequence for a given thread. Each thread can be individually advanced in time, or can be set on a time scale that is used when all threads are advanced in TSShapeInstance::advanceTime. A thread can also manage transitions between sequences with the TSShapeInstance::transitionToSequence method. + +TSShape animation sequences can be composed of node/bone animation (for example, joints in an explosion), material animation (a texture animation on an explosion) and mesh animation (a morphing blob - note most mesh animations can be accomplished with node scale and rotation animations). Animations can also contain visibility tracks so that some meshes in the shape are not visible until an animation is played. +*/ + +//------------------------------------------------------ +/*! \page Networking Networking + +\section NetOver Overview +The V12 was designed from the foundation to offer robust client/server network simulation support. Performance over the internet drove the design for the networking model. The V12 attempts to deal with three fundamental problems of network simulation programming - limited bandwidth, packet loss and latency. For a more detailed, if somewhat outdated, description of the V12 network architecture, see "The Tribes II Engine Networking Model" paper by Tim Gift and Mark Frohnmayer and the accompanying PowerPoint slides. + +An instance of the V12 example can be set up as a dedicated server, a client, or a client and server both. If the game is a client and server both, it still behaves as a client connected to a server - instead of using the network, however, the NetConnection object has a short-circuit link to another NetConnection object in the same application instance. + +Bandwidth is a problem because in the large, open environments the V12 allows, and with the large number of clients the V12 supports (up to 128 per server), potentially many different objects can be moving and updating at once. The V12 uses three main strategies to maximize available bandwidth. First, prioritize data - send updates to what is most "important" to a client at a greater frequency than update data that is less important. Second, send only data that is necessary - using the BitStream class, only the absolute minimum number of bits needed for a given piece of data can be sent. Also, when object state changes, the V12 only sends the part of the object state that changed. Last, the V12 caches common strings (NetStringTable) and data (SimDataBlock) so that they need only be transmitted once. + +Packet loss is a problem because the information in lost data packets must somehow be retransmitted, yet in many cases the data in the dropped packet, if resent directly, will be stale by the time it gets to the client - for example, suppose that packet 1 contains a position update for a player and packet 2 contains a more recent position update for that same player. If packet 1 is dropped but packet 2 makes it across the engine shouldn't resend the data that was in packet 1 - it is older than the version that was received by the client. In order to minimize data that gets resent unnecessarily, the engine classifies data into four groups: + +1. Unguaranteed Data (NetEvent) - if this data is lost, don't re-transmit it. An example of this type of data could be real-time voice traffic - by the time it is resent subsequent voice segments will already have played. +2. Guaranteed Data (NetEvent) - if this data is lost, resend it. Chat messages, messages for players joining and leaving the game and mission end messages are all examples of guaranteed data. +3. Most-Recent State Data (NetObject) - Only the most current version of the data is important - if an update is lost, send the current state, unless it has been sent already. +4. Guaranteed Quickest Data (Move) - critical data that must get through as soon as possible. + +Latency is a problem in the simulation because the network delay in information transfer (which, for modems, can be up to a quarter of a second or more) makes the client's view of the world perpetually out-of-sync with the server. Twitch FPS games, for which the V12 was initially designed, require instant control response in order to feel anything but sluggish. Also, fast moving objects can be difficult for highly latent players to hit. In order to solve these problems the V12 employs several strategies: + +1. Interpolation is used to smoothly move an object from where the client thinks it is to where the server says it is. +2. Extrapolation is used to guess where the object is going based on its state and rules of movement. +3. Prediction is used to form an educated guess about where an object is going based on rules of movement and client input. + +The network architecture is layered: at the bottom is the OS/platform layer, above that the notify protocol layer, followed by the NetConnection object and event management layer. The following sections explain how each layer addresses some or all of the fundamental network simulation problems. + +\section NetPlatform Platform Networking Layer (TCP/UDP) +The platform library provides the interface between the game engine and the OS dependent network functionality. The platform library's Net interface contains functions for opening reliable and unreliable communication sockets, converting between string and numeric network addresses and sending and receiving data. + +The Net::openPort function opens an unreliable socket, of which only one is allowed per application instance. Net::sendto sends an unreliable datagram to the specified NetAddress. Net::openListenPort opens a reliable socket for incoming TCP connections. Net::openConnectTo begins the process of asynchronously connecting to a remote TCP socket. Net::sendtoSocket sends data over an established TCP connection. Net::process processes the platform network layer, possibly generating network related events that are then posted into the simulation via GameInterface::processEvent. +Connection Negotiation +The negotiation of a game network connection is not actually a part of the network class tree in the V12 - instead a set of functions, declared in engine/game/netDispatch.cc perform this service. The function V12Game::processPacketReceiveEvent is the main dispatch function for incoming network packets. + +The first step of the connection process is the console function connect(), which initiates a connection attempt by sending a connect challenge request packet to the server from sendConnectChallengeRequest. + +The server, in function handleConnectChallengeRequest, may issue the client a connect challenge response, which the client will process in handleConnectChallengeResponse. The client will in turn issue a connect request (sendConnectRequest) with the challenge information it received from the server. The server processes this message in handleConnectRequest. If the server decides to accept the request, it issues a sendConnectAccept back to the client and constructs a NetConnection object on the server to handle that client. The client, in handleConnectAccept creates a complementary NetConnection object to manage the client side of the connection. The dispatchCheckTimeouts function periodically checks if a connection request or challenge has been waiting too long and reissues the request if it has. + +\section NetProtocol ConnectionProtocol +Once a connection has been established, the function of the ConnectionProtocol class is to provide a common low-level mechanism for supporting the delivery of the four fundamental types of network data in the V12. The ConnectionProtocol abstract base class implements a sliding window connected message stream over an unreliable transport (UDP). Rather than supporting guaranteed messages directly, the ConnectionProtocol class implements a notify protocol. Each packet sent is prepended with a message header containing tracking information, including what packets the other end of the connection has received or were dropped in transit. When a ConnectionProtocol instance determines that a packet it sent has been either received or dropped, it calls ConnectionProtocol::handleNotify. Notifies are always delivered in the order packets were sent - so for every packet sent through a ConnectionProtocol object, eventually a notification of successful (ack) or unsuccessful (nack) delivery will be executed. + +Because the base network protocol exports the inherently unreliable nature of the network to the simulation, at a higher level the V12 can directly support different types of data guarantee: for unguaranteed data, if it is nacked, there is no need to resend it. For guaranteed data, if it is nacked, the engine queues it up for resend (NetConnection::eventPacketDropped). If the data is most recent state data and the packet is nacked and that object's state hasn't been subsequently changed and resent, queue the data up for resend (NetConnection::ghostPacketDropped). If the data is set for quickest possible delivery, continue sending the data with every packet until a packet containing the data is acked (GameConnection::readPacket). + +\section NetConnection NetConnection +The NetConnection class is derivative from both SimGroup and ConnectionProtocol, and is responsible for managing the data streaming between client and server. The NetEvent class encapsulates the guaranteed and unguaranteed message delivery types and the ghost management portion of the NetConnection class handles state updates of world objects from server to client. The V12 example game-specific sublclass of NetConnection is GameConnection and handles transmission of game specific data such as player moves. + +The NetConnection class sends packets of a fixed size in a regular stream between the client and server. When a message is posted for transmission, it is aggregated with other messages and sent based on the packet rate and packet size settings for that connection. + +\section NetBitstream The BitStream +The BitStream class is a utility class used to pack data for transmission. BitStream has methods for reading and writing variable-sized integers (BitStream::readInt, BitStream::writeInt), floats, vectors, Huffman-coded strings and bits. + +When a NetConnection instance determines it is ready to send a packet across the network (NetConnection::checkPacketSend), it allocates a BitStream and calls NetConnection::writePacket with the stream. When a packet is received it is processed through the corresponding NetConnection::readPacket function. + +\section NetEvents Network Events +The NetEvent class provides a foundation for guaranteed, guaranteed ordered and unguaranteed message transmission. NetEvent uses the same class instance creation mechanism as the console, but rather than instantiating by name, NetEvents use a class id, assigned when the console initializes. + +A simple network event: +\code +class SimpleMessageEvent : public NetEvent +{ + 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 *connection) + { Con::printf("RMSG %d %s", connection->getId(), msg); } + + DECLARE_CONOBJECT(SimpleMessageEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(SimpleMessageEvent); + +ConsoleMethod(NetConnection, sendMsg, void, 3, 3, "con.sendMsg(messageString)") +{ + (NetConnection *) object)->postNetEvent(new SimpleMessageEvent(argv[2])); +} +\endcode + +Some items to note - events have virtual methods to pack and unpack themselves into the network packet BitStream. If the read and write methods don't match in terms of what they read and write into the stream, serious network errors can occur. The client and server should gracefully disconnect in these cases, but the errors themselves can be very difficult to track down. If the DEBUG_NET macro is defined, a special key will be written into the packet stream after each event and object update, and the system will assert immediately when it detects that this problem has occurred. + +NetEvent instances may be unpacked out of order (if, for example there was a dropped packet), so, for guaranteed ordered events, the process function will not be called until the previous events have been processed. The NetEvent::write function is specifically for demo recording - all as yet unprocessed events on the client side of a connection are written using this method. + +All network events must use the DECLARE_CONOBJECT macro in the class definition, and must use the IMPLEMENT_CO_NETEVENT_V1 macro outside the class definition. There are two special forms of IMPLEMENT_CO_NETEVENT: IMPLEMENT_CO_CLIENTEVENT_V1, which specifies an event that can only travel from the server to the client, and IMPLEMENT_CO_SERVEREVENT_V1 which specifies an event that can only travel to the server. + +\section NetGhost Network Ghosts and Scoping +The NetObject class is a derivative of SimObject that can replicate (ghost) itself across a network connection. All world object classes are subclassed from NetObject (the superclass of SceneObject). In order to best utilize the available bandwidth, the NetConnection attempts to determine which objects are "interesting" to each client - and among those objects, which ones are most important. If an object is interesting to a client it is said to be "in scope" - for example, a visible enemy to a player in a first person shooter would be in scope. + +Each NetConnection object maintains a scoping object - responsible for determining which objects are in scope for that client. Before the NetConnection writes ghost update information into each packet in NetConnection::ghostWritePacket, it calls the scope object's onCameraScopeQuery function which performs two services: first, it determines which objects are "in scope" for that client and calls NetConnection::objectInScope for each object on that client. Second, the onCameraScopeQuery call fills in the CameraScopeQuery structure which is then used to determine the priority of object updates. + +The default NetObject::onCameraScopeQuery function scopes everything in the world, but the V12 game example overrides this in ShapeBase::onCameraScopeQuery. ShapeBase calls the server SceneGraph::scopeScene function to traverse the scene from the client's point of view and scope all potentially visible objects. Each scoped object that needs to be updated is then prioritized based on the return value from the NetObject::getUpdatePriority function, which by default returns a constant value. This function is overridden in ShapeBase::getUpdatePriority to take into account the object's distance from the camera, its velocity perpendicular to the view vector, and other factors. + +Rather than always sending the full state of the object each time it is updated across the network, the V12 supports only sending portions of the object's state that have changed. To facilitate this, each NetObject can specify up to 32 independent sub-states that can be modified individually. For example, a player object might have a movement state, detailing its position and velocity, a damage state, detailing its damage level and hit locations, and an animation state, signifying what animation, if any, the player is performing. Each state data group is assigned a bit position in the class. When an object's state changes, the object notifies the network system with the NetObject::setMaskBits function. When the object is to be written into a packet in NetObject::packUpdate, the object's current state mask is passed in. The object's state mask is NOT written into the packet directly - it is the responsibility of the pack function to accurately encode which states are updated. + +Initially an object's state mask is set to all 1's - signifying that all the object's states need to be updated. + +An example NetObject: + +\code +class SimpleNetObject : public NetObject +{ +public: + char message1[256]; + char message2[256]; + enum { + Message1Mask = (1 << 0), + Message2Mask = (1 << 1), + }; + 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!"); + } + 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; + } + 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); + } + } + void setMessage1(const char *msg) + { + setMaskBits(Message1Mask); + dStrcpy(message1, msg); + } + void setMessage2(const char *msg) + { + setMaskBits(Message2Mask); + dStrcpy(message2, msg); + } + DECLARE_CONOBJECT(SimpleNetObject); +}; + +IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject); + +ConsoleMethod(SimpleNetObject, setMessage1, void, 3, 3, "obj.setMessage1(msg)") +{ + ((SimpleNetObject *) object)->setMessage1(argv[2]); +} + +ConsoleMethod(SimpleNetObject, setMessage2, void, 3, 3, "obj.setMessage2(msg)") +{ + ((SimpleNetObject *) object)->setMessage2(argv[2]); +} +\endcode + +\section NetGameConnection GameConnection, Moves and the Control Object +The GameConnection class is the game-specific subclass of NetConnection. Applications can subclass NetConnection to directly write and read data from packets, as well as hook into the notify mechanism. The NetConnection::allocNotify function is called at the beginning of a packet write and is used to allocate a NetConnection::PacketNotify structure. This structure is used to store information about the data written into the network packet. When the packet is either acked or nacked, this notify structure is passed into the NetConnection::handleNotify function. Subclasses of NetConnection can subclass the PacketNotify structure and override the allocNotify method to add custom data to the packet tracking record. + +The GameConnection in the V12 example introduces the concept of the control object. The control object is simply the object that the client associated with that network connection controls. By default in the example the control object is an instance of the Player class, but can also be an instance of Camera (when editing the mission, for example). + +The V12 example uses a model in which the server is the authoritative master of the simulation. To prevent clients from cheating, the server simulates all player moves and then tells the client where his player is in the world. This model, while secure, can have problems - if the network latency is high, this round-trip time can give the player a very noticeable sense of movement lag. To correct this problem, the example uses a form of prediction - it simulates the movement of the control object on the client and on the server both. This way the client doesn't need to wait for round-trip verification of his moves - only in the case of a force acting on the control object on the server that doesn't exist on the client does the client's position need to be forcefully changed. + +To support this, all control objects (derivative of ShapeBase) must supply a writePacketData and readPacketData function that send enough data to accurately simulate the object on the client. These functions are only called for the current control object, and only when the server can determine that the client's simulation is somehow out of sync with the server. This occurs usually if the client is affected by a force not present on the server (like an interpolating object) or if the server object is affected by a server only force (such as the impulse from an explosion). + +The Move structure is a 32 millisecond snapshot of player input, containing x, y, and z positional and rotational changes as well as trigger state changes. When time passes in the simulation moves are collected (depending on how much time passes), and applied to the current control object on the client. The same moves are then packed over to the server in GameConnection::writePacket, for processing on the server's version of the control object. + +\section NetDatablocks Datablocks +Datablocks (derivate of SimDataBlock) are used in the network system to store common instance data for objects. For example, a datablock may store animation data, model information, physical movement properties, etc, all of which are shared across a set of common objects. All declared datablocks are sent to clients upon connection as guaranteed events (SimDataBlockEvent), and can then be referenced and sent as part of the initial ghost update. An advantage of datablocks is that they are declared only on the server, so mods to the game can be created without forcing the client to downloading any script data. + +\section NetStringTable NetStringTable +The NetStringTable class manages string data across connections. Every tagged string in the console - those enclosed by single quotes ('), will be sent across a connection only a single time. Every subsequent time that string is sent, an integer tag is substituted for the actual string data. Strings like player names can be added with the addTaggedString console function and removed with the removeTaggedString console function. + +\section NetConsole Network Console Commands +There are two remote procedure call network console commands - commandToServer and commandToClient. The commandToServer function takes the form: commandToServer(functionNameTag, arg1, arg2, arg3, ... ), where functionNameTag is some string tag. This call is converted into a RemoteCommandEvent and set across to the server. Once there the server calls the local script function serverCmdXXX(clientId, arg1, arg2, arg3, ... ), where XXX is the text of the string tag. The commandToClient function takes the form: commandToClient(clientId, functionNameTag, arg1, arg2, arg3, ... ) where the clientId argument is the object id of the connection object to send to. + +The commandTo* functions perform string argument substitution automatically using the in-string % modifier. For example: + +\code +commandToClient('EchoMessage', + 'This %1 guy is super %2', + 'Got Milk?', + 'slow at writing documentation'); +\endcode + +is executed on the client as: + +\code function clientCmdEchoMessage(%message, %a1, %a2, %a3, %a4) +{ + // tagged strings must be detagged in order to be displayed. + echo(detag(%message)); + echo("a1 = " @ detag(%a1)); + echo("a2 = " @ detag(%a2)); + echo("a3 = " @ detag(%a3)); + echo("a4 = " @ detag(%a4)); +} +\endcode + +and would echo: +\code +This Got Milk? guy is super slow at writing documentation +a1 = Got Milk? +a2 = slow at writing documentation +a3 = +a4 = +\endcode + +The string substitution number (after the %) refers to the argument position n spaces after the current argument: + +\code +CommandToClient('EchoMessage', + '%1 is a good %2 for %3', + '%1 the good %2', + 'Role Model', + 'SuperDood %1', + 'the dude of super'); +\endcode + +Would echo: + +\code +Role Model the good SuperDood the dude of super is a good Role Model +for SuperDood the dude of super +A1 = Role Model the good SuperDood the dude of super +A2 = Role Model +A3 = SuperDood the dude of super +A4 = the dude of super +\endcode + +This functionality is especially useful for status and game messages coming from the server, because each text message compresses into just a small array of tag identifiers. +*/ \ No newline at end of file diff --git a/game/RCa03592 b/game/RCa03592 new file mode 100644 index 0000000..5c8df03 Binary files /dev/null and b/game/RCa03592 differ diff --git a/game/RDa03592 b/game/RDa03592 new file mode 100644 index 0000000..b7e4bbe Binary files /dev/null and b/game/RDa03592 differ diff --git a/game/aiConnection.cc b/game/aiConnection.cc new file mode 100644 index 0000000..5f310b7 --- /dev/null +++ b/game/aiConnection.cc @@ -0,0 +1,198 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/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. + */ +void AIConnection::getMoveList( Move **lngMove, U32 *numMoves ) +{ + *numMoves = 1; + *lngMove = &mMove; +} + + +//----------------------------------------------------------------------------- +// 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_PI,M_PI); + return (a < 0)? a + M_2PI: a; +} + + +//----------------------------------------------------------------------------- +/// Construct and connect an AI connection object +/** + * Construct and registers a new AI connection. No control object + * is set. + */ +ConsoleFunction(aiConnect, S32 , 2, 20, "aiConnect(val 0...n);") +{ + // 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(); +} + + +//----------------------------------------------------------------------------- +/// Console interface function to set the current move +/** + */ +ConsoleMethod(AIConnection,setMove,void,4, 4,"conn.setMove([x,y,z,yaw,pitch,roll],value);") +{ + AIConnection *ai = static_cast(object); + Move move = ai->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])); + + // + ai->setMove(&move); +} + +ConsoleMethod(AIConnection,getMove,F32,3, 3,"value conn.getMove([x,y,z,yaw,pitch,roll]);") +{ + AIConnection *ai = static_cast(object); + const Move& move = ai->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,"conn.setFreeLook(bool);") +{ + AIConnection *ai = static_cast(object); + Move move = ai->getMove(); + move.freeLook = dAtob(argv[2]); + ai->setMove(&move); +} + +ConsoleMethod(AIConnection,getFreeLook,bool,2, 2,"bool conn.getFreeLook();") +{ + AIConnection *ai = static_cast(object); + return ai->getMove().freeLook; +} + + +//----------------------------------------------------------------------------- + +ConsoleMethod(AIConnection,setTrigger,void,4, 4,"conn.setTrigger(trigger#,bool);") +{ + AIConnection *ai = static_cast(object); + S32 idx = dAtoi(argv[2]); + if (idx >= 0 && idx < MaxTriggerKeys) { + Move move = ai->getMove(); + move.trigger[idx] = dAtob(argv[3]); + ai->setMove(&move); + } +} + +ConsoleMethod(AIConnection,getTrigger,bool,4, 4,"bool conn.getTrigger(trigger#);") +{ + AIConnection *ai = static_cast(object); + S32 idx = dAtoi(argv[2]); + if (idx >= 0 && idx < MaxTriggerKeys) + return ai->getMove().trigger[idx]; + return false; +} + + +//----------------------------------------------------------------------------- + +ConsoleMethod(AIConnection,getAddress,const char*,2, 2,"bool conn.getAddress();") +{ + // Override the netConnection method to return to indicate + // this is an ai connection. + return "ai:local"; +} diff --git a/game/aiConnection.h b/game/aiConnection.h new file mode 100644 index 0000000..c3928fd --- /dev/null +++ b/game/aiConnection.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AICONNECTION_H_ +#define _AICONNECTION_H_ + +#ifndef _GAMECONNECTION_H_ +#include "game/gameConnection.h" +#endif +#ifndef _MOVEMANAGE_H_ +#include "game/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 void getMoveList(Move **,U32 *numMoves); +}; + + +#endif diff --git a/game/aiCore.cc b/game/aiCore.cc new file mode 100644 index 0000000..c0a21fc --- /dev/null +++ b/game/aiCore.cc @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/aiCore.h" + +/** + * Merge lists + */ +void AICore::processNewObjectList( Vector &newList, Vector &ignore ) { + + for( Vector::iterator i = newList.begin(); i != newList.end(); i++ ) { + bool reallyAdd = true; + + for( Vector::iterator j = ignore.begin(); j != ignore.end(); j++ ) { + if( (*j)->getId() == (*i)->getId() ) { + reallyAdd = false; + break; + } + } + + if( reallyAdd ) + mTrackedObjects.addObject( (*i), (*i)->getPosition() ); + } +} + +/** + * Test to see if the list contains this object + */ +bool AICore::contains( const SceneObject *testObj ) const { + return mTrackedObjects.contains( testObj ); +} + +/** + * Get the list of objects that are tracked that collide with the line + */ +Vector AICore::testLine( const Point3F &a, const Point3F &b ) const { + + Vector retVect; + Vector temp = mTrackedObjects.toVector(); + + for( Vector::iterator i = temp.begin(); i != temp.end(); i++ ) { + + if( (*i)->getWorldBox().collideLine( a, b ) ) { + // Add to collision list + retVect.push_back( AITrackedObject( (*i), mTrackedObjects.getTag( (*i) ) ) ); + } + } + + return retVect; +} + +/** + * Get all the objects tracked + */ +Vector AICore::getObjectList() const { + + Vector retVect; + Vector temp( mTrackedObjects.toVector() ); + + for( Vector::iterator i = temp.begin(); i != temp.end(); i++ ) + retVect.push_back( AITrackedObject( (*i), mTrackedObjects.getTag( (*i) ) ) ); + + return retVect; +} + +/** + * Gets the tag of the object + */ +Point3F AICore::getTag( const SceneObject *test ) const { + return mTrackedObjects.getTag( test ); +} diff --git a/game/aiCore.h b/game/aiCore.h new file mode 100644 index 0000000..ad94143 --- /dev/null +++ b/game/aiCore.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AICORE_H_ +#define _AICORE_H_ + +#ifndef _TSORTEDSCENEOBJECTLIST_H_ +#include "core/tSortedSceneObjectList.h" +#endif + +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif + +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif + +#include "dgl/dgl.h" + +class AITrackedObject { + + private: + Point3F mLoc; + SceneObject *mObj; + + public: + AITrackedObject( SceneObject *obj, Point3F &loc ) : + mObj( obj ), mLoc( loc ) { }; + + Point3F getLoc() const { return mLoc; }; + SceneObject *getObject() { return mObj; }; +}; + +class AICore { + + private: + SortedSceneObjectList mTrackedObjects; + + public: + + void processNewObjectList( Vector &newList, Vector &ignore ); + bool contains( const SceneObject *testObj ) const; + Vector testLine( const Point3F &a, const Point3F &b ) const; + Vector getObjectList() const; + Point3F getTag( const SceneObject *test ) const; +}; + +#endif \ No newline at end of file diff --git a/game/aiPlayer.cc b/game/aiPlayer.cc new file mode 100644 index 0000000..cb210b4 --- /dev/null +++ b/game/aiPlayer.cc @@ -0,0 +1,449 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- +#include "game/aiPlayer.h" +#include "core/realcomp.h" +#include "math/mMatrix.h" +#include "game/player.h" +#include "game/moveManager.h" + +IMPLEMENT_CONOBJECT( AIPlayer ); + +/** + * Constructor + */ +AIPlayer::AIPlayer() { + mMoveMode = ModeStop; + mMoveDestination.set( 0.0f, 0.0f, 0.0f ); + mAimLocation.set( 0.0f, 0.0f, 0.0f ); + mMoveSpeed = 0.0f; + mMoveTolerance = 0.25f; + + // Clear the triggers + for( int i = 0; i < MaxTriggerKeys; i++ ) + mTriggers[i] = false; + + mAimToDestination = true; + + mRotation.set( 0.0f, 0.0f, 0.0f ); + mLocation.set( 0.0f, 0.0f, 0.0f ); + mPlayer = NULL; +} + +/** + * Sets the object the bot is targeting + * + * @param targetObject The object to target + */ +void AIPlayer::setTargetObject( ShapeBase *targetObject ) { + + if ( !targetObject || !bool( mTargetObject ) || targetObject->getId() != mTargetObject->getId() ) + mTargetInSight = false; + + mTargetObject = targetObject; +} + +/** + * Returns the target object + * + * @return Object bot is targeting + */ +S32 AIPlayer::getTargetObject() { + 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 AIPlayer::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 AIPlayer::setMoveMode( S32 mode ) { + if( mode < 0 || mode >= ModeCount ) + mode = 0; + + mMoveMode = mode; +} + +/** + * Sets how far away from the move location is considered + * "on target" + * + * @param tolerance Movement tolerance for error + */ +void AIPlayer::setMoveTolerance( 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 ) { + // 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; + + // TEST CODE + RayInfo dummy; + + if( mPlayer ) { + if( !mPlayer->getContainer()->castRay( mLocation, location, InteriorObjectType | + StaticShapeObjectType | StaticObjectType | + TerrainObjectType, &dummy ) ) + Con::printf( "I can see the target." ); + else + Con::printf( "I can't see my target!! AAAAHHHHH!" ); + } +} + +/** + * Sets the location for the bot to aim at + * + * @param location Point to aim at + */ +void AIPlayer::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 AIPlayer::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 + */ +void AIPlayer::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; + + // If system is disabled, don't process + if ( !gAISystemEnabled ) + return; + + // 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, zDiff; + + + + // Check if we are dead, if so, throw the script callback -- PUT THIS IN AIPLAYER + //if( !dStricmp( mPlayer->getStateName(), "dead" ) ) { + // TO-DO: Script callback + // return; + //} + + F32 moveSpeed = mMoveSpeed; + + switch( mMoveMode ) { + + case ModeStop: + return; // Stop means no action, peroid + break; + + case ModeIdle: + // TO-DO: Insert callback for scripted idle stuff + break; + + case ModeWalk: + moveSpeed /= 2.0f; // Walking speed is half running speed + // Fall through to run + case ModeRun: + + // Get my location + MatrixF const& myTransform = mPlayer->getTransform(); + myTransform.getColumn( 3, &mLocation ); + + // Set rotation variables + mRotation = mPlayer->getRotation(); + Point3F headRotation = mPlayer->getHeadRotation(); + curYaw = mRotation.z; + curPitch = headRotation.x; + xDiff = mAimLocation.x - mLocation.x; + yDiff = mAimLocation.y - mLocation.y; + + // first do Yaw + if( !isZero( xDiff ) || !isZero( yDiff ) ) { + // use the cur yaw between -Pi and Pi + while( curYaw > M_2PI ) + curYaw -= M_2PI; + while( curYaw < -M_2PI ) + curYaw += M_2PI; + + // find the new yaw + newYaw = mAtan( xDiff, yDiff ); + + // find the yaw diff + F32 yawDiff = newYaw - curYaw; + + // make it between 0 and 2PI + if( yawDiff < 0.0f ) + yawDiff += M_2PI; + else if( yawDiff >= M_2PI ) + yawDiff -= M_2PI; + + // now make sure we take the short way around the circle + if( yawDiff > M_PI ) + yawDiff -= M_2PI; + else if( yawDiff < -M_PI ) + yawDiff += M_2PI; + + mMove.yaw = yawDiff; + + // set up the movement matrix + moveMatrix.set( EulerF( 0, 0, newYaw ) ); + } + else + moveMatrix.set( EulerF( 0, 0, curYaw ) ); + + // next do pitch + F32 horzDist = Point2F( mAimLocation.x, mAimLocation.y ).len(); + + if( !isZero( horzDist ) ) { + //we shoot from the gun, not the eye... + F32 vertDist = mAimLocation.z; + + newPitch = mAtan( horzDist, vertDist ) - ( M_PI / 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 ) ) && ( !isZero( mMoveSpeed ) ) ) + { + if( isZero( xDiff ) ) + mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed ); + else if( isZero( 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 ), &newMove ); + + //and sub the result back in the mMove structure + mMove.x = newMove.x; + mMove.y = newMove.y; + } + else { + // Ok, we are close enough + setMoveMode( ModeStop ); + Con::printf( "I'm close enough to my destination to stop." ); + } + break; + } + + // Copy over the trigger status + for( int i = 0; i < MaxTriggerKeys; i++ ) { + mMove.trigger[i] = mTriggers[i]; + mTriggers[i] = false; + } +} + +/** + * This method is just called to stop the bots from running amuck + * while the mission cycles + */ +void AIPlayer::missionCycleCleanup() { + setMoveMode( ModeStop ); +} + +/** + * Sets the move speed for an AI object, remember, walk is 1/2 run + */ +static void cAISetMoveSpeed( SimObject *obj, S32, const char** argv ) { + AIPlayer *ai = static_cast( obj ); + ai->setMoveSpeed( dAtoi( argv[2] ) ); +} + +/** + * Stops all AI movement, halt! + */ +static void cAIStop( SimObject *obj, S32, const char** ) { + AIPlayer *ai = static_cast( obj ); + ai->setMoveMode( AIPlayer::ModeStop ); +} + +/** + * Tells the AI to aim at the location provided + */ +static void cAIAimAtLocation( SimObject *obj, S32 argc, const char** argv ) { + AIPlayer *ai = static_cast( obj ); + Point3F v( 0.0f,0.0f,0.0f ); + dSscanf( argv[2], "%f %f %f", &v.x, &v.y, &v.z ); + + ai->setAimLocation( v ); +} + +/** + * Returns the point the AI is aiming at + */ +static const char *cAIGetAimLocation( SimObject *obj, S32, const char** ) { + AIPlayer *ai = static_cast( obj ); + Point3F aimPoint = ai->getAimLocation(); + + char* returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%f %f %f", aimPoint.x, aimPoint.y, aimPoint.z ); + + return returnBuffer; +} + +/** + * Sets the bots target object + */ +static void cAISetTargetObject( SimObject *obj, S32 argc, const char **argv ) { + AIPlayer *ai = static_cast( obj ); + + // 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 + */ +static const char *cAIGetTargetObject( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + S32 targetId = ai->getTargetObject(); + char* returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%d", targetId ); + + return returnBuffer; +} + +/** + * Checks to see if the target is in sight + */ +static bool cAITargetInSight( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + return ai->targetInSight(); +} + +/** + * Tells the bot the mission is cycling + */ +static void cAIMissionCycleCleanup( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + ai->missionCycleCleanup(); +} + +/** + * Tells the AI to move forward 100 units...TEST FXN + */ +static void cAIMoveForward( SimObject *obj, S32, const char ** ) { + + AIPlayer *ai = static_cast( obj ); + ShapeBase *player = ai->getControlObject(); + Point3F location; + MatrixF const &myTransform = player->getTransform(); + myTransform.getColumn( 3, &location ); + + location.y += 100.0f; + + ai->setMoveDestination( location ); +} // *** /TEST FXN + +/** + * Sets the AI to walk mode + */ +static void cAIWalk( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + ai->setMoveMode( AIPlayer::ModeWalk ); +} + +/** + * Sets the AI to run mode + */ +static void cAIRun( SimObject *obj, S32, const char ** ) { + AIPlayer *ai = static_cast( obj ); + ai->setMoveMode( AIPlayer::ModeRun ); +} + + +/** + * Console init function + */ +void AIPlayer::consoleInit() { + // TEST FXN + Con::addCommand( "AIPlayer", "moveForward", cAIMoveForward, "ai.moveForward()", 2, 2 ); + + Con::addCommand( "AIPlayer", "walk", cAIWalk, "ai.walk()", 2, 2 ); + Con::addCommand( "AIPlayer", "run", cAIRun, "ai.run()", 2, 2 ); + Con::addCommand( "AIPlayer", "stop", cAIStop, "ai.stop()", 2, 2 ); + Con::addCommand( "AIPlayer", "setMoveSpeed", cAISetMoveSpeed, "ai.setMoveSpeed( float )", 3, 3 ); + + Con::addCommand( "AIPlayer", "setTargetObject", cAISetTargetObject, "ai.setTargetObject( object )", 3, 4 ); + Con::addCommand( "AIPlayer", "getTargetObject", cAIGetTargetObject, "ai.getTargetObject()", 2, 2 ); + Con::addCommand( "AIPlayer", "targetInSight", cAITargetInSight, "ai.targetInSight()", 2, 2 ); + Con::addCommand( "AIPlayer", "aimAt", cAIAimAtLocation, "ai.aimAt( point )", 3, 3 ); + Con::addCommand( "AIPlayer", "getAimLocation", cAIGetAimLocation, "ai.getAimLocation()", 2, 2 ); +} \ No newline at end of file diff --git a/game/aiPlayer.h b/game/aiPlayer.h new file mode 100644 index 0000000..d39ec4e --- /dev/null +++ b/game/aiPlayer.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AIPLAYER_H_ +#define _AIPLAYER_H_ + +#include "game/aiConnection.h" + +class Player; + +class AIPlayer : 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]; + + bool mTargetInSight; + Player *mPlayer; + + Point3F mMoveDestination; + Point3F mLocation; + Point3F mRotation; // Euler really + + bool mAimToDestination; // Why is this in here? + Point3F mAimLocation; // Because objects would have facing as well. + + + SimObjectPtr mTargetObject; + public: + + DECLARE_CONOBJECT( AIPlayer ); + + enum + { + ModeStop = 0, + ModeWalk, // Walk is runSpeed / 2 + ModeRun, + ModeIdle, + ModeCount // This is in there as a max index value + }; + + AIPlayer(); + + void getMoveList( Move **movePtr,U32 *numMoves ); + static void consoleInit(); + + // ---Targeting and aiming sets/gets + void setTargetObject( ShapeBase *targetObject ); + S32 getTargetObject(); + bool targetInSight() { return mTargetInSight; } + + // ---Movement sets/gets + void setMoveSpeed( F32 speed ); + F32 getMoveSpeed() { return mMoveSpeed; } + + void setMoveMode( S32 mode ); + S32 getMoveMode() { return mMoveMode; } + + void setMoveTolerance( F32 tolerance ); + F32 getMoveTolerance() { return mMoveTolerance; } + + void setMoveDestination( const Point3F &location ); + Point3F getMoveDestination() { return mMoveDestination; } + + // ---Facing(Aiming) sets/gets + void setAimLocation( const Point3F &location ); + Point3F getAimLocation() { return mAimLocation; } + void clearAim(); + + // ---Other + void missionCycleCleanup(); + + // ---Callbacks +}; + +#endif \ No newline at end of file diff --git a/game/ambientAudioManager.cc b/game/ambientAudioManager.cc new file mode 100644 index 0000000..2532d94 --- /dev/null +++ b/game/ambientAudioManager.cc @@ -0,0 +1,262 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/ambientAudioManager.h" +#include "interior/interiorInstance.h" +#include "game/gameConnection.h" +#include "interior/interior.h" +#include "terrain/waterBlock.h" +#include "sceneGraph/sceneGraph.h" + +AmbientAudioManager gAmbientAudioManager; + +//------------------------------------------------------------------------------ +AmbientAudioManager::AmbientAudioManager() +{ + mOutsideScale = 1.f; + mInteriorAudioHandle = NULL_AUDIOHANDLE; + mPowerAudioHandle = NULL_AUDIOHANDLE; + mLastAlarmState = false; +} + +//------------------------------------------------------------------------------ +static void cSetPowerAudioProfiles(SimObject *, S32, const char ** argv) +{ + gAmbientAudioManager.mPowerUpProfile = dynamic_cast(Sim::findObject(argv[1])); + gAmbientAudioManager.mPowerDownProfile = dynamic_cast(Sim::findObject(argv[2])); +} + +void AmbientAudioManager::consoleInit() +{ + Con::addCommand("setPowerAudioProfiles", cSetPowerAudioProfiles, "setPowerAudioProfiles(powerUp, powerDown)", 3, 3); +} + +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::getServerConnection()); + 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)) + { + AudioProfile * 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) + { + AudioProfile * 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; + alxGetListener3f(AL_POSITION, &pos.x, &pos.y, &pos.z); + + // 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; + 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); +} diff --git a/game/ambientAudioManager.h b/game/ambientAudioManager.h new file mode 100644 index 0000000..4a837a9 --- /dev/null +++ b/game/ambientAudioManager.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AMBIENTAUDIOMANAGER_H_ +#define _AMBIENTAUDIOMANAGER_H_ + +#ifndef _AUDIOEMITTER_H_ +#include "game/audioEmitter.h" +#endif + +static void cSetPowerAudioProfiles(SimObject *, S32, const char **); + +class InteriorInstance; +class AmbientAudioManager +{ + friend void cSetPowerAudioProfiles(SimObject *, S32, const char **); + + private: + F32 mOutsideScale; // 0:inside -> 1:outside + Vector mEmitters; + SimObjectPtr mInteriorInstance; + + SimObjectPtr mCurrentEnvironment; + F32 mEnvironmentScale; + + AUDIOHANDLE mInteriorAudioHandle; + AUDIOHANDLE mPowerAudioHandle; + bool mLastAlarmState; + + bool getOutsideScale(F32 *, InteriorInstance **); + void updateEnvironment(); + void updateEmitter(AudioEmitter *); + void stopInteriorAudio(); + + SimObjectPtr mPowerUpProfile; + SimObjectPtr mPowerDownProfile; + + public: + AmbientAudioManager(); + + static void consoleInit(); + void addEmitter(AudioEmitter*); + void removeEmitter(AudioEmitter*); + void update(); +}; + +extern AmbientAudioManager gAmbientAudioManager; + +#endif diff --git a/game/audioEmitter.cc b/game/audioEmitter.cc new file mode 100644 index 0000000..afe58a6 --- /dev/null +++ b/game/audioEmitter.cc @@ -0,0 +1,884 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/audioEmitter.h" +#include "game/ambientAudioManager.h" +#include "console/consoleTypes.h" +#include "Editor/editor.h" +#include "dgl/dgl.h" +#include "sceneGraph/sceneState.h" +#include "game/gameConnection.h" + +//------------------------------------------------------------------------------ +static MRandomLCG sgRandom(0xdeadbeef); + +#define UPDATE_BUMP_MS 50 +extern ALuint alxGetWaveLen(ALuint buffer); +extern ALuint alxFindSource(AUDIOHANDLE handle); + +IMPLEMENT_CO_NETOBJECT_V1(AudioEmitter); + +//------------------------------------------------------------------------------ +class AudioEmitterLoopEvent : public SimEvent +{ + public: + void process(SimObject * obj) { + ((AudioEmitter*)obj)->processLoopEvent(); + } +}; + +//------------------------------------------------------------------------------ +AudioEmitter::AudioEmitter(bool client) +{ + // ::packData depends on these values... make sure it is updated with changes + mTypeMask |= MarkerObjectType; + + if(client) + mNetFlags = 0; + else + mNetFlags.set(Ghostable|ScopeAlways); + + mAudioHandle = NULL_AUDIOHANDLE; + + mAudioProfileId = 0; + mAudioDescriptionId = 0; + + mLoopCount = smDefaultDescription.mLoopCount; + mEventID = 0; + + mOwnedByClient = false; + + // field defaults + mAudioProfile = 0; + mAudioDescription = 0; + mFilename = 0; + + mUseProfileDescription = false; + mOutsideAmbient = true; + mDescription = smDefaultDescription; +} + +Audio::Description AudioEmitter::smDefaultDescription; + +//------------------------------------------------------------------------------ +void AudioEmitter::processLoopEvent() +{ + if(mLoopCount == 0) + return; + else if(mLoopCount > 0) + mLoopCount--; + + if(mUseProfileDescription && mAudioProfileId) + return; + + if(!mDescription.mIsLooping || ((mDescription.mLoopCount <= 0) && !mDescription.mMaxLoopGap)) + return; + + // check if still playing... + U32 source = alxFindSource(mAudioHandle); + if(source != -1) + { + ALint state = AL_STOPPED; + alGetSourcei(source, AL_SOURCE_STATE, &state); + if(state == AL_PLAYING) + { + mEventID = Sim::postEvent(this, new AudioEmitterLoopEvent, Sim::getCurrentTime() + UPDATE_BUMP_MS); + return; + } + } + + mEventID = 0; + mDirty.set(SourceMask); + update(); +} + +//------------------------------------------------------------------------------ +bool AudioEmitter::update() +{ + // object does not know it is really a client object if created through event + AssertFatal(isClientObject() || mOwnedByClient, "AudioEmitter::update: only clients can update!"); + + if(mDirty.test(LoopCount)) + mLoopCount = mDescription.mLoopCount; + + // updating source? + if(mDirty.test(SourceMask|UseProfileDescription) || (!mUseProfileDescription && + ((mDescription.mIsLooping && mDirty.test(LoopingMask)) || mDirty.test(IsLooping|Is3D|AudioType)))) + { + if(mAudioHandle != NULL_AUDIOHANDLE) + { + alxStop(mAudioHandle); + mAudioHandle = NULL_AUDIOHANDLE; + } + + // profile: + if(mDirty.test(Profile)) + { + if(!mAudioProfileId) + mAudioProfile = 0; + else + mAudioProfile = dynamic_cast(Sim::findObject(mAudioProfileId)); + } + + // description: + if(mDirty.test(Description)) + { + if(!mAudioDescriptionId) + mAudioDescription = 0; + else + mAudioDescription = dynamic_cast(Sim::findObject(mAudioDescriptionId)); + } + + MatrixF transform = getTransform(); + + // use the profile? + if(mUseProfileDescription && mAudioProfileId) + mAudioHandle = alxCreateSource(mAudioProfile, &transform); + else + { + // grab the filename + const char * fileName = 0; + if(mFilename && mFilename[0]) + fileName = mFilename; + else if(mAudioProfile) + fileName = mAudioProfile->mFilename; + + if(!fileName) + { + Con::errorf(ConsoleLogEntry::General, "AudioEmitter::update: invalid audio filename!"); + return(false); + } + + // use a description? + if(mAudioDescription) + mAudioHandle = alxCreateSource(mAudioDescription, fileName, &transform); + else + { + if(mDescription.mIsLooping) + { + S32 minGap = mClamp(mDescription.mMinLoopGap, 0, mDescription.mMaxLoopGap); + S32 maxGap = mClamp(mDescription.mMaxLoopGap, mDescription.mMinLoopGap, mDescription.mMaxLoopGap); + + // controlling looping? + if((mDescription.mLoopCount != -1) || maxGap) + { + mDescription.mIsLooping = false; + mAudioHandle = alxCreateSource(&mDescription, fileName, &transform); + mDescription.mIsLooping = true; + + // handle may come back as null.. (0 volume, no handles, ...), still + // want to try again (assume good buffer) since this is a looper + Resource buffer = AudioBuffer::find(fileName); + S32 waveLen = 0; + if(bool(buffer)) + { + ALuint alBuffer = buffer->getALBuffer(true); + waveLen = alxGetWaveLen(alBuffer); + } + + if(waveLen) + { + S32 offset = waveLen + minGap + sgRandom.randI(minGap, maxGap); + + if(mEventID) + Sim::cancelEvent(mEventID); + mEventID = Sim::postEvent(this, new AudioEmitterLoopEvent, Sim::getCurrentTime() + offset); + + if(mAudioHandle == NULL_AUDIOHANDLE) + return(true); + } + } + else + mAudioHandle = alxCreateSource(&mDescription, fileName, &transform); + } + else + mAudioHandle = alxCreateSource(&mDescription, fileName, &transform); + } + } + + if(mAudioHandle == NULL_AUDIOHANDLE) + { + Con::errorf(ConsoleLogEntry::General, "AudioEmitter::update: failed to create source!"); + return(false); + } + + alxPlay(mAudioHandle); + } + else + { + // don't clear dirty flags until there is a source + if(mAudioHandle == NULL_AUDIOHANDLE) + return(true); + + if(mDirty.test(Transform)) + { + Point3F pos; + mObjToWorld.getColumn(3, &pos); + alxSource3f(mAudioHandle, AL_POSITION, pos.x, pos.y, pos.z); + } + + // grab the description + const Audio::Description * desc = 0; + + if(mUseProfileDescription && mAudioProfile) + desc = mAudioProfile->getDescription(); + else if(mAudioDescription) + desc = mAudioDescription->getDescription(); + else + desc = &mDescription; + + if(!desc) + return(true); + + if(mDirty.test(Volume)) + alxSourcef(mAudioHandle, AL_GAIN_LINEAR, desc->mVolume); + + if(mDirty.test(MinDistance|MaxDistance)) + { + alxSourcef(mAudioHandle, AL_MIN_DISTANCE, desc->mMinDistance); + alxSourcef(mAudioHandle, AL_MAX_DISTANCE, desc->mMaxDistance); + } + + if(mDirty.test(ConeInsideAngle)) + alxSourcei(mAudioHandle, AL_CONE_INNER_ANGLE, desc->mConeInsideAngle); + + if(mDirty.test(ConeOutsideAngle)) + alxSourcei(mAudioHandle, AL_CONE_OUTER_ANGLE, desc->mConeOutsideAngle); + + if(mDirty.test(ConeOutsideVolume)) + alxSourcef(mAudioHandle, AL_CONE_OUTER_GAIN, desc->mConeOutsideVolume); + } + + mDirty.clear(); + return(true); +} + +//------------------------------------------------------------------------------ +bool AudioEmitter::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + // object does not know it is really a client object if created through event + if(isServerObject() && !mOwnedByClient) + { + // validate the object data + mDescription.mVolume = mClampF(mDescription.mVolume, 0.0f, 1.0f); + mDescription.mLoopCount = mClamp(mDescription.mLoopCount, -1, mDescription.mLoopCount); + mDescription.mMaxLoopGap = mClamp(mDescription.mMaxLoopGap, mDescription.mMinLoopGap, mDescription.mMaxLoopGap); + mDescription.mMinLoopGap = mClamp(mDescription.mMinLoopGap, 0, mDescription.mMaxLoopGap); + + if(mDescription.mIs3D) + { + mDescription.mMinDistance = mClampF(mDescription.mMinDistance, 0.f, mDescription.mMinDistance); + mDescription.mMaxDistance = (mDescription.mMaxDistance > mDescription.mMinDistance) ? mDescription.mMaxDistance : (mDescription.mMinDistance+0.01f); + mDescription.mConeInsideAngle = mClamp(mDescription.mConeInsideAngle, 0, 360); + mDescription.mConeOutsideAngle = mClamp(mDescription.mConeOutsideAngle, mDescription.mConeInsideAngle, 360); + mDescription.mConeOutsideVolume = mClampF(mDescription.mConeOutsideVolume, 0.0f, 1.0f); + mDescription.mConeVector.normalize(); + } + } + else + { + if(!update()) + { + Con::errorf(ConsoleLogEntry::General, "AudioEmitter::onAdd: client failed initial update!"); + if(mOwnedByClient) + return(false); + } + gAmbientAudioManager.addEmitter(this); + } + + // + mObjBox.max = mObjScale; + mObjBox.min = mObjScale; + mObjBox.min.neg(); + resetWorldBox(); + addToScene(); + + return(true); +} + +void AudioEmitter::onRemove() +{ + if(isClientObject()) + { + gAmbientAudioManager.removeEmitter(this); + if(mAudioHandle != NULL_AUDIOHANDLE) + alxStop(mAudioHandle); + } + removeFromScene(); + Parent::onRemove(); +} + +void AudioEmitter::setTransform(const MatrixF & mat) +{ + Point3F pos; + mat.getColumn(3, &pos); + + MatrixF transform(true); + transform.setColumn(3, pos); + + Parent::setTransform(transform); + setMaskBits(TransformUpdateMask); +} + +void AudioEmitter::setScale(const VectorF &) +{ +} + +//------------------------------------------------------------------------------ +namespace { + static 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 U32 cubeFaces[6][4] = { + { 0, 2, 6, 4 }, { 0, 2, 3, 1 }, { 0, 1, 5, 4 }, + { 3, 2, 6, 7 }, { 7, 6, 4, 5 }, { 3, 7, 5, 1 } + }; + + static Point2F textureCoords[4] = { + Point2F(0, 0), Point2F(1, 0), Point2F(1, 1), Point2F(0, 1) + }; +} + +bool AudioEmitter::prepRenderImage(SceneState * state, const U32 stateKey, const U32, const bool) +{ + if(!gEditingMission || isLastState(state, stateKey)) + return(false); + + setLastState(state, stateKey); + if(gEditingMission && state->isObjectRendered(this)) + { + SceneRenderImage * image = new SceneRenderImage; + image->obj = this; + state->insertRenderImage(image); + } + return(false); +} + +void AudioEmitter::renderObject(SceneState*, SceneRenderImage*) +{ + TextureHandle texture("special/BlueImpact", BitmapTexture, false); + if(bool(texture)) + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texture.getGLName()); + } + + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + glDisable(GL_CULL_FACE); + Point3F size(1.f, 1.f, 1.f); + + // draw the cube + for(int i = 0; i < 6; i++) + { + glBegin(GL_QUADS); + for(int vert = 0; vert < 4; vert++) + { + int idx = cubeFaces[i][vert]; + if(bool(texture)) + glTexCoord2f(textureCoords[vert].x, textureCoords[vert].y); + glVertex3f(cubePoints[idx].x * size.x, cubePoints[idx].y * size.y, cubePoints[idx].z * size.z); + } + glEnd(); + } + + if(bool(texture)) + glDisable(GL_TEXTURE_2D); + + glPopMatrix(); +} + +//------------------------------------------------------------------------------ +namespace { + static AudioProfile * saveAudioProfile; + static AudioDescription * saveAudioDescription; + static StringTableEntry saveFilename; + static bool saveUseProfileDescription; + static Audio::Description saveDescription; + static Point3F savePos; + static bool saveOutsideAmbient; +} + +void AudioEmitter::inspectPreApply() +{ + if(isClientObject()) + return; + + Parent::inspectPostApply(); + + mObjToWorld.getColumn(3, &savePos); + saveAudioProfile = mAudioProfile; + saveAudioDescription = mAudioDescription; + saveFilename = mFilename; + saveUseProfileDescription = mUseProfileDescription; + saveDescription = mDescription; + saveOutsideAmbient = mOutsideAmbient; +} + +void AudioEmitter::inspectPostApply() +{ + if(isClientObject()) + return; + + Parent::inspectPostApply(); + + Point3F pos; + mObjToWorld.getColumn(3, &pos); + + // set some dirty flags + mDirty.clear(); + mDirty.set((savePos != pos) ? Transform : 0); + mDirty.set((saveAudioProfile != mAudioProfile) ? Profile : 0); + mDirty.set((saveAudioDescription != mAudioDescription) ? Description : 0); + mDirty.set((saveFilename != mFilename) ? Filename : 0); + mDirty.set((saveUseProfileDescription != mUseProfileDescription) ? UseProfileDescription : 0); + mDirty.set((saveDescription.mVolume != mDescription.mVolume) ? Volume : 0); + mDirty.set((saveDescription.mIsLooping != mDescription.mIsLooping) ? IsLooping : 0); + mDirty.set((saveDescription.mIs3D != mDescription.mIs3D) ? Is3D : 0); + mDirty.set((saveDescription.mMinDistance != mDescription.mMinDistance) ? MinDistance : 0); + mDirty.set((saveDescription.mMaxDistance != mDescription.mMaxDistance) ? MaxDistance : 0); + mDirty.set((saveDescription.mConeInsideAngle != mDescription.mConeInsideAngle) ? ConeInsideAngle : 0); + mDirty.set((saveDescription.mConeOutsideAngle != mDescription.mConeOutsideAngle) ? ConeOutsideAngle : 0); + mDirty.set((saveDescription.mConeOutsideVolume != mDescription.mConeOutsideVolume) ? ConeOutsideVolume : 0); + mDirty.set((saveDescription.mConeVector != mDescription.mConeVector) ? ConeVector : 0); + mDirty.set((saveDescription.mLoopCount != mDescription.mLoopCount) ? LoopCount : 0); + mDirty.set((saveDescription.mMinLoopGap != mDescription.mMinLoopGap) ? MinLoopGap : 0); + mDirty.set((saveDescription.mMaxLoopGap != mDescription.mMaxLoopGap) ? MaxLoopGap : 0); + mDirty.set((saveDescription.mType != mDescription.mType) ? AudioType : 0); + mDirty.set((saveOutsideAmbient != mOutsideAmbient) ? OutsideAmbient : 0); + + if(mDirty) + setMaskBits(DirtyUpdateMask); +} + +void AudioEmitter::packData(NetConnection *, U32 mask, BitStream * stream) +{ + // initial update + if(stream->writeFlag(mask & InitialUpdateMask)) + { + mask |= TransformUpdateMask; + mDirty = AllDirtyMask; + + // see if can remove some items from initial update: description checked below + if(!mAudioProfile) + mDirty.clear(Profile); + if(!mAudioDescription) + mDirty.clear(Description); + if(!mFilename || !mFilename[0]) + mDirty.clear(Filename); + if(!mUseProfileDescription) + mDirty.clear(UseProfileDescription); + if(mOutsideAmbient) + mDirty.clear(OutsideAmbient); + } + + // transform + if(stream->writeFlag(mask & TransformUpdateMask)) + stream->writeAffineTransform(mObjToWorld); + + // profile + if(stream->writeFlag(mDirty.test(Profile))) + if(stream->writeFlag(mAudioProfile)) + stream->writeRangedU32(mAudioProfile->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + // description + if(stream->writeFlag(mDirty.test(Description))) + if(stream->writeFlag(mAudioDescription)) + stream->writeRangedU32(mAudioDescription->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + // filename + if(stream->writeFlag(mDirty.test(Filename))) + stream->writeString(mFilename); + + // useprofiledescription + if(stream->writeFlag(mDirty.test(UseProfileDescription))) + { + if(mUseProfileDescription) + { + if(!mAudioProfile) + mUseProfileDescription = false; + else + mDirty.clear(UseProfileDescriptionMask); + } + + if(!mUseProfileDescription) + mDirty.set(UseProfileDescriptionMask); + + stream->writeFlag(mUseProfileDescription); + } + + if(mAudioDescription && !mUseProfileDescription) + mDirty.clear(UseProfileDescriptionMask); + + // check initial update against the default description + if((mask & InitialUpdateMask) && !mUseProfileDescription) + { + if(mDescription.mVolume == smDefaultDescription.mVolume) mDirty.clear(Volume); + AssertFatal(smDefaultDescription.mIsLooping, "Doh!"); + if(mDescription.mIsLooping != smDefaultDescription.mIsLooping) + mDirty.clear(LoopingMask); + else + { + mDirty.clear(IsLooping); + if(mDescription.mLoopCount == smDefaultDescription.mLoopCount) mDirty.clear(LoopCount); + if(mDescription.mMinLoopGap == smDefaultDescription.mMinLoopGap) mDirty.clear(MinLoopGap); + if(mDescription.mMaxLoopGap == smDefaultDescription.mMaxLoopGap) mDirty.clear(MaxLoopGap); + } + AssertFatal(smDefaultDescription.mIs3D, "Doh!"); + if(mDescription.mIs3D != smDefaultDescription.mIs3D) + mDirty.clear(Is3DMask); + else + { + mDirty.clear(Is3D); + if(mDescription.mMinDistance == smDefaultDescription.mMinDistance) mDirty.clear(MinDistance); + if(mDescription.mMaxDistance == smDefaultDescription.mMaxDistance) mDirty.clear(MaxDistance); + if(mDescription.mConeInsideAngle == smDefaultDescription.mConeInsideAngle) mDirty.clear(ConeInsideAngle); + if(mDescription.mConeOutsideAngle == smDefaultDescription.mConeOutsideAngle) mDirty.clear(ConeOutsideAngle); + if(mDescription.mConeOutsideVolume == smDefaultDescription.mConeOutsideVolume) mDirty.clear(ConeOutsideVolume); + if(mDescription.mConeVector == smDefaultDescription.mConeVector) mDirty.clear(ConeVector); + } + if(smDefaultDescription.mType == smDefaultDescription.mType) mDirty.clear(AudioType); + } + + // volume + if(stream->writeFlag(mDirty.test(Volume))) + stream->write(mDescription.mVolume); + + // islooping + if(stream->writeFlag(mDirty.test(IsLooping))) + { + mDescription.mIsLooping ? mDirty.set(LoopingMask) : mDirty.clear(LoopingMask); + stream->writeFlag(mDescription.mIsLooping); + } + + // is3d + if(stream->writeFlag(mDirty.test(Is3D))) + { + mDescription.mIs3D ? mDirty.set(Is3DMask) : mDirty.clear(Is3DMask); + stream->writeFlag(mDescription.mIs3D); + } + + // mindistance + if(stream->writeFlag(mDirty.test(MinDistance))) + stream->write(mDescription.mMinDistance); + + // 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); + + // conevector + if(stream->writeFlag(mDirty.test(ConeVector))) + { + stream->write(mDescription.mConeVector.x); + stream->write(mDescription.mConeVector.y); + stream->write(mDescription.mConeVector.z); + } + + // loopcount + if(stream->writeFlag(mDirty.test(LoopCount))) + stream->write(mDescription.mLoopCount); + + // minloopgap + if(stream->writeFlag(mDirty.test(MinLoopGap))) + stream->write(mDescription.mMinLoopGap); + + // maxloopgap + if(stream->writeFlag(mDirty.test(MaxLoopGap))) + stream->write(mDescription.mMaxLoopGap); + + // audiotype + if(stream->writeFlag(mDirty.test(AudioType))) + stream->write(mDescription.mType); + + // outside ambient + if(stream->writeFlag(mDirty.test(OutsideAmbient))) + stream->writeFlag(mOutsideAmbient); + + mDirty.clear(); +} + +//------------------------------------------------------------------------------ +U32 AudioEmitter::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + packData(con, mask, stream); + return(retMask); +} + +bool AudioEmitter::readDirtyFlag(BitStream * stream, U32 mask) +{ + bool flag = stream->readFlag(); + if(flag) + mDirty.set(mask); + return(flag); +} + +void AudioEmitter::unpackData(NetConnection *, BitStream * stream) +{ + // initial update? + bool initialUpdate = stream->readFlag(); + + // transform + if(readDirtyFlag(stream, Transform)) + { + MatrixF mat; + stream->readAffineTransform(&mat); + Parent::setTransform(mat); + } + + // profile + if(readDirtyFlag(stream, Profile)) + if(stream->readFlag()) + mAudioProfileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + mAudioProfileId = 0; + + // description + if(readDirtyFlag(stream, Description)) + if(stream->readFlag()) + mAudioDescriptionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + mAudioDescriptionId = 0; + + // filename + if(readDirtyFlag(stream, Filename)) + mFilename = stream->readSTString(); + + // useprofiledescription + if(readDirtyFlag(stream, UseProfileDescription)) + mUseProfileDescription = stream->readFlag(); + + // volume + if(readDirtyFlag(stream, Volume)) + stream->read(&mDescription.mVolume); + + // islooping + if(readDirtyFlag(stream, IsLooping)) + mDescription.mIsLooping = stream->readFlag(); + + // is3d + if(readDirtyFlag(stream, Is3D)) + mDescription.mIs3D = stream->readFlag(); + + // mindistance + if(readDirtyFlag(stream, MinDistance)) + stream->read(&mDescription.mMinDistance); + + // 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); + + // conevector + if(readDirtyFlag(stream, ConeVector)) + { + stream->read(&mDescription.mConeVector.x); + stream->read(&mDescription.mConeVector.y); + stream->read(&mDescription.mConeVector.z); + } + + // loopcount + if(readDirtyFlag(stream, LoopCount)) + stream->read(&mDescription.mLoopCount); + + // minloopgap + if(readDirtyFlag(stream, MinLoopGap)) + { + stream->read(&mDescription.mMinLoopGap); + if(mDescription.mMinLoopGap < 0) + mDescription.mMinLoopGap = 0; + } + + // maxloopgap + if(readDirtyFlag(stream, MaxLoopGap)) + { + stream->read(&mDescription.mMaxLoopGap); + if(mDescription.mMaxLoopGap < 0) + mDescription.mMaxLoopGap = 0; + } + + // audiotype + if(readDirtyFlag(stream, AudioType)) + { + stream->read(&mDescription.mType); + if(mDescription.mType >= Audio::NumAudioTypes) + mDescription.mType = Audio::DefaultAudioType; + } + + // outside ambient + if(readDirtyFlag(stream, OutsideAmbient)) + mOutsideAmbient = stream->readFlag(); + + // update the emitter now? + if(!initialUpdate) + update(); +} + +void AudioEmitter::unpackUpdate(NetConnection * con, BitStream * stream) +{ + Parent::unpackUpdate(con, stream); + unpackData(con, stream); +} + +//------------------------------------------------------------------------------ +void AudioEmitter::consoleInit() +{ + // put functions in here to play/stop/change the client's sound! +} + +static EnumTable::Enums audioEnums[] = +{ + { Audio::DefaultAudioType, "DefaultAudioType" }, + { Audio::ChatAudioType, "ChatAudioType" }, + { Audio::GuiAudioType, "GuiAudioType" }, + { Audio::EffectAudioType, "EffectAudioType" }, + { Audio::VoiceAudioType, "VoiceAudioType" } +}; +static EnumTable gAudioTypeTable(Audio::NumAudioTypes, &audioEnums[0]); + +void AudioEmitter::initPersistFields() +{ + Parent::initPersistFields(); + + addField("profile", TypeAudioProfilePtr, Offset(mAudioProfile, AudioEmitter)); + addField("description", TypeAudioDescriptionPtr, Offset(mAudioDescription, AudioEmitter)); + addField("fileName", TypeString, Offset(mFilename, AudioEmitter)); + + addField("useProfileDescription", TypeBool, Offset(mUseProfileDescription, AudioEmitter)); + addField("outsideAmbient", TypeBool, Offset(mOutsideAmbient, AudioEmitter)); + + addField("volume", TypeF32, Offset(mDescription.mVolume, AudioEmitter)); + addField("isLooping", TypeBool, Offset(mDescription.mIsLooping, AudioEmitter)); + addField("is3D", TypeBool, Offset(mDescription.mIs3D, AudioEmitter)); + addField("minDistance", TypeF32, Offset(mDescription.mMinDistance, AudioEmitter)); + addField("maxDistance", TypeF32, Offset(mDescription.mMaxDistance, AudioEmitter)); + addField("coneInsideAngle", TypeS32, Offset(mDescription.mConeInsideAngle, AudioEmitter)); + addField("coneOutsideAngle", TypeS32, Offset(mDescription.mConeOutsideAngle, AudioEmitter)); + addField("coneOutsideVolume", TypeF32, Offset(mDescription.mConeOutsideVolume, AudioEmitter)); + addField("coneVector", TypePoint3F, Offset(mDescription.mConeVector, AudioEmitter)); + addField("loopCount", TypeS32, Offset(mDescription.mLoopCount, AudioEmitter)); + addField("minLoopGap", TypeS32, Offset(mDescription.mMinLoopGap, AudioEmitter)); + addField("maxLoopGap", TypeS32, Offset(mDescription.mMaxLoopGap, AudioEmitter)); + addField("type", TypeEnum, Offset(mDescription.mType, AudioEmitter), 1, &gAudioTypeTable); + + // create the static description + smDefaultDescription.mVolume = 1.0f; + smDefaultDescription.mIsLooping = true; + smDefaultDescription.mIs3D = true; + smDefaultDescription.mMinDistance = 1.0f; + smDefaultDescription.mMaxDistance = 100.0f; + smDefaultDescription.mConeInsideAngle = 360; + smDefaultDescription.mConeOutsideAngle = 360; + smDefaultDescription.mConeOutsideVolume = 1.0f; + smDefaultDescription.mConeVector.set(0, 0, 1); + smDefaultDescription.mLoopCount = -1; + smDefaultDescription.mMinLoopGap = 0; + smDefaultDescription.mMaxLoopGap = 0; + smDefaultDescription.mType = Audio::EffectAudioType; +} + +////-------------------------------------------------------------------------- +//IMPLEMENT_CO_NETOBJECT_V1(ClientAudioEmitter); +// +//ClientAudioEmitter::ClientAudioEmitter() +//{ +// mNetFlags = 0; +// mEventCount = 0; +//} +// +//bool ClientAudioEmitter::onAdd() +//{ +// if(!SceneObject::onAdd()) +// return(false); +// +// // create and send events to all clients +// bool postedEvent = false; +// for(NetConnection * conn = NetConnection::getConnectionList(); conn; conn = conn->getNext()) +// { +// if(!conn->isServerConnection()) +// { +// postedEvent = true; +// conn->postNetEvent(new AudioEmitterToEvent(this)); +// } +// } +// +// return(postedEvent); +//} +// +//void ClientAudioEmitter::initPersistFields() +//{ +// Parent::initPersistFields(); +//} +// +////-------------------------------------------------------------------------- +//IMPLEMENT_CO_NETEVENT_V1(AudioEmitterToEvent); +// +//AudioEmitterToEvent::AudioEmitterToEvent(ClientAudioEmitter * emitter) +//{ +// mEmitter = emitter; +// if(bool(mEmitter)) +// mEmitter->mEventCount++; +//} +// +//void AudioEmitterToEvent::notifyDelivered(NetConnection *, bool) +//{ +// if(bool(mEmitter) && mEmitter->mEventCount && (--mEmitter->mEventCount == 0)) +// { +// mEmitter->deleteObject(); +// mEmitter = 0; +// } +//} +// +//void AudioEmitterToEvent::write(NetConnection * conn, BitStream * bstream) +//{ +// if(bool(mEmitter)) +// mEmitter->packData(conn, AudioEmitter::InitialUpdateMask, bstream); +//} +// +//void AudioEmitterToEvent::pack(NetConnection * conn, BitStream * bstream) +//{ +// if(bool(mEmitter)) +// mEmitter->packData(conn, AudioEmitter::InitialUpdateMask, bstream); +//} +// +//void AudioEmitterToEvent::unpack(NetConnection * conn, BitStream * bstream) +//{ +// // clients are responsible for deleting these audioemitters! +// AudioEmitter * emitter = new AudioEmitter(true); +// emitter->mOwnedByClient = true; +// emitter->unpackData(conn, bstream); +// emitter->registerOb diff --git a/game/audioEmitter.h b/game/audioEmitter.h new file mode 100644 index 0000000..296f642 --- /dev/null +++ b/game/audioEmitter.h @@ -0,0 +1,150 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUDIOEMITTER_H_ +#define _AUDIOEMITTER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif +#ifndef _AUDIO_H_ +#include "audio/audio.h" +#endif +#ifndef _NETCONNECTION_H_ +#include "sim/netConnection.h" +#endif + +//--------------------------------------------------------------------------- +class AudioEmitter : public SceneObject +{ + friend class AmbientAudioManager; + friend class AudioEmitterToEvent; + + private: + typedef SceneObject Parent; + + AUDIOHANDLE mAudioHandle; + U32 mAudioProfileId; + U32 mAudioDescriptionId; + + // field data + AudioProfile * mAudioProfile; + AudioDescription * mAudioDescription; + StringTableEntry mFilename; + + bool mUseProfileDescription; + static Audio::Description smDefaultDescription; + Audio::Description mDescription; + bool mOutsideAmbient; + S32 mLoopCount; + U32 mEventID; + + bool mOwnedByClient; + + enum Dirty { + Profile = BIT(0), + Description = BIT(1), + Filename = BIT(2), + UseProfileDescription = BIT(3), + Volume = BIT(4), + IsLooping = BIT(5), + Is3D = BIT(6), + MinDistance = BIT(7), + MaxDistance = BIT(8), + ConeInsideAngle = BIT(9), + ConeOutsideAngle = BIT(10), + ConeOutsideVolume = BIT(11), + ConeVector = BIT(12), + LoopCount = BIT(13), + MinLoopGap = BIT(14), + MaxLoopGap = BIT(15), + Transform = BIT(16), + AudioType = BIT(17), + OutsideAmbient = BIT(18), + + AllDirtyMask = BIT(19) - 1, + + SourceMask = (Profile|Description|Filename), + LoopingMask = (LoopCount|MinLoopGap|MaxLoopGap), + Is3DMask = (MinDistance|MaxDistance|ConeInsideAngle|ConeOutsideAngle|ConeOutsideVolume|ConeVector), + UseProfileDescriptionMask = (Volume|LoopingMask|Is3DMask|IsLooping|Is3D|AudioType), + }; + BitSet32 mDirty; + bool readDirtyFlag(BitStream *, U32); + + public: + + AudioEmitter(bool client = false); + + void processLoopEvent(); + bool update(); + + enum UpdateMasks { + InitialUpdateMask = BIT(0), + TransformUpdateMask = BIT(1), + DirtyUpdateMask = BIT(2), + }; + + void packData(NetConnection *, U32, BitStream *); + void unpackData(NetConnection *, BitStream *); + + // SceneObject + void setTransform(const MatrixF &); + void setScale(const VectorF &); + void inspectPreApply(); + void inspectPostApply(); + bool prepRenderImage(SceneState *, const U32, const U32, const bool); + void renderObject(SceneState *, SceneRenderImage *); + + // NetObject + U32 packUpdate(NetConnection *, U32, BitStream *); + void unpackUpdate(NetConnection *, BitStream *); + + // SimObject + bool onAdd(); + void onRemove(); + + static void consoleInit(); + static void initPersistFields(); + + DECLARE_CONOBJECT(AudioEmitter); +}; + +////--------------------------------------------------------------------------- +//class ClientAudioEmitter : public AudioEmitter +//{ +// private: +// typedef AudioEmitter Parent; +// +// public: +// U32 mEventCount; +// ClientAudioEmitter(); +// bool onAdd(); +// +// static void initPersistFields(); +// DECLARE_CONOBJECT(ClientAudioEmitter); +//}; +// +////--------------------------------------------------------------------------- +//class AudioEmitterToEvent : public NetEvent +//{ +// private: +// SimObjectPtr mEmitter; +// +// public: +// AudioEmitterToEvent(ClientAudioEmitter * emitter = 0); +// +// void notifyDelivered(NetConnection *, bool); +// void write(NetConnection *, BitStream *); +// void pack(NetConnection *, BitStream *); +// void unpack(NetConnection *, BitStream *); +// void process(NetConnection *) {} +// +// DECLARE_CONOBJECT(AudioEmitterToEvent); +//}; + +#endif diff --git a/game/auth.h b/game/auth.h new file mode 100644 index 0000000..a7b0813 --- /dev/null +++ b/game/auth.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUTH_H_ +#define _AUTH_H_ + +#ifndef _EVENT_H_ +#include "Platform/event.h" +#endif + +class Auth2Certificate +{ + U32 xxx; +}; + +struct InfoServer +{ + NetAddress address; + U32 region; +}; + +enum { + MaxWarriorNameLen = 31, + MaxTribeTagLen = 15, + MaxTribeNameLen = 63, + MaxTribes = 10, + + Tribes2ClientCommunityId = 98, + Tribes2ServerCommunityId = 99, +}; + +struct AuthTribeInfo +{ + char tribeName[MaxTribeNameLen + 1]; + char tribeTag[MaxTribeTagLen + 1]; + U32 tribeId; + U32 privLevel; + bool tagAppend; +}; + +struct AuthInfo +{ + bool valid; + char warriorName[MaxWarriorNameLen + 1]; + U32 wonID; + U32 tribeCount; + AuthTribeInfo ti[MaxTribes]; +}; + +#endif diff --git a/game/badWordFilter.cc b/game/badWordFilter.cc new file mode 100644 index 0000000..6bfafb2 --- /dev/null +++ b/game/badWordFilter.cc @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "game/badWordFilter.h" + +BadWordFilter *gBadWordFilter = NULL; +bool BadWordFilter::filteringEnabled = true; + +BadWordFilter::BadWordFilter() +{ + 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]; + S32 index = 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);") +{ + 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);") +{ + argc; + return gBadWordFilter->containsBadWords(argv[1]); +} diff --git a/game/badWordFilter.h b/game/badWordFilter.h new file mode 100644 index 0000000..f162031 --- /dev/null +++ b/game/badWordFilter.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BADWORDFILTER_H_ +#define _BADWORDFILTER_H_ + +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + +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 diff --git a/game/banList.cc b/game/banList.cc new file mode 100644 index 0000000..65f8838 --- /dev/null +++ b/game/banList.cc @@ -0,0 +1,188 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/banList.h" +#include "console/consoleTypes.h" +#include "core/resManager.h" +#include "core/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 *TA) +{ + 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 *TA) +{ + 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 *fileName) +{ + FileStream banlist; + + if(ResourceManager->openFileForWrite(banlist, ResourceManager->getBasePath(), fileName, FileStream::Write)) + { + 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); + } + } + + banlist.close(); +} + +// --------------------------------------------------------- +// console methods +// --------------------------------------------------------- + +static void cAdd(SimObject *, S32, const char **argv) +{ + gBanList.addBan( dAtoi( argv[1] ), argv[2], dAtoi( argv[3] ) ); +} + +//------------------------------------------------------------------------------ + +static void cAddRelative(SimObject *, S32, const char **argv) +{ + gBanList.addBanRelative( dAtoi( argv[1] ), argv[2], dAtoi( argv[3] ) ); +} + +//------------------------------------------------------------------------------ + +static void cRemoveBan(SimObject *, S32, const char **argv) +{ + gBanList.removeBan( dAtoi( argv[1] ), argv[2] ); +} + +//------------------------------------------------------------------------------ + +static bool cIsBanned(SimObject *, S32, const char **argv) +{ + return (gBanList.isBanned( dAtoi( argv[1] ), argv[2] )); +} + +//------------------------------------------------------------------------------ + +static void cExport(SimObject *, S32, const char **argv) +{ + gBanList.exportToFile( argv[1] ); +} + +//------------------------------------------------------------------------------ + +void BanList::consoleInit() +{ + Con::addCommand("BanList", "add", cAddRelative, "BanList::add( id, TA, banTime )", 4, 4); + Con::addCommand("BanList", "addAbsolute", cAdd, "BanList::addAbsolute( id, TA, banTime )", 4, 4); + Con::addCommand("BanList", "removeBan", cRemoveBan, "BanList::removeBan( id, TA )", 3, 3); + Con::addCommand("BanList", "isBanned", cIsBanned, "BanList::isBanned( id, TA )", 3, 3); + Con::addCommand("BanList", "export", cExport, "BanList::export( filename )", 2, 2); +} + +//------------------------------------------------------------------------------ diff --git a/game/banList.h b/game/banList.h new file mode 100644 index 0000000..b56bb71 --- /dev/null +++ b/game/banList.h @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BANLIST_H_ +#define _BANLIST_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif + +class BanList : public SimObject +{ +public: + + struct BanInfo + { + S32 uniqueId; + char transportAddress[128]; + S32 bannedUntil; + }; + + Vector list; + + BanList(){} + ~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); + + static void consoleInit(); +}; + +extern BanList gBanList; + +#endif diff --git a/game/bombSight.cc b/game/bombSight.cc new file mode 100644 index 0000000..94169cf --- /dev/null +++ b/game/bombSight.cc @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/bombSight.h" +#include "platformWin32/platformGL.h" +#include "dgl/dgl.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "ts/tsShapeInstance.h" + +IMPLEMENT_CONOBJECT(BombSight); + +//-------------------------------------------------------------------------- +BombSight::BombSight() +{ + mTypeMask |= ProjectileObjectType; + mMoveShape = NULL; + mDtsFileName = NULL; + mRenderObject = false; + mAlpha = 1.0f; +} + +//-------------------------------------------------------------------------- +BombSight::~BombSight() +{ +} + +//-------------------------------------------------------------------------- +bool BombSight::onAdd() +{ + if(!Parent::onAdd()) + return false; + + char fullName[256]; + dSprintf(fullName,sizeof(fullName),"shapes/%s", mDtsFileName); + + mMoveShape = ResourceManager->load(fullName); + mShapeInstance = new TSShapeInstance(mMoveShape, true); + mObjBox = mMoveShape->bounds; + resetWorldBox(); + + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + + return true; +} + +//-------------------------------------------------------------------------- +void BombSight::onRemove() +{ + gClientContainer.removeObject(this); + mSceneManager->removeObjectFromScene(this); + + Parent::onRemove(); +} + +//-------------------------------------------------------------------------- +bool BombSight::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey) || !mRenderObject) + 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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::EndSort; + state->insertRenderImage(image); + } + + return false; +} + +//-------------------------------------------------------------------------- +void BombSight::renderObject(SceneState* state, SceneRenderImage *) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + // RENDER CODE HERE + glDisable(GL_DEPTH_TEST); + mShapeInstance->setAlphaAlways(mAlpha); + mShapeInstance->render(); + glEnable(GL_DEPTH_TEST); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + dglSetCanonicalState(); + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +void BombSight::setDtsShape(StringTableEntry dtsName) +{ + mDtsFileName = dtsName; +} + +void BombSight::setRender(const bool renderObj, const F32 alpha) +{ + mRenderObject = renderObj; + mAlpha = alpha; +} diff --git a/game/bombSight.h b/game/bombSight.h new file mode 100644 index 0000000..8d28d8f --- /dev/null +++ b/game/bombSight.h @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BOMBSIGHT_H_ +#define _BOMBSIGHT_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif +#ifndef _RESMANAGER_H_ +#include "core/resManager.h" +#endif +class TSShape; +class TSShapeInstance; + +class BombSight : public SceneObject +{ + typedef SceneObject Parent; + Resource mMoveShape; + TSShapeInstance* mShapeInstance; + StringTableEntry mDtsFileName; + bool mRenderObject; + F32 mAlpha; + protected: + bool onAdd(); + void onRemove(); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + public: + BombSight(); + ~BombSight(); + + void setDtsShape(StringTableEntry); + void setRender(const bool , const F32 alpha = 0.0f); + + DECLARE_CONOBJECT(BombSight); +}; + +#endif // _H_BOMBSIGHT + diff --git a/game/camera.cc b/game/camera.cc new file mode 100644 index 0000000..4dd4b07 --- /dev/null +++ b/game/camera.cc @@ -0,0 +1,645 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "game/game.h" +#include "math/mMath.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/bitStream.h" +#include "core/dnet.h" +#include "game/camera.h" +#include "game/gameConnection.h" +#include "math/mathIO.h" +#include "editor/editor.h" + +#define MaxPitch 1.3962 +#define CameraRadius 0.05; + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(CameraData); + +void CameraData::consoleInit() +{ +} + +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; + +Camera::Camera() +{ + mNetFlags.clear(Ghostable); + mTypeMask |= CameraObjectType; + delta.pos = Point3F(0,0,100); + delta.rot = Point3F(0,0,0); + delta.posVec = delta.rotVec = VectorF(0,0,0); + mObjToWorld.setColumn(3,delta.pos); + mRot = delta.rot; + + mMinOrbitDist = 0; + mMaxOrbitDist = 0; + mCurOrbitDist = 0; + mOrbitObject = NULL; + mPosition.set(0.f, 0.f, 0.f); + mObservingClientObject = false; + mode = 2; +} + +Camera::~Camera() +{ +} + + +//---------------------------------------------------------------------------- + +bool Camera::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.max = mObjScale; + mObjBox.min = mObjScale; + mObjBox.min.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 + getEyeTransform(mat); +} + +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); + Point3F vec,pos; + if (move) { + // If using editor then force camera into fly mode + if(gEditingMission && mode != FlyMode) + setFlyMode(); + + // Update orientation + delta.rotVec = mRot; + mObjToWorld.getColumn(3,&delta.posVec); + + mRot.x += move->pitch; + if(mRot.x > MaxPitch) + mRot.x = MaxPitch; + else if(mRot.x < -MaxPitch) + mRot.x = -MaxPitch; + + mRot.z += move->yaw; + if (mRot.z > M_PI) + mRot.z -= M_2PI; + + if(mode == OrbitObjectMode || mode == OrbitPointMode) + { + if(mode == OrbitObjectMode && bool(mOrbitObject)) + mOrbitObject->getWorldBox().getCenter(&mPosition); + setPosition(mPosition, mRot); + validateEyePoint(1.0f, &mObjToWorld); + pos = mPosition; + } + else + { + // Update pos + bool faster = move->trigger[0] || move->trigger[1]; + F32 scale = mMovementSpeed * (faster + 1); + + mObjToWorld.getColumn(3,&pos); + mObjToWorld.getColumn(0,&vec); + pos += vec * move->x * TickSec * scale; + mObjToWorld.getColumn(1,&vec); + pos += vec * move->y * TickSec * scale; + mObjToWorld.getColumn(2,&vec); + pos += vec * move->z * TickSec * scale; + setPosition(pos,mRot); + } + + // If on the client, calc delta for backstepping + if (isClientObject()) { + delta.pos = pos; + delta.rot = mRot; + delta.posVec = delta.posVec - delta.pos; + delta.rotVec = delta.rotVec - delta.rot; + } + 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); + Point3F rot = delta.rot + delta.rotVec * dt; + + if(mode == OrbitObjectMode || mode == OrbitPointMode) + { + if(mode == OrbitObjectMode && bool(mOrbitObject)) + mOrbitObject->getRenderWorldBox().getCenter(&mPosition); + setPosition(mPosition, rot); + validateEyePoint(1.0f, &mObjToWorld); + } + else { + Point3F pos = delta.pos + delta.posVec * dt; + setPosition(pos,rot); + } +} + +void Camera::setPosition(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::setTransform(temp); + mRot = rot; +} + + +//---------------------------------------------------------------------------- + +bool Camera::writePacketData(GameConnection *connection, BitStream *bstream) +{ + // Update client regardless of status flags. + bool ret = Parent::writePacketData(connection, bstream); + + Point3F pos; + mObjToWorld.getColumn(3,&pos); + connection->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) ? getControllingClient()->getGhostIndex(mOrbitObject): -1; + if(gIndex == -1) + { + writeMode = OrbitPointMode; + mOrbitObject->getWorldBox().getCenter(&writePos); + ret = false; + } + } + 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, 10); + } + if (writeMode == OrbitPointMode) + connection->writeCompressed(bstream, writePos); + } + return ret; +} + +void Camera::readPacketData(GameConnection *connection, BitStream *bstream) +{ + Parent::readPacketData(connection, bstream); + Point3F pos,rot; + mathRead(*bstream, &pos); + bstream->read(&rot.x); + bstream->read(&rot.z); + + GameBase* obj = 0; + mode = 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(10); + obj = static_cast + (getControllingClient()->resolveGhost(gIndex)); + } + if (mode == OrbitPointMode) + connection->readCompressed(bstream, &mPosition); + } + if (obj != (GameBase*)mOrbitObject) { + if (mOrbitObject) { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + mOrbitObject = obj; + if (mOrbitObject) { + processAfter(mOrbitObject); + deleteNotify(mOrbitObject); + } + } + + getControllingClient()->setCompressionPoint(pos); + setPosition(pos,rot); + delta.pos = pos; + delta.rot = rot; + delta.rotVec.set(0,0,0); + delta.posVec.set(0,0,0); +} + +U32 Camera::packUpdate(NetConnection *con, U32 mask, BitStream *bstream) +{ + Parent::packUpdate(con,mask,bstream); + + // 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); + } + + return 0; +} + +void Camera::unpackUpdate(NetConnection *con, BitStream *bstream) +{ + Parent::unpackUpdate(con,bstream); + + // controlled by the client? + if(bstream->readFlag()) + return; + + 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); + + // New delta for client side interpolation + delta.pos = pos; + delta.rot = rot; + delta.posVec = delta.rotVec = VectorF(0,0,0); + } +} + + +//---------------------------------------------------------------------------- + +void Camera::initPersistFields() +{ + Parent::initPersistFields(); +} + +Point3F &Camera::getPosition() +{ + static Point3F position; + mObjToWorld.getColumn(3, &position); + return position; +} + +const char* cGetPosition(SimObject* obj, S32, const char**) +{ + static char buffer[100]; + Camera *camObj = (Camera *) obj; + + Point3F& pos = camObj->getPosition(); + dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z); + return buffer; +} + +void cSetOrbitMode(SimObject *obj, S32 argc, const char **argv) +{ + Point3F pos; + AngAxisF aa; + F32 minDis, maxDis, curDis; + + Camera *camObj = (Camera *) obj; + GameBase *orbitObject = NULL; + if(Sim::findObject(argv[2],orbitObject) == false) + { + Con::warnf("Cannot orbit non-existing object."); + camObj->setFlyMode(); + return; + } + + dSscanf(argv[3],"%f %f %f %f %f %f %f", + &pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle); + minDis = dAtof(argv[4]); + maxDis = dAtof(argv[5]); + curDis = dAtof(argv[6]); + + camObj->setControlDirty(); + camObj->setOrbitMode(orbitObject, pos, aa, minDis, maxDis, curDis, (argc == 8) ? dAtob(argv[7]) : false); +} + +void cSetFlyMode(SimObject *obj, S32, const char **) +{ + Camera *camObj = (Camera *) obj; + camObj->setControlDirty(); + camObj->setFlyMode(); +} + + +void Camera::consoleInit() +{ + Con::addVariable("Camera::movementSpeed",TypeF32,&mMovementSpeed); + Con::addCommand("Camera", "getPosition", cGetPosition, "camera.getPosition()", 2, 2); + Con::addCommand("Camera", "setOrbitMode", cSetOrbitMode, "camera.setOrbitMode(obj, Transform, min-dist, max-dist, cur-dist, )", 7, 8); + Con::addCommand("Camera", "setFlyMode", cSetFlyMode, "camera.setFlyMode()", 2, 2); +} + + +//---------------------------------------------------------------------------- + +void Camera::renderImage(SceneState*, SceneRenderImage*) +{ + if(gEditingMission) + { + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + glScalef(mObjScale.x,mObjScale.y,mObjScale.z); + wireCube(Point3F(1, 1, 1),Point3F(0,0,0)); + glPopMatrix(); + } +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +// NEW Observer Code +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +void Camera::setFlyMode() +{ + mode = FlyMode; + + if(bool(mOrbitObject)) { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + mOrbitObject = NULL; +} + +void Camera::setOrbitMode(GameBase *obj, Point3F &pos, AngAxisF &rot, float minDist, float maxDist, float curDist, bool ownClientObject) +{ + mObservingClientObject = ownClientObject; + + rot; + 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; + } + + QuatF q(rot); + MatrixF tempMat(true); + q.setMatrix(&tempMat); + Point3F dir; + tempMat.getColumn(1, &dir); + + setPosition(mPosition, dir); + + mMinOrbitDist = minDist; + mMaxOrbitDist = maxDist; + mCurOrbitDist = curDist; +} + + +void Camera::validateEyePoint(F32 pos, MatrixF *mat) +{ + if (pos != 0) { + // Use the eye transform to orient the camera + Point3F dir; + mat->getColumn(1, &dir); + pos *= mMaxOrbitDist - mMinOrbitDist; + // Use the camera node's pos. + Point3F startPos; + Point3F endPos; + mObjToWorld.getColumn(3,&startPos); + + // Make sure we don't extend the camera into anything solid + if(mOrbitObject) + mOrbitObject->disableCollision(); + disableCollision(); + RayInfo collision; + U32 mask = TerrainObjectType | + InteriorObjectType | + WaterObjectType | + ForceFieldObjectType | + 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.01) + { + float colDist = mDot(startPos - collision.point, dir) - (1 / dot) * CameraRadius; + if(colDist > pos) + colDist = pos; + if(colDist < 0) + colDist = 0; + 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, 0)); + zRot.set(EulerF(0, 0, 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(-mAtan(vec.z, mSqrt(vec.x*vec.x + vec.y*vec.y)),0,-mAtan(-vec.x,vec.y)); + setPosition(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; +} + diff --git a/game/camera.h b/game/camera.h new file mode 100644 index 0000000..decec0c --- /dev/null +++ b/game/camera.h @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CAMERA_H_ +#define _CAMERA_H_ + +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif + +//---------------------------------------------------------------------------- +struct CameraData: public ShapeBaseData { + typedef ShapeBaseData Parent; + + // + DECLARE_CONOBJECT(CameraData); + static void consoleInit(); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- +class Camera: public ShapeBase +{ + typedef ShapeBase Parent; + + enum MaskBits { + MoveMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + struct StateDelta { + Point3F pos; + Point3F rot; + VectorF posVec; + VectorF rotVec; + }; + Point3F mRot; + StateDelta delta; + + static F32 mMovementSpeed; + + void setPosition(const Point3F& pos,const Point3F& viewRot); + + SimObjectPtr mOrbitObject; + F32 mMinOrbitDist; + F32 mMaxOrbitDist; + F32 mCurOrbitDist; + Point3F mPosition; + bool mObservingClientObject; + + enum + { + StationaryMode = 0, + + FreeRotateMode = 1, + FlyMode = 2, + OrbitObjectMode = 3, + OrbitPointMode = 4, + + CameraFirstMode = 0, + CameraLastMode = 4 + }; + int mode; + void setPosition(const Point3F& pos,const Point3F& viewRot, MatrixF *mat); + void setTransform(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(); + + void onEditorEnable(); + void onEditorDisable(); + + bool onAdd(); + void onRemove(); + void renderImage(SceneState* state, SceneRenderImage*); + void processTick(const Move*); + void interpolateTick(F32 dt); + void getCameraTransform(F32* pos,MatrixF* mat); + + bool writePacketData(GameConnection *connection, BitStream *stream); + void readPacketData(GameConnection *connection, BitStream *stream); + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); + Point3F &getPosition(); + void setFlyMode(); + void setOrbitMode(GameBase *obj, Point3F &pos, AngAxisF &rot, + F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject); + void validateEyePoint(F32 pos, MatrixF *mat); + void onDeleteNotify(SimObject *obj); + + GameBase * getOrbitObject() { return(mOrbitObject); } + bool isObservingClientObject() { return(mObservingClientObject); } +}; + + +#endif diff --git a/game/cameraFXMgr.cc b/game/cameraFXMgr.cc new file mode 100644 index 0000000..077658b --- /dev/null +++ b/game/cameraFXMgr.cc @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "game/cameraFXMgr.h" +#include "math/mRandom.h" +#include "math/mMatrix.h" +#include "dgl/dgl.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.link( newFX ); +} + +//-------------------------------------------------------------------------- +// Clear all currently running camera effects +//-------------------------------------------------------------------------- +void CameraFXManager::clear() +{ + mFXList.free(); +} + +//-------------------------------------------------------------------------- +// Update camera effects +//-------------------------------------------------------------------------- +void CameraFXManager::update( F32 dt ) +{ + CameraFXPtr *cur = NULL; + mCamFXTrans.identity(); + + for( cur = mFXList.next( cur ); cur; cur = mFXList.next( cur ) ) + { + CameraFX * curFX = *cur; + curFX->update( dt ); + MatrixF fxTrans = curFX->getTrans(); + + mCamFXTrans.mul( fxTrans ); + + if( curFX->isExpired() ) + { + CameraFXPtr *prev = mFXList.prev( cur ); + mFXList.free( cur ); + cur = prev; + } + } +} diff --git a/game/cameraFXMgr.h b/game/cameraFXMgr.h new file mode 100644 index 0000000..87c1689 --- /dev/null +++ b/game/cameraFXMgr.h @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CAMERAFXMGR_H_ +#define _CAMERAFXMGR_H_ + +#ifndef _LLIST_H_ +#include "core/llist.h" +#endif +#ifndef _MPOINT_H_ +#include "math/mPoint.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif + +//************************************************************************** +// Abstract camera effect template +//************************************************************************** +class CameraFX +{ +protected: + F32 mElapsedTime; + F32 mDuration; + MatrixF mCamFXTrans; + +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; + + LList< CameraFXPtr > mFXList; + MatrixF mCamFXTrans; + +public: + void addFX( CameraFX *newFX ); + void clear(); + MatrixF & getTrans(){ return mCamFXTrans; } + void update( F32 dt ); + + CameraFXManager(); + ~CameraFXManager(); + +}; + +extern CameraFXManager gCamFXMgr; + + +#endif diff --git a/game/collisionTest.cc b/game/collisionTest.cc new file mode 100644 index 0000000..828c442 --- /dev/null +++ b/game/collisionTest.cc @@ -0,0 +1,216 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "game/game.h" +#include "math/mMath.h" +#include "console/simBase.h" +#include "console/consoleTypes.h" +#include "console/console.h" +#include "game/collisionTest.h" + +static F32 BoxSize = 2; + +bool CollisionTest::testPolytope = false; +bool CollisionTest::testClippedPolyList = false; +bool CollisionTest::testDepthSortList = false; +bool CollisionTest::testExtrudedPolyList = false; +bool CollisionTest::depthSort = false; +bool CollisionTest::depthRender = false; +bool CollisionTest::renderAlways = false; + + +//---------------------------------------------------------------------------- + +CollisionTest::CollisionTest() +{ +} + +CollisionTest::~CollisionTest() +{ +} + +void CollisionTest::consoleInit() +{ + Con::addVariable("Collision::boxSize",TypeF32,&BoxSize); + + Con::addVariable("Collision::testPolytope",TypeBool,&testPolytope); + Con::addVariable("Collision::testClippedPolyList",TypeBool,&testClippedPolyList); + Con::addVariable("Collision::testExtrudedPolyList",TypeBool,&testExtrudedPolyList); + Con::addVariable("Collision::testDepthSortList",TypeBool,&testDepthSortList); + + Con::addVariable("Collision::depthSort",TypeBool,&depthSort); + Con::addVariable("Collision::depthRender",TypeBool,&depthRender); + Con::addVariable("Collision::renderAlways",TypeBool,&renderAlways); +} + +void CollisionTest::collide(const MatrixF& transform) +{ + // + Point3F pos; + transform.getColumn(3,&pos); + boundingBox.min = pos - Point3F(BoxSize,BoxSize,BoxSize); + boundingBox.max = pos + Point3F(BoxSize,BoxSize,BoxSize); + boundingSphere.center = pos; + boundingSphere.radius = BoxSize * 1.5; + + if (testPolytope) { + MatrixF imat(true); + volume.buildBox(imat,boundingBox); + tree.clear(); + } + + if (testClippedPolyList) { + polyList.clear(); + polyList.mPlaneList.clear(); + polyList.mNormal.set(0,0,0); + + // Planes bounding the square. + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(boundingBox.min,VectorF(-1,0,0)); + polyList.mPlaneList[1].set(boundingBox.max,VectorF(0,1,0)); + polyList.mPlaneList[2].set(boundingBox.max,VectorF(1,0,0)); + polyList.mPlaneList[3].set(boundingBox.min,VectorF(0,-1,0)); + polyList.mPlaneList[4].set(boundingBox.min,VectorF(0,0,-1)); + polyList.mPlaneList[5].set(boundingBox.max,VectorF(0,0,1)); + } + + if (testDepthSortList) { + depthSortList.clear(); + mDepthSortExtent.set(5,20,5); // hard-code for now + MatrixF mat = transform; + mat.inverse(); // we want world to camera (or whatever transform represents) + depthSortList.set(mat,mDepthSortExtent); + + // we use a different box and sphere... + // our box starts at the camera and goes forward mDepthSortExtent.y + // with width and height of mDepthSortExtent.x and mDepthSortExtent.z + Point3F x,y,z,p; + transform.getColumn(0,&x); + transform.getColumn(1,&y); + transform.getColumn(2,&z); + transform.getColumn(3,&p); + x *= 0.5f * mDepthSortExtent.x; + y *= mDepthSortExtent.y; + z *= 0.5f * mDepthSortExtent.z; + Point3F boxMin = p; + Point3F boxMax = p; + boxMin.setMin(p-x-z); + boxMin.setMin(p-x+z); + boxMin.setMin(p+x-z); + boxMin.setMin(p+x+z); + boxMin.setMin(p-x-z+y); + boxMin.setMin(p-x+z+y); + boxMin.setMin(p+x-z+y); + boxMin.setMin(p+x+z+y); + + boxMax.setMax(p-x-z); + boxMax.setMax(p-x+z); + boxMax.setMax(p+x-z); + boxMax.setMax(p+x+z); + boxMax.setMax(p-x-z+y); + boxMax.setMax(p-x+z+y); + boxMax.setMax(p+x-z+y); + boxMax.setMax(p+x+z+y); + + Point3F boxCenter = boxMin + boxMax; + boxCenter *= 0.5f; + F32 boxRadius = (boxMax-boxMin).len(); + + mDepthBox.min = boxMin; + mDepthBox.max = boxMax; + mDepthSphere.center = boxCenter; + mDepthSphere.radius = boxRadius; + } + + if (testExtrudedPolyList) { + MatrixF imat(1); + polyhedron.buildBox(imat,boundingBox); + VectorF v1(0,3,0); + transform.mulV(v1,&extrudeVector); + extrudedList.extrude(polyhedron,extrudeVector); + extrudedList.setVelocity(extrudeVector); + extrudedList.setCollisionList(&collisionList); + + Point3F p1 = pos + extrudeVector; + boundingBox.min = boundingBox.max = pos; + boundingBox.min.setMin(p1); + boundingBox.max.setMax(p1); + boundingBox.min -= Point3F(BoxSize,BoxSize,BoxSize); + boundingBox.max += Point3F(BoxSize,BoxSize,BoxSize); + boundingSphere.radius += extrudeVector.len(); + } + + if (testPolytope || testClippedPolyList || testExtrudedPolyList || testDepthSortList) { + testPos = boundingSphere.center; + gClientContainer.findObjects(-1,CollisionTest::callback,S32(this)); + } + + if (testExtrudedPolyList) { + extrudedList.adjustCollisionTime(); + } +} + +void CollisionTest::callback(SceneObject* obj, S32 thisPtr) +{ + CollisionTest* ptr = reinterpret_cast(thisPtr); + + if (testPolytope) { + if (BSPNode* root = obj->buildCollisionBSP(&ptr->tree,ptr->boundingBox,ptr->boundingSphere)) + ptr->volume.intersect(obj,root); + } + if (testClippedPolyList) { + obj->buildPolyList(&ptr->polyList,ptr->boundingBox,ptr->boundingSphere); + } + if (testExtrudedPolyList) { + obj->buildPolyList(&ptr->extrudedList,ptr->boundingBox,ptr->boundingSphere); + } + if (testDepthSortList) { + obj->buildPolyList(&ptr->depthSortList,ptr->mDepthBox,ptr->mDepthSphere); + } +} + +extern void wireCube(F32 size,Point3F pos); + +void CollisionTest::render() +{ + bool collision = false; + if (testPolytope || renderAlways) { + if (volume.didIntersect()) + volume.render(); + } + if (testClippedPolyList || renderAlways) { + if (polyList.mPolyList.size()) + collision = true; + polyList.render(); + } + if (testExtrudedPolyList || renderAlways) { + if (collisionList.count) + collision = true; + extrudedList.render(); + glPushAttrib(GL_DEPTH_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + polyhedron.render(extrudeVector,collisionList.t); + glPopAttrib(); + } + if (testDepthSortList || renderAlways) { + if (depthSort && testDepthSortList) + depthSortList.sort(); + // should we write depth values of polys... + // if polys are correctly sorted then writing depth values + // should result in no overlap of polys when looking down + // from camera...otoh, if polys are out of order, we should + // see overlap + DepthSortList::renderWithDepth = depthRender; + depthSortList.render(); + } + + if (collision || renderAlways) + wireCube(BoxSize,testPos); +} + diff --git a/game/collisionTest.h b/game/collisionTest.h new file mode 100644 index 0000000..0156972 --- /dev/null +++ b/game/collisionTest.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLISIONTEST_H_ +#define _COLLISIONTEST_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif + +#ifndef _POLYTOPE_H_ +#include "collision/polytope.h" +#endif +#ifndef _CLIPPEDPOLYLIST_H_ +#include "collision/clippedPolyList.h" +#endif +#ifndef _EXTRUDEDPOLYLIST_H_ +#include "collision/extrudedPolyList.h" +#endif +#ifndef _DEPTHSORTLIST_H_ +#include "collision/depthSortList.h" +#endif +#ifndef _POLYHEDRON_H_ +#include "collision/polyhedron.h" +#endif + +struct CollisionTest +{ + Box3F boundingBox; + SphereF boundingSphere; + static bool renderAlways; + + Point3F testPos; + + // use a slightly different box and sphere for depthSortList + Box3F mDepthBox; + SphereF mDepthSphere; + Point3F mDepthSortExtent; + + // Polytopte/BSP test + static bool testPolytope; + BSPTree tree; + Polytope volume; + + // Clipped polylists + static bool testClippedPolyList; + ClippedPolyList polyList; + + // Depth sort poly lists + static bool testDepthSortList; + static bool depthSort; + static bool depthRender; + DepthSortList depthSortList; + + // Extruded + CollisionList collisionList; + static bool testExtrudedPolyList; + Polyhedron polyhedron; + VectorF extrudeVector; + ExtrudedPolyList extrudedList; + + CollisionTest(); + ~CollisionTest(); + void consoleInit(); + static void callback(SceneObject*, S32 thisPtr); + void collide(const MatrixF& transform); + void render(); +}; + + +#endif diff --git a/game/commanderMapIcon.cc b/game/commanderMapIcon.cc new file mode 100644 index 0000000..38e86c3 --- /dev/null +++ b/game/commanderMapIcon.cc @@ -0,0 +1,225 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/commanderMapIcon.h" +#include "console/consoleTypes.h" +#include "dgl/materialList.h" +#include "dgl/gTexManager.h" +#include "core/resManager.h" +#include "core/bitStream.h" +#include "math/mMath.h" + +//------------------------------------------------------------------------------ +// Class:: CommanderIconImage +//------------------------------------------------------------------------------ +CommanderIconImage::CommanderIconImage() +{ + mType = Static; + mOverlay = false; + mModulate = true; + mMaterialList = 0; + mTexture = 0; + mAnimationType = Looping; + mAnimationSpeed = 100; +} + +CommanderIconImage::~CommanderIconImage() +{ + delete mMaterialList; +} + +// "type image overlay modulate " +CommanderIconImage * CommanderIconImage::construct(const char * desc) +{ + if(!desc || !desc[0]) + return(0); + + char buf[1024]; + dStrncpy(buf, desc, sizeof(buf)); + + char * typeStr = dStrtok(buf, " "); + char * imageStr = dStrtok(0, " "); + char * overlayStr = dStrtok(0, " "); + char * modulateStr = dStrtok(0, " "); + + if(!typeStr || !imageStr || !overlayStr || !modulateStr) + { + Con::errorf(ConsoleLogEntry::General, "CommanderIconImage::construct: invalid fields"); + return(0); + } + + CommanderIconImage * image = new CommanderIconImage(); + + // type: + if(!dStricmp(typeStr, "static")) + image->mType = Static; + else if(!dStricmp(typeStr, "animation")) + image->mType = Animation; + else + goto Failed; + + // overlay/modulate: + image->mOverlay = dAtob(overlayStr); + image->mModulate = dAtob(modulateStr); + + // image/animation?: + if(image->mType == Animation) // dml + { + char * animType = dStrtok(0, " "); + char * animSpeed = dStrtok(0, " "); + + if(!animType || !animSpeed) + goto Failed; + + if(!dStricmp(animType, "looping")) + image->mAnimationType = Looping; + else if(!dStricmp(animType, "flipflop")) + image->mAnimationType = FlipFlop; + else if(!dStricmp(animType, "oneshot")) + image->mAnimationType = OneShot; + else + goto Failed; + + image->mAnimationSpeed = dAtoi(animSpeed); + + char fileBuf[256]; + dSprintf(fileBuf, sizeof(fileBuf), "textures/commander/icons/%s.dml", imageStr); + + Stream * stream = ResourceManager->openStream(fileBuf); + if(!stream) + goto Failed; + + image->mMaterialList = new MaterialList; + image->mMaterialList->setTextureType(BitmapKeepTexture); + + bool ret = image->mMaterialList->read(*stream); + ResourceManager->closeStream(stream); + + if(!ret) + goto Failed; + + if(!image->mMaterialList->load()) + goto Failed; + + if(!image->mMaterialList->size()) + goto Failed; + } + else // image + { + char fileBuf[256]; + dSprintf(fileBuf, sizeof(fileBuf), "commander/icons/%s", imageStr); + image->mTexture.set(fileBuf, BitmapKeepTexture, false); + if(!bool(image->mTexture)) + goto Failed; + } + return(image); + + Failed: + delete image; + Con::errorf(ConsoleLogEntry::General, "CommanderIconImage::construct: failed to construct image '%s'", desc); + return(0); +} + +bool CommanderIconImage::getFrameSize(Point2I & size, U32 frame) +{ + switch(mType) + { + case Static: + { + if(!bool(mTexture) || frame) + return(false); + size.x = mTexture.getWidth(); + size.y = mTexture.getHeight(); + return(true); + } + + case Animation: + { + if(!mMaterialList || (frame >= mMaterialList->size())) + return(false); + TextureHandle handle = mMaterialList->getMaterial(frame); + if(!bool(handle)) + return(false); + size.x = handle.getWidth(); + size.y = handle.getHeight(); + return(true); + } + } + return(false); +} + +//------------------------------------------------------------------------------ +// Class: CommanderIconData +//------------------------------------------------------------------------------ +IMPLEMENT_CO_DATABLOCK_V1(CommanderIconData); + +CommanderIconData::CommanderIconData() +{ + mLoaded = false; + for(U32 i = 0; i < NumImages; i++) + { + mImages[i] = 0; + mImageDesc[i] = StringTable->insert(""); + } +} + +CommanderIconData::~CommanderIconData() +{ + for(U32 i = 0; i < NumImages; i++) + delete mImages[i]; +} + +void CommanderIconData::packData(BitStream * stream) +{ + Parent::packData(stream); + for(U32 i = 0; i < NumImages; i++) + stream->writeString(mImageDesc[i]); +} + +void CommanderIconData::unpackData(BitStream * stream) +{ + Parent::unpackData(stream); + for(U32 i = 0; i < NumImages; i++) + mImageDesc[i] = stream->readSTString(); +} + +bool CommanderIconData::preload(bool server, char errorBuffer[256]) +{ + if(!Parent::preload(server, errorBuffer)) + return(false); + + if(!server && !mLoaded) + { + for(U32 i = 0; i < NumImages; i++) + mImages[i] = CommanderIconImage::construct(mImageDesc[i]); + mLoaded = true; + } + + return(true); +} + +//------------------------------------------------------------------------------ +IMPLEMENT_SETDATATYPE(CommanderIconData) +IMPLEMENT_GETDATATYPE(CommanderIconData) + +void CommanderIconData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("images", TypeString, Offset(mImageDesc, CommanderIconData), NumImages); + addField("baseImage", TypeString, Offset(mImageDesc[BaseImage], CommanderIconData)); + addField("activeImage", TypeString, Offset(mImageDesc[ActiveImage], CommanderIconData)); + addField("inactiveImage", TypeString, Offset(mImageDesc[InactiveImage], CommanderIconData)); + addField("selectImage", TypeString, Offset(mImageDesc[SelectImage], CommanderIconData)); + addField("hilightImage", TypeString, Offset(mImageDesc[HilightImage], CommanderIconData)); + + Con::registerType(TypeCommanderIconDataPtr, sizeof(CommanderIconData*), + REF_GETDATATYPE(CommanderIconData), + REF_SETDATATYPE(CommanderIconData)); +} + + diff --git a/game/commanderMapIcon.h b/game/commanderMapIcon.h new file mode 100644 index 0000000..7e67e3d --- /dev/null +++ b/game/commanderMapIcon.h @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COMMANDERMAPICON_H_ +#define _COMMANDERMAPICON_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif + +class MaterialList; + +//------------------------------------------------------------------------------ +class CommanderIconImage +{ + public: + enum Type { + Static = 0, + Animation + }; + Type mType; + + bool mOverlay; + bool mModulate; + + TextureHandle mTexture; + MaterialList * mMaterialList; + + enum AnimationType { + Looping, + FlipFlop, + OneShot, + }; + AnimationType mAnimationType; + S32 mAnimationSpeed; + + CommanderIconImage(); + ~CommanderIconImage(); + + bool getFrameSize(Point2I &, U32 frame = 0); + + static CommanderIconImage * construct(const char *); +}; + +//------------------------------------------------------------------------------ +class CommanderIconData : public SimDataBlock +{ + private: + typedef SimDataBlock Parent; + + public: + enum { + BaseImage = 0, + ActiveImage, + InactiveImage, + SelectImage, + HilightImage, + + NumImages + }; + CommanderIconImage * mImages[NumImages]; + + private: + StringTableEntry mImageDesc[NumImages]; + bool mLoaded; + + public: + CommanderIconData(); + ~CommanderIconData(); + + void packData(BitStream *); + void unpackData(BitStream *); + bool preload(bool, char errorBuffer[256]); + + static void initPersistFields(); + + DECLARE_CONOBJECT(CommanderIconData); +}; + +#endif diff --git a/game/debris.cc b/game/debris.cc new file mode 100644 index 0000000..e89c1b6 --- /dev/null +++ b/game/debris.cc @@ -0,0 +1,979 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/Debris.h" +#include "console/consoleTypes.h" +#include "console/consoleObject.h" +#include "game/particleEngine.h" +#include "core/bitStream.h" +#include "scenegraph/sceneGraph.h" +#include "game/explosion.h" +#include "dgl/dgl.h" +#include "scenegraph/sceneState.h" +#include "ts/tsShapeInstance.h" +#include "ts/tsPartInstance.h" +#include "scenegraph/detailManager.h" +#include "math/mathUtils.h" +#include "sim/netConnection.h" + +const U32 csmStaticCollisionMask = TerrainObjectType | + InteriorObjectType; + +const U32 csmDynamicCollisionMask = StaticShapeObjectType | + ForceFieldObjectType | + TurretObjectType; + + + +//************************************************************************** +// Debris Data +//************************************************************************** + +IMPLEMENT_CO_DATABLOCK_V1(DebrisData); + +//-------------------------------------------------------------------------- +// Constructor +//-------------------------------------------------------------------------- +DebrisData::DebrisData() +{ + dMemset( emitterList, 0, sizeof( emitterList ) ); + dMemset( emitterIDList, 0, sizeof( emitterIDList ) ); + + explosion = NULL; + explosionId = 0; + + velocity = 0.0; + velocityVariance = 0.0; + elasticity = 0.3; + friction = 0.2; + numBounces = 0; + bounceVariance = 0; + minSpinSpeed = maxSpinSpeed = 0.0; + render2D = false; + staticOnMaxBounce = false; + explodeOnMaxBounce = false; + snapOnMaxBounce = false; + lifetime = 3.0; + lifetimeVariance = 0.0; + minSpinSpeed = 0.0; + maxSpinSpeed = 0.0; + textureName = NULL; + mTypeMask |= DebrisObjectType; + shapeName = NULL; + fade = true; + useRadiusMass = false; + baseRadius = 1.0; + gravModifier = 1.0; + terminalVelocity = 0.0; + ignoreWater = true; +} + + +//-------------------------------------------------------------------------- +// Initialize - Check data +//-------------------------------------------------------------------------- +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.0 || friction > 10.0 ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: friction invalid", getName()); + friction = 0.2; + } + if( elasticity < -10.0 || elasticity > 10.0 ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: elasticity invalid", getName()); + elasticity = 0.2; + } + if( lifetime < 0.0 || lifetime > 1000.0 ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: lifetime invalid", getName()); + lifetime = 3.0; + } + if( lifetimeVariance < 0.0 || lifetimeVariance > lifetime ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: lifetimeVariance invalid", getName()); + lifetimeVariance = 0.0; + } + 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.0 || minSpinSpeed > 10000.0 || minSpinSpeed > maxSpinSpeed ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: minSpinSpeed invalid", getName()); + minSpinSpeed = maxSpinSpeed - 1.0; + } + if( maxSpinSpeed < -10000.0 || maxSpinSpeed > 10000.0 ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: maxSpinSpeed invalid", getName()); + maxSpinSpeed = 0.0; + } + + + + + return true; +} + +//-------------------------------------------------------------------------- +// Preload +//-------------------------------------------------------------------------- +bool DebrisData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if( server ) return true; + + if( shapeName && shapeName[0] != '\0' && !bool(shape) ) + { + char fullName[256]; + dSprintf(fullName, sizeof(fullName), "shapes/%s", shapeName); + + shape = ResourceManager->load(fullName); + if( bool(shape) == false ) + { + dSprintf(errorBuffer, sizeof(errorBuffer), "DebrisData::load: Couldn't load shape \"%s\"", shapeName); + return false; + } + else + { + TSShapeInstance* pDummy = new TSShapeInstance(shape, !server); + delete pDummy; + } + + } + + return true; +} + +//-------------------------------------------------------------------------- +// Initialize console fields (static) +//-------------------------------------------------------------------------- +IMPLEMENT_SETDATATYPE(DebrisData) +IMPLEMENT_GETDATATYPE(DebrisData) + +void DebrisData::initPersistFields() +{ + Parent::initPersistFields(); + + Con::registerType(TypeDebrisDataPtr, sizeof(DebrisData*), + REF_GETDATATYPE(DebrisData), + REF_SETDATATYPE(DebrisData)); + + addField("texture", TypeString, Offset(textureName, DebrisData)); + addField("emitters", TypeParticleEmitterDataPtr, Offset(emitterList, DebrisData), DDC_NUM_EMITTERS); + addField("explosion", TypeExplosionDataPtr, Offset(explosion, DebrisData)); + 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("render2D", TypeBool, Offset(render2D, DebrisData)); + addField("shapeName", TypeString, Offset(shapeName, DebrisData)); + addField("explodeOnMaxBounce", TypeBool, Offset(explodeOnMaxBounce, DebrisData)); + addField("staticOnMaxBounce", TypeBool, Offset(staticOnMaxBounce, DebrisData)); + addField("snapOnMaxBounce", TypeBool, Offset(snapOnMaxBounce, DebrisData)); + addField("lifetime", TypeF32, Offset(lifetime, DebrisData)); + addField("lifetimeVariance", TypeF32, Offset(lifetimeVariance, DebrisData)); + addField("velocity", TypeF32, Offset(velocity, DebrisData)); + addField("velocityVariance", TypeF32, Offset(velocityVariance, DebrisData)); + addField("fade", TypeBool, Offset(fade, DebrisData)); + addField("useRadiusMass", TypeBool, Offset(useRadiusMass, DebrisData)); + addField("baseRadius", TypeF32, Offset(baseRadius, DebrisData)); + addField("gravModifier", TypeF32, Offset(gravModifier, DebrisData)); + addField("terminalVelocity", TypeF32, Offset(terminalVelocity, DebrisData)); + addField("ignoreWater", TypeBool, Offset(ignoreWater, DebrisData)); + + +} + +//-------------------------------------------------------------------------- +// Pack data +//-------------------------------------------------------------------------- +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(minSpinSpeed); + stream->write(maxSpinSpeed); + 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); + } + +} + + +//-------------------------------------------------------------------------- +// Unpack data +//-------------------------------------------------------------------------- +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(&minSpinSpeed); + stream->read(&maxSpinSpeed); + 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; + } + +} + + +//************************************************************************** +// Debris +//************************************************************************** + +IMPLEMENT_CO_NETOBJECT_V1(Debris); + +//---------------------------------------------------------------------------- +// Initialize debris piece +//---------------------------------------------------------------------------- +static bool cInitDebris( SimObject *obj, S32, const char **argv ) +{ + Debris* debris = static_cast(obj); + + 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 ); + + debris->init( pos, vel ); + + return true; +} + +//---------------------------------------------------------------------------- +// Constructor +//---------------------------------------------------------------------------- +Debris::Debris() +{ + mVelocity = Point3F( 0.0, 0.0, 4.0 ); + mLifetime = gRandGen.randF( 1.0, 10.0 ); + mLastPos = getPosition(); + mNumBounces = gRandGen.randI( 0, 1 ); + mSize = 2.0; + mElapsedTime = 0.0; + mShape = NULL; + mPart = NULL; + mXRotSpeed = 0.0; + mZRotSpeed = 0.0; + mInitialTrans.identity(); + mRadius = 0.2; + mStatic = false; + + dMemset( mEmitterList, 0, sizeof( mEmitterList ) ); +} + +//---------------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------------- +Debris::~Debris() +{ + if( mShape ) + { + delete mShape; + mShape = NULL; + } + + if( mPart ) + { + delete mPart; + mPart = NULL; + } +} + +//---------------------------------------------------------------------------- +// Init class (static) +//---------------------------------------------------------------------------- +void Debris::initPersistFields() +{ + addField("lifetime", TypeF32, Offset(mLifetime, Debris)); +} + +//---------------------------------------------------------------------------- +// Console init (static) +//---------------------------------------------------------------------------- +void Debris::consoleInit() +{ + Con::addCommand("Debris", "init", cInitDebris, "obj.init( position, velocity )", 4, 4); +} + +//---------------------------------------------------------------------------- +// Init +//---------------------------------------------------------------------------- +void Debris::init( const Point3F &position, const Point3F &velocity ) +{ + setPosition( position ); + setVelocity( velocity ); +} + +//---------------------------------------------------------------------------- +// On new data block +//---------------------------------------------------------------------------- +bool Debris::onNewDataBlock( GameBaseData* dptr ) +{ + mDataBlock = dynamic_cast< DebrisData* >( dptr ); + if( !mDataBlock || !Parent::onNewDataBlock( dptr ) ) + return false; + + scriptOnNewDataBlock(); + return true; + +} + +//---------------------------------------------------------------------------- +// On Add +//---------------------------------------------------------------------------- +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[ParticleEngine::PC_SIZE_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 = gRandGen.randI( -mDataBlock->bounceVariance, mDataBlock->bounceVariance ); + 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.1, 0.5 ); + + mRotAngles.set( xRotSpeed, 0.0, 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::getServerConnection(); + AssertFatal(pNC != NULL, "Error, must have a connection to the server!"); + pNC->addObject(this); + + return true; +} + +//---------------------------------------------------------------------------- +// On Remove +//---------------------------------------------------------------------------- +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(); +} + +//---------------------------------------------------------------------------- +// Process tick +//---------------------------------------------------------------------------- +void Debris::processTick(const Move*) +{ + if (mLifetime <= 0.0) + deleteObject(); +} + +//---------------------------------------------------------------------------- +// Advance Time +//---------------------------------------------------------------------------- +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.0, 0.0, 0.10 ) ); + + setTransform( newTrans ); + } + if( mDataBlock->staticOnMaxBounce ) + { + mStatic = true; + } + } + } + else + { + setPosition( nextPos ); + } + } + + Point3F pos( getPosition( ) ); + updateEmitters( pos, mVelocity, dt * 1000.0 ); + +} + +//---------------------------------------------------------------------------- +// Rotate debris +//---------------------------------------------------------------------------- +void Debris::rotate( F32 dt ) +{ + + MatrixF curTrans = getTransform(); + curTrans.setPosition( Point3F(0.0, 0.0, 0.0) ); + + Point3F curAngles = mRotAngles * dt * M_PI/180.0; + MatrixF rotMatrix( EulerF( curAngles.x, curAngles.y, curAngles.z ) ); + + curTrans.mul( rotMatrix ); + curTrans.setPosition( getPosition() ); + setTransform( curTrans ); + +} + +//---------------------------------------------------------------------------- +// Bounce the debris - returns true if debris bounces +//---------------------------------------------------------------------------- +bool Debris::bounce( const Point3F &nextPos, F32 dt ) +{ + Point3F curPos = getPosition(); + + Point3F dir = nextPos - curPos; + if( dir.magnitudeSafe() == 0.0 ) 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.0); + 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; + +} + +//---------------------------------------------------------------------------- +// Explode +//---------------------------------------------------------------------------- +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; +} + +//---------------------------------------------------------------------------- +// Compute state of debris as if it hasn't collided with anything +//---------------------------------------------------------------------------- +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; + +} + +//---------------------------------------------------------------------------- +// Update emitters +//---------------------------------------------------------------------------- +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 ); + } + } + +} + +//---------------------------------------------------------------------------- +// Render debris +//---------------------------------------------------------------------------- +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->getCameraPosition(); + F32 dist = cameraOffset.len(); + F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); + + if( mShape ) + { + DetailManager::selectPotentialDetails(mShape,dist,invScale); + if( mShape->getCurrentDetail() < 0 ) + { + return false; + } + } + + if( mPart ) + { + DetailManager::selectPotentialDetails(mPart,dist,invScale); + } + + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + + state->insertRenderImage(image); + } + + return false; +} + +//---------------------------------------------------------------------------- +// Render Object +//---------------------------------------------------------------------------- +void Debris::renderObject(SceneState* state, SceneRenderImage* ) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + F32 alpha = 1.0; + if( mDataBlock->fade ) + { + if( mLifetime < 1.0 ) alpha = mLifetime; + } + + if( (mShape && DetailManager::selectCurrentDetail(mShape)) || + (mPart && DetailManager::selectCurrentDetail(mPart)) ) + { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + installLights(); + + Point3F cameraOffset; + mObjToWorld.getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 fogAmount = state->getHazeAndFog(cameraOffset.len(),cameraOffset.z); + + if( mShape ) + { + TSMesh::setOverrideFade( alpha ); + mShape->setupFog(fogAmount, state->getFogColor()); + mShape->render(); + TSMesh::setOverrideFade( 1.0 ); + } + else + { + if (mPart->getCurrentObjectDetail() != -1) + { + TSShapeInstance *parent = mPart->getSourceShapeInstance(); + + parent->setupFog(fogAmount, state->getFogColor()); + TSMesh::setOverrideFade( alpha ); + mPart->render(); + TSMesh::setOverrideFade( 1.0 ); + } + } + + uninstallLights(); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + + render2D(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + dglSetCanonicalState(); + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//---------------------------------------------------------------------------- +// Render 2D debris +//---------------------------------------------------------------------------- +void Debris::render2D() +{ + if( !mDataBlock->render2D ) return; + + glBindTexture( GL_TEXTURE_2D, mDataBlock->texture.getGLName() ); + glColor4f( 1.0, 1.0, 1.0, 1.0 ); + + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_TEXTURE_2D); + + dglDrawBillboard( getPosition(), 0.1, 0.0 ); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +} + +//---------------------------------------------------------------------------- +// Set size +//---------------------------------------------------------------------------- +void Debris::setSize( F32 size ) +{ + mSize = size; + +} diff --git a/game/debris.h b/game/debris.h new file mode 100644 index 0000000..a06ef74 --- /dev/null +++ b/game/debris.h @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DEBRIS_H_ +#define _DEBRIS_H_ + +#ifndef _GAMEBASE_H_ +#include "game/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, char errorBuffer[256] ); + static void initPersistFields(); + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + DECLARE_CONOBJECT(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; + + ParticleEmitter * mEmitterList[ DebrisData::DDC_NUM_EMITTERS ]; + + bool bounce( const Point3F &nextPos, F32 dt ); + void computeNewState( Point3F &newPos, Point3F &newVel, F32 dt ); + void explode(); + void render2D(); + void rotate( F32 dt ); + +protected: + virtual void processTick(const Move*); + virtual void advanceTime( F32 dt ); + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + + bool onAdd(); + void onRemove(); + void updateEmitters( Point3F &pos, Point3F &vel, U32 ms ); + +public: + + Debris(); + ~Debris(); + + static void initPersistFields(); + static void consoleInit(); + + 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/game/debugView.cc b/game/debugView.cc new file mode 100644 index 0000000..2831ffb --- /dev/null +++ b/game/debugView.cc @@ -0,0 +1,212 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "gui/guiTSControl.h" +#include "game/gameConnection.h" +#include "game/player.h" +#include "game/DebugView.h" + +IMPLEMENT_CONOBJECT(DebugView); + +DebugView::DebugView() +{ + for (int i = 0; i < MaxTextLines; i++) + mTextLines[i][0] = '\0'; +} + +static void cDebugAddLine(SimObject *obj, S32, const char **argv) +{ + Point3F start(0, 0, 0); + Point3F end(0, 0, 0); + ColorF color(0, 0, 0, 1.0f); + int numArgsRead; + + //read the args in + numArgsRead = dSscanf(argv[2], "%f %f %f", &start.x, &start.y, &start.z); + if (numArgsRead != 3) + { + Con::printf("%s() - invalid start point.", argv[0]); + return; + } + + numArgsRead = dSscanf(argv[3], "%f %f %f", &end.x, &end.y, &end.z); + if (numArgsRead != 3) + { + Con::printf("%s() - invalid end point.", argv[0]); + return; + } + + numArgsRead = dSscanf(argv[4], "%f %f %f", &color.red, &color.green, &color.blue); + if (numArgsRead != 3) + { + Con::printf("%s() - invalid color.", argv[0]); + return; + } + + DebugView *dv = static_cast(obj); + dv->addLine(start, end, color); +} + +static void cDebugClearLines(SimObject *obj, S32, const char **) +{ + DebugView *dv = static_cast(obj); + dv->clearLines(); +} + +static void cDebugViewSetText(SimObject *obj, S32 argc, const char **argv) +{ + DebugView *dv = static_cast(obj); + ColorF color(0.0f, 0.0f, 0.0f, 1.0f); + bool setColor = false; + if (argc >= 5) + { + int numArgsRead = dSscanf(argv[4], "%f %f %f", &color.red, &color.green, &color.blue); + if (numArgsRead == 3) + setColor = true; + } + dv->setTextLine(dAtoi(argv[2]), argv[3], setColor ? &color : NULL); +} + +static void cDebugViewClearText(SimObject *obj, S32 argc, const char **argv) +{ + DebugView *dv = static_cast(obj); + int lineNum = -1; + if (argc == 3) + lineNum = dAtoi(argv[2]); + dv->clearTextLine(lineNum); +} + +void DebugView::consoleInit() +{ + Con::addCommand("DebugView", "addLine", cDebugAddLine, "debugView.addLine(startPt, endPt, color)", 5, 5); + Con::addCommand("DebugView", "clearLines", cDebugClearLines, "debugView.clearLines()", 2, 2); + + Con::addCommand("DebugView", "setText", cDebugViewSetText, "debugView.SetText(line, text [, colorF])", 4, 5); + Con::addCommand("DebugView", "clearText", cDebugViewClearText, "debugView.ClearText()", 2, 3); +} + +void DebugView::addLine(const Point3F &start, const Point3F &end, const ColorF &color) +{ + DebugLine newLine(start, end, color); + mLines.push_back(newLine); +} + +void DebugView::clearLines() +{ + mLines.clear(); +} + +void DebugView::setTextLine(int line, const char *text, ColorF *color) +{ + if (line < 0 || line >= MaxTextLines || !text) + return; + dStrncpy(&mTextLines[line][0], text, MaxTextLineLength); + mTextLines[line][MaxTextLineLength] = '\0'; + + if (!color) + mTextColors[line] = mProfile->mFontColor; + else + mTextColors[line] = *color; +} + +void DebugView::clearTextLine(int line) +{ + if (line < 0) + { + for (int i = 0; i < MaxTextLines; i++) + mTextLines[i][0] = '\0'; + } + else if (line < MaxTextLines) + mTextLines[line][0] = '\0'; +} + +void DebugView::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + S32 i; +#if defined(DEBUG) || defined(INTERNAL_RELEASE) + GuiTSCtrl *tsCtrl; + if (! Sim::findObject("PlayGui", tsCtrl)) + { + Con::printf("DebugView failed - unable to find TS ctrl."); + return; + } + + //draw the lines first + for (i = 0; i < mLines.size(); i++) + { + //project the line to the screen + Point3F startPos, endPos; + if (tsCtrl->project(mLines[i].start, &startPos) && tsCtrl->project(mLines[i].end, &endPos)) + { + glBegin(GL_LINES); + glColor4f(mLines[i].color.red, mLines[i].color.green, mLines[i].color.blue, 1.0f); + glVertex2i((S32)startPos.x, (S32)startPos.y); + glVertex2i((S32)endPos.x, (S32)endPos.y); + glEnd(); + } + } + + //draw the task above each player's head + SimGroup *g = Sim::getClientGroup(); + SimGroup::iterator j; + for (j = g->begin(); j != g->end(); j++) + { + GameConnection *client = static_cast(*j); + Player *player = NULL; + if (! client->getControlObject()) + continue; + + player = dynamic_cast(client->getControlObject()); + if (! player) + continue; + + //draw a test string above everyone's head + Point3F playerPos; + MatrixF const& tempTransform = player->getTransform(); + tempTransform.getColumn(3, &playerPos); + playerPos.z += 1.7f; + Point3F textPos; + if (tsCtrl->project(playerPos, &textPos)) + { + //const char *textStr = client->getDataField("objective", NULL); + const char *textStr = Con::executef(2, "aiGetTaskDesc", avar("%d", client->getId())); + if (!textStr || !textStr[0]) + textStr = "Shoot Me!"; + if ((textStr[0] == 'E' || textStr[0] == 'F') && textStr[1] == ':') + { + if (textStr[0] == 'E') + dglSetBitmapModulation(ColorF(1.0, 0.0, 0.0, 1.0)); + else + dglSetBitmapModulation(ColorF(0.0, 1.0, 0.0, 1.0)); + dglDrawText(mFont, Point2I(textPos.x, textPos.y), &textStr[2]); + } + else + { + dglSetBitmapModulation(mProfile->mFontColor); + dglDrawText(mFont, Point2I(textPos.x, textPos.y), textStr); + } + } + } + +#endif + + //draw the text - for final release, this is the only thing to be rendered + Point2I textOffset = offset; + for (i = 0; i < MaxTextLines; i++) + { + dglSetBitmapModulation(mTextColors[i]); + if (mTextLines[i][0] != '\0') + dglDrawText(mFont, textOffset, mTextLines[i]); + textOffset.y += mFont->getHeight(); + } + + renderChildControls(offset, updateRect, firstResponder); +} + + diff --git a/game/debugView.h b/game/debugView.h new file mode 100644 index 0000000..2e9a660 --- /dev/null +++ b/game/debugView.h @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DEBUGVIEW_H_ +#define _DEBUGVIEW_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "gui/guiTextCtrl.h" +#endif + +class DebugView : public GuiTextCtrl +{ + private: + typedef GuiTextCtrl Parent; + + enum + { + MaxTextLines = 64, + MaxTextLineLength = 255 + }; + + //text members + char mTextLines[MaxTextLines][MaxTextLineLength + 1]; + ColorF mTextColors[MaxTextLines]; + + struct DebugLine + { + Point3F start; + Point3F end; + ColorF color; + DebugLine(const Point3F &inStart, const Point3F &inEnd, const ColorI &inColor) + { + color = inColor; + start = inStart; + end = inEnd; + } + }; + Vector mLines; + + public: + DECLARE_CONOBJECT(DebugView); + DebugView(); + static void consoleInit(); + + void addLine(const Point3F &start, const Point3F &end, const ColorF &color); + void clearLines(); + + void setTextLine(int line, const char *text, ColorF *color); + void clearTextLine(int line = -1); + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + +}; + +#endif diff --git a/game/demoGame.h b/game/demoGame.h new file mode 100644 index 0000000..3c4767d --- /dev/null +++ b/game/demoGame.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _V12GAME_H_ +#define _V12GAME_H_ + +#ifndef _GAMEINTERFACE_H_ +#include "platform/gameInterface.h" +#endif + +class DemoGame : public GameInterface +{ +public: + void textureKill(); + void textureResurrect(); + void refreshWindow(); + + int main(int argc, const char **argv); + + void processPacketReceiveEvent(PacketReceiveEvent *event); + void processMouseMoveEvent(MouseMoveEvent *event); + void processInputEvent(InputEvent *event); + void processQuitEvent(); + void processTimeEvent(TimeEvent *event); + void processConsoleEvent(ConsoleEvent *event); + void processConnectedAcceptEvent(ConnectedAcceptEvent *event); + void processConnectedReceiveEvent(ConnectedReceiveEvent *event); + void processConnectedNotifyEvent(ConnectedNotifyEvent *event); +}; + +#endif diff --git a/game/explosion.cc b/game/explosion.cc new file mode 100644 index 0000000..f416c9a --- /dev/null +++ b/game/explosion.cc @@ -0,0 +1,1061 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "platformWIN32/platformGL.h" +#include "platform/platformAudio.h" +#include "audio/audioDataBlock.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sceneState.h" +#include "core/bitStream.h" +#include "ts/tsShape.h" +#include "ts/tsShapeInstance.h" +#include "math/mRandom.h" +#include "game/particleEngine.h" +#include "game/explosion.h" +#include "math/mathIO.h" +#include "game/Debris.h" +#include "game/shockwave.h" +#include "math/mathUtils.h" +#include "sim/netConnection.h" +#include "game/cameraFXMgr.h" +#include "game/gameConnection.h" + +IMPLEMENT_CONOBJECT(Explosion); + +namespace { + +MRandomLCG sgRandom(0xdeadbeef); + +F32 cCalcCoverage(SimObject*, S32, const char** argv) +{ + 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.0; + } + if (sceneObject->isClientObject() || sceneObject->getContainer() == NULL) { + Con::warnf(ConsoleLogEntry::General, "calcExplosionCoverage: object is on the client, or not in the container system"); + return 1.0; + } + + 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, 0, 1), covMask, &rayInfo) == false) + { + if (sceneObject->getContainer()->castRay(pos + Point3F(0, 0, 1), center, covMask, &rayInfo) == false) { + sceneObject->enableCollision(); + return 1.0; + } + } + + sceneObject->enableCollision(); + return 0.0; + } else { + sceneObject->enableCollision(); + return 1.0; + } +} + +} // namespace {} + +//---------------------------------------------------------------------------- +//-------------------------------------- +// +IMPLEMENT_CO_DATABLOCK_V1(ExplosionData); + +ExplosionData::ExplosionData() +{ + dtsFileName = NULL; + particleDensity = 10; + particleRadius = 1; + + faceViewer = false; + + soundProfile = NULL; + particleEmitter = NULL; + soundProfileId = 0; + particleEmitterId = 0; + + explosionScale.set(1, 1, 1); + playSpeed = 1.0; + + dMemset( emitterList, 0, sizeof( emitterList ) ); + dMemset( emitterIDList, 0, sizeof( emitterIDList ) ); + dMemset( debrisList, 0, sizeof( debrisList ) ); + dMemset( debrisIDList, 0, sizeof( debrisIDList ) ); + + debrisThetaMin = 0; + debrisThetaMax = 90; + debrisPhiMin = 0; + debrisPhiMax = 360; + debrisNum = 1; + debrisNumVariance = 0; + debrisVelocity = 2.0; + debrisVelocityVariance = 0.0; + + dMemset( explosionList, 0, sizeof( explosionList ) ); + dMemset( explosionIDList, 0, sizeof( explosionIDList ) ); + + delayMS = 0; + delayVariance = 0; + lifetimeMS = 1000; + lifetimeVariance = 0; + offset = 0; + + shockwave = NULL; + shockwaveID = 0; + shockwaveOnTerrain = false; + + shakeCamera = false; + camShakeFreq.set( 10.0, 10.0, 10.0 ); + camShakeAmp.set( 1.0, 1.0, 1.0 ); + camShakeDuration = 1.5; + camShakeRadius = 10.0; + camShakeFalloff = 10.0; + + for( U32 i=0; i= 0.01", getName()); + explosionScale.x = explosionScale.x < 0.01 ? 0.01 : explosionScale.x; + explosionScale.y = explosionScale.y < 0.01 ? 0.01 : explosionScale.y; + explosionScale.z = explosionScale.z < 0.01 ? 0.01 : 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.1) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisVelocity < 0.1", getName()); + debrisVelocity = 0.1; + } + if (debrisVelocityVariance > 1000) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisVelocityVariance > 1000", getName()); + debrisVelocityVariance = 1000; + } + if (playSpeed < 0.05) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) playSpeed < 0.05", getName()); + playSpeed = 0.05; + } + 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.0) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) offset < 0.0", getName()); + offset = 0.0; + } + + if( !shockwave && shockwaveID ) + { + if( !Sim::findObject( shockwaveID, shockwave ) ) + { + Con::errorf( ConsoleLogEntry::General, "ExplosionData::onAdd: Invalid packet, bad datablockId(shockwave): 0x%x", shockwaveID ); + } + } + + + 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(explosionScale.x * 100, 16); + stream->writeInt(explosionScale.y * 100, 16); + stream->writeInt(explosionScale.z * 100, 16); + } + stream->writeInt(playSpeed * 20, 14); + stream->writeRangedU32(debrisThetaMin, 0, 180); + stream->writeRangedU32(debrisThetaMax, 0, 180); + stream->writeRangedU32(debrisPhiMin, 0, 360); + stream->writeRangedU32(debrisPhiMax, 0, 360); + stream->writeRangedU32(debrisNum, 0, 1000); + stream->writeRangedU32(debrisNumVariance, 0, 1000); + stream->writeInt(debrisVelocity * 10, 14); + stream->writeRangedU32(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(shockwaveOnTerrain); + + 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); + + if( stream->writeFlag( shockwave ) ) + { + stream->writeRangedU32( shockwave->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + 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( sizes[i].x * 100, 0, 16000); + stream->writeRangedU32( sizes[i].y * 100, 0, 16000); + stream->writeRangedU32( sizes[i].z * 100, 0, 16000); + } +} + +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); + shockwaveOnTerrain = stream->readFlag(); + + 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); + + + if( stream->readFlag() ) + { + shockwaveID = (S32) stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + 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; + } +} + +bool ExplosionData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (dtsFileName && dtsFileName[0]) { + char fullName[256]; + dSprintf(fullName,sizeof(fullName),"shapes/%s", dtsFileName); + explosionShape = ResourceManager->load(fullName); + if (!bool(explosionShape)) { + dSprintf(errorBuffer, sizeof(errorBuffer), "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; + + mExplosionInstance = NULL; + mExplosionThread = NULL; + + dMemset( mEmitterList, 0, sizeof( mEmitterList ) ); + + mDelayMS = 0; + mCurrMS = 0; + mEndingMS = 1000; + mActive = false; + mCollideType = 0; + + mInitialNormal.set( 0.0, 0.0, 1.0 ); + mRandAngle = sgRandom.randF( 0.0, 1.0 ) * M_PI * 2.0; +} + +Explosion::~Explosion() +{ + if( mExplosionInstance ) + { + delete mExplosionInstance; + mExplosionInstance = NULL; + mExplosionThread = NULL; + } +} + + +void Explosion::setInitialState(const Point3F& point, const Point3F& normal, const F32 fade) +{ + mInitialPosition = point; + mInitialNormal = normal; + mFade = fade; + mFog = 0.0f; +} + +//-------------------------------------------------------------------------- +void Explosion::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void Explosion::consoleInit() +{ + Con::addCommand("calcExplosionCoverage", cCalcCoverage, "calcExplosionCoverage(\"x y z\", object, coverageMask)", 4, 4); +} + + +//-------------------------------------------------------------------------- +bool Explosion::onAdd() +{ + if(!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.001 ) + { + MatrixF axisOrient = MathUtils::createOrientFromDir( mInitialNormal ); + + MatrixF trans = getTransform(); + Point3F randVec; + randVec.x = sgRandom.randF( -1.0, 1.0 ); + randVec.y = sgRandom.randF( 0.0, 1.0 ); + randVec.z = sgRandom.randF( -1.0, 1.0 ); + randVec.normalize(); + randVec *= mDataBlock->offset; + axisOrient.mulV( randVec ); + trans.setPosition( trans.getPosition() + randVec ); + setTransform( trans ); + } + + // start shockwave immediately - it has its own delay + if( mDataBlock->shockwave ) + { + if( !mDataBlock->shockwaveOnTerrain || (mCollideType & TerrainObjectType) ) + { + MatrixF trans = getTransform(); + Shockwave* shockwave = new Shockwave; + shockwave->onNewDataBlock( mDataBlock->shockwave ); + shockwave->setTransform( trans ); + shockwave->setInitialState( trans.getPosition(), mInitialNormal ); + if (!shockwave->registerObject()) + delete shockwave; + } + } + + // shake camera + if( mDataBlock->shakeCamera ) + { + // first check if explosion is near player + GameConnection* connection = GameConnection::getServerConnection(); + ShapeBase *obj = 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 + falloff * 10.0; + falloff = 1.0 / (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::getServerConnection(); + AssertFatal(pNC != NULL, "Error, must have a connection to the server!"); + pNC->addObject(this); + + return true; +} + +void Explosion::onRemove() +{ + for( int i=0; ideleteWhenEmpty(); + mEmitterList[i] = 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)) { + Point3F explosionPos; + mObjToWorld.getColumn(3,&explosionPos); + if (mExplosionInstance) { + Point3F cameraOffset = explosionPos - state->getCameraPosition(); + mFog = state->getHazeAndFog(cameraOffset.len(),cameraOffset.z); + } else { + mFog = 0.0; + } + + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + image->textureSortKey = U32(mDataBlock); + state->setImageRefPoint(this, image); + state->insertRenderImage(image); + } + + 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.0 - firstPart)) + + (mDataBlock->sizes[i] * firstPart); + + return; + } + } + +} + +void Explosion::prepModelView(SceneState* state) +{ + MatrixF rotMatrix( true ); + Point3F targetVector; + if (mDataBlock->faceViewer == true) { + targetVector = mInitialPosition - state->getCameraPosition(); + targetVector.normalize(); + rotMatrix.set( EulerF( 0.0, mRandAngle, 0.0 ) ); + } else { + targetVector = mInitialNormal; + } + + // rotate explosion each time so it's a little different + MatrixF explOrient = MathUtils::createOrientFromDir( targetVector ); + explOrient.mul( rotMatrix ); + explOrient.setPosition( getPosition() ); + dglMultMatrix( &explOrient ); + setCurrentScale(); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + +} + +void Explosion::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + prepModelView(state); + + if( mExplosionInstance ) + { + mExplosionInstance->animate(); + + if (mFade == 1.0) { + mExplosionInstance->setupFog(mFog, state->getFogColor()); + } else { + mExplosionInstance->setupFog(0.0, state->getFogColor()); + mExplosionInstance->setAlphaAlways(mFade * (1.0 - mFog)); + } + mExplosionInstance->render(); + } + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glScalef( 1.0, 1.0, 1.0 ); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +void Explosion::processTick(const Move*) +{ + mCurrMS += TickMs; + + if( mCurrMS >= mEndingMS ) + { + deleteObject(); + } + + if( (mCurrMS > mDelayMS) && !mActive ) + { + explode(); + } + +} + +void Explosion::advanceTime(F32 dt) +{ + if (dt == 0.0) + 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.0, 0.0, 0.0 ), dt * 1000 ); + } + } + +} + +//---------------------------------------------------------------------------- +// Launch Debris +//---------------------------------------------------------------------------- +void Explosion::launchDebris( Point3F &axis ) +{ + + 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, 0, 1), &axisx); + else + mCross(axis, Point3F(0, 1, 0), &axisx); + axisx.normalize(); + + Point3F pos = getPosition() + Point3F( 0.0, 0.0, 0.5 ); + + + 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.0, 1.0 ); + + launchDir *= debrisVel; + + Debris *debris = new Debris; + debris->onNewDataBlock( 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() +{ + + for( S32 i=0; iexplosionList[i] ) + { + MatrixF trans = getTransform(); + Explosion* pExplosion = new Explosion; + pExplosion->onNewDataBlock( mDataBlock->explosionList[i] ); + pExplosion->setTransform( trans ); + pExplosion->setInitialState( trans.getPosition(), mInitialNormal, 1); + if (!pExplosion->registerObject()) + delete pExplosion; + } + } +} + +//---------------------------------------------------------------------------- +// Explode +//---------------------------------------------------------------------------- +bool Explosion::explode() +{ + mActive = true; + + 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) + alxPlay(mDataBlock->soundProfile, &getTransform() ); + + if (mDataBlock->particleEmitter) { + ParticleEmitter* emitter = new ParticleEmitter; + emitter->onNewDataBlock(mDataBlock->particleEmitter); + emitter->registerObject(); + + emitter->emitParticles(mInitialPosition, mInitialNormal, mDataBlock->particleRadius, + Point3F(0, 0, 0), U32(mDataBlock->particleDensity * mFade)); + emitter->deleteWhenEmpty(); + } + + 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; + } + } + + return true; +} + diff --git a/game/explosion.h b/game/explosion.h new file mode 100644 index 0000000..b8731ad --- /dev/null +++ b/game/explosion.h @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EXPLOSION_H_ +#define _EXPLOSION_H_ + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; +class TSThread; +class AudioProfile; +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; + + AudioProfile* 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; + + ExplosionData(); + DECLARE_CONOBJECT(ExplosionData); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//-------------------------------------------------------------------------- +class Explosion : public GameBase +{ + typedef GameBase Parent; + + private: + ExplosionData* mDataBlock; + + TSShapeInstance* mExplosionInstance; + TSThread* mExplosionThread; + + ParticleEmitter * mEmitterList[ ExplosionData::EC_NUM_EMITTERS ]; + + U32 mCurrMS; + U32 mEndingMS; + F32 mRandAngle; + + protected: + Point3F mInitialPosition; + Point3F mInitialNormal; + F32 mFade; + F32 mFog; + bool mActive; + S32 mDelayMS; + F32 mRandomVal; + U32 mCollideType; + + protected: + bool onAdd(); + void onRemove(); + bool explode(); + + void processTick(const Move*); + void advanceTime(F32 dt); + void updateEmitters( F32 dt ); + void launchDebris( Point3F &axis ); + void spawnSubExplosions(); + void setCurrentScale(); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + void prepModelView(SceneState*); + + public: + Explosion(); + ~Explosion(); + void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0); + + bool onNewDataBlock(GameBaseData* dptr); + void setCollideType( U32 cType ){ mCollideType = cType; } + + DECLARE_CONOBJECT(Explosion); + static void initPersistFields(); + static void consoleInit(); +}; + +#endif // _H_EXPLOSION + diff --git a/game/fireballAtmosphere.cc b/game/fireballAtmosphere.cc new file mode 100644 index 0000000..7854e78 --- /dev/null +++ b/game/fireballAtmosphere.cc @@ -0,0 +1,331 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "audio/audioDataBlock.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "game/gameConnection.h" +#include "math/mathIO.h" +#include "game/player.h" +#include "game/fireballAtmosphere.h" +#include "game/Debris.h" +#include "math/mathUtils.h" + +#define DML_DIR "textures/" +#define COLOR_OFFSET 0.25 + + +IMPLEMENT_CO_NETOBJECT_V1(FireballAtmosphere); +IMPLEMENT_CO_DATABLOCK_V1(FireballAtmosphereData); + + +//************************************************************************** +// Fireball Atmosphere Data +//************************************************************************** +FireballAtmosphereData::FireballAtmosphereData() +{ + + fireball = NULL; + fireballID = 0; +} + +IMPLEMENT_GETDATATYPE(FireballAtmosphereData) +IMPLEMENT_SETDATATYPE(FireballAtmosphereData) + +//-------------------------------------------------------------------------- +// Init persist fields +//-------------------------------------------------------------------------- +void FireballAtmosphereData::initPersistFields() +{ + Parent::initPersistFields(); + + Con::registerType(TypeGameBaseDataPtr, sizeof(FireballAtmosphereData*), + REF_GETDATATYPE(FireballAtmosphereData), + REF_SETDATATYPE(FireballAtmosphereData)); + + addField("fireball", TypeDebrisDataPtr, Offset(fireball, FireballAtmosphereData)); + +} + +//-------------------------------------------------------------------------- +// onAdd +//-------------------------------------------------------------------------- +bool FireballAtmosphereData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + + if( !fireball && fireballID != 0 ) + { + if( !Sim::findObject( SimObjectId( fireballID ), fireball ) ) + { + Con::errorf( ConsoleLogEntry::General, "FireballAtmosphereData::preload: Invalid packet, bad datablockId(fireball): 0x%x", fireballID ); + } + } + + return true; +} + +//-------------------------------------------------------------------------- +// Pack Data +//-------------------------------------------------------------------------- +void FireballAtmosphereData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if( stream->writeFlag( fireball ) ) + { + stream->writeRangedU32(packed? SimObjectId(fireball): + fireball->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + +} + +//-------------------------------------------------------------------------- +// Unpack Data +//-------------------------------------------------------------------------- +void FireballAtmosphereData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + if(stream->readFlag()) + { + fireballID = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } +} + +//************************************************************************** +// Fireball Atmosphere +//************************************************************************** +FireballAtmosphere::FireballAtmosphere() +{ + mTimeSinceLastDrop = 0.0; + + mDropRadius = 600.0; + mDropsPerMinute = 3.0; + mMinDropAngle = 0.0; + mMaxDropAngle = 30.0; + mStartVelocity = 20.0; + mDropHeight = 500.0; + mDropDir.set( 0.5, 0.5, -0.5 ); +} + +//-------------------------------------------------------------------------- +// initPersistFields +//-------------------------------------------------------------------------- +void FireballAtmosphere::initPersistFields() +{ + Parent::initPersistFields(); + + addField("dropRadius", TypeF32, Offset(mDropRadius, FireballAtmosphere)); + addField("dropsPerMinute", TypeF32, Offset(mDropsPerMinute, FireballAtmosphere)); + addField("minDropAngle", TypeF32, Offset(mMinDropAngle, FireballAtmosphere)); + addField("maxDropAngle", TypeF32, Offset(mMaxDropAngle, FireballAtmosphere)); + addField("startVelocity", TypeF32, Offset(mStartVelocity, FireballAtmosphere)); + addField("dropHeight", TypeF32, Offset(mDropHeight, FireballAtmosphere)); + addField("dropDir", TypePoint3F, Offset(mDropDir, FireballAtmosphere)); +} + +//-------------------------------------------------------------------------- +// onAdd +//-------------------------------------------------------------------------- +bool FireballAtmosphere::onAdd() +{ + if(!Parent::onAdd()) + return false; + + + if (isClientObject()) + { + int test = 1; + } + else + { + int test = 1; + } + + if( mDataBlock && mDataBlock->fireball ) + { + mDataBlock->fireball->terminalVelocity = mStartVelocity; + } + + mDropDir.normalize(); + + mObjBox.min.set(-1e6, -1e6, -1e6); + mObjBox.max.set( 1e6, 1e6, 1e6); + + resetWorldBox(); + + addToScene(); + + return true; +} + +//-------------------------------------------------------------------------- +// onRemove +//-------------------------------------------------------------------------- +void FireballAtmosphere::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +//-------------------------------------------------------------------------- +// onNewDataBlock +//-------------------------------------------------------------------------- +bool FireballAtmosphere::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +// prepRenderImage +//-------------------------------------------------------------------------- +bool FireballAtmosphere::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + 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)) { +// SceneRenderImage* image = new SceneRenderImage; +// image->obj = this; +// image->isTranslucent = true; +// image->sortKey = -1000.0; +// state->insertRenderImage(image); +// } + +// return false; +} + +//-------------------------------------------------------------------------- +// RENDER +//-------------------------------------------------------------------------- +void FireballAtmosphere::renderObject(SceneState* , SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +// advanceTime +//-------------------------------------------------------------------------- +void FireballAtmosphere::advanceTime(F32 dt) +{ + mTimeSinceLastDrop += dt; + + F32 dropFrequency = 60.0 / mDropsPerMinute; + + if( mTimeSinceLastDrop > dropFrequency ) + { + mTimeSinceLastDrop -= dropFrequency; + dropNewFireball(); + } +} + +//-------------------------------------------------------------------------- +// dropNewFireball +//-------------------------------------------------------------------------- +void FireballAtmosphere::dropNewFireball() +{ + MatrixF camTrans; + GameConnection* connection = GameConnection::getServerConnection(); + connection->getControlCameraTransform( 0.0, &camTrans ); + + + Debris *fireball = new Debris; + fireball->onNewDataBlock( mDataBlock->fireball ); + + + // set velocity + VectorF launchVel = MathUtils::randomDir( mDropDir, mMinDropAngle, mMaxDropAngle ); + launchVel *= mStartVelocity; + + // set start point + VectorF down( 0.0, 0.0, -1.0 ); + Point3F launchPoint = MathUtils::randomDir( down, 90.0, 90.0 ); + launchPoint *= mDropRadius * gRandGen.randF( 0.1, 1.0 ); + launchPoint += camTrans.getPosition(); + launchPoint.z = 0.0; + + F32 timeToHit = mDropHeight / launchVel.z; + launchPoint += launchVel * timeToHit; + + + if( !fireball->registerObject() ) + { + delete fireball; + } + else + { + fireball->init( launchPoint, launchVel ); + } + +} + +//-------------------------------------------------------------------------- +// packUpdate +//-------------------------------------------------------------------------- +U32 FireballAtmosphere::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if( stream->writeFlag(mask & GameBase::InitialUpdateMask) ) + { + stream->write(mDropRadius); + stream->write(mDropsPerMinute); + stream->write(mMaxDropAngle); + stream->write(mMinDropAngle); + stream->write(mStartVelocity); + stream->write(mDropHeight); + stream->write(mDropDir.x); + stream->write(mDropDir.y); + stream->write(mDropDir.z); + } + + return retMask; +} + +//-------------------------------------------------------------------------- +// unpackUpdate +//-------------------------------------------------------------------------- +void FireballAtmosphere::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if( stream->readFlag() ) + { + stream->read(&mDropRadius); + stream->read(&mDropsPerMinute); + stream->read(&mMaxDropAngle); + stream->read(&mMinDropAngle); + stream->read(&mStartVelocity); + stream->read(&mDropHeight); + stream->read(&mDropDir.x); + stream->read(&mDropDir.y); + stream->read(&mDropDir.z); + } +} diff --git a/game/fireballAtmosphere.h b/game/fireballAtmosphere.h new file mode 100644 index 0000000..c4ac0e2 --- /dev/null +++ b/game/fireballAtmosphere.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FIREBALLATMOSPHERE_H_ +#define _FIREBALLATMOSPHERE_H_ + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif + +class AudioProfile; +struct DebrisData; + +//-------------------------------------------------------------------------- +// Fireball Atmosphere Data +//-------------------------------------------------------------------------- +class FireballAtmosphereData : public GameBaseData { + typedef GameBaseData Parent; + + public: + + DebrisData * fireball; + S32 fireballID; + + FireballAtmosphereData(); + + bool onAdd(); + static void initPersistFields(); + + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + + DECLARE_CONOBJECT(FireballAtmosphereData); +}; + + +//-------------------------------------------------------------------------- +// Fireball atmosphere +//-------------------------------------------------------------------------- +class FireballAtmosphere : public GameBase +{ + typedef GameBase Parent; + + private: + FireballAtmosphereData * mDataBlock; + AUDIOHANDLE mAudioHandle; + F32 mTimeSinceLastDrop; + + F32 mDropRadius; + F32 mDropsPerMinute; + F32 mMinDropAngle; + F32 mMaxDropAngle; + F32 mStartVelocity; + F32 mDropHeight; + VectorF mDropDir; + + protected: + bool onAdd(); + void onRemove(); + + void advanceTime(F32 dt); + void dropNewFireball(); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); + + public: + + FireballAtmosphere(); + + bool onNewDataBlock(GameBaseData* dptr); + static void initPersistFields(); + + + DECLARE_CONOBJECT(FireballAtmosphere); +}; + +#endif // _H_FIREBALL_ATMOSPHERE + diff --git a/game/flyingVehicle.cc b/game/flyingVehicle.cc new file mode 100644 index 0000000..0332570 --- /dev/null +++ b/game/flyingVehicle.cc @@ -0,0 +1,748 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "game/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 "game/moveManager.h" +#include "core/bitStream.h" +#include "core/dnet.h" +#include "game/gameConnection.h" +#include "ts/tsShapeInstance.h" +#include "game/particleEngine.h" +#include "audio/audio.h" +#include "game/flyingVehicle.h" +#include "game/forceFieldBare.h" +#include "game/missionArea.h" + +//---------------------------------------------------------------------------- + +const static U32 sCollisionMoveMask = (TerrainObjectType | InteriorObjectType | + WaterObjectType | PlayerObjectType | + StaticShapeObjectType | VehicleObjectType | + VehicleBlockerObjectType | ForceFieldObjectType | + StaticTSObjectType); +static U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType +static U32 sClientCollisionMask = sCollisionMoveMask; + +static F32 sFlyingVehicleGravity = -20; + +// Sound +static F32 sIdleEngineVolume = 0.2; + +// +const char* FlyingVehicle::sJetSequence[FlyingVehicle::JetAnimCount] = +{ + "activateBack", + "maintainBack", + "activateBot", + "maintainBot", +}; + +const char* FlyingVehicleData::sJetNode[FlyingVehicleData::MaxJetNodes] = +{ + "JetNozzle0", // Thrust Forward + "JetNozzle1", + "JetNozzleX", // Thrust Backward + "JetNozzleX", + "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; + 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, char errorBuffer[256]) +{ + if (!Parent::preload(server, errorBuffer)) + return false; + + TSShapeInstance* si = new TSShapeInstance(shape,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] = shape->findNode(sJetNode[j]); + + // + maxSpeed = maneuveringForce / minDrag; + + delete si; + return true; +} + +void FlyingVehicleData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("jetSound", TypeAudioProfilePtr, Offset(sound[JetSound], FlyingVehicleData)); + addField("engineSound", TypeAudioProfilePtr, 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)); +} + +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] = (AudioProfile*)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() +{ + mGenerateShadow = true; + + 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() +{ + if (mJetSound) + alxStop(mJetSound); + if (mEngineSound) + alxStop(mEngineSound); +} + + +//---------------------------------------------------------------------------- + +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 (mJetSound) { + alxStop(mJetSound); + mJetSound = 0; + } + if (mEngineSound) { + alxStop(mEngineSound); + mEngineSound = 0; + } + if (isGhost()) { + if (mDataBlock->sound[FlyingVehicleData::EngineSound]) + mEngineSound = alxPlay(mDataBlock->sound[FlyingVehicleData::EngineSound], &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() +{ + scriptOnRemove(); + removeFromScene(); + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- +void FlyingVehicle::updateWarp() +{ +} + +void FlyingVehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + updateEngineSound(1); + updateJet(dt); +} + + +//---------------------------------------------------------------------------- + +void FlyingVehicle::updateMove(const Move* move) +{ + Parent::updateMove(move); + + if (move == &NullMove) + mSteering.set(0,0); + + F32 speed = mRigid.state.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("MissionArea")); + mCeilingFactor = 1.0f; + if (obj != NULL) + { + F32 flightCeiling = obj->getFlightCeiling(); + F32 ceilingRange = obj->getFlightCeilingRange(); + + if (mRigid.state.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.state.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; +} + + +//---------------------------------------------------------------------------- + +Point3F JetOffset[4] = +{ + Point3F(-1,-1,0), + Point3F(+1,-1,0), + Point3F(-1,+1,0), + Point3F(+1,+1,0) +}; + +void FlyingVehicle::updateForces(F32 /*dt*/) +{ + MatrixF currPosMat; + mRigid.state.getTransform(&currPosMat); + + 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.state.linVelocity.len(); + + Point3F force = Point3F(0, 0, sFlyingVehicleGravity * mMass * mGravityMod); + Point3F torque = Point3F(0, 0, 0); + + // Drag at any speed + force -= mRigid.state.linVelocity * mDataBlock->minDrag; + torque -= mRigid.state.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.state.linVelocity); + force -= xv * sf * mDot(xv, mRigid.state.linVelocity); + } + + // Hovering Jet +// if (!mBuoyancy) { + F32 vf = -sFlyingVehicleGravity * mMass * 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 * speed * mDot(xv,mRigid.state.linVelocity) * mDataBlock->horizontalSurfaceForce; + force -= zv * speed * mDot(zv,mRigid.state.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.state.linVelocity); + torque += yv * ar; + + force += mAppliedForce; + force -= Point3F(0, 0, 1) * (mBuoyancy * sFlyingVehicleGravity * mGravityMod); + force -= mRigid.state.linVelocity * mDrag; + +// // Add in force from physical zones... +// force += mAppliedForce; +// +//// // Container buoyancy & drag +//// mRigid.state.linVelocity.z -= mBuoyancy * sFlyingVehicleGravity * mGravityMod * TickSec; +//// mRigid.state.linVelocity -= mRigid.state.linVelocity * mDrag * TickSec; +//// mRigid.state.angVelocity -= mRigid.state.angVelocity * mDrag * TickSec; + + mRigid.state.force = force * mOneOverMass; + mRigid.state.torque = torque * mOneOverMass; +} + + +//---------------------------------------------------------------------------- + +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,-1,&collision)) + 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) { + alxSourceMatrixF(mEngineSound, &getTransform()); + alxSourcef(mEngineSound, AL_GAIN_LINEAR, 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: -1); + 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: -1); + 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.state.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 (!mDataBlock->sound[FlyingVehicleData::JetSound]) + return; + if (!mJetting) { + if (mJetSound) { + alxStop(mJetSound); + mJetSound = 0; + } + } + else { + if (!mJetSound) + mJetSound = alxPlay(mDataBlock->sound[FlyingVehicleData::JetSound], &getTransform()); + + alxSourceMatrixF(mJetSound, &getTransform()); + } +} + +//---------------------------------------------------------------------------- + +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(),dt * 1000); + } + } + else { + for (S32 j = idx; j < idx + count; j++) + if (bool(mJetEmitter[j])) { + mJetEmitter[j]->deleteWhenEmpty(); + mJetEmitter[j] = 0; + } + } +} + + +//---------------------------------------------------------------------------- + +bool FlyingVehicle::writePacketData(GameConnection *connection, BitStream *stream) +{ + return Parent::writePacketData(connection, stream); +} + +void FlyingVehicle::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + + setPosition(mRigid.state.linPosition,mRigid.state.angPosition); + mDelta.pos = mRigid.state.linPosition; + mDelta.rot[1] = mRigid.state.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(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(getControllingClient() == con) + return; + + createHeightOn = stream->readFlag(); + + mThrustDirection = ThrustDirection(stream->readInt(NumThrustBits)); +} + +void FlyingVehicle::initPersistFields() +{ + Parent::initPersistFields(); +} + +void cUseCreateHeight(SimObject *obj, S32, const char **argv) +{ + FlyingVehicle *ctrl = static_cast(obj); + ctrl->useCreateHeight(dAtob(argv[2])); +} + +void FlyingVehicle::consoleInit() +{ + Con::addCommand("FlyingVehicle", "useCreateHeight", cUseCreateHeight, "FlyingVehicle.setCreateHeight(bool)", 3, 3); +} + +void FlyingVehicle::useCreateHeight(bool val) +{ + createHeightOn = val; + setMaskBits(HoverHeight); +} diff --git a/game/flyingVehicle.h b/game/flyingVehicle.h new file mode 100644 index 0000000..847cbee --- /dev/null +++ b/game/flyingVehicle.h @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FLYINGVEHICLE_H_ +#define _FLYINGVEHICLE_H_ + +#ifndef _VEHICLE_H_ +#include "game/vehicle.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; + + +//---------------------------------------------------------------------------- + +struct FlyingVehicleData: public VehicleData { + typedef VehicleData Parent; + + enum Sounds { + JetSound, + EngineSound, + MaxSounds, + }; + AudioProfile* 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, char errorBuffer[256]); + void packData(BitStream* stream); + void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class FlyingVehicle: public Vehicle +{ + typedef Vehicle Parent; + + FlyingVehicleData* mDataBlock; + + AUDIOHANDLE mJetSound; + AUDIOHANDLE 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 updateWarp(); + 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); + + bool writePacketData(GameConnection *, BitStream *stream); + void readPacketData(GameConnection *, BitStream *stream); + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); + void useCreateHeight(bool val); + static void consoleInit(); +}; + + +#endif diff --git a/game/forceFieldBare.cc b/game/forceFieldBare.cc new file mode 100644 index 0000000..b398624 --- /dev/null +++ b/game/forceFieldBare.cc @@ -0,0 +1,854 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/forceFieldBare.h" +#include "core/bitStream.h" +#include "platformWin32/platformGL.h" +#include "dgl/dgl.h" +#include "sceneGraph/sceneState.h" +#include "math/mathIO.h" +#include "console/consoleTypes.h" +#include "collision/boxConvex.h" + +IMPLEMENT_CO_DATABLOCK_V1(ForceFieldBareData); +IMPLEMENT_CO_NETOBJECT_V1(ForceFieldBare); + +namespace { + +static void cOpen(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-forcefield get in here?"); + ForceFieldBare* pBare = static_cast(obj); + + if (pBare->isClientObject()) + return; + + pBare->open(); +} + +static void cClose(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-forcefield get in here?"); + ForceFieldBare* pBare = static_cast(obj); + + if (pBare->isClientObject()) + return; + + pBare->close(); +} + +class ForceConvex : public BoxConvex +{ + typedef BoxConvex Parent; + friend class ForceFieldBare; + + ForceFieldBare* pField; + + protected: + void getPolyList(AbstractPolyList* list); +}; + +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 }, +}; +void ForceConvex::getPolyList(AbstractPolyList* list) +{ + list->setTransform(&getTransform(), getScale()); + list->setObject(getObject()); + + U32 base = list->addPoint(Point3F(0, 0, 0)); + list->addPoint(Point3F(1, 0, 0)); + list->addPoint(Point3F(0, 1, 0)); + list->addPoint(Point3F(1, 1, 0)); + list->addPoint(Point3F(0, 0, 1)); + list->addPoint(Point3F(1, 0, 1)); + list->addPoint(Point3F(0, 1, 1)); + list->addPoint(Point3F(1, 1, 1)); + + 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(); + } +} + + + +} // namespace {} + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ForceFieldBareData::ForceFieldBareData() +{ + fadeMS = 1000; + baseTranslucency = 0.65; + powerOffTranslucency = 0.35; + teamPermiable = false; + otherPermiable = false; + framesPerSec = 10; + numFrames = NUM_TEX; + scrollSpeed = 0.5; + umapping = 1.0; + vmapping = 1.0; + + color.set(0.5, 0.5, 1); + powerOffColor.set(0.25, 0, 0); + + dMemset( textureName, 0, sizeof( textureName ) ); + dMemset( textureHandle, 0, sizeof( textureHandle ) ); +} + +ForceFieldBareData::~ForceFieldBareData() +{ + +} + + +//-------------------------------------------------------------------------- +void ForceFieldBareData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("fadeMS", TypeS32, Offset(fadeMS, ForceFieldBareData)); + addField("baseTranslucency", TypeF32, Offset(baseTranslucency, ForceFieldBareData)); + addField("powerOffTranslucency", TypeF32, Offset(powerOffTranslucency, ForceFieldBareData)); + addField("teamPermiable", TypeBool, Offset(teamPermiable, ForceFieldBareData)); + addField("otherPermiable", TypeBool, Offset(otherPermiable, ForceFieldBareData)); + addField("color", TypeColorF, Offset(color, ForceFieldBareData)); + addField("powerOffColor", TypeColorF, Offset(powerOffColor, ForceFieldBareData)); + addField("texture", TypeString, Offset(textureName, ForceFieldBareData), NUM_TEX); + addField("framesPerSec", TypeS32, Offset(framesPerSec, ForceFieldBareData)); + addField("numFrames", TypeS32, Offset(numFrames, ForceFieldBareData)); + addField("scrollSpeed", TypeF32, Offset(scrollSpeed, ForceFieldBareData)); + addField("umapping", TypeF32, Offset(umapping, ForceFieldBareData)); + addField("vmapping", TypeF32, Offset(vmapping, ForceFieldBareData)); +} + + +//-------------------------------------------------------------------------- +bool ForceFieldBareData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (fadeMS < 0 || fadeMS > 10000) { + Con::warnf(ConsoleLogEntry::General, "ForceFieldBareData(%s)::onAdd: fadeMS must be in the range [0, 10000]", getName()); + fadeMS = fadeMS < 0 ? 0 : 10000; + } + if (baseTranslucency < 0.0f || baseTranslucency > 1.0f) { + Con::warnf(ConsoleLogEntry::General, "ForceFieldBareData(%s)::onAdd: baseTranslucency must be in the range [0, 1]", getName()); + baseTranslucency = baseTranslucency < 0 ? 0 : 1; + } + + return true; +} + +//-------------------------------------------------------------------------- +// Preload data - load resources +//-------------------------------------------------------------------------- +bool ForceFieldBareData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (!server) + { + U32 i; + for( i=0; iwrite(fadeMS); + stream->write(baseTranslucency); + stream->write(powerOffTranslucency); + stream->writeFlag(teamPermiable); + stream->writeFlag(otherPermiable); + stream->write(color); + stream->write(powerOffColor); + stream->write(framesPerSec); + stream->write(numFrames); + stream->write(scrollSpeed); + stream->write(umapping); + stream->write(vmapping); + + U32 i; + for( i=0; iwriteString(textureName[i]); + } +} + +void ForceFieldBareData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&fadeMS); + stream->read(&baseTranslucency); + stream->read(&powerOffTranslucency); + teamPermiable = stream->readFlag(); + otherPermiable = stream->readFlag(); + stream->read(&color); + stream->read(&powerOffColor); + stream->read(&framesPerSec); + stream->read(&numFrames); + stream->read(&scrollSpeed); + stream->read(&umapping); + stream->read(&vmapping); + + for( U32 i=0; ireadSTString(); + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ForceFieldBare::ForceFieldBare() +{ + mNetFlags.set(Ghostable); + + mTypeMask |= ForceFieldObjectType; + + mCurrState = Closed; + mCurrPosition = 0; + + mClientLastPosition = 0; + mCurrBackDelta = 0.0; + mCurrFade = 1.0; + + mDataBlock = NULL; + + mConvexList = new Convex; + + mElapsedTime = 0.0; +} + +ForceFieldBare::~ForceFieldBare() +{ + delete mConvexList; + mConvexList = NULL; +} + +//-------------------------------------------------------------------------- +void ForceFieldBare::initPersistFields() +{ + Parent::initPersistFields(); +} + + +void ForceFieldBare::consoleInit() +{ + Con::addCommand("ForceFieldBare", "open", cOpen, "obj.open()", 2, 2); + Con::addCommand("ForceFieldBare", "close", cClose, "obj.close()", 2, 2); +} + + +//-------------------------------------------------------------------------- +bool ForceFieldBare::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.min.set(0, 0, 0); + mObjBox.max.set(1, 1, 1); + resetWorldBox(); + + addToScene(); + scriptOnAdd(); + + return true; +} + + +void ForceFieldBare::onRemove() +{ + scriptOnRemove(); + mConvexList->nukeList(); + removeFromScene(); + + Parent::onRemove(); +} + + +bool ForceFieldBare::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------- +void ForceFieldBare::open() +{ + AssertFatal(isServerObject(), "Client objects not allowed in ForceFieldInstance::open()"); + + if (mCurrState != Opening && + mCurrState != Open) { + mCurrState = Opening; + setMaskBits(StateChangeMask); + } +} + +void ForceFieldBare::close() +{ + AssertFatal(isServerObject(), "Client objects not allowed in ForceFieldInstance::close()"); + + if (mCurrState != Closing && + mCurrState != Closed) { + mCurrState = Closing; + setMaskBits(StateChangeMask); + } +} + +void ForceFieldBare::setClientState(const State newState, const U32 newPosition) +{ + // DMMNOTE: This is not the best way to interpolate, but it'll do for now + mCurrState = newState; + mCurrPosition = newPosition; +} + +void ForceFieldBare::setImagePoly(SceneRenderImage* image) +{ + if (mObjScale.x <= mObjScale.y && mObjScale.x <= mObjScale.z) + { + // X is smallest (or all are equal) + image->poly[0].set((mObjBox.min.x + mObjBox.max.x) * 0.5, mObjBox.min.y, mObjBox.min.z); + image->poly[1].set((mObjBox.min.x + mObjBox.max.x) * 0.5, mObjBox.min.y, mObjBox.max.z); + image->poly[2].set((mObjBox.min.x + mObjBox.max.x) * 0.5, mObjBox.max.y, mObjBox.max.z); + image->poly[3].set((mObjBox.min.x + mObjBox.max.x) * 0.5, mObjBox.max.y, mObjBox.min.z); + } + else if (mObjScale.y <= mObjScale.x && mObjScale.y <= mObjScale.z) + { + // Y is smallest + image->poly[0].set(mObjBox.min.x, (mObjBox.min.y + mObjBox.max.y) * 0.5, mObjBox.min.z); + image->poly[1].set(mObjBox.min.x, (mObjBox.min.y + mObjBox.max.y) * 0.5, mObjBox.max.z); + image->poly[2].set(mObjBox.max.x, (mObjBox.min.y + mObjBox.max.y) * 0.5, mObjBox.max.z); + image->poly[3].set(mObjBox.max.x, (mObjBox.min.y + mObjBox.max.y) * 0.5, mObjBox.min.z); + } + else + { + // Z is smallest + image->poly[0].set(mObjBox.min.x, mObjBox.min.y, (mObjBox.min.z + mObjBox.max.z) * 0.5); + image->poly[1].set(mObjBox.min.x, mObjBox.max.y, (mObjBox.min.z + mObjBox.max.z) * 0.5); + image->poly[2].set(mObjBox.max.x, mObjBox.max.y, (mObjBox.min.z + mObjBox.max.z) * 0.5); + image->poly[3].set(mObjBox.max.x, mObjBox.min.y, (mObjBox.min.z + mObjBox.max.z) * 0.5); + } + for (U32 i = 0; i < 4; i++) + { + image->poly[i].convolve(mObjScale); + getTransform().mulP(image->poly[i]); + } + image->plane.set(image->poly[0], image->poly[1], image->poly[2]); + // Calc the area of this poly + Point3F intermed; + mCross(image->poly[2] - image->poly[0], image->poly[3] - image->poly[1], &intermed); + image->polyArea = intermed.len() * 0.5; +} + +//-------------------------------------------------------------------------- +bool ForceFieldBare::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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + + // Since force fields can be fairly large and planar, we need to do a bit more + // heavy lifting here to determine what the proper point to calculate the sort key + // is. In essence, it's the closest point on the box to the camera. + // Transform camera point to object space... + Point3F camObj = state->getCameraPosition(); + getWorldTransform().mulP(camObj); + camObj.convolveInverse(getScale()); + + image->isTranslucent = true; + image->sortType = SceneRenderImage::Plane; + setImagePoly(image); + state->insertRenderImage(image); + } + + return false; +} + + +void ForceFieldBare::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + + + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 dist = cameraOffset.len(); + F32 fogAmount = state->getHazeAndFog(dist,cameraOffset.z); + + ColorF fieldColor; + fieldColor.red = (mDataBlock->color.red * mCurrFade) + (mDataBlock->powerOffColor.red * (1.0f - mCurrFade)); + fieldColor.green = (mDataBlock->color.green * mCurrFade) + (mDataBlock->powerOffColor.green * (1.0f - mCurrFade)); + fieldColor.blue = (mDataBlock->color.blue * mCurrFade) + (mDataBlock->powerOffColor.blue * (1.0f - mCurrFade)); + fieldColor.alpha = (mDataBlock->baseTranslucency * mCurrFade) + (mDataBlock->powerOffTranslucency * (1.0f - mCurrFade)); + + ColorF fogColor = state->getFogColor(); + fogColor.alpha = 0.0; + fieldColor.interpolate( fieldColor, fogColor, fogAmount ); + + glColor4fv( fieldColor ); + + U32 texNum = mElapsedTime * mDataBlock->framesPerSec; + texNum %= mDataBlock->numFrames; + + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[texNum].getGLName()); + glEnable( GL_TEXTURE_2D ); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + F32 umap = mObjScale.x * mDataBlock->umapping; + F32 vmap = mObjScale.y * mDataBlock->vmapping; + F32 vScroll = mElapsedTime * mDataBlock->scrollSpeed; + + glBegin(GL_TRIANGLE_FAN); + { + glTexCoord2f(0, vScroll); + glVertex3f(0, 0, 0); + + glTexCoord2f(0, vmap+vScroll); + glVertex3f(0, 1, 0); + + glTexCoord2f(umap, vmap+vScroll); + glVertex3f(1, 1, 0); + + glTexCoord2f(umap, vScroll); + glVertex3f(1, 0, 0); + } + glEnd(); + + glBegin(GL_TRIANGLE_FAN); + { + glTexCoord2f(0, vScroll); + glVertex3f(0, 0, 1); + + glTexCoord2f(0, vmap+vScroll); + glVertex3f(0, 1, 1); + + glTexCoord2f(umap, vmap+vScroll); + glVertex3f(1, 1, 1); + + glTexCoord2f(umap, vScroll); + glVertex3f(1, 0, 1); + } + glEnd(); + + umap = mObjScale.y * mDataBlock->umapping; + vmap = mObjScale.z * mDataBlock->vmapping; + + glBegin(GL_TRIANGLE_FAN); + { + glTexCoord2f(0, vScroll); + glVertex3f(1, 0, 0); + + glTexCoord2f(0, vmap+vScroll); + glVertex3f(1, 0, 1); + + glTexCoord2f(umap, vmap+vScroll); + glVertex3f(1, 1, 1); + + glTexCoord2f(umap, vScroll); + glVertex3f(1, 1, 0); + } + glEnd(); + + glBegin(GL_TRIANGLE_FAN); + { + glTexCoord2f(0, vScroll); + glVertex3f(0, 0, 0); + + glTexCoord2f(0, vmap+vScroll); + glVertex3f(0, 0, 1); + + glTexCoord2f(umap, vmap+vScroll); + glVertex3f(0, 1, 1); + + glTexCoord2f(umap, vScroll); + glVertex3f(0, 1, 0); + } + glEnd(); + + umap = mObjScale.x * mDataBlock->umapping; + + glBegin(GL_TRIANGLE_FAN); + { + glTexCoord2f(0, vScroll); + glVertex3f(0, 1, 0); + + glTexCoord2f(0, vmap+vScroll); + glVertex3f(0, 1, 1); + + glTexCoord2f(umap, vmap+vScroll); + glVertex3f(1, 1, 1); + + glTexCoord2f(umap, vScroll); + glVertex3f(1, 1, 0); + } + glEnd(); + + glBegin(GL_TRIANGLE_FAN); + { + glTexCoord2f(0, vScroll); + glVertex3f(0, 0, 0); + + glTexCoord2f(0, vmap+vScroll); + glVertex3f(0, 0, 1); + + glTexCoord2f(umap, vmap+vScroll); + glVertex3f(1, 0, 1); + + glTexCoord2f(umap, vScroll); + glVertex3f(1, 0, 0); + } + glEnd(); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glDisable( GL_TEXTURE_2D ); + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +void ForceFieldBare::setTransform(const MatrixF& xform) +{ + Parent::setTransform(xform); + + if (isServerObject()) + setMaskBits(TransformMask); +} + +void ForceFieldBare::setScale(const VectorF & scale) +{ + Parent::setScale(scale); + + if (isServerObject()) + setMaskBits(TransformMask); +} + + +//-------------------------------------------------------------------------- +bool ForceFieldBare::buildPolyList(AbstractPolyList* list, const Box3F&, const SphereF&) +{ + list->setTransform(&mObjToWorld, mObjScale); + list->setObject(this); + list->addBox(mObjBox); + return true; +} + + +bool ForceFieldBare::castRay(const Point3F& s, const Point3F& e, RayInfo* info) +{ + if (mCurrState != Open && mObjBox.collideLine(s, e, &info->t, &info->normal)) { + PlaneF fakePlane; + fakePlane.x = info->normal.x; + fakePlane.y = info->normal.y; + fakePlane.z = info->normal.z; + fakePlane.d = 0; + + PlaneF result; + mTransformPlane(getTransform(), getScale(), fakePlane, &result); + info->normal = result; + + info->object = this; + return true; + } else { + return false; + } +} + + +//-------------------------------------------------------------------------- +void ForceFieldBare::processClientTick() +{ + mClientLastPosition = mCurrPosition; + mCurrBackDelta = 0.0; + + if (mCurrState == Open || + mCurrState == Closed) { + // Nothing to do + return; + } + + if (mCurrState == Opening) { + mCurrPosition += TickMs; + if (mCurrPosition >= mDataBlock->fadeMS) { + mCurrState = Open; + mCurrPosition = mDataBlock->fadeMS; + } + } else if (mCurrState == Closing) { + mCurrPosition -= TickMs; + if (mCurrPosition <= 0) { + mCurrState = Closed; + mCurrPosition = 0; + } + } else { + Con::errorf(ConsoleLogEntry::General, "Bad client state on forcefield! Setting to closed"); + mCurrState = Closed; + mCurrPosition = 0; + } +} + +void ForceFieldBare::processServerTick() +{ + if (mCurrState == Open || + mCurrState == Closed) { + // Nothing to do + return; + } + + if (mCurrState == Opening) { + mCurrPosition += TickMs; + if (mCurrPosition >= mDataBlock->fadeMS) { + mCurrState = Open; + mCurrPosition = mDataBlock->fadeMS; + } + } else if (mCurrState == Closing) { + mCurrPosition -= TickMs; + if (mCurrPosition <= 0) { + mCurrState = Closed; + mCurrPosition = 0; + } + } else { + Con::errorf(ConsoleLogEntry::General, "Bad server state on forcefield! Setting to closed"); + mCurrState = Closed; + mCurrPosition = 0; + setMaskBits(GameBase::InitialUpdateMask); + } +} + +void ForceFieldBare::processTick(const Move*) +{ + if (isServerObject()) + processServerTick(); + else + processClientTick(); +} + +void ForceFieldBare::interpolateTick(F32 backDelta) +{ + mCurrBackDelta = backDelta; + + F32 currPos = F32(mClientLastPosition) + ((mCurrPosition - mClientLastPosition) * backDelta); + mCurrFade = 1.0f - (currPos / F32(mDataBlock->fadeMS)); + mCurrFade = mCurrFade < 0.0 ? 0.0 : mCurrFade; +} + + +//-------------------------------------------------------------------------- +U32 ForceFieldBare::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag((mask & GameBase::InitialUpdateMask) != 0)) { + + stream->writeAffineTransform(mObjToWorld); + mathWrite(*stream, mObjScale); + + } else { + if (stream->writeFlag((mask & TransformMask) != 0)) { + stream->writeAffineTransform(mObjToWorld); + mathWrite(*stream, mObjScale); + } + } + + if (stream->writeFlag((mask & StateChangeMask) != 0)) { + stream->writeInt(mCurrState, StateBitsRequired); + + // DMMTODO: Optimize based on fadeMS. Should recover 21 bits or so in general... + if (mCurrState == Opening || mCurrState == Closing) + stream->write(mCurrPosition); + } + + + return retMask; +} + +void ForceFieldBare::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + MatrixF tempXForm; + Point3F tempScale; + + if (stream->readFlag()) { + // Initial update... + // + stream->readAffineTransform(&tempXForm); + mathRead(*stream, &tempScale); + + setScale(tempScale); + setTransform(tempXForm); + + } else { + if (stream->readFlag()) { + // Transform update... + // + stream->readAffineTransform(&tempXForm); + mathRead(*stream, &tempScale); + + setScale(tempScale); + setTransform(tempXForm); + } + } + + if (stream->readFlag()) { + // State update... + + State newState; + U32 newPosition; + newState = (State)stream->readInt(StateBitsRequired); + + // DMMTODO: Optimize based on fadeMS. Should recover 21 bits or so in general... + if (newState == Closed) + newPosition = 0; + else if (newState == Open) + newPosition = mDataBlock->fadeMS; + else + stream->read(&newPosition); + + setClientState(newState, newPosition); + } + +} + + +void ForceFieldBare::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. + ForceConvex* cp = new ForceConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->init(this); + cp->pField = this; + + cp->mCenter = Point3F(0.5, 0.5, 0.5); + cp->mSize = getScale() * 0.5; +} + +bool ForceFieldBare::isPermiableTo(GameBase* pass) +{ + // Vehicles never pass through forcefields, even when unpowered. + if (pass->getType() & VehicleObjectType) + return false; + + if (isOpen() || + (mDataBlock->otherPermiable && getSensorGroup() != pass->getSensorGroup()) || + (mDataBlock->teamPermiable && getSensorGroup() == pass->getSensorGroup())) { + return true; + } + + return false; +} + +bool ForceFieldBare::isTeamControlled() +{ + return !mDataBlock->otherPermiable; +} + +void ForceFieldBare::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + mElapsedTime += dt; +} diff --git a/game/forceFieldBare.h b/game/forceFieldBare.h new file mode 100644 index 0000000..ea53820 --- /dev/null +++ b/game/forceFieldBare.h @@ -0,0 +1,153 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FORCEFIELDBARE_H_ +#define _FORCEFIELDBARE_H_ + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif + +class Convex; + +// ------------------------------------------------------------------------- +class ForceFieldBareData : public GameBaseData +{ + typedef GameBaseData Parent; + + enum Constants + { + NUM_TEX = 5, + }; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + S32 fadeMS; + F32 baseTranslucency; + F32 powerOffTranslucency; + + StringTableEntry textureName[NUM_TEX]; + TextureHandle textureHandle[NUM_TEX]; + + F32 umapping; + F32 vmapping; + F32 scrollSpeed; + U32 framesPerSec; + U32 numFrames; + bool teamPermiable; + bool otherPermiable; + ColorF color; + ColorF powerOffColor; + + //-------------------------------------- load set variables + public: + + public: + ForceFieldBareData(); + ~ForceFieldBareData(); + + void packData(BitStream*); + void unpackData(BitStream*); + + DECLARE_CONOBJECT(ForceFieldBareData); + static void initPersistFields(); + bool preload(bool server, char errorBuffer[256]); +}; + + +// ------------------------------------------------------------------------- +class ForceFieldBare : public GameBase +{ + typedef GameBase Parent; + + private: + ForceFieldBareData* mDataBlock; + + protected: + // Most of this copied from ForceFieldInstance (Interior forcefields) + enum State { + Open = 0, + Opening = 1, + Closing = 2, + Closed = 3, + + StateBitsRequired = 2 + }; + + State mCurrState; + S32 mCurrPosition; + + S32 mClientLastPosition; // Client interpolation _only_ + F32 mCurrBackDelta; + F32 mCurrFade; + F32 mElapsedTime; + + enum FFBStateChangeMasks { + TransformMask = Parent::NextFreeMask << 0, + StateChangeMask = Parent::NextFreeMask << 1 + }; + + Convex* mConvexList; + void buildConvex(const Box3F& box, Convex* convex); + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + void advanceTime(F32 dt); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + void setImagePoly(SceneRenderImage*); + + // Collision + public: + bool buildPolyList(AbstractPolyList*, const Box3F&, const SphereF&); + bool castRay(const Point3F&, const Point3F&, RayInfo*); + + protected: + void setTransform(const MatrixF&); + void setScale(const VectorF & scale); + + // Time/Move Management + public: + virtual void processClientTick(); // These functions got unwieldy as processTick. + virtual void processServerTick(); // derived classes should override these... + + void processTick(const Move*); + void interpolateTick(F32); + void setClientState(const State newState, const U32 newPosition); + + public: + ForceFieldBare(); + ~ForceFieldBare(); + + bool isOpen() { return mCurrState == Open; } + void open(); + void close(); + + bool isPermiableTo(GameBase*); + bool isTeamControlled(); + + DECLARE_CONOBJECT(ForceFieldBare); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_FORCEFIELDBARE + diff --git a/game/fps/guiClockHud.cc b/game/fps/guiClockHud.cc new file mode 100644 index 0000000..c19a036 --- /dev/null +++ b/game/fps/guiClockHud.cc @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "gui/guiControl.h" +#include "console/consoleTypes.h" +#include "game/gameConnection.h" +#include "game/shapeBase.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(S32 newTime); + + void onRender( Point2I, const RectI &, GuiControl * ); + static void initPersistFields(); + DECLARE_CONOBJECT( GuiClockHud ); +}; + + +//----------------------------------------------------------------------------- + +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() +{ + Parent::initPersistFields(); + + 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 ) ); +} + + +//----------------------------------------------------------------------------- + +void GuiClockHud::onRender(Point2I offset, const RectI &updateRect,GuiControl *) +{ + // Background first + if (mShowFill) + dglDrawRectFill(updateRect, mFillColor); + + // Convert ms time into hours, minutes and seconds. + S32 time = (mTimeOffset + Platform::getVirtualMilliseconds()) / 1000; + S32 secs = time % 60; + S32 mins = (time % 3600) / 60; + S32 hours = time / 3600; + + // Currently only displays min/sec + char buf[256]; + dSprintf(buf,sizeof(buf), "%02d:%02d",mins,secs); + + // Center the text + offset.x += (mBounds.extent.x - mProfile->mFont->getStrWidth(buf)) / 2; + offset.y += (mBounds.extent.y - mProfile->mFont->getHeight()) / 2; + dglSetBitmapModulation(mTextColor); + dglDrawText(mProfile->mFont, offset, buf); + dglClearBitmapModulation(); + + // Border last + if (mShowFrame) + dglDrawRect(updateRect, mFrameColor); +} + + +//----------------------------------------------------------------------------- + +void GuiClockHud::setTime(S32 time) +{ + mTimeOffset = (time * 1000) - Platform::getVirtualMilliseconds(); +} + +ConsoleMethod(GuiClockHud,setTime,void,3, 3,"(time in sec)Sets the current base time for the clock") +{ + GuiClockHud *hud = static_cast(object); + hud->setTime(dAtoi(argv[2])); +} diff --git a/game/fps/guiCrossHairHud.cc b/game/fps/guiCrossHairHud.cc new file mode 100644 index 0000000..7b0adb6 --- /dev/null +++ b/game/fps/guiCrossHairHud.cc @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "gui/guiControl.h" +#include "gui/guiBitmapCtrl.h" +#include "console/consoleTypes.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" + +//----------------------------------------------------------------------------- +/** + Vary basic cross hair hud. + Use the base bitmap control to render a bitmap, and only decides whether + to draw or not depending on the current control object and it's state. + This simple behavior would normally be scripted, but we're going to + be adding new feature to this class. +*/ +class GuiCrossHairHud : public GuiBitmapCtrl +{ + typedef GuiBitmapCtrl Parent; + +public: + GuiCrossHairHud(); + + void onRender( Point2I, const RectI &, GuiControl * ); + static void initPersistFields(); + DECLARE_CONOBJECT( GuiCrossHairHud ); +}; + +/// 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() +{ +} + +void GuiCrossHairHud::initPersistFields() +{ + Parent::initPersistFields(); +} + + +//----------------------------------------------------------------------------- + +void GuiCrossHairHud::onRender(Point2I offset, const RectI &updateRect,GuiControl *ctrl) +{ + // Must have a connection and player control object + GameConnection* conn = GameConnection::getServerConnection(); + if (!conn) + return; + ShapeBase* control = conn->getControlObject(); + if (!control || !(control->getType() & ObjectMask) || !conn->isFirstPerson()) + return; + + // Parent render. + Parent::onRender(offset,updateRect,ctrl); +} + + diff --git a/game/fps/guiHealthBarHud.cc b/game/fps/guiHealthBarHud.cc new file mode 100644 index 0000000..186df54 --- /dev/null +++ b/game/fps/guiHealthBarHud.cc @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "gui/guiControl.h" +#include "console/consoleTypes.h" +#include "game/gameConnection.h" +#include "game/shapeBase.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; + + ColorF mFillColor; + ColorF mFrameColor; + ColorF mDamageFillColor; + + S32 mPulseRate; + F32 mPulseThreshold; + + F32 mValue; + +public: + GuiHealthBarHud(); + + void onRender( Point2I, const RectI &, GuiControl * ); + static void initPersistFields(); + DECLARE_CONOBJECT( GuiHealthBarHud ); +}; + + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT( GuiHealthBarHud ); + +GuiHealthBarHud::GuiHealthBarHud() +{ + mShowFrame = mShowFill = true; + 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() +{ + Parent::initPersistFields(); + + addField( "showFill", TypeBool, Offset( mShowFill, GuiHealthBarHud ) ); + addField( "showFrame", TypeBool, Offset( mShowFrame, GuiHealthBarHud ) ); + addField( "fillColor", TypeColorF, Offset( mFillColor, GuiHealthBarHud ) ); + addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiHealthBarHud ) ); + addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, GuiHealthBarHud ) ); + addField( "pulseRate", TypeS32, Offset( mPulseRate, GuiHealthBarHud ) ); + addField( "pulseThreshold", TypeF32, Offset( mPulseThreshold, GuiHealthBarHud ) ); +} + + +//----------------------------------------------------------------------------- +/** + Gui onRender method. + Renders a health bar with filled background and border. +*/ +void GuiHealthBarHud::onRender(Point2I offset, const RectI &updateRect,GuiControl *) +{ + // Must have a connection and player control object + GameConnection* conn = GameConnection::getServerConnection(); + if (!conn) + return; + ShapeBase* control = conn->getControlObject(); + if (!control || !(control->getType() & PlayerObjectType)) + return; + + // We'll just grab the damage right off the control object. + // Damage value 0 = no damage. + mValue = 1 - control->getDamageValue(); + + // Background first + if (mShowFill) + dglDrawRectFill(updateRect, mFillColor); + + // Pulse the damage fill if it's below the threshold + if (mPulseRate != 0) + if (mValue < mPulseThreshold) { + F32 time = Platform::getVirtualMilliseconds(); + F32 alpha = mFmod(time,mPulseRate) / (mPulseRate / 2.0); + mDamageFillColor.alpha = (alpha > 1.0)? 2.0 - alpha: alpha; + } + else + mDamageFillColor.alpha = 1; + + // Render damage fill % + RectI rect(updateRect); + rect.extent.x *= mValue; + dglDrawRectFill(rect, mDamageFillColor); + + // Border last + if (mShowFrame) + dglDrawRect(updateRect, mFrameColor); +} diff --git a/game/fps/guiShapeNameHud.cc b/game/fps/guiShapeNameHud.cc new file mode 100644 index 0000000..7344b08 --- /dev/null +++ b/game/fps/guiShapeNameHud.cc @@ -0,0 +1,260 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "gui/guiControl.h" +#include "gui/guiTSControl.h" +#include "console/consoleTypes.h" +#include "game/shapeBase.h" +#include "game/gameConnection.h" +#include "scenegraph/scenegraph.h" + +//----------------------------------------------------------------------------- +/** + Displays name & damage ubove shape objects. + This control displays the name and damage value of all named + ShapeBase objects on the client. The name & damage are overlayed + ubove the object, only if the object is within the control's + display area. This gui control only works as a child of a + TSControl, a server connection and client control object must also + be set. This is a stand-alone control and relies only only the + standard base GuiControl. +*/ +class GuiShapeNameHud : public GuiControl { + typedef GuiControl Parent; + + // field data + ColorF mFillColor; + ColorF mFrameColor; + ColorF mTextColor; + ColorF mDamageFillColor; + ColorF mDamageFrameColor; + Point2I mDamageRectSize; + + F32 mVerticalOffset; + F32 mDistanceFade; + bool mShowFrame; + bool mShowFill; + +protected: + void drawName( Point2I offset, const char *buf, F32 opacity); + void drawDamage(Point2I offset, F32 damage, F32 opacity); + +public: + GuiShapeNameHud(); + + // GuiControl + virtual void onRender(Point2I offset, + const RectI &updateRect, GuiControl *firstResponder ); + + static void initPersistFields(); + DECLARE_CONOBJECT( GuiShapeNameHud ); +}; + + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiShapeNameHud); + +static const F32 cDefaultVisibleDistance = 500.0f; + +GuiShapeNameHud::GuiShapeNameHud() +{ + mFillColor.set( 0.25, 0.25, 0.25, 0.25 ); + mFrameColor.set( 0, 1, 0, 1 ); + mTextColor.set( 0, 1, 0, 1 ); + mDamageFillColor.set( 0, 1, 0, 1 ); + mDamageFrameColor.set( 1, 0.6, 0, 1 ); + mDamageRectSize.set(50, 4); + mShowFrame = mShowFill = true; + mVerticalOffset = 0.5; + mDistanceFade = 0.1; +} + +void GuiShapeNameHud::initPersistFields() +{ + Parent::initPersistFields(); + addField( "fillColor", TypeColorF, Offset( mFillColor, GuiShapeNameHud ) ); + addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiShapeNameHud ) ); + addField( "textColor", TypeColorF, Offset( mTextColor, GuiShapeNameHud ) ); + addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, GuiShapeNameHud ) ); + addField( "damageFrameColor", TypeColorF, Offset( mDamageFrameColor, GuiShapeNameHud ) ); + addField( "damageRect", TypePoint2I, Offset( mDamageRectSize, GuiShapeNameHud ) ); + + addField( "showFill", TypeBool, Offset( mShowFill, GuiShapeNameHud ) ); + addField( "showFrame", TypeBool, Offset( mShowFrame, GuiShapeNameHud ) ); + addField( "verticalOffset", TypeF32, Offset( mVerticalOffset, GuiShapeNameHud ) ); + addField( "distanceFade", TypeF32, Offset( mDistanceFade, GuiShapeNameHud ) ); +} + + +//----------------------------------------------------------------------------- +/** + Core render method wich does all the work. + This method scans through all the current client ShapeBase objects + and if they are named, displays their name and damage value. If the + shape is a PlayerObjectType then values are displayed offset from it's + eye point, otherwise the bounding box center is used. +*/ +void GuiShapeNameHud::onRender( Point2I, const RectI &updateRect,GuiControl *) +{ + // Background fill first + if (mShowFill) + dglDrawRectFill(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::getServerConnection(); + if (!conn) return; + ShapeBase* control = 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 = gClientSceneGraph->getVisibleDistance(); + F32 visDistanceSqr = 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(); + + // 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. + for (SimSetIterator itr(conn); *itr; ++itr) { + if ((*itr)->getType() & ShapeBaseObjectType) { + ShapeBase* shape = static_cast(*itr); + 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; + shape->getEyeTransform(&eye); + eye.getColumn(3, &shapePos); + } + else + shapePos = shape->getBoxCenter(); + 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 = 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(projPnt.x, projPnt.y),shape->getShapeName(),opacity); + + // Render the shape's damage bar + F32 damage = mClampF(1 - shape->getDamageValue(), 0, 1); + drawDamage(Point2I(projPnt.x, projPnt.y),damage,opacity); + } + } + } + + // Restore control object collision + control->enableCollision(); + + // Border last + if (mShowFrame) + dglDrawRect(updateRect, mFrameColor); +} + + +//----------------------------------------------------------------------------- +/** + Display the name ubove the shape. + This is a support funtion, called by onRender. +*/ +void GuiShapeNameHud::drawName(Point2I offset, const char *name, F32 opacity) +{ + // Center the name + offset.x -= mProfile->mFont->getStrWidth(name) / 2; + offset.y -= mProfile->mFont->getHeight(); + + // + mTextColor.alpha = opacity; + dglSetBitmapModulation(mTextColor); + dglDrawText(mProfile->mFont, offset, name); + dglClearBitmapModulation(); +} + + +//----------------------------------------------------------------------------- +/** + Display a damage bar ubove the shape. + This is a support funtion, called by onRender. +*/ +void GuiShapeNameHud::drawDamage(Point2I offset, F32 damage, F32 opacity) +{ + mDamageFillColor.alpha = mDamageFrameColor.alpha = opacity; + + // Center the bar + RectI rect(offset, mDamageRectSize); + rect.point.x -= mDamageRectSize.x / 2; + + // Draw the border + dglDrawRect(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) + dglDrawRectFill(rect, mDamageFillColor); +} diff --git a/game/fps/shapeNameHud.cc b/game/fps/shapeNameHud.cc new file mode 100644 index 0000000..03f1976 --- /dev/null +++ b/game/fps/shapeNameHud.cc @@ -0,0 +1,198 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "game/fps/shapeNameHud.h" + +#include "dgl/dgl.h" +#include "gui/guiTSControl.h" +#include "console/consoleTypes.h" +#include "terrain/sky.h" +#include "game/shapeBase.h" +#include "game/gameConnection.h" + +IMPLEMENT_CONOBJECT(ShapeNameHud); + +static const F32 cDefaultVisibleDistance = 500.0f; + +ShapeNameHud::ShapeNameHud() +{ + mFillColor.set( 0.25, 0.25, 0.25, 0.25 ); + mFrameColor.set( 0, 1, 0, 1 ); + mTextColor.set( 0, 1, 0, 1 ); + mDamageFillColor.set( 0, 1, 0, 1 ); + mDamageFrameColor.set( 1, 0.6, 0, 1 ); + mDamageRectSize.set(50, 4); + mOpacity = 0.5f; + mShowFrame = mShowFill = true; + mVerticalOffset = 0.5; + mDistanceFade = 0.1; +} + +void ShapeNameHud::initPersistFields() +{ + Parent::initPersistFields(); + addField( "fillColor", TypeColorF, Offset( mFillColor, ShapeNameHud ) ); + addField( "frameColor", TypeColorF, Offset( mFrameColor, ShapeNameHud ) ); + addField( "textColor", TypeColorF, Offset( mTextColor, ShapeNameHud ) ); + addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, ShapeNameHud ) ); + addField( "damageFrameColor", TypeColorF, Offset( mDamageFrameColor, ShapeNameHud ) ); + addField( "damageRect", TypePoint2I, Offset( mDamageRectSize, ShapeNameHud ) ); + + addField( "opacity", TypeF32, Offset( mOpacity, ShapeNameHud ) ); + addField( "showFill", TypeBool, Offset( mShowFill, ShapeNameHud ) ); + addField( "showFrame", TypeBool, Offset( mShowFrame, ShapeNameHud ) ); + addField( "verticalOffset", TypeF32, Offset( mVerticalOffset, ShapeNameHud ) ); + addField( "distanceFade", TypeF32, Offset( mDistanceFade, ShapeNameHud ) ); +} + +void ShapeNameHud::onRender( Point2I, const RectI &updateRect,GuiControl *) +{ + // Border and background stuff. + if (mShowFill) { + mFillColor.alpha = mOpacity; + dglDrawRectFill(updateRect, mFillColor); + } + if (mShowFrame) + dglDrawRect(updateRect, mFrameColor); + + // Must be in a TS Control + GuiTSCtrl *parent = dynamic_cast(getParent()); + if (!parent) return; + + // Must have a connection and control object + GameConnection* conn = GameConnection::getServerConnection(); + if (!conn) return; + ShapeBase* control = 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 + Sky* sky = gClientSceneGraph->getCurrentSky(); + F32 visDistance = sky ? sky->getVisibleDistance() : cDefaultVisibleDistance; + F32 visDistanceSqr = 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(); + + // 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. + for (SimSetIterator itr(conn); *itr; ++itr) { + if ((*itr)->getType() & ShapeBaseObjectType) { + ShapeBase* shape = static_cast(*itr); + 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; + shape->getEyeTransform(&eye); + eye.getColumn(3, &shapePos); + } + else + shapePos = shape->getBoxCenter(); + 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 = 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(projPnt.x, projPnt.y),shape->getShapeName(),opacity); + + // Render the shape's damage bar + F32 damage = mClampF(1 - shape->getDamageValue(), 0, 1); + drawDamage(Point2I(projPnt.x, projPnt.y),damage,opacity); + } + } + } + + // Restore control object collision + control->enableCollision(); +} + +void ShapeNameHud::drawName(Point2I offset, const char *name, F32 opacity) +{ + // Center the name + offset.x -= mProfile->mFont->getStrWidth(name) / 2; + offset.y -= mProfile->mFont->getHeight(); + + // + mTextColor.alpha = opacity; + dglSetBitmapModulation(mTextColor); + dglDrawText(mProfile->mFont, offset, name); + dglClearBitmapModulation(); +} + +void ShapeNameHud::drawDamage(Point2I offset, F32 damage, F32 opacity) +{ + mDamageFillColor.alpha = mDamageFrameColor.alpha = opacity; + + // Center the bar + RectI rect(offset, mDamageRectSize); + rect.point.x -= mDamageRectSize.x / 2; + + // Draw the border + dglDrawRect(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) + dglDrawRectFill(rect, mDamageFillColor); +} diff --git a/game/fps/shapeNameHud.h b/game/fps/shapeNameHud.h new file mode 100644 index 0000000..61e083c --- /dev/null +++ b/game/fps/shapeNameHud.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _SHAPENAMEHUD_H_ +#define _SHAPENAMEHUD_H_ + +#include "gui/guiControl.h" + +class ShapeNameHud : public GuiControl { + typedef GuiControl Parent; + + // field data + ColorF mFillColor; + ColorF mFrameColor; + ColorF mTextColor; + ColorF mDamageFillColor; + ColorF mDamageFrameColor; + Point2I mDamageRectSize; + + F32 mOpacity; + F32 mVerticalOffset; + F32 mDistanceFade; + bool mShowFrame; + bool mShowFill; + +protected: + void drawName( Point2I offset, const char *buf, F32 opacity); + void drawDamage(Point2I offset, F32 damage, F32 opacity); + +public: + ShapeNameHud(); + + // GuiControl + virtual void onRender(Point2I offset, + const RectI &updateRect, GuiControl *firstResponder ); + + static void initPersistFields(); + DECLARE_CONOBJECT( ShapeNameHud ); +}; + +#endif + diff --git a/game/fx/cameraFXMgr.cc b/game/fx/cameraFXMgr.cc new file mode 100644 index 0000000..de0b5f2 --- /dev/null +++ b/game/fx/cameraFXMgr.cc @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "game/fx/cameraFXMgr.h" + +#include "math/mRandom.h" +#include "math/mMatrix.h" +#include "dgl/dgl.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.link( newFX ); +} + +//-------------------------------------------------------------------------- +// Clear all currently running camera effects +//-------------------------------------------------------------------------- +void CameraFXManager::clear() +{ + mFXList.free(); +} + +//-------------------------------------------------------------------------- +// Update camera effects +//-------------------------------------------------------------------------- +void CameraFXManager::update( F32 dt ) +{ + CameraFXPtr *cur = NULL; + mCamFXTrans.identity(); + + for( cur = mFXList.next( cur ); cur; cur = mFXList.next( cur ) ) + { + CameraFX * curFX = *cur; + curFX->update( dt ); + MatrixF fxTrans = curFX->getTrans(); + + mCamFXTrans.mul( fxTrans ); + + if( curFX->isExpired() ) + { + CameraFXPtr *prev = mFXList.prev( cur ); + mFXList.free( cur ); + cur = prev; + } + } +} diff --git a/game/fx/cameraFXMgr.h b/game/fx/cameraFXMgr.h new file mode 100644 index 0000000..4530672 --- /dev/null +++ b/game/fx/cameraFXMgr.h @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CAMERAFXMGR_H_ +#define _CAMERAFXMGR_H_ + +#ifndef _LLIST_H_ +#include "core/llist.h" +#endif +#ifndef _MPOINT_H_ +#include "math/mPoint.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif + +//************************************************************************** +// Abstract camera effect template +//************************************************************************** +class CameraFX +{ +protected: + F32 mElapsedTime; + F32 mDuration; + MatrixF mCamFXTrans; + +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; + + LList< CameraFXPtr > mFXList; + MatrixF mCamFXTrans; + +public: + void addFX( CameraFX *newFX ); + void clear(); + MatrixF & getTrans(){ return mCamFXTrans; } + void update( F32 dt ); + + CameraFXManager(); + ~CameraFXManager(); + +}; + +extern CameraFXManager gCamFXMgr; + + +#endif diff --git a/game/fx/explosion.cc b/game/fx/explosion.cc new file mode 100644 index 0000000..8f886ad --- /dev/null +++ b/game/fx/explosion.cc @@ -0,0 +1,1022 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/fx/explosion.h" + +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "platform/platformAudio.h" +#include "audio/audioDataBlock.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "core/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 "game/debris.h" +#include "game/gameConnection.h" +#include "game/fx/particleEngine.h" +#include "game/fx/cameraFXMgr.h" + +IMPLEMENT_CONOBJECT(Explosion); + +namespace { + +MRandomLCG sgRandom(0xdeadbeef); + +F32 cCalcCoverage(SimObject*, S32, const char** argv) +{ + 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.0; + } + if (sceneObject->isClientObject() || sceneObject->getContainer() == NULL) { + Con::warnf(ConsoleLogEntry::General, "calcExplosionCoverage: object is on the client, or not in the container system"); + return 1.0; + } + + 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, 0, 1), covMask, &rayInfo) == false) + { + if (sceneObject->getContainer()->castRay(pos + Point3F(0, 0, 1), center, covMask, &rayInfo) == false) { + sceneObject->enableCollision(); + return 1.0; + } + } + + sceneObject->enableCollision(); + return 0.0; + } else { + sceneObject->enableCollision(); + return 1.0; + } +} + +} // namespace {} + +//---------------------------------------------------------------------------- +//-------------------------------------- +// +IMPLEMENT_CO_DATABLOCK_V1(ExplosionData); + +ExplosionData::ExplosionData() +{ + dtsFileName = NULL; + particleDensity = 10; + particleRadius = 1; + + faceViewer = false; + + soundProfile = NULL; + particleEmitter = NULL; + soundProfileId = 0; + particleEmitterId = 0; + + explosionScale.set(1, 1, 1); + playSpeed = 1.0; + + dMemset( emitterList, 0, sizeof( emitterList ) ); + dMemset( emitterIDList, 0, sizeof( emitterIDList ) ); + dMemset( debrisList, 0, sizeof( debrisList ) ); + dMemset( debrisIDList, 0, sizeof( debrisIDList ) ); + + debrisThetaMin = 0; + debrisThetaMax = 90; + debrisPhiMin = 0; + debrisPhiMax = 360; + debrisNum = 1; + debrisNumVariance = 0; + debrisVelocity = 2.0; + debrisVelocityVariance = 0.0; + + dMemset( explosionList, 0, sizeof( explosionList ) ); + dMemset( explosionIDList, 0, sizeof( explosionIDList ) ); + + delayMS = 0; + delayVariance = 0; + lifetimeMS = 1000; + lifetimeVariance = 0; + offset = 0; + + shockwave = NULL; + shockwaveID = 0; + shockwaveOnTerrain = false; + + shakeCamera = false; + camShakeFreq.set( 10.0, 10.0, 10.0 ); + camShakeAmp.set( 1.0, 1.0, 1.0 ); + camShakeDuration = 1.5; + camShakeRadius = 10.0; + camShakeFalloff = 10.0; + + for( U32 i=0; i= 0.01", getName()); + explosionScale.x = explosionScale.x < 0.01 ? 0.01 : explosionScale.x; + explosionScale.y = explosionScale.y < 0.01 ? 0.01 : explosionScale.y; + explosionScale.z = explosionScale.z < 0.01 ? 0.01 : 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.1) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisVelocity < 0.1", getName()); + debrisVelocity = 0.1; + } + if (debrisVelocityVariance > 1000) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisVelocityVariance > 1000", getName()); + debrisVelocityVariance = 1000; + } + if (playSpeed < 0.05) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) playSpeed < 0.05", getName()); + playSpeed = 0.05; + } + 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.0) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) offset < 0.0", getName()); + offset = 0.0; + } + + 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(explosionScale.x * 100, 16); + stream->writeInt(explosionScale.y * 100, 16); + stream->writeInt(explosionScale.z * 100, 16); + } + stream->writeInt(playSpeed * 20, 14); + stream->writeRangedU32(debrisThetaMin, 0, 180); + stream->writeRangedU32(debrisThetaMax, 0, 180); + stream->writeRangedU32(debrisPhiMin, 0, 360); + stream->writeRangedU32(debrisPhiMax, 0, 360); + stream->writeRangedU32(debrisNum, 0, 1000); + stream->writeRangedU32(debrisNumVariance, 0, 1000); + stream->writeInt(debrisVelocity * 10, 14); + stream->writeRangedU32(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( sizes[i].x * 100, 0, 16000); + stream->writeRangedU32( sizes[i].y * 100, 0, 16000); + stream->writeRangedU32( sizes[i].z * 100, 0, 16000); + } +} + +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; + } +} + +bool ExplosionData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (dtsFileName && dtsFileName[0]) { + explosionShape = ResourceManager->load(dtsFileName); + if (!bool(explosionShape)) { + dSprintf(errorBuffer, sizeof(errorBuffer), "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; + + mExplosionInstance = NULL; + mExplosionThread = NULL; + + dMemset( mEmitterList, 0, sizeof( mEmitterList ) ); + + mDelayMS = 0; + mCurrMS = 0; + mEndingMS = 1000; + mActive = false; + mCollideType = 0; + + mInitialNormal.set( 0.0, 0.0, 1.0 ); + mRandAngle = sgRandom.randF( 0.0, 1.0 ) * M_PI * 2.0; +} + +Explosion::~Explosion() +{ + if( mExplosionInstance ) + { + delete mExplosionInstance; + mExplosionInstance = NULL; + mExplosionThread = NULL; + } +} + + +void Explosion::setInitialState(const Point3F& point, const Point3F& normal, const F32 fade) +{ + mInitialPosition = point; + mInitialNormal = normal; + mFade = fade; + mFog = 0.0f; +} + +//-------------------------------------------------------------------------- +void Explosion::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void Explosion::consoleInit() +{ + Con::addCommand("calcExplosionCoverage", cCalcCoverage, "calcExplosionCoverage(\"x y z\", object, coverageMask)", 4, 4); +} + + +//-------------------------------------------------------------------------- +bool Explosion::onAdd() +{ + if(!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.001 ) + { + MatrixF axisOrient = MathUtils::createOrientFromDir( mInitialNormal ); + + MatrixF trans = getTransform(); + Point3F randVec; + randVec.x = sgRandom.randF( -1.0, 1.0 ); + randVec.y = sgRandom.randF( 0.0, 1.0 ); + randVec.z = sgRandom.randF( -1.0, 1.0 ); + 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::getServerConnection(); + ShapeBase *obj = 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 + falloff * 10.0; + falloff = 1.0 / (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::getServerConnection(); + AssertFatal(pNC != NULL, "Error, must have a connection to the server!"); + pNC->addObject(this); + + return true; +} + +void Explosion::onRemove() +{ + for( int i=0; ideleteWhenEmpty(); + mEmitterList[i] = 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)) { + Point3F explosionPos; + mObjToWorld.getColumn(3,&explosionPos); + if (mExplosionInstance) { + Point3F cameraOffset = explosionPos - state->getCameraPosition(); + mFog = state->getHazeAndFog(cameraOffset.len(),cameraOffset.z); + } else { + mFog = 0.0; + } + + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + image->textureSortKey = U32(mDataBlock); + state->setImageRefPoint(this, image); + state->insertRenderImage(image); + } + + 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.0 - firstPart)) + + (mDataBlock->sizes[i] * firstPart); + + return; + } + } + +} + +void Explosion::prepModelView(SceneState* state) +{ + MatrixF rotMatrix( true ); + Point3F targetVector; + if (mDataBlock->faceViewer == true) { + targetVector = mInitialPosition - state->getCameraPosition(); + targetVector.normalize(); + rotMatrix.set( EulerF( 0.0, mRandAngle, 0.0 ) ); + } else { + targetVector = mInitialNormal; + } + + // rotate explosion each time so it's a little different + MatrixF explOrient = MathUtils::createOrientFromDir( targetVector ); + explOrient.mul( rotMatrix ); + explOrient.setPosition( getPosition() ); + dglMultMatrix( &explOrient ); + setCurrentScale(); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + +} + +void Explosion::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + prepModelView(state); + + if( mExplosionInstance ) + { + mExplosionInstance->animate(); + + if (mFade == 1.0) { + mExplosionInstance->setupFog(mFog, state->getFogColor()); + } else { + mExplosionInstance->setupFog(0.0, state->getFogColor()); + mExplosionInstance->setAlphaAlways(mFade * (1.0 - mFog)); + } + mExplosionInstance->render(); + } + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glScalef( 1.0, 1.0, 1.0 ); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +void Explosion::processTick(const Move*) +{ + mCurrMS += TickMs; + + if( mCurrMS >= mEndingMS ) + { + deleteObject(); + } + + if( (mCurrMS > mDelayMS) && !mActive ) + { + explode(); + } + +} + +void Explosion::advanceTime(F32 dt) +{ + if (dt == 0.0) + 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.0, 0.0, 0.0 ), dt * 1000 ); + } + } + +} + +//---------------------------------------------------------------------------- +// Launch Debris +//---------------------------------------------------------------------------- +void Explosion::launchDebris( Point3F &axis ) +{ + + 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, 0, 1), &axisx); + else + mCross(axis, Point3F(0, 1, 0), &axisx); + axisx.normalize(); + + Point3F pos = getPosition() + Point3F( 0.0, 0.0, 0.5 ); + + + 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.0, 1.0 ); + + 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() +{ + + 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; + + 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) + alxPlay(mDataBlock->soundProfile, &getTransform() ); + + if (mDataBlock->particleEmitter) { + ParticleEmitter* emitter = new ParticleEmitter; + emitter->setDataBlock(mDataBlock->particleEmitter); + emitter->registerObject(); + + emitter->emitParticles(mInitialPosition, mInitialNormal, mDataBlock->particleRadius, + Point3F(0, 0, 0), U32(mDataBlock->particleDensity * mFade)); + emitter->deleteWhenEmpty(); + } + + 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() ); + delete pEmitter; + pEmitter = NULL; + } + mEmitterList[i] = pEmitter; + } + } + + return true; +} + diff --git a/game/fx/explosion.h b/game/fx/explosion.h new file mode 100644 index 0000000..bfe20a2 --- /dev/null +++ b/game/fx/explosion.h @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EXPLOSION_H_ +#define _EXPLOSION_H_ + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; +class TSThread; +class AudioProfile; +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; + + AudioProfile* 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; + + ExplosionData(); + DECLARE_CONOBJECT(ExplosionData); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//-------------------------------------------------------------------------- +class Explosion : public GameBase +{ + typedef GameBase Parent; + + private: + ExplosionData* mDataBlock; + + TSShapeInstance* mExplosionInstance; + TSThread* mExplosionThread; + + ParticleEmitter * mEmitterList[ ExplosionData::EC_NUM_EMITTERS ]; + + U32 mCurrMS; + U32 mEndingMS; + F32 mRandAngle; + + protected: + Point3F mInitialPosition; + Point3F mInitialNormal; + F32 mFade; + F32 mFog; + bool mActive; + S32 mDelayMS; + F32 mRandomVal; + U32 mCollideType; + + protected: + bool onAdd(); + void onRemove(); + bool explode(); + + void processTick(const Move*); + void advanceTime(F32 dt); + void updateEmitters( F32 dt ); + void launchDebris( Point3F &axis ); + void spawnSubExplosions(); + void setCurrentScale(); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + void prepModelView(SceneState*); + + public: + Explosion(); + ~Explosion(); + void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0); + + bool onNewDataBlock(GameBaseData* dptr); + void setCollideType( U32 cType ){ mCollideType = cType; } + + DECLARE_CONOBJECT(Explosion); + static void initPersistFields(); + static void consoleInit(); +}; + +#endif // _H_EXPLOSION + diff --git a/game/fx/lightning.cc b/game/fx/lightning.cc new file mode 100644 index 0000000..c85b3aa --- /dev/null +++ b/game/fx/lightning.cc @@ -0,0 +1,1228 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/fx/lightning.h" + +#include "dgl/dgl.h" +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" +#include "math/mathIO.h" +#include "core/bitStream.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "math/mRandom.h" +#include "math/mathUtils.h" +#include "audio/audioDataBlock.h" +#include "platform/platformAudio.h" +#include "terrain/terrData.h" +#include "sceneGraph/sceneGraph.h" +#include "game/player.h" +#include "game/camera.h" + + +IMPLEMENT_CO_DATABLOCK_V1(LightningData); +IMPLEMENT_CO_NETOBJECT_V1(Lightning); + +namespace { + +MRandomLCG sgLightningRand; + +void cLightningWarningFlashes(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-lightning object get in here?"); + Lightning* lightning = static_cast(obj); + + if (lightning->isServerObject()) + lightning->warningFlashes(); +} + +void cLightningStrikeRandomPoint(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-lightning object get in here?"); + Lightning* lightning = static_cast(obj); + + if (lightning->isServerObject()) + lightning->strikeRandomPoint(); +} + +void cLightningStrikeObject(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-lightning object get in here?"); + Lightning* lightning = static_cast(obj); + + S32 id = dAtoi(argv[2]); + ShapeBase* pSB; + + if (lightning->isServerObject() && Sim::findObject(id, pSB)) + lightning->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; + } +} + +} // namespace {} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +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() +{ + Parent::initPersistFields(); + + addField("strikeSound", TypeAudioProfilePtr, Offset(strikeSound, LightningData)); + addField("thunderSounds", TypeAudioProfilePtr, Offset(thunderSounds, LightningData), MaxThunders); + addField("strikeTextures", TypeString, Offset(strikeTextureNames, LightningData), MaxTextures); +} + + +//-------------------------------------------------------------------------- +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, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + dQsort(thunderSounds, MaxThunders, sizeof(AudioProfile*), cmpSounds); + for (numThunders = 0; numThunders < MaxThunders && thunderSounds[numThunders] != NULL; numThunders++) { + // + } + + if (server == false) { + for (U32 i = 0; i < MaxTextures; i++) { + strikeTextures[i] = TextureHandle(strikeTextureNames[i], MeshTexture); + } + } + + + 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.5; + strikeRadius = 20.0; + boltStartRadius = 20.0; + color.set( 1.0, 1.0, 1.0, 1.0 ); + fadeColor.set( 0.1, 0.1, 1.0, 1.0 ); + useFog = true; + + setScale( VectorF( 512, 512, 300 ) ); +} + +Lightning::~Lightning() +{ + // +} + +//-------------------------------------------------------------------------- +void Lightning::initPersistFields() +{ + Parent::initPersistFields(); + + addField("strikesPerMinute",TypeS32, Offset(strikesPerMinute, Lightning)); + addField("strikeWidth", TypeF32, Offset(strikeWidth, Lightning)); + addField("chanceToHitTarget", TypeF32, Offset(chanceToHitTarget, Lightning)); + addField("strikeRadius", TypeF32, Offset(strikeRadius, Lightning)); + addField("boltStartRadius", TypeF32, Offset(boltStartRadius, Lightning)); + addField("color", TypeColorF, Offset(color, Lightning)); + addField("fadeColor", TypeColorF, Offset(fadeColor, Lightning)); + addField("useFog", TypeBool, Offset(useFog, Lightning)); + +} + +void Lightning::consoleInit() +{ + Con::addCommand("Lightning", "warningFlashes", cLightningWarningFlashes, "[LightningObject].warningFlashes()", 2, 2); + Con::addCommand("Lightning", "strikeRandomPoint", cLightningStrikeRandomPoint, "[LightningObject].strikeRandomPoint()", 2, 2); + Con::addCommand("Lightning", "strikeObject", cLightningStrikeObject, "[LightningObject].strikeObject(id)", 3, 3); +} + +//-------------------------------------------------------------------------- +bool Lightning::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.min.set( -0.5, -0.5, -0.5 ); + mObjBox.max.set( 0.5, 0.5, 0.5 ); + + 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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::EndSort; + state->insertRenderImage(image); + } + + return false; +} + + +void Lightning::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + + // RENDER CODE HERE + MatrixF mv; + dglGetModelview(&mv); + Point3F camAxis; + mv.getRow(1, &camAxis); + + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glDepthMask( GL_FALSE ); + + if( useFog ) + { + + if (dglDoesSupportARBMultitexture() && dglDoesSupportFogCoord()) { + glEnable(GL_FOG); + glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT); + GLfloat fogColor[4]; + fogColor[0] = state->getFogColor().red; + fogColor[1] = state->getFogColor().green; + fogColor[2] = state->getFogColor().blue; + fogColor[3] = 0.5f; + glFogfv(GL_FOG_COLOR, fogColor); + glFogi(GL_FOG_MODE, GL_LINEAR); + glFogf(GL_FOG_START, 0.0f); + glFogf(GL_FOG_END, 1.0f); + } + } + + Strike* walk = mStrikeListHead; + while (walk != NULL) { + + glBindTexture(GL_TEXTURE_2D, mDataBlock->strikeTextures[0].getGLName()); + + for( U32 i=0; i<3; i++ ) + { + if( walk->bolt[i].isFading ) + { + F32 alpha = 1.0 - walk->bolt[i].percentFade; + if( alpha < 0.0 ) alpha = 0.0; + glColor4f( fadeColor.red, fadeColor.green, fadeColor.blue, alpha ); + } + else + { + glColor4fv( color ); + } + walk->bolt[i].render( state->getCameraPosition() ); + } + + + walk = walk->next; + } + + glDepthMask( GL_TRUE ); + + glDisable(GL_FOG); + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +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::getServerConnection(); + 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; + + F32 dist = worldPos.len(); + F32 t = dist / 330.0; + + // Ok, we need to schedule a random strike sound t secs in the future... + // + if (t <= 0.03) { + // If it's really close, just play it... + U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1); + alxPlay(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 = 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.0) { + *pThunderWalker = pThunder->next; + delete pThunder; + + // Play the sound... + U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1); + alxPlay(mDataBlock->thunderSounds[thunder]); + } else { + pThunderWalker = &((*pThunderWalker)->next); + } + } +} + + +//-------------------------------------------------------------------------- +void Lightning::processEvent(LightningStrikeEvent* pEvent) +{ + AssertFatal(pEvent->mStart.x >= 0 && pEvent->mStart.x <= 1.0, "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.5, -mObjScale.y * 0.5, 0.0 ); + + RayInfo rayInfo; + Point3F start = strikePoint; + start.z = mObjScale.z * 0.5 + getPosition().z; + strikePoint.z += -mObjScale.z * 0.5; + 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.6; + pStrike->currentAge = 0.0; + pStrike->next = mStrikeListHead; + + for( U32 i=0; i<3; i++ ) + { + F32 randStart = boltStartRadius; + F32 height = mObjScale.z * 0.5 + getPosition().z; + pStrike->bolt[i].startPoint = Point3F( 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; + pStrike->bolt[i].numMinorNodes = 4; + pStrike->bolt[i].maxMinorAngle = 15; + pStrike->bolt[i].generate(); + pStrike->bolt[i].startSplits(); + pStrike->bolt[i].lifetime = 1.0; + pStrike->bolt[i].fadeTime = 0.2; + pStrike->bolt[i].renderTime = gRandGen.randF(0.0, 0.25); + } + + mStrikeListHead = pStrike; + + scheduleThunder(pStrike); + + MatrixF trans(true); + trans.setPosition( strikePoint ); + + if (mDataBlock->strikeSound) + { + alxPlay(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; + strikePoint.x = gRandGen.randF( 0.0, 1.0 ); + strikePoint.y = gRandGen.randF( 0.0, 1.0 ); + strikePoint.z = 0.0; + + // check if an object is within target range + + strikePoint *= mObjScale; + strikePoint += getPosition(); + strikePoint += Point3F( -mObjScale.x * 0.5, -mObjScale.y * 0.5, 0.0 ); + + Box3F queryBox; + F32 boxWidth = strikeRadius * 2; + + queryBox.min.set( -boxWidth * 0.5, -boxWidth * 0.5, -mObjScale.z * 0.5 ); + queryBox.max.set( boxWidth * 0.5, boxWidth * 0.5, mObjScale.z * 0.5 ); + queryBox.min += strikePoint; + queryBox.max += strikePoint; + + SimpleQueryList sql; + getContainer()->findObjects(queryBox, DAMAGEABLE_MASK, + SimpleQueryList::insertionCallback, S32(&sql)); + + SceneObject *highestObj = NULL; + F32 highestPnt = 0.0; + + 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.5 + getPosition().z; + Point3F end = objectCenter; + end.z = -mObjScale.z * 0.5 + getPosition().z; + bool rayHit = gServerContainer.castRay( start, end, + (-1), + &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.0, 0.0, 1.0 ), 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, "not yet done"); +} + + +//-------------------------------------------------------------------------- +U32 Lightning::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & GameBase::InitialUpdateMask)) + { + // 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, 5, "applyDamage", + Con::getIntArg(getId()), + Con::getIntArg(hitObject->getId()), + posArg, + normalArg); + } +} + +//************************************************************************** +// Lightning Bolt +//************************************************************************** +LightningBolt::LightningBolt() +{ + width = 0.1; + startPoint.zero(); + endPoint.zero(); + chanceOfSplit = 0.0; + isFading = false; + elapsedTime = 0.0; + lifetime = 1.0; + startRender = false; +} + +//-------------------------------------------------------------------------- +// Generate nodes +//-------------------------------------------------------------------------- +void LightningBolt::NodeManager::generateNodes() +{ + F32 overallDist = VectorF( endPoint - startPoint ).magnitudeSafe(); + F32 minDistBetweenNodes = overallDist / (numNodes-1); + F32 maxDistBetweenNodes = minDistBetweenNodes / mCos( maxAngle * M_PI / 180.0 ); + + VectorF mainLineDir = endPoint - startPoint; + mainLineDir.normalizeSafe(); + + for( U32 i=0; iisFading = true; + } + curBolt->render( camPos ); + } + + +} + +//-------------------------------------------------------------------------- +// Render segment +//-------------------------------------------------------------------------- +void LightningBolt::renderSegment( NodeManager &segment, const Point3F &camPos, bool renderLastPoint ) +{ + + for( int i=0; igetCurrentTerrain(); + if( !pTerrain ) return 0.0; + + Point3F terrPt = point; + pTerrain->getWorldTransform().mulP(terrPt); + F32 h; + if (pTerrain->getHeight(Point2F(terrPt.x, terrPt.y), &h)) + { + return h; + } + + + return 0.0; +} + + +//---------------------------------------------------------------------------- +// 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.70 ) return; + + if( width < 0.75 ) width = 0.75; + + 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; + newBolt.numMinorNodes = 3; + newBolt.maxMinorAngle = 10; + newBolt.startRender = true; + newBolt.generate(); + + splitList.link( newBolt ); + + VectorF newDir1 = MathUtils::randomDir( diff, 10.0, 45.0 ); + Point3F newEndPoint1 = endPoint + newDir1 * gRandGen.randF( 0.5, 1.5 ) * length; + + VectorF newDir2 = MathUtils::randomDir( diff, 10.0, 45.0 ); + Point3F newEndPoint2 = endPoint + newDir2 * gRandGen.randF( 0.5, 1.5 ) * length; + + createSplit( endPoint, newEndPoint1, depth - 1, width * 0.30 ); + createSplit( endPoint, newEndPoint2, depth - 1, width * 0.30 ); + +} + +//---------------------------------------------------------------------------- +// Start split - kick off the recursive 'createSplit' procedure +//---------------------------------------------------------------------------- +void LightningBolt::startSplits() +{ + + for( U32 i=0; i 0.3 ) 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.0, 40.0 ); + Point3F newEndPoint = node.point + newDir * gRandGen.randF( 0.5, 1.5 ) * length; + + + createSplit( node.point, newEndPoint, 4, width * 0.30 ); + } + + +} + +//---------------------------------------------------------------------------- +// 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.0; + } +} diff --git a/game/fx/lightning.h b/game/fx/lightning.h new file mode 100644 index 0000000..8606097 --- /dev/null +++ b/game/fx/lightning.h @@ -0,0 +1,216 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTNING_H_ +#define _LIGHTNING_H_ + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _LLIST_H_ +#include "core/llist.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif + +class ShapeBase; +class LightningStrikeEvent; +class AudioProfile; + + +// ------------------------------------------------------------------------- +class LightningData : public GameBaseData +{ + typedef GameBaseData Parent; + + public: + enum Constants { + MaxThunders = 8, + MaxTextures = 8 + }; + + //-------------------------------------- Console set variables + public: + AudioProfile* thunderSounds[MaxThunders]; + AudioProfile* strikeSound; + StringTableEntry strikeTextureNames[MaxTextures]; + + //-------------------------------------- load set variables + public: + S32 thunderSoundIds[MaxThunders]; + S32 strikeSoundID; + + TextureHandle strikeTextures[MaxTextures]; + U32 numThunders; + + protected: + bool onAdd(); + + + public: + LightningData(); + ~LightningData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + 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; + LList< LightningBolt > 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(); + + void createSplit( Point3F startPoint, 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; + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + // Time management + void processTick(const Move*); + void interpolateTick(F32); + void advanceTime(F32); + + // 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(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_LIGHTNING + diff --git a/game/fx/particleEmitter.cc b/game/fx/particleEmitter.cc new file mode 100644 index 0000000..d0df6f1 --- /dev/null +++ b/game/fx/particleEmitter.cc @@ -0,0 +1,226 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/fx/particleEmitter.h" +#include "game/fx/particleEngine.h" +#include "core/bitStream.h" +#include "console/consoleTypes.h" +#include "console/objectTypes.h" +#include "math/mathIO.h" + +IMPLEMENT_CO_DATABLOCK_V1(ParticleEmissionDummyData); +IMPLEMENT_CO_NETOBJECT_V1(ParticleEmissionDummy); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ParticleEmissionDummyData::ParticleEmissionDummyData() +{ + timeMultiple = 1.0; +} + +ParticleEmissionDummyData::~ParticleEmissionDummyData() +{ + +} + + +//-------------------------------------------------------------------------- +void ParticleEmissionDummyData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("timeMultiple", TypeF32, Offset(timeMultiple, ParticleEmissionDummyData)); +} + + +//-------------------------------------------------------------------------- +bool ParticleEmissionDummyData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (timeMultiple < 0.01 || timeMultiple > 100) { + Con::warnf("ParticleEmissionDummyData::onAdd(%s): timeMultiple must be between 0.01 and 100", getName()); + timeMultiple = timeMultiple < 0.01 ? 0.01 : 100; + } + + return true; +} + + +bool ParticleEmissionDummyData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + return true; +} + + +//-------------------------------------------------------------------------- +void ParticleEmissionDummyData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(timeMultiple); +} + +void ParticleEmissionDummyData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&timeMultiple); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ParticleEmissionDummy::ParticleEmissionDummy() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + mTypeMask |= EnvironmentObjectType; + + mEmitterDatablock = NULL; + mEmitterDatablockId = 0; + mEmitter = NULL; + mVelocity = 1.0; +} + +ParticleEmissionDummy::~ParticleEmissionDummy() +{ + // +} + +//-------------------------------------------------------------------------- +void ParticleEmissionDummy::initPersistFields() +{ + Parent::initPersistFields(); + + addField("emitter", TypeParticleEmitterDataPtr, Offset(mEmitterDatablock, ParticleEmissionDummy)); + addField("velocity", TypeF32, Offset(mVelocity, ParticleEmissionDummy)); +} + + +void ParticleEmissionDummy::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +bool ParticleEmissionDummy::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (!mEmitterDatablock && mEmitterDatablockId != 0) { + if (Sim::findObject(mEmitterDatablockId, mEmitterDatablock) == false) + Con::errorf(ConsoleLogEntry::General, "ParticleEmissionDummy::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", mDataBlock->getName()); + delete pEmitter; + return false; + } + mEmitter = pEmitter; + } + + mObjBox.min.set(-0.5, -0.5, -0.5); + mObjBox.max.set( 0.5, 0.5, 0.5); + resetWorldBox(); + addToScene(); + + return true; +} + + +void ParticleEmissionDummy::onRemove() +{ + removeFromScene(); + if (isClientObject()) { + mEmitter->deleteWhenEmpty(); + mEmitter = NULL; + } + + Parent::onRemove(); +} + + +bool ParticleEmissionDummy::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; +} + + +//-------------------------------------------------------------------------- +void ParticleEmissionDummy::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + Point3F emitPoint, emitVelocity; + Point3F emitAxis(0, 0, 1); + getTransform().mulV(emitAxis); + getTransform().getColumn(3, &emitPoint); + emitVelocity = emitAxis * mVelocity; + + mEmitter->emitParticles(emitPoint, emitPoint, + emitAxis, + emitVelocity, (dt * mDataBlock->timeMultiple * 1000.0f)); +} + + +//-------------------------------------------------------------------------- +U32 ParticleEmissionDummy::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + if (stream->writeFlag(mEmitterDatablock != NULL)) { + stream->writeRangedU32(mEmitterDatablock->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + return retMask; +} + +void ParticleEmissionDummy::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + 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); +} + diff --git a/game/fx/particleEmitter.h b/game/fx/particleEmitter.h new file mode 100644 index 0000000..d3e6388 --- /dev/null +++ b/game/fx/particleEmitter.h @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_PARTICLEEMISSIONDUMMY +#define _H_PARTICLEEMISSIONDUMMY + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif + +class ParticleEmitterData; +class ParticleEmitter; + +// ------------------------------------------------------------------------- +class ParticleEmissionDummyData : public GameBaseData +{ + typedef GameBaseData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + F32 timeMultiple; + + //-------------------------------------- load set variables + public: + + public: + ParticleEmissionDummyData(); + ~ParticleEmissionDummyData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(ParticleEmissionDummyData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class ParticleEmissionDummy : public GameBase +{ + typedef GameBase Parent; + + private: + ParticleEmissionDummyData* mDataBlock; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + ParticleEmitterData* mEmitterDatablock; + S32 mEmitterDatablockId; + + ParticleEmitter* mEmitter; + F32 mVelocity; + + public: + ParticleEmissionDummy(); + ~ParticleEmissionDummy(); + + // Time/Move Management + public: + void advanceTime(F32); + + DECLARE_CONOBJECT(ParticleEmissionDummy); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_PARTICLEEMISSIONDUMMY + diff --git a/game/fx/particleEngine.cc b/game/fx/particleEngine.cc new file mode 100644 index 0000000..41699ed --- /dev/null +++ b/game/fx/particleEngine.cc @@ -0,0 +1,1477 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/fx/particleEngine.h" + +#include "dgl/dgl.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" +#include "core/bitStream.h" +#include "math/mRandom.h" +#include "dgl/gTexManager.h" + +//-------------------------------------------------------------------------- +//-------------------------------------- Internal global data +// +namespace { + +PEngine* sgParticleEngine = NULL; +MRandomLCG sgRandom(0x1); + +} // namespace {} + + + +//-------------------------------------------------------------------------- +//-------------------------------------- Internal classes +// +struct Particle; +class PEngine +{ + // Interface for emitters. + static const U32 csmBlockSize; + Vector mAllocatedBlocks; + Particle* mFreeList; + + public: + void updateParticles(Particle* particles, ParticleEmitter &emitter, const U32 ms); + void updateSingleParticle(Particle* particle, ParticleEmitter &emitter, const U32 ms); + + Particle* allocateParticle(ParticleEmitter*); + void releaseParticle(Particle*); + + public: + PEngine(); + ~PEngine(); +}; + +const U32 PEngine::csmBlockSize = 512; + +#define MaxParticleSize 50.0 +//-------------------------------------- +class ParticleData : public SimDataBlock +{ + typedef SimDataBlock Parent; + + enum PDConst + { + PDC_MAX_TEX = 50, + }; + + public: + 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[ParticleEngine::PC_COLOR_KEYS]; + F32 sizes[ParticleEngine::PC_SIZE_KEYS]; + F32 times[4]; + + StringTableEntry textureNameList[PDC_MAX_TEX]; + TextureHandle textureList[PDC_MAX_TEX]; + + public: + ParticleData(); + ~ParticleData(); + + void initializeParticle(Particle*, const Point3F&); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + DECLARE_CONOBJECT(ParticleData); + static void initPersistFields(); +}; + + +//-------------------------------------- +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 + + Particle* nextInList; // Managed by the current owning emitter + U32 currentAge; + + Particle* nextInEngine; // Managed by the global engine object + ParticleEmitter* currentOwner; + ColorF color; + F32 size; + F32 spinSpeed; +}; + + + +//-------------------------------------------------------------------------- +//-------------------------------------- Datablock implementation +IMPLEMENT_CO_DATABLOCK_V1(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; + + lifetimeMS = 0; + lifetimeVarianceMS = 0; + + overrideAdvance = false; + orientParticles = false; + orientOnVelocity = true; + useEmitterSizes = false; + useEmitterColors = false; + particleString = NULL; +} + + +IMPLEMENT_GETDATATYPE(ParticleEmitterData) +IMPLEMENT_SETDATATYPE(ParticleEmitterData) + +void ParticleEmitterData::initPersistFields() +{ + Parent::initPersistFields(); + + Con::registerType("ParticleEmitterDataPtr", TypeParticleEmitterDataPtr, sizeof(ParticleEmitterData*), + REF_GETDATATYPE(ParticleEmitterData), + REF_SETDATATYPE(ParticleEmitterData)); + + 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("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)); +} + +static ParticleEmitterData gDefaultEmitterData; + +void ParticleEmitterData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeInt(ejectionPeriodMS, 10); + stream->writeInt(periodVarianceMS, 10); + stream->writeInt(ejectionVelocity * 100, 16); + stream->writeInt(velocityVariance * 100, 14); + if(stream->writeFlag(ejectionOffset != gDefaultEmitterData.ejectionOffset)) + stream->writeInt(ejectionOffset * 100, 16); + stream->writeRangedU32(thetaMin, 0, 180); + stream->writeRangedU32(thetaMax, 0, 180); + if(stream->writeFlag(phiReferenceVel != gDefaultEmitterData.phiReferenceVel)) + stream->writeRangedU32(phiReferenceVel, 0, 360); + if(stream->writeFlag(phiVariance != gDefaultEmitterData.phiVariance)) + stream->writeRangedU32(phiVariance, 0, 360); + stream->writeFlag(overrideAdvance); + stream->writeFlag(orientParticles); + stream->writeFlag(orientOnVelocity); + stream->writeInt(lifetimeMS >> 5, 10); + stream->writeInt(lifetimeVarianceMS >> 5, 10); + stream->writeFlag(useEmitterSizes); + stream->writeFlag(useEmitterColors); + + stream->write(dataBlockIds.size()); + for (U32 i = 0; i < dataBlockIds.size(); i++) + stream->write(dataBlockIds[i]); +} + +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 = stream->readRangedU32(0, 180); + thetaMax = stream->readRangedU32(0, 180); + if(stream->readFlag()) + phiReferenceVel = stream->readRangedU32(0, 360); + else + phiReferenceVel = gDefaultEmitterData.phiReferenceVel; + + if(stream->readFlag()) + phiVariance = stream->readRangedU32(0, 360); + else + phiVariance = gDefaultEmitterData.phiVariance; + + overrideAdvance = stream->readFlag(); + orientParticles = stream->readFlag(); + orientOnVelocity = stream->readFlag(); + lifetimeMS = stream->readInt(10) << 5; + lifetimeVarianceMS = stream->readInt(10) << 5; + 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]); +} + +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 (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; + } + + + // Tokenize and load the particle datablocks... + // + if (particleString != NULL) { + 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; + } + 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()); + } + } + delete [] tokCopy; + if (particleDataBlocks.size() == 0) { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName()); + return false; + } + } + + return true; +} + +bool ParticleEmitterData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == 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); + } + + return true; +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +IMPLEMENT_CO_DATABLOCK_V1(ParticleData); + +ParticleData::ParticleData() +{ + dragCoefficient = 0.0f; + windCoefficient = 1.0f; + gravityCoefficient = 0.0f; + inheritedVelFactor = 0.0f; + constantAcceleration = 0.0f; + lifetimeMS = 1000; + lifetimeVarianceMS = 0; + spinSpeed = 0.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); + stream->writeSignedFloat(gravityCoefficient / 10, 12); + stream->writeFloat(inheritedVelFactor, 9); + if(stream->writeFlag(constantAcceleration != gDefaultParticleData.constantAcceleration)) + stream->write(constantAcceleration); + + stream->writeInt(lifetimeMS >> 5, 10); + stream->writeInt(lifetimeVarianceMS >> 5,10); + if(stream->writeFlag(spinSpeed != gDefaultParticleData.spinSpeed)) + stream->write(spinSpeed); + if(stream->writeFlag(spinRandomMin != gDefaultParticleData.spinRandomMin || spinRandomMax != gDefaultParticleData.spinRandomMax)) + { + stream->writeInt(spinRandomMin + 1000, 11); + stream->writeInt(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); + } + + for( count=0; countwriteInt(count, 6); + for(i = 0; i < count; i++) + stream->writeString( textureNameList[i] ); +} + +void ParticleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + dragCoefficient = stream->readFloat(10) * 5; + if(stream->readFlag()) + stream->read(&windCoefficient); + else + windCoefficient = gDefaultParticleData.windCoefficient; + gravityCoefficient = stream->readSignedFloat(12) * 10; + inheritedVelFactor = stream->readFloat(9); + if(stream->readFlag()) + stream->read(&constantAcceleration); + else + constantAcceleration = gDefaultParticleData.constantAcceleration; + + lifetimeMS = stream->readInt(10) << 5; + lifetimeVarianceMS = stream->readInt(10) << 5; + if(stream->readFlag()) + stream->read(&spinSpeed); + else + spinSpeed = gDefaultParticleData.spinSpeed; + + if(stream->readFlag()) + { + spinRandomMin = stream->readInt(11) - 1000; + spinRandomMax = 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); + } + count = stream->readInt(6); + for(i = 0; i < count;i ++) + textureNameList[i] = stream->readSTString(); +} + +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 (numFrames > PDC_MAX_TEX){ + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) numFrames invalid", getName()); + numFrames = PDC_MAX_TEX; + return false; + } + if (framesPerSec > 200){ + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) framesPerSec invalid", getName()); + framesPerSec = 20; + 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]; + } + } + + return true; +} + +bool ParticleData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if(!server) + { + numFrames = 0; + for( int i=0; idataBlock = 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(sgRandom.randI() % (2 * lifetimeVarianceMS + 1)) - S32(lifetimeVarianceMS); + + // assign spin amount + init->spinSpeed = spinSpeed + sgRandom.randF( spinRandomMin, spinRandomMax ); +} + + +//---------------------------------------------------------------------------- +//-------------------------------------- Emitter implementation +// +ParticleEmitter::ParticleEmitter() +{ + mDeleteWhenEmpty = false; + mDeleteOnTick = false; + + mParticleListHead = NULL; + + mInternalClock = 0; + mNextParticleTime = 0; + + mLastPosition.set(0, 0, 0); + mHasLastPosition = false; + + mLifetimeMS = 0; + mElapsedTimeMS = 0; +} + +ParticleEmitter::~ParticleEmitter() +{ + AssertFatal(mParticleListHead == NULL, "Error, particles remain in emitter after remove?"); +} + +//-------------------------------------------------------------------------- +bool ParticleEmitter::onAdd() +{ + if(!Parent::onAdd()) + return false; + + removeFromProcessList(); + + mLifetimeMS = mDataBlock->lifetimeMS; + if( mDataBlock->lifetimeVarianceMS ) + { + mLifetimeMS += S32( sgRandom.randI() % (2 * mDataBlock->lifetimeVarianceMS + 1)) - S32(mDataBlock->lifetimeVarianceMS ); + } + + return true; +} + + +//-------------------------------------------------------------------------- +void ParticleEmitter::onRemove() +{ + Particle* pProbe = mParticleListHead; + while (pProbe != NULL) { + Particle* pRemove = pProbe; + pProbe = pProbe->nextInList; + + pRemove->nextInList = NULL; + sgParticleEngine->releaseParticle(pRemove); + } + mParticleListHead = NULL; + + if (mSceneManager != NULL) { + gClientContainer.removeObject(this); + gClientSceneGraph->removeObjectFromScene(this); + } + + Parent::onRemove(); +} + + +bool ParticleEmitter::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +bool ParticleEmitter::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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + + state->insertRenderImage(image); + } + + return false; +} + + +struct SortParticle +{ + Particle* p; + F32 k; +}; + +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::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + + MatrixF modelview; + dglGetModelview(&modelview); + Point3F x; + Point3F y; + modelview.getRow(0, &x); + modelview.getRow(2, &y); + + Point3F viewvec; + modelview.getRow(1, &viewvec); + + MatrixF camView; + modelview.transposeTo( (F32*) &camView ); + + // DMMFIX: slow! + // + Vector orderedVector(__FILE__, __LINE__); + Particle* pProbe = mParticleListHead; + while (pProbe) { + orderedVector.increment(); + orderedVector.last().p = pProbe; + orderedVector.last().k = mDot(pProbe->pos, viewvec); + pProbe = pProbe->nextInList; + } +// if (orderedVector.size() != 0) +// dQsort(orderedVector.address(), orderedVector.size(), sizeof(SortParticle), cmpSortParticles); + + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glDepthMask(GL_FALSE); + + + if( orderedVector.size() > 0 && orderedVector[0].p->dataBlock->useInvAlpha ) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + + 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); + + F32 spinFactor = (1.0/1000.0) * (1.0/360.0) * M_PI * 2.0; + + + for (U32 i = 0; i < orderedVector.size(); i++) { + Particle* particle = orderedVector[i].p; + + if( particle->dataBlock->animateTexture ) + { + U32 texNum = particle->currentAge * (1.0/1000.0) * particle->dataBlock->framesPerSec; + texNum %= particle->dataBlock->numFrames; + glBindTexture(GL_TEXTURE_2D, particle->dataBlock->textureList[texNum].getGLName()); + } + else + { + glBindTexture(GL_TEXTURE_2D, particle->dataBlock->textureList[0].getGLName()); + } + + if( mDataBlock->orientParticles ) + { + renderOrientedParticle( *particle, state->getCameraPosition() ); + } + else + { + renderBillboardParticle( *particle, basePoints, camView, spinFactor ); + } + + } + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +inline void ParticleEmitter::renderBillboardParticle( Particle &part, Point3F *basePnts, + MatrixF &camView, F32 spinFactor ) +{ + + glBegin(GL_QUADS); + + glColor4f(part.color.red, + part.color.green, + part.color.blue, + part.color.alpha); + + + F32 width = part.size * 0.5; + F32 spinAngle = part.spinSpeed * part.currentAge * spinFactor; + + F32 sy, cy; + mSinCos(spinAngle, sy, cy); + Point3F points[4]; + + for( int i=0; i<4; i++ ) + { + points[i].x = cy * basePnts[i].x - sy * basePnts[i].z; + points[i].y = basePnts[i].y; + points[i].z = sy * basePnts[i].x + cy * basePnts[i].z; + camView.mulP( points[i] ); + points[i] *= width; + points[i] += part.pos; + } + + glTexCoord2f(0, 1); + glVertex3fv(points[0]); + glTexCoord2f(1, 1); + glVertex3fv(points[1]); + glTexCoord2f(1, 0); + glVertex3fv(points[2]); + glTexCoord2f(0, 0); + glVertex3fv(points[3]); + + glEnd(); +} + +//-------------------------------------------------------------------------- +inline void ParticleEmitter::renderOrientedParticle( Particle &part, const Point3F &camPos ) +{ + 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(); + + glBegin(GL_QUADS); + + glColor4f(part.color.red, + part.color.green, + part.color.blue, + part.color.alpha); + + + F32 width = part.size * 0.5; + + dir *= width; + crossDir *= width; + Point3F start = part.pos - dir; + Point3F end = part.pos + dir; + + + glTexCoord2f(0, 0); + glVertex3fv( start + crossDir ); + + glTexCoord2f(0, 1); + glVertex3fv( start - crossDir ); + + glTexCoord2f(1, 1); + glVertex3fv( end - crossDir ); + + glTexCoord2f(1, 0); + glVertex3fv( end + crossDir ); + + glEnd(); +} + + +//-------------------------------------------------------------------------- +void ParticleEmitter::stealParticle(Particle* steal) +{ + Particle** ppParticle = &mParticleListHead; + while (*ppParticle) { + if (*ppParticle == steal) { + *ppParticle = (*ppParticle)->nextInList; + steal->nextInList = NULL; + return; + } + + ppParticle = &((*ppParticle)->nextInList); + } + AssertFatal(false, "Trying to steal a particle that doesn't belong to this emitter!"); +} + +//-------------------------------------------------------------------------- +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); +} + +void ParticleEmitter::emitParticles(const Point3F& start, + const Point3F& end, + const Point3F& axis, + const Point3F& velocity, + const U32 numMilliseconds) +{ + // 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; + + U32 advanceMS = numMilliseconds - currTime; + if (advanceMS > mParticleListHead->totalLifetime) { + // Well, shoot, why did we create this in the first place? + Particle* old = mParticleListHead; + mParticleListHead = old->nextInList; + old->nextInList = NULL; + sgParticleEngine->releaseParticle(old); + } else { + if (advanceMS != 0) + sgParticleEngine->updateSingleParticle(mParticleListHead, *this, advanceMS); + } + mNextParticleTime = 0; + } + } + + while (currTime < numMilliseconds) { + S32 nextTime = mDataBlock->ejectionPeriodMS; + if (mDataBlock->periodVarianceMS != 0) { + nextTime += S32(sgRandom.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; + + // 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) { + if (advanceMS > mParticleListHead->totalLifetime) { + // Well, shoot, why did we create this in the first place? + Particle* old = mParticleListHead; + mParticleListHead = old->nextInList; + old->nextInList = NULL; + sgParticleEngine->releaseParticle(old); + } else { + if (advanceMS != 0) + sgParticleEngine->updateSingleParticle(mParticleListHead, *this, advanceMS); + } + } + } + + // DMMFIX: Lame and slow... + if (particlesAdded == true) + updateBBox(); + if (mParticleListHead != NULL && mSceneManager == NULL) { + gClientSceneGraph->addObjectToScene(this); + gClientContainer.addObject(this); + gClientProcessList.addObject(this); + } + + mLastPosition = end; + mHasLastPosition = true; +} + +void ParticleEmitter::updateBBox() +{ + Point3F min(1e10, 1e10, 1e10); + Point3F max(-1e10, -1e10, -1e10); + + Particle* pProbe = mParticleListHead; + while (pProbe != NULL) { + min.setMin(pProbe->pos); + max.setMax(pProbe->pos); + + pProbe = pProbe->nextInList; + } + + mObjBox = Box3F(min, max); + MatrixF temp = getTransform(); + setTransform(temp); +} + + +//-------------------------------------------------------------------------- + +void ParticleEmitter::emitParticles(const Point3F& rCenter, + const Point3F& rNormal, + const F32 radius, + const Point3F& velocity, + S32 count) +{ + // 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 * sgRandom.randF()))); + pos += axisy * (radius * (1 - (2 * sgRandom.randF()))); + pos += axisz * (radius * sgRandom.randF()); + + Point3F axis = pos; + axis.normalize(); + pos += rCenter; + + addParticle(pos, axis, velocity, axisz); + } + + // Set world bounding box + mObjBox.min = rCenter - Point3F(radius, radius, radius); + mObjBox.max = rCenter + Point3F(radius, radius, radius); + resetWorldBox(); + + // Make sure we're part of the world + if (mParticleListHead != NULL && mSceneManager == NULL) { + gClientSceneGraph->addObjectToScene(this); + gClientContainer.addObject(this); + gClientProcessList.addObject(this); + } + mHasLastPosition = false; +} + + +//-------------------------------------------------------------------------- +void ParticleEmitter::addParticle(const Point3F& pos, + const Point3F& axis, + const Point3F& vel, + const Point3F& axisx) +{ + Particle* pNew = sgParticleEngine->allocateParticle(this); + pNew->nextInList = mParticleListHead; + mParticleListHead = pNew; + + Point3F ejectionAxis = axis; + F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * sgRandom.randF() + + mDataBlock->thetaMin; + + F32 ref = (F32(mInternalClock) / 1000.0) * mDataBlock->phiReferenceVel; + F32 phi = ref + sgRandom.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 * sgRandom.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; + + // Select a datablock for this particle + U32 dBlockIndex = (U32)(mCeil(sgRandom.randF() * F32(mDataBlock->particleDataBlocks.size())) - 1); + mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel); +} + + +//-------------------------------------------------------------------------- +void ParticleEmitter::processTick(const Move*) +{ + if (mDeleteOnTick == true) + deleteObject(); +} + + +void ParticleEmitter::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + mElapsedTimeMS += dt * 1000.0f; + + U32 numMSToUpdate = (U32)(dt * 1000.0f); + if (numMSToUpdate == 0) + return; + + Particle** ppProbe = &mParticleListHead; + while (*ppProbe != NULL) { + (*ppProbe)->currentAge += numMSToUpdate; + if ((*ppProbe)->currentAge >= (*ppProbe)->totalLifetime) { + // Remove this particle + Particle* remove = *ppProbe; + *ppProbe = remove->nextInList; + remove->nextInList = NULL; + sgParticleEngine->releaseParticle(remove); + } else { + ppProbe = &((*ppProbe)->nextInList); + } + } + + if (mParticleListHead == NULL && mDeleteWhenEmpty) { + mDeleteOnTick = true; + } else { + if (numMSToUpdate != 0 && mParticleListHead) + sgParticleEngine->updateParticles(mParticleListHead, *this, numMSToUpdate); + } +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +namespace ParticleEngine { + +Point3F windVelocity(0.f, 0.f, 0.f); + +void init() +{ + AssertFatal(sgParticleEngine == NULL, "ParticleEngine::init: engine already initialized"); + + sgParticleEngine = new PEngine; +} + +void destroy() +{ + AssertFatal(sgParticleEngine != NULL, "ParticleEngine::destroy: engine not initialized"); + + delete sgParticleEngine; + sgParticleEngine = NULL; +} + +} // namespace ParticleEngine + +//-------------------------------------------------------------------------- +PEngine::PEngine() +{ + mFreeList = NULL; +} + + +PEngine::~PEngine() +{ + mFreeList = NULL; + for (U32 i = 0; i < mAllocatedBlocks.size(); i++) + { + delete [] mAllocatedBlocks[i]; + mAllocatedBlocks[i] = NULL; + } +} + +//-------------------------------------------------------------------------- +Particle* PEngine::allocateParticle(ParticleEmitter* emitter) +{ + if (mFreeList == NULL) + { + // Add a new block to the free list... + mAllocatedBlocks.push_back(new Particle[csmBlockSize]); + Particle* pArray = mAllocatedBlocks.last(); + for (U32 i = 0; i < csmBlockSize - 1; i++) + pArray[i].nextInEngine = &pArray[i + 1]; + pArray[csmBlockSize - 1].nextInEngine = NULL; + mFreeList = &pArray[0]; + } + AssertFatal(mFreeList != NULL, "Error, must have a free list here!"); + + Particle* pParticle = mFreeList; + mFreeList = pParticle->nextInEngine; + + dMemset(pParticle, 0, sizeof(Particle)); + pParticle->nextInEngine = NULL; + pParticle->currentOwner = emitter; + + return pParticle; +} + +void PEngine::releaseParticle(Particle* release) +{ + release->nextInEngine = mFreeList; + mFreeList = release; +} + + +//-------------------------------------------------------------------------- +void PEngine::updateParticles(Particle* particles, ParticleEmitter &emitter, const U32 ms) +{ + AssertFatal(particles != NULL, "PEngine::updateParticles: Error, must have particles to process in this function"); + AssertFatal(ms != 0, "PEngine::updateParticles: error, no time to update?"); + + Particle* pProbe = particles; + while (pProbe != NULL) { + updateSingleParticle(pProbe, emitter, ms); + pProbe = pProbe->nextInList; + } +} + +void PEngine::updateSingleParticle(Particle* particle, ParticleEmitter &emitter, const U32 ms) +{ + AssertFatal(particle != NULL, "PEngine::updateSingleParticle: Error, must have a particle to process in this function"); + AssertFatal(ms != 0, "PEngine::updateSingleParticle: error, no time to update?"); + + F32 t = F32(ms) / 1000.0; + + Point3F a = particle->acc; + a -= particle->vel * particle->dataBlock->dragCoefficient; + a -= ParticleEngine::windVelocity * particle->dataBlock->windCoefficient; + a += Point3F(0, 0, -9.81) * particle->dataBlock->gravityCoefficient; + + particle->vel += a * t; + particle->pos += particle->vel * t; + + // Now update the particle's color + t = F32(particle->currentAge) / F32(particle->totalLifetime); + AssertFatal(t <= 1.0f, "Out out bounds filter function for particle."); + + for (U32 i = 1; i < 4; i++) { + if (particle->dataBlock->times[i] >= t) { + F32 firstPart = t - particle->dataBlock->times[i-1]; + F32 total = particle->dataBlock->times[i] - + particle->dataBlock->times[i-1]; + + firstPart /= total; + + if( emitter.getDataBlock()->useEmitterColors ) + { + particle->color.interpolate(emitter.colors[i-1], emitter.colors[i], firstPart); + } + else + { + particle->color.interpolate(particle->dataBlock->colors[i-1], + particle->dataBlock->colors[i], + firstPart); + } + + if( emitter.getDataBlock()->useEmitterSizes ) + { + particle->size = (emitter.sizes[i-1] * (1.0 - firstPart)) + + (emitter.sizes[i] * firstPart); + } + else + { + particle->size = (particle->dataBlock->sizes[i-1] * (1.0 - firstPart)) + + (particle->dataBlock->sizes[i] * firstPart); + } + + return; + } + } + particle->color = particle->dataBlock->colors[ParticleEngine::PC_COLOR_KEYS - 1]; + particle->size = particle->dataBlock->sizes[ParticleEngine::PC_COLOR_KEYS - 1]; +} + + diff --git a/game/fx/particleEngine.h b/game/fx/particleEngine.h new file mode 100644 index 0000000..5ce3416 --- /dev/null +++ b/game/fx/particleEngine.h @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_PARTICLEEMITTER +#define _H_PARTICLEEMITTER + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif + +//-------------------------------------- Engine initialization... +// +namespace ParticleEngine { + + enum ParticleConsts + { + PC_COLOR_KEYS = 4, + PC_SIZE_KEYS = 4, + }; + + void init(); + void destroy(); + + extern Point3F windVelocity; + inline void setWindVelocity(const Point3F & vel) { windVelocity = vel; } + inline Point3F getWindVelocity() { return windVelocity; } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- The data and the Emitter class +// are all that the game should deal +// with (other than initializing the +// global engine pointer of course) +// +struct Particle; +class ParticleData; + +//-------------------------------------- +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, char errorBuffer[256]); + + bool onAdd(); + + public: + S32 ejectionPeriodMS; + S32 periodVarianceMS; + + F32 ejectionVelocity; + F32 velocityVariance; + F32 ejectionOffset; + + F32 thetaMin; + F32 thetaMax; + + F32 phiReferenceVel; + F32 phiVariance; + + U32 lifetimeMS; + U32 lifetimeVarianceMS; + + bool overrideAdvance; + bool orientParticles; + bool orientOnVelocity; + bool useEmitterSizes; + bool useEmitterColors; + + StringTableEntry particleString; + Vector particleDataBlocks; + Vector dataBlockIds; +}; + + +//-------------------------------------- +class ParticleEmitter : public GameBase +{ + typedef GameBase Parent; + friend class PEngine; + + public: + ParticleEmitter(); + ~ParticleEmitter(); + + void setSizes( F32 *sizeList ); + 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(); + + // 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); + + // Internal interface + protected: + void addParticle(const Point3F&, const Point3F&, const Point3F&, const Point3F&); + void renderBillboardParticle( Particle &part, Point3F *basePnts, MatrixF &camView, F32 spinFactor ); + void renderOrientedParticle( Particle &part, const Point3F &camPos ); + void updateBBox(); + + protected: + bool onAdd(); + void onRemove(); + + void processTick(const Move*); + void advanceTime(F32); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + // PEngine interface + private: + void stealParticle(Particle*); + + private: + ParticleEmitterData* mDataBlock; + + Particle* mParticleListHead; + + U32 mInternalClock; + + U32 mNextParticleTime; + + Point3F mLastPosition; + bool mHasLastPosition; + + bool mDeleteWhenEmpty; + bool mDeleteOnTick; + + S32 mLifetimeMS; + S32 mElapsedTimeMS; + + F32 sizes[ParticleEngine::PC_SIZE_KEYS]; + ColorF colors[ParticleEngine::PC_COLOR_KEYS]; +}; + +#endif // _H_PARTICLEEMITTER + diff --git a/game/fx/precipitation.cc b/game/fx/precipitation.cc new file mode 100644 index 0000000..2ae0291 --- /dev/null +++ b/game/fx/precipitation.cc @@ -0,0 +1,976 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/fx/precipitation.h" + +#include "dgl/dgl.h" +#include "math/mathIO.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "terrain/sky.h" +#include "game/gameConnection.h" +#include "game/player.h" + +#define COLOR_OFFSET 0.25 + +bool Precipitation::smPrecipitationOn = true; +bool Precipitation::smPrecipitationPause = false; + +IMPLEMENT_CO_NETOBJECT_V1(Precipitation); +IMPLEMENT_CO_DATABLOCK_V1(PrecipitationData); + +namespace { + +MRandomLCG sgRandom(0xdeadbeef); + + +} // namespace {} + +//---------------------------------------------------------------------------- +//-------------------------------------- + + +PrecipitationData::PrecipitationData() +{ + soundProfile = NULL; + soundProfileId = 0; + mType = 0; + mMaxSize = 1.0f; + mMaterialListName = NULL; + mSizeX = 1.0; + mSizeY = 1.0; +} + +IMPLEMENT_GETDATATYPE(PrecipitationData) + IMPLEMENT_SETDATATYPE(PrecipitationData) + + void PrecipitationData::initPersistFields() +{ + Parent::initPersistFields(); + + Con::registerType("GameBaseDataPtr", TypeGameBaseDataPtr, sizeof(PrecipitationData*), + REF_GETDATATYPE(PrecipitationData), + REF_SETDATATYPE(PrecipitationData)); + + addField("soundProfile", TypeAudioProfilePtr, Offset(soundProfile, PrecipitationData)); + addField("type", TypeS32, Offset(mType, PrecipitationData)); + addField("maxSize", TypeF32, Offset(mMaxSize, PrecipitationData)); + addField("materialList", TypeString, Offset(mMaterialListName,PrecipitationData)); + addField("sizeX", TypeF32, Offset(mSizeX, PrecipitationData)); + addField("sizeY", TypeF32, Offset(mSizeY, PrecipitationData)); + + ///////////////////////// + //JohnA Will remove + // Used to tweak the precipitation + ///////////////////////// + addField("movingBoxPer", TypeF32, Offset(tMoveingBoxPer, PrecipitationData)); + addField("divHeightVal", TypeF32, Offset(tDivHeightVal, PrecipitationData)); + addField("sizeBigBox", TypeF32, Offset(tSizeBigBox, PrecipitationData)); + addField("topBoxSpeed", TypeF32, Offset(tTopBoxSpeed, PrecipitationData)); + addField("frontBoxSpeed", TypeF32, Offset(tFrontBoxSpeed, PrecipitationData)); + addField("topBoxDrawPer", TypeF32, Offset(tTopBoxDrawPer, PrecipitationData)); + addField("bottomDrawHeight", TypeF32, Offset(tBottomDrawHeight, PrecipitationData)); + addField("skipIfPer", TypeF32, Offset(tSkipIfPer, PrecipitationData)); + addField("bottomSpeedPer", TypeF32, Offset(tBottomSpeedPer, PrecipitationData)); + addField("FrontSpeedPer", TypeF32, Offset(tFrontSpeedPer, PrecipitationData)); + addField("FrontRadiusPer", TypeF32, Offset(tFrontRadiusPer, PrecipitationData)); + ///////////////////////// + +} + +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"); + + if (mSizeX <= 0.0f || mSizeX > 20.0f) { + Con::warnf(ConsoleLogEntry::General, "PrecipitationData(%s)::onAdd: sizeX must be in the range [0 >, 20]", getName()); + mSizeX = 1.0; + } + + if (mSizeY <= 0.0f || mSizeY > 20.0f) { + Con::warnf(ConsoleLogEntry::General, "PrecipitationData(%s)::onAdd: sizeY must be in the range [0 >, 20]", getName()); + mSizeY = 1.0; + } + + return true; +} + +void PrecipitationData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if (stream->writeFlag(soundProfile != NULL)) + stream->writeRangedU32(soundProfile->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + stream->write(mType); + stream->write(mMaxSize); + stream->writeString(mMaterialListName); + stream->write(mSizeX); + stream->write(mSizeY); + + ///////////////////////// + //JohnA Will remove + // Used to tweak the precipitation + ///////////////////////// + stream->write(tMoveingBoxPer); + stream->write(tDivHeightVal); + stream->write(tSizeBigBox); + stream->write(tTopBoxSpeed); + stream->write(tFrontBoxSpeed); + stream->write(tTopBoxDrawPer); + stream->write(tBottomDrawHeight); + stream->write(tSkipIfPer); + stream->write(tBottomSpeedPer); + stream->write(tFrontSpeedPer); + stream->write(tFrontRadiusPer); + ///////////////////////// + +} + +void PrecipitationData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + if (stream->readFlag()) + soundProfileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + soundProfileId = 0; + + stream->read(&mType); + stream->read(&mMaxSize); + mMaterialListName = stream->readSTString(); + stream->read(&mSizeX); + stream->read(&mSizeY); + + ///////////////////////// + //JohnA Will remove + // Used to tweak the precipitation + ///////////////////////// + stream->read(&tMoveingBoxPer); + stream->read(&tDivHeightVal); + stream->read(&tSizeBigBox); + stream->read(&tTopBoxSpeed); + stream->read(&tFrontBoxSpeed); + stream->read(&tTopBoxDrawPer); + stream->read(&tBottomDrawHeight); + stream->read(&tSkipIfPer); + stream->read(&tBottomSpeedPer); + stream->read(&tFrontSpeedPer); + stream->read(&tFrontRadiusPer); + ///////////////////////// +} + +//-------------------------------------------------------------------------- +//-------------------------------------- + +Precipitation::Precipitation() +{ + mTypeMask |= ProjectileObjectType; + + mNetFlags.set(ScopeAlways); + mNumDrops = 0; + mCurrentTime = 0; + mAudioHandle = 0; + mPercentage = 1.0f; + mFirstTime = true; + mStormData.lastTime = 0.0f; + mStormData.endPercentage = -1.0f; + mStormData.time = 0.0f; + mStormData.speed = 0.0f; + mStormData.state = done; + mStormData.numDrops = 0.0f; + mStormData.currentTime = 0.0f; + mStormPrecipitationOn = true; + mLastPos.set(0.0f, 0.0f, 0.0f); + mRandomHeight = false; + mAverageSpeed = 0.0f; + mLastHeight = 0.0f; + mShiftPrecip.set(0.0f, 0.0f, 0.0f); + mColorCount = 0; + for(S32 x = 0; x < MAX_NUM_COLOR; ++x) + mColor[x].set(-1.0f, 0.0f, 0.0f); + + mMinVelocity = -1.0f; + mMaxVelocity = -1.0f; + mOffset.set(0.0f, 0.0f, 1.0f); + mOffsetSpeed = 0.25f; + mMaxDrops = -1; + mRadius = -1; +} + +Precipitation::~Precipitation() +{ + +} + +void Precipitation::inspectPostApply() +{ + setMaskBits(InitMask); +} + +//-------------------------------------------------------------------------- +void Precipitation::initPersistFields() +{ + Parent::initPersistFields(); + addField("percentage", TypeF32, Offset(mPercentage, Precipitation)); + addField("color1", TypeColorF, Offset(mColor[0], Precipitation)); + addField("color2", TypeColorF, Offset(mColor[1], Precipitation)); + addField("color3", TypeColorF, Offset(mColor[2], Precipitation)); + addField("offsetSpeed", TypeF32, Offset(mOffsetSpeed, Precipitation)); + addField("minVelocity", TypeF32, Offset(mMinVelocity, Precipitation)); + addField("maxVelocity", TypeF32, Offset(mMaxVelocity, Precipitation)); + addField("maxNumDrops", TypeS32, Offset(mMaxDrops, Precipitation)); + addField("maxRadius", TypeS32, Offset(mRadius, Precipitation)); +} + +void cSetPercentage(SimObject *obj, S32, const char **argv) +{ + Precipitation *ctrl = static_cast(obj); + ctrl->setPercentage(dAtof(argv[2])); +} + +static void cSetupStorm(SimObject *obj, S32, const char **argv) +{ + Precipitation *ctrl = static_cast(obj); + ctrl->setupStorm(dAtof(argv[2]), dAtof(argv[3])); +} + +static void cStormShow(SimObject *obj, S32, const char **argv) +{ + Precipitation *ctrl = static_cast(obj); + ctrl->stormShow(dAtob(argv[2])); +} + +void Precipitation::consoleInit() +{ + Con::addVariable("$pref::precipitationOn", TypeBool, &smPrecipitationOn); + Con::addVariable("$pref::prePause", TypeBool, &smPrecipitationPause); + Con::addCommand("Precipitation", "setPercentage", cSetPercentage, "precipitation.setPercentage(percentage <1.0 to 0.0>)", 3, 3); + Con::addCommand("Precipitation", "stormPrecipitation", cSetupStorm, "precipitation.stormPrecipitation(Percentage <0 to 1>, Time)", 4, 4); + Con::addCommand("Precipitation", "stormShow", cStormShow, "precipitation.stormShow(bool)",3, 3); +} + +//-------------------------------------------------------------------------- +bool Precipitation::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (mPercentage > 1.0f || mPercentage < 0.0f) { + Con::warnf(ConsoleLogEntry::General, "Precipitation::onAdd - Percentage is invalid. <= 1.0 or >= 0.0 "); + mPercentage = 1.0f; + } + + if (isClientObject()) { + mRandomHeight = true; + for (U32 i = 0; i < mMaxDrops; i++) + mFallingObj[i].notValid = true; + + if(mDataBlock->mMaterialListName[0]) + loadDml(); + + if (mDataBlock->soundProfile && smPrecipitationOn) + mAudioHandle = alxPlay(mDataBlock->soundProfile, &getTransform() ); + mNumDrops = mMaxDrops * mPercentage; + mStormData.numDrops = mNumDrops; + setupTexCoords(); + } + else + assignName("Precipitation"); + + mObjBox.min.set(-1e6, -1e6, -1e6); + mObjBox.max.set( 1e6, 1e6, 1e6); + + resetWorldBox(); + addToScene(); + setDefaultValues(); + + return true; +} + +void Precipitation::setDefaultValues() +{ + mColorCount = 0; + for(S32 x = 0; x < MAX_NUM_COLOR; ++x) + if(mColor[x].red >= 0.0f) + mColorCount++; + else + break; + if(mColorCount == 0) + { + if(mDataBlock->mType == 0) + mColor[0].set(0.6, 0.6, 0.6); + else if(mDataBlock->mType == 1) + mColor[0].set(1.0, 1.0, 1.0); + else + mColor[0].set(0.9, 0.8, 0.5); + mColorCount = 1; + } + if(mMinVelocity == -1.0f) + { + if(mDataBlock->mType == 0) + mMinVelocity = 1.25f; + else if(mDataBlock->mType == 1) + mMinVelocity = 0.25f; + else + mMinVelocity = 0.25f; + } + if(mMaxVelocity == -1.0f) + { + if(mDataBlock->mType == 0) + mMaxVelocity = 4.0f; + else if(mDataBlock->mType == 1) + mMaxVelocity = 1.5f; + else + mMaxVelocity = 1.0f; + } + + if(mRadius == -1.0f) + { + if(mDataBlock->mType == 0) + mRadius = 80; + else if(mDataBlock->mType == 1) + mRadius = 125; + else + mRadius = 80; + } + + if(mMaxDrops > MAX_NUM_DROPS || mMaxDrops < 0) + mMaxDrops = MAX_NUM_DROPS; +} + +U32 Precipitation::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + Parent::packUpdate(con, mask, stream); + + if(stream->writeFlag(mask & InitMask)) + { + stream->write(mPercentage); + stream->write(mColorCount); + for(S32 x = 0; x < mColorCount; ++x) + stream->write(mColor[x]); + stream->write(mOffsetSpeed); + stream->write(mMinVelocity); + stream->write(mMaxVelocity); + stream->write(mMaxDrops); + stream->write(mRadius); + + if(stream->writeFlag((mStormData.currentTime / 32.0f) < mStormData.time)) + { + stream->write(mStormData.currentTime); + stream->write(mStormData.endPercentage); + stream->write(mStormData.time); + } + } + + if(stream->writeFlag(mask & StormShowMask)) + stream->write(mStormPrecipitationOn); + + if(stream->writeFlag(mask & StormMask)) + { + stream->write(mStormData.endPercentage); + stream->write(mStormData.time); + mStormData.currentTime = 0.0f; + } + + if(stream->writeFlag(mask & PercentageMask)) + stream->write(mPercentage); + + return 0; +} + +void Precipitation::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if(stream->readFlag()) + { + stream->read(&mPercentage); + stream->read(&mColorCount); + for(S32 x = 0; x < mColorCount; ++x) + stream->read(&mColor[x]); + stream->read(&mOffsetSpeed); + stream->read(&mMinVelocity); + stream->read(&mMaxVelocity); + stream->read(&mMaxDrops); + stream->read(&mRadius); + + if(stream->readFlag()) + { + stream->read(&mStormData.currentTime); + stream->read(&mStormData.endPercentage); + stream->read(&mStormData.time); + + F32 percentage = mStormData.endPercentage; + + mStormData.speed = (percentage - mPercentage) / (mStormData.time * 32.0f); + mStormData.endPercentage = percentage; + mStormData.state = (mPercentage > percentage) ? out : in; + mPercentage += (mStormData.state == in) ? mStormData.speed * mStormData.currentTime : + -mStormData.speed * mStormData.currentTime; + mStormPrecipitationOn = true; + + } + } + if(stream->readFlag()) + { + stream->read(&mStormPrecipitationOn); + if(!mStormPrecipitationOn) + mPercentage = 0.0f; + } + + if(stream->readFlag()) + { + stream->read(&mStormData.endPercentage); + stream->read(&mStormData.time); + + if(mStormData.time) + startStorm(); + } + + if(stream->readFlag()) + { + stream->read(&mPercentage); + mNumDrops = mMaxDrops * mPercentage; + } +} + +void Precipitation::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +bool Precipitation::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + setDefaultValues(); + + mAverageSpeed = ((mMinVelocity + mMaxVelocity) / 2.0f) * 0.65f; + scriptOnNewDataBlock(); + return true; +} + +void Precipitation::loadDml() +{ + S32 x; + mNumTextures = 0; + Stream *stream = ResourceManager->openStream(mDataBlock->mMaterialListName); + if(stream) + { + mMaterialList.read(*stream); + ResourceManager->closeStream(stream); + mMaterialList.load(); + for(x = 0; x < mMaterialList.size(); ++x, ++mNumTextures) + mTextures[x] = mMaterialList.getMaterial(x); + } +} + +//-------------------------------------------------------------------------- +bool Precipitation::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (!smPrecipitationOn) + { + if (mAudioHandle) + { + alxStop(mAudioHandle); + mAudioHandle = 0; + } + return false; + } + else + { + if(!mStormPrecipitationOn) + return false; + if (!mAudioHandle && mDataBlock->soundProfile) + mAudioHandle = alxPlay(mDataBlock->soundProfile, &getTransform()); + } + + 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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::EndSort; + state->insertRenderImage(image); + } + + return false; +} + +void Precipitation::renderObject(SceneState* state, SceneRenderImage*) +{ + if(smPrecipitationOn) + { + if(!mStormPrecipitationOn) + return; + } + else + return; + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + state->mModelview.getRow(0,&mRotX); + state->mModelview.getRow(2,&mRotZ); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + if(mStormData.state != done) + updateStorm(); + if(mDataBlock->mType == 0 || mDataBlock->mType == 1) + renderPrecip(state->getCameraPosition()); + else if(mDataBlock->mType == 2) + renderSand(); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + mFirstTime = false; + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +void Precipitation::advanceTime(F32 dt) +{ + if(!mFirstTime) + mCurrentTime += dt; +} + +void Precipitation::renderPrecip(Point3F camPos) +{ + Point3F point[4]; + F32 coordX, coordY; + F32 xRadius, yRadius; + mRotX *= mDataBlock->mSizeX; + mRotZ *= mDataBlock->mSizeY; + Point2F value; + F32 renderPer = 1.0f, minSpeed = 0.0f; + F32 theRadius, curZVelocity = 0.0f; + F32 height, speed, radVal; + F32 random1, random2, random3; + mOffsetVal.set(0.0f, 0.0f); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glEnable(GL_ALPHA_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glAlphaFunc(GL_GREATER, 0.1f); + glBindTexture(GL_TEXTURE_2D,mTextures[0].getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + calcVelocityBox(camPos, curZVelocity, renderPer); + if(curZVelocity < 0.0f) + { + if(mDataBlock->mType == 0) + minSpeed = ((mCurrentTime - mLastRenderTime) / (1.0f / curZVelocity)) - mMinVelocity; + } + glBegin(GL_QUADS); + { + for(S32 x=0; x < mNumDrops; ++x) + { + if(mStormData.numDrops > x || mFallingObj[x].stillFalling) + { + speed = mFallingObj[x].speed; + if(smPrecipitationPause) + speed = 0; + else if(minSpeed < 0.0f) + speed = (mFallingObj[x].speed > minSpeed) ? minSpeed : mFallingObj[x].speed; + + mFallingObj[x].curPos.set((mFallingObj[x].curPos.x + mShiftPrecip.x) + (mFallingObj[x].offset.x * speed), + (mFallingObj[x].curPos.y + mShiftPrecip.y) + (mFallingObj[x].offset.y * speed), + (mFallingObj[x].curPos.z + mShiftPrecip.z) + speed); + + if(mFallingObj[x].notValid || !mBox.isContained(mFallingObj[x].curPos)) + { + if(mStormData.numDrops > x) + { + xRadius = mRadius * ((mOffsetVal.x > 0.0f) ? 1.0f - mOffsetVal.x : 1.0f + mOffsetVal.x); + yRadius = mRadius * ((mOffsetVal.y > 0.0f) ? 1.0f - mOffsetVal.y : 1.0f + mOffsetVal.y); + + theRadius = (xRadius > yRadius) ? yRadius : xRadius; + + random1 = Platform::getRandom(); + random2 = Platform::getRandom(); + random3 = Platform::getRandom(); + + value.set( Platform::getRandom() * 2.0f - 1.0f, Platform::getRandom() * 2.0f - 1.0f); + value.normalize(); + + mFallingObj[x].speed = -mMinVelocity + (-(mMaxVelocity - mMinVelocity) * random3); + if(random1 > (1.0f - renderPer) || (Platform::getRandom() < mDataBlock->tTopBoxDrawPer && renderPer < - 0.4)) + { + //Start Drop on top of box + if(renderPer < -0.4) + mFallingObj[x].speed *= mDataBlock->tFrontSpeedPer * 2.5f; + if(mRandomHeight) + height = (mBox.max.z - 5.0f) * (Platform::getRandom() * 2.0f - 1.0f);// + Platform::getRandom() * 30; + else + height = mBox.max.z - 5.0f; + mFallingObj[x].dropType = 1; + radVal = theRadius * mClampF(((random2 < 0.2f) ? random3 : random2), + 0.01f, 1.0f); + } + else if(random1 > (0.0f - renderPer)) + { + //Start Drop on side of box + height = random3 * mBox.max.z; + radVal = theRadius * mDataBlock->tFrontRadiusPer; + mFallingObj[x].speed *= mDataBlock->tFrontSpeedPer; + mFallingObj[x].dropType = 2; + } + else + { + //Start Drop on bottom of box + height = mBox.min.z + mDataBlock->tBottomDrawHeight; + mFallingObj[x].dropType = 3; + radVal = theRadius * random3; + mFallingObj[x].speed *= mDataBlock->tBottomSpeedPer; + } + value *= radVal; + mFallingObj[x].colorIndex = (S32)(mCeil(mColorCount * random2) - 1.0f); + mFallingObj[x].startPos.set(camPos.x + mNewCenter.x + value.x, camPos.y + mNewCenter.y + value.y, height); + mFallingObj[x].curPos = mFallingObj[x].startPos; + mFallingObj[x].startDiff = mFallingObj[x].startPos.z - camPos.z + 20; + mFallingObj[x].offset = mOffset; + mFallingObj[x].texIndex = (S32)(mCeil((NUM_TEXTURES - 1) * getRandomVal()) - 1.0f); + mFallingObj[x].stillFalling = true; + mFallingObj[x].notValid = false; + } + else + { + mFallingObj[x].stillFalling = false; + } + } + + if(mFallingObj[x].stillFalling)// && mBox.isContained(mFallingObj[x].curPos)) + { + coordX = mTexCoord[mFallingObj[x].texIndex].x; + coordY = mTexCoord[mFallingObj[x].texIndex].y; + + point[1] = mFallingObj[x].curPos + mRotX - mRotZ; + point[2] = mFallingObj[x].curPos - mRotX - mRotZ; + if( mDataBlock->mType == 1 ) + { + point[0] = mFallingObj[x].curPos + mRotX + mRotZ; + point[3] = mFallingObj[x].curPos - mRotX + mRotZ; + } + else + { + point[0].set(point[1].x + (mFallingObj[x].offset.x * mDataBlock->mSizeY * 2), point[1].y + (mFallingObj[x].offset.y * mDataBlock->mSizeY * 2), point[1].z + (mFallingObj[x].offset.z * mDataBlock->mSizeY * 2)); + point[3].set(point[2].x + (mFallingObj[x].offset.x * mDataBlock->mSizeY * 2), point[2].y + (mFallingObj[x].offset.y * mDataBlock->mSizeY * 2), point[2].z + (mFallingObj[x].offset.z * mDataBlock->mSizeY * 2)); + } + + if(mFallingObj[x].dropType == 1) + { + F32 alphaVal = 1.0f - ((mFallingObj[x].curPos.z - camPos.z) / mFallingObj[x].startDiff); + glColor4f(mColor[mFallingObj[x].colorIndex].red, + mColor[mFallingObj[x].colorIndex].green, + mColor[mFallingObj[x].colorIndex].blue, + alphaVal); + } + else + glColor4f(mColor[mFallingObj[x].colorIndex].red, + mColor[mFallingObj[x].colorIndex].green, + mColor[mFallingObj[x].colorIndex].blue, + 1.0f); + + glTexCoord2f(coordX, coordY); + glVertex3f(point[0].x, point[0].y, point[0].z); + glTexCoord2f(coordX, coordY + 0.25); + glVertex3f(point[1].x, point[1].y, point[1].z); + glTexCoord2f(coordX + 0.25, coordY + 0.25); + glVertex3f(point[2].x, point[2].y, point[2].z); + glTexCoord2f(coordX + 0.25, coordY); + glVertex3f(point[3].x, point[3].y, point[3].z); + } + } + } + } + glEnd(); + glDisable(GL_TEXTURE_2D); + glDisable(GL_ALPHA_TEST); + glDisable(GL_BLEND); + mLastRenderTime = mCurrentTime; + mRandomHeight = false; +} + +void Precipitation::renderSand() +{ + Point3F objPos; + F32 dt, modVal; + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + for(S32 x=0; x < mNumDrops; ++x) + { + if(mStormData.numDrops > x || mFallingObj[x].stillFalling) + { + mFallingObj[x].offset.x += mOffset.x; + mFallingObj[x].offset.y += mOffset.y; + if(mFallingObj[x].endTime < mCurrentTime) + { + if(mStormData.numDrops > x) + { + modVal = mFmod(mCurrentTime - mFallingObj[x].endTime, mFallingObj[x].endTime - mFallingObj[x].startTime); + mFallingObj[x].startTime = mCurrentTime - modVal; + mFallingObj[x].endTime = (mCurrentTime + ((mMinVelocity * Platform::getRandom())+mMaxVelocity)) - modVal; + mFallingObj[x].size = (mDataBlock->mMaxSize * Platform::getRandom()); + mFallingObj[x].offset.set(0.0, 0.0, 0.0); + mFallingObj[x].colorIndex = (S32)(mCeil(mColorCount * Platform::getRandom()) - 1.0f); + mFallingObj[x].stillFalling = true; + } + else + mFallingObj[x].stillFalling = false; + } + dt = (mFallingObj[x].endTime - mCurrentTime) / + (mFallingObj[x].endTime - mFallingObj[x].startTime); + + glPointSize(mFallingObj[x].size); + glColor4f(mColor[mFallingObj[x].colorIndex].red, mColor[mFallingObj[x].colorIndex].green, mColor[mFallingObj[x].colorIndex].blue, 1.0f - dt); + objPos = Point3F((mFallingObj[x].startPos.x * mRadius)+mFallingObj[x].offset.x, + (mFallingObj[x].startPos.y * mRadius)+mFallingObj[x].offset.y, + (mFallingObj[x].startPos.z * (dt * mRadius)) - mRadius); + glBegin(GL_POINTS); + glVertex3f(objPos.x, objPos.y, objPos.z); + glEnd(); + } + } + glDisable(GL_BLEND); +} + +void Precipitation::calcVelocityBox(Point3F camPos, F32 &curZVelocity, F32 &renderPer) +{ + GameConnection * con = GameConnection::getServerConnection(); + if(!con) + return; + + Sky* pSky = mSceneManager->getCurrentSky(); + mOffset.set(0.0f, 0.0f, 1.0f); + if (pSky) + if(pSky->mEffectPrecip) + mOffset.set(pSky->mWindDir.x * mOffsetSpeed, pSky->mWindDir.y * mOffsetSpeed, 1.0f); + + F32 radZ, speed; + F32 randomVal, bottomHeight; + Player *player = NULL; + + mNewCenter.set(mOffset.x * mRadius, mOffset.y * mRadius, 0.0f); + mShiftPrecip.set(0.0f, 0.0f, 0.0f); + if((camPos - mLastPos).len() > mRadius / 2.0f) + mShiftPrecip = camPos - mLastPos; + + mLastPos = camPos; + bottomHeight = (camPos.z - (mRadius / mDataBlock->tDivHeightVal)); + + ShapeBase * conObj = con->getControlObject(); + if(conObj->getWaterCoverage()) + bottomHeight = conObj->getLiquidHeight() + mDataBlock->mSizeY; + + player = dynamic_cast(conObj); + if(!player) + { + mBox.min.set(camPos.x - mRadius, camPos.y - mRadius, bottomHeight); + mBox.max.set(camPos.x + mRadius, camPos.y + mRadius, camPos.z + (mRadius / mDataBlock->tDivHeightVal)); + return; + } + Point3F curVel(player->getVelocity()); + F32 percentage = 0.0f; + F32 movePer = mDataBlock->tMoveingBoxPer; + + //Calc the offset of the inner box along x and y + if(curVel.x) + { + percentage = curVel.x / 40.0f; + mOffsetVal.x = (curVel.x > 0.0f) ? + (percentage > movePer) ? movePer : percentage : + (percentage < -movePer) ? -movePer : percentage; + } + if(curVel.y) + { + percentage = curVel.y / 40.0f; + mOffsetVal.y = (curVel.y > 0.0f) ? + (percentage > movePer) ? movePer : percentage : + (percentage < -movePer) ? -movePer : percentage; + } + + curZVelocity = curVel.z; + speed = Point2F(curVel.x, curVel.y).len(); + radZ = mRadius; + if(speed > 1.0f) + { + F32 val = 40.0f / speed; + radZ *= (val > 1.0f) ? 1.0f : (val < MIN_ZRADIUS) ? MIN_ZRADIUS : val; + } + //Move the box up slow... Helps keep snow around the player... + F32 topHeight = (radZ / mDataBlock->tDivHeightVal); + if(topHeight > mLastHeight && (topHeight - mLastHeight) > 5.0f) + topHeight += 5.0f; + mLastHeight = topHeight; + + if(mDataBlock->tSizeBigBox) + { + mBox.min.set(camPos.x - (mRadius - (mRadius * mOffsetVal.x)), + camPos.y - (mRadius - (mRadius * mOffsetVal.y)), bottomHeight); + mBox.max.set(camPos.x + (mRadius + (mRadius * mOffsetVal.x)), + camPos.y + (mRadius + (mRadius * mOffsetVal.y)), camPos.z + topHeight); + } + else + { + mBox.min.set(camPos.x - mRadius, camPos.y - mRadius, camPos.z - (mRadius / mDataBlock->tDivHeightVal)); + mBox.max.set(camPos.x + mRadius, camPos.y + mRadius, camPos.z + (mRadius / mDataBlock->tDivHeightVal)); + } + + speed = Point3F(curVel.x, curVel.y, curVel.z).len(); + randomVal = Platform::getRandom(); + if(speed < mDataBlock->tTopBoxSpeed) + renderPer = 1.0f; + else if ( speed < mDataBlock->tFrontBoxSpeed ) + { + if(randomVal < 0.25) + renderPer = 1.0f; + else + renderPer = (curVel.z / speed) + (1.0f - ((speed - mDataBlock->tTopBoxSpeed)/ (mDataBlock->tFrontBoxSpeed - mDataBlock->tTopBoxSpeed))); + } + else + { + if(randomVal < 0.25) + renderPer = 1.0f; + else + renderPer = curVel.z / speed; + } + + if(speed) + curVel.normalize(); + else + curVel.set(0.0f, 0.0f, 0.0f); + + mNewCenter.set(curVel.x * mAbs(mOffsetVal.x * mRadius), curVel.y * mAbs(mOffsetVal.y * mRadius), curVel.z * mRadius); + + if(mAverageSpeed && mOffset.z && (mOffset.x || mOffset.y)) + mNewCenter.set(mNewCenter.x + ((mRadius / (mAverageSpeed * mOffset.z)) * mOffset.x), + mNewCenter.y + ((mRadius / (mAverageSpeed * mOffset.z)) * mOffset.y), + 0.0f); +} + +void Precipitation::setPercentage(F32 newPer) +{ + if(newPer <= 1.0f && newPer >= 0.0f) + { + mPercentage = newPer; + mNumDrops = mMaxDrops * mPercentage; + setMaskBits(PercentageMask); + } +} + +void Precipitation::stormShow(bool show) +{ + mStormPrecipitationOn = show; + setMaskBits(StormShowMask); +} + +void Precipitation::setupStorm(F32 percentage, F32 time) +{ + mStormData.time = time; + if(mStormData.endPercentage >= 0.0f) + { + mStormData.state = (mStormData.endPercentage > percentage) ? out : in; + mPercentage = mStormData.endPercentage; + } + else + mStormData.state = (mPercentage > percentage) ? out : in; + mStormData.endPercentage = percentage; + + setMaskBits(StormMask); +} + +void Precipitation::startStorm() +{ + F32 percentage = mStormData.endPercentage; + + mStormData.speed = (percentage - mPercentage) / (mStormData.time * 32.0f); + mStormData.endPercentage = percentage; + mStormData.state = (mPercentage > percentage) ? out : in; + mStormPrecipitationOn = true; +} + +void Precipitation::updateStorm() +{ + F32 offset = 1.0f; + U32 currentTime = Sim::getCurrentTime(); + if(mStormData.lastTime != 0) + offset = (currentTime - mStormData.lastTime) / 32.0f; + + mStormData.lastTime = currentTime; + + mPercentage += (mStormData.speed * offset); + if((mStormData.state == in && mPercentage >= mStormData.endPercentage) || + (mStormData.state == out && mPercentage <= mStormData.endPercentage)) + { + mPercentage = mStormData.endPercentage; + mStormData.state = done; + mStormData.lastTime = 0.0f; + } + mStormData.numDrops = mMaxDrops * mPercentage; +} + +void Precipitation::processTick(const Move* move) +{ + Parent::processTick(move); + mStormData.currentTime++; +} + +void Precipitation::setupTexCoords() +{ + S32 x, y, numTimes = NUM_TEXTURES / 4; + + for(y = 0; y < numTimes ; ++y) + for(x = 0; x < numTimes ; ++x) + mTexCoord[y * numTimes + x].set(x * 0.25f, y * 0.25f); +} + +F32 Precipitation::getRandomVal() +{ + F32 randVal = Platform::getRandom(); + if(randVal > 0.99995f && randVal < 0.99999f) + randVal = 1.01f; + return randVal; +} diff --git a/game/fx/precipitation.h b/game/fx/precipitation.h new file mode 100644 index 0000000..3319e79 --- /dev/null +++ b/game/fx/precipitation.h @@ -0,0 +1,193 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_precipitation +#define _H_precipitation + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif +#ifndef _AUDIODATABLOCK_H_ +#include "audio/audioDataBlock.h" +#endif + + +#define MAX_NUM_DROPS 2000 +#define MAX_NUM_COLOR 3 +#define NUM_TEXTURES 16 +#define MIN_ZRADIUS 0.01f + +class AudioProfile; + +//-------------------------------------------------------------------------- +class PrecipitationData : public GameBaseData { + typedef GameBaseData Parent; + + public: + AudioProfile* soundProfile; + S32 soundProfileId; + S32 mType; + F32 mMaxSize; + F32 mSizeX; + F32 mSizeY; + StringTableEntry mMaterialListName; + + ///////////////////////// + //JohnA Will remove + // Used to tweak the precipitation + ///////////////////////// + F32 tMoveingBoxPer; + F32 tDivHeightVal; + F32 tSizeBigBox; + F32 tTopBoxSpeed; + F32 tFrontBoxSpeed; + F32 tTopBoxDrawPer; + F32 tBottomDrawHeight; + F32 tSkipIfPer; + F32 tBottomSpeedPer; + F32 tFrontSpeedPer; + F32 tFrontRadiusPer; + ///////////////////////// + + PrecipitationData(); + DECLARE_CONOBJECT(PrecipitationData); + bool onAdd(); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +typedef struct +{ + F32 speed; + Point3F curPos; + F32 startTime; + F32 endTime; + Point3F startPos; + Point3F sPosOffset; + F32 size; + Point3F offset; + S32 colorIndex; + S32 texIndex; + ColorF colorOffset; + F32 rotation; + bool stillFalling; + F32 startDiff; + S32 dropType; + bool notValid; +}FallingInfo; + +//-------------------------------------------------------------------------- +enum State +{ + done = 0, + in = 1, + out = 2 +}; + +typedef struct +{ + F32 lastTime; + F32 endPercentage; + F32 time; + F32 speed; + S32 numDrops; + State state; + F32 currentTime; +}PrecipStormData; + +class Precipitation : public GameBase +{ + private: + typedef GameBase Parent; + PrecipitationData* mDataBlock; + + FallingInfo mFallingObj[MAX_NUM_DROPS]; + Point2F mTexCoord[NUM_TEXTURES]; + F32 mCurrentTime; + F32 mLastRenderTime; + Point3F mRotX, mRotZ; + Point2F mOffsetVal; + Point3F mNewCenter; + Point3F mLastPos; + bool mRandomHeight; + TextureHandle mTextures[20]; + MaterialList mMaterialList; + S32 mNumTextures; + bool mFirstTime; + AUDIOHANDLE mAudioHandle; + F32 mPercentage; + S32 mNumDrops; + PrecipStormData mStormData; + bool mStormPrecipitationOn; + Box3F mBox; + F32 mAverageSpeed; + F32 mLastHeight; + Point3F mOffset; + Point3F mShiftPrecip; + S32 mColorCount; + ColorF mColor[MAX_NUM_COLOR]; + F32 mMinVelocity; + F32 mMaxVelocity; + S32 mMaxDrops; + S32 mRadius; + F32 mOffsetSpeed; + + void processTick(const Move*); + protected: + bool onAdd(); + void onRemove(); + + void advanceTime(F32 dt); + + // Rendering + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + void renderPrecip(Point3F camPos); + void renderSand(); + void calcVelocityBox(Point3F camPos, F32 &curZVelocity, F32 &renderPer); + void loadDml(); + void setupTexCoords(); + F32 getRandomVal(); + public: + enum NetMaskBits { + InitMask = BIT(0), + PercentageMask = BIT(1), + StormMask = BIT(2), + StormShowMask = BIT(3) + }; + + Precipitation(); + ~Precipitation(); + void inspectPostApply(); + void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0); + void setPercentage(F32); + + bool onNewDataBlock(GameBaseData* dptr); + DECLARE_CONOBJECT(Precipitation); + static void initPersistFields(); + static void consoleInit(); + + static bool smPrecipitationOn; + static bool smPrecipitationPause; + + void setDefaultValues(); + void stormShow(bool show); + void setupStorm(F32 percentage, F32 time); + void startStorm(); + void updateStorm(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_precipitation + diff --git a/game/fx/splash.cc b/game/fx/splash.cc new file mode 100644 index 0000000..5bf5fa2 --- /dev/null +++ b/game/fx/splash.cc @@ -0,0 +1,834 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/fx/splash.h" + +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "platform/platformAudio.h" +#include "audio/audioDataBlock.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "core/bitStream.h" +#include "math/mathIO.h" +#include "terrain/terrData.h" +#include "game/fx/explosion.h" +#include "game/fx/particleEngine.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, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == 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; + + + if( isClientObject() ) + { + 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.min = Point3F( -1, -1, -1 ); + mObjBox.max = Point3F( 1, 1, 1 ); + resetWorldBox(); + + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + + removeFromProcessList(); + gClientProcessList.addObject(this); + + return true; +} + +//-------------------------------------------------------------------------- +// OnRemove +//-------------------------------------------------------------------------- +void Splash::onRemove() +{ + for( U32 i=0; ideleteWhenEmpty(); + mEmitterList[i] = NULL; + } + } + + ringList.free(); + + 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; +} + + +//-------------------------------------------------------------------------- +// Prep render image +//-------------------------------------------------------------------------- +bool Splash::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)) { + mFog = 0.0; + + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + image->textureSortKey = U32(mDataBlock); + state->setImageRefPoint(this, image); + state->insertRenderImage(image); + } + + return false; +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void Splash::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + state->setupObjectProjection(this); + + render(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +// 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 ), dt * 1000 ); + } + } + +} + +//---------------------------------------------------------------------------- +// Update wave +//---------------------------------------------------------------------------- +void Splash::updateWave( F32 dt ) +{ + mVelocity += mDataBlock->acceleration * dt; + mRadius += mVelocity * dt; + +} + +//---------------------------------------------------------------------------- +// Render splash +//---------------------------------------------------------------------------- +void Splash::render() +{ + + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_TEXTURE_2D); + + + SplashRing *ring = NULL; + + U32 i=0; + while( bool( ring = ringList.next( ring ) ) ) + { + SplashRing *next = ringList.next( ring ); + if( !next ) break; + + renderSegment( *ring, *next ); + i++; + } + + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + +} + +//---------------------------------------------------------------------------- +// Render horizontal segment created from 2 sets of points that are the +// top and bottom rings of the segment. +//---------------------------------------------------------------------------- +void Splash::renderSegment( SplashRing &top, SplashRing &bottom ) +{ + F32 texFactor = mDataBlock->texWrap; + + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[0].getGLName() ); + + + F32 topAlpha = top.elapsedTime / top.lifetime; + F32 bottomAlpha = bottom.elapsedTime / bottom.lifetime; + + if( topAlpha < 0.5 ) topAlpha *= 2.0; + else topAlpha = 1.0 - topAlpha; + + if( bottomAlpha < 0.5 ) bottomAlpha *= 2.0; + else bottomAlpha = 1.0 - bottomAlpha; + + top.color.alpha = topAlpha; + bottom.color.alpha = bottomAlpha; + + + glBegin( GL_QUAD_STRIP ); + { + for( U32 i=0; ielapsedTime) / 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 = 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.link( ring ); + } +} + +//---------------------------------------------------------------------------- +// Update rings +//---------------------------------------------------------------------------- +void Splash::updateRings( F32 dt ) +{ + + SplashRing *ring = NULL; + while( bool( ring = ringList.next( ring ) ) ) + { + ring->elapsedTime += dt; + + if( !ring->isActive() ) + { + SplashRing *inactiveRing = ring; + ring = ringList.prev( ring ); + ringList.free( inactiveRing ); + } + else + { + updateRing( ring, dt ); + } + } + +} + +//---------------------------------------------------------------------------- +// Update ring +//---------------------------------------------------------------------------- +void Splash::updateRing( SplashRing *ring, F32 dt ) +{ + + for( U32 i=0; ipoints.size(); i++ ) + { + if( mDead ) + { + Point3F vel = ring->points[i].velocity; + vel.normalize(); + vel *= mDataBlock->acceleration; + ring->points[i].velocity += vel * dt; + } + + ring->points[i].velocity += Point3F( 0.0, 0.0, -9.8 ) * 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/game/fx/splash.h b/game/fx/splash.h new file mode 100644 index 0000000..49d9ec6 --- /dev/null +++ b/game/fx/splash.h @@ -0,0 +1,185 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_SPLASH +#define _H_SPLASH + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _LLIST_H_ +#include "core/llist.h" +#endif + +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]; + TextureHandle textureHandle[NUM_TEX]; + + ExplosionData* explosion; + S32 explosionId; + + SplashData(); + DECLARE_CONOBJECT(SplashData); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//-------------------------------------------------------------------------- +// Splash +//-------------------------------------------------------------------------- +class Splash : public GameBase +{ + typedef GameBase Parent; + + private: + SplashData* mDataBlock; + + ParticleEmitter * mEmitterList[ SplashData::NUM_EMITTERS ]; + + LList 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*); + 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 render(); + void renderSegment( SplashRing &top, SplashRing &bottom ); + void spawnExplosion(); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + public: + Splash(); + ~Splash(); + void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); + + bool onNewDataBlock(GameBaseData* dptr); + DECLARE_CONOBJECT(Splash); + static void initPersistFields(); +}; + + +#endif // _H_SPLASH diff --git a/game/fx/underLava.cc b/game/fx/underLava.cc new file mode 100644 index 0000000..e58d4db --- /dev/null +++ b/game/fx/underLava.cc @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/fx/underLava.h" + +#include "dgl/dgl.h" +#include "math/mRect.h" +#include "dgl/gTexManager.h" +#include "terrain/waterBlock.h" +#include "math/mConstants.h" + +UnderLavaFX gLavaFX; + + +//************************************************************************** +// Under lava FX - "Lava - With pumice!" +//************************************************************************** +UnderLavaFX::UnderLavaFX() +{ + +} + +//-------------------------------------------------------------------------- +// Init +//-------------------------------------------------------------------------- +void UnderLavaFX::init() +{ + RectI viewport; + dglGetViewport( &viewport ); + + mViewSize = viewport.extent; + + mTexFrequency.x = F32(viewport.extent.x / viewport.extent.y); + mTexFrequency.y = 1.0; + + mNumPoints.x = 50 * F32(viewport.extent.x / viewport.extent.y); + mNumPoints.y = 50; + + mWave[0].amplitude = 0.02; + mWave[0].frequency = 2.0; + mWave[0].velocity = Sim::getCurrentTime() / 1000.0 * 2.0; + + mMoveSpeed = Sim::getCurrentTime() / 1000.0 * 0.025; +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void UnderLavaFX::render() +{ + init(); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glEnable(GL_TEXTURE_2D); +// TextureHandle lavaTex = WaterBlock::getSubmergeTexture(0); + glBindTexture(GL_TEXTURE_2D, WaterBlock::getSubmergeTexture(0).getGLName()); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_FALSE); + glColor4f(1.0, 1.0, 1.0, 0.5); + + if( WaterBlock::getSubmergeTexture(0) ) + { + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // render layer 1 + for( U32 i=0; igetExtent(); + U8 * pixels = new U8[extent.x * extent.y * 3]; + glReadPixels(0, 0, extent.x, extent.y, GL_RGB, GL_UNSIGNED_BYTE, pixels); + + GBitmap * bitmap = new GBitmap; + bitmap->allocateBitmap(U32(extent.x), U32(extent.y)); + + // flip the rows + for(U32 y = 0; y < extent.y; y++) + dMemcpy(bitmap->getAddress(0, extent.y - y - 1), pixels + y * extent.x * 3, U32(extent.x * 3)); + + bitmap->writePNG(fStream); + + fStream.close(); + delete [] pixels; + delete bitmap; +} + + +//-------------------------------------------------------------------------- +ConsoleFunction( gotoWebPage, void, 2, 2, "gotoWebPage( address )" ) +{ + argc; + Platform::openWebBrowser( argv[1] ); +} + +//-------------------------------------------------------------------------- +ConsoleFunction( deactivateDirectInput, void, 1, 1, "deactivateDirectInput()" ) +{ + argc; argv; + if ( Input::isActive() ) + Input::deactivate(); +} + +//-------------------------------------------------------------------------- +ConsoleFunction( activateDirectInput, void, 1, 1, "activateDirectInput()" ) +{ + argc; argv; + if ( !Input::isActive() ) + Input::activate(); +} + + +//-------------------------------------------------------------------------- +void cPurgeResources(SimObject *, S32, const char **) +{ + ResourceManager->purge(); +} + + +//-------------------------------------------------------------------------- +static bool cLightScene(SimObject*, S32 argc, const char ** argv) +{ + const char * callback = StringTable->insert(argv[1]); + BitSet32 flags = 0; + + // might have more flags some day... + for(S32 i = 2; i < argc; i++) + { + if(!dStricmp(argv[2], "forceAlways")) + flags.set(SceneLighting::ForceAlways); + else if(!dStricmp(argv[2], "forceWritable")) + flags.set(SceneLighting::ForceWritable); + } + + return(SceneLighting::lightScene(callback, flags)); +} + + +//-------------------------------------------------------------------------- +static const U32 MaxPlayerNameLength = 16; +ConsoleFunction( strToPlayerName, const char*, 2, 2, "strToPlayerName( string )" ) +{ + 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( ret ); + } + + return( "" ); +} + +//-------------------------------------------------------------------------- +ConsoleFunction( stripTrailingSpaces, const char*, 2, 2, "stripTrailingSpaces( string )" ) +{ + 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( "" ); +} + + +//-------------------------------------------------------------------------- +void cFlushTextureCache(SimObject *, S32, const char **) +{ + TextureManager::flush(); +} + + +#ifdef GATHER_METRICS +void cDumpTextureStats(SimObject*, S32, const char**) +{ + TextureManager::dumpStats(); +} +#endif + +#ifdef DEBUG +void cDumpResourceStats(SimObject*, S32, const char**) +{ + ResourceManager->dumpLoadedResources(); +} +#endif + +} // namespace {} + +//------------------------------------------------------------------------------ + +static U32 moveCount = 0; + +bool GameGetCameraTransform(MatrixF *mat, Point3F *velocity) +{ + // Return the position and velocity of the control object + GameConnection* connection = GameConnection::getServerConnection(); + return connection && connection->getControlCameraTransform(0, mat) && + connection->getControlCameraVelocity(velocity); +} + +//------------------------------------------------------------------------------ +// 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 = 0; // ms per 90deg fov change + +} // namespace {} + +//------------------------------------------------------------------------------ +ConsoleFunction(setDefaultFov, void, 2,2, "setDefaultFov(defaultFov);") +{ + argc; + sDefaultFov = mClampF(dAtof(argv[1]), MinCameraFov, MaxCameraFov); + if(sCameraFov == sTargetFov) + sTargetFov = sDefaultFov; +} + +ConsoleFunction(setZoomSpeed, void, 2,2, "setZoomSpeed(speed);") +{ + argc; + sZoomSpeed = mClamp(dAtoi(argv[1]), 0, MaxZoomSpeed); +} + +ConsoleFunction(setFov, void, 2, 2, "setFov(fov);") +{ + argc; + sTargetFov = mClampF(dAtof(argv[1]), MinCameraFov, MaxCameraFov); +} + +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::getServerConnection(); + 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 DEBUG +// ConsoleFunction(dumpTSShapes, void, 1, 1, "dumpTSShapes();") +// { +// argc, argv; + +// FindMatch match("*.dts", 4096); +// ResourceManager->findMatches(&match); + +// for (U32 i = 0; i < match.numMatches(); i++) +// { +// U32 j; +// Resource shape = ResourceManager->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 + +static const char* cGetControlObjectAltitude(SimObject *, S32, const char**) +{ + GameConnection* connection = GameConnection::getServerConnection(); + if (connection) { + ShapeBase* pSB = connection->getControlObject(); + if (pSB != NULL && pSB->isClientObject()) + { + Point3F pos(0.f, 0.f, 0.f); + + // if this object is mounted, then get the bottom position of the mount's bbox + if(pSB->getObjectMount()) + { + 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) + }; + + ShapeBase * mount = pSB->getObjectMount(); + Box3F box = mount->getObjBox(); + MatrixF mat = mount->getTransform(); + VectorF scale = mount->getScale(); + + Point3F projPnts[8]; + F32 minZ = 1e30; + + for(U32 i = 0; i < 8; i++) + { + Point3F pnt(BoxPnts[i].x ? box.max.x : box.min.x, + BoxPnts[i].y ? box.max.y : box.min.y, + BoxPnts[i].z ? box.max.z : box.min.z); + + pnt.convolve(scale); + mat.mulP(pnt, &projPnts[i]); + + if(projPnts[i].z < minZ) + pos = projPnts[i]; + } + } + else + pSB->getTransform().getColumn(3, &pos); + + TerrainBlock* pBlock = gClientSceneGraph->getCurrentTerrain(); + if (pBlock != NULL) { + Point3F terrPos = pos; + pBlock->getWorldTransform().mulP(terrPos); + terrPos.convolveInverse(pBlock->getScale()); + + F32 height; + if (pBlock->getHeight(Point2F(terrPos.x, terrPos.y), &height) == true) { + terrPos.z = height; + terrPos.convolve(pBlock->getScale()); + pBlock->getTransform().mulP(terrPos); + + pos.z -= terrPos.z; + } + } + + char* retBuf = Con::getReturnBuffer(128); + dSprintf(retBuf, 128, "%g", mFloor(getMax(pos.z, 0.f))); + return retBuf; + } + } + + return "0"; +} + +static const char* cGetControlObjectSpeed(SimObject *, S32, const char**) +{ + GameConnection* connection = GameConnection::getServerConnection(); + if (connection) + { + ShapeBase* pSB = connection->getControlObject(); + if (pSB != NULL && pSB->isClientObject()) { + Point3F vel = pSB->getVelocity(); + F32 speed = vel.len(); + + // We're going to force the formating to be what we want... + F32 intPart = mFloor(speed); + speed -= intPart; + speed *= 10; + speed = mFloor(speed); + + char* retBuf = Con::getReturnBuffer(128); + dSprintf(retBuf, 128, "%g.%g", intPart, speed); + return retBuf; + } + } + + return "0"; +} + + +//------------------------------------------------------------------------------ +void GameInit() +{ + // Make sure the exporter draws from the correct directories... + // + TSMaterialList::csmTSTexturePrefix = ""; + TSMaterialList::csmOldTSTexturePrefix = "skins/"; + + Con::addVariable("movementSpeed", TypeF32, &gMovementSpeed); + Con::addCommand("screenShot", cScreenShot, "screenShot(file);", 2, 2); + Con::addCommand("panoramaScreenShot", cPanoramaScreenShot, "panoramaScreenShot(file);", 2, 2); + + Con::addCommand("purgeResources", cPurgeResources, "purgeResources();", 1, 1); + Con::addCommand("lightScene", cLightScene, "lightScene(, ;", 1, 7); + + Con::addVariable("$pref::OpenGL::disableEXTPalettedTexture", TypeBool, &gOpenGLDisablePT); + Con::addVariable("$pref::OpenGL::disableEXTCompiledVertexArray", TypeBool, &gOpenGLDisableCVA); + Con::addVariable("$pref::OpenGL::disableARBMultitexture", TypeBool, &gOpenGLDisableARBMT); + Con::addVariable("$pref::OpenGL::disableEXTFogCoord", TypeBool, &gOpenGLDisableFC); + Con::addVariable("$pref::OpenGL::disableEXTTexEnvCombine", TypeBool, &gOpenGLDisableTEC); + Con::addVariable("$pref::OpenGL::disableARBTextureCompression", TypeBool, &gOpenGLDisableTCompress); + Con::addVariable("$pref::OpenGL::noEnvColor", TypeBool, &gOpenGLNoEnvColor); + Con::addVariable("$pref::OpenGL::gammaCorrection", TypeF32, &gOpenGLGammaCorrection); + Con::addVariable("$pref::OpenGL::noDrawArraysAlpha", TypeBool, &gOpenGLNoDrawArraysAlpha); + + Con::addVariable("$pref::TS::autoDetail", TypeF32, &DetailManager::smDetailScale); + Con::addVariable("$pref::visibleDistanceMod", TypeF32, &SceneGraph::smVisibleDistanceMod); + + Con::addCommand("flushTextureCache", cFlushTextureCache, "flushTextureCache()", 1, 1); +#ifdef GATHER_METRICS + Con::addCommand("dumpTextureStats", cDumpTextureStats, "dumpTextureStats()", 1, 1); +#endif + +#ifdef DEBUG + Con::addCommand("dumpResourceStats", cDumpResourceStats, "dumpResourceStats()", 1, 1); +#endif + + // updated every frame + Con::addVariable("cameraFov", TypeF32, &sConsoleCameraFov); + + Con::addCommand("getControlObjectAltitude", cGetControlObjectAltitude, "getControlObjectAltitude();", 1, 1); + Con::addCommand("getControlObjectSpeed", cGetControlObjectSpeed, "getControlObjectSpeed();", 1, 1); + + collisionTest.consoleInit(); + gAmbientAudioManager.consoleInit(); +} + +const U32 AudioUpdatePeriod = 125; // milliseconds for audio update + +void clientProcess(U32 timeDelta) +{ + ShowTSShape::advanceTime(timeDelta); + + gClientProcessList.advanceClientTime(timeDelta); + + // Run the collision test and update the Audio system + // by checking the controlObject + MatrixF mat; + Point3F velocity; + + if (GameGetCameraTransform(&mat, &velocity)) + { + alxListenerMatrixF(&mat); +// alxListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z); + collisionTest.collide(mat); + } + + // all seeker audio is managed here + GameConnection* connection = GameConnection::getServerConnection(); + if(connection) + { + // if a seeker is being used, update all tones + //ShapeBase *obj = connection->getControlObject(); + //if(obj) + //{ + //if(obj->getMountedImage(0) != NULL && obj->getMountedImage(0)->isSeeker) + connection->updateLockTones(); + //} + + // update all possible lock or homes on us + connection->updateLockWarnings(); + } + + // determine if were lagging + if(connection) + connection->detectLag(); + + // alxUpdate is somewhat expensive and does not need to be updated constantly, + // though it does need to be updated in real time + static U32 lastAudioUpdate = 0; + U32 realTime = Platform::getRealMilliseconds(); + if((realTime - lastAudioUpdate) >= AudioUpdatePeriod) + { + alxUpdate(); + gAmbientAudioManager.update(); + lastAudioUpdate = realTime; + } +} + +void serverProcess(U32 timeDelta) +{ + gServerProcessList.advanceServerTime(timeDelta); +} + +static ColorF cubeColors[8] = { + ColorF(0, 0, 0), + ColorF(1, 0, 0), + ColorF(0, 1, 0), + ColorF(0, 0, 1), + ColorF(1, 1, 0), + ColorF(1, 0, 1), + ColorF(0, 1, 1), + ColorF(1, 1, 1) +}; + +static 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 U32 cubeFaces[6][4] = { + { 0, 2, 6, 4 }, + { 0, 2, 3, 1 }, + { 0, 1, 5, 4 }, + { 3, 2, 6, 7 }, + { 7, 6, 4, 5 }, + { 3, 7, 5, 1 } +}; + +void wireCube(F32 size, Point3F pos) +{ + glDisable(GL_CULL_FACE); + + for (S32 i = 0; i < 6; i++) + { + glBegin(GL_LINE_LOOP); + for(S32 vert = 0; vert < 4; vert++) + { + U32 idx = cubeFaces[i][vert]; + glColor3f(cubeColors[idx].red, cubeColors[idx].green, cubeColors[idx].blue); + glVertex3f(cubePoints[idx].x * size + pos.x, cubePoints[idx].y * size + pos.y, cubePoints[idx].z * size + pos.z); + } + glEnd(); + } +} + +bool GameProcessCameraQuery(CameraQuery *query) +{ + GameConnection* connection = GameConnection::getServerConnection(); + if (connection && connection->getControlCameraTransform(0.032f, &query->cameraMatrix)) + { + query->object = connection->getControlObject(); + + query->nearPlane = 0.1f; + + Sky* pSky = gClientSceneGraph->getCurrentSky(); + if (pSky) { + query->farPlane = pSky->getVisibleDistance(); + } else { + query->farPlane = 1000.0f; + } + + F32 cameraFov; + if(!connection->getControlCameraFov(&cameraFov)) + return(false); + query->fov = mDegToRad(cameraFov); + return true; + } + return false; +} + + +struct OutputPoint +{ + Point3F point; + U8 color[4]; + Point2F texCoord; + Point2F fogCoord; +}; + +//---------------------------------------------------------------------------- +void GameRenderTest() +{ + glClearColor(1, 1, 1, 1); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 640, 480); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glFrustum(-1.3, 1.3, -1.0, 1.0, 1.0, 10.0); + + GLubyte lum[4] = {0xFF, 0x80, 0x80, 0x00}; + GLubyte color[3] = { 0xFF, 0x00, 0xFF }; + + GLuint names[2]; + glGenTextures(2, names); + glBindTexture(GL_TEXTURE_2D, names[0]); + glTexImage2D(GL_TEXTURE_2D, + 0, GL_RGB, + 1, 1, 0, + GL_RGB, GL_UNSIGNED_BYTE, + color); + glBindTexture(GL_TEXTURE_2D, names[1]); + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_LUMINANCE, + 2, 2, + 0, + GL_LUMINANCE, + GL_UNSIGNED_BYTE, + lum); + + OutputPoint outputPoints[4]; + + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(OutputPoint), &outputPoints[0].point); + + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, names[1]); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &outputPoints[0].fogCoord); + + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, names[0]); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &outputPoints[0].texCoord); + + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(OutputPoint), &outputPoints[0].color[0]); + + ColorF fogColor(0.5, 0.5, 0.5, 1); + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, fogColor); + + outputPoints[0].point.x = 0; + outputPoints[0].point.y = 0; + outputPoints[0].point.z = -2; + outputPoints[0].color[0] = 0xFF; + outputPoints[0].color[1] = 0xFF; + outputPoints[0].color[2] = 0xFF; + outputPoints[0].color[3] = 0xFF; + outputPoints[0].texCoord.x = 0; + outputPoints[0].texCoord.y = 0; + outputPoints[0].fogCoord.x = 0; + outputPoints[0].fogCoord.y = 0; + + outputPoints[1].point.x = 0; + outputPoints[1].point.y = 1; + outputPoints[1].point.z = -2; + outputPoints[1].color[0] = 0xFF; + outputPoints[1].color[1] = 0xFF; + outputPoints[1].color[2] = 0xFF; + outputPoints[1].color[3] = 0xFF; + outputPoints[1].texCoord.x = 0; + outputPoints[1].texCoord.y = 0; + outputPoints[1].fogCoord.x = 0; + outputPoints[1].fogCoord.y = 1; + + outputPoints[2].point.x = 1; + outputPoints[2].point.y = 1; + outputPoints[2].point.z = -2; + outputPoints[2].color[0] = 0xFF; + outputPoints[2].color[1] = 0xFF; + outputPoints[2].color[2] = 0xFF; + outputPoints[2].color[3] = 0xFF; + outputPoints[2].texCoord.x = 0; + outputPoints[2].texCoord.y = 0; + outputPoints[2].fogCoord.x = 1; + outputPoints[2].fogCoord.y = 1; + + outputPoints[3].point.x = 1; + outputPoints[3].point.y = 0; + outputPoints[3].point.z = -2; + outputPoints[3].color[0] = 0xFF; + outputPoints[3].color[1] = 0xFF; + outputPoints[3].color[2] = 0xFF; + outputPoints[3].color[3] = 0xFF; + outputPoints[3].texCoord.x = 0; + outputPoints[3].texCoord.y = 0; + outputPoints[3].fogCoord.x = 1; + outputPoints[3].fogCoord.y = 0; + + glLockArraysEXT(0, 4); + GLuint windings[6] = { 0, 1, 2, 0, 2, 3 }; + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, windings); + glUnlockArraysEXT(); + + glDisableClientState(GL_VERTEX_ARRAY); + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glDeleteTextures(2, names); +} + +#define USEOLDFILTERS 1 + +void GameRenderFilters(const CameraQuery& camq) +{ +#if USEOLDFILTERS + + GameConnection* connection = GameConnection::getServerConnection(); + + F32 damageFlash = 0; + F32 whiteOut = 0; + F32 blackOut = 0; + + if(connection) + { + damageFlash = connection->getDamageFlash(); + whiteOut = connection->getWhiteOut(); + blackOut = connection->getBlackOut(); + } + + ShapeBase* psb = dynamic_cast(camq.object); + if (psb != NULL) { + if (damageFlash > 0.0) { + if (damageFlash > 0.76) + damageFlash = 0.76f; + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(1, 0, 0, damageFlash); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(-1, -1, 0); + glVertex3f(-1, 1, 0); + glVertex3f( 1, 1, 0); + glVertex3f( 1, -1, 0); + glEnd(); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + + if (whiteOut > 0.0) { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(1.0f, 1.0f, 0.92f, (whiteOut > 1.0f ? 1.0f : whiteOut)); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(-1, -1, 0); + glVertex3f(-1, 1, 0); + glVertex3f( 1, 1, 0); + glVertex3f( 1, -1, 0); + glEnd(); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + + if (blackOut > 0.0) { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(0.0f, 0.0f, 0.0f, (blackOut > 1.0f ? 1.0f : blackOut)); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(-1, -1, 0); + glVertex3f(-1, 1, 0); + glVertex3f( 1, 1, 0); + glVertex3f( 1, -1, 0); + glEnd(); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + + F32 invincible = psb->getInvincibleEffect(); + if (invincible > 0.0) { + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(0, 0, 1, (invincible > 1.0f ? 1.0f : invincible)); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(-1, -1, 0); + glVertex3f(-1, 1, 0); + glVertex3f( 1, 1, 0); + glVertex3f( 1, -1, 0); + glEnd(); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + + if (WaterBlock::mCameraSubmerged) + { + if (WaterBlock::isWater(WaterBlock::mSubmergedType)) + { + // view filter for camera below the water surface + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glDisable(GL_TEXTURE_2D); + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(.2, .6, .6, .3); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(-1, -1, 0); + glVertex3f(-1, 1, 0); + glVertex3f( 1, 1, 0); + glVertex3f( 1, -1, 0); + glEnd(); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + else if (WaterBlock::isLava(WaterBlock::mSubmergedType)) + { + gLavaFX.render(); + } + else if (WaterBlock::isQuicksand(WaterBlock::mSubmergedType)) + { + } + } + WaterBlock::mCameraSubmerged = false; + WaterBlock::mSubmergedType = 0; + } +#else + + // + // Need to build a filter for damage, invincibility, underwater, whiteout... ect + // + // Damage, Whiteout, and invincible effects have constant color with variable + // alpha values. The water filter has a constant color and alpha value. This + // looks kinda tricky, and it is. See Frohn for more details Jett- + + // first get the game connection for this player + GameConnection* connection = GameConnection::getServerConnection(); + + bool addWaterFilter = false; + bool addLavaFilter = false; + F32 maxDamageFilter = 0.77; + F32 damageFlash = 0.f; ColorF damageFilterColor(1.f, 0.f, 0.f, 0.f); + F32 whiteOut = 0.f; ColorF whiteoutFilterColor(1.f, 1.f, 1.f, 0.f); + F32 waterFilter = 0.f; ColorF waterFilterColor(0.2, 0.6, 0.6, 0.6); + F32 invincible = 0.f; ColorF invincibleFilterColor(0.f, 0.f, 1.f, 0.f); + + // final color and alpha of filter + an adder + ColorF Xcolor(0.f, 0.f, 0.f, 1.f); + + if(connection) + { + // grab the damage flash alpha value + damageFlash = connection->getDamageFlash(); + if( damageFlash > maxDamageFilter ) + damageFlash = maxDamageFilter; + + damageFilterColor.alpha = damageFlash; + + // grab the whiteout value + whiteoutFilterColor.alpha = connection->getWhiteOut(); + + // need to grab the player obj to get inv. alpha value + ShapeBase* psb = dynamic_cast(camq.object); + if(psb != NULL) + invincibleFilterColor.alpha = psb->getInvincibleEffect(); + + // determine if we need to add in our water filter (constant color and alpha) + if( WaterBlock::mCameraSubmerged ) + { + if( WaterBlock::isWater( WaterBlock::mSubmergedType ) ) + addWaterFilter = true; + else if( WaterBlock::isLava( WaterBlock::mSubmergedType)) + addLavaFilter = true; + } + + // compute the final color and alpha + Xcolor = ( Xcolor * ( 1 - damageFilterColor.alpha ) ) + ( damageFilterColor * damageFilterColor.alpha ); + Xcolor.alpha = Xcolor.alpha * ( 1 - damageFilterColor.alpha ); + + Xcolor = ( Xcolor * ( 1 - whiteoutFilterColor.alpha ) ) + ( whiteoutFilterColor * whiteoutFilterColor.alpha ); + Xcolor.alpha = Xcolor.alpha * ( 1 - whiteoutFilterColor.alpha ); + + Xcolor = ( Xcolor * ( 1 - invincibleFilterColor.alpha ) ) + ( invincibleFilterColor * invincibleFilterColor.alpha ); + Xcolor.alpha = Xcolor.alpha * ( 1 - invincibleFilterColor.alpha ); + + // if were sitting in water, then add that filter in as well. + if(addWaterFilter) + { + Xcolor = ( Xcolor * ( 1 - waterFilterColor.alpha ) ) + ( waterFilterColor * waterFilterColor.alpha ); + Xcolor.alpha = Xcolor.alpha * ( 1 - waterFilterColor.alpha ); + } + + // draw our filter with final color + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glColor4f(Xcolor.red, Xcolor.blue, Xcolor.blue, Xcolor.alpha); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(-1, -1, 0); + glVertex3f(-1, 1, 0); + glVertex3f( 1, 1, 0); + glVertex3f( 1, -1, 0); + glEnd(); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + + // if were under lava, apply appropriate texture + if( addLavaFilter ) + { + gLavaFX.render(); + } + + WaterBlock::mCameraSubmerged = false; + WaterBlock::mSubmergedType = 0; + } + +#endif +} + +void GameRenderWorld() +{ + PROFILE_START(GameRenderWorld); + FrameAllocator::setWaterMark(0); + +#if defined(GATHER_METRICS) && GATHER_METRICS > 1 + TextureManager::smTextureCacheMisses = 0; +#endif + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glClear(GL_DEPTH_BUFFER_BIT); + glDisable(GL_CULL_FACE); + glMatrixMode(GL_MODELVIEW); + + dglSetCanonicalState(); + gClientSceneGraph->renderScene(); + glDisable(GL_DEPTH_TEST); + collisionTest.render(); + +#if defined(GATHER_METRICS) && GATHER_METRICS > 1 + Con::setFloatVariable("Video::texResidentPercentage", + TextureManager::getResidentFraction()); + Con::setIntVariable("Video::textureCacheMisses", + TextureManager::smTextureCacheMisses); +#endif + + AssertFatal(FrameAllocator::getWaterMark() == 0, "Error, someone didn't reset the water mark on the frame allocator!"); + FrameAllocator::setWaterMark(0); + PROFILE_END(); +} + +//-------------------------------------------------------------------------- +static void cPanoramaScreenShot(SimObject *, S32 argc, const char ** argv) +{ + S32 numShots = 3; + if (argc == 3) + numShots = dAtoi(argv[2]); + + CameraQuery query; + if (!GameProcessCameraQuery( &query )) + return; + + SceneObject *object = dynamic_cast(query.object); + if (!object) + return; + + F32 rotInc = query.fov * 0.75f; + + FileStream fStream; + GBitmap bitmap; + Point2I extent = Canvas->getExtent(); + bitmap.allocateBitmap(U32(extent.x), U32(extent.y)); + U8 * pixels = new U8[extent.x * extent.y * 3]; + + + S32 start = -(numShots/2); + for (S32 i=0; isetTransform( result ); + Canvas->renderFrame(false); + dSprintf(buffer, sizeof(buffer), "%s-%d.png", argv[1], i); + + glReadBuffer(GL_FRONT); + glReadPixels(0, 0, extent.x, extent.y, GL_RGB, GL_UNSIGNED_BYTE, pixels); + + if(!fStream.open(buffer, FileStream::Write)) + { + Con::printf("Failed to open file '%s'.", buffer); + break; + } + + // flip the rows + for(U32 y = 0; y < extent.y; y++) + dMemcpy(bitmap.getAddress(0, extent.y - y - 1), pixels + y * extent.x * 3, U32(extent.x * 3)); + + bitmap.writePNG(fStream); + fStream.close(); + } + + delete [] pixels; +} + + + + diff --git a/game/game.h b/game/game.h new file mode 100644 index 0000000..71fa6ff --- /dev/null +++ b/game/game.h @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAME_H_ +#define _GAME_H_ + +struct CameraQuery; + +const F32 MinCameraFov = 1.f; // min camera FOV +const F32 MaxCameraFov = 179.f; // max camera FOV + +void GameRenderWorld(); +void GameRenderFilters(const CameraQuery& camq); +bool GameProcessCameraQuery(CameraQuery *query); +bool GameGetCameraTransform(MatrixF *mat, Point3F *velocity); +F32 GameGetCameraFov(); +void GameSetCameraFov(F32 fov); +void GameSetCameraTargetFov(F32 fov); +void GameUpdateCameraFov(); +void GameInit(); + +void clientProcess(U32 timeDelta); +void serverProcess(U32 timeDelta); + +#endif diff --git a/game/gameBase.cc b/game/gameBase.cc new file mode 100644 index 0000000..b665ac5 --- /dev/null +++ b/game/gameBase.cc @@ -0,0 +1,739 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "game/gameBase.h" +#include "console/consoleTypes.h" +#include "console/consoleInternal.h" +#include "core/bitStream.h" +#include "sim/netConnection.h" +#include "game/gameConnection.h" +#include "game/targetManager.h" +#include "game/sensor.h" + +//---------------------------------------------------------------------------- +// Ghost update relative priority values + +static F32 sUpFov = 1.0; +static F32 sUpDistance = 0.4; +static F32 sUpVelocity = 0.4; +static F32 sUpSkips = 0.2; +static F32 sUpOwnership = 0.2; +static F32 sUpInterest = 0.2; + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(GameBaseData); + +GameBaseData::GameBaseData() +{ + catagory = ""; + className = ""; + packed = false; + beacon = false; + beaconType = GameBase::enemyBeacon; +} + +bool GameBaseData::onAdd() +{ + if (!Parent::onAdd()) + return false; + + // Link our object name to the datablock class name and + // then onto our C++ class name. + const char* name = getName(); + if (name && name[0] && getClassRep()) { + Namespace *parent = getClassRep()->getNameSpace(); + if (className && className[0] && dStricmp(className, parent->mName)) { + Con::linkNamespaces(parent->mName,className); + Con::linkNamespaces(className,name); + } + else + Con::linkNamespaces(parent->mName,name); + mNameSpace = Con::lookupNamespace(name); + } + + // If no className was specified, set it to our C++ class name + if (!className || !className[0]) + className = getClassRep()->getClassName(); + + return true; +} + +static EnumTable::Enums beaconEnums[] = +{ + { GameBase::enemyBeacon, "enemy" }, + { GameBase::friendBeacon, "friend" }, + { GameBase::vehicleBeacon, "vehicle" } +}; +static EnumTable gBeaconTypeTable(3, &beaconEnums[0]); + +void GameBaseData::initPersistFields() +{ + Parent::initPersistFields(); + addField("catagory", TypeCaseString, Offset(catagory, GameBaseData)); + addField("className", TypeString, Offset(className, GameBaseData)); + addField("beacon", TypeBool, Offset(beacon, GameBaseData)); + addField("beaconType", TypeEnum, Offset(beaconType, GameBaseData), 1 , &gBeaconTypeTable); +} + +bool GameBaseData::preload(bool server, char errorBuffer[256]) +{ + if (!Parent::preload(server, errorBuffer)) + return false; + packed = false; + return true; +} + +void GameBaseData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + packed = true; +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +bool GameBase::gShowBoundingBox; +StringTableEntry GameBase::smEnemyBeaconText; +StringTableEntry GameBase::smFriendBeaconText; +StringTableEntry GameBase::smVehicleBeaconText; + +//---------------------------------------------------------------------------- +IMPLEMENT_CO_NETOBJECT_V1(GameBase); + +GameBase::GameBase() +{ + mNetFlags.set(Ghostable); + mTypeMask |= GameBaseObjectType; + + mProcessLink.next = mProcessLink.prev = this; + mAfterObject = 0; + mProcessTag = 0; + mLastDelta = 0; + mDataBlock = 0; + mProcessTick = true; + mNameTag = ""; + mBeacon = false; + mBeaconType = enemyBeacon; + + mTargetInfo = NULL; + mTargetId = -1; + + mLockCount = 0; + mHomingCount = 0; +} + +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 (!onNewDataBlock(mDataBlock)) + return false; + gServerProcessList.addObject(this); + + setBeacon(mDataBlock->beacon); + setBeaconType(mDataBlock->beaconType); + } + return true; +} + +void GameBase::onRemove() +{ + plUnlink(); + Parent::onRemove(); +} + +void GameBase::setBeacon(bool beacon) +{ + if(beacon ^ mBeacon) + { + SimSet * set = isServerObject() ? Sim::getServerTargetSet() : Sim::getClientTargetSet(); + beacon ? set->addObject(this) : set->removeObject(this); + mBeacon = beacon; + + if(isServerObject()) + setMaskBits(ExtendedInfoMask); + } +} + +void GameBase::setBeaconType(S32 beaconType) +{ + mBeaconType = beaconType; + if(isServerObject()) + setMaskBits(ExtendedInfoMask); +} + +bool GameBase::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dptr; + + if (!mDataBlock) + return false; + + if(isProperlyAdded() && isServerObject()) + { + setBeacon(mDataBlock->beacon); + setBeaconType(mDataBlock->beaconType); + } + setMaskBits(DataBlockMask); + return true; +} + +void GameBase::inspectPostApply() +{ + setMaskBits(ExtendedInfoMask); +} + +//---------------------------------------------------------------------------- +void GameBase::processTick(const Move*) +{ + mLastDelta = 0; +} + +void GameBase::interpolateTick(F32 delta) +{ + mLastDelta = delta; +} + +void GameBase::advanceTime(F32) +{ + // +} + + +//---------------------------------------------------------------------------- + +F32 GameBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips) +{ + 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; +} + + +Point3F GameBase::getVelocity() const +{ + return Point3F(0, 0, 0); +} + + +//---------------------------------------------------------------------------- +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 (!isGhost()) + Con::executef(mDataBlock,2,"onAdd",scriptThis()); +} + +void GameBase::scriptOnNewDataBlock() +{ + // Script onNewDataBlock() must be called by the leaf class + // after everything is loaded. + if (!isGhost()) + Con::executef(mDataBlock,2,"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,2,"onRemove",scriptThis()); +} + + +//-------------------------------------------------------------------------- +bool GameBase::getTarget(Point3F* p, U32* tid) +{ + if (mBeacon && getTargetInfo()) { + getTransform().getColumn(3, p); + *tid = getTargetInfo()->sensorGroup; + return true; + } else { + return false; + } +} + +TargetInfo *GameBase::getTargetInfo() +{ + return mTargetInfo; +} + +void GameBase::setTarget(S32 targetId) +{ + if(mTargetId == targetId) + return; + + mTargetId = targetId; + if(mTargetId == -1) + mTargetInfo = 0; + else + mTargetInfo = isServerObject() ? gTargetManager->getServerTarget(mTargetId) : + gTargetManager->getClientTarget(mTargetId); + + // set the targets object as this (only non-team targets can have objects) + if(mTargetId >= 32) + gTargetManager->setTargetObject(mTargetInfo, this); + + targetInfoChanged(mTargetInfo); + + if(isServerObject()) + setMaskBits(ExtendedInfoMask); +} + +void GameBase::setBeaconGameNames(const char * enemyName, const char * friendName, const char * vehicleName) +{ + smEnemyBeaconText = StringTable->insert(enemyName); + smFriendBeaconText = StringTable->insert(friendName); + smVehicleBeaconText = StringTable->insert(vehicleName); +} + +bool GameBase::getGameName(char * buf, S32 bufSize) +{ + if(gTargetManager->getGameName(getTarget(), buf, bufSize, isServerObject())) + return(true); + + // special cases: + // beacon: + if(mBeacon) + { + const char * text = 0; + switch(mBeaconType) + { + case enemyBeacon: + text = smEnemyBeaconText; + break; + case friendBeacon: + text = smFriendBeaconText; + break; + case vehicleBeacon: + text = smVehicleBeaconText; + break; + default: + AssertFatal(false, "Unknown beacon type"); + return(false); + } + dSprintf(buf, bufSize, "%s", text); + return(true); + } + + return(false); +} + +void GameBase::setTransform(const MatrixF & mat) +{ + if(isClientObject() && mTargetId >= 0) + { + AUDIOHANDLE & handle = gTargetManager->mClientAudioHandles[mTargetId]; + if(handle != NULL_AUDIOHANDLE) + { + if(alxIsPlaying(handle)) + alxSourceMatrixF(handle, &mat); + else + handle = NULL_AUDIOHANDLE; + } + } + Parent::setTransform(mat); +} + +//---------------------------------------------------------------------------- +void GameBase::plUnlink() +{ + mProcessLink.next->mProcessLink.prev = mProcessLink.prev; + mProcessLink.prev->mProcessLink.next = mProcessLink.next; + mProcessLink.next = mProcessLink.prev = this; +} + +void GameBase::plLinkAfter(GameBase* obj) +{ + // Link this after obj + mProcessLink.next = obj->mProcessLink.next; + mProcessLink.prev = obj; + obj->mProcessLink.next = this; + mProcessLink.next->mProcessLink.prev = this; +} + +void GameBase::plLinkBefore(GameBase* obj) +{ + // Link this before obj + mProcessLink.next = obj; + mProcessLink.prev = obj->mProcessLink.prev; + obj->mProcessLink.prev = this; + mProcessLink.prev->mProcessLink.next = this; +} + +void GameBase::plJoin(GameBase* head) +{ + GameBase *tail1 = head->mProcessLink.prev; + GameBase *tail2 = mProcessLink.prev; + tail1->mProcessLink.next = this; + mProcessLink.prev = tail1; + tail2->mProcessLink.next = head; + head->mProcessLink.prev = tail2; +} + + +//---------------------------------------------------------------------------- +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; +} + + +//---------------------------------------------------------------------------- + +bool GameBase::writePacketData(GameConnection*, BitStream*) +{ + return true; +} + +void GameBase::readPacketData(GameConnection*,BitStream*) +{ + // +} + +U32 GameBase::packUpdate(NetConnection *, U32 mask, BitStream *stream) +{ + if (stream->writeFlag((mask & DataBlockMask) && mDataBlock != NULL)) { + stream->writeRangedU32(mDataBlock->getId(), + DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + if(stream->writeFlag(mask & ExtendedInfoMask)) + { + if(stream->writeFlag(mTargetId != -1)) + stream->writeInt(mTargetId, TargetManager::TargetIdBitSize); + + stream->writeFlag(mBeacon); + stream->write(mBeaconType); + } + + return 0; +} + +void GameBase::unpackUpdate(NetConnection *con, BitStream *stream) +{ + if (stream->readFlag()) { + GameBaseData* dptr = 0; + SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (!Sim::findObject(id,dptr) || !setDataBlock(dptr)) + con->setLastError("Invalid packet GameBase::unpackUpdate()"); + } + + // ExtendedInfo + if(stream->readFlag()) + { + // target + S32 targetId = -1; + if(stream->readFlag()) + targetId = stream->readInt(TargetManager::TargetIdBitSize); + + if(targetId != mTargetId) + setTarget(targetId); + + // beacon + setBeacon(stream->readFlag()); + stream->read(&mBeaconType); + } +} + +//---------------------------------------------------------------------------- +U32 GameBase::getSensorGroup() +{ + if(!mTargetInfo) + return 0; + else + return mTargetInfo->sensorGroup; +} + +//---------------------------------------------------------------------------- +static int cGetDataBlock(SimObject *obj, S32, const char **) +{ + GameBase* ptr = static_cast(obj); + return ptr->getDataBlock()? ptr->getDataBlock()->getId(): 0; +} + +//---------------------------------------------------------------------------- +static bool cSetDataBlock(SimObject *obj, S32, const char **argv) +{ + GameBaseData* data; + if (Sim::findObject(argv[2],data)) { + GameBase* ptr = static_cast(obj); + return ptr->setDataBlock(data); + } + Con::printf("Could not find data block \"%s\"",argv[2]); + return false; +} + +//---------------------------------------------------------------------------- + +static void cSetTargetId(SimObject *obj, S32, const char **argv) +{ + GameBase* ptr = static_cast(obj); + if(!ptr->isServerObject()) + return; + + // check for valid targetId + S32 targetId = dAtoi(argv[2]); + if((targetId < -1) || (targetId >= TargetManager::MaxTargets)) + { + Con::errorf(ConsoleLogEntry::General, "GameBase::cSetTargetId: invalid target id [%s]", argv[2]); + return; + } + ptr->setTarget(targetId); +} + +static S32 cGetTargetId(SimObject *obj, S32, const char **) +{ + GameBase* ptr = static_cast(obj); + return ptr->getTarget(); +} + +static bool cIsBeacon(SimObject * obj, S32, const char **) +{ + GameBase * gameObj = static_cast(obj); + return(gameObj->isBeacon()); +} + +static void cSetBeacon(SimObject * obj, S32, const char ** argv) +{ + GameBase * gameObj = static_cast(obj); + if(gameObj->isServerObject()) + gameObj->setBeacon(dAtoi(argv[2])); +} + +static void cSetBeaconType(SimObject * obj, S32, const char ** argv) +{ + GameBase * gameObj = static_cast(obj); + if(gameObj->isServerObject()) + for(S32 x = 0; x < 3; ++x) + if(!dStrcmp(argv[2], beaconEnums[x].label)) + { + gameObj->setBeaconType(x); + return; + } +} + +static bool cIsBeaconType(SimObject * obj, S32, const char ** argv) +{ + GameBase * gameObj = static_cast(obj); + if(gameObj->isServerObject()) + for(S32 x = 0; x < 3; ++x) + if(!dStrcmp(argv[2], beaconEnums[x].label)) + { + if(gameObj->getBeaconType() == x) + return true; + break; + } + return false; +} + +static void cSetBeaconNames(SimObject *, S32, const char ** argv) +{ + GameBase::setBeaconGameNames(argv[1], argv[2], argv[3]); +} + +//---------------------------------------------------------------------------- + +IMPLEMENT_GETDATATYPE(GameBaseData) +IMPLEMENT_SETDATATYPE(GameBaseData) + +void GameBase::initPersistFields() +{ + Parent::initPersistFields(); + + // set some static membors + smEnemyBeaconText = StringTable->insert("Target Beacon"); + smFriendBeaconText = StringTable->insert("Marker Beacon"); + smVehicleBeaconText = StringTable->insert("Bomb Target"); + + Con::registerType(TypeGameBaseDataPtr, sizeof(GameBaseData*), + REF_GETDATATYPE(GameBaseData), + REF_SETDATATYPE(GameBaseData)); + + addField("nameTag", TypeCaseString, Offset(mNameTag, GameBase)); + addField("dataBlock", TypeGameBaseDataPtr, Offset(mDataBlock, GameBase)); + + addField("lockCount", TypeS32, Offset(mLockCount, GameBase)); + addField("homingCount", TypeS32, Offset(mHomingCount, GameBase)); +} + +void GameBase::consoleInit() +{ +#ifdef DEBUG + Con::addVariable("GameBase::boundingBox", TypeBool, &gShowBoundingBox); +#endif + + Con::addCommand("GameBase", "getDataBlock", cGetDataBlock, "obj.getDataBlock()", 2, 2); + Con::addCommand("GameBase", "setDataBlock", cSetDataBlock, "obj.setDataBlock(DataBlock)", 3, 3); + Con::addCommand("GameBase", "setTarget", cSetTargetId, "obj.setTarget(targetId)", 3, 3); + Con::addCommand("GameBase", "getTarget", cGetTargetId, "obj.getTarget()", 2, 2); + Con::addCommand("GameBase", "isBeacon", cIsBeacon, "obj.isBeacon()", 2, 2); + Con::addCommand("GameBase", "setBeacon", cSetBeacon, "obj.setBeacon(bool)", 3, 3); + Con::addCommand("GameBase", "setBeaconType", cSetBeaconType, "obj.setBeaconType(enemy or friend or vehicle)", 3, 3); + Con::addCommand("GameBase", "isBeaconType", cIsBeaconType, "obj.isBeaconType(enemy or friend or vehicle)", 3, 3); + Con::addCommand("setBeaconNames", cSetBeaconNames, "setBeaconNames(target, marker, vehicle)", 4, 4); +} + + + +//-------------------------------------------------------------------------- +// Sensor stuff... +SensorData* GameBase::getSensorData() +{ + if(mTargetInfo) + return mTargetInfo->sensorData; + else + return NULL; +} + +void GameBase::targetInfoChanged(TargetInfo *info) +{ + if(isClientObject()) + { + if(info) + Sim::getCommandTargetSet()->addObject(this); + else + Sim::getCommandTargetSet()->removeObject(this); + } + + if(info) + { + if(mTargetId >= 32) + gTargetManager->setTargetObject(info, this); + } + else + { + mTargetId = -1; + mTargetInfo = 0; + } +} + + +void GameBase::setHeat(const F32) +{ + // Do nothing +} + +F32 GameBase::getHeat() const +{ + return 0.0f; +} + diff --git a/game/gameBase.h b/game/gameBase.h new file mode 100644 index 0000000..ccf17e3 --- /dev/null +++ b/game/gameBase.h @@ -0,0 +1,266 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMEBASE_H_ +#define _GAMEBASE_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif +#ifndef _RESMANAGER_H_ +#include "core/resManager.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif + +class NetConnection; +class ProcessList; +struct Move; +struct SensorData; +struct TargetInfo; + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +struct GameBaseData : public SimDataBlock { + private: + typedef SimDataBlock Parent; + + public: + bool packed; + StringTableEntry catagory; + StringTableEntry className; + + bool beacon; // will show a beacon on this object through the current connection sensorgroup + S32 beaconType; + + // sensor data + bool onAdd(); + + // The derived class should provide the following: + DECLARE_CONOBJECT(GameBaseData); + GameBaseData(); + static void initPersistFields(); + bool preload(bool server, char errorBuffer[256]); + void unpackData(BitStream* stream); +}; + +//---------------------------------------------------------------------------- +class GameConnection; + +class GameBase : public SceneObject +{ + private: + typedef SceneObject Parent; + friend class ProcessList; + + // Datablock + private: + GameBaseData* mDataBlock; + bool mBeacon; + S32 mBeaconType; + StringTableEntry mNameTag; + + // Processing interface + private: + void plUnlink(); + void plLinkAfter(GameBase*); + void plLinkBefore(GameBase*); + void plJoin(GameBase*); + struct Link { + GameBase *next; + GameBase *prev; + }; + U32 mProcessTag; // Tag used during sort + Link mProcessLink; // Ordered process queue + SimObjectPtr mAfterObject; + + // Collision Notification + public: + enum gBeaconType + { + enemyBeacon = 0, + friendBeacon, + vehicleBeacon + }; + + struct CollisionTimeout { + CollisionTimeout* next; + GameBase* object; + U32 objectNumber; + SimTime expireTime; + }; + private: + CollisionTimeout* mTimeoutList; + + public: + static bool gShowBoundingBox; + protected: + bool mProcessTick; // Tick or no tick? + F32 mLastDelta; + TargetInfo *mTargetInfo; + S32 mTargetId; + + GameBase *mNextSensor; + GameBase *mPrevSensor; + + static StringTableEntry smEnemyBeaconText; + static StringTableEntry smFriendBeaconText; + static StringTableEntry smVehicleBeaconText; + + S32 mLockCount; + S32 mHomingCount; + + // + F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips); + + public: + GameBase(); + ~GameBase(); + + enum GameBaseMasks { + InitialUpdateMask = 1 << 0, + DataBlockMask = 1 << 1, + ExtendedInfoMask = 1 << 2, + NextFreeMask = ExtendedInfoMask << 1 + }; + + // Init + bool onAdd(); + void onRemove(); + void inspectPostApply(); + static void consoleInit(); + static void initPersistFields(); + + // Data block + bool setDataBlock(GameBaseData* dptr); + GameBaseData* getDataBlock() { return mDataBlock; } + virtual bool onNewDataBlock(GameBaseData* dptr); + + // The scriptOnXX methods must be invoked by the leaf classes + void scriptOnAdd(); + void scriptOnNewDataBlock(); + void scriptOnRemove(); + + // Processing + void setProcessTick(bool t) { mProcessTick = t; } + void processAfter(GameBase*); + void clearProcessAfter(); + GameBase* getProcessAfter() { return mAfterObject; } + void removeFromProcessList() { plUnlink(); } + virtual void processTick(const Move*); + virtual void interpolateTick(F32 backDelta); + virtual void advanceTime(F32 dt); + + virtual Point3F getVelocity() const; + + virtual void setHeat(const F32); + virtual F32 getHeat() const; + + // Targeting + virtual bool getTarget(Point3F* pTarget, U32* pTeamId); + virtual void targetInfoChanged(TargetInfo *targ); + void setTarget(S32 targetId); + TargetInfo *getTargetInfo(); + S32 getTarget() { return mTargetId; } + void sendTargetTo(NetConnection *conn); + + void setTransform(const MatrixF&); + + bool isBeacon() { return(mBeacon); } + void setBeacon(bool beacon); + void setBeaconType(S32); + S32 getBeaconType() { return mBeaconType; } + static void setBeaconGameNames(const char * target, const char * marker, const char * vehicle); + + S32 getLockCount() { return mLockCount; } + S32 getHomingCount() { return mHomingCount; } + void incHomingCount(); + void decHomingCount(); + void incLockCount(); + void decLockCount(); + + // Sensor + SensorData *getSensorData(); + bool isSensorVisible(); + U32 getSensorGroup(); + bool getGameName(char * buf, S32 bufSize); + + // Network + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); + // writePacketData returns false if it needs to write more... for + // example if its control object hasn't been written across yet. + virtual bool writePacketData(GameConnection *, BitStream *stream); + virtual void readPacketData(GameConnection *, BitStream *stream); + + DECLARE_CONOBJECT(GameBase); +}; + +inline void GameBase::incHomingCount() +{ + mHomingCount++; + + if( mLockCount > 0 ) + decLockCount(); +} + +inline void GameBase::decHomingCount() +{ + if (mHomingCount) + mHomingCount--; +} + +inline void GameBase::incLockCount() +{ + mLockCount++; +} + +inline void GameBase::decLockCount() +{ + if (mLockCount) + mLockCount--; +} + + +//----------------------------------------------------------------------------- + +#define TickShift 5 +#define TickMs (1 << TickShift) +#define TickSec (F32(1) / TickMs) +#define SecTick (F32(1) / TickSec) +#define TickMask (TickMs - 1) + +class ProcessList +{ + GameBase head; + U32 mCurrentTag; + SimTime mLastTick; + SimTime mLastTime; + SimTime mLastDelta; + bool mDirty; + + void orderList(); + void advanceObjects(); + +public: + SimTime getLastTime() { return mLastTime; } + ProcessList(); + void markDirty() { mDirty = true; } + bool isDirty() { return mDirty; } + void addObject(GameBase* obj) { + obj->plLinkBefore(&head); + } + void advanceServerTime(SimTime timeDelta); + void advanceClientTime(SimTime timeDelta); +}; + +extern ProcessList gClientProcessList; +extern ProcessList gServerProcessList; + +#endif diff --git a/game/gameConnection.cc b/game/gameConnection.cc new file mode 100644 index 0000000..9c5e689 --- /dev/null +++ b/game/gameConnection.cc @@ -0,0 +1,1635 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/consoleTypes.h" +#include "console/simBase.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "core/bitStream.h" +#include "sim/pathManager.h" +#include "audio/audio.h" +#include "game/targetManager.h" +#include "game/netDispatch.h" +#include "game/game.h" +#include "scenegraph/sceneGraph.h" +#include "game/gameConnectionEvents.h" +#include "game/auth.h" + +//---------------------------------------------------------------------------- +#define MAX_VOICE_CHANNELS 3 +#define MAX_MOVE_PACKET_SENDS 4 + +//---------------------------------------------------------------------------- + +const U32 targetCollisionMask = (TerrainObjectType | + InteriorObjectType | + PlayerObjectType | + VehicleObjectType | + StaticShapeObjectType | + ForceFieldObjectType | + TurretObjectType); + +void GameConnection::sendTargetToServer(S32 targetId, Point3F pos) +{ + AssertFatal((targetId >= -1) && (targetId < TargetManager::MaxTargets), "GameConnection::sendTargetToServer: invalid target id"); + postNetEvent(new SetServerTargetEvent(targetId, pos)); +} + +bool GameConnection::sendLOSTarget() +{ + MatrixF transform; + Point3F pos; + + if(ShapeBase *cam = getControlObject()) + { + cam->getEyeTransform(&transform); + transform.getColumn(3, &pos); + VectorF vec; + transform.mulV(VectorF(0,1000,0), &vec); + + RayInfo rayInfo; + Point3F endPos = pos + vec; + cam->disableCollision(); + if (gClientContainer.castRay(pos, endPos, targetCollisionMask, &rayInfo)) + { + cam->enableCollision(); + GameBase *obj = dynamic_cast(rayInfo.object); + S32 target = -1; + if(obj) + target = obj->getTarget(); + sendTargetToServer(target, rayInfo.point); + return true; + } + cam->enableCollision(); + } + return false; +} + + +void GameConnection::setServerTarget(S32 targetId, Point3F targetPos) +{ + AssertFatal((targetId >= -1) && (targetId < TargetManager::MaxTargets), "GameConnection::setServerTarget: invalid target id"); + mTargetId = targetId; + mTargetPos = targetPos; +} + +void GameConnection::sendTargetTo(NetConnection *conn, bool assign) +{ + if((mTargetId < -1) || (mTargetId >= TargetManager::MaxTargets)) + return; + + if(!isServerConnection()) + conn->postNetEvent(new TargetToEvent(mTargetId, mTargetPos, assign)); +} + +//---------------------------------------------------------------------------- + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GameConnection); +bool GameConnection::mFirstPerson = true; +S32 GameConnection::mLagThresholdMS = 0; +S32 GameConnection::smVoiceConnections[MaxClients]; + +//---------------------------------------------------------------------------- +GameConnection::GameConnection(bool ghostFrom, bool ghostTo, bool sendEvents) + : NetConnection(ghostFrom, ghostTo, sendEvents) +{ + mControlObject = NULL; + mLastMoveAck = 0; + mControlStateSkipCount = 0; + mLastClientMove = 0; + mFirstMoveIndex = 0; + mMoveCredit = MaxMoveCount; + mDataBlockModifiedKey = 0; + mMaxDataBlockModifiedKey = 0; + mAuthInfo = NULL; + mControlObjectModifyKey = 0; + mAckedControlObjectModifyKey = 0; + mRemoteControlObjectModifyKey = 0; + mLastSentControlObjectModifyKey = 0; + mMissionCRC = 0xffffffff; + + mDamageFlash = mWhiteOut = 0; + mSelfLocked = false; + mSelfHomed = false; + mSeekerTracking = false; + mSeekerMode = ShapeBase::NotLocked; + + mCameraPos = 0; + mCameraSpeed = 10; + + mCameraFov = 90.f; + mUpdateCameraFov = false; + + mAIControlled = false; + + // voice stream + mWouldListenTo.setSize(MaxClients); + mWouldListenTo.set(); + + mListeningTo.setSize(MaxClients); + mListeningTo.clear(); + + mMaxVoicechannels = 0; + mCurVoicechannels = 0; + mVoiceDecodingMask = 0; + mVoiceEncodingLevel = -1; + mPinged = false; + mJammed = false; + mDisconnectReason[0] = 0; + for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++) + mTargetVisibleMask[i] = 0; + + mSensorGroup = 0; + mInCommanderMap = false; + mLockAudioHandle = NULL_AUDIOHANDLE; + mHomingAudioHandle = NULL_AUDIOHANDLE; + mTargetLockedAudioHandle = NULL_AUDIOHANDLE; + mTargetingAudioHandle = NULL_AUDIOHANDLE; + + mTargetId = -1; + mTargetPos.set(0.f, 0.f, 0.f); + + //blackout vars + mBlackOut = 0.0f; + mBlackOutTimeMS = 0; + mBlackOutStartTimeMS = 0; + mFadeToBlack = false; +} + +GameConnection::~GameConnection() +{ + if (mLockAudioHandle != NULL_AUDIOHANDLE) + alxStop(mLockAudioHandle); + if (mHomingAudioHandle != NULL_AUDIOHANDLE) + alxStop(mHomingAudioHandle); + if (mTargetLockedAudioHandle != NULL_AUDIOHANDLE) + alxStop(mTargetLockedAudioHandle); + if (mTargetingAudioHandle != NULL_AUDIOHANDLE) + alxStop(mTargetingAudioHandle); + mLockAudioHandle = NULL_AUDIOHANDLE; + mHomingAudioHandle = NULL_AUDIOHANDLE; + mTargetLockedAudioHandle = NULL_AUDIOHANDLE; + mTargetingAudioHandle = NULL_AUDIOHANDLE; + + delete mAuthInfo; +} + +void GameConnection::setAuthInfo(const AuthInfo *info) +{ + mAuthInfo = new AuthInfo; + *mAuthInfo = *info; +} + +const AuthInfo *GameConnection::getAuthInfo() +{ + return mAuthInfo; +} + + +//---------------------------------------------------------------------------- + +void GameConnection::setControlObject(ShapeBase *obj) +{ + if(obj == mControlObject) + return; + + // if this is a connection to a client, update the control object modify key: + if(!isServerConnection()) + mControlObjectModifyKey++; + if(mControlObject) + mControlObject->setControllingClient(0); + if(obj) + { + if (ShapeBase* coo = obj->getControllingObject()) + coo->setControlObject(0); + if (GameConnection *con = obj->getControllingClient()) + con->setControlObject(0); + obj->setControllingClient(this); + } + mControlObject = obj; + setScopeObject(mControlObject); + + // if this is a client then set the fov and active image + if(isServerConnection() && mControlObject) + { + F32 fov = mControlObject->getDefaultCameraFov(); + GameSetCameraFov(fov); + } +} + +static S32 sChaseQueueSize = 0; +static MatrixF* sChaseQueue = 0; +static S32 sChaseQueueHead = 0; +static S32 sChaseQueueTail = 0; + +bool GameConnection::getControlCameraTransform(F32 dt, MatrixF* mat) +{ + ShapeBase* obj = getControlObject(); + if(!obj) + return false; + + ShapeBase* cObj = obj; + while((cObj = cObj->getControlObject()) != 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) +// { +// if(ShapeBase * obj = getControlObject()) +// { +// ShapeBase* cObj = obj; +// while((cObj = cObj->getControlObject()) != 0) +// { +// if(cObj->useObjsEyePoint()) +// obj = cObj; +// } +// *fov = obj->getCameraFov(); +// return(true); +// } +// return(false); +// } + +bool GameConnection::getControlCameraFov(F32 * fov) +{ + //find the last control object in the chain (client->player->turret->whatever...) + ShapeBase *obj = getControlObject(); + ShapeBase *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...) + ShapeBase *obj = getControlObject(); + ShapeBase *cObj = NULL; + while (obj) + { + cObj = obj; + obj = obj->getControlObject(); + } + if (cObj) + return (cObj->isValidCameraFov(fov)); + return(true); +} + +bool GameConnection::setControlCameraFov(F32 fov) +{ + //find the last control object in the chain (client->player->turret->whatever...) + ShapeBase *obj = getControlObject(); + ShapeBase *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 (ShapeBase* obj = getControlObject()) { + *vel = obj->getVelocity(); + return true; + } + return false; +} + + +void GameConnection::setObjectActiveImage(ShapeBase * obj, U32 slot) +{ + AssertFatal(obj, "GameConnection::setControlObjectActiveImage: invalid obj ptr"); + AssertFatal(slot < ShapeBase::MaxMountedImages, "GameConnection::setControlObjectActiveImage: image slot out of range"); + + if(isServerConnection()) + return; + + S32 gi = getGhostIndex(obj); + if(gi == -1) + return; + + postNetEvent(new SetObjectActiveImageEvent(U32(gi), slot)); +} + +//---------------------------------------------------------------------------- +bool GameConnection::onAdd() +{ + if (!Parent::onAdd()) + return false; + + if (!isServerConnection()) + { + // set the voice id + AssertFatal(MaxClients < 255, "Must increase voice id size"); + for(U32 i = 1; i <= MaxClients; i++) + if(!smVoiceConnections[i]) + { + smVoiceConnections[i] = getId(); + mVoiceID = i; + break; + } + } + + return true; +} + +void GameConnection::onRemove() +{ + if(isNetworkConnection()) + { + Con::printf("Issuing Disconnect packet."); + + // send a disconnect packet... + U32 serverConnectSequence, clientConnectSequence; + getSequences(&clientConnectSequence, &serverConnectSequence); + + BitStream *out = BitStream::getPacketStream(); + out->write(U8(Disconnect)); + out->write(serverConnectSequence); + out->write(clientConnectSequence); + out->writeString(mDisconnectReason); + + BitStream::sendPacketStream(getNetAddress()); + } + if(!isServerConnection()) + Con::executef(this, 2, "onDrop", mDisconnectReason); + + if (mControlObject) + mControlObject->setControllingClient(0); + Parent::onRemove(); + + // clear the voice id + smVoiceConnections[mVoiceID] = 0; + mVoiceID = 0; +} + +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: + pushMove(*((Move *) data)); + if(isRecording()) // put it back into the stream + recordBlock(Sim::getCurrentTime(), 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->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); + Parent::writeDemoStartBlock(stream); + gTargetManager->writeDemoStartBlock(stream, this); +} + +void GameConnection::readDemoStartBlock(BitStream *stream) +{ + while(stream->readFlag()) + { + SimDataBlockEvent evt; + evt.unpack(this, stream); + evt.process(this); + } + stream->read(&mFirstPerson); + stream->read(&mCameraPos); + stream->read(&mCameraSpeed); + 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); + } + Parent::readDemoStartBlock(stream); + gTargetManager->readDemoStartBlock(stream, this); +} + + +void GameConnection::demoPlaybackComplete() +{ + static const char *demoPlaybackArgv[1] = { "demoPlaybackComplete" }; + Sim::postCurrentEvent(Sim::getRootGroup(), new SimConsoleEvent(1, demoPlaybackArgv, false)); + Parent::demoPlaybackComplete(); +} + + +//---------------------------------------------------------------------------- + +void GameConnection::readPacket(BitStream *bstream) +{ + char stringBuf[256]; + stringBuf[0] = 0; + bstream->setStringBuffer(stringBuf); + + clearCompression(); + if (isServerConnection()) + { + mLastMoveAck = bstream->readInt(32); + if (mLastMoveAck < mFirstMoveIndex) + mLastMoveAck = mFirstMoveIndex; + if(mLastMoveAck > mLastClientMove) + mLastClientMove = mLastMoveAck; + while(mFirstMoveIndex < mLastMoveAck) + { + AssertFatal(mMoveList.size(), "Popping off too many moves!"); + mMoveList.pop_front(); + mFirstMoveIndex++; + } + + 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()) + { + mSelfLocked = bstream->readFlag(); + mSelfHomed = bstream->readFlag(); + } + else + { + mSelfLocked = false; + mSelfHomed = false; + } + if(bstream->readFlag()) + { + mSeekerTracking = bstream->readFlag(); + mSeekerMode = bstream->readRangedU32(ShapeBase::NotLocked, ShapeBase::LockPosition); + if(mSeekerMode == ShapeBase::LockObject) + { + if(bstream->readFlag()) + mSeekerObject = (ShapeBase *) resolveGhost(bstream->readRangedU32(0, MaxGhostCount-1)); + else + mSeekerObject = NULL; + } + else if( mSeekerMode == ShapeBase::LockPosition ) + { + bstream->read(&mTargetPos.x); + bstream->read(&mTargetPos.y); + bstream->read(&mTargetPos.z); + } + else + mSeekerObject = NULL; + } + else + { + mSeekerTracking = false; + mSeekerMode = ShapeBase::NotLocked; + mSeekerObject = NULL; + } + bool pinged = bstream->readFlag(); + if(pinged != mPinged) + { + Con::executef(this, 2, "sensorPing", Con::getIntArg(pinged)); + mPinged = pinged; + } + + bool jammed = bstream->readFlag(); + if(jammed != mJammed) + { + Con::executef(this, 2, "sensorJammed", Con::getIntArg(jammed)); + mJammed = jammed; + } + + if (bstream->readFlag()) + { + if(bstream->readFlag()) + { + // the control object is dirty... + // so we get an update: + mLastClientMove = mLastMoveAck; + bool callScript = false; + if(mControlObject.isNull()) + callScript = true; + + S32 gIndex = bstream->readInt(10); + ShapeBase* obj = static_cast(resolveGhost(gIndex)); + if (obj != mControlObject) + setControlObject(obj); + obj->readPacketData(this, bstream); + + if(callScript) + Con::executef(this, 2, "initialControlSet"); + } + else + { + // read out the compression point + Point3F pos; + bstream->read(&pos.x); + bstream->read(&pos.y); + bstream->read(&pos.z); + setCompressionPoint(pos); + } + } + + while(bstream->readFlag()) + { + U32 index = bstream->readInt(4); + U32 mask = bstream->readInt(32); + U32 start = index << 5; + while(mask) + { + if(mask & 1) + { + TargetInfo *ct = gTargetManager->getClientTarget(start); + ct->sensorFlags ^= TargetInfo::VisibleToSensor; + } + mask >>= 1; + start++; + } + } + + // server forcing a fov change? + if(bstream->readFlag()) + { + S32 fov = bstream->readInt(8); + setControlCameraFov(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 + GameSetCameraFov(fov); + } + } + else + { + bool fp = bstream->readFlag(); + if(fp) + mCameraPos = 0; + else + mCameraPos = 1; + + if(bstream->readFlag()) + { + // the client wants a new control object state + U32 remoteKey = bstream->readInt(5); + if(remoteKey != mRemoteControlObjectModifyKey) + { + mControlObjectModifyKey++; + mRemoteControlObjectModifyKey = remoteKey; + } + } + moveReadPacket(bstream); + + // check fov change.. 1degree granularity on server + if(bstream->readFlag()) + { + S32 fov = mClamp(bstream->readInt(8), S32(MinCameraFov), S32(MaxCameraFov)); + setControlCameraFov(fov); + + // may need to force client back to a valid fov + F32 setFov; + if(getControlCameraFov(&setFov) && (S32(setFov) == fov)) + mUpdateCameraFov = false; + } + } + Parent::readPacket(bstream); + clearCompression(); + bstream->setStringBuffer(NULL); +} + +void GameConnection::writePacket(BitStream *bstream, PacketNotify *note) +{ + char stringBuf[256]; + clearCompression(); + stringBuf[0] = 0; + bstream->setStringBuffer(stringBuf); + + GamePacketNotify *gnote = (GamePacketNotify *) note; + gnote->controlObjectModifyKey = 0; + + U32 startPos = bstream->getCurPos(); + if (isServerConnection()) + { + bstream->writeFlag(mCameraPos == 0); + + if(bstream->writeFlag(mControlObjectModifyKey != mAckedControlObjectModifyKey)) + { + gnote->controlObjectModifyKey = mControlObjectModifyKey; + mLastSentControlObjectModifyKey = mControlObjectModifyKey; + bstream->writeInt(mControlObjectModifyKey, 5); + } + + moveWritePacket(bstream); + + // 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 + { + // The only time mMoveList will not be empty at this + // point is during a change in control object. + + bstream->writeInt(mLastMoveAck - mMoveList.size(),32); + TargetInfo *info = NULL; + + S32 gIndex = -1; + + // get the ghost index of the control object, and write out + // all the damage flash, white out and missile tracking information + + if (!mControlObject.isNull()) + { + gIndex = getGhostIndex(mControlObject); + info = mControlObject->getTargetInfo(); + + 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); + } + S32 lockCount = mControlObject->getLockCount(), + homingCount = mControlObject->getHomingCount(); + + // get the lock and homing counts off our mount as well.. + ShapeBase *mount = mControlObject->getObjectMount(); + if(mount) + { + lockCount += mount->getLockCount(); + homingCount += mount->getHomingCount(); + } + if(bstream->writeFlag(lockCount | homingCount)) + { + bstream->writeFlag(lockCount > 0); + bstream->writeFlag(homingCount > 0); + } + ShapeBase *co = mControlObject->getControlObject(); + if(!co) + co = mControlObject; + + bool tracking = co->isTracking(); + ShapeBase::LockMode mode = co->getLockMode(); + ShapeBase *target = co->getLockedTarget(); + + if(bstream->writeFlag(tracking || mode != ShapeBase::NotLocked)) + { + bstream->writeFlag(tracking); + bstream->writeRangedU32(mode, ShapeBase::NotLocked, ShapeBase::LockPosition); + if(mode == ShapeBase::LockObject) + { + S32 gi = -1; + if(target) + gi = getGhostIndex(target); + if(bstream->writeFlag(gi != -1)) + bstream->writeRangedU32(U32(gi), 0, MaxGhostCount - 1); + } + else if( mode == ShapeBase::LockPosition ) + { + Point3F targetPos = co->getLockedPosition(); + bstream->write(targetPos.x); + bstream->write(targetPos.y); + bstream->write(targetPos.z); + } + } + } + else + { + bstream->writeFlag(false); + bstream->writeFlag(false); + bstream->writeFlag(false); + } + // always write out sensor ping stuff... + bstream->writeFlag(info ? info->sensorFlags & TargetInfo::SensorPinged : false); + bstream->writeFlag(info ? info->sensorFlags & TargetInfo::SensorJammed : false); + + if (bstream->writeFlag(gIndex != -1)) + { + // assume that the control object will write in a compression point + if(bstream->writeFlag(mControlObjectModifyKey != mAckedControlObjectModifyKey || mControlStateSkipCount >= ControlStateSkipAmount)) + { + bstream->writeInt(gIndex,10); + // write out the control object - if it returns false, that means we have + // to send it again soon. + + if(mControlObject->writePacketData(this, bstream)) + { + gnote->controlObjectModifyKey = mControlObjectModifyKey; + mLastSentControlObjectModifyKey = mControlObjectModifyKey; + mControlStateSkipCount = 0; + } + } + else + { + // we'll have to use the control object's position as the compression point + // should make this lower res for better space usage: + mControlStateSkipCount++; + Point3F coPos = mControlObject->getPosition(); + bstream->write(coPos.x); + bstream->write(coPos.y); + bstream->write(coPos.z); + setCompressionPoint(coPos); + } + } + DEBUG_LOG(("PKLOG %d CONTROLOBJECTSTATE: %d", getId(), bstream->getCurPos() - startPos)); + startPos = bstream->getCurPos(); + // now write the visible target mask + U32 *pingMask = gTargetManager->mSensorInfoArray[mSensorGroup].targetPingMask; + + for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++) + { + gnote->xorVisibleMask[i] = pingMask[i] ^ mTargetVisibleMask[i]; + mTargetVisibleMask[i] = pingMask[i]; + if(gnote->xorVisibleMask[i]) + { + bstream->writeFlag(true); + bstream->writeInt(i, 4); + bstream->writeInt(gnote->xorVisibleMask[i], 32); + } + } + bstream->writeFlag(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); + clearCompression(); + bstream->setStringBuffer(NULL); +} + +void GameConnection::updateLockTones() +{ + // we've locked our target + if( (mSeekerMode == ShapeBase::LockObject) || (mSeekerMode == ShapeBase::LockPosition) ) + { + Con::executef(this, 2, "onTargetLocked", "true"); + } + else + { + Con::executef(this, 2, "onTargetLocked", "false"); + } + + // hot targets in our view + if( mSeekerTracking ) + { + Con::executef(this, 2, "onTrackingTarget", "true"); + } + else + { + Con::executef(this, 2, "onTrackingTarget", "false"); + } +} + +void GameConnection::updateLockWarnings() +{ + // someone has a lock on us + if( mSelfLocked ) + { + if(!mSelfHomed) + Con::executef(this, 2, "onLockWarning", "true"); + } + else + { + Con::executef(this, 2, "onLockWarning", "false"); + } + + // missile in the air, homing in on us + if( mSelfHomed ) + { + Con::executef(this, 2, "onHomeWarning", "true"); + } + else + { + Con::executef(this, 2, "onHomeWarning", "false"); + } +} + +void GameConnection::detectLag() +{ + //see if we're lagging... + S32 curTime = Sim::getCurrentTime(); + if (curTime - mLastPacketTime > mLagThresholdMS) + { + if (!mLagging) + { + mLagging = true; + Con::executef(this, 2, "setLagIcon", "true"); + } + } + else if (mLagging) + { + mLagging = false; + Con::executef(this, 2, "setLagIcon", "false"); + } +} + +GameConnection::GamePacketNotify::GamePacketNotify() +{ + // need to fill in empty notifes for demo start block + cameraFov = 0; + controlObjectModifyKey = 0; + for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++) + xorVisibleMask[i] = 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(); + GamePacketNotify *gnote = (GamePacketNotify *) note; + if(gnote->controlObjectModifyKey) + mAckedControlObjectModifyKey = gnote->controlObjectModifyKey; + + Parent::packetReceived(note); +} + +void GameConnection::packetDropped(PacketNotify *note) +{ + Parent::packetDropped(note); + GamePacketNotify *gnote = (GamePacketNotify *) note; + for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++) + mTargetVisibleMask[i] ^= gnote->xorVisibleMask[i]; + if(gnote->cameraFov != -1) + mUpdateCameraFov = true; +} + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +void GameConnection::play2D(const AudioProfile* profile) +{ + postNetEvent(new Sim2DAudioEvent(profile)); +} + +void GameConnection::play3D(const AudioProfile* profile, const MatrixF *transform) +{ + if (transform) { + if (mControlObject) { + // 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->mDescriptionObject->mDescription.mMaxDistance) + postNetEvent(new Sim3DAudioEvent(profile,transform)); + } + else + postNetEvent(new Sim3DAudioEvent(profile,transform)); + } + else + play2D(profile); +} + +//-------------------------------------- +bool GameConnection::getListenState(U8 voiceId) +{ + return(mWouldListenTo.test(voiceId)); +} + +void GameConnection::listenTo(U8 voiceId, bool tf) +{ + if (tf) + mWouldListenTo.set(voiceId); + else + { + // terminate any existing stream + GameConnection *itr = static_cast(getConnectionList()); + while (itr != NULL) + { + if (!itr->isServerConnection()) + itr->stopListening(mVoiceID); + itr = static_cast(itr->getNext()); + } + + // refuse any future request to talk to me + mWouldListenTo.clear(voiceId); + } +} + +//-------------------------------------- +void GameConnection::listenToAll() +{ + mWouldListenTo.set(); +} + +//-------------------------------------- +void GameConnection::listenToNone() +{ + mWouldListenTo.clear(); +} + +bool GameConnection::canListen(GameConnection * con) +{ + AssertFatal(con, "GameConnection::canListen: invalid connection passed!"); + +#ifndef DEBUG + // never allow a connection to listen to self + if(con == this) + return(false); +#endif + + // Can't listen if no channels are available: + if ( mMaxVoicechannels == 0 ) + return( false ); + + // make sure encoder/decoder's match + if(con->getVoiceEncodingLevel() < 0) + return(false); + + if(!(mVoiceDecodingMask & (1 << con->getVoiceEncodingLevel()))) + return(false); + + // check the listen mask for this group + U32 listenMask = gTargetManager->getSensorGroupListenMask(getSensorGroup()); + return(listenMask & (1 << con->getSensorGroup())); +} + +//-------------------------------------- +bool GameConnection::willListen(U8 voiceId) +{ + AssertFatal(voiceId < MaxClients, "GameConnection::willListen: invalid voice id"); + if(voiceId >= MaxClients) + return false; + + // would we consider listening to this person? + if (mWouldListenTo.test(voiceId)) + { + // can we listen to another person? + if (mCurVoicechannels < mMaxVoicechannels) + { + if (mListeningTo.test(voiceId)) + stopListening(voiceId); // just in case + mListeningTo.set(voiceId); + mCurVoicechannels++; + return true; + } + else + { + // notify client that someone attempted to talk + Con::evaluatef("commandToClient(%d, 'playerStartTalking', %d, 0);", getId(), smVoiceConnections[voiceId]); + } + } + return false; +} + +//-------------------------------------- +void GameConnection::stopListening(U8 voiceId) +{ + AssertFatal(voiceId < MaxClients, "GameConnection::stopListening: invalid voice id"); + if(voiceId >= MaxClients) + return; + + // if this client is talking to me quit listening and terminiate voice stream + if (mListeningTo.test(voiceId)) + { + mCurVoicechannels--; + AssertWarn(0, "GameConnection::stopListening: Need to terminate existing stream & send event"); + } + else + { + if (mWouldListenTo.test(voiceId)) + { + // notify client that someone quit talking + Con::evaluatef("commandToClient(%d, 'playerStoppedTalking', %d, 0);", getId(), smVoiceConnections[voiceId]); + } + } + + mListeningTo.clear(voiceId); +} + + +void GameConnection::setVoiceChannels(S32 num) +{ + mMaxVoicechannels = mClamp(num, 0, MAX_VOICE_CHANNELS); // 0-MAX_VOICE_CHANNELS active talking channels +} + +//---------------------------------------------------------------------------- +// Object scoping callback +static void commanderScopeCallback(SceneObject* obj, S32 value) +{ + const U32 scopeMask = (TerrainObjectType | + InteriorObjectType | + WaterObjectType | + PlayerObjectType | + VehicleObjectType | + StaticShapeObjectType); + + GameConnection * con = reinterpret_cast(value); + if(obj->isScopeable() && (obj->getType() & scopeMask)) + con->objectInScope(obj); +} + +void GameConnection::doneScopingScene() +{ + // scope all in commander map + if(mInCommanderMap) + gServerContainer.findObjects(-1, commanderScopeCallback, reinterpret_cast(this)); + else + { + // scope any visible object from sensorVisibleSet + SimSet * scopeSet = Sim::getScopeSensorVisibleSet(); + for(SimSet::iterator itr = scopeSet->begin(); itr != scopeSet->end(); itr++) + { + AssertFatal(((*itr)->getType() & ShapeBaseObjectType), "GameConnection: invalid object in scope set"); + + ShapeBase * obj = static_cast(*itr); + if(obj->isHidden() || (obj->getTarget() == -1) || !obj->isScopeable() || + !gTargetManager->isTargetVisible(obj->getTarget(), getSensorGroup())) + continue; + + objectInScope(obj); + } + } +} + +//---------------------------------------------------------------------------- +void GameConnection::setSensorGroup(U32 group) +{ + if(group == mSensorGroup) + return; + + if(!isServerConnection()) + { + gTargetManager->clientSensorGroupChanged(this, group); + postNetEvent(new SetSensorGroupEvent(group)); + } + mSensorGroup = group; +} + +void GameConnection::resetVisibleMasks() +{ + for(U32 i = 0; i < TargetManager::TargetFreeMaskSize; i++) + mTargetVisibleMask[i] = 0; +} + +//---------------------------------------------------------------------------- +//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::handleGhostMessage(S32 message, U32 sequence, U32 ghostCount) +{ + if(isServerConnection()) + { + if(message == GhostAlwaysStarting) + { +// Authentication removed. +// if(!clientAuth.valid) +// Sim::postEvent( Sim::getRootGroup(), new QuitEvent(), Sim::getTargetTime() + 10000 ); + } + } + Parent::handleGhostMessage(message, sequence, ghostCount); +} + +//---------------------------------------------------------------------------- +static void cTransmitDataBlocks(SimObject *obj, S32, const char **argv) +{ + + GameConnection *cptr = (GameConnection *) obj; + + cptr->setDataBlockSequence(dAtoi(argv[2])); + SimDataBlockGroup *g = Sim::getDataBlockGroup(); + //g->sort(); + + // check for the early out: + U32 groupCount = g->size(); + if(!groupCount) + return; + S32 key = cptr->getDataBlockModifiedKey(); + // find the first one we haven't sent: + U32 i; + for(i = 0; i < groupCount; i++) + if(( (SimDataBlock *)(*g)[i])->getModifiedKey() > key) + break; + + if(i == groupCount) + { + Con::executef(cptr, 2, "dataBlocksDone", Con::getIntArg(cptr->getDataBlockSequence())); + return; + } + cptr->setMaxDataBlockModifiedKey(key); + + U32 max = getMin(i + DataBlockQueueCount, groupCount); + for(;i < max; i++) + { + SimDataBlock *data = (SimDataBlock *)(*g)[i]; + cptr->postNetEvent(new SimDataBlockEvent(data, i, groupCount, cptr->getDataBlockSequence())); + } +} + +static void cActivateGhosting(SimObject *obj, S32, const char **) +{ + NetConnection *cptr = (NetConnection *) obj; + cptr->activateGhosting(); +} + +static void cResetGhosting(SimObject *obj, S32, const char **) +{ + NetConnection *cptr = (NetConnection *) obj; + cptr->resetGhosting(); +} + +static bool cSetControlObject(SimObject *obj, S32, const char **argv) +{ + GameConnection *cptr = (GameConnection *) obj; + ShapeBase *gb; + if(!Sim::findObject(argv[2], gb)) + return false; + + cptr->setControlObject(gb); + return true; +} + +static int cGetControlObject(SimObject *obj, S32, const char **argv) +{ + argv; + GameConnection *cptr = (GameConnection *) obj; + SimObject* cp = cptr->getControlObject(); + return cp? cp->getId(): 0; +} + +static bool cIsAIControlled(SimObject *obj, S32, const char **) +{ + GameConnection *cptr = (GameConnection *) obj; + return cptr->isAIControlled(); +} + +static bool cPlay2D(SimObject *obj, S32, const char **argv) +{ + GameConnection *cptr = (GameConnection *) obj; + AudioProfile *profile; + if(!Sim::findObject(argv[2], profile)) + return false; + cptr->play2D(profile); + return true; +} + +static bool cPlay3D(SimObject *obj, S32, const char **argv) +{ + GameConnection *cptr = (GameConnection *) obj; + AudioProfile *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],"%f %f %f %f %f %f %f", + &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); + + cptr->play3D(profile,&mat); + return true; +} + +static bool cChaseCam(SimObject *obj, S32, const char **argv) +{ + GameConnection *cptr = (GameConnection *) obj; + S32 size = dAtoi(argv[2]); + if (size != sChaseQueueSize) { + delete [] sChaseQueue; + sChaseQueue = 0; + sChaseQueueSize = size; + sChaseQueueHead = sChaseQueueTail = 0; + if (size) { + sChaseQueue = new MatrixF[size]; + return true; + } + } + return false; +} + +static void cSetSensorGroup(SimObject *obj, S32, const char **argv) +{ + GameConnection *cptr = (GameConnection *) obj; + if(!cptr->isServerConnection()) + cptr->setSensorGroup(dAtoi(argv[2])); +} + +static S32 cGetSensorGroup(SimObject *obj, S32, const char **) +{ + GameConnection *con = static_cast(obj); + return(S32(con->getSensorGroup())); +} + +static bool cSendLOSTarget(SimObject *obj, S32, const char **) +{ + GameConnection *cptr = (GameConnection *) obj; + return cptr->sendLOSTarget(); +} + +static void cSendTargetToServer(SimObject * obj, S32, const char ** argv) +{ + S32 targetId = dAtoi(argv[2]); + if((targetId < -1) || (targetId >= TargetManager::MaxTargets)) + { + Con::errorf(ConsoleLogEntry::General, "GameConnection::sendTargetToServer: invalid target id [%s]", argv[2]); + return; + } + + GameConnection * con = static_cast(obj); + Point3F pos(0,0,0); + dSscanf(argv[3], "%f %f %f", &pos.x, &pos.y, &pos.z); + con->sendTargetToServer(targetId, pos); +} + +static void cSendTargetTo(SimObject *obj, S32, const char **argv) +{ + GameConnection *cptr = (GameConnection *) obj; + NetConnection *dest; + if(Sim::findObject(argv[2], dest)) + cptr->sendTargetTo(dest, dAtob(argv[3])); +} + +static S32 cGetTargetId(SimObject *obj, S32, const char **) +{ + GameConnection * con = static_cast(obj); + if(con->isServerConnection()) + return(-1); + + return(con->getTargetId()); +} + +static S32 cGetHomingCount(SimObject *obj, S32, const char **) +{ + GameConnection * con = static_cast(obj); + if(con->isServerConnection()) + return(-1); + + return(con->getSelfHomed()); +} + +static const char * cGetTargetPos(SimObject *obj, S32, const char **) +{ + GameConnection * con = static_cast(obj); + if(con->isServerConnection()) + return(""); + + Point3F pos = con->getTargetPos(); + + char * buf = Con::getReturnBuffer(128); + dSprintf(buf, 128, "%f %f %f", pos.x, pos.y, pos.z); + return(buf); +} + +static void cSetTargetId(SimObject *obj, S32, const char **argv) +{ + GameConnection *con = static_cast(obj); + + S32 targetId = dAtoi(argv[2]); + if((targetId < -1) || (targetId >= TargetManager::MaxTargets)) + { + Con::errorf(ConsoleLogEntry::General, "GameConnection::cSetTargetId: invalid target id [%d]", targetId); + return; + } + con->setTargetId(targetId); +} + +static void cSetTargetPos(SimObject *obj, S32, const char **argv) +{ + GameConnection * con = static_cast(obj); + Point3F pos(0,0,0); + dSscanf(argv[2],"%f %f %f", &pos.x, &pos.y, &pos.z); + con->setTargetPos(pos); +} + +static bool cIsScopingCommanderMap(SimObject *obj, S32, const char **) +{ + GameConnection * con = static_cast(obj); + return(con->isScopingCommanderMap()); +} + +static void cScopeCommanderMap(SimObject *obj, S32, const char ** argv) +{ + GameConnection * con = static_cast(obj); + con->scopeCommanderMap(dAtob(argv[2])); +} + +// Camera: (fov in degrees) -------------------------------------------------- +static void cSetControlCameraFov(SimObject * obj, S32, const char ** argv) +{ + GameConnection * con = static_cast(obj); + con->setControlCameraFov(dAtoi(argv[2])); +} + +static F32 cGetControlCameraFov(SimObject * obj, S32, const char **) +{ + GameConnection * con = static_cast(obj); + F32 fov = 0.f; + if(!con->getControlCameraFov(&fov)) + return(0.f); + return(fov); +} + +//-------------------------------------------------------------------------- +static bool cListenEnabled( SimObject* obj, S32, const char** ) +{ + return( static_cast( obj )->listenEnabled() ); +} + +static bool cGetListenState(SimObject *obj, S32, const char **argv) +{ + GameConnection * me = (GameConnection *) obj; + GameConnection * him = dynamic_cast(Sim::findObject(argv[2])); + if(!him) + return(false); + + return(me->getListenState(him->getVoiceID())); +} + +static bool cCanListenTo(SimObject *obj, S32, const char **argv) +{ + GameConnection* me = (GameConnection*) obj; + GameConnection* him = dynamic_cast(Sim::findObject(argv[2])); + if ( !him ) + return( false ); + + return( me->canListen( him ) ); +} + +static void cListenTo(SimObject *obj, S32, const char **argv) +{ + GameConnection * me = (GameConnection *) obj; + GameConnection * him = dynamic_cast(Sim::findObject(argv[2])); + if(!him) + return; + + me->listenTo(him->getVoiceID(), dAtob(argv[3])); +} + +static void cListenToAll(SimObject *obj, S32, const char **argv) +{ + argv; + GameConnection *cptr = (GameConnection *) obj; + cptr->listenToAll(); +} + +static void cListenToNone(SimObject *obj, S32, const char **argv) +{ + argv; + GameConnection *cptr = (GameConnection *) obj; + cptr->listenToNone(); +} + +static void cSetVoiceChannels(SimObject *obj, S32, const char **argv) +{ + GameConnection *cptr = (GameConnection *) obj; + cptr->setVoiceChannels(dAtoi(argv[2])); +} + +static void cSetVoiceDecodingMask(SimObject *obj, S32, const char ** argv) +{ + GameConnection *cptr = (GameConnection *) obj; + cptr->setVoiceDecodingMask(dAtoi(argv[2])); +} + +static void cSetVoiceEncodingLevel(SimObject *obj, S32, const char ** argv) +{ + GameConnection *cptr = (GameConnection *) obj; + cptr->setVoiceEncodingLevel(dAtoi(argv[2])); +} + +//-------------------------------------------------------------------------- +static void cSetObjectActiveImage(SimObject * ptr, S32, const char ** argv) +{ + GameConnection * con = static_cast(ptr); + if(con->isServerConnection()) + return; + + ShapeBase * obj = dynamic_cast(Sim::findObject(argv[2])); + if(!obj) + { + Con::errorf(ConsoleLogEntry::General, "GameConnection::cSetObjectActiveImage: invalid object %s", argv[2]); + return; + } + + U32 imageSlot = U32(dAtoi(argv[3])); + if(imageSlot >= ShapeBase::MaxMountedImages) + { + Con::errorf(ConsoleLogEntry::General, "GameConnection::cSetControlObjectActiveImage: image slot out of range [%d]", imageSlot); + return; + } + + con->setObjectActiveImage(obj, imageSlot); +} + +static void cSetBlackOut(SimObject * ptr, S32, const char ** argv) +{ + GameConnection * con = static_cast(ptr); + con->setBlackOut(dAtob(argv[2]), dAtoi(argv[3])); +} + +static void cSetMissionCRC(SimObject * obj, S32, const char ** argv) +{ + GameConnection * con = static_cast(obj); + if(con->isServerConnection()) + return; + + con->postNetEvent(new SetMissionCRCEvent(dAtoi(argv[2]))); +} + +//-------------------------------------------------------------------------- +void GameConnection::consoleInit() +{ + Con::addVariable("firstPerson", TypeBool, &mFirstPerson); + Con::addVariable("pref::Net::lagThreshold", TypeS32, &mLagThresholdMS); + + Con::addCommand("GameConnection", "chaseCam", cChaseCam, "conn.chaseCam(size)", 3, 3); + + Con::addCommand("GameConnection", "setControlCameraFov", cSetControlCameraFov, "conn.setControlCameraFov(fov)", 3, 3); + Con::addCommand("GameConnection", "getControlCameraFov", cGetControlCameraFov, "conn.getControlCameraFov()", 2, 2); + + Con::addCommand("GameConnection", "setSensorGroup", cSetSensorGroup, "conn.setSensorGroup(groupId)", 3, 3); + Con::addCommand("GameConnection", "getSensorGroup", cGetSensorGroup, "conn.getSensorGroup()", 2, 2); + Con::addCommand("GameConnection", "transmitDataBlocks", cTransmitDataBlocks, "conn.transmitDataBlocks(seq)", 3, 3); + Con::addCommand("GameConnection", "activateGhosting", cActivateGhosting, "conn.activateGhosting()", 2, 2); + Con::addCommand("GameConnection", "resetGhosting", cResetGhosting, "conn.resetGhosting()", 2, 2); + Con::addCommand("GameConnection", "setControlObject", cSetControlObject, "conn.setControlObject(%obj)", 3, 3); + Con::addCommand("GameConnection", "getControlObject", cGetControlObject, "conn.getControlObject()", 2, 2); + Con::addCommand("GameConnection", "isAIControlled", cIsAIControlled, "conn.isAIControlled()", 2, 2); + Con::addCommand("GameConnection", "setObjectActiveImage", cSetObjectActiveImage, "conn.setObjectActiveImage(obj, imageSlot)", 4, 4); + + Con::addCommand("GameConnection", "play2D", cPlay2D, "conn.play2D(AudioProfile)", 3, 3); + Con::addCommand("GameConnection", "play3D", cPlay3D, "conn.play3D(AudioProfile,Transform)", 4, 4); + + Con::addCommand("GameConnection", "sendLOSTarget", cSendLOSTarget, "conn.sendLOSTarget()", 2, 2); + Con::addCommand("GameConnection", "sendTargetToServer", cSendTargetToServer, "conn.sendTargetToServer(id, pos)", 4, 4); + Con::addCommand("GameConnection", "sendTargetTo", cSendTargetTo, "conn.sendTargetTo(conn, assign)", 4, 4); + Con::addCommand("GameConnection", "getTargetId", cGetTargetId, "conn.getTargetId()", 2, 2); + Con::addCommand("GameConnection", "getTargetPos", cGetTargetPos, "conn.getTargetPos()", 2, 2); + Con::addCommand("GameConnection", "setTargetId", cSetTargetId, "conn.setTargetId(targetId)", 3, 3); + Con::addCommand("GameConnection", "setTargetPos", cSetTargetPos, "conn.setTargetPos(Point3F)", 3, 3); + Con::addCommand("GameConnection", "getHomingCount", cGetHomingCount, "conn.getHomingCount()", 2, 2); + + Con::addCommand("GameConnection", "isScopingCommanderMap", cIsScopingCommanderMap, "conn.isScopingCommanderMap()", 2, 2); + Con::addCommand("GameConnection", "scopeCommanderMap", cScopeCommanderMap, "conn.scopeCommanderMap(bool)", 3, 3); + + Con::addCommand("GameConnection", "listenEnabled", cListenEnabled, "conn.listenEnabled()", 2, 2); + Con::addCommand("GameConnection", "getListenState", cGetListenState, "conn.getListenState(clientId)", 3, 3); + Con::addCommand("GameConnection", "canListenTo", cCanListenTo, "conn.canListen(clientId)", 3, 3); + Con::addCommand("GameConnection", "listenTo", cListenTo, "conn.listenTo(clientId, true|false)", 4, 4); + Con::addCommand("GameConnection", "listenToAll", cListenToAll, "conn.listenToAll()", 2, 2); + Con::addCommand("GameConnection", "listenToNone", cListenToNone, "conn.listenToNone()", 2, 2); + Con::addCommand("GameConnection", "setVoiceChannels", cSetVoiceChannels, "conn.setVoiceChannels(0-3)", 3, 3); + Con::addCommand("GameConnection", "setVoiceDecodingMask", cSetVoiceDecodingMask, "conn.setVoiceDecodingMask(mask)", 3, 3); + Con::addCommand("GameConnection", "setVoiceEncodingLevel", cSetVoiceEncodingLevel, "conn.setVoiceEncodingLevel(codecLevel)", 3, 3); + + Con::addVariable("specialFog", TypeBool, &SceneGraph::useSpecial); + Con::addCommand("GameConnection", "setBlackOut", cSetBlackOut, "conn.setBlackOut(fadeTOBlackBool, timeMS)", 4, 4); + Con::addCommand("GameConnection", "setMissionCRC", cSetMissionCRC, "conn.setMissionCRC(crc)", 3, 3); +} + diff --git a/game/gameConnection.h b/game/gameConnection.h new file mode 100644 index 0000000..607c294 --- /dev/null +++ b/game/gameConnection.h @@ -0,0 +1,265 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMECONNECTION_H_ +#define _GAMECONNECTION_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _NETCONNECTION_H_ +#include "sim/netConnection.h" +#endif +#ifndef _MOVEMANAGER_H_ +#include "game/moveManager.h" +#endif +#ifndef _BITVECTOR_H_ +#include "core/bitVector.h" +#endif +#ifndef _TARGETMANAGER_H_ +#include "game/targetManager.h" +#endif + +enum { + MaxClients = 126, + DataBlockQueueCount = 16, + ControlStateSkipAmount = 16, +}; + +class AudioProfile; +class MatrixF; +class MatrixF; +class Point3F; +class MoveManager; +class ShapeBase; +struct Move; +struct AuthInfo; + +class GameConnection : public NetConnection +{ + typedef NetConnection Parent; + + enum Constants { + MoveCountBits = 5, + // MaxMoveCount should not exceed the MoveManager's + // own maximum (MaxMoveQueueSize) + MaxMoveCount = 30, + }; + typedef Vector MoveList; + + SimObjectPtr mControlObject; + bool mInCommanderMap; + bool mPinged; + bool mJammed; + U32 mDataBlockSequence; + char mDisconnectReason[256]; + + U32 mMissionCRC; // crc of the current mission file from the server + +private: + S32 mDataBlockModifiedKey; + S32 mMaxDataBlockModifiedKey; + + // Client side first/third person + static bool mFirstPerson; // Currently first person or not + 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 + + // + void moveWritePacket(BitStream *bstream); + void moveReadPacket(BitStream *bstream); + enum { + BlockTypeMove = NetConnectionBlockTypeCount, + GameConnectionBlockTypeCount + }; + + static S32 smVoiceConnections[MaxClients]; // voice<->connection mapping + U8 mVoiceID; // voice id for bitvectors + BitVector mWouldListenTo; // willing to listen to these clients + BitVector mListeningTo; // currently talking to me + U8 mMaxVoicechannels; // maximum number of people to listen to at one time + U8 mCurVoicechannels; // current number of people I'm listening to + U32 mVoiceDecodingMask; // the decoders this person can use + S32 mVoiceEncodingLevel; // what codec level this client is encoding at (-1 for not encoding) + + AUDIOHANDLE mLockAudioHandle; + AUDIOHANDLE mHomingAudioHandle; + AUDIOHANDLE mTargetLockedAudioHandle; + AUDIOHANDLE mTargetingAudioHandle; + +protected: + struct GamePacketNotify : public NetConnection::PacketNotify + { + U32 xorVisibleMask[TargetManager::TargetFreeMaskSize]; + U32 controlObjectModifyKey; + S32 cameraFov; + GamePacketNotify(); + }; + PacketNotify *allocNotify(); + U32 mTargetVisibleMask[TargetManager::TargetFreeMaskSize]; + U32 mLastMoveAck; + U32 mLastClientMove; + U32 mFirstMoveIndex; + U32 mMoveCredit; + U32 mControlObjectModifyKey; + U32 mLastSentControlObjectModifyKey; + U32 mAckedControlObjectModifyKey; + U32 mRemoteControlObjectModifyKey; + U32 mControlStateSkipCount; + + MoveList mMoveList; + bool mAIControlled; + AuthInfo *mAuthInfo; + + static S32 mLagThresholdMS; + S32 mLastPacketTime; + bool mLagging; + + U32 mSensorGroup; + S32 mTargetId; + Point3F mTargetPos; + + F32 mDamageFlash; + F32 mWhiteOut; + bool mSelfLocked; + bool mSelfHomed; + bool mSeekerTracking; + U32 mSeekerMode; + SimObjectPtr mSeekerObject; + + //the black out vars are not network transmitted, they are for local connections only... + F32 mBlackOut; + S32 mBlackOutTimeMS; + S32 mBlackOutStartTimeMS; + bool mFadeToBlack; + + // + 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); + void readDemoStartBlock(BitStream *stream); + void handleRecordedBlock(U32 type, U32 size, void *data); + +public: + DECLARE_CONOBJECT(GameConnection); + static void consoleInit(); + void handleGhostMessage(S32 message, U32 sequence, U32 ghostCount); + void setDisconnectReason(const char *reason); + GameConnection(bool ghostFrom = false, bool ghostTo = false, bool sendEvents = false); + ~GameConnection(); + + U32 getDataBlockSequence() { return mDataBlockSequence; } + void setDataBlockSequence(U32 seq) { mDataBlockSequence = seq; } + + bool onAdd(); + void onRemove(); + + static GameConnection *getServerConnection() { return dynamic_cast(mServerConnection); } + static GameConnection *getLocalClientConnection() { return dynamic_cast(mLocalClientConnection); } + + // Control object + void setControlObject(ShapeBase *co); + void setControlObjectDirty() { if(mControlObjectModifyKey == mLastSentControlObjectModifyKey) mControlObjectModifyKey++; } + ShapeBase* getControlObject() { return mControlObject; } + bool getControlCameraTransform(F32 dt,MatrixF* mat); + bool getControlCameraVelocity(Point3F *vel); + + void setObjectActiveImage(ShapeBase * obj, U32 slot); + + bool getControlCameraFov(F32 * fov); + bool setControlCameraFov(F32 fov); + bool isValidControlCameraFov(F32 fov); + void updateLockTones(); + void updateLockWarnings(); + void detectLag(); + + // Datablock management + S32 getDataBlockModifiedKey() { return mDataBlockModifiedKey; } + void setDataBlockModifiedKey(S32 key) { mDataBlockModifiedKey = key; } + S32 getMaxDataBlockModifiedKey() { return mMaxDataBlockModifiedKey; } + void setMaxDataBlockModifiedKey(S32 key) { mMaxDataBlockModifiedKey = key; } + + // presentation of flash and seekers + F32 getDamageFlash() { return mDamageFlash; } + F32 getWhiteOut() { return mWhiteOut; } + bool getSelfLocked() { return mSelfLocked; } + bool getSelfHomed() { return mSelfHomed; } + bool getSeekerTracking() { return mSeekerTracking; } + U32 getSeekerMode() { return mSeekerMode; } + ShapeBase *getSeekerObject() { return mSeekerObject; } + + void setBlackOut(bool fadeToBlack, S32 timeMS); + F32 getBlackOut(); + + // Move management + void pushMove(const Move &mv); + bool getNextMove(Move &curMove); + bool isBacklogged(); + virtual void getMoveList(Move**,U32* numMoves); + virtual void clearMoves(U32 count); + void collectMove(U32 simTime); + virtual bool areMovesPending(); + void incMoveCredit(U32 count); + + void setAuthInfo(const AuthInfo *info); + const AuthInfo *getAuthInfo(); + + // Sound + U8 getVoiceID() { return(mVoiceID); } + void play2D(const AudioProfile *profile); + void play3D(const AudioProfile *profile, const MatrixF *transform); + void listenTo(U8 clientId, bool tf); + void listenToAll(); + void listenToNone(); + void setVoiceChannels(S32 num); + U32 getVoiceDecodingMask() { return(mVoiceDecodingMask); } + void setVoiceDecodingMask(U32 mask) { mVoiceDecodingMask = mask; } + U32 getVoiceEncodingLevel() { return(mVoiceEncodingLevel); } + void setVoiceEncodingLevel(S32 level) { mVoiceEncodingLevel = level; } + + bool listenEnabled() { return( mMaxVoicechannels > 0 ); } + bool canListen(GameConnection *); + bool willListen(U8 voiceId); + void stopListening(U8 voiceId); + bool isListening(U8 voiceId) { return mListeningTo.test(voiceId); } + bool getListenState(U8 voiceId); + + // Misc. + bool isFirstPerson() { return mCameraPos == 0; } + bool isAIControlled() { return mAIControlled; } + + bool isScopingCommanderMap() const { return(mInCommanderMap); } + void scopeCommanderMap(const bool scope) {mInCommanderMap = scope;} + + void doneScopingScene(); + void demoPlaybackComplete(); + U32 getSensorGroup() { return mSensorGroup; } + void setSensorGroup(U32 group); + + bool sendLOSTarget(); + void sendTargetToServer(S32 targetId, Point3F pos); + void sendTargetTo(NetConnection *conn, bool assign); + void setServerTarget(S32 targetId, Point3F targetPos); + + S32 getTargetId() { return(mTargetId); } + Point3F getTargetPos() { return(mTargetPos); } + void setTargetId(S32 id) { mTargetId = id; } + void setTargetPos(const Point3F &pos) { mTargetPos = pos; } + + void resetVisibleMasks(); + + void setMissionCRC(U32 crc) { mMissionCRC = crc; } + U32 getMissionCRC() { return(mMissionCRC); } +}; + +#endif diff --git a/game/gameConnectionEvents.cc b/game/gameConnectionEvents.cc new file mode 100644 index 0000000..6fdb7e4 --- /dev/null +++ b/game/gameConnectionEvents.cc @@ -0,0 +1,301 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/consoleTypes.h" +#include "console/simBase.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "core/bitStream.h" +#include "sim/pathManager.h" +#include "audio/audio.h" +#include "game/targetManager.h" +#include "game/netDispatch.h" +#include "game/game.h" +#include "scenegraph/sceneGraph.h" +#include "game/gameConnectionEvents.h" + +//-------------------------------------------------------------------------- +IMPLEMENT_CO_NETEVENT_V1(SetSensorGroupEvent); + +//-------------------------------------------------------------------------- +IMPLEMENT_CO_NETEVENT_V1(SetServerTargetEvent); + +//-------------------------------------------------------------------------- +IMPLEMENT_CO_NETEVENT_V1(TargetToEvent); +IMPLEMENT_CO_CLIENTEVENT_V1(SimDataBlockEvent); +IMPLEMENT_CO_CLIENTEVENT_V1(Sim2DAudioEvent); +IMPLEMENT_CO_CLIENTEVENT_V1(Sim3DAudioEvent); +IMPLEMENT_CO_NETEVENT_V1(SetObjectActiveImageEvent); +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 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 // 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(); +// Con::printf("datablock delivered: %d, %d, %d", mIndex, mTotal, g->size()); + + if(mIndex == g->size() - 1) + { + gc->setDataBlockModifiedKey(gc->getMaxDataBlockModifiedKey()); + Con::executef(gc, 2, "dataBlocksDone", Con::getIntArg(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) +{ + 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(); + AssertFatal(classId >= DataBlockClassFirst && classId <= DataBlockClassLast, + "Out of range event class id... check consoleObject.h"); + bstream->writeInt(classId - DataBlockClassFirst, DataBlockClassBitSize); + bstream->writeInt(mIndex, DataBlockObjectIdBitSize); + bstream->writeInt(mTotal, DataBlockObjectIdBitSize + 1); + obj->packData(bstream); + } +} + +void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream) +{ + if(bstream->readFlag()) + { + mProcess = true; + id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst; + S32 classId = bstream->readInt(DataBlockClassBitSize) + DataBlockClassFirst; + mIndex = bstream->readInt(DataBlockObjectIdBitSize); + mTotal = bstream->readInt(DataBlockObjectIdBitSize + 1); + + SimObject* ptr = (SimObject *) ConsoleObject::create(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()"); + } + } +} + +void SimDataBlockEvent::write(NetConnection *, BitStream *bstream) +{ + if(bstream->writeFlag(mProcess)) + { + bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize); + S32 classId = mObj->getClassId(); + bstream->writeInt(classId - DataBlockClassFirst, DataBlockClassBitSize); + 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(3, "clientReceivedDataBlock", Con::getIntArg(mIndex), Con::getIntArg(mTotal)); + + SimDataBlock* obj; + char *errorBuffer = NetConnection::getErrorBuffer(); + + if (Sim::findObject(id,obj)) + { + U8 buf[1500]; + BitStream stream(buf, 1500); + mObj->packData(&stream); + stream.setPosition(0); + obj->unpackData(&stream); + obj->preload(false, errorBuffer); + AssertFatal(errorBuffer[0] == 0, errorBuffer); + } + else + { + bool reg = mObj->registerObject(id); + cptr->addObject(mObj); + bool load = mObj->preload(false, errorBuffer); + mObj = NULL; + AssertFatal(errorBuffer[0] == 0, errorBuffer); + } + } +} + + +//---------------------------------------------------------------------------- + + +Sim2DAudioEvent::Sim2DAudioEvent(const AudioProfile *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) + alxPlay(mProfile); +} + +//---------------------------------------------------------------------------- + +static F32 SoundPosAccuracy = 0.5; +static S32 SoundRotBits = 8; + + +Sim3DAudioEvent::Sim3DAudioEvent(const AudioProfile *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 paramaters, the orientation is + // transmitted as well. + Audio::Description* ad = &mProfile->mDescriptionObject->mDescription; + 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); + con->writeCompressed(bstream,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; + con->readCompressed(bstream,&pos,SoundPosAccuracy); + mTransform.setColumn(3, pos); +} + +void Sim3DAudioEvent::process(NetConnection *) +{ + if (mProfile) + alxPlay(mProfile, &mTransform); +} + diff --git a/game/gameConnectionEvents.h b/game/gameConnectionEvents.h new file mode 100644 index 0000000..9e4480f --- /dev/null +++ b/game/gameConnectionEvents.h @@ -0,0 +1,288 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMECONNECTIONEVENTS_H_ +#define _GAMECONNECTIONEVENTS_H_ + +class QuitEvent : public SimEvent +{ + void process(SimObject *) + { + Platform::postQuitMessage(0); + } +}; + +class SetSensorGroupEvent : public NetEvent +{ + U32 mSensorGroup; + public: + SetSensorGroupEvent(U32 sensGrp = 0) + { + mSensorGroup = sensGrp; + } + void pack(NetConnection *, BitStream *bstream) + { + bstream->writeInt(mSensorGroup, 5); + } + void write(NetConnection *con, BitStream *bstream) + { + pack(con, bstream); + } + void unpack(NetConnection *, BitStream *bstream) + { + mSensorGroup = bstream->readInt(5); + } + void process(NetConnection *con) + { + if(con->isServerConnection()) + ((GameConnection *) con)->setSensorGroup(mSensorGroup); + } + DECLARE_CONOBJECT(SetSensorGroupEvent); +}; + +class SetServerTargetEvent : public NetEvent +{ + S32 mTargetId; + Point3F mTargetPos; + + public: + SetServerTargetEvent(S32 targetId = -1, Point3F targetPos = Point3F(0,0,0)) + { + mTargetId = targetId; + mTargetPos = targetPos; + } + + void pack(NetConnection *, BitStream *bstream) + { + if(bstream->writeFlag(mTargetId != -1)) + bstream->writeInt(mTargetId, TargetManager::TargetIdBitSize); + bstream->write(mTargetPos.x); + bstream->write(mTargetPos.y); + bstream->write(mTargetPos.z); + } + + void write(NetConnection *conn, BitStream *bstream) + { + pack(conn, bstream); + } + void unpack(NetConnection *, BitStream *bstream) + { + if(bstream->readFlag()) + mTargetId = bstream->readInt(TargetManager::TargetIdBitSize); + bstream->read(&mTargetPos.x); + bstream->read(&mTargetPos.y); + bstream->read(&mTargetPos.z); + } + void process(NetConnection* conn) + { + ((GameConnection *)conn)->setServerTarget(mTargetId, mTargetPos); + } + DECLARE_CONOBJECT(SetServerTargetEvent); +}; + +class TargetToEvent : public NetEvent { + public: + S32 mTargetType; + S32 mTargetId; + bool mAssign; + Point3F mTargetPos; + SimObjectPtr mTarget; + + TargetToEvent(S32 targetId = -1, Point3F targetPos = Point3F(0,0,0), bool assign = false) + { + mTargetId = targetId; + mTargetPos = targetPos; + mAssign = assign; + } + void pack(NetConnection *conn, BitStream *bstream) + { + S32 ghostIndex = -1; + if(mTargetId != -1) + { + TargetInfo *inf = gTargetManager->getServerTarget(mTargetId); + if(bool(inf->targetObject)) + ghostIndex = conn->getGhostIndex(inf->targetObject); + } + if(bstream->writeFlag(mTargetId != -1)) + bstream->writeInt(mTargetId, TargetManager::TargetIdBitSize); + + if(bstream->writeFlag(ghostIndex == -1)) + { + bstream->write(mTargetPos.x); + bstream->write(mTargetPos.y); + bstream->write(mTargetPos.z); + } + bstream->writeFlag(mAssign); + } + void write(NetConnection *conn, BitStream *bstream) + { + conn; //unused + if(mTarget) + { + bstream->writeFlag(true); + bstream->writeInt(mTarget->getNetIndex(), NetConnection::GhostIdBitSize); + } + else + { + bstream->write(mTargetPos.x); + bstream->write(mTargetPos.y); + bstream->write(mTargetPos.z); + } + bstream->writeFlag(mAssign); + } + void unpack(NetConnection *conn, BitStream *bstream) + { + if(!conn->isServerConnection()) + { + conn->setLastError("Invalid Packet (TargetToEvent::unpack() connection id)"); + return; + } + if(bstream->readFlag()) + { + mTargetId = bstream->readInt(TargetManager::TargetIdBitSize); + TargetInfo *clTarget = gTargetManager->getClientTarget(mTargetId); + mTarget = clTarget->targetObject; + } + if(bstream->readFlag()) + { + bstream->read(&mTargetPos.x); + bstream->read(&mTargetPos.y); + bstream->read(&mTargetPos.z); + } + else if(bool(mTarget)) + { + Box3F box = mTarget->getWorldBox(); + mTargetPos = box.min + (box.max - box.min) * 0.5; + } + mAssign = bstream->readFlag(); + } + void process(NetConnection* conn) + { + conn; // unused + ClientTarget *targetObject = new ClientTarget(mTargetType, mTargetId, mTargetPos); + targetObject->registerObject(); + conn->addObject(targetObject); + targetObject->mType = mAssign ? ClientTarget::AssignedTask : ClientTarget::PotentialTask; + targetObject->process(); + } + DECLARE_CONOBJECT(TargetToEvent); +}; + +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 DEBUG_NET + const char *getDebugName(); +#endif + DECLARE_CONOBJECT(SimDataBlockEvent); +}; + +class Sim2DAudioEvent: public NetEvent +{ + private: + const AudioProfile *mProfile; + + public: + Sim2DAudioEvent(const AudioProfile *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: + const AudioProfile *mProfile; + MatrixF mTransform; + + public: + Sim3DAudioEvent(const AudioProfile *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); +}; + +//---------------------------------------------------------------------------- +// - An event to set the active image slot for a control object. This is needed +// for the targeting system to determing which weapon image is currently +// active on the server. +// - Clients reset the active image (set to 0) when control objects change +// (so the server only needs to send events on non-default image slots) +// - Not always the active image on THE control object.. could be down the control chain +//---------------------------------------------------------------------------- +class SetObjectActiveImageEvent : public NetEvent +{ + private: + U32 mImageSlot; + U32 mObjectId; + + public: + SetObjectActiveImageEvent(U32 objectId = 0, U32 imageSlot = 0) + { mObjectId = objectId, mImageSlot = imageSlot; } + void pack(NetConnection *, BitStream * bstream) + { + bstream->writeRangedU32(mObjectId, 0, NetConnection::MaxGhostCount-1); + bstream->writeRangedU32(mImageSlot, 0, ShapeBase::MaxMountedImages); + } + void write(NetConnection * con, BitStream * bstream) + { pack(con, bstream); } + void unpack(NetConnection *, BitStream * bstream) + { + mObjectId = bstream->readRangedU32(0, NetConnection::MaxGhostCount-1); + mImageSlot = bstream->readRangedU32(0, ShapeBase::MaxMountedImages); + } + void process(NetConnection * con) + { + ShapeBase * obj = dynamic_cast(con->resolveGhost(mObjectId)); + if(obj) + obj->setActiveImage(mImageSlot); + } + + DECLARE_CONOBJECT(SetObjectActiveImageEvent); +}; + +//---------------------------------------------------------------------------- +// 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/game/gameConnectionMoves.cc b/game/gameConnectionMoves.cc new file mode 100644 index 0000000..5d5271d --- /dev/null +++ b/game/gameConnectionMoves.cc @@ -0,0 +1,366 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/consoleTypes.h" +#include "console/simBase.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "core/bitStream.h" +#include "sim/pathManager.h" +#include "audio/audio.h" +#include "game/targetManager.h" +#include "game/netDispatch.h" +#include "game/game.h" +#include "sceneGraph/sceneGraph.h" +#include "game/gameConnectionEvents.h" + +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; + +U32 MoveManager::mTriggerCount[MaxTriggerKeys] = { 0, }; +U32 MoveManager::mPrevTriggerCount[MaxTriggerKeys] = { 0, }; + +#define MAX_MOVE_PACKET_SENDS 4 + +const Move NullMove = +{ + 16,16,16, + 0,0,0, + 0,0,0, // x,y,z + 0,0,0, // Yaw, pitch, roll, + 0,0, + false,false,false,false,false,false +}; + +void MoveManager::init() +{ + 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("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); + + 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 F32 clampFloatClamp(F32 val, U32 bits) +{ + if(val < 0) + val = 0; + else if(val > 1) + val = 1; + F32 mask = (1 << bits); + return U32(val * mask) / F32(mask); +} + +static inline S32 clampRangeClamp(F32 val) +{ + if(val < -1) + return 0; + if(val > 1) + return 32; + return (val + 1) * 16; +} + +void Move::unclamp() +{ + yaw = S16(pyaw) * M_2PI / F32(0x10000); + pitch = S16(ppitch) * M_2PI / F32(0x10000); + roll = S16(proll) * M_2PI / F32(0x10000); + + x = (px - 16) / F32(16); + y = (py - 16) / F32(16); + z = (pz - 16) / F32(16); +} + +void Move::clamp() +{ + // angles are all 16 bit. + pyaw = U32((yaw / M_2PI) * 0x10000) & 0xFFFF; + ppitch = U32((pitch / M_2PI) * 0x10000) & 0xFFFF; + proll = U32((roll / M_2PI) * 0x10000) & 0xFFFF; + + px = clampRangeClamp(x); + py = clampRangeClamp(y); + pz = clampRangeClamp(z); + unclamp(); +} + +void Move::pack(BitStream *stream) +{ + if(stream->writeFlag(pyaw != 0)) + stream->writeInt(pyaw, 16); + if(stream->writeFlag(ppitch != 0)) + stream->writeInt(ppitch, 16); + if(stream->writeFlag(proll != 0)) + stream->writeInt(proll, 16); + + stream->writeInt(px, 6); + stream->writeInt(py, 6); + stream->writeInt(pz, 6); + stream->writeFlag(freeLook); + + for(U32 i = 0; i < MaxTriggerKeys; i++) + stream->writeFlag(trigger[i]); +} + +void Move::unpack(BitStream *stream) +{ + if(stream->readFlag()) + pyaw = stream->readInt(16); + else + pyaw = 0; + if(stream->readFlag()) + ppitch = stream->readInt(16); + else + ppitch = 0; + if(stream->readFlag()) + proll = stream->readInt(16); + else + proll = 0; + px = stream->readInt(6); + py = stream->readInt(6); + pz = stream->readInt(6); + + unclamp(); + freeLook = stream->readFlag(); + + for(U32 i = 0; i < MaxTriggerKeys; i++) + trigger[i] = stream->readFlag(); +} + +bool GameConnection::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; + curMove.y = MoveManager::mForwardAction - MoveManager::mBackwardAction; + curMove.z = MoveManager::mUpAction - MoveManager::mDownAction; + + curMove.freeLook = MoveManager::mFreeLook; + + 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]; + } + curMove.clamp(); // clamp for net traffic + return true; +} + +void GameConnection::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; +} + +void GameConnection::getMoveList(Move** movePtr,U32* numMoves) +{ + if (isServerConnection()) + { + // 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() < mMoveCredit)? + mMoveList.size(): mMoveCredit; + *movePtr = mMoveList.begin(); + // + mMoveCredit -= *numMoves; + mMoveList.setSize(*numMoves); + } +} + +void GameConnection::collectMove(U32 time) +{ + Move mv; + if(!isPlayingBack() && getNextMove(mv)) + { + pushMove(mv); + recordBlock(time + 1, BlockTypeMove, sizeof(Move), &mv); + } +} + +void GameConnection::clearMoves(U32 count) +{ + if (isServerConnection()) { + mLastClientMove += count; + } + else { + AssertFatal(count <= mMoveList.size(),"GameConnection: Clearing too many moves"); + if (count == mMoveList.size()) + mMoveList.clear(); + else + while (count--) + mMoveList.pop_front(); + } +} + +void GameConnection::incMoveCredit(U32 ticks) +{ + AssertFatal(!isServerConnection(), "Cannot inc move credit on the client."); + // Game tick increment + if ((mMoveCredit += ticks) > MaxMoveCount) + mMoveCredit = MaxMoveCount; + + // Clear pending moves for the elapsed time if there + // is no control object. + if (!mControlObject) + mMoveList.clear(); +} + +bool GameConnection::areMovesPending() +{ + return isServerConnection() ? + mMoveList.size() - mLastClientMove + mFirstMoveIndex : + mMoveList.size(); +} + +bool GameConnection::isBacklogged() +{ + // If there are no pending moves and the input queue is full, + // then the connection to the server must be clogged. + if(!isServerConnection()) + return false; + return mLastClientMove - mFirstMoveIndex == mMoveList.size() && + mMoveList.size() >= MaxMoveCount; +} + + +void GameConnection::moveWritePacket(BitStream *bstream) +{ + Move* move; + U32 count; + AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index."); + count = mMoveList.size(); + 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); + for (int i = 0; i < count; i++) + { + move[offset + i].sendCount++; + move[offset + i].pack(bstream); + } +} + +void GameConnection::moveReadPacket(BitStream *bstream) +{ + // Server side packet read. + U32 start = bstream->readInt(32); + U32 count = bstream->readInt(MoveCountBits); + + // Skip forward (must be starting up), or over the moves + // we already have. + int skip = mLastMoveAck - start; + if (skip < 0) { + setControlObjectDirty(); + mLastMoveAck = start; + //mMoveList.clear(); + } + else { + Move tmp; + if (skip > count) + skip = count; + for (int i = 0; i < skip; i++) + tmp.unpack(bstream); + 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); + mMoveList[index].id = start++; + index ++; + } + + mLastMoveAck += count; +} + + diff --git a/game/gameFunctions.cc b/game/gameFunctions.cc new file mode 100644 index 0000000..0d9c8ca --- /dev/null +++ b/game/gameFunctions.cc @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "sim/sceneObject.h" +#include "core/fileStream.h" + +void RegisterGameFunctions(); + +// query +SimpleQueryList gServerQueryList; +U32 gServerQueryIndex = 0; + +//SERVER FUNCTIONS ONLY +static const char* cContainerFindFirst(SimObject *, S32, const char ** argv) +{ + //find out what we're looking for + U32 typeMask = U32(dAtoi(argv[1])); + + //find the center of the container volume + Point3F origin(0, 0, 0); + dSscanf(argv[1], "%f %f %f", &origin.x, &origin.y, &origin.z); + + //find the box dimensions + Point3F size(0, 0, 0); + 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.min = origin; + queryBox.max = origin; + queryBox.min -= size; + queryBox.max += size; + + //initialize the list, and do the query + gServerQueryList.mList.clear(); + gServerContainer.findObjects(queryBox, typeMask, SimpleQueryList::insertionCallback, S32(&gServerQueryList)); + + //return the first element + gServerQueryIndex = 0; + char *buff = Con::getReturnBuffer(100); + if (gServerQueryList.mList.size()) + dSprintf(buff, 100, "%d", gServerQueryList.mList[gServerQueryIndex++]->getId()); + else + buff[0] = '\0'; + + return buff; +} + +static const char* cContainerFindNext(SimObject *, S32, const char **) +{ + //return the next element + char *buff = Con::getReturnBuffer(100); + if (gServerQueryIndex < gServerQueryList.mList.size()) + dSprintf(buff, 100, "%d", gServerQueryList.mList[gServerQueryIndex++]->getId()); + else + buff[0] = '\0'; + + return buff; +} + + + +//----------------------------------------------------------------------------------- + +void RegisterGameFunctions() +{ + //NOTE: SERVER FUNCTIONS ONLY + Con::addCommand("containerFindFirst", cContainerFindFirst, "containerFindFirst(type, point, x, y, z)", 6, 6); + Con::addCommand("containerFindNext", cContainerFindNext, "containerFindNext()", 1, 1); +} diff --git a/game/gameFunctions.h b/game/gameFunctions.h new file mode 100644 index 0000000..32acdb8 --- /dev/null +++ b/game/gameFunctions.h @@ -0,0 +1,13 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMEFUNCTIONS_H_ +#define _GAMEFUNCTIONS_H_ + +void RegisterGameFunctions(); + +#endif diff --git a/game/gameProcess.cc b/game/gameProcess.cc new file mode 100644 index 0000000..1f09c21 --- /dev/null +++ b/game/gameProcess.cc @@ -0,0 +1,248 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "core/dnet.h" +#include "game/gameConnection.h" +#include "game/gameBase.h" +#include "game/shapeBase.h" +#include "game/targetManager.h" +#include "platform/profiler.h" + +//---------------------------------------------------------------------------- + +ProcessList gClientProcessList; +ProcessList gServerProcessList; + +ProcessList::ProcessList() +{ + mDirty = false; + mCurrentTag = 0; + mLastTick = 0; + mLastTime = 0; + mLastDelta = 0; +} + + +//---------------------------------------------------------------------------- + +void ProcessList::orderList() +{ + // GameBase tags are intialized to 0, so current tag + // should never be 0. + if (!++mCurrentTag) + mCurrentTag++; + + // Install a temporary head node + GameBase list; + list.plLinkBefore(head.mProcessLink.next); + head.plUnlink(); + + // Reverse topological sort into the orignal head node + while (list.mProcessLink.next != &list) { + GameBase* ptr = list.mProcessLink.next; + ptr->mProcessTag = mCurrentTag; + ptr->plUnlink(); + if (ptr->mAfterObject) { + // Build chain "stack" of dependant objects and patch + // it to the end of the current list. + while (bool(ptr->mAfterObject) && + ptr->mAfterObject->mProcessTag != mCurrentTag) { + ptr->mAfterObject->mProcessTag = mCurrentTag; + ptr->mAfterObject->plUnlink(); + ptr->mAfterObject->plLinkBefore(ptr); + ptr = ptr->mAfterObject; + } + ptr->plJoin(&head); + } + else + ptr->plLinkBefore(&head); + } + mDirty = false; + + /* + GameBase *walk = head.mProcessLink.next; + Con::printf("Sorted process list."); + while(walk != &head) + { + if (walk->mTypeMask & ShapeBaseObjectType) + { + ShapeBase *sb = (ShapeBase *) walk; + if(sb->isMounted()) + Con::printf(" %s %d: mounted to %d", sb->getClassName(), sb->getId(), sb->getObjectMount()->getId()); + if(sb->getMountedObjectCount()) + { + Con::printf(" %s %d: has %d mounted objects", sb->getClassName(), sb->getId(), sb->getMountedObjectCount()); + for(U32 i = 0; i < sb->getMountedObjectCount(); i++) + Con::printf(" %s %d", sb->getMountedObject(i)->getClassName(),sb->getMountedObject(i)->getId()); + } + if(sb->getControllingObject() != NULL) + Con::printf(" %s %d: controlled by %d", sb->getClassName(), sb->getId(), sb->getControllingObject()->getId()); + if(sb->getProcessAfter()) + Con::printf(" %s %d: processes after %d", sb->getClassName(), sb->getId(), sb->getProcessAfter()->getId()); + } + walk = walk->mProcessLink.next; + }*/ +} + + +//---------------------------------------------------------------------------- + +void ProcessList::advanceServerTime(SimTime timeDelta) +{ + PROFILE_START(AdvanceServerTime); + + if (mDirty) orderList(); + + SimTime targetTime = mLastTime + timeDelta; + SimTime targetTick = targetTime & ~TickMask; + SimTime tickCount = (targetTick - mLastTick) >> TickShift; + + // Advance all the objects + for (; mLastTick != targetTick; mLastTick += TickMs) + { + gTargetManager->tickSensorState(); + 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->incMoveCredit(tickCount); + + mLastTime = targetTime; + PROFILE_END(); +} + + +//---------------------------------------------------------------------------- + +void ProcessList::advanceClientTime(SimTime timeDelta) +{ + PROFILE_START(AdvanceClientTime); + + if (mDirty) orderList(); + + SimTime targetTime = mLastTime + timeDelta; + SimTime targetTick = (targetTime + TickMask) & ~TickMask; + SimTime tickCount = (targetTick - mLastTick) >> TickShift; + + // See if the control object has pending moves. + GameBase* control = 0; + GameConnection* connection = GameConnection::getServerConnection(); + if (connection) { + // If the connection to the server is backlogged + // the simulation is frozen. + if (connection->isBacklogged()) + { + mLastTime = targetTime; + mLastTick = targetTick; + PROFILE_END(); + return; + } + if (connection->areMovesPending()) + control = connection->getControlObject(); + } + + // If we are going to tick, or have moves pending for the + // control object, we need to reset everyone back to their + // last full tick pos. + if (mLastDelta && (tickCount || control)) + { + for (GameBase* obj = head.mProcessLink.next; obj != &head; + obj = obj->mProcessLink.next) + { + if (obj->mProcessTick) + obj->interpolateTick(0); + } + } + + // Produce new moves and advance all the objects + if (tickCount) { + for (; mLastTick != targetTick; mLastTick += TickMs) { + if(connection) + connection->collectMove(mLastTick); + advanceObjects(); + } + } + else + { + if (control) { + // Sync up the control object with the latest client moves. + Move* movePtr; + U32 m = 0, numMoves; + connection->getMoveList(&movePtr, &numMoves); + while (m < numMoves) + control->processTick(&movePtr[m++]); + connection->clearMoves(m); + } + } + + mLastDelta = (TickMs - (targetTime & TickMask)) & TickMask; + F32 dt = mLastDelta / F32(TickMs); + for (GameBase* obj = head.mProcessLink.next; obj != &head; + obj = obj->mProcessLink.next) + { + if (obj->mProcessTick) + obj->interpolateTick(dt); + } + + // Inform objects of total elapsed delta so they can advance + // client side animations. + dt = F32(timeDelta) / 1000; + for (GameBase* obj = head.mProcessLink.next; obj != &head; + obj = obj->mProcessLink.next) + { + obj->advanceTime(dt); + } + + // update the hud targets + gTargetList->update(targetTime); + + mLastTime = targetTime; + + PROFILE_END(); +} + + +//---------------------------------------------------------------------------- + +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. + GameBase list; + GameBase* obj; + list.plLinkBefore(head.mProcessLink.next); + head.plUnlink(); + while ((obj = list.mProcessLink.next) != &list) + { + obj->plUnlink(); + obj->plLinkBefore(&head); + + // Each object is either advanced a single tick, or if it's + // being controlled by a client, ticked once for each pending move. + if (obj->mTypeMask & ShapeBaseObjectType) { + ShapeBase* pSB = static_cast(obj); + GameConnection* con = pSB->getControllingClient(); + if (con && con->getControlObject() == pSB) { + Move* movePtr; + U32 m,numMoves; + con->getMoveList(&movePtr, &numMoves); + for (m = 0; m < numMoves && pSB->getControllingClient() == con; ) + obj->processTick(&movePtr[m++]); + con->clearMoves(m); + continue; + } + } + if (obj->mProcessTick) + obj->processTick(0); + } + PROFILE_END(); +} diff --git a/game/gameTSCtrl.cc b/game/gameTSCtrl.cc new file mode 100644 index 0000000..a3b0154 --- /dev/null +++ b/game/gameTSCtrl.cc @@ -0,0 +1,394 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/gameTSCtrl.h" +#include "console/consoleTypes.h" +#include "game/projectile.h" +#include "game/gameBase.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "game/player.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() +{ + // field data + mBeaconBaseTextureName = StringTable->insert("gui/beacon_base"); + mBeaconTargetTextureName = StringTable->insert("gui/crosshairs"); + mBeaconTargetPeriod = 4000; + mBeaconsVisible = true; + mBeaconLineWidth = 2.5f; + mBeaconLineBeginColor = ColorF(0.f, 1.f, 0.f, 0.2f); + mBeaconLineEndColor = ColorF(0.f, 1.f, 0.f, 0.8f); + mVehicleBeaconBeginColor = ColorF(1.f, 0.f, 0.f, 0.2f); + mVehicleBeaconEndColor = ColorF(1.f, 0.f, 0.f, 0.8f); + mFriendBeaconBeginColor = ColorF(1.f, 1.f, 0.f, 0.2f); + mFriendBeaconEndColor = ColorF(1.f, 1.f, 0.f, 0.8f); + mBeaconTextYOffset = 14; + mShowAlternateTarget = false; +} + +bool GameTSCtrl::onWake() +{ + if(!Parent::onWake()) + return(false); + + mBeaconTargetTexture = TextureHandle(mBeaconTargetTextureName); + mBeaconBaseTexture = TextureHandle(mBeaconBaseTextureName); + return(true); +} + +void GameTSCtrl::onSleep() +{ + Parent::onSleep(); + mBeaconTargetTexture = 0; + mBeaconBaseTexture = 0; +} + +//--------------------------------------------------------------------------- +bool GameTSCtrl::processCameraQuery(CameraQuery *camq) +{ + GameUpdateCameraFov(); + return GameProcessCameraQuery(camq); +} + +//--------------------------------------------------------------------------- +void GameTSCtrl::renderBeacon(const GameBase * beaconObj, const Point3F & eyePos, + const Point3F & sourcePos, const VectorF & sourceVel, + const ProjectileData * projectile, + const bool pilot, const F32 pilotHeight) +{ + Point3F pos; + beaconObj->getTransform().getColumn(3, &pos); + ShapeBase * shBeacon = static_cast(const_cast(beaconObj)); + mBeaconTextPos.push_back(Point3F(pos.x, pos.y, (pilot) ? pilotHeight : pos.z)); + + Point3F dir = Point3F(pos - eyePos); + if( dir.isZero() ) return; + dir.normalize(); + + F32 width = F32(mBeaconBaseTexture.getWidth()) * (100.f * mTan(mDegToRad(GameGetCameraFov()/2.f))) / F32(getExtent().x); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, mBeaconBaseTexture.getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + ColorF beginColor(mBeaconLineBeginColor); + ColorF endColor(mBeaconLineEndColor); + S32 beaconType = shBeacon->getBeaconType(); + if(beaconType == GameBase::vehicleBeacon) + { + beginColor = mVehicleBeaconBeginColor; + endColor = mVehicleBeaconEndColor; + } + else if(beaconType == GameBase::friendBeacon) + { + beginColor = mFriendBeaconBeginColor; + endColor = mFriendBeaconEndColor; + } + + glColor4f(beginColor.red, beginColor.green, + beginColor.blue, beginColor.alpha); + + dglDrawBillboard(eyePos + dir * 100.f, width, 0.f); + + // targeted image: + if(!projectile && !pilot || (beaconType == GameBase::friendBeacon)) + return; + + VectorF vector[2]; + F32 time[2]; + Point3F hit; + + if(!pilot) + { + // solveQuartic has problems if both velocities are |0| + Point3F vel = beaconObj->getVelocity(); + Point3F srcVel = sourceVel; + if(vel.isZero() && srcVel.isZero()) + { + vel.set(1.f,0.f,0.f); + srcVel.set(1.f,0.f,0.f); + } + + // can reach it? + if(!(const_cast(projectile))->calculateAim(pos, vel, sourcePos, srcVel, + &vector[0], &time[0], &vector[1], &time[1])) + return; + + if(mShowAlternateTarget) + { + vector[0] = vector[1]; + time[0] = time[1]; + } + + // just show the min time target: + dir = Point3F(pos - sourcePos); + if( dir.isZero() ) return; + dir.normalize(); + + PlaneF plane(sourcePos + dir * 100.f, -vector[0]); + F32 dist = plane.distToPlane(sourcePos); + + hit = sourcePos + (eyePos - sourcePos) + vector[0] * dist; + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, mBeaconTargetTexture.getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + F32 rot = 0.f; + if(mBeaconTargetPeriod > 0) + rot = (F32(Sim::getCurrentTime() % mBeaconTargetPeriod) * 2.f * M_PI) / F32(mBeaconTargetPeriod); + + width = F32(mBeaconTargetTexture.getWidth()) * (dist * mTan(mDegToRad(GameGetCameraFov()/2.f))) / F32(getExtent().x); + glColor4f(beginColor.red, beginColor.green, + beginColor.blue, beginColor.alpha); + + dglDrawBillboard(hit, width, rot); + } + else + { + width = 5; + dglDrawBillboard(Point3F(pos.x, pos.y, pilotHeight), width, 0.f); + hit.set(pos.x, pos.y, pilotHeight - (width * 0.31f)); + } + + // render a line: + glDisable(GL_TEXTURE_2D); + glLineWidth(mBeaconLineWidth); + + glBegin(GL_LINES); + glColor4f(beginColor.red, beginColor.green, + beginColor.blue, beginColor.alpha); + glVertex3f(pos.x, pos.y, pos.z); + + glColor4f(beginColor.red, beginColor.green, + beginColor.blue, beginColor.alpha); + glVertex3f(hit.x, hit.y, hit.z); + glEnd(); + + glLineWidth(1.f); +} + +void GameTSCtrl::renderBeacons() +{ + if(!mBeaconsVisible) + return; + + mBeaconTextPos.clear(); + + if(!bool(mBeaconBaseTexture) || !bool(mBeaconTargetTexture)) + return; + + GameConnection * gc = GameConnection::getServerConnection(); + if(!gc) + return; + + U32 sensorGroup = gc->getSensorGroup(); + + // make sure firstperson... + if(!gc->isFirstPerson()) + return; + + // make sure its a player? + Player * player = static_cast(gc->getControlObject()); + if(!player) + return; + + F32 pilotHeight = 0.0f; + if(player->isPilot()) + { + Point3F pilotPos; + player->getTransform().getColumn(3, &pilotPos); + pilotHeight = pilotPos.z; + } + + // grab the control object + ShapeBase * controlObj = player->getControlObject(); + if(!controlObj) + controlObj = player; + + // grab the weapon image: + ShapeBaseImageData * weaponImage = controlObj->getMountedImage(controlObj->getActiveImage()); + + ProjectileData * projectile = 0; + if(weaponImage) + projectile = weaponImage->projectile; + + // get the source point: + Point3F muzzlePos; + controlObj->getMuzzlePoint(0, &muzzlePos); + + // get the eye point; + MatrixF mat; + controlObj->getEyeTransform(&mat); + + Point3F eyePos; + mat.getColumn(3, &eyePos); + + SimSet * targetSet = Sim::getClientTargetSet(); + for(SimSet::iterator itr = targetSet->begin(); itr != targetSet->end(); ++itr) + { + GameBase * target = dynamic_cast(*itr); + if(!target || !target->isBeacon()) + continue; + + S32 targId = target->getTarget(); + if((targId != -1) && (gTargetManager->getTargetSensorGroup(targId) == sensorGroup)) + renderBeacon(target, eyePos, muzzlePos, controlObj->getVelocity(), projectile, player->isPilot(), pilotHeight); + } +} + +void GameTSCtrl::renderBeaconsText() +{ + if(!mBeaconsVisible || !mBeaconTextPos.size()) + return; + + GameConnection * gc = GameConnection::getServerConnection(); + if(!gc) + return; + + // grab the control object + ShapeBase * controlObj = gc->getControlObject(); + if(!controlObj) + return; + + // get the eye point; + MatrixF mat; + controlObj->getEyeTransform(&mat); + + Point3F eyePos; + mat.getColumn(3, &eyePos); + + U32 height = mProfile->mFont->getHeight(); + + Point3F sPos; + for(U32 i = 0; i < mBeaconTextPos.size(); i++) + { + if(project(mBeaconTextPos[i], &sPos)) + { + F32 dist = Point3F(mBeaconTextPos[i] - eyePos).len(); + dist = mClampF(dist, 0.f, 999.9f); + + char buf[40]; + dSprintf(buf, sizeof(buf), "%3.1f", dist); + + U32 width = mProfile->mFont->getStrWidth(buf); + + dglClearBitmapModulation(); + + Point2I pos(S32(sPos.x - F32(width / 2)), S32(sPos.y - F32(height / 2)) + mBeaconTextYOffset); + dglDrawText(mProfile->mFont, pos, buf); + } + } +} + +//--------------------------------------------------------------------------- +static bool gHudHackCalled = false; +void GameTSCtrl::renderWorld(const RectI &updateRect) +{ +//#pragma message("E3 hack here to reshow the HUD elements after loading is complete."); + if (! gHudHackCalled) + { + gHudHackCalled = true; + Con::executef(2, "HideHudHACK", "true"); + } + + GameRenderWorld(); + + renderBeacons(); + dglSetClipRect(updateRect); + renderBeaconsText(); +} + +//--------------------------------------------------------------------------- +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(evt.mousePoint.x, evt.mousePoint.y, -1); + 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, GuiControl *firstResponder) +{ + // check if should bother with a render + GameConnection * con = GameConnection::getServerConnection(); + bool skipRender = !con || (con->getWhiteOut() >= 1.f) || (con->getDamageFlash() >= 1.f) || (con->getBlackOut() >= 1.f); + + if(!skipRender) + { + Parent::onRender(offset, updateRect, firstResponder); + renderChildControls(offset, updateRect, firstResponder); + } + + dglSetViewport(updateRect); + CameraQuery camq; + if(GameProcessCameraQuery(&camq)) + GameRenderFilters(camq); +} + +//--------------------------------------------------------------------------- +void GameTSCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("beaconBaseTextureName", TypeString, Offset(mBeaconBaseTextureName, GameTSCtrl)); + addField("beaconTargetTextureName", TypeString, Offset(mBeaconTargetTextureName, GameTSCtrl)); + addField("beaconTargetPeriod", TypeS32, Offset(mBeaconTargetPeriod, GameTSCtrl)); + addField("beaconsVisible", TypeBool, Offset(mBeaconsVisible, GameTSCtrl)); + addField("enemyBeaconLineBeginColor", TypeColorF, Offset(mBeaconLineBeginColor, GameTSCtrl)); + addField("enemyBeaconLineEndColor", TypeColorF, Offset(mBeaconLineEndColor, GameTSCtrl)); + addField("vehicleBeaconLineBeginColor", TypeColorF, Offset(mVehicleBeaconBeginColor, GameTSCtrl)); + addField("vehicleBeaconLineEndColor", TypeColorF, Offset(mVehicleBeaconEndColor, GameTSCtrl)); + addField("friendBeaconLineBeginColor", TypeColorF, Offset(mFriendBeaconBeginColor, GameTSCtrl)); + addField("friendBeaconLineEndColor", TypeColorF, Offset(mFriendBeaconEndColor, GameTSCtrl)); + addField("beaconLineWidth", TypeF32, Offset(mBeaconLineWidth, GameTSCtrl)); + addField("beaconTextYOffset", TypeS32, Offset(mBeaconTextYOffset, GameTSCtrl)); + addField("showAlternateTarget", TypeBool, Offset(mShowAlternateTarget, GameTSCtrl)); +} + +//-------------------------------------------------------------------------- +void cSnapToggle(SimObject *, S32, const char **) +{ + gSnapLine = !gSnapLine; +} + +void GameTSCtrl::consoleInit() +{ + Con::addCommand("snapToggle", cSnapToggle, "snapToggle();", 1, 1); +} diff --git a/game/gameTSCtrl.h b/game/gameTSCtrl.h new file mode 100644 index 0000000..d268fe3 --- /dev/null +++ b/game/gameTSCtrl.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMETSCTRL_H_ +#define _GAMETSCTRL_H_ + +#ifndef _DGL_H_ +#include "dgl/dgl.h" +#endif +#ifndef _GAME_H_ +#include "game/game.h" +#endif +#ifndef _GUITSCONTROL_H_ +#include "gui/guiTSControl.h" +#endif + +class ProjectileData; +class GameBase; + +//---------------------------------------------------------------------------- +class GameTSCtrl : public GuiTSCtrl +{ + typedef GuiTSCtrl Parent; + + TextureHandle mBeaconBaseTexture; + TextureHandle mBeaconTargetTexture; + +public: + StringTableEntry mBeaconBaseTextureName; + StringTableEntry mBeaconTargetTextureName; + S32 mBeaconTargetPeriod; + bool mBeaconsVisible; + ColorF mBeaconLineBeginColor; + ColorF mBeaconLineEndColor; + ColorF mVehicleBeaconBeginColor; + ColorF mVehicleBeaconEndColor; + ColorF mFriendBeaconBeginColor; + ColorF mFriendBeaconEndColor; + F32 mBeaconLineWidth; + S32 mBeaconTextYOffset; + bool mShowAlternateTarget; + +private: + Vector mBeaconTextPos; + + void renderBeacon(const GameBase * beaconObj, const Point3F & eyePos, + const Point3F & sourcePos, const VectorF & sourceVel, + const ProjectileData * projectile, const bool pilot, + const F32 pilotHeight); + + void renderBeaconsText(); + void renderBeacons(); + +public: + GameTSCtrl(); + + bool processCameraQuery(CameraQuery *query); + void renderWorld(const RectI &updateRect); + + bool onWake(); + void onSleep(); + + void onMouseMove(const GuiEvent &evt); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + + static void initPersistFields(); + static void consoleInit(); + + DECLARE_CONOBJECT(GameTSCtrl); +}; + +#endif diff --git a/game/guiNoMouseCtrl.cc b/game/guiNoMouseCtrl.cc new file mode 100644 index 0000000..739aaec --- /dev/null +++ b/game/guiNoMouseCtrl.cc @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiControl.h" + +//------------------------------------------------------------------------------ +class GuiNoMouseCtrl : public GuiControl +{ + public: + + // GuiControl + bool pointInControl(const Point2I &) { return(false); } + DECLARE_CONOBJECT(GuiNoMouseCtrl); +}; +IMPLEMENT_CONOBJECT(GuiNoMouseCtrl); diff --git a/game/guiPlayerView.cc b/game/guiPlayerView.cc new file mode 100644 index 0000000..a4f564a --- /dev/null +++ b/game/guiPlayerView.cc @@ -0,0 +1,336 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "gui/guiCanvas.h" +#include "game/guiPlayerView.h" +#include "console/consoleTypes.h" + +static const F32 MaxOrbitDist = 5.0f; +static const S32 MaxAnimations = 6; + +IMPLEMENT_CONOBJECT( GuiPlayerView ); + +//------------------------------------------------------------------------------ +GuiPlayerView::GuiPlayerView() : GuiTSCtrl() +{ + mActive = true; + mMouseState = None; + mModel = NULL; + mWeapon = NULL; + mLastMousePoint.set( 0, 0 ); + lastRenderTime = 0; + runThread = 0; + wNode = -1; + pNode = -1; + mAnimationSeq = 0; +} + + +//------------------------------------------------------------------------------ +GuiPlayerView::~GuiPlayerView() +{ + if ( mModel ) + { + delete mModel; + mModel = NULL; + } + if ( mWeapon ) + { + delete mWeapon; + mWeapon = NULL; + } +} + + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPlayerView, setModel, void, 4, 4, "playerView.setModel( raceGender, skin )" ) +{ + argc; + GuiPlayerView* view = static_cast( object ); + view->setPlayerModel( argv[2], argv[3] ); +} + +ConsoleMethod( GuiPlayerView, setSeq, void, 3, 3, "playerView.setSeq( index )" ) +{ + argc; + GuiPlayerView* view = static_cast( object ); + view->setPlayerSeq( dAtoi(argv[2]) ); +} + + +//------------------------------------------------------------------------------ +void GuiPlayerView::consoleInit() +{ +} + + +//------------------------------------------------------------------------------ +bool GuiPlayerView::onWake() +{ + if ( !Parent::onWake() ) + return( false ); + + mCameraMatrix.identity(); + mCameraRot.set( 0, 0, 3.9 ); + mCameraPos.set( 0, 1.75, 1.25 ); + mCameraMatrix.setColumn( 3, mCameraPos ); + mOrbitPos.set( 0, 0, 0 ); + mOrbitDist = 3.5f; + + return( true ); +} + +//------------------------------------------------------------------------------ +void GuiPlayerView::onMouseDown( const GuiEvent &event ) +{ + if ( !mActive || !mVisible || !mAwake ) + return; + + mMouseState = Rotating; + mLastMousePoint = event.mousePoint; + mouseLock(); +} + + +//------------------------------------------------------------------------------ +void GuiPlayerView::onMouseUp( const GuiEvent &/*event*/ ) +{ + mouseUnlock(); + mMouseState = None; +} + + +//------------------------------------------------------------------------------ +void GuiPlayerView::onMouseDragged( const GuiEvent &event ) +{ + if ( mMouseState != Rotating ) + return; + + Point2I delta = event.mousePoint - mLastMousePoint; + mLastMousePoint = event.mousePoint; + + mCameraRot.x += ( delta.y * 0.01 ); + mCameraRot.z += ( delta.x * 0.01 ); +} + + +//------------------------------------------------------------------------------ +void GuiPlayerView::onRightMouseDown( const GuiEvent &event ) +{ + mMouseState = Zooming; + mLastMousePoint = event.mousePoint; + mouseLock(); +} + + +//------------------------------------------------------------------------------ +void GuiPlayerView::onRightMouseUp( const GuiEvent &/*event*/ ) +{ + mouseUnlock(); + mMouseState = None; +} + + +//------------------------------------------------------------------------------ +void GuiPlayerView::onRightMouseDragged( const GuiEvent &event ) +{ + if ( mMouseState != Zooming ) + return; + + S32 delta = event.mousePoint.y - mLastMousePoint.y; + mLastMousePoint = event.mousePoint; + + mOrbitDist += ( delta * 0.01 ); +} + +void GuiPlayerView::setPlayerSeq( S32 index ) +{ + if( index > MaxAnimations || index < 0 ) + return; + + mAnimationSeq = index; +} + +//------------------------------------------------------------------------------ +void GuiPlayerView::setPlayerModel( const char* shape, const char* skin ) +{ + if ( mModel ) + { + delete mModel; + mModel = NULL; + } + + if ( mWeapon ) + { + delete mWeapon; + mWeapon = NULL; + } + + runThread = 0; + + char fileBuffer[256]; + dSprintf( fileBuffer, sizeof( fileBuffer ), "shapes/%s.dts", shape ); + + Resource hShape = ResourceManager->load( fileBuffer ); + if ( !bool( hShape ) ) + return; + + mModel = new TSShapeInstance( hShape, true ); + AssertFatal( mModel, "ERROR! Failed to load warrior model!" ); + + // Set the skin: + if ( !mModel->ownMaterialList() ) + mModel->cloneMaterialList(); + + TSMaterialList* materialList = mModel->getMaterialList(); + for ( U32 i = 0; i < materialList->mMaterialNames.size(); i++ ) + { + const char* name = materialList->mMaterialNames[i]; + if ( !name ) + continue; + + const U32 len = dStrlen( name ); + AssertFatal( len < 200, "GuiPlayerView::setPlayerModel - Skin name exceeds maximum length!" ); + if ( len < 6 ) + continue; + + const char* replace = dStrstr( name, "base." ); + if ( !replace ) + continue; + + char newName[256]; + dStrncpy( newName, name, replace - name ); + newName[replace - name] = 0; + dStrcat( newName, skin ); + dStrcat( newName, "." ); + dStrcat( newName, replace + 5 ); + + TextureHandle test = TextureHandle( newName, MeshTexture, false ); + if ( test.getGLName() ) + materialList->mMaterials[i] = test; + else + materialList->mMaterials[i] = TextureHandle( name, MeshTexture, false ); + } + + // Initialize camera values: + mOrbitPos = mModel->getShape()->center; + mMinOrbitDist = mModel->getShape()->radius; + +// // initialize run thread +// S32 sequence = hShape->findSequence("dummyRun"); +// +// if( sequence != -1 ) +// { +// runThread = mModel->addThread(); +// mModel->setPos( runThread, 0 ); +// mModel->setTimeScale( runThread, 1 ); +// mModel->setSequence( runThread, sequence, 0 ); +// } + + // create a weapon for this dude + Resource wShape; + if( dStrcmp( shape, "heavy_male") == 0 || dStrcmp( shape, "bioderm_heavy") == 0 || dStrcmp( shape, "heavy_female") == 0 ) + { + wShape = ResourceManager->load( "shapes/weapon_mortar.dts" ); + } + else if( dStrcmp( shape, "medium_male") == 0 || dStrcmp( shape, "bioderm_medium") == 0 || dStrcmp( shape, "medium_female") == 0 ) + { + wShape = ResourceManager->load( "shapes/weapon_plasma.dts" ); + } + else + { + wShape = ResourceManager->load( "shapes/weapon_disc.dts" ); + } + + if ( !bool( wShape ) ) + return; + + pNode = hShape->findNode("mount0"); + wNode = wShape->findNode("mountPoint"); + + mWeapon = new TSShapeInstance( wShape, true ); + AssertFatal( mWeapon, "ERROR! Failed to load Weapon Model!" ); + + // the first time recording + lastRenderTime = Platform::getVirtualMilliseconds(); +} + +void GuiPlayerView::getWeaponTransform( MatrixF *mat ) +{ + MatrixF weapTrans = mWeapon->mNodeTransforms[wNode]; + Point3F weapOffset = -weapTrans.getPosition(); + MatrixF modelTrans = mModel->mNodeTransforms[pNode]; + modelTrans.mulP( weapOffset ); + modelTrans.setPosition( weapOffset ); + *mat = modelTrans; +} + +//------------------------------------------------------------------------------ +bool GuiPlayerView::processCameraQuery( CameraQuery* query ) +{ + // Make sure the orbit distance is within the acceptable range: + mOrbitDist = ( mOrbitDist < mMinOrbitDist ) ? mMinOrbitDist : ( ( mOrbitDist > MaxOrbitDist ) ? MaxOrbitDist : mOrbitDist ); + + // Adjust the camera so that we are still facing the model: + Point3F vec; + MatrixF xRot, zRot; + xRot.set( EulerF( mCameraRot.x, 0, 0 ) ); + zRot.set( EulerF( 0, 0, mCameraRot.z ) ); + + mCameraMatrix.mul( zRot, xRot ); + mCameraMatrix.getColumn( 1, &vec ); + vec *= mOrbitDist; + mCameraPos = mOrbitPos - vec; + + query->nearPlane = 0.1; + query->farPlane = 2100.0; + query->fov = 3.1415 / 3.5; + mCameraMatrix.setColumn( 3, mCameraPos ); + query->cameraMatrix = mCameraMatrix; + + return( true ); +} + + +//------------------------------------------------------------------------------ +void GuiPlayerView::renderWorld( const RectI &updateRect ) +{ + if ( !(bool)mModel || !(bool)mWeapon ) + return; + + S32 time = Platform::getVirtualMilliseconds(); + S32 dt = time - lastRenderTime; + lastRenderTime = time; + + glClear( GL_DEPTH_BUFFER_BIT ); + glMatrixMode( GL_MODELVIEW ); + + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_LEQUAL ); + + // animate and render in a run pose + F32 fdt = dt; +// mModel->advanceTime( fdt/1000.f, runThread ); + mModel->animate(); + mModel->render(); + + // render a weapon + MatrixF mat; + getWeaponTransform( &mat ); + glPushMatrix(); + dglMultMatrix( &mat ); + + mWeapon->render(); + + glPopMatrix(); + + glDisable( GL_DEPTH_TEST ); + dglSetClipRect( updateRect ); + dglSetCanonicalState(); +} + + diff --git a/game/guiPlayerView.h b/game/guiPlayerView.h new file mode 100644 index 0000000..b4fe129 --- /dev/null +++ b/game/guiPlayerView.h @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIPLAYERVIEW_H_ +#define _GUIPLAYERVIEW_H_ + +#ifndef _GUITSCONTROL_H_ +#include "gui/guiTSControl.h" +#endif +#ifndef _TSSHAPEINSTANCE_H_ +#include "ts/tsShapeInstance.h" +#endif +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _CAMERA_H_ +#include "game/camera.h" +#endif + +class TSThread; + +class GuiPlayerView : public GuiTSCtrl +{ + private: + typedef GuiTSCtrl Parent; + + enum + { + run = 0, + hi, + anim1, + anim2, + anim3, + anim4, + anim5 + }; + + protected: + enum MouseState + { + None, + Rotating, + Zooming + }; + + MouseState mMouseState; + + TSShapeInstance* mModel; + TSShapeInstance* mWeapon; + U32 mSkinTag; + + Point3F mCameraPos; + MatrixF mCameraMatrix; + EulerF mCameraRot; + Point3F mOrbitPos; + F32 mMinOrbitDist; + F32 mOrbitDist; + S32 wNode; + S32 pNode; + + TSThread *runThread; + S32 lastRenderTime; + S32 mAnimationSeq; + + Point2I mLastMousePoint; + + public: + DECLARE_CONOBJECT( GuiPlayerView ); + GuiPlayerView(); + ~GuiPlayerView(); + + static void consoleInit(); + + bool onWake(); + + 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 ); + + void setPlayerModel( const char* shape, const char* skin ); + void setPlayerSeq( S32 index ); + void getWeaponTransform( MatrixF *mat ); + + bool processCameraQuery( CameraQuery *query ); + void renderWorld( const RectI &updateRect ); +}; + +#endif // _GUI_PLAYERVIEW_H diff --git a/game/guiServerBrowser.cc b/game/guiServerBrowser.cc new file mode 100644 index 0000000..b682fce --- /dev/null +++ b/game/guiServerBrowser.cc @@ -0,0 +1,904 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "game/guiServerBrowser.h" +#include "gui/guiCanvas.h" +#include "console/consoleTypes.h" +#include "game/serverQuery.h" +#include "game/version.h" + +//------------------------------------------------------------------------------ +// Static stuff for sorting: +//------------------------------------------------------------------------------ +static S32 sSortColumnKey; +static bool sSortInc; +static S32 sSecondarySortColumnKey; +static bool sSecondarySortInc; + +//------------------------------------------------------------------------------ +static int FN_CDECL serverListRowCompare( const void* a, const void* b ) +{ + ServerInfo* siA = (ServerInfo*) a; + ServerInfo* siB = (ServerInfo*) b; + + enum SortStage + { + FirstSort, + SecondSort, + Done, + }; + + SortStage stage = FirstSort; + //bool done = false; + bool AEqualB = false; + bool ALessB = true; + bool sortInc = true; + S32 cmpResult; + + // First push timed-out servers to the end: + if ( siA->isTimedOut() ) + { + if ( !siB->isTimedOut() ) + { + ALessB = false; + stage = Done; + } + } + else if ( siB->isTimedOut() ) + stage = Done; + + // Now bring servers that have already responded to the top: + if ( stage != Done ) + { + if ( !siA->hasResponded() ) + { + if ( siB->hasResponded() ) + { + ALessB = false; + stage = Done; + } + } + else if ( !siB->hasResponded() ) + stage = Done; + } + + while ( stage != Done ) + { + sortInc = ( stage == FirstSort ) ? sSortInc : sSecondarySortInc; + switch ( ( stage == FirstSort ) ? sSortColumnKey : sSecondarySortColumnKey ) + { + case GuiServerBrowser::Name_Column: + if ( siA->name && siB->name ) + { + cmpResult = dStricmp( siA->name, siB->name ); + if ( cmpResult == 0 ) + AEqualB = true; + else + ALessB = ( cmpResult < 0 ); + } + break; + + case GuiServerBrowser::Status_Column: + { + U32 numIconsA = 0, numIconsB = 0; + U32 iconMaskA = 0, iconMaskB = 0; + if ( siA->isPassworded() ) + { + numIconsA++; + iconMaskA += 4; + } + if ( siA->isDedicated() ) + { + numIconsA++; + iconMaskA += 2; + } + if ( siA->isTournament() ) + { + numIconsA++; + iconMaskA++; + } + if ( siA->isLinux() ) + numIconsA++; + + if ( siB->isPassworded() ) + { + numIconsB++; + iconMaskB += 4; + } + if ( siB->isDedicated() ) + { + numIconsB++; + iconMaskB += 2; + } + if ( siB->isTournament() ) + { + numIconsB++; + iconMaskB++; + } + if ( siB->isLinux() ) + numIconsB++; + + if ( numIconsA == numIconsB ) + { + if ( iconMaskA == iconMaskB ) + AEqualB = true; + else + ALessB = iconMaskA < iconMaskB; + } + else + ALessB = numIconsA < numIconsB; + + break; + } + + case GuiServerBrowser::Favorite_Column: + if ( siA->isFavorite == siB->isFavorite ) + AEqualB = true; + else + ALessB = !siB->isFavorite; + break; + + case GuiServerBrowser::Ping_Column: + if ( siA->ping == siB->ping ) + AEqualB = true; + else + ALessB = ( siA->ping < siB->ping ); + break; + + case GuiServerBrowser::MissionType_Column: + if ( siA->missionType && siB->missionType ) + { + cmpResult = dStricmp( siA->missionType, siB->missionType ); + if ( cmpResult == 0 ) + AEqualB = true; + else + ALessB = ( cmpResult < 0 ); + } + break; + + case GuiServerBrowser::Map_Column: + if ( siA->missionName && siB->missionName ) + { + cmpResult = dStricmp( siA->missionName, siB->missionName ); + if ( cmpResult == 0 ) + AEqualB = true; + else + ALessB = ( cmpResult < 0 ); + } + break; + + case GuiServerBrowser::GameType_Column: + if ( siA->gameType && siB->gameType ) + { + cmpResult = dStricmp( siA->gameType, siB->gameType ); + if ( cmpResult == 0 ) + AEqualB = true; + else + ALessB = ( cmpResult < 0 ); + } + break; + + case GuiServerBrowser::Players_Column: + if ( siA->numPlayers == siB->numPlayers ) + AEqualB = true; + else + ALessB = ( siA->numPlayers < siB->numPlayers ); + break; + + case GuiServerBrowser::CPU_Column: + if ( siA->cpuSpeed == siB->cpuSpeed ) + AEqualB = true; + else + ALessB = ( siA->cpuSpeed < siB->cpuSpeed ); + break; + + case GuiServerBrowser::IP_Column: + { + char addrA[256], addrB[256]; + Net::addressToString( &siA->address, addrA ); + Net::addressToString( &siB->address, addrB ); + cmpResult = dStricmp( addrA, addrB ); + if ( cmpResult == 0 ) + AEqualB = true; + else + ALessB = ( cmpResult < 0 ); + break; + } + + case GuiServerBrowser::Version_Column: + if ( siA->version == siB->version ) + AEqualB = true; + else + ALessB = ( siA->version < siB->version ); + break; + } + + if ( stage == FirstSort && AEqualB ) + stage = SecondSort; + else + stage = Done; + } + + if ( AEqualB ) + { + cmpResult = dStricmp( siA->name, siB->name ); + if ( cmpResult == 0 ) + return 0; + else + ALessB = ( cmpResult < 0 ); + } + + if ( ALessB ) + return ( sortInc ? -1 : 1 ); + else + return ( sortInc ? 1 : -1 ); +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(GuiServerBrowser); + +//------------------------------------------------------------------------------ +GuiServerBrowser::GuiServerBrowser() : ShellFancyArray() +{ + mIconBase = StringTable->insert( "gui/shll_icon" ); + mTexFavorite = NULL; + mTexFavoriteHI = NULL; + mTexNotQueried = NULL; + mTexNotQueriedHI = NULL; + mTexQuerying = NULL; + mTexQueryingHI = NULL; + mTexTimedOut = NULL; + mTexDedicated = NULL; + mTexDedicatedHI = NULL; + mTexPassworded = NULL; + mTexPasswordedHI = NULL; + mTexTournament = NULL; + mTexTournamentHI = NULL; +} + +//------------------------------------------------------------------------------ +ConsoleFunction( queryLanServers, void, 2, 3, "queryLanServers( port{, flags} )" ) +{ + queryLanServers( dAtoi( argv[1] ), ( argc == 2 ? U8( dAtoi( argv[2] ) ) : 0 ) ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( queryMasterGameTypes, void, 1, 1, "queryMasterGameTypes()" ) +{ + argc; + argv; + queryMasterGameTypes(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( queryMasterServer, void, 2, 13, "queryMasterServer( port{, flags{, rulesSet{, missionType{, minPlayers{, maxPlayers{, maxBots{, regionMask{, maxPing{, minCPUSpeed{, filterFlags{, buddyList }}}}}}}}}}} )" ) +{ + char rulesSet[64]; + char missionType[64]; + dStrncpy( rulesSet, ( argc > 3 ? argv[3] : "any" ), sizeof( rulesSet ) ); + dStrncpy( missionType, ( argc > 4 ? argv[4] : "any" ), sizeof( missionType ) ); + U8 buddyCount = 0; + U32* guidArray = NULL; + if ( argc == 13 ) + { + // fill the guid array: + char* buf = new char[dStrlen( argv[11] ) + 1]; + dStrcpy( buf, argv[11] ); + + char* slave = buf; + U8 last = 0; + while ( *slave ) + { + last = *slave++; + if ( last == '\t' ) + { + buddyCount++; + last = 0; + } + } + if ( last ) + buddyCount++; + + if ( buddyCount ) + { + guidArray = new U32[buddyCount]; + + slave = dStrtok( buf, "\t" ); + for ( U32 i = 0; i < buddyCount && slave; i++ ) + { + guidArray[i] = U32( dAtoi( slave ) ); + slave = dStrtok( NULL, "\t" ); + } + } + + delete [] buf; + } + + queryMasterServer( dAtoi(argv[1]), + ( argc > 2 ? U8( dAtoi( argv[2] ) ) : 0 ), + rulesSet, + missionType, + ( argc > 5 ? U8( dAtoi( argv[5] ) ) : 0 ), + ( argc > 6 ? U8( dAtoi( argv[6] ) ) : 255 ), + ( argc > 7 ? U8( dAtoi( argv[7] ) ) : 16 ), + ( argc > 8 ? U32( dAtoi( argv[8] ) ) : 0xFFFFFFFF ), + ( argc > 9 ? U32( dAtoi( argv[9] ) ) : 0 ), + ( argc > 10 ? U32( dAtoi( argv[10] ) ) : 0 ), + ( argc > 11 ? U32( dAtoi( argv[11] ) ) : 0 ), + buddyCount, + guidArray ); + + if ( guidArray ) + delete [] guidArray; +} + +//------------------------------------------------------------------------------ +ConsoleFunction( queryFavoriteServers, void, 1, 2, "queryFavoriteServers( {, flags} )" ) +{ + queryFavoriteServers( argc == 2 ? U8( dAtoi( argv[1] ) ) : 0 ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( querySingleServer, void, 2, 3, "querySingleServer( address{, flags} )" ) +{ + NetAddress addr; + Net::stringToAddress( argv[1], &addr ); + querySingleServer( &addr, ( argc == 3 ? U8( dAtoi( argv[2] ) ) : 0 ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiServerBrowser, sort, void, 2, 2, "browser.sort()" ) +{ + argc; argv; + static_cast( object )->sort(); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiServerBrowser, getServerStatus, const char*, 2, 2, "browser.getServerStatus()" ) +{ + argc; argv; + return( static_cast( object )->getSelectedServerStatus() ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiServerBrowser, getServerInfoString, const char*, 2, 2, "browser.getServerInfoString()" ) +{ + argc; argv; + return( static_cast( object )->getSelectedServerInfoString() ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiServerBrowser, getServerContentString, const char*, 2, 2, "browser.getServerContentString()" ) +{ + argc; argv; + return( static_cast( object )->getSelectedServerContentString() ); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +#ifdef DEBUG +ConsoleFunction( addFakeServers, void, 2, 2, "addFakeServers( howMany )" ) +{ + argc; + addFakeServers( dAtoi( argv[1] ) ); +} +#endif // DEBUG + +//------------------------------------------------------------------------------ +void GuiServerBrowser::initPersistFields() +{ + Parent::initPersistFields(); + addField( "iconBase", TypeString, Offset( mIconBase, GuiServerBrowser ) ); +} + +//------------------------------------------------------------------------------ +const char* GuiServerBrowser::getScriptValue() +{ + if ( mSelectedRow < 0 || mSelectedRow >= gServerList.size() ) + return ""; + + static char buf[256]; + char address[256]; + + ServerInfo &si = gServerList[mSelectedRow]; + Net::addressToString( &si.address, address ); + dSprintf( buf, sizeof( buf ), "%s\t%s\t%d\t%s\t%s", si.name ? si.name : "?", address, si.ping, si.missionName ? si.missionName : "?", si.missionType ? si.missionType : "?" ); + return buf; +} + +//------------------------------------------------------------------------------ +bool GuiServerBrowser::onWake() +{ + if ( !Parent::onWake() ) + return false; + + // Set the row height based on the font: + if ( mFont ) + mRowHeight = mFont->getHeight() + 3; + + // Get the browser icons: + if ( mIconBase[0] ) + { + char buf[256]; + dSprintf( buf, sizeof( buf ), "%s_favorite.png", mIconBase ); + mTexFavorite = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_favorite_hi.png", mIconBase ); + mTexFavoriteHI = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_notqueried.png", mIconBase ); + mTexNotQueried = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_notqueried_hi.png", mIconBase ); + mTexNotQueriedHI = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_querying.png", mIconBase ); + mTexQuerying = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_querying_hi.png", mIconBase ); + mTexQueryingHI = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_timedout.png", mIconBase ); + mTexTimedOut = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_dedicated.png", mIconBase ); + mTexDedicated = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_dedicated_hi.png", mIconBase ); + mTexDedicatedHI = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_passworded.png", mIconBase ); + mTexPassworded = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_passworded_hi.png", mIconBase ); + mTexPasswordedHI = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_tourney.png", mIconBase ); + mTexTournament = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_tourney_hi.png", mIconBase ); + mTexTournamentHI = TextureHandle( buf, BitmapTexture ); + } + + return true; +} + +//------------------------------------------------------------------------------ +void GuiServerBrowser::onSleep() +{ + Parent::onSleep(); + + mTexFavorite = NULL; + mTexFavoriteHI = NULL; + mTexNotQueried = NULL; + mTexNotQueriedHI = NULL; + mTexQuerying = NULL; + mTexQueryingHI = NULL; + mTexTimedOut = NULL; + mTexDedicated = NULL; + mTexDedicatedHI = NULL; + mTexPassworded = NULL; + mTexPasswordedHI = NULL; + mTexTournament = NULL; + mTexTournamentHI = NULL; +} + +//------------------------------------------------------------------------------ +void GuiServerBrowser::onPreRender() +{ + if ( gServerBrowserDirty ) + { + sort(); + gServerBrowserDirty = false; + } +} + +//------------------------------------------------------------------------------ +void GuiServerBrowser::updateList() +{ + if ( mNumRows != gServerList.size() ) + setNumRows( gServerList.size() ); + + setUpdate(); +} + +//------------------------------------------------------------------------------ +void GuiServerBrowser::sort() +{ + mNumRows = gServerList.size(); + sSortColumnKey = mSortColumnKey; + sSortInc = mSortInc; + sSecondarySortColumnKey = mSecondarySortColumnKey; + sSecondarySortInc = mSecondarySortInc; + + if ( gServerList.size() > 1 ) + dQsort( (void*) &(gServerList[0]), gServerList.size(), sizeof(ServerInfo), serverListRowCompare ); + + selectRowByAddress(); + setUpdate(); +} + +//------------------------------------------------------------------------------ +void GuiServerBrowser::onCellSelected( S32 row, S32 column ) +{ + AssertFatal( ( column >= 0 && column < mColumnInfoList.size() ), "Invalid column selected!" ); + AssertFatal( ( row >= 0 && row < mNumRows ), "Invalid row selected!" ); + + char addrString[256]; + Net::addressToString( &gServerList[row].address, addrString ); + if ( mColumnInfoList[column].key == Favorite_Column ) + { + gServerList[row].isFavorite = !gServerList[row].isFavorite; + setUpdate(); + + // Execute the script function: + if ( gServerList[row].isFavorite ) + Con::executef( this, 3, "addFavorite", gServerList[row].name, (const char*) addrString ); + else + Con::executef( this, 2, "removeFavorite", (const char*) addrString ); + } + + mSelectedAddress = gServerList[row].address; + Con::executef( this, 2, "onSelect", (const char*) addrString ); + + // Call the console function: + if ( mConsoleCommand[0] ) + Con::evaluate( mConsoleCommand, false ); +} + +//------------------------------------------------------------------------------ +void GuiServerBrowser::onRenderCell( Point2I offset, Point2I cell, bool selected, bool mouseOver ) +{ + ServerInfo &si = gServerList[cell.y]; + ColumnInfo* ci = &mColumnInfoList[cell.x]; + + // Let the parent take care of the basics: + Parent::onRenderCell( offset, cell, selected, mouseOver ); + + bool drawText = true; + const char* text = NULL; + char buffer[256]; + S32 bmpOffsetX = 0; + Point2I drawPos; + TextureHandle hTex = NULL; + dglClearBitmapModulation(); + + switch( ci->key ) + { + case Name_Column: + text = si.name ? si.name : "--"; + break; + + case Status_Column: + // Draw bitmap(s): + if ( si.isNew() ) + { + hTex = selected ? mTexNotQueriedHI : mTexNotQueried; + if ( hTex ) + { + drawPos.x = offset.x + ( ( ci->width - hTex.getWidth() ) / 2 ); + drawPos.y = offset.y; + if ( mRowHeight > hTex.getHeight() ) + drawPos.y += ( ( mRowHeight - hTex.getHeight() ) / 2 ); + dglDrawBitmap( hTex, drawPos ); + } + else + text = "?"; + } + else if ( si.isQuerying() ) + { + hTex = selected ? mTexQueryingHI : mTexQuerying; + if ( hTex ) + { + drawPos.x = offset.x + ( ( ci->width - hTex.getWidth() ) / 2 ); + drawPos.y = offset.y; + if ( mRowHeight > hTex.getHeight() ) + drawPos.y += ( ( mRowHeight - hTex.getHeight() ) / 2 ); + dglDrawBitmap( hTex, drawPos ); + } + else + text = "Q"; + } + else if ( si.hasResponded() ) + { + TextureHandle hTexDedicated = NULL, hTexTournament = NULL; + U32 numIcons = 0; + U32 width = 0, height = 0; + if ( si.isPassworded() ) + { + hTex = selected ? mTexPasswordedHI : mTexPassworded; + if ( hTex ) + { + width += hTex.getWidth(); + height = hTex.getHeight(); + numIcons++; + } + } + + if ( si.isDedicated() ) + { + hTexDedicated = selected ? mTexDedicatedHI : mTexDedicated; + if ( hTexDedicated ) + { + width += hTexDedicated.getWidth(); + if ( hTexDedicated.getHeight() > height ) + height = hTexDedicated.getHeight(); + numIcons++; + } + } + + if ( si.isTournament() ) + { + hTexTournament = selected ? mTexTournamentHI : mTexTournament; + if ( hTexTournament ) + { + width += hTexTournament.getWidth(); + if ( hTexTournament.getHeight() > height ) + height = hTexTournament.getHeight(); + numIcons++; + } + } + + if ( si.isLinux() ) + { + width += mFont->getStrWidth( "L" ); + numIcons++; + } + + // Draw icons if there are any: + if ( numIcons > 0 ) + { + // Try to center the icons: + drawPos = offset; + if ( ci->width > width ) + drawPos.x += ( ( ci->width - width ) / 2 ); + + if ( mRowHeight > height ) + drawPos.y += ( ( mRowHeight - height ) / 2 ); + + if ( hTex ) + { + dglDrawBitmap( hTex, drawPos ); + drawPos.x += hTex.getWidth(); + } + + if ( hTexDedicated ) + { + dglDrawBitmap( hTexDedicated, drawPos ); + drawPos.x += hTexDedicated.getWidth(); + } + + if ( hTexTournament ) + { + dglDrawBitmap( hTexTournament, drawPos ); + drawPos.x += hTexTournament.getWidth(); + } + + if ( si.isLinux() ) + { + dglSetBitmapModulation( selected ? ColorI( 4, 40, 5 ) : ColorI( 0, 255, 0 ) ); + drawPos.y = offset.y + ( ( mRowHeight - mFont->getHeight() ) / 2 ); + dglDrawText( mFont, drawPos, "L" ); + } + } + } + else if ( mTexTimedOut ) + { + drawPos.x = offset.x + ( ( ci->width - mTexTimedOut.getWidth() ) / 2 ); + drawPos.y = offset.y; + if ( mRowHeight > mTexTimedOut.getHeight() ) + drawPos.y += ( ( mRowHeight - mTexTimedOut.getHeight() ) / 2 ); + dglDrawBitmap( mTexTimedOut, drawPos ); + } + else + text = "X"; + break; + + case Favorite_Column: + if ( si.isFavorite ) + { + hTex = selected ? mTexFavoriteHI : mTexFavorite; + if ( hTex ) + { + drawPos.x = offset.x + ( ( ci->width - hTex.getWidth() ) / 2 ); + drawPos.y = offset.y; + if ( mRowHeight > hTex.getHeight() ) + drawPos.y += ( ( mRowHeight - hTex.getHeight() ) / 2 ); + dglDrawBitmap( hTex, drawPos ); + } + else + text = "X"; + } + break; + + case Ping_Column: + dSprintf( buffer, 255, "%d", si.ping ); + text = buffer; + break; + + case IP_Column: + Net::addressToString( &si.address, buffer ); + dStrcpy( buffer, buffer + 3 ); + text = buffer; + break; + + case Players_Column: + dSprintf( buffer, 255, "%d/%d (%d)", si.numPlayers, si.maxPlayers, si.numBots ); + text = buffer; + break; + + case GameType_Column: + text = si.gameType ? si.gameType : "--"; + break; + + case MissionType_Column: + text = si.missionType ? si.missionType : "--"; + break; + + case Map_Column: + if ( si.missionName ) + { + dStrcpy( buffer, si.missionName ); + // Clip off the file extension: + char* temp = dStrstr( buffer, const_cast( ".mis" ) ); + if ( temp ) + *temp = 0; + text = buffer; + } + else + text = "--"; + break; + + case CPU_Column: + if ( si.cpuSpeed ) + { + dSprintf( buffer, 255, "%d MHz", si.cpuSpeed ); + text = buffer; + } + else + text = "--"; + break; + + case Version_Column: + if ( si.version ) + { + dSprintf( buffer, 255, "%d", si.version ); + text = buffer; + } + else + text = "--"; + break; + + default: + Con::errorf( ConsoleLogEntry::General, "Invalid column key encountered!" ); + return; + } + + // Finally, draw the text ( if there is any ): + if ( drawText && text && *text ) + { + // Determine the font color: + ColorI color; + if ( si.gameType && dStricmp( si.gameType, "base" ) != 0 ) // Mod colors + color = selected ? mProfile->mFontColors[6] : ( mouseOver ? mProfile->mFontColors[5] : mProfile->mFontColors[4] ); + else if ( si.version != getVersionNumber() ) // Disparate build version colors + color = selected ? mProfile->mFontColors[9] : ( mouseOver ? mProfile->mFontColors[8] : mProfile->mFontColors[7] ); + else // Base colors + color = selected ? mProfile->mFontColorSEL : ( mouseOver ? mProfile->mFontColorHL : mProfile->mFontColor ); + + S32 textWidth = mFont->getStrWidth( text ); + Point2I textStart; + textStart.x = offset.x + getMax( 4, ( ci->width - textWidth ) / 2 ); + textStart.y = offset.y + ( ( mRowHeight - ( mFont->getHeight() - 2 ) ) / 2 ); + dglSetBitmapModulation( color ); + dglDrawText( mFont, textStart, text ); + } +} + +//------------------------------------------------------------------------------ +void GuiServerBrowser::selectRowByAddress() +{ + if ( mSelectedRow >= 0 || mSelectedRow < gServerList.size() ) + { + bool foundIt = false; + for ( S32 i = 0; i < gServerList.size(); i++ ) + { + if ( Net::compareAddresses( &mSelectedAddress, &gServerList[i].address ) ) + { + selectCell( i, 0 ); + foundIt = true; + break; + } + } + + if ( !foundIt ) + mSelectedRow = -1; + } +} + +//------------------------------------------------------------------------------ +const char* GuiServerBrowser::getSelectedServerStatus() +{ + if ( mSelectedRow < 0 || mSelectedRow >= gServerList.size() ) + return( "invalid" ); + + ServerInfo* si = &gServerList[mSelectedRow]; + if ( si->isNew() ) + return( "new" ); + if ( si->isQuerying() ) + return( "querying" ); + if ( si->hasResponded() ) + return( "responded" ); + if ( si->isUpdating() ) + return( "updating" ); + + return( "timedOut" ); +} + +//------------------------------------------------------------------------------ +const char* GuiServerBrowser::getSelectedServerInfoString() +{ + if ( mSelectedRow < 0 || mSelectedRow >= gServerList.size() ) + return( "" ); + + ServerInfo* si = &gServerList[mSelectedRow]; + U32 bufSize = 0; + if ( si->name ) + bufSize += ( dStrlen( si->name ) + 1 ); + char addrString[256]; + Net::addressToString( &si->address, addrString ); + bufSize += dStrlen( addrString ) - 2; + if ( si->gameType ) + bufSize += ( dStrlen( si->gameType ) + 1 ); + + // Build the flag string: + char flagString[256]; + flagString[0] = 0; + bool firstFlag = true; + if ( si->isDedicated() ) + { + dStrcpy( flagString, "Dedicated" ); + firstFlag = false; + } + if ( si->isPassworded() ) + { + dStrcat( flagString, firstFlag ? "Passworded" : "; Passworded" ); + firstFlag = false; + } + if ( si->isTournament() ) + { + dStrcat( flagString, firstFlag ? "Tournament Mode" : "; Tournament Mode" ); + firstFlag = false; + } + if ( si->isLinux() ) + { + dStrcat( flagString, firstFlag ? "Linux" : "; Linux" ); + firstFlag = false; + } + if ( !si->areSmurfsAllowed() ) + dStrcat( flagString, firstFlag ? "No Aliases" : "; No Aliases" ); + bufSize += ( dStrlen( flagString ) + 1 ); + + if ( si->missionType ) + bufSize += ( dStrlen( si->missionType ) + 1 ); + if ( si->missionName ) + bufSize += ( dStrlen( si->missionName ) + 1 ); + if ( si->infoString ) + bufSize += ( dStrlen( si->infoString ) + 1 ); + + char* returnString = Con::getReturnBuffer( bufSize ); + dSprintf( returnString, bufSize, "%s\n%s\n%s\n%s\n%s\n%s\n%s", + ( si->name ? si->name : "" ), + &addrString[3], + ( si->gameType ? si->gameType : "" ), + flagString, + ( si->missionType ? si->missionType : "" ), + ( si->missionName ? si->missionName : "" ), + ( si->infoString ? si->infoString : "" ) ); + + return( returnString ); +} + +//------------------------------------------------------------------------------ +const char* GuiServerBrowser::getSelectedServerContentString() +{ + if ( mSelectedRow < 0 || mSelectedRow >= gServerList.size() ) + return( "" ); + + return( gServerList[mSelectedRow].contentString ? gServerList[mSelectedRow].contentString : "" ); +} diff --git a/game/guiServerBrowser.h b/game/guiServerBrowser.h new file mode 100644 index 0000000..ba000b6 --- /dev/null +++ b/game/guiServerBrowser.h @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUISERVERBROWSER_H_ +#define _GUISERVERBROWSER_H_ + +#ifndef _SHELLFANCYARRAY_H_ +#include "Shell/shellFancyArray.h" +#endif +#ifndef _EVENT_H_ +#include "Platform/event.h" +#endif + +class GuiServerBrowser : public ShellFancyArray +{ + private: + typedef ShellFancyArray Parent; + + public: + enum ColumnKey + { + Name_Column = 0, // 0 + Status_Column, // 1 + Favorite_Column, // 2 + Ping_Column, // 3 + MissionType_Column, // 4 + Map_Column, // 5 + GameType_Column, // 6 + Players_Column, // 7 + CPU_Column, // 8 + IP_Column, // 9 + Version_Column, // 10 + + ColumnCount + }; + + protected: + StringTableEntry mIconBase; + + TextureHandle mTexFavorite; + TextureHandle mTexFavoriteHI; + TextureHandle mTexNotQueried; + TextureHandle mTexNotQueriedHI; + TextureHandle mTexQuerying; + TextureHandle mTexQueryingHI; + TextureHandle mTexTimedOut; + TextureHandle mTexDedicated; + TextureHandle mTexDedicatedHI; + TextureHandle mTexPassworded; + TextureHandle mTexPasswordedHI; + TextureHandle mTexTournament; + TextureHandle mTexTournamentHI; + + NetAddress mSelectedAddress; + + public: + DECLARE_CONOBJECT(GuiServerBrowser); + GuiServerBrowser(); + + // GuiControl overrides: + static void initPersistFields(); + + const char* getScriptValue(); + + bool onWake(); + void onSleep(); + + void onPreRender(); + + // ShellFancyArray overrides: + void updateList(); + void sort(); + void onCellSelected( S32 row, S32 column ); + void onRenderCell( Point2I offset, Point2I cell, bool selected, bool mouseOver ); + + // Class-specific functions: + void selectRowByAddress(); + + const char* getSelectedServerStatus(); + const char* getSelectedServerInfoString(); + const char* getSelectedServerContentString(); +}; + +#endif // _GUI_SERVERBROWSER_H + diff --git a/game/hoverVehicle.cc b/game/hoverVehicle.cc new file mode 100644 index 0000000..5da33e4 --- /dev/null +++ b/game/hoverVehicle.cc @@ -0,0 +1,974 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/hoverVehicle.h" +#include "core/bitStream.h" +#include "dgl/dgl.h" +#include "scenegraph/sceneState.h" +#include "collision/clippedPolyList.h" +#include "collision/planeExtractor.h" +#include "game/moveManager.h" +#include "ts/tsShapeInstance.h" +#include "console/consoleTypes.h" +#include "terrain/terrData.h" +#include "scenegraph/sceneGraph.h" +#include "audio/audio.h" +#include "game/particleEngine.h" +#include "math/mathIO.h" +#include "game/forceFieldBare.h" +#include "dgl/materialPropertyMap.h" +#include "terrain/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 | ForceFieldObjectType); + +const U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType +const U32 sClientCollisionMask = sCollisionMoveMask; + +void nonFilter(SceneObject* object,S32 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.25; + floatingThrustFactor = 0.15; + + mainThrustForce = 0; + reverseThrustForce = 0; + strafeThrustForce = 0; + turboFactor = 1.0; + + stabLenMin = 0.5; + stabLenMax = 2.0; + stabSpringConstant = 30; + stabDampingConstant = 10; + + gyroDrag = 10; + normalForce = 30; + restorativeForce = 10; + steeringForce = 25; + rollForce = 2.5; + pitchForce = 2.5; + + dustTrailEmitter = NULL; + dustTrailID = 0; + dustTrailOffset.set( 0.0, 0.0, 0.0 ); + dustTrailFreqMod = 15.0; + triggerTrailHeight = 2.5; + + 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() +{ + Parent::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", TypeAudioProfilePtr, Offset(sound[JetSound], HoverVehicleData)); + addField("engineSound", TypeAudioProfilePtr, Offset(sound[EngineSound], HoverVehicleData)); + addField("floatSound", TypeAudioProfilePtr, 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)); +} + + +//-------------------------------------------------------------------------- +bool HoverVehicleData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + return true; +} + + +bool HoverVehicleData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == 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] = shape->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()? + (AudioProfile*) 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_AUDIOHANDLE; + mEngineSound = NULL_AUDIOHANDLE; + mFloatSound = NULL_AUDIOHANDLE; + + mDustTrailEmitter = NULL; + + mBackMaintainOn = false; + for (S32 i = 0; i < JetAnimCount; i++) + mJetThread[i] = 0; +} + +HoverVehicle::~HoverVehicle() +{ + // +} + +//-------------------------------------------------------------------------- +void HoverVehicle::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void HoverVehicle::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +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() +{ + scriptOnRemove(); + removeFromScene(); + + if (mJetSound != NULL_AUDIOHANDLE) + alxStop(mJetSound); + if (mEngineSound != NULL_AUDIOHANDLE) + alxStop(mEngineSound); + if (mFloatSound != NULL_AUDIOHANDLE) + alxStop(mFloatSound); + + Parent::onRemove(); +} + + +bool HoverVehicle::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; +} + + + +//-------------------------------------------------------------------------- +void HoverVehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + // Update jetsound... + if (mJetting) { + if (mJetSound == NULL_AUDIOHANDLE && mDataBlock->sound[HoverVehicleData::JetSound] != NULL) + mJetSound = alxPlay(mDataBlock->sound[HoverVehicleData::JetSound], &getTransform()); + + if (mJetSound != NULL_AUDIOHANDLE) + alxSourceMatrixF(mJetSound, &getTransform()); + } else { + if (mJetSound != NULL_AUDIOHANDLE) { + alxStop(mJetSound); + mJetSound = NULL_AUDIOHANDLE; + } + } + + // Update engine sound... + if (mEngineSound == NULL_AUDIOHANDLE && mDataBlock->sound[HoverVehicleData::EngineSound] != NULL) + mEngineSound = alxPlay(mDataBlock->sound[HoverVehicleData::EngineSound], &getTransform()); + if (mEngineSound != NULL_AUDIOHANDLE) { + alxSourceMatrixF(mEngineSound, &getTransform()); + + F32 denom = mDataBlock->mainThrustForce + mDataBlock->strafeThrustForce; + F32 factor = getMin(mThrustLevel, denom) / denom; + F32 vol = 0.25 + factor * 0.75; + alxSourcef(mEngineSound, AL_GAIN_LINEAR, vol); + } + + // Are we floating? If so, start the floating sound... + if (mFloating) { + if (mFloatSound == NULL_AUDIOHANDLE && mDataBlock->sound[HoverVehicleData::FloatSound] != NULL) + mFloatSound = alxPlay(mDataBlock->sound[HoverVehicleData::FloatSound], &getTransform()); + + if (mFloatSound != NULL_AUDIOHANDLE) + alxSourceMatrixF(mFloatSound, &getTransform()); + } else { + if (mFloatSound != NULL_AUDIOHANDLE) { + alxStop(mFloatSound); + mFloatSound = NULL_AUDIOHANDLE; + } + } + 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.state.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*/) +{ + Point3F gravForce(0, 0, sHoverVehicleGravity * mGravityMod); + + MatrixF currTransform; + mRigid.state.getTransform(&currTransform); + + 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.state.linVelocity; + F32 baseStabLen = getBaseStabilizerLength(); + Point3F stabExtend(0, 0, -baseStabLen); + currTransform.mulV(stabExtend); + + StabPoint stabPoints[2]; + stabPoints[0].osPoint = Point3F((mObjBox.min.x + mObjBox.min.x) * 0.5, + mObjBox.max.y, + (mObjBox.min.z + mObjBox.max.z) * 0.5); + stabPoints[1].osPoint = Point3F((mObjBox.min.x + mObjBox.min.x) * 0.5, + mObjBox.min.y, + (mObjBox.min.z + mObjBox.max.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.state.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; + } + + // Check the waterblock directly + SimpleQueryList sql; + mSceneManager->getWaterObjectList(sql); + for (U32 i = 0; i < sql.mList.size(); i++) + { + WaterBlock* pBlock = static_cast(sql.mList[i]); + if (pBlock->isPointSubmerged(stabPoints[j].wsPoint)) + { + compression[j] = baseStabLen; + break; + } + } + } + + 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.state.linVelocity.len(); + if (mThrottle == 0.0f && + mLeftThrust == 0.0f && + mRightThrust == 0.0f && + vellen != 0.0f && + vellen < mDataBlock->brakingActivationSpeed) + { + Point3F dir = mRigid.state.linVelocity; + dir.normalize(); + dir.neg(); + force += dir * mDataBlock->brakingForce; + } + + // Gyro Drag + torque = -mRigid.state.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.state.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 * mGravityMod); + force -= mRigid.state.linVelocity * mDrag; + torque -= mRigid.state.angMomentum * mDrag; + + mRigid.state.force = force; + mRigid.state.torque = torque; +} + + +void HoverVehicle::updateWarp() +{ + // +} + + + +//-------------------------------------------------------------------------- +U32 HoverVehicle::getCollisionMask() +{ + if (isServerObject()) + return sServerCollisionMask; + else + return sClientCollisionMask; +} + + +//bool HoverVehicle::collideBody(const MatrixF& mat,Collision* info) +//{ +// // Database bounding box +// Box3F wBox = mObjBox; +// mat.mul(wBox); +// +// // Test the body against the database +// Box3F box; +// SphereF sphere; +// MatrixF imat(1); +// PlaneExtractorPolyList extractor; +// sPolyList->mPlaneList.clear(); +// extractor.mPlaneList = &sPolyList->mPlaneList; +// extractor.setTransform(&mat, VectorF(1,1,1)); +// mShapeInstance->buildPolyList(&extractor,mDataBlock->collisionDetails[0]); +// +// sPolyList->clear(); +// // 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) { +// pConvex->getPolyList(sPolyList); +// } +// pList = pList->wLink.mNext; +// } +// +// info->face = sPolyList->mPolyList.size() ? BodyCollision : 0; +// +// // Pick best collision point +// F32 bestDist = 1.0E30; +// ClippedPolyList::Poly* bestPoly = 0; +// ClippedPolyList::Vertex* bestVertex; +// +// if (sPolyList->mPolyList.size()) { +// Point3F massCenter; +// mat.mulP(mDataBlock->massCenter,&massCenter); +// +// // Pick surface with best vertex velocity +// F32 bestVd = -1; +// ClippedPolyList::Poly* poly = sPolyList->mPolyList.begin(); +// ClippedPolyList::Poly* end = sPolyList->mPolyList.end(); +// for (; poly != end; poly++) { +// U32* vi = &sPolyList->mIndexList[poly->vertexStart]; +// U32* ve = vi + poly->vertexCount; +// for (; vi != ve; vi++) { +// ClippedPolyList::Vertex* ev = &sPolyList->mVertexList[*vi]; +// +// VectorF v,r; +// r = ev->point - massCenter; +// getVelocity(r,&v); +// +// F32 dist = mDot(poly->plane,v); +// if (dist < 0 && dist < bestDist) { +// bestDist = dist; +// bestVertex = ev; +// bestPoly = poly; +// } +// } +// } +// } +// +// if (bestPoly) { +// info->point = bestVertex->point; +// info->object = bestPoly->object; +// info->normal = bestPoly->plane; +// info->material = bestPoly->material; +// return true; +// } +// +// return false; +//} + +void HoverVehicle::updateDustTrail( F32 dt ) +{ + if( !mDustTrailEmitter ) return; + + // check if close to ground + Point3F startPos = getPosition(); + Point3F endPos = startPos + Point3F( 0.0, 0.0, -mDataBlock->triggerTrailHeight ); + + RayInfo rayInfo; + if( !getContainer()->castRay( startPos, endPos, TerrainObjectType, &rayInfo ) ) + { + return; + } + + VectorF vel = getVelocity(); + + TerrainBlock* tBlock = static_cast(rayInfo.object); + S32 mapIndex = tBlock->mMPMIndex[0]; + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntryFromIndex(mapIndex); + + // emit dust if moving + if( vel.len() > 2.0 && pEntry) + { + VectorF axis = vel; + axis.normalize(); + + S32 x; + ColorF colorList[ParticleEngine::PC_COLOR_KEYS]; + + for(x = 0; x < 2; ++x) + colorList[x].set( pEntry->puffColor[x].red, pEntry->puffColor[x].green, pEntry->puffColor[x].blue, pEntry->puffColor[x].alpha ); + for(x = 2; x < ParticleEngine::PC_COLOR_KEYS; ++x) + colorList[x].set( 1.0, 1.0, 1.0, 0.0 ); + + mDustTrailEmitter->setColors( colorList ); + + Point3F contactPoint = rayInfo.point + mDataBlock->dustTrailOffset; + mDustTrailEmitter->emitParticles( contactPoint , true, axis, vel, dt * 1000 * (vel.len() / mDataBlock->dustTrailFreqMod) ); + } + +} + +void HoverVehicle::updateJet(F32 dt) +{ + if (mJetThread[BackActivate] == NULL) + return; + + F32 pos = mShapeInstance->getPos(mJetThread[BackActivate]); + F32 scale = mShapeInstance->getTimeScale(mJetThread[BackActivate]); + + // 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: -1); + 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]); + + // 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(),dt * 1000); + } + } + else { + for (S32 j = idx; j < idx + count; j++) + if (bool(mJetEmitter[j])) { + mJetEmitter[j]->deleteWhenEmpty(); + mJetEmitter[j] = 0; + } + } +} diff --git a/game/hoverVehicle.h b/game/hoverVehicle.h new file mode 100644 index 0000000..fb7a22a --- /dev/null +++ b/game/hoverVehicle.h @@ -0,0 +1,200 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HOVERVEHICLE_H_ +#define _HOVERVEHICLE_H_ + +#ifndef _VEHICLE_H_ +#include "game/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 + }; + AudioProfile* 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, char errorBuffer[256]); + + 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*); + void updateDustTrail( F32 dt ); + + // Vehicle overrides + protected: + void updateMove(const Move *move); + void updateWarp(); + + // Physics + protected: + void updateForces(F32); + F32 getBaseStabilizerLength() const; + + bool mFloating; + F32 mThrustLevel; + + F32 mForwardThrust; + F32 mReverseThrust; + F32 mLeftThrust; + F32 mRightThrust; + + AUDIOHANDLE mJetSound; + AUDIOHANDLE mEngineSound; + AUDIOHANDLE 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); + + DECLARE_CONOBJECT(HoverVehicle); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_HOVERVEHICLE + diff --git a/game/httpObject.cc b/game/httpObject.cc new file mode 100644 index 0000000..28885a4 --- /dev/null +++ b/game/httpObject.cc @@ -0,0 +1,319 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/simBase.h" +#include "Platform/event.h" +#include "console/consoleInternal.h" +#include "game/httpObject.h" +#include "Core/fileStream.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['\"'] = true; + asciiEscapeTable['_'] = true; + asciiEscapeTable['\''] = true; + asciiEscapeTable['#'] = true; + asciiEscapeTable['$'] = true; + asciiEscapeTable['%'] = true; + asciiEscapeTable['&'] = true; + asciiEscapeTable['+'] = true; + asciiEscapeTable['-'] = true; + asciiEscapeTable['~'] = true; + } + + U32 destIndex = 0; + U32 srcIndex = 0; + while(path[srcIndex] && destIndex < destSize - 3) + { + char c = path[srcIndex++]; + if(asciiEscapeTable[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(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; +} + +//-------------------------------------- +static void cHTTPObjectGet(SimObject *obj, S32 argc, const char **argv) +{ + ((HTTPObject *) obj)->get(argv[2], argv[3], argc == 4 ? NULL : argv[4]); +} + +static void cHTTPObjectPost(SimObject *obj, S32, const char **argv) +{ + ((HTTPObject *) obj)->post(argv[2], argv[3], argv[4], argv[5]); +} + + +//-------------------------------------- +void HTTPObject::consoleInit() +{ + Con::addCommand("HTTPObject", "get", cHTTPObjectGet, "obj.get(addr, request-uri, )", 4, 5); + Con::addCommand("HTTPObject", "post", cHTTPObjectPost, "obj.post(addr, request-uri, query, post)", 6, 6); +} + diff --git a/game/httpObject.h b/game/httpObject.h new file mode 100644 index 0000000..913bf9a --- /dev/null +++ b/game/httpObject.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HTTPOBJECT_H_ +#define _HTTPOBJECT_H_ + +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _TCPOBJECT_H_ +#include "game/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); + + static void consoleInit(); +}; + + +#endif // _H_HTTPOBJECT_ diff --git a/game/item.cc b/game/item.cc new file mode 100644 index 0000000..d590b82 --- /dev/null +++ b/game/item.cc @@ -0,0 +1,1094 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "core/bitStream.h" +#include "game/game.h" +#include "math/mMath.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "sim/netConnection.h" +#include "game/item.h" +#include "collision/boxConvex.h" +#include "game/shadow.h" +#include "collision/earlyOutPolyList.h" +#include "collision/extrudedPolyList.h" +#include "math/mathIO.h" + +//---------------------------------------------------------------------------- + +const F32 sRotationSpeed = 3.0; // Secs/Rotation +const F32 sAtRestVelocity = 0.15; // Min speed after collision +const S32 sCollisionTimeout = 15; // Timout value in ticks + +// Client prediction +static F32 sMaxLatencyTicks = 0; // Max latency 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; + +const U32 sClientCollisionMask = (TerrainObjectType | InteriorObjectType | + StaticShapeObjectType | VehicleObjectType | + PlayerObjectType | StaticTSObjectType | + ForceFieldObjectType); + +const U32 sServerCollisionMask = (sClientCollisionMask | + TriggerObjectType); + +const S32 Item::csmAtRestTimer = 64; + +static const U32 sgAllowedDynamicTypes = DamagableItemObjectType; + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(ItemData); + +ItemData::ItemData() +{ + friction = 0; + elasticity = 0; + + sticky = false; + gravityMod = 1.0; + maxVelocity = -1; + + density = 2; + drag = 0.5; + + genericShadowLevel = Item_GenericShadowLevel; + noShadowLevel = Item_NoShadowLevel; + 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; +} + +void ItemData::consoleInit() +{ + // +} + +static EnumTable::Enums itemLightEnum[] = +{ + { Item::NoLight, "NoLight" }, + { Item::ConstantLight, "ConstantLight" }, + { Item::PulsingLight, "PulsingLight" } +}; +static EnumTable gItemLightTypeTable(Item::NumLightTypes, &itemLightEnum[0]); + +void ItemData::initPersistFields() +{ + Parent::initPersistFields(); + + 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("pickUpName", TypeString, Offset(pickUpName, 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)); +} + +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; + mVelocity = VectorF(0,0,0); + mAtRest = true; + mAtRestCounter = 0; + mInLiquid = false; + delta.warpTicks = 0; + mCollisionObject = 0; + mCollisionTimeout = 0; +// mGenerateShadow = true; + + mConvex.init(this); + mWorkingQueryBox.min.set(-1e9, -1e9, -1e9); + mWorkingQueryBox.max.set(-1e9, -1e9, -1e9); +} + +Item::~Item() +{ +} + + +//---------------------------------------------------------------------------- + +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.min.set(-1e9, -1e9, -1e9); + mWorkingQueryBox.max.set(-1e9, -1e9, -1e9); + + addToScene(); + + if (isServerObject()) + { + scriptOnAdd(); + } + else if (mDataBlock->lightType != NoLight) + { + Sim::getLightSet()->addObject(this); + 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.min.set(-1e9, -1e9, -1e9); + mWorkingQueryBox.max.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: + { + F32 delta = Sim::getCurrentTime() - mDropTime; + intensity = 0.5f + 0.5f * mSin(M_PI * delta / F32(mDataBlock->lightTime)); + intensity = 0.15f + intensity * 0.85f; + intensity *= mFadeVal; // fade out light on flags + break; + } + + default: + return; + } + + mLight.mColor = mDataBlock->lightColor * intensity; + mLight.mColor.clamp(); + mLight.mType = LightInfo::Point; + mLight.mRadius = mDataBlock->lightRadius; + + mLight.mPos = getBoxCenter(); + + lightManager->addLight(&mLight); +} + +//---------------------------------------------------------------------------- + +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; + } + } + + 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) +{ + // Client side interpolation + Parent::interpolateTick(dt); + Point3F pos = delta.pos + delta.posVec * dt; + MatrixF mat = mObjToWorld; + mat.setColumn(3,pos); + Parent::setRenderTransform(mat); +} + + +//---------------------------------------------------------------------------- + +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,-mAtan(-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.min -= Point3F(l, l, l); + convexBox.max += Point3F(l, l, l); + + // Check containment + { + if (mWorkingQueryBox.min.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.min -= Point3F(2 * l, 2 * l, 2 * l); + mWorkingQueryBox.max += 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.1; + 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.min.x + mObjBox.max.x) * 0.5, + (mObjBox.min.y + mObjBox.max.y) * 0.5, + mObjBox.max.z); + Point3F endCast((mObjBox.min.x + mObjBox.max.x) * 0.5, + (mObjBox.min.y + mObjBox.max.y) * 0.5, + mObjBox.min.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() & (InteriorObjectType|TerrainObjectType)) { + 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)) + queueCollision(static_cast(rinfo.object)); + } + } + } + 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.min; + Point3F oldMax = testBox.max; + testBox.min.setMin(oldMin + (mVelocity * time)); + testBox.max.setMin(oldMax + (mVelocity * time)); + + sEarlyOutPolyList.clear(); + sEarlyOutPolyList.mNormal.set(0,0,0); + sEarlyOutPolyList.mPlaneList.setSize(6); + sEarlyOutPolyList.mPlaneList[0].set(wBox.min,VectorF(-1,0,0)); + sEarlyOutPolyList.mPlaneList[1].set(wBox.max,VectorF(0,1,0)); + sEarlyOutPolyList.mPlaneList[2].set(wBox.max,VectorF(1,0,0)); + sEarlyOutPolyList.mPlaneList[3].set(wBox.min,VectorF(0,-1,0)); + sEarlyOutPolyList.mPlaneList[4].set(wBox.min,VectorF(0,0,-1)); + sEarlyOutPolyList.mPlaneList[5].set(wBox.max,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.t < 1.0) + { + // Set to collision point + F32 dt = time * collisionList.t; + pos += mVelocity * dt; + time -= dt; + + // Pick the most resistant surface + F32 bd = 0; + Collision* collision = 0; + for (int c = 0; c < collisionList.count; c++) { + Collision &cp = collisionList.collision[c]; + F32 dot = -mDot(mVelocity,cp.normal); + if (dot > bd) { + bd = dot; + collision = &cp; + } + } + + if (collision && mDataBlock->sticky && collision->object->getType() & (InteriorObjectType|TerrainObjectType)) { + 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)) + queueCollision(static_cast(collision->object)); + } + } + } + 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; + } + + // 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, 2, "onStickyCollision", scriptThis()); + } + else + notifyCollision(); + + // water + if(bool(safePtr)) + { + if(!mInLiquid && mWaterCoverage != 0.0f) + { + Con::executef(mDataBlock,4,"onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), Con::getIntArg(mLiquidType)); + mInLiquid = true; + } + else if(mInLiquid && mWaterCoverage == 0.0f) + { + Con::executef(mDataBlock,3,"onLeaveLiquid",scriptThis(), Con::getIntArg(mLiquidType)); + 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(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,10); + } + 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(); + 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(10); + 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,0,stream->readFlag()? -1: 1); + 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,0,0); + else { + mathRead(*stream, &mVelocity); + } + if (stream->readFlag() && isProperlyAdded()) { + // Warp to the server's position over time + // First advance pos to estimated server time. + S32 ticks = (S32)(mCeil(connection->getRoundTripTime() / (2 * TickMs))); + if (ticks > sMaxLatencyTicks) + ticks = (S32)sMaxLatencyTicks; + if (ticks > 0) { + MatrixF orgPos = mObjToWorld; + VectorF orgVel = mVelocity; + bool orgRest = mAtRest; + + MatrixF mat = mObjToWorld; + mat.setColumn(3,pos); + Parent::setTransform(mat); + while (ticks--) { + updateVelocity(TickSec); + updateWorkingCollisionSet(sClientCollisionMask, TickSec); + updatePos(sClientCollisionMask, TickSec); + } + mObjToWorld.getColumn(3,&pos); + + Parent::setTransform(orgPos); + mVelocity = orgVel; + mAtRest = orgRest; + } + + // 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.5 * TickSec; + F32 dt = as? delta.warpOffset.len() / as: sMaxWarpTicks; + delta.warpTicks = (S32)((dt > sMinWarpTicks)? getMax(mFloor(dt + 0.5), 1.0f): 0.0f); + if (delta.warpTicks) { + if (delta.warpTicks > sMaxWarpTicks) + delta.warpTicks = sMaxWarpTicks; + delta.warpOffset /= 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; + mObjToWorld.getColumn(3,&cp); + 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; + mat.setColumn(3,pos); + } + } + Parent::setTransform(mat); +} + + +static bool cIsStatic(SimObject *ptr, S32, const char **) +{ + Item* obj = static_cast(ptr); + return obj->isStatic(); +} + +static bool cIsRotating(SimObject *ptr, S32, const char **) +{ + Item* obj = static_cast(ptr); + return obj->isRotating(); +} + +static bool cSetCollisionTimeout(SimObject *ptr, S32, const char **argv) +{ + Item* obj = static_cast(ptr); + ShapeBase* source; + if (Sim::findObject(dAtoi(argv[2]),source)) { + obj->setCollisionTimeout(source); + return true; + } + return false; +} + +static const char * cGetLastStickyPos(SimObject *ptr, S32, const char **) +{ + AssertFatal(dynamic_cast(ptr) != NULL, "error, how did a non-item get in here?"); + Item* obj = static_cast(ptr); + + char* ret = Con::getReturnBuffer(256); + if (obj->isServerObject()) + dSprintf(ret, 255, "%g %g %g", + obj->mStickyCollisionPos.x, + obj->mStickyCollisionPos.y, + obj->mStickyCollisionPos.z); + else + dStrcpy(ret, "0 0 0"); + + return ret; +} + +static const char * cGetLastStickyNormal(SimObject *ptr, S32, const char **) +{ + AssertFatal(dynamic_cast(ptr) != NULL, "error, how did a non-item get in here?"); + Item* obj = static_cast(ptr); + + char* ret = Con::getReturnBuffer(256); + if (obj->isServerObject()) + dSprintf(ret, 255, "%g %g %g", + obj->mStickyCollisionNormal.x, + obj->mStickyCollisionNormal.y, + obj->mStickyCollisionNormal.z); + else + dStrcpy(ret, "0 0 0"); + + return ret; +} + +//---------------------------------------------------------------------------- + +void Item::initPersistFields() +{ + Parent::initPersistFields(); + addField("collideable", TypeBool, Offset(mCollideable, Item)); + addField("static", TypeBool, Offset(mStatic, Item)); + addField("rotate", TypeBool, Offset(mRotate, Item)); +} + +void Item::consoleInit() +{ + Con::addCommand("Item", "isStatic", cIsStatic, "obj.isStatic()", 2, 2); + Con::addCommand("Item", "isRotating", cIsRotating, "obj.isRotating()", 2, 2); + Con::addCommand("Item", "setCollisionTimeout", cSetCollisionTimeout, "obj.setCollisionTimeout(object)", 3, 3); + + Con::addCommand("Item", "getLastStickyPos", cGetLastStickyPos, "obj.getLastStickyPos()", 2, 2); + Con::addCommand("Item", "getLastStickyNormal", cGetLastStickyNormal, "obj.getLastStickyNormal()", 2, 2); + + Con::addVariable("Item::minWarpTicks",TypeF32,&sMinWarpTicks); + Con::addVariable("Item::maxWarpTicks",TypeS32,&sMaxWarpTicks); + Con::addVariable("Item::maxLatencyTicks",TypeF32,&sMaxLatencyTicks); +} + + +//---------------------------------------------------------------------------- + +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::renderImage(SceneState* state, SceneRenderImage* image) +{ + // Client side rotation + if (mRotate) { + F32 t = Sim::getCurrentTime() * F32(1)/1000; + F32 r = (t / sRotationSpeed) * M_2PI; + Point3F pos; + mRenderObjToWorld.getColumn(3,&pos); + MatrixF mat = mRenderObjToWorld; + mat.set(Point3F(0,0,r)); + mat.setColumn(3,pos); + Parent::setRenderTransform(mat); + } + Parent::renderImage(state, image); + + if (mShadow) + { + mShadow->setMoving(!mAtRest); + mShadow->setAnimating(!mAtRest && !mRotate); + } +} + + +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; +} + diff --git a/game/item.h b/game/item.h new file mode 100644 index 0000000..0a355a4 --- /dev/null +++ b/game/item.h @@ -0,0 +1,160 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ITEM_H_ +#define _ITEM_H_ + +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "scenegraph/lightManager.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 consoleInit(); + 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; + }; + StateDelta delta; + + // Static attributes + ItemData* mDataBlock; + static F32 mGravity; + bool mCollideable; + bool mStatic; + bool mRotate; + + // + 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&, const SphereF&); + void buildConvex(const Box3F& box, Convex* convex); + void onDeleteNotify(SimObject*); + + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + + 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; } + 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*); + void interpolateTick(F32 dt); + void setTransform(const MatrixF&); + void renderImage(SceneState* state, SceneRenderImage*); + + + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); +}; + +#endif diff --git a/game/lightning.cc b/game/lightning.cc new file mode 100644 index 0000000..decf9cf --- /dev/null +++ b/game/lightning.cc @@ -0,0 +1,1228 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/lightning.h" +#include "dgl/dgl.h" +#include "platformWIN32/platformGL.h" +#include "scenegraph/sceneState.h" +#include "console/consoleTypes.h" +#include "math/mathIO.h" +#include "core/bitStream.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "math/mRandom.h" +#include "math/mathUtils.h" +#include "audio/audioDataBlock.h" +#include "platform/platformAudio.h" +#include "terrain/terrData.h" +#include "scenegraph/sceneGraph.h" +#include "game/player.h" +#include "game/camera.h" + + +IMPLEMENT_CO_DATABLOCK_V1(LightningData); +IMPLEMENT_CO_NETOBJECT_V1(Lightning); + +namespace { + +MRandomLCG sgLightningRand; + +void cLightningWarningFlashes(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-lightning object get in here?"); + Lightning* lightning = static_cast(obj); + + if (lightning->isServerObject()) + lightning->warningFlashes(); +} + +void cLightningStrikeRandomPoint(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-lightning object get in here?"); + Lightning* lightning = static_cast(obj); + + if (lightning->isServerObject()) + lightning->strikeRandomPoint(); +} + +void cLightningStrikeObject(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-lightning object get in here?"); + Lightning* lightning = static_cast(obj); + + S32 id = dAtoi(argv[2]); + ShapeBase* pSB; + + if (lightning->isServerObject() && Sim::findObject(id, pSB)) + lightning->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; + } +} + +} // namespace {} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +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() +{ + Parent::initPersistFields(); + + addField("strikeSound", TypeAudioProfilePtr, Offset(strikeSound, LightningData)); + addField("thunderSounds", TypeAudioProfilePtr, Offset(thunderSounds, LightningData), MaxThunders); + addField("strikeTextures", TypeString, Offset(strikeTextureNames, LightningData), MaxTextures); +} + + +//-------------------------------------------------------------------------- +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, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + dQsort(thunderSounds, MaxThunders, sizeof(AudioProfile*), cmpSounds); + for (numThunders = 0; numThunders < MaxThunders && thunderSounds[numThunders] != NULL; numThunders++) { + // + } + + if (server == false) { + for (U32 i = 0; i < MaxTextures; i++) { + strikeTextures[i] = TextureHandle(strikeTextureNames[i], MeshTexture); + } + } + + + 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.5; + strikeRadius = 20.0; + boltStartRadius = 20.0; + color.set( 1.0, 1.0, 1.0, 1.0 ); + fadeColor.set( 0.1, 0.1, 1.0, 1.0 ); + useFog = true; + + setScale( VectorF( 512, 512, 300 ) ); +} + +Lightning::~Lightning() +{ + // +} + +//-------------------------------------------------------------------------- +void Lightning::initPersistFields() +{ + Parent::initPersistFields(); + + addField("strikesPerMinute",TypeS32, Offset(strikesPerMinute, Lightning)); + addField("strikeWidth", TypeF32, Offset(strikeWidth, Lightning)); + addField("chanceToHitTarget", TypeF32, Offset(chanceToHitTarget, Lightning)); + addField("strikeRadius", TypeF32, Offset(strikeRadius, Lightning)); + addField("boltStartRadius", TypeF32, Offset(boltStartRadius, Lightning)); + addField("color", TypeColorF, Offset(color, Lightning)); + addField("fadeColor", TypeColorF, Offset(fadeColor, Lightning)); + addField("useFog", TypeBool, Offset(useFog, Lightning)); + +} + +void Lightning::consoleInit() +{ + Con::addCommand("Lightning", "warningFlashes", cLightningWarningFlashes, "[LightningObject].warningFlashes()", 2, 2); + Con::addCommand("Lightning", "strikeRandomPoint", cLightningStrikeRandomPoint, "[LightningObject].strikeRandomPoint()", 2, 2); + Con::addCommand("Lightning", "strikeObject", cLightningStrikeObject, "[LightningObject].strikeObject(id)", 3, 3); +} + +//-------------------------------------------------------------------------- +bool Lightning::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.min.set( -0.5, -0.5, -0.5 ); + mObjBox.max.set( 0.5, 0.5, 0.5 ); + + 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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::EndSort; + state->insertRenderImage(image); + } + + return false; +} + + +void Lightning::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + + // RENDER CODE HERE + MatrixF mv; + dglGetModelview(&mv); + Point3F camAxis; + mv.getRow(1, &camAxis); + + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glDepthMask( GL_FALSE ); + + if( useFog ) + { + + if (dglDoesSupportARBMultitexture() && dglDoesSupportFogCoord()) { + glEnable(GL_FOG); + glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT); + GLfloat fogColor[4]; + fogColor[0] = state->getFogColor().red; + fogColor[1] = state->getFogColor().green; + fogColor[2] = state->getFogColor().blue; + fogColor[3] = 0.5f; + glFogfv(GL_FOG_COLOR, fogColor); + glFogi(GL_FOG_MODE, GL_LINEAR); + glFogf(GL_FOG_START, 0.0f); + glFogf(GL_FOG_END, 1.0f); + } + } + + Strike* walk = mStrikeListHead; + while (walk != NULL) { + + glBindTexture(GL_TEXTURE_2D, mDataBlock->strikeTextures[0].getGLName()); + + for( U32 i=0; i<3; i++ ) + { + if( walk->bolt[i].isFading ) + { + F32 alpha = 1.0 - walk->bolt[i].percentFade; + if( alpha < 0.0 ) alpha = 0.0; + glColor4f( fadeColor.red, fadeColor.green, fadeColor.blue, alpha ); + } + else + { + glColor4fv( color ); + } + walk->bolt[i].render( state->getCameraPosition() ); + } + + + walk = walk->next; + } + + glDepthMask( GL_TRUE ); + + glDisable(GL_FOG); + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +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::getServerConnection(); + 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; + + F32 dist = worldPos.len(); + F32 t = dist / 330.0; + + // Ok, we need to schedule a random strike sound t secs in the future... + // + if (t <= 0.03) { + // If it's really close, just play it... + U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1); + alxPlay(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 = 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.0) { + *pThunderWalker = pThunder->next; + delete pThunder; + + // Play the sound... + U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1); + alxPlay(mDataBlock->thunderSounds[thunder]); + } else { + pThunderWalker = &((*pThunderWalker)->next); + } + } +} + + +//-------------------------------------------------------------------------- +void Lightning::processEvent(LightningStrikeEvent* pEvent) +{ + AssertFatal(pEvent->mStart.x >= 0 && pEvent->mStart.x <= 1.0, "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.5, -mObjScale.y * 0.5, 0.0 ); + + RayInfo rayInfo; + Point3F start = strikePoint; + start.z = mObjScale.z * 0.5 + getPosition().z; + strikePoint.z += -mObjScale.z * 0.5; + 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.6; + pStrike->currentAge = 0.0; + pStrike->next = mStrikeListHead; + + for( U32 i=0; i<3; i++ ) + { + F32 randStart = boltStartRadius; + F32 height = mObjScale.z * 0.5 + getPosition().z; + pStrike->bolt[i].startPoint = Point3F( 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; + pStrike->bolt[i].numMinorNodes = 4; + pStrike->bolt[i].maxMinorAngle = 15; + pStrike->bolt[i].generate(); + pStrike->bolt[i].startSplits(); + pStrike->bolt[i].lifetime = 1.0; + pStrike->bolt[i].fadeTime = 0.2; + pStrike->bolt[i].renderTime = gRandGen.randF(0.0, 0.25); + } + + mStrikeListHead = pStrike; + + scheduleThunder(pStrike); + + MatrixF trans(true); + trans.setPosition( strikePoint ); + + if (mDataBlock->strikeSound) + { + alxPlay(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; + strikePoint.x = gRandGen.randF( 0.0, 1.0 ); + strikePoint.y = gRandGen.randF( 0.0, 1.0 ); + strikePoint.z = 0.0; + + // check if an object is within target range + + strikePoint *= mObjScale; + strikePoint += getPosition(); + strikePoint += Point3F( -mObjScale.x * 0.5, -mObjScale.y * 0.5, 0.0 ); + + Box3F queryBox; + F32 boxWidth = strikeRadius * 2; + + queryBox.min.set( -boxWidth * 0.5, -boxWidth * 0.5, -mObjScale.z * 0.5 ); + queryBox.max.set( boxWidth * 0.5, boxWidth * 0.5, mObjScale.z * 0.5 ); + queryBox.min += strikePoint; + queryBox.max += strikePoint; + + SimpleQueryList sql; + getContainer()->findObjects(queryBox, DAMAGEABLE_MASK, + SimpleQueryList::insertionCallback, S32(&sql)); + + SceneObject *highestObj = NULL; + F32 highestPnt = 0.0; + + 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.5 + getPosition().z; + Point3F end = objectCenter; + end.z = -mObjScale.z * 0.5 + getPosition().z; + bool rayHit = gServerContainer.castRay( start, end, + (-1), + &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.0, 0.0, 1.0 ), 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, "not yet done"); +} + + +//-------------------------------------------------------------------------- +U32 Lightning::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & GameBase::InitialUpdateMask)) + { + // 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, 5, "applyDamage", + Con::getIntArg(getId()), + Con::getIntArg(hitObject->getId()), + posArg, + normalArg); + } +} + +//************************************************************************** +// Lightning Bolt +//************************************************************************** +LightningBolt::LightningBolt() +{ + width = 0.1; + startPoint.zero(); + endPoint.zero(); + chanceOfSplit = 0.0; + isFading = false; + elapsedTime = 0.0; + lifetime = 1.0; + startRender = false; +} + +//-------------------------------------------------------------------------- +// Generate nodes +//-------------------------------------------------------------------------- +void LightningBolt::NodeManager::generateNodes() +{ + F32 overallDist = VectorF( endPoint - startPoint ).magnitudeSafe(); + F32 minDistBetweenNodes = overallDist / (numNodes-1); + F32 maxDistBetweenNodes = minDistBetweenNodes / mCos( maxAngle * M_PI / 180.0 ); + + VectorF mainLineDir = endPoint - startPoint; + mainLineDir.normalizeSafe(); + + for( U32 i=0; iisFading = true; + } + curBolt->render( camPos ); + } + + +} + +//-------------------------------------------------------------------------- +// Render segment +//-------------------------------------------------------------------------- +void LightningBolt::renderSegment( NodeManager &segment, const Point3F &camPos, bool renderLastPoint ) +{ + + for( int i=0; igetCurrentTerrain(); + if( !pTerrain ) return 0.0; + + Point3F terrPt = point; + pTerrain->getWorldTransform().mulP(terrPt); + F32 h; + if (pTerrain->getHeight(Point2F(terrPt.x, terrPt.y), &h)) + { + return h; + } + + + return 0.0; +} + + +//---------------------------------------------------------------------------- +// 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.70 ) return; + + if( width < 0.75 ) width = 0.75; + + 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; + newBolt.numMinorNodes = 3; + newBolt.maxMinorAngle = 10; + newBolt.startRender = true; + newBolt.generate(); + + splitList.link( newBolt ); + + VectorF newDir1 = MathUtils::randomDir( diff, 10.0, 45.0 ); + Point3F newEndPoint1 = endPoint + newDir1 * gRandGen.randF( 0.5, 1.5 ) * length; + + VectorF newDir2 = MathUtils::randomDir( diff, 10.0, 45.0 ); + Point3F newEndPoint2 = endPoint + newDir2 * gRandGen.randF( 0.5, 1.5 ) * length; + + createSplit( endPoint, newEndPoint1, depth - 1, width * 0.30 ); + createSplit( endPoint, newEndPoint2, depth - 1, width * 0.30 ); + +} + +//---------------------------------------------------------------------------- +// Start split - kick off the recursive 'createSplit' procedure +//---------------------------------------------------------------------------- +void LightningBolt::startSplits() +{ + + for( U32 i=0; i 0.3 ) 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.0, 40.0 ); + Point3F newEndPoint = node.point + newDir * gRandGen.randF( 0.5, 1.5 ) * length; + + + createSplit( node.point, newEndPoint, 4, width * 0.30 ); + } + + +} + +//---------------------------------------------------------------------------- +// 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.0; + } +} diff --git a/game/lightning.h b/game/lightning.h new file mode 100644 index 0000000..f769571 --- /dev/null +++ b/game/lightning.h @@ -0,0 +1,216 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTNING_H_ +#define _LIGHTNING_H_ + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _LLIST_H_ +#include "Core/llist.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +class ShapeBase; +class LightningStrikeEvent; +class AudioProfile; + + +// ------------------------------------------------------------------------- +class LightningData : public GameBaseData +{ + typedef GameBaseData Parent; + + public: + enum Constants { + MaxThunders = 8, + MaxTextures = 8 + }; + + //-------------------------------------- Console set variables + public: + AudioProfile* thunderSounds[MaxThunders]; + AudioProfile* strikeSound; + StringTableEntry strikeTextureNames[MaxTextures]; + + //-------------------------------------- load set variables + public: + S32 thunderSoundIds[MaxThunders]; + S32 strikeSoundID; + + TextureHandle strikeTextures[MaxTextures]; + U32 numThunders; + + protected: + bool onAdd(); + + + public: + LightningData(); + ~LightningData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + 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; + LList< LightningBolt > 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(); + + void createSplit( Point3F startPoint, 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; + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + // Time management + void processTick(const Move*); + void interpolateTick(F32); + void advanceTime(F32); + + // 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(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_LIGHTNING + diff --git a/game/linearProjectile.cc b/game/linearProjectile.cc new file mode 100644 index 0000000..789bdec --- /dev/null +++ b/game/linearProjectile.cc @@ -0,0 +1,1280 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "core/bitStream.h" +#include "math/mathIO.h" +#include "sim/netConnection.h" +#include "game/shapeBase.h" +#include "ts/tsShapeInstance.h" +#include "game/explosion.h" +#include "terrain/waterBlock.h" +#include "sim/decalManager.h" +#include "game/linearProjectile.h" +#include "scenegraph/sceneGraph.h" +#include "game/splash.h" + +#include "dgl/dgl.h" +#include "platformWIN32/platformGL.h" +#include "scenegraph/sceneState.h" +#include "math/mathUtils.h" + +IMPLEMENT_CO_DATABLOCK_V1(LinearProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(LinearProjectile); + +const U32 LinearProjectile::csmDecalMask = TerrainObjectType | InteriorObjectType; + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +LinearProjectileData::LinearProjectileData() +{ + dryVelocity = 5.0; + wetVelocity = 5.0; + + fizzleTimeMS = 1000; + lifetimeMS = 1000; + explodeOnDeath = false; + + reflectOnWaterImpactAngle = 0.0; + deflectionOnWaterImpact = 0.0; + fizzleUnderwaterMS = -1; + + activateDelayMS = -1; + + activateSeq = -1; + maintainSeq = -1; + + doDynamicClientHits = false; +} + +LinearProjectileData::~LinearProjectileData() +{ + +} + +//-------------------------------------------------------------------------- +void LinearProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("dryVelocity", TypeF32, Offset(dryVelocity, LinearProjectileData)); + addField("wetVelocity", TypeF32, Offset(wetVelocity, LinearProjectileData)); + addField("fizzleTimeMS", TypeS32, Offset(fizzleTimeMS, LinearProjectileData)); + addField("lifetimeMS", TypeS32, Offset(lifetimeMS, LinearProjectileData)); + addField("explodeOnDeath", TypeBool, Offset(explodeOnDeath, LinearProjectileData)); + addField("reflectOnWaterImpactAngle", TypeF32, Offset(reflectOnWaterImpactAngle, LinearProjectileData)); + addField("deflectionOnWaterImpact", TypeF32, Offset(deflectionOnWaterImpact, LinearProjectileData)); + + addField("fizzleUnderwaterMS", TypeS32, Offset(fizzleUnderwaterMS, LinearProjectileData)); + addField("activateDelayMS", TypeS32, Offset(activateDelayMS, LinearProjectileData)); + addField("doDynamicClientHits", TypeBool, Offset(doDynamicClientHits, LinearProjectileData)); +} + +void LinearProjectileData::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +bool LinearProjectileData::calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& sourceVel, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime) +{ + // Not implemented: underwater shots... + + Point3F effTargetPos = targetPos - sourcePos; + Point3F effTargetVel = targetVel - sourceVel * velInheritFactor; + + // Without underwater aiming, this is a straightforward law of cosines + // calculation. We're not being especially efficient here, but hey. + // + Point3F normPos = effTargetPos; + Point3F normVel = effTargetVel; + if (normPos.isZero() == false) + normPos.normalize(); + if (normVel.isZero() == false) + normVel.normalize(); + + F32 a = effTargetVel.lenSquared() - (dryVelocity*dryVelocity); + F32 b = 2 * effTargetPos.len() * effTargetVel.len() * mDot(normPos, normVel); + F32 c = effTargetPos.lenSquared(); + + F32 det = b*b - 4*a*c; + if (det < 0.0) { + // No solution is possible in the real numbers + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + return false; + } + + F32 sol1 = (-b + mSqrt(det)) / (2 * a); + F32 sol2 = (-b - mSqrt(det)) / (2 * a); + + F32 t; + if (sol2 > 0.0) { + t = sol2; + } else { + t = sol1; + } + if (t < 0.0) { + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + return false; + } + + // Once we know how long the projectile's path will take, it's straightforward to + // find out where it should go... + Point3F finalAnswer = (effTargetPos / (dryVelocity * t)) + (effTargetVel / dryVelocity); + finalAnswer.normalize(); + + *outputVectorMin = finalAnswer; + *outputVectorMax = finalAnswer; + *outputMinTime = t; + *outputMaxTime = t; + + return (t * 1000.0) <= lifetimeMS; +} + + +//-------------------------------------------------------------------------- +bool LinearProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + // Check for validity, and fix what we can... + // + if (dryVelocity < 0.1) { + Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: dryVelocity < .1, resetting", getName()); + dryVelocity = 0.1; + } + if (wetVelocity < 0.1 && wetVelocity != -1) { + Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: wetVelocity < .1, resetting", getName()); + wetVelocity = 0.1; + } + if (lifetimeMS < TickMs || lifetimeMS > ((LinearProjectile::MaxLivingTicks-1) * TickMs)) { + Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: lifetime out of range [%d, %d]", getName(), TickMs, ((LinearProjectile::MaxLivingTicks-1) * TickMs)); + lifetimeMS = lifetimeMS < TickMs ? TickMs : ((LinearProjectile::MaxLivingTicks-1) * TickMs); + } + if (fizzleTimeMS < 0) { + Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: fizzle time < 0", getName()); + fizzleTimeMS = 0; + } + if (fizzleTimeMS > lifetimeMS) { + Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: fizzleTimeMS > lifetimeMS", getName()); + fizzleTimeMS = lifetimeMS; + } + + // Bound the angle variables + if (reflectOnWaterImpactAngle > 90.0f) + reflectOnWaterImpactAngle = 90.0f; + if (reflectOnWaterImpactAngle < 0.0f) + reflectOnWaterImpactAngle = -1.0f; + if (deflectionOnWaterImpact > 90.0f) + deflectionOnWaterImpact = 90.0f; + if (deflectionOnWaterImpact < 0.0f) + deflectionOnWaterImpact = 0.0f; + + reflectOnWaterImpactAngle = U32(reflectOnWaterImpactAngle); + deflectionOnWaterImpact = U32(deflectionOnWaterImpact); + + if (fizzleUnderwaterMS < 0 || fizzleUnderwaterMS > lifetimeMS) + fizzleUnderwaterMS = -1; + else + fizzleUnderwaterMS = (fizzleUnderwaterMS + TickMs - 1) & ~(TickMs - 1); + + if (activateDelayMS < 0 || activateDelayMS > lifetimeMS) + activateDelayMS = -1; + + // Make sure lifetimeMS falls on a tick boundary + lifetimeMS = (lifetimeMS + TickMs - 1) & ~(TickMs - 1); + fizzleTimeMS = (fizzleTimeMS + TickMs - 1) & ~(TickMs - 1); + + return true; +} + + +bool LinearProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (bool(projectileShape) && activateDelayMS != -1) { + activateSeq = projectileShape->findSequence("activate"); + maintainSeq = projectileShape->findSequence("maintain"); + + if (activateSeq == -1 || maintainSeq == -1) { + Con::warnf(ConsoleLogEntry::General, + "LinearProjectileData(%s)::load: activateDelayMS != -1, but no activate/maintain sequence on shape %s", + getName(), projectileShapeName); + activateDelayMS = -1; + activateSeq = -1; + maintainSeq = -1; + } + } + + return true; +} + +//-------------------------------------------------------------------------- +void LinearProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(dryVelocity); + stream->write(wetVelocity); + stream->write(fizzleTimeMS); + stream->write(lifetimeMS); + stream->writeFlag(explodeOnDeath); + stream->writeRangedU32(reflectOnWaterImpactAngle, 0, 90); + stream->writeRangedU32(deflectionOnWaterImpact, 0, 90); + stream->write(fizzleUnderwaterMS); + stream->write(activateDelayMS); + stream->writeFlag(doDynamicClientHits); +} + +void LinearProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&dryVelocity); + stream->read(&wetVelocity); + stream->read(&fizzleTimeMS); + stream->read(&lifetimeMS); + explodeOnDeath = stream->readFlag(); + reflectOnWaterImpactAngle = stream->readRangedU32(0, 90); + deflectionOnWaterImpact = stream->readRangedU32(0, 90); + stream->read(&fizzleUnderwaterMS); + stream->read(&activateDelayMS); + doDynamicClientHits = stream->readFlag(); +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +LinearProjectile::LinearProjectile() +{ + mActivateThread = NULL; + mMaintainThread = NULL; + + mSplashTick = 999999999999; + mPlayedSplash = false; + + mEndedWithDecal = false; + mWetStart = false; + mHitWater = false; + + mCurrBackDelta.set( 0.0f, 0.0f, 0.0f ); + mCurrDeltaBase.set( 0.0f, 0.0f, 0.0f ); +} + +LinearProjectile::~LinearProjectile() +{ + +} + +//-------------------------------------------------------------------------- +void LinearProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void LinearProjectile::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +bool LinearProjectile::calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime) +{ + AssertFatal(isServerObject() == true, "Error, bad assumption. This should only be called on the server..."); + + if (mHidden == true) { + impactTime = 0; + pointOfImpact.set(0, 0, 0); + return false; + } + + Point3F startPt; + getTransform().getColumn(3, &startPt); + + U32 forwardTicks = U32((simTime * 1000.0f) / TickMs); + if ((mCurrTick + forwardTicks) * TickMs > mSegments[0].msEnd) { + // Hit a wall or the terrain... + U32 currMS = mCurrTick * TickMs; + impactTime = (F32(mSegments[0].msEnd) - F32(currMS)) / 1000.0; + if (impactTime < 0.0) + impactTime = 0.0; + pointOfImpact = mSegments[0].end; + return true; + } + + Point3F endPt = deriveExactPosition(mCurrTick + forwardTicks); + + U32 mask = PlayerObjectType | TerrainObjectType | InteriorObjectType | WaterObjectType; + RayInfo rayInfo; + if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo)) + { + impactTime = 0; + pointOfImpact.set(0, 0, 0); + return false; + } + else + { + Point3F currVel = deriveExactVelocity(mCurrTick); + + pointOfImpact = rayInfo.point; + float distToImpact = (pointOfImpact - startPt).len(); + if (distToImpact > 1.0f && !currVel.isZero()) + impactTime = distToImpact / currVel.len(); + else + impactTime = 0.0f; + return true; + } +} + +bool LinearProjectile::determineWetStart() +{ + SimpleQueryList sql; + if (isServerObject()) + gServerSceneGraph->getWaterObjectList(sql); + else + gClientSceneGraph->getWaterObjectList(sql); + + for (U32 i = 0; i < sql.mList.size(); i++) + { + WaterBlock* pBlock = dynamic_cast(sql.mList[i]); + if (pBlock && pBlock->isPointSubmergedSimple(mInitialPosition)) + return true; + } + + return false; +} + + + +//-------------------------------------------------------------------------- +bool LinearProjectile::createSegments() +{ + // Assumption: a linear projectile can have at most two segments. + // Not handled in this version: compressed intial position + + // First, check to see if the fire point is wet... + mWetStart = determineWetStart(); + + Point3F compressedVelocity(0, 0, 0); + if (isServerObject()) { + if (mWetStart == false) + compressedVelocity += (BitStream::dumbDownNormal(mInitialDirection, InitialDirectionBits) * + mDataBlock->dryVelocity); + else + compressedVelocity += (BitStream::dumbDownNormal(mInitialDirection, InitialDirectionBits) * + mDataBlock->wetVelocity); + compressedVelocity += mExcessDirDumb * F32(mExcessVel); + } else { + if (mWetStart == false) + compressedVelocity += mInitialDirection * mDataBlock->dryVelocity; + else + compressedVelocity += mInitialDirection * mDataBlock->wetVelocity; + compressedVelocity += mExcessDirDumb * F32(mExcessVel); + } + + mSegments[0].start = mInitialPosition; + mSegments[0].end = mInitialPosition + compressedVelocity * (F32(mDataBlock->lifetimeMS) / 1000.0); + mSegments[0].segmentVel = compressedVelocity; + + RayInfo rInfo; + rInfo.object = NULL; + Container* pContainer = isServerObject() ? &gServerContainer : &gClientContainer; + if (pContainer->castRay(mSegments[0].start, mSegments[0].end, + csmStaticCollisionMask, &rInfo) == false) { + // Didn't hit anything, lifetime goes to the end... + mSegments[0].msStart = 0; + mSegments[0].msEnd = mDataBlock->lifetimeMS; + + mSegments[0].cutShort = false; + mSegments[0].endNormal = compressedVelocity; + mSegments[0].endNormal.normalize(); + mSegments[0].endNormal.neg(); + } else { + // Hit something, cut the segment short... + mSegments[0].msStart = 0; + mSegments[0].msEnd = U32(mDataBlock->lifetimeMS * rInfo.t); + mSegments[0].end = rInfo.point; + + mSegments[0].cutShort = true; + mSegments[0].endNormal = rInfo.normal; + mSegments[0].endTypeMask = rInfo.object->getTypeMask(); + } + + mSegments[0].hitObj = rInfo.object; + + mNumSegments = 1; + + if (mWetStart == false) { + // If we started in air, then we potentially have to modify the segments if + // we collide with a water block... + if (pContainer->castRay(mSegments[0].start, mSegments[0].end, WaterObjectType, &rInfo)) { + // We're going to have to fix this... + + if (mDataBlock->explodeOnWaterImpact) { + + // check if we actually hit water + bool hitWater = false; + + WaterBlock* waterBlock = dynamic_cast(rInfo.object); + if( waterBlock ) + { + if( waterBlock->isWater( waterBlock->getLiquidType() ) ) + { + hitWater = true; + } + } + + // Check to see if we explode or bounce off... + Point3F normVel = compressedVelocity; + normVel.normalize(); + if (mFabs(mDot(normVel, rInfo.normal)) >= mCos(mDegToRad(90 - mDataBlock->reflectOnWaterImpactAngle)) || !hitWater ) { + // Explode + mHitWater = true; + mSegments[0].msEnd = (U32)(mSegments[0].msEnd * rInfo.t); + mSegments[0].end = rInfo.point; + mSegments[0].cutShort = true; + mSegments[0].endNormal = rInfo.normal; + mSegments[0].endTypeMask = rInfo.object->getTypeMask(); + + mDeleteTick = (mSegments[0].msEnd / TickMs) + DeleteWaitTicks; + } else { + mSegments[0].msEnd = (U32)(mSegments[0].msEnd * rInfo.t); + mSegments[0].end = rInfo.point; + mSegments[0].endNormal = rInfo.normal; + mSegments[0].cutShort = false; + + Point3F n = rInfo.normal; + MatrixF refMatrix(true); + F32* ra = refMatrix; + 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; + ra[12] = 0; + ra[13] = 0; + ra[14] = 0; + ra[15] = 1.0f; + // the GGems series (as of v1) uses row vectors (arg) + refMatrix.transpose(); + + // Reflect the velocity around the normal... + Point3F refVelocity = compressedVelocity; + refMatrix.mulV(refVelocity); + + + Point3F newStart = mSegments[0].end; + Point3F newEnd = newStart; + newEnd += refVelocity * (F32(mDataBlock->lifetimeMS - mSegments[0].msEnd) / 1000); + + if (pContainer->castRay(newStart, newEnd, + csmStaticCollisionMask, &rInfo) == false) { + // Didn't hit anything, lifetime goes to the end... + mSegments[1].msStart = mSegments[0].msEnd; + mSegments[1].msEnd = mDataBlock->lifetimeMS; + mSegments[1].start = mSegments[0].end; + mSegments[1].end = newEnd; + + mSegments[1].cutShort = false; + mSegments[1].endNormal = refVelocity; + mSegments[1].endNormal.normalize(); + mSegments[1].endNormal.neg(); + mSegments[1].segmentVel = refVelocity; + } else { + // Hit something, cut the segment short... + mSegments[1].msStart = mSegments[0].msEnd; + mSegments[1].msEnd = (U32)(mSegments[1].msStart + (mDataBlock->lifetimeMS - mSegments[0].msEnd) * rInfo.t); + mSegments[1].start = mSegments[0].end; + mSegments[1].end = rInfo.point; + + mSegments[1].cutShort = true; + mSegments[1].endNormal = rInfo.normal; + mSegments[1].endTypeMask = rInfo.object->getTypeMask(); + mSegments[1].segmentVel = refVelocity; + } + + mDeleteTick = (mSegments[1].msEnd / TickMs) + DeleteWaitTicks; + mNumSegments = 2; + } + } else { + // Continue through, at wetVelocity... + mSegments[1].start = rInfo.point; + mSegments[0].end = rInfo.point; + mSegments[0].msEnd *= rInfo.t; + mSegments[0].endNormal = rInfo.normal; + mSegments[0].cutShort = false; + + compressedVelocity.set(0, 0, 0); + if (isServerObject()) { + compressedVelocity += (BitStream::dumbDownNormal(mInitialDirection, InitialDirectionBits) * + mDataBlock->wetVelocity); + compressedVelocity += mExcessDirDumb * F32(mExcessVel); + } else { + compressedVelocity += mInitialDirection * mDataBlock->wetVelocity; + compressedVelocity += mExcessDirDumb * F32(mExcessVel); + } + + Point3F newEnd = mSegments[1].start; + newEnd += compressedVelocity * (F32(mDataBlock->lifetimeMS - mSegments[0].msEnd) / 1000.0); + + if (pContainer->castRay(mSegments[1].start, newEnd, + csmStaticCollisionMask, &rInfo) == false) { + // Didn't hit anything, lifetime goes to the end... + mSegments[1].msStart = mSegments[0].msEnd; + mSegments[1].msEnd = mDataBlock->lifetimeMS; + mSegments[1].end = newEnd; + + mSegments[1].cutShort = false; + mSegments[1].endNormal = compressedVelocity; + mSegments[1].endNormal.normalize(); + mSegments[1].endNormal.neg(); + } else { + // Hit something, cut the segment short... + mSegments[1].msStart = mSegments[0].msEnd; + mSegments[1].msEnd = (U32)(mSegments[1].msStart + (mDataBlock->lifetimeMS - mSegments[0].msEnd) * rInfo.t); + mSegments[1].end = rInfo.point; + + mSegments[1].cutShort = true; + mSegments[1].endNormal = rInfo.normal; + mSegments[1].endTypeMask = rInfo.object->getTypeMask(); + } + + mSegments[1].hitObj = rInfo.object; + mSegments[1].segmentVel = compressedVelocity; + + mNumSegments = 2; + mDeleteTick = (mSegments[1].msEnd / TickMs) + DeleteWaitTicks; + } + } else { + // No water collision, we're cool... + mDeleteTick = (mSegments[0].msEnd / TickMs) + DeleteWaitTicks; + } + } else { + mDeleteTick = (mSegments[0].msEnd / TickMs) + DeleteWaitTicks; + } + + AssertFatal(mSegments[0].msEnd >= mSegments[0].msStart, "Error, 1st segment is bad!"); + if (mNumSegments > 1) { + AssertFatal(mSegments[1].msStart == mSegments[0].msEnd, "Error, 2nd segment is bad!"); + AssertFatal(mSegments[1].msEnd >= mSegments[1].msStart, "Error, 2nd segment is bad!"); + } + + return true; +} + +bool LinearProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isServerObject()) { + // Set the initial position + MatrixF xform(true); + xform.setColumn(3, mInitialPosition); + setTransform(xform); + + // Ok, we have all the variables we need to forecast our own demise. Do that now + if (createSegments() == false) + { + return false; + } + } else { + if (mHidden == false) { + createSegments(); + calcSplash(); + + if (bool(mSourceObject)) { + // Setup the warping. We have to take into account the fact that we might be + // in the middle of a tick. + mWarpTicksRemaining = smProjectileWarpTicks; + mWarpEnd = deriveExactPosition(mCurrTick + smProjectileWarpTicks); + mSourceObject->getMuzzlePoint(mSourceObjectSlot, &mWarpStart); + } else { + mWarpTicksRemaining = 0; + } + } else { + setPosition( mInitialPosition ); + + mWetStart = determineWetStart(); + calcSplash(); + + if( mCurrTick >= mSplashTick ) + { + createSplash(); + } + + // All we need to do is explode... + if (mDataBlock->explosion) { + Explosion* pExplosion = new Explosion; + pExplosion->onNewDataBlock(mDataBlock->explosion); + + MatrixF xform(true); + xform.setColumn(3, mExplosionPosition); + pExplosion->setTransform(xform); + pExplosion->setInitialState(mExplosionPosition, mExplosionNormal, 1.0); + if (pExplosion->registerObject() == false) { + Con::errorf(ConsoleLogEntry::General, "LinearProjectile(%s)::explode: couldn't register explosion(%s)", + mDataBlock->getName(), mDataBlock->explosion->getName()); + delete pExplosion; + pExplosion = NULL; + } else { + if (mEndedWithDecal == true && gClientSceneGraph->getCurrentDecalManager() && mDataBlock->numDecals != 0) { + // DMM: randomly choose from [0 .. numDecals - 1]; + if (mDataBlock->decalData[0]) { + gClientSceneGraph->getCurrentDecalManager()->addDecal(mExplosionPosition, + mExplosionNormal, + mDataBlock->decalData[0]); + } + } + } + } + } + } + + addToScene(); + + return true; +} + + +void LinearProjectile::onRemove() +{ + if (isClientObject()) + updateSound(Point3F(0, 0, 0), Point3F(0, 0, 0), false); + removeFromScene(); + + Parent::onRemove(); +} + + +bool LinearProjectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//---------------------------------------------------------------------------- +bool LinearProjectile::updatePos(const Point3F& oldPos, + const Point3F& newPos, + F32* hitDelta, + Point3F* hitPos, + Point3F* hitNormal, + SceneObject*& hitObject) +{ + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->disableCollision(); + if(mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->disableCollision(); + + RayInfo rInfo; + if (mContainer->castRay(oldPos, newPos, csmDynamicCollisionMask, &rInfo) == true) { + *hitPos = rInfo.point; + *hitNormal = rInfo.normal; + *hitDelta = rInfo.t; + hitObject = rInfo.object; + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->enableCollision(); + if(mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->enableCollision(); + return false; + } else { + *hitDelta = 1.0; + hitObject = NULL; + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->enableCollision(); + if(mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->enableCollision(); + return true; + } +} + + +//-------------------------------------------------------------------------- +void LinearProjectile::explode(const Point3F& p, const Point3F& n, const bool dynamicObject) +{ + // don't explode if projectile dies on water impact - play big splash instead + if( mHitWater && !mDataBlock->explodeOnWaterImpact ) + { + mHidden = true; + return; + } + + // Already blown up + if (mHidden == true) + return; + mHidden = true; + + if (isServerObject()) { + mExplosionPosition = p + (n * 0.01); + mExplosionNormal = n; + + // do radius damage if appropriate + char posBuffer[128]; + char fadeBuffer[128]; + dSprintf(posBuffer, sizeof(posBuffer), "%f %f %f", mExplosionPosition.x, + mExplosionPosition.y, + mExplosionPosition.z); + dSprintf(fadeBuffer, sizeof(fadeBuffer), "%f", mFadeValue); + + Con::executef(mDataBlock, 4, "onExplode", scriptThis(), posBuffer, fadeBuffer); + + if (dynamicObject) + setMaskBits(ExplosionMask); + } else { + // Client just plays the explosion at the right place... + // + Explosion* pExplosion = new Explosion; + + F32 waterHeight; + if( pointInWater( (Point3F &)p, &waterHeight ) && mDataBlock->underwaterExplosion ) + { + F32 depth = waterHeight - p.z; + if( depth >= mDataBlock->depthTolerance ) + { + pExplosion->onNewDataBlock(mDataBlock->underwaterExplosion); + } + else + { + pExplosion->onNewDataBlock(mDataBlock->explosion); + } + } + else + { + if (mDataBlock->explosion) + { + pExplosion->onNewDataBlock(mDataBlock->explosion); + } + } + + if( pExplosion ) + { + MatrixF xform(true); + xform.setPosition(p); + pExplosion->setTransform(xform); + pExplosion->setInitialState(p, n); + if (pExplosion->registerObject() == false) + { + Con::errorf(ConsoleLogEntry::General, "LinearProjectile(%s)::explode: couldn't register explosion", + mDataBlock->getName() ); + delete pExplosion; + pExplosion = NULL; + } + + if (mEndedWithDecal == true && gClientSceneGraph->getCurrentDecalManager() && mDataBlock->numDecals != 0) { + // DMM: randomly choose from [0 .. numDecals - 1]; + if (mDataBlock->decalData[0]) + gClientSceneGraph->getCurrentDecalManager()->addDecal(p, n, mDataBlock->decalData[0]); + } + } + + // Client object + updateSound(Point3F(0, 0, 0), Point3F(0, 0, 0), false); + } +} + + +//-------------------------------------------------------------------------- +Point3F LinearProjectile::getVelocity() const +{ + return deriveExactVelocity(mCurrTick); +} + + +//-------------------------------------------------------------------------- +void LinearProjectile::processTick(const Move* move) +{ + Parent::processTick(move); + AssertFatal(mCurrTick >= 1, "Hm, bad assumption somewhere"); + + if (isServerObject()) { + if (mCurrTick >= mDeleteTick) { + deleteObject(); + return; + } + else if (mHidden == true) { + // Already exploded + return; + } + else if (((mCurrTick - 1) * TickMs) >= mSegments[mNumSegments - 1].msEnd) { + if (mSegments[mNumSegments - 1].cutShort == true && (mSegments[mNumSegments - 1].endTypeMask & csmDecalMask)) + mEndedWithDecal = true; + + if (mSegments[mNumSegments - 1].cutShort == true || mDataBlock->explodeOnDeath == true) + { + SceneObject *so = mSegments[mNumSegments-1].hitObj; + if( so ) + { + Point3F normal = mSegments[mNumSegments-1].endNormal; + onCollision( mSegments[mNumSegments-1].end, normal, so ); + } + explode(mSegments[mNumSegments - 1].end, mSegments[mNumSegments - 1].endNormal, false); + } + else + { + // End of the line without explosion. Just delete the object. If we don't, the + // current unpackUpdate logic can break the client, since it interprets hidden + // on initial update to mean: explosion. + mHidden = true; + deleteObject(); + } + + return; + } + + // Otherwise update our position + Point3F oldPos; + getTransform().getColumn(3, &oldPos); + Point3F newPos = deriveExactPosition(mCurrTick); + + F32 hitDelta; + Point3F hitPos, hitNormal; + SceneObject* hitObject; + if (updatePos(oldPos, newPos, &hitDelta, &hitPos, &hitNormal, hitObject) == false) { + // Hit something dynamic in this pass + + MatrixF xform(true); + xform.setColumn(3, hitPos); + setTransform(xform); + + onCollision(hitPos, hitNormal, hitObject); + + if (hitObject->getTypeMask() & csmDecalMask) + mEndedWithDecal = true; + explode(hitPos, hitNormal, true); + } else { + // No hit, continue on... + MatrixF xform(true); + xform.setColumn(3, newPos); + setTransform(xform); + } + } else { + if( mCurrTick >= mSplashTick ) + { + createSplash(); + } + + if (mHidden == true) + return; + else if (((mCurrTick - 1) * TickMs) >= mSegments[mNumSegments - 1].msEnd) { + if (mSegments[mNumSegments - 1].cutShort == true && (mSegments[mNumSegments - 1].endTypeMask & csmDecalMask)) + mEndedWithDecal = true; + + if (mSegments[mNumSegments - 1].cutShort == true || mDataBlock->explodeOnDeath == true) { + explode(mSegments[mNumSegments - 1].end, mSegments[mNumSegments - 1].endNormal, false); + } else { + mHidden = true; + } + + return; + } + + Point3F currPos; + Point3F nextPos; + + if (mWarpTicksRemaining) { + currPos = mWarpStart * (mWarpTicksRemaining) + + mWarpEnd * (smProjectileWarpTicks - mWarpTicksRemaining); + nextPos = mWarpStart * (mWarpTicksRemaining - 1) + + mWarpEnd * (smProjectileWarpTicks - mWarpTicksRemaining + 1); + + currPos /= smProjectileWarpTicks; + nextPos /= smProjectileWarpTicks; + + mWarpTicksRemaining--; + } + else { + // Normal update... + currPos = deriveExactPosition(mCurrTick - 1); + nextPos = deriveExactPosition(mCurrTick); + } + + Point3F partVel = deriveExactVelocity(mCurrTick); + if (partVel.isZero()) + partVel = mSegments[mNumSegments - 1].segmentVel; + + emitParticles(currPos, nextPos, partVel, TickMs); + + // Check for collision + F32 hitDelta; + Point3F hitPos, hitNormal; + SceneObject* hitObject; + if (mDataBlock->doDynamicClientHits == true && + updatePos(currPos, nextPos, &hitDelta, &hitPos, &hitNormal, hitObject) == false) { + if (hitObject->getTypeMask() & csmDecalMask) + mEndedWithDecal = true; + + // Hit something dynamic in this pass + explode(hitPos, hitNormal, true); + + mCurrBackDelta = currPos - hitPos; + mCurrDeltaBase = hitPos; + mSegments[0].msEnd = (U32)(mCurrTick * TickMs - (hitDelta * TickMs)); + mSegments[1].msEnd = (U32)(mCurrTick * TickMs - (hitDelta * TickMs)); + } + else { + // No hit, continue on... + mCurrBackDelta = currPos - nextPos; + mCurrDeltaBase = nextPos; + } + MatrixF temp(true); + temp.setColumn(3, mCurrDeltaBase); + setTransform(temp); + + // If we're past our activate time, create our activate sequence + if (mDataBlock->activateDelayMS != -1 && mProjectileShape != NULL && + mCurrTick * TickMs >= mDataBlock->activateDelayMS && + !mActivateThread) { + mActivateThread = mProjectileShape->addThread(); + mProjectileShape->setTimeScale(mActivateThread, 1); + mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0); + } + } +} + +void LinearProjectile::interpolateTick(F32 delta) +{ + Parent::interpolateTick(delta); + + // Client object. Must check to see if there are any warp ticks left... + if (mHidden == true) + return; + + Point3F newPos = mCurrDeltaBase + (mCurrBackDelta * delta); + MatrixF xform(true); + + Point3F dir = deriveExactVelocity(getMax(S32(mCurrTick) - 1, 0)); + if( dir.isZero() ) + { + dir.set( 0.0, 0.0, 1.0 ); // odd but prevents crash + } + else + { + dir.normalize(); + } + + xform = MathUtils::createOrientFromDir( dir ); + xform.setPosition( newPos ); + setRenderTransform( xform ); + + + S32 currMs = (S32)(mCurrTick * TickMs - (delta * TickMs)); + if (currMs > mDataBlock->fizzleTimeMS) { + U32 fizzle = currMs - mDataBlock->fizzleTimeMS; + mFadeValue = 1.0 - (F32(fizzle) / + F32(mDataBlock->lifetimeMS - mDataBlock->fizzleTimeMS)); + } else { + mFadeValue = 1.0; + } + + updateSound(newPos, deriveExactVelocity(mCurrTick), true); +} + +void LinearProjectile::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); + } + } +} + + +//-------------------------------------------------------------------------- +U32 LinearProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & GameBase::InitialUpdateMask)) { + // Initial update + + // Write: initialPosition + // initialVector (compressed to 10b standard) + // currServerTime + // if (sourceId) + // sourceId + // sourceSlot + if (stream->writeFlag(mHidden) == false) { + con->writeCompressed(stream, mInitialPosition); + stream->writeNormalVector(mInitialDirection, InitialDirectionBits); + stream->writeRangedU32(mCurrTick, 0, MaxLivingTicks); + + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + S32 ghostIndex = -1; + if (bool(mSourceObject)) + ghostIndex = con->getGhostIndex(mSourceObject); + + if (stream->writeFlag(ghostIndex != -1)) + { + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount - 1); + stream->writeRangedU32(U32(mSourceObjectSlot), + 0, ShapeBase::MaxMountedImages - 1); + + if (stream->writeFlag(mExcessVel != 0)) + { + stream->writeRangedU32(mExcessVel, 0, 255); + stream->writeNormalVector(mExcessDir, ExcessVelDirBits); + } + } + + if (bool(mVehicleObject)) { + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + S32 ghostIndex = con->getGhostIndex(mVehicleObject); + if (stream->writeFlag(ghostIndex != -1)) { + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount - 1); + } + } else { + stream->writeFlag(false); + } + } else { + // We've already exploded. Write out our explosion position and normal... + con->writeCompressed(stream, mExplosionPosition); + stream->writeNormalVector(mExplosionNormal, InitialDirectionBits); + stream->writeFlag(mEndedWithDecal); + } + } else { + // Explosion update + AssertFatal((mask & ExplosionMask) != 0, + "LinearProjectile::packUpdate: only two types of packets, initial and explosion, this is neither?"); + con->writeCompressed(stream, mExplosionPosition); + stream->writeNormalVector(mExplosionNormal, InitialDirectionBits); + stream->writeFlag(mEndedWithDecal); + } + + return retMask; +} + +void LinearProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) { + // Initial update + if (stream->readFlag() == false) { + con->readCompressed(stream, &mInitialPosition); + stream->readNormalVector(&mInitialDirection, InitialDirectionBits); + mCurrTick = stream->readRangedU32(0, MaxLivingTicks); + + if (stream->readFlag()) { + mSourceObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount - 1); + mSourceObjectSlot = stream->readRangedU32(0, ShapeBase::MaxMountedImages - 1); + + NetObject* pObject = con->resolveGhost(mSourceObjectId); + if (pObject != NULL) + mSourceObject = dynamic_cast(pObject); + + if (bool(mSourceObject) == false) + Con::errorf(ConsoleLogEntry::General, "LinearProjectile::unpackUpdate: could not resolve source ghost properly on initial update"); + + if (stream->readFlag()) { + mExcessVel = stream->readRangedU32(0, 255); + stream->readNormalVector(&mExcessDir, ExcessVelDirBits); + } else { + mExcessDir.set(0, 0, 1); + mExcessVel = 0; + } + mExcessDirDumb = mExcessDir; + } else { + mSourceObjectId = -1; + mSourceObjectSlot = -1; + mSourceObject = NULL; + + mExcessDir.set(0, 0, 1); + mExcessDirDumb = mExcessDir; + mExcessVel = 0; + } + + if (stream->readFlag()) { + mVehicleObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount - 1); + NetObject* pObject = con->resolveGhost(mVehicleObjectId); + if (pObject != NULL) + mVehicleObject = dynamic_cast(pObject); + if (bool(mVehicleObject) == false) + Con::errorf(ConsoleLogEntry::General, "LinearProjectile::unpackUpdate: could not resolve source ghost properly on initial update"); + } else { + mVehicleObjectId = 0; + mVehicleObject = NULL; + } + } else { + mHidden = true; + con->readCompressed(stream, &mExplosionPosition); + stream->readNormalVector(&mExplosionNormal, InitialDirectionBits); + mInitialPosition = mExplosionPosition + mExplosionNormal; + mInitialDirection = - mExplosionNormal; + + mEndedWithDecal = stream->readFlag(); + } + } else { + // Explosion notification + con->readCompressed(stream, &mExplosionPosition); + stream->readNormalVector(&mExplosionNormal, InitialDirectionBits); + mEndedWithDecal = stream->readFlag(); + explode(mExplosionPosition, mExplosionNormal, false); + } +} + + +//-------------------------------------------------------------------------- +Point3F LinearProjectile::deriveExactPosition(const U32 tick) const +{ + U32 currMs = tick * TickMs; + + // The hard way... + if (currMs == 0) { + return mSegments[0].start; + } else if (currMs <= mSegments[0].msEnd) { + return mSegments[0].start + (mSegments[0].segmentVel * (F32(currMs) / 1000.0f)); + } else if (mNumSegments > 1 && currMs <= mSegments[1].msEnd) { + AssertFatal(currMs > mSegments[1].msStart, "Error, should have been greater here"); + return mSegments[1].start + (mSegments[1].segmentVel * (F32(currMs - mSegments[1].msStart) / 1000.0f)); + } else { + if (mNumSegments == 1) { + return mSegments[0].end; + } else { + return mSegments[1].end; + } + } +} + +//-------------------------------------------------------------------------- +Point3F LinearProjectile::deriveExactVelocity(const U32 tick) const +{ + U32 currMs = tick * TickMs; + + if (currMs < mSegments[0].msEnd) + return mSegments[0].segmentVel; + else if (mNumSegments > 1 && currMs < mSegments[1].msEnd) + return mSegments[1].segmentVel; + else { + return Point3F(0, 0, 0); + } +} + +//-------------------------------------------------------------------------- +void LinearProjectile::calcSplash() +{ + Point3F vel = mInitialDirection; + + if( mWetStart ) + { + vel *= mDataBlock->wetVelocity; + } + else + { + vel *= mDataBlock->dryVelocity; + } + + // If the hidden flag is set at this point then the server has indicated that the + // collision point is so close that the projectile has already hit and exploded. + // It also means the mExcess variables haven't come down to the client and are invalid + if( !mHidden ) + { + vel += mExcessDirDumb * F32(mExcessVel); + } + + Point3F start = mInitialPosition; + Point3F end = mInitialPosition + vel * (F32(mDataBlock->lifetimeMS) / 1000.0); + + bool startUnder = pointInWater( start ); + bool endUnder = pointInWater( end ); + + mSplashTick = U32(-1); + + if( startUnder && endUnder ) + { + return; + } + + RayInfo rInfo; + + if( startUnder ) + { + if( gClientContainer.castRay(end, start, WaterObjectType, &rInfo) ) + { + mSplashPos = rInfo.point; + mSplashTick = U32(mDataBlock->lifetimeMS * (1.0 - rInfo.t) / TickMs); + } + } + else + { + if( gClientContainer.castRay(start, end, WaterObjectType, &rInfo) ) + { + mSplashPos = rInfo.point; + mSplashTick = U32(mDataBlock->lifetimeMS * rInfo.t / TickMs); + } + } +} + +//-------------------------------------------------------------------------- +void LinearProjectile::renderObject(SceneState* state, SceneRenderImage* sri) +{ + if( !mHitWater || !mPlayedSplash ) + { + Parent::renderObject( state, sri ); + } +} + +//-------------------------------------------------------------------------- +void LinearProjectile::createSplash() +{ + if( mDataBlock->splash && !mPlayedSplash ) + { + MatrixF trans = getTransform(); + trans.setPosition( mSplashPos ); + 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; + + mPlayedSplash = true; + } +} diff --git a/game/linearProjectile.h b/game/linearProjectile.h new file mode 100644 index 0000000..2e1f689 --- /dev/null +++ b/game/linearProjectile.h @@ -0,0 +1,181 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LINEARPROJECTILE_H_ +#define _LINEARPROJECTILE_H_ + +#ifndef _PROJECTILE_H_ +#include "game/projectile.h" +#endif + +class TSThread; + +//-------------------------------------------------------------------------- +class LinearProjectileData : public ProjectileData +{ + typedef ProjectileData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + F32 dryVelocity; + F32 wetVelocity; + + S32 fizzleTimeMS; + S32 lifetimeMS; + bool explodeOnDeath; + + F32 reflectOnWaterImpactAngle; + F32 deflectionOnWaterImpact; + S32 fizzleUnderwaterMS; + + S32 activateDelayMS; + bool doDynamicClientHits; + //-------------------------------------- load set variables + public: + S32 activateSeq; + S32 maintainSeq; + + public: + LinearProjectileData(); + ~LinearProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + bool calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& sourceVel, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime); + + DECLARE_CONOBJECT(LinearProjectileData); + static void initPersistFields(); + static void consoleInit(); +}; + + +//-------------------------------------------------------------------------- +class LinearProjectile : public Projectile +{ + typedef Projectile Parent; + + private: + LinearProjectileData* mDataBlock; + + bool mWetStart; + U32 mSplashTick; + Point3F mSplashPos; + + public: + enum LPConstants { + InitialDirectionBits = 10, + + MaxLivingTicks = 511 + }; + + enum LPUpdateMasks { + ExplosionMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + protected: + struct Segment { + Point3F start; + Point3F end; + Point3F segmentVel; + + bool cutShort; + U32 endTypeMask; + Point3F endNormal; + + U32 msStart; + U32 msEnd; + + SceneObject *hitObj; + + Segment() + { + dMemset( this, 0, sizeof( Segment ) ); + } + + }; + + bool calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime); + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + virtual void renderObject(SceneState*, SceneRenderImage*); + + bool updatePos(const Point3F&, const Point3F&, F32*, Point3F*, Point3F*, SceneObject*&); + void processTick(const Move*); + void interpolateTick(F32); + void advanceTime(F32); + + Point3F mExplosionPosition; + Point3F mExplosionNormal; + bool mEndedWithDecal; + static const U32 csmDecalMask; + + void explode(const Point3F& p, const Point3F& n, const bool dynamicObject); + + Point3F getVelocity() const; + + void calcSplash(); + void createSplash(); + bool determineWetStart(); + + // Only valid on tick boundaries... + Point3F deriveExactPosition(const U32 tick) const; + Point3F deriveExactVelocity(const U32 tick) const; + + Segment mSegments[2]; + U32 mNumSegments; + U32 mDeleteTick; + bool createSegments(); // sets the above variables + + // Warping and back delta variables. Only valid on the client + // + Point3F mWarpStart; + Point3F mWarpEnd; + U32 mWarpTicksRemaining; + + Point3F mCurrDeltaBase; + Point3F mCurrBackDelta; + + // activate/maintain threads + TSThread* mActivateThread; + TSThread* mMaintainThread; + + bool mHitWater; + bool mPlayedSplash; + + public: + LinearProjectile(); + ~LinearProjectile(); + + DECLARE_CONOBJECT(LinearProjectile); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_LINEARPROJECTILE + diff --git a/game/main.cc b/game/main.cc new file mode 100644 index 0000000..47204ab --- /dev/null +++ b/game/main.cc @@ -0,0 +1,713 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformVideo.h" +#include "platform/platformInput.h" +#include "platform/platformAudio.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "core/tVector.h" +#include "math/mMath.h" +#include "dgl/dgl.h" +#include "dgl/gBitmap.h" +#include "core/resManager.h" +#include "core/fileStream.h" +#include "dgl/gTexManager.h" +#include "dgl/gFont.h" +#include "console/console.h" +#include "console/simBase.h" +#include "gui/guiCanvas.h" +#include "sim/actionMap.h" +#include "core/dnet.h" +#include "game/game.h" +#include "core/bitStream.h" +#include "console/telnetConsole.h" +#include "console/telnetDebugger.h" +#include "console/consoleTypes.h" +#include "math/mathTypes.h" +#include "dgl/gTexManager.h" +#include "core/resManager.h" +#include "interior/interiorRes.h" +#include "interior/interiorInstance.h" +#include "ts/tsShapeInstance.h" +#include "terrain/terrData.h" +#include "terrain/terrRender.h" +#include "editor/terraformer.h" +#include "scenegraph/sceneGraph.h" +#include "dgl/materialList.h" +#include "scenegraph/sceneRoot.h" +#include "game/moveManager.h" +#include "platform/platformVideo.h" +#include "dgl/materialPropertyMap.h" +#include "sim/netStringTable.h" +#include "sim/pathManager.h" +#include "game/GameFunctions.h" +#include "game/particleEngine.h" +#include "game/targetManager.h" +#include "platform/platformRedBook.h" +#include "game/tribesGame.h" +#include "game/netDispatch.h" +#include "sim/decalManager.h" +#include "sim/frameAllocator.h" +#include "scenegraph/detailManager.h" +#include "interior/interiorLMManager.h" +#include "game/version.h" +#include "platform/profiler.h" +#include "game/shapeBase.h" +//#define IHVBUILD +#ifdef IHVBUILD +#include "ihvSignature.h" +#include "crypt/cryptSHA1.h" +#endif + +#ifndef BUILD_TOOLS +TribesGame GameObject; +#endif + +extern ResourceInstance *constructTerrainFile(Stream &stream); +extern ResourceInstance *constructTSShape(Stream &); + +IMPLEMENT_CONOBJECT(Terraformer); + +static void cLockMouse(SimObject *, S32, const char **argv) +{ + Platform::setWindowLocked(dAtob(argv[1])); +} + +static void cSetNetPort(SimObject *, S32, const char **argv) +{ + Net::openPort(dAtoi(argv[1])); +} + +static bool cCreateCanvas(SimObject *, S32, const char **) +{ + AssertISV(!Canvas, "cCreateCanvas: canvas has already been instantiated"); + +#ifndef TARG_MACCARB // macs can only run one instance in general. +#if !defined(DEBUG) && !defined(INTERNAL_RELEASE) + if(!Platform::excludeOtherInstances("Tribes2Game")) + return false; +#endif +#endif + Platform::initWindow(Point2I(800, 600), "Tribes 2"); + + // create the canvas, and add it to the manager + Canvas = new GuiCanvas(); + Canvas->registerObject("Canvas"); // automatically adds to GuiGroup + return true; +} + +static void cSaveJournal(SimObject *, S32, const char **argv) +{ + Game->saveJournal(argv[1]); +} + +static void cLoadJournal(SimObject *, S32, const char **argv) +{ + Game->loadJournal(argv[1]); +} + +static void cPlayJournal(SimObject *, S32, const char **argv) +{ + Game->playJournal(argv[1]); +} + +extern void AIInit(); +extern void netInit(); +extern void clientNetProcess(); +extern void serverNetProcess(); +extern void processConnectedReceiveEvent( ConnectedReceiveEvent * event ); +extern void processConnectedNotifyEvent( ConnectedNotifyEvent * event ); +extern void processConnectedAcceptEvent( ConnectedAcceptEvent * event ); +extern void ShowInit(); + +static bool initLibraries() +{ + if(!Net::init()) + { + Platform::AlertOK("Network Error", "Unable to initialize the network... aborting."); + return false; + } + + // asserts should be created FIRST + PlatformAssert::create(); + + FrameAllocator::init(3 << 20); // 3 meg frame allocator buffer + +// // Cryptographic pool next +// CryptRandomPool::init(); + + _StringTable::create(); + TextureManager::create(); + ResManager::create(); + + // Register known file types here + ResourceManager->registerExtension(".jpg", constructBitmapJPEG); + ResourceManager->registerExtension(".png", constructBitmapPNG); + ResourceManager->registerExtension(".gif", constructBitmapGIF); + ResourceManager->registerExtension(".dbm", constructBitmapDBM); + ResourceManager->registerExtension(".bmp", constructBitmapBMP); + ResourceManager->registerExtension(".bm8", constructBitmapBM8); + ResourceManager->registerExtension(".gft", constructFont); + ResourceManager->registerExtension(".dif", constructInteriorDIF); + ResourceManager->registerExtension(".ter", constructTerrainFile); + ResourceManager->registerExtension(".dts", constructTSShape); + ResourceManager->registerExtension(".dml", constructMaterialList); + + RegisterCoreTypes(); + RegisterMathTypes(); + RegisterGuiTypes(); + + Con::init(); + NetStringTable::create(); + + RegisterMathFunctions(); + RegisterGameFunctions(); + TelnetConsole::create(); + TelnetDebugger::create(); + + Processor::init(); + Math::init(); + Platform::init(); // platform specific initialization + Audio::init(); + MathConsoleInit(); + InteriorLMManager::init(); + InteriorInstance::init(); + TSShapeInstance::init(); + RedBook::init(); + + return true; +} + + +// shut down +static void shutdownLibraries() +{ + // Purge any resources on the timeout list... + if (ResourceManager) + ResourceManager->purge(); + + RedBook::destroy(); + TSShapeInstance::destroy(); + InteriorInstance::destroy(); + InteriorLMManager::destroy(); + + Audio::destroy(); + TextureManager::preDestroy(); + + Platform::shutdown(); + TelnetDebugger::destroy(); + TelnetConsole::destroy(); + + NetStringTable::destroy(); + Con::shutdown(); + + ResManager::destroy(); + TextureManager::destroy(); + + _StringTable::destroy(); + +// CryptRandomPool::destroy(); + + // asserts should be destroyed LAST + FrameAllocator::destroy(); + + PlatformAssert::destroy(); + Net::shutdown(); +} + +const char *defaultPaths[] = { // searched left to right +"base" +}; + +static void cRebuildModPaths(SimObject *, S32, const char **) +{ + ResourceManager->setModPaths(sizeof(defaultPaths)/sizeof(char*), defaultPaths); +} + +static void cSetModPaths(SimObject*, S32, const char** argv) +{ + char buf[1024]; + + dStrncpy(buf, argv[1], 1023-5); + buf[1023-5] = '\0'; + + // if base is not the root path then add it. + int len = dStrlen(buf); + if (!((len == 4 && dStricmp(buf, "base")) || + (len > 4 && (dStrnicmp(buf+len-5, ";base", 5)) )) ) + dStrcat(buf, ";base"); + + Vector paths; + char* temp = dStrtok( buf, ";" ); + while ( temp ) + { + if ( temp[0] ) + paths.push_back( temp ); + + temp = dStrtok( NULL, ";" ); + } + + ResourceManager->setModPaths( paths.size(), (const char**) paths.address() ); +} + +static const char* cGetModPaths(SimObject*, S32, const char**) +{ + return( ResourceManager->getModPaths() ); +} + +static S32 cGetVersionNumber(SimObject *, S32, const char **) +{ + return getVersionNumber(); +} + +static S32 cGetSimTime(SimObject *, S32, const char **) +{ + return Sim::getCurrentTime(); +} + +static S32 cGetRealTime( SimObject*, S32, const char** ) +{ + return Platform::getRealMilliseconds(); +} + +static F32 gTimeScale = 1.0; +static U32 gTimeAdvance = 0; +static U32 gFrameSkip = 0; +static U32 gFrameCount = 0; + + +bool initGame() +{ + Con::addCommand("getVersionNumber", cGetVersionNumber, "getVersionNumber()", 1, 1); + + Con::addCommand("getSimTime", cGetSimTime, "getSimTime();", 1, 1); + Con::addCommand("getRealTime", cGetRealTime, "getRealTime()'", 1, 1); + Con::addCommand("setNetPort", cSetNetPort, "setNetPort(port);", 2, 2); + Con::addCommand("lockMouse", cLockMouse, "lockMouse(isLocked);", 2, 2); + Con::addCommand("rebuildModPaths", cRebuildModPaths, "rebuildModPaths();", 1, 1); + Con::addCommand("setModPaths", cSetModPaths, "setModPaths( paths )", 2, 2 ); + Con::addCommand("getModPaths", cGetModPaths, "getModPaths()", 1, 1 ); + Con::addCommand("createCanvas", cCreateCanvas, "createCanvas();", 1, 1); + + Con::addCommand("saveJournal", cSaveJournal, "saveJournal(jname);", 2, 2); + Con::addCommand("loadJournal", cLoadJournal, "loadJournal(jname);", 2, 2); +// Con::addCommand("playJournal", cPlayJournal, "playJournal(jname);", 2, 2); + + Con::setFloatVariable("Video::texResidentPercentage", -1.0f); + Con::setIntVariable("Video::textureCacheMisses", -1); + Con::addVariable("timeScale", TypeF32, &gTimeScale); + Con::addVariable("timeAdvance", TypeS32, &gTimeAdvance); + Con::addVariable("frameSkip", TypeS32, &gFrameSkip); + +#ifdef GATHER_METRICS + Con::addVariable("Video::numTexelsLoaded", TypeS32, &TextureManager::smTextureSpaceLoaded); +#else + static U32 sBogusNTL = 0; + Con::addVariable("Video::numTexelsLoaded", TypeS32, &sBogusNTL); +#endif + + ResourceManager->setModPaths(sizeof(defaultPaths)/sizeof(char*), defaultPaths); + TerrainRender::init(); + + netInit(); + dispatchInit(); + GameInit(); + ShowInit(); + AIInit(); + MoveManager::init(); + + Sim::init(); + + ActionMap* globalMap = new ActionMap; + globalMap->registerObject("GlobalActionMap"); + Sim::getActiveActionMapSet()->pushObject(globalMap); + + MaterialPropertyMap *map = new MaterialPropertyMap; + + map->registerObject("MaterialPropertyMap"); + Sim::getRootGroup()->addObject(map); + + gClientSceneGraph = new SceneGraph(true); + gClientSceneRoot = new SceneRoot; + gClientSceneGraph->addObjectToScene(gClientSceneRoot); + gServerSceneGraph = new SceneGraph(false); + gServerSceneRoot = new SceneRoot; + gServerSceneGraph->addObjectToScene(gServerSceneRoot); + gDecalManager = new DecalManager; + gClientContainer.addObject(gDecalManager); + gClientSceneGraph->addObjectToScene(gDecalManager); + + DetailManager::init(); + PathManager::init(); + ParticleEngine::init(); + TargetManager::create(); + + //execute the console.cs script + FileStream str; + if(!str.open("main.cs", FileStream::Read)) + return false; + + U32 size = str.getStreamSize(); + char *script = new char[size + 1]; + str.read(size, script); + str.close(); + + script[size] = 0; + Con::executef(2, "eval", script); + delete[] script; + return true; +} + +void shutdownGame() +{ + //exec the script onExit() function + Con::executef(1, "onExit"); + + ParticleEngine::destroy(); + PathManager::destroy(); + DetailManager::shutdown(); + + // 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(); + + gClientSceneGraph->removeObjectFromScene(gDecalManager); + gClientContainer.removeObject(gDecalManager); + gClientSceneGraph->removeObjectFromScene(gClientSceneRoot); + gServerSceneGraph->removeObjectFromScene(gServerSceneRoot); + delete gClientSceneRoot; + delete gServerSceneRoot; + delete gClientSceneGraph; + delete gServerSceneGraph; + delete gDecalManager; + gClientSceneRoot = NULL; + gServerSceneRoot = NULL; + gClientSceneGraph = NULL; + gServerSceneGraph = NULL; + gDecalManager = NULL; + + TargetManager::destroy(); + TerrainRender::shutdown(); +} + +extern bool gDGLRender; +bool gShuttingDown = false; + +int TribesGame::main(int argc, const char **argv) +{ +// if (argc == 1) { +// static const char* argvFake[] = { "dtest.exe", "-jload", "test.jrn" }; +// argc = 3; +// argv = argvFake; +// } + +// Memory::enableLogging("testMem.log"); +// Memory::setBreakAlloc(104717); + + if(!initLibraries()) + return 0; + +#ifdef IHVBUILD + char* pVer = new char[sgVerStringLen + 1]; + U32 hi; + for (hi = 0; hi < sgVerStringLen; hi++) + pVer[hi] = sgVerString[hi] ^ 0xFF; + pVer[hi] = '\0'; + + SHA1Context hashCTX; + hashCTX.init(); + hashCTX.hashBytes(pVer, sgVerStringLen); + hashCTX.finalize(); + + U8 hash[20]; + hashCTX.getHash(hash); + + for (hi = 0; hi < 20; hi++) + if (U8(hash[hi]) != U8(sgHashVer[hi])) + return 0; +#endif + + // 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]); + if (initGame() == false) + return 0; + +#ifdef IHVBUILD + char* pPrint = new char[dStrlen(sgVerPrintString) + 1]; + for (U32 pi = 0; pi < dStrlen(sgVerPrintString); pi++) + pPrint[pi] = sgVerPrintString[pi] ^ 0xff; + pPrint[dStrlen(sgVerPrintString)] = '\0'; + + Con::printf(""); + Con::errorf(ConsoleLogEntry::General, pPrint, pVer); + delete [] pVer; +#endif + + +// extern void QGL_EnableLogging(bool); +// QGL_EnableLogging(true); + + while(Game->isRunning()) + { + PROFILE_START(MainLoop); + Game->journalProcess(); + //if(Game->getJournalMode() != GameInterface::JournalLoad) + //{ + Net::process(); // read in all events + Platform::process(); // keys, etc. + TelConsole->process(); + TelDebugger->process(); + TimeManager::process(); // guaranteed to produce an event + //} + PROFILE_END(); + } + shutdownGame(); + shutdownLibraries(); + + gShuttingDown = true; +// QGL_EnableLogging(false); + +#if 0 +// tg: Argh! This should OS version check should be part of Platform, not here... +// + // check os + OSVERSIONINFO osInfo; + dMemset(&osInfo, 0, sizeof(OSVERSIONINFO)); + osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + // see if osversioninfoex fails + if(!GetVersionEx((OSVERSIONINFO*)&osInfo)) + { + osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if(!GetVersionEx((OSVERSIONINFO*)&osInfo)) + return 0; + } + + // terminate the process if win95 only! + if((osInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && // 95, 98, ME + (osInfo.dwMajorVersion == 4) && // 95, 98, ME, NT + (osInfo.dwMinorVersion == 0)) // 95 + { + AssertWarn(0, "Forcing termination of app (Win95)! Upgrade your OS now!"); + TerminateProcess(GetCurrentProcess(), 0xffffffff); + } +#endif + + return 0; +} + + +static bool serverTick = false; + + +static F32 fpsRealStart; +static F32 fpsRealLast; +//static F32 fpsRealTotal; +static F32 fpsReal; +static F32 fpsVirtualStart; +static F32 fpsVirtualLast; +//static F32 fpsVirtualTotal; +static F32 fpsVirtual; +static F32 fpsFrames; +static F32 fpsNext; +static bool fpsInit = false; +const F32 UPDATE_INTERVAL = 0.25f; + +//-------------------------------------- +void fpsReset() +{ + fpsRealStart = (F32)Platform::getRealMilliseconds()/1000.0f; // Real-World Tick Count + fpsVirtualStart = (F32)Platform::getVirtualMilliseconds()/1000.0f; // Engine Tick Count (does not vary between frames) + fpsNext = fpsRealStart + UPDATE_INTERVAL; + +// fpsRealTotal= 0.0f; + fpsRealLast = 0.0f; + fpsReal = 0.0f; +// fpsVirtualTotal = 0.0f; + fpsVirtualLast = 0.0f; + fpsVirtual = 0.0f; + fpsFrames = 0; + fpsInit = true; +} + +//-------------------------------------- +void fpsUpdate() +{ + if (!fpsInit) + fpsReset(); + + const float alpha = 0.07f; + F32 realSeconds = (F32)Platform::getRealMilliseconds()/1000.0f; + F32 virtualSeconds = (F32)Platform::getVirtualMilliseconds()/1000.0f; + + fpsFrames++; + if (fpsFrames > 1) + { + fpsReal = fpsReal*(1.0-alpha) + (realSeconds-fpsRealLast)*alpha; + fpsVirtual = fpsVirtual*(1.0-alpha) + (virtualSeconds-fpsVirtualLast)*alpha; + } +// fpsRealTotal = fpsFrames/(realSeconds - fpsRealStart); +// fpsVirtualTotal = fpsFrames/(virtualSeconds - fpsVirtualStart); + + fpsRealLast = realSeconds; + fpsVirtualLast = virtualSeconds; + + // update variables every few frames + F32 update = fpsRealLast - fpsNext; + if (update > 0.5f) + { +// Con::setVariable("fps::realTotal", avar("%4.1f", fpsRealTotal)); +// Con::setVariable("fps::virtualTotal", avar("%4.1f", fpsVirtualTotal)); + Con::setVariable("fps::real", avar("%4.1f", 1.0f/fpsReal)); + Con::setVariable("fps::virtual", avar("%4.1f", 1.0f/fpsVirtual)); + if (update > UPDATE_INTERVAL) + fpsNext = fpsRealLast + UPDATE_INTERVAL; + else + fpsNext += UPDATE_INTERVAL; + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- NOTE: Entropy distilling occurs in this +// function. Talk to dmoore for details. +// + + +void TribesGame::processMouseMoveEvent(MouseMoveEvent * mEvent) +{ +// CryptRandomPool::submitEntropy(mEvent->xPos, 2); // Take the least significant 2 bits of the mouse pos +// CryptRandomPool::submitEntropy(mEvent->yPos, 2); + if (Canvas) + Canvas->processMouseMoveEvent(mEvent); +} + +void TribesGame::processInputEvent(InputEvent *event) +{ + PROFILE_START(ProcessInputEvent); + if (!ActionMap::handleEventGlobal(event)) + { + // Other input consumers here... + if (!(Canvas && Canvas->processInputEvent(event))) + ActionMap::handleEvent(event); + } + PROFILE_END(); +} + +void TribesGame::processQuitEvent() +{ + setRunning(false); +} + +void TribesGame::refreshWindow() +{ + if(Canvas) + Canvas->resetUpdateRegions(); +} + +void TribesGame::processConsoleEvent(ConsoleEvent *event) +{ + char *argv[2]; + argv[0] = "eval"; + argv[1] = event->data; + Sim::postCurrentEvent(Sim::getRootGroup(), new SimConsoleEvent(2, const_cast(argv), false)); +} + +void TribesGame::processTimeEvent(TimeEvent *event) +{ + PROFILE_START(ProcessTimeEvent); + U32 elapsedTime = event->elapsedTime; + // 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(gTimeAdvance) + timeDelta = gTimeAdvance; + else + timeDelta = elapsedTime * gTimeScale; + + Platform::advanceTime(elapsedTime); + + PROFILE_START(ServerProcess); + serverProcess(timeDelta); + PROFILE_END(); + PROFILE_START(ServerNetProcess); + serverNetProcess(); + PROFILE_END(); + + PROFILE_START(SimAdvanceTime); + Sim::advanceTime(timeDelta); + PROFILE_END(); + + PROFILE_START(ClientProcess); + clientProcess(timeDelta); + PROFILE_END(); + PROFILE_START(ClientNetProcess); + clientNetProcess(); + PROFILE_END(); + + if(Canvas && gDGLRender) + { + bool preRenderOnly = false; + if(gFrameSkip && gFrameCount % gFrameSkip) + preRenderOnly = true; + + PROFILE_START(RenderFrame); + ShapeBase::sLastRenderFrame++; + Canvas->renderFrame(preRenderOnly); + PROFILE_END(); + gFrameCount++; + } + dispatchCheckTimeouts(); + fpsUpdate(); + PROFILE_END(); +} + +void GameReactivate() +{ + if ( !Input::isEnabled() ) + Input::enable(); + + if ( !Input::isActive() ) + Input::reactivate(); + + gDGLRender = true; + if ( Canvas ) + Canvas->resetUpdateRegions(); +} + +void GameDeactivate( bool noRender ) +{ + if ( Input::isActive() ) + Input::deactivate(); + + if ( Input::isEnabled() ) + Input::disable(); + + if ( noRender ) + gDGLRender = false; +} + +void TribesGame::textureKill() +{ + TextureManager::makeZombie(); +} + +void TribesGame::textureResurrect() +{ + TextureManager::resurrect(); +} + diff --git a/game/missionArea.cc b/game/missionArea.cc new file mode 100644 index 0000000..58d886a --- /dev/null +++ b/game/missionArea.cc @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/missionArea.h" +#include "console/consoleTypes.h" +#include "Core/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() +{ + Parent::initPersistFields(); + addField("area", TypeRectI, Offset(mArea, MissionArea)); + addField("flightCeiling", TypeF32, Offset(mFlightCeiling, MissionArea)); + addField("flightCeilingRange", TypeF32, Offset(mFlightCeilingRange, MissionArea)); +} + +//------------------------------------------------------------------------------ + +static const char * cGetArea(SimObject * obj, S32, const char **) +{ + static char buf[48]; + MissionArea * missionArea = static_cast(obj); + + RectI area = missionArea->getArea(); + dSprintf(buf, sizeof(buf), "%d %d %d %d", area.point.x, area.point.y, area.extent.x, area.extent.y); + return(buf); +} + +static void cSetArea(SimObject * obj, S32, const char ** argv) +{ + MissionArea * missionArea = static_cast(obj); + + if(missionArea->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]); + + missionArea->setArea(rect); +} + +void MissionArea::consoleInit() +{ + Con::addCommand("MissionArea", "getArea", cGetArea, "missionArea.getArea();", 2, 2); + Con::addCommand("MissionArea", "setArea", cSetArea, "missionArea.setArea(x, y, w, h);", 6, 6); +} + +//------------------------------------------------------------------------------ + +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/game/missionArea.h b/game/missionArea.h new file mode 100644 index 0000000..3ac8dd3 --- /dev/null +++ b/game/missionArea.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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); + + // SimObject + bool onAdd(); + + static void initPersistFields(); + static void consoleInit(); + + // NetObject + enum NetMaskBits { + UpdateMask = BIT(0) + }; + + void unpackUpdate(NetConnection *, BitStream * stream); + U32 packUpdate(NetConnection *, U32 mask, BitStream * stream); + + DECLARE_CONOBJECT(MissionArea); +}; + +#endif diff --git a/game/missionMarker.cc b/game/missionMarker.cc new file mode 100644 index 0000000..3da58fa --- /dev/null +++ b/game/missionMarker.cc @@ -0,0 +1,281 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/missionMarker.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.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(gEditingMission) + { + 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); + } +} + +//------------------------------------------------------------------------------ +// 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(); +} + +static const char * getDataTypeWayPointTeam(void * dptr, EnumTable *, BitSet32) +{ + char * buf = Con::getReturnBuffer(32); + dSprintf(buf, 32, "%d", ((WayPointTeam*)dptr)->mTeamId); + return(buf); +} + +static void setDataTypeWayPointTeam(void * dptr, S32, const char ** argv, EnumTable *, BitSet32) +{ + WayPointTeam * pTeam = (WayPointTeam*)dptr; + pTeam->mTeamId = dAtoi(argv[0]); + + if(pTeam->mWayPoint && pTeam->mWayPoint->isServerObject()) + pTeam->mWayPoint->setMaskBits(WayPoint::UpdateTeamMask); +} + +void WayPoint::initPersistFields() +{ + Parent::initPersistFields(); + addField("name", TypeCaseString, Offset(mName, WayPoint)); + + // + Con::registerType(TypeWayPointTeam, sizeof(WayPointTeam), getDataTypeWayPointTeam, setDataTypeWayPointTeam); + addField("team", TypeWayPointTeam, Offset(mTeam, WayPoint)); +} + +//------------------------------------------------------------------------------ +// Class: SpawnSphere +//------------------------------------------------------------------------------ +IMPLEMENT_CO_NETOBJECT_V1(SpawnSphere); + +Sphere SpawnSphere::smSphere(Sphere::Octahedron); + +SpawnSphere::SpawnSphere() +{ + mRadius = 100.f; + mSphereWeight = 100.f; + mIndoorWeight = 100.f; + mOutdoorWeight = 100.f; +} + +bool SpawnSphere::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + if(!isClientObject()) + setMaskBits(UpdateSphereMask); + return(true); +} + +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->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()) + { + stream->read(&mRadius); + stream->read(&mSphereWeight); + stream->read(&mIndoorWeight); + stream->read(&mOutdoorWeight); + } +} + +void SpawnSphere::initPersistFields() +{ + Parent::initPersistFields(); + addField("radius", TypeF32, Offset(mRadius, SpawnSphere)); + addField("sphereWeight", TypeF32, Offset(mSphereWeight, SpawnSphere)); + addField("indoorWeight", TypeF32, Offset(mIndoorWeight, SpawnSphere)); + addField("outdoorWeight", TypeF32, Offset(mOutdoorWeight, SpawnSphere)); +} diff --git a/game/missionMarker.h b/game/missionMarker.h new file mode 100644 index 0000000..fb924f1 --- /dev/null +++ b/game/missionMarker.h @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MISSIONMARKER_H_ +#define _MISSIONMARKER_H_ + +#ifndef _BITSTREAM_H_ +#include "core/bitStream.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif +#ifndef _MATHIO_H_ +#include "math/mathIO.h" +#endif +#ifndef _SPHERE_H_ +#include "game/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 *); + + // SceneObject + void setTransform(const MatrixF &); + + // SimObject + bool onAdd(); + void onRemove(); + void onEditorEnable(); + void onEditorDisable(); + + void inspectPostApply(); + + // NetObject + U32 packUpdate(NetConnection *, U32, BitStream *); + void unpackUpdate(NetConnection *, BitStream *); + + DECLARE_CONOBJECT(MissionMarker); +}; + +//------------------------------------------------------------------------------ +// Class: WayPoint +//------------------------------------------------------------------------------ +class WayPoint; +class WayPointTeam +{ + public: + WayPointTeam(); + + S32 mTeamId; + WayPoint * mWayPoint; +}; + +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 *, U32, BitStream *); + void unpackUpdate(NetConnection *, BitStream *); + + // 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 *, U32, BitStream *); + void unpackUpdate(NetConnection *, BitStream *); + + // field data + F32 mRadius; + F32 mSphereWeight; + F32 mIndoorWeight; + F32 mOutdoorWeight; + + static void initPersistFields(); + + DECLARE_CONOBJECT(SpawnSphere); +}; + +#endif diff --git a/game/motionBlurLine.cc b/game/motionBlurLine.cc new file mode 100644 index 0000000..1ecdc55 --- /dev/null +++ b/game/motionBlurLine.cc @@ -0,0 +1,213 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Math/mMath.h" + +#include "game/motionBlurLine.h" +#include "PlatformWin32/platformGL.h" +#include "dgl/dgl.h" + +#define MAX_SEG_LENGTH 10.0 + +//************************************************************************** +// Motion Blur line +//************************************************************************** +MotionBlurLine::MotionBlurLine() +{ + mLifetime = 1.0; + mWidth = 0.5; + mColor.set( 1.0, 1.0, 1.0, 1.0 ); + + segments.reserve( 100 ); +} + +//-------------------------------------------------------------------------- +// Add segment +//-------------------------------------------------------------------------- +void MotionBlurLine::addSegment( const Point3F &start, const Point3F &end ) +{ + MotionBlurLineSeg segment; + segment.start = start; + segment.end = end; + segment.startElapsedTime = 0.0; + segment.endElapsedTime = 0.0; + segment.color = mColor; + + if( segments.size() != 0 ) + { + MotionBlurLineSeg *lastSeg = &segments[ segments.size() - 1 ]; + Point3F dir1 = lastSeg->end - lastSeg->start; + F32 dir1len = dir1.len(); + dir1.normalize(); + Point3F dir2 = end - start; + if( dir2.isZero() ) return; + F32 dir2len = dir2.len(); + dir2.normalize(); + + // check if colinear + if( dir1.equal( dir2 ) ) + { + if( dir1len + dir2len < MAX_SEG_LENGTH ) + { + lastSeg->end = end; + lastSeg->endElapsedTime = 0.0; + return; + } + } + + lastSeg->endElapsedTime = 0.0; // synch segments + } + + segments.push_back( segment ); +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void MotionBlurLine::render( const Point3F &camPos ) +{ + U32 i; + for( i=0; i segments; + + F32 mLifetime; + F32 mWidth; + ColorF mColor; + +public: + MotionBlurLine(); + + void addSegment( const Point3F &start, const Point3F &end ); + void calcLineAlpha(); + Box3F getBox( const Point3F &relPoint ); + U32 numSegments(){ return segments.size(); } + void render( const Point3F &camPos ); + void renderSegment( MotionBlurLineSeg &segment, const Point3F &camPos ); + void setColor( ColorF &color ){ mColor = color; } + void setLifetime( F32 lifetime ){ mLifetime = lifetime; } + void setWidth( F32 w ){ mWidth = w; } + void update( F32 dt ); + + +}; + + + + + +#endif diff --git a/game/moveManager.h b/game/moveManager.h new file mode 100644 index 0000000..665198c --- /dev/null +++ b/game/moveManager.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_MOVEMANAGER +#define _H_MOVEMANAGER + +enum { + MaxTriggerKeys = 6, + MaxMoveQueueSize = 45, +}; + +class BitStream; + +struct Move +{ + // packed storage rep, set in clamp + S32 px, py, pz; + U32 pyaw, ppitch, proll; + F32 x, y, z; // float -1 to 1 + F32 yaw, pitch, roll; // 0-2PI + U32 id; // sync'd between server & client - debugging tool. + U32 sendCount; + + bool freeLook; + bool trigger[MaxTriggerKeys]; + + void pack(BitStream *stream); + void unpack(BitStream *stream); + void clamp(); + void unclamp(); +}; + +extern const Move NullMove; + +class MoveManager +{ +public: + static F32 mForwardAction; + static F32 mBackwardAction; + static F32 mUpAction; + static F32 mDownAction; + static F32 mLeftAction; + static F32 mRightAction; + + static bool mFreeLook; + static F32 mPitch; + static F32 mYaw; + static F32 mRoll; + + static F32 mPitchUpSpeed; + static F32 mPitchDownSpeed; + static F32 mYawLeftSpeed; + static F32 mYawRightSpeed; + static F32 mRollLeftSpeed; + static F32 mRollRightSpeed; + + static U32 mTriggerCount[MaxTriggerKeys]; + static U32 mPrevTriggerCount[MaxTriggerKeys]; + + static void init(); +}; + +#endif diff --git a/game/net.cc b/game/net.cc new file mode 100644 index 0000000..b3f2001 --- /dev/null +++ b/game/net.cc @@ -0,0 +1,289 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/bitStream.h" +#include "sim/netObject.h" +#include "game/gameConnection.h" +#include "core/idGenerator.h" +#include "game/serverQuery.h" +#include "game/targetManager.h" + +static char gMasterAddress[256]; + +//---------------------------------------------------------------- +// remote procedure call console functions +//---------------------------------------------------------------- + +class RemoteCommandEvent : public NetEvent +{ +public: + enum { + MaxRemoteCommandArgs = 20, + CommandArgsBits = 5 + }; + +private: + S32 mArgc; + char *mArgv[MaxRemoteCommandArgs + 1]; + U32 mTagv[MaxRemoteCommandArgs + 1]; + static char mBuf[1024]; +public: + RemoteCommandEvent(S32 argc=0, const char **argv=NULL) + { + mArgc = argc; + for(S32 i = 0; i < argc; i++) + { + mArgv[i+1] = dStrdup(argv[i]); + if(argv[i][0] == StringTagPrefixByte) + { + mTagv[i+1] = dAtoi(argv[i]+1); + gNetStringTable->incStringRef(mTagv[i+1]); + } + else + mTagv[i+1] = 0; + } + } +#ifdef DEBUG_NET + const char *getDebugName() + { + static char buffer[256]; + dSprintf(buffer, sizeof(buffer), "%s [%s]", getClassName(), gNetStringTable->lookupString(dAtoi(mArgv[1] + 1)) ); + return buffer; + } +#endif + void notifyDelivered(NetConnection* , bool) + { + // Moved this to the destructor: +// for(S32 i = 0; i < mArgc; i++) +// if(mTagv[i+1]) +// gNetStringTable->removeString(mTagv[i+1]); + } + ~RemoteCommandEvent() + { + for(S32 i = 0; i < mArgc; i++) + { + if(mTagv[i+1]) + gNetStringTable->removeString(mTagv[i+1]); + 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); + mTagv[i+1] =0; + } + } + + 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 tag = conn->translateRemoteStringId(dAtoi(arg+1)); + 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->isServerConnection()) + { + 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->postNetEvent(cevt); +} + +static void cCommandToServer(SimObject *, S32 argc, const char **argv) +{ + NetConnection *conn = NetConnection::getServerConnection(); + if(!conn) + return; + sendRemoteCommand(conn, argc - 1, argv + 1); +} + +static void cCommandToClient(SimObject *, S32 argc, const char **argv) +{ + NetConnection *conn; + if(!Sim::findObject(argv[1], conn)) + return; + sendRemoteCommand(conn, argc - 2, argv + 2); +} + +//---------------------------------------------------------------- +// Console function registration / processing +//---------------------------------------------------------------- + +void cRemoveTaggedString(SimObject *, S32, const char **argv) +{ + gNetStringTable->removeString(dAtoi(argv[1]+1)); +} + +const char *cAddTaggedString(SimObject *, S32, const char **argv) +{ + U32 id = gNetStringTable->addString(argv[1]); + char *ret = Con::getReturnBuffer(10); + ret[0] = StringTagPrefixByte; + dSprintf(ret + 1, 9, "%d", id); + return ret; +} + +const char *cGetTaggedString(SimObject *, S32, const char **argv) +{ + const char *indexPtr = argv[1]; + if (*indexPtr == StringTagPrefixByte) + indexPtr++; + return gNetStringTable->lookupString(dAtoi(indexPtr)); +} + +const char *cBuildTaggedString(SimObject *, S32 argc, const char **argv) +{ + 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; +} + +void netInit() +{ + Con::addCommand("addTaggedString", cAddTaggedString, "addTaggedString(string)", 2, 2); + Con::addCommand("removeTaggedString", cRemoveTaggedString, "removeTaggedString(tag)", 2, 2); + Con::addCommand("getTaggedString", cGetTaggedString, "getTaggedString(tag)", 2, 2); + + Con::addCommand("buildTaggedString", cBuildTaggedString, "buildTaggedString(fmtTag, );", 2, 11); + + Con::addCommand("commandToServer", cCommandToServer, "commandToServer(func, );", 2, RemoteCommandEvent::MaxRemoteCommandArgs + 1); + Con::addCommand("commandToClient", cCommandToClient, "commandToClient(client, func, );", 3, RemoteCommandEvent::MaxRemoteCommandArgs + 2); + + Con::addVariable( "MasterServerAddress", TypeString, &gMasterAddress ); +} diff --git a/game/net/httpObject.cc b/game/net/httpObject.cc new file mode 100644 index 0000000..ee9310e --- /dev/null +++ b/game/net/httpObject.cc @@ -0,0 +1,320 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/net/httpObject.h" + +#include "platform/platform.h" +#include "platform/event.h" +#include "core/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['\"'] = true; + asciiEscapeTable['_'] = true; + asciiEscapeTable['\''] = true; + asciiEscapeTable['#'] = true; + asciiEscapeTable['$'] = true; + asciiEscapeTable['%'] = true; + asciiEscapeTable['&'] = true; + asciiEscapeTable['+'] = true; + asciiEscapeTable['-'] = true; + asciiEscapeTable['~'] = true; + } + + U32 destIndex = 0; + U32 srcIndex = 0; + while(path[srcIndex] && destIndex < destSize - 3) + { + char c = path[srcIndex++]; + if(asciiEscapeTable[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(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; +} + +//-------------------------------------- +static void cHTTPObjectGet(SimObject *obj, S32 argc, const char **argv) +{ + ((HTTPObject *) obj)->get(argv[2], argv[3], argc == 4 ? NULL : argv[4]); +} + +static void cHTTPObjectPost(SimObject *obj, S32, const char **argv) +{ + ((HTTPObject *) obj)->post(argv[2], argv[3], argv[4], argv[5]); +} + + +//-------------------------------------- +void HTTPObject::consoleInit() +{ + Con::addCommand("HTTPObject", "get", cHTTPObjectGet, "obj.get(addr, request-uri, )", 4, 5); + Con::addCommand("HTTPObject", "post", cHTTPObjectPost, "obj.post(addr, request-uri, query, post)", 6, 6); +} + diff --git a/game/net/httpObject.h b/game/net/httpObject.h new file mode 100644 index 0000000..f39a1e4 --- /dev/null +++ b/game/net/httpObject.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HTTPOBJECT_H_ +#define _HTTPOBJECT_H_ + +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif +#ifndef _TCPOBJECT_H_ +#include "game/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); + + static void consoleInit(); +}; + + +#endif // _H_HTTPOBJECT_ diff --git a/game/net/net.cc b/game/net/net.cc new file mode 100644 index 0000000..b72f944 --- /dev/null +++ b/game/net/net.cc @@ -0,0 +1,288 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "core/idGenerator.h" +#include "core/bitStream.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "sim/netConnection.h" +#include "sim/netObject.h" +#include "game/gameConnection.h" +#include "game/net/serverQuery.h" + +static char gMasterAddress[256]; + +//---------------------------------------------------------------- +// remote procedure call console functions +//---------------------------------------------------------------- + +class RemoteCommandEvent : public NetEvent +{ +public: + enum { + MaxRemoteCommandArgs = 20, + CommandArgsBits = 5 + }; + +private: + S32 mArgc; + char *mArgv[MaxRemoteCommandArgs + 1]; + U32 mTagv[MaxRemoteCommandArgs + 1]; + static char mBuf[1024]; +public: + RemoteCommandEvent(S32 argc=0, const char **argv=NULL) + { + mArgc = argc; + for(S32 i = 0; i < argc; i++) + { + mArgv[i+1] = dStrdup(argv[i]); + if(argv[i][0] == StringTagPrefixByte) + { + mTagv[i+1] = dAtoi(argv[i]+1); + gNetStringTable->incStringRef(mTagv[i+1]); + } + else + mTagv[i+1] = 0; + } + } +#ifdef DEBUG_NET + const char *getDebugName() + { + static char buffer[256]; + dSprintf(buffer, sizeof(buffer), "%s [%s]", getClassName(), gNetStringTable->lookupString(dAtoi(mArgv[1] + 1)) ); + return buffer; + } +#endif + void notifyDelivered(NetConnection* , bool) + { + // Moved this to the destructor: +// for(S32 i = 0; i < mArgc; i++) +// if(mTagv[i+1]) +// gNetStringTable->removeString(mTagv[i+1]); + } + ~RemoteCommandEvent() + { + for(S32 i = 0; i < mArgc; i++) + { + if(mTagv[i+1]) + gNetStringTable->removeString(mTagv[i+1]); + 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); + mTagv[i+1] =0; + } + } + + 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 tag = conn->translateRemoteStringId(dAtoi(arg+1)); + 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->isServerConnection()) + { + 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->postNetEvent(cevt); +} + +static void cCommandToServer(SimObject *, S32 argc, const char **argv) +{ + NetConnection *conn = NetConnection::getServerConnection(); + if(!conn) + return; + sendRemoteCommand(conn, argc - 1, argv + 1); +} + +static void cCommandToClient(SimObject *, S32 argc, const char **argv) +{ + NetConnection *conn; + if(!Sim::findObject(argv[1], conn)) + return; + sendRemoteCommand(conn, argc - 2, argv + 2); +} + +//---------------------------------------------------------------- +// Console function registration / processing +//---------------------------------------------------------------- + +void cRemoveTaggedString(SimObject *, S32, const char **argv) +{ + gNetStringTable->removeString(dAtoi(argv[1]+1)); +} + +const char *cAddTaggedString(SimObject *, S32, const char **argv) +{ + U32 id = gNetStringTable->addString(argv[1]); + char *ret = Con::getReturnBuffer(10); + ret[0] = StringTagPrefixByte; + dSprintf(ret + 1, 9, "%d", id); + return ret; +} + +const char *cGetTaggedString(SimObject *, S32, const char **argv) +{ + const char *indexPtr = argv[1]; + if (*indexPtr == StringTagPrefixByte) + indexPtr++; + return gNetStringTable->lookupString(dAtoi(indexPtr)); +} + +const char *cBuildTaggedString(SimObject *, S32 argc, const char **argv) +{ + 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; +} + +void netInit() +{ + Con::addCommand("addTaggedString", cAddTaggedString, "addTaggedString(string)", 2, 2); + Con::addCommand("removeTaggedString", cRemoveTaggedString, "removeTaggedString(tag)", 2, 2); + Con::addCommand("getTaggedString", cGetTaggedString, "getTaggedString(tag)", 2, 2); + + Con::addCommand("buildTaggedString", cBuildTaggedString, "buildTaggedString(fmtTag, );", 2, 11); + + Con::addCommand("commandToServer", cCommandToServer, "commandToServer(func, );", 2, RemoteCommandEvent::MaxRemoteCommandArgs + 1); + Con::addCommand("commandToClient", cCommandToClient, "commandToClient(client, func, );", 3, RemoteCommandEvent::MaxRemoteCommandArgs + 2); + + Con::addVariable( "MasterServerAddress", TypeString, &gMasterAddress ); +} diff --git a/game/net/netDispatch.cc b/game/net/netDispatch.cc new file mode 100644 index 0000000..e99a025 --- /dev/null +++ b/game/net/netDispatch.cc @@ -0,0 +1,1062 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Connect protocol is as follows: +// Client issues ConnectChallengeRequest +// Server responds with ConnectChallengeReject or ConnectChallengeResponse +// Client issues ConnectRequest +// Server responds with with ConnectReject or ConnectAccept +//----------------------------------------------------------------------------- + +#include "game/net/netDispatch.h" + +#include "platform/platform.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "core/dnet.h" +#include "core/bitStream.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "sim/netConnection.h" +#include "sim/netObject.h" +#include "game/gameConnection.h" +#include "game/demoGame.h" +#include "game/banList.h" +#include "game/auth.h" +#include "game/net/serverQuery.h" + +//----------------------------------------------------------------------------- + +enum { + MaxConnectArgs = 16, + MaxPendingConnects = 20, + PendingConnectTimeout = 7000, // 7 seconds + + ChallengeRetryCount = 4, + ChallengeRetryTime = 2500, // 2.5 seconds + + ConnectRetryCount = 4, + ConnectRetryTime = 2500, + TimeoutCheckInterval = 1500, // check for timeouts every 1.5 secs + MaxAuthInfoSize = 1024, + MaxPasswordLength = 16, +}; + +const char *gServerConnectionName = "ServerConnection"; +const char *gLocalClientConnectionName = "LocalClientConnection"; +bool gAllowConnections = false; + +void GameClientAdded(GameConnection *newClient, S32 argc, const char **argv); + + +//----------------------------------------------------------------------------- +// Connect protocol message formats +//----------------------------------------------------------------------------- + +// ConnectChallengeRequest +// ProtocolVersion +// Certificate +// ClientConnectSequence +// Password + +// ConnectChallengeResponse +// ProtocolVersion +// ServerConnectSequence +// ClientConnectSequence + +// ConnectChallengeReject +// ClientConnectSequence +// Reason + +// ConnectRequest +// ServerConnectSequence +// ClientConnectSequence +// ProtocolVersion +// console args + +// ConnectAccept +// ServerConnectSequence +// ClientConnectSequence +// ProtocolVersion +// ClientID + +// ConnectReject +// ServerConnectSequence +// ClientConnectSequence +// Reason + +// Disconnect +// ServerConnectSequence +// ClientConnectSequence +// Reason string + +// Connect request and server response structures + +struct ConnectRequestStruct +{ + enum { + NotConnecting, + Challenging, + Connecting, + } state; + + U32 clientConnectSequence; + U32 serverConnectSequence; + U32 protocolVersion; + U32 lastSendTime; + U32 sendCount; + NetAddress serverAddress; + U32 challenge; + bool authenticated; + char password[MaxPasswordLength + 1]; + + U32 argc; + char *argv[MaxConnectArgs]; + U8 argBuffer[MaxPacketDataSize]; +}; + +struct ConnectRequestPending +{ + // Certificate needs to be stored here. + // V12: Challenge stuff was partially hacked out + U32 challenge1; + + NetAddress sourceAddress; + U32 clientProtocolVersion; + U32 clientConnectSequence; + U32 serverConnectSequence; + U32 lastRecvTime; +}; + +ConnectRequestStruct gConnectRequest; +ConnectRequestPending gPendingConnects[MaxPendingConnects]; +U32 gNumPendingConnects; + + +//----------------------------------------------------------------------------- + +void connectRequestRejected(const char *buffer) +{ + Con::executef(2, "onConnectRequestRejected", buffer); +} + +void challengeRequestRejected(const char *buffer) +{ + Con::executef(2, "onChallengeRequestRejected", buffer); +} + +void connectRequestTimedOut() +{ + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + Con::executef(1, "onConnectRequestTimedOut"); +} + +void connectionToServerTimedOut() +{ + Con::executef(1, "onConnectionToServerTimedOut"); +} + +void connectionToServerLost(const char* msg) +{ + Con::executef(2, "onConnectionToServerLost", msg); +} + + +//----------------------------------------------------------------------------- + +bool isServerOnline() +{ + if(Game->isJournalReading()) + { + U32 res; + Game->journalRead(&res); + return bool(res); + } + else + { + bool ret = false; + // Authentication removed... + ret = true; + + if(Game->isJournalWriting()) + Game->journalWrite(U32(ret)); + return ret; + } +} + +bool isClientOnline() +{ + if(Game->isJournalReading()) + { + U32 res; + Game->journalRead(&res); + return bool(res); + } + else + { + bool ret = false; + // Authentication removed... + ret = true; + + if(Game->isJournalWriting()) + Game->journalWrite(U32(ret)); + return ret; + } +} + + +//----------------------------------------------------------------------------- +// Connect request and server response functions +//----------------------------------------------------------------------------- + +void rejectConnectChallengeRequest(const NetAddress *source, U32 clientConnectSequence, const char *reason); +void sendConnectChallengeResponse(U32 index); +void sendConnectChallengeRequest(); +void sendConnectAccept(NetConnection *conn); +void sendConnectReject(const NetAddress *address,U32 clientSeq,U32 serverSeq,const char *reason); +void sendConnectRequest(); + +void handleConnectChallengeReject(const NetAddress *address, BitStream *stream); +void handleConnectChallengeResponse(const NetAddress *addr, BitStream *stream); +void handleConnectChallengeRequest(const NetAddress *source, BitStream *stream); +void handleConnectAccept(const NetAddress *addr, BitStream *stream); +void handleConnectReject(const NetAddress *address, BitStream *stream); +void handleConnectRequest(const NetAddress *address, BitStream *stream); + + +//----------------------------------------------------------------------------- +// Challenge Request +//----------------------------------------------------------------------------- + +void sendConnectChallengeRequest() +{ + BitStream *out = BitStream::getPacketStream(); + out->write(U8(ConnectChallengeRequest)); + out->write(CurrentProtocolVersion); + out->write(U32(AbstractClassRep::getClassCRC())); + out->write(gConnectRequest.clientConnectSequence); + out->writeString(gConnectRequest.password); + + // Authentication removed... + // BUT flag still checked below -- need to write clear-flag. + out->writeFlag(0); + // need to add out->write for challenge... + + gConnectRequest.sendCount++; + gConnectRequest.lastSendTime = Platform::getVirtualMilliseconds(); + + Con::printf("Connect Challenge Request: %d", gConnectRequest.sendCount); + + BitStream::sendPacketStream(&gConnectRequest.serverAddress); +} + +void handleConnectChallengeRequest(const NetAddress *source, BitStream *stream) +{ + if(!gAllowConnections) + return; + + // reasons for rejection + // password wrong + // server full + // bad protocol version + // invalid certificate + // don't meet v12/UID connect restrictions + + U32 protoVersion, classCRC; + U32 clientConnectSequence; + const char *rejectString = NULL; + char joinPassword[MaxPasswordLength + 1]; + + stream->read(&protoVersion); + stream->read(&classCRC); + stream->read(&clientConnectSequence); + + if(protoVersion < MinRequiredProtocolVersion) + { + rejectConnectChallengeRequest(source, clientConnectSequence, "CHR_PROTOCOL"); + return; + } + if(classCRC != AbstractClassRep::getClassCRC()) + { + rejectConnectChallengeRequest(source, clientConnectSequence, "CHR_CLASSCRC"); + return; + } + + if(protoVersion > CurrentProtocolVersion) + protoVersion = CurrentProtocolVersion; + + // first time out any outstanding challenge requests: + U32 time = Platform::getVirtualMilliseconds(); + + for(U32 i = 0; i < gNumPendingConnects;) + { + ConnectRequestPending *p = gPendingConnects + i; + + // see if this is already in the list: + if(Net::compareAddresses(source, &p->sourceAddress) && p->clientConnectSequence == clientConnectSequence) + { + // just up the time and ping back + sendConnectChallengeResponse(i); + return; + } + if(time > p->lastRecvTime + PendingConnectTimeout) + *p = gPendingConnects[--gNumPendingConnects]; + else + i++; + } + + // Test the password: + stream->readString(joinPassword); + const char* password = Con::getVariable( "Pref::Server::Password" ); + if ( password[0] ) + { + if ( dStrncmp( password, joinPassword, MaxPasswordLength ) != 0 ) + { + // IMPORTANT! Do NOT change the message here, it is used by the game. + rejectConnectChallengeRequest(source, clientConnectSequence, "CHR_PASSWORD"); + return; + } + } + + // V12: Challenge stuff was partially hacked out + U32 challenge1 = 0; + + if(stream->readFlag()) + { + U32 size = stream->readInt(16); + if(size > MaxAuthInfoSize) + { + rejectConnectChallengeRequest(source, clientConnectSequence, "CHR_INVALID_CHALLENGE_PACKET"); + return; + } + + // V12: Used to have WON stuff here... + } + + U32 index; + if(gNumPendingConnects == MaxPendingConnects) + index = mRandI(0, MaxPendingConnects-1); + else + index = gNumPendingConnects++; + + ConnectRequestPending *p = gPendingConnects + index; + p->challenge1 = challenge1; + p->clientConnectSequence = clientConnectSequence; + p->sourceAddress = *source; + p->clientProtocolVersion = protoVersion; + p->serverConnectSequence = Platform::getVirtualMilliseconds(); + + Con::printf("Got challenge request"); + sendConnectChallengeResponse(index); +} + + +//----------------------------------------------------------------------------- +// Challenge Reject +//----------------------------------------------------------------------------- + +void rejectConnectChallengeRequest(const NetAddress *source, U32 clientConnectSequence, const char *reason) +{ + BitStream *out = BitStream::getPacketStream(); + + out->write(U8(ConnectChallengeReject)); + out->write(clientConnectSequence); + out->writeString(reason); + BitStream::sendPacketStream(source); +} + +void handleConnectChallengeReject(const NetAddress *address, BitStream *stream) +{ + if(gConnectRequest.state != ConnectRequestStruct::Challenging) + return; + U32 clientConnectSequence; + stream->read(&clientConnectSequence); + + if(!Net::compareAddresses(address, &gConnectRequest.serverAddress) || + clientConnectSequence != gConnectRequest.clientConnectSequence) + return; + char buffer[256]; + stream->readString(buffer); + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + challengeRequestRejected(buffer); +} + + +//----------------------------------------------------------------------------- +// Challenge Response +//----------------------------------------------------------------------------- + +void sendConnectChallengeResponse(U32 index) +{ + ConnectRequestPending *p = gPendingConnects + index; + + BitStream *out = BitStream::getPacketStream(); + + out->write(U8(ConnectChallengeResponse)); + out->write(CurrentProtocolVersion); + out->write(p->serverConnectSequence); + out->write(p->clientConnectSequence); + + // Authentication removed... + // BUT flag still checked below -- need to write clear-flag. + out->writeFlag(0); + // need to write out challenge somethingorother + + p->lastRecvTime = Platform::getVirtualMilliseconds(); + Con::printf("Sent challenge response"); + + BitStream::sendPacketStream(&p->sourceAddress); +} + +void handleConnectChallengeResponse(const NetAddress *addr, BitStream *stream) +{ + if(gConnectRequest.state != ConnectRequestStruct::Challenging) + return; + if(!Net::compareAddresses(&gConnectRequest.serverAddress, addr)) + return; + U32 serverProtocol; + U32 serverConnectSequence; + U32 clientConnectSequence; + + stream->read(&serverProtocol); + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + + // must be an old connect request... + if(clientConnectSequence != gConnectRequest.clientConnectSequence) + return; + + // check if it's authenticated + gConnectRequest.authenticated = stream->readFlag(); + if(gConnectRequest.authenticated) + { + // V12: Authentication removed... + // V12: Used to have WON stuff here... + + // need to read in challenge somethingorother + } + + gConnectRequest.state = ConnectRequestStruct::Connecting; + gConnectRequest.serverConnectSequence = serverConnectSequence; + gConnectRequest.sendCount = 0; + gConnectRequest.protocolVersion = serverProtocol < CurrentProtocolVersion ? serverProtocol : CurrentProtocolVersion; + Con::printf("Got challenge response"); + + sendConnectRequest(); +} + + +//----------------------------------------------------------------------------- +// Connection Request +//----------------------------------------------------------------------------- + +void sendConnectRequest() +{ + gConnectRequest.sendCount++; + gConnectRequest.lastSendTime = Platform::getVirtualMilliseconds(); + + BitStream *out = BitStream::getPacketStream(); + + out->write(U8(ConnectRequest)); + out->write(gConnectRequest.serverConnectSequence); + out->write(gConnectRequest.clientConnectSequence); + out->write(gConnectRequest.protocolVersion); + if(out->writeFlag(gConnectRequest.authenticated)) + { + // we shouldn't actually hit this at the moment. + out->write(gConnectRequest.challenge); + } + out->write(gConnectRequest.argc); + for(U32 i = 0; i < gConnectRequest.argc; i++) + out->writeString(gConnectRequest.argv[i]); + + Con::printf("Sent connect request"); + BitStream::sendPacketStream(&gConnectRequest.serverAddress); +} + +void handleConnectRequest(const NetAddress *address, BitStream *stream) +{ + if(!gAllowConnections) + return; + + char addrString[256]; + + Net::addressToString(address, addrString); + Con::printf("Connection request from: %s", addrString); + + U32 serverConnectSequence; + U32 clientConnectSequence; + U32 protocolRequestVersion; + + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + stream->read(&protocolRequestVersion); + + const char *rejectString = NULL; + + if(protocolRequestVersion < MinRequiredProtocolVersion || + protocolRequestVersion > CurrentProtocolVersion) + rejectString = "CR_INVALID_PROTOCOL_VERSION"; + + U32 i; + for(i = 0; i < gNumPendingConnects; i++) + { + ConnectRequestPending *p = gPendingConnects + i; + + if(Net::compareAddresses(address, &p->sourceAddress) && + p->clientConnectSequence == clientConnectSequence && + p->serverConnectSequence == serverConnectSequence) + break; + } + if(i == gNumPendingConnects) + { + // if it's not in the pending list, check if + // we're already connected to this guy: + NetConnection *connect = NetConnection::lookup(address); + if(connect) + { + U32 clientConSeq, serverConSeq; + connect->getSequences(&clientConSeq, &serverConSeq); + + // we already accepted this connection: + if(clientConSeq == clientConnectSequence && + serverConSeq == serverConnectSequence) + sendConnectAccept(connect); + } + return; + } + U32 clientId = 0; + + // check the peer auth... + if(stream->readFlag()) + { + // V12: Authentication removed... + // V12: Also used to have WON stuff here... + + // for now, since authentication SHOULD be OFF, + // we post an error if we get into this block. + rejectString = "CR_INVALID_CONNECT_PACKET"; + + // however, the write code will write the challenge if flag set, + // so we must have the code read it here or stream gets out of sync. + U32 challengeResp; + stream->read(&challengeResp); + } + +#if !defined(__linux__) && !defined(__OpenBSD__) // can't have this for now, - rjp + if ( !rejectString ) + { + if ( gBanList.isBanned( 0 /* V12: Was Won net ID */, addrString ) ) + rejectString = "CR_YOUAREBANNED"; + else if ( Con::getIntVariable( "Server::PlayerCount" ) >= Con::getIntVariable( "Pref::Server::MaxPlayers" ) ) + rejectString = "CR_SERVERFULL"; + } +#endif + + // erase the request from the pending list + gPendingConnects[i] = gPendingConnects[--gNumPendingConnects]; + gPendingConnects[gNumPendingConnects].challenge1 = NULL; // kill the byte buffer. + + const char *argv[MaxConnectArgs]; + char argbuf[MaxConnectArgs][256]; + U32 argc; + + stream->read(&argc); + if(argc > MaxConnectArgs) + rejectString = "CR_INVALID_CONNECT_PACKET"; + else + { + for(U32 i = 0; i < argc; i++) + { + argv[i] = argbuf[i]; + stream->readString(argbuf[i]); + } + } + + if(rejectString) + sendConnectReject(address, clientConnectSequence, + serverConnectSequence, rejectString); + else + { + Con::printf("Accepting connect... CLIENT ID = %d", clientId); + // let this guy into the game: + GameConnection *conn = new GameConnection(true, false, true); + + conn->registerObject(); + conn->setProtocolVersion(protocolRequestVersion); + conn->setNetAddress(address); + conn->setNetworkConnection(true); + + conn->setSequences(clientConnectSequence, serverConnectSequence); + conn->setConnectSequence(clientConnectSequence ^ serverConnectSequence); + sendConnectAccept(conn); + GameClientAdded(conn, argc, argv); + } +} + + +//----------------------------------------------------------------------------- +// Connection Accept +//----------------------------------------------------------------------------- + +void sendConnectAccept(NetConnection *conn) +{ + BitStream *out = BitStream::getPacketStream(); + + out->write(U8(ConnectAccept)); + U32 clientConnectSeq, serverConnectSeq; + U32 protocol; + + conn->getSequences(&clientConnectSeq, &serverConnectSeq); + protocol = conn->getProtocolVersion(); + + out->write(serverConnectSeq); + out->write(clientConnectSeq); + out->write(protocol); + out->write(U32(conn->getId())); + Con::printf("Sending connect accept: %d", conn->getId()); + + BitStream::sendPacketStream(conn->getNetAddress()); +} + +void handleConnectAccept(const NetAddress *addr, BitStream *stream) +{ + if(gConnectRequest.state != ConnectRequestStruct::Connecting) + return; + if(!Net::compareAddresses(&gConnectRequest.serverAddress, addr)) + return; + + U32 serverConnectSequence, clientConnectSequence; + U32 protocol, id; + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + stream->read(&protocol); + stream->read(&id); + + if(serverConnectSequence != gConnectRequest.serverConnectSequence || + clientConnectSequence != gConnectRequest.clientConnectSequence || + protocol > CurrentProtocolVersion || + protocol < MinRequiredProtocolVersion) + return; + + // ok, we can connect up now: + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + + GameConnection *conn = new GameConnection(false, true, true); + conn->registerObject(); + NetConnection::setServerConnection(conn); + + conn->setProtocolVersion(protocol); + conn->setSequences(clientConnectSequence, serverConnectSequence); + conn->setConnectSequence(clientConnectSequence ^ serverConnectSequence); + conn->setNetAddress(addr); + conn->setNetworkConnection(true); + + Sim::getRootGroup()->addObject(conn, gServerConnectionName); + Con::executef(conn, 2, "onConnectionAccepted", Con::getIntArg(conn->getId())); + Con::printf("Connection accepted - id %d protocol %d", id, protocol); +} + + +//----------------------------------------------------------------------------- +// Connection Reject +//----------------------------------------------------------------------------- + +void sendConnectReject(const NetAddress *address,U32 clientSeq,U32 serverSeq,const char *reason) +{ + BitStream *out = BitStream::getPacketStream(); + out->write(U8(ConnectReject)); + out->write(serverSeq); + out->write(clientSeq); + out->writeString(reason); + BitStream::sendPacketStream(address); +} + +void handleConnectReject(const NetAddress *address, BitStream *stream) +{ + if(gConnectRequest.state != ConnectRequestStruct::Connecting) + return; + U32 clientConnectSequence, serverConnectSequence; + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + + if(!Net::compareAddresses(address, &gConnectRequest.serverAddress) || + clientConnectSequence != gConnectRequest.clientConnectSequence || + serverConnectSequence != gConnectRequest.serverConnectSequence) + return; + char buffer[256]; + stream->readString(buffer); + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + connectRequestRejected(buffer); +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void handleDisconnect(const NetAddress *addr, BitStream *stream) +{ + Con::printf("Got disconnect packet."); + + NetConnection *conn = NetConnection::lookup(addr); + U32 serverConnectSequence, clientConnectSequence; + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + char reason[256]; + stream->readString(reason); + + U32 cServerConnectSequence, cClientConnectSequence; + if(!conn) + return; + conn->getSequences(&cClientConnectSequence, &cServerConnectSequence); + if(cClientConnectSequence != clientConnectSequence || + cServerConnectSequence != serverConnectSequence) + return; + + // ok, it's gone. + if(conn->isServerConnection()) + { + Con::printf("Connection with server lost."); + connectionToServerLost( reason ); + } + else + { + Con::printf("Client %d disconnected.", conn->getId()); + ((GameConnection *) conn)->setDisconnectReason(reason); + } + conn->deleteObject(); +} + + +//----------------------------------------------------------------------------- + +void GameConnection::connectionError(const char *errorString) +{ + if(isServerConnection()) + { + Con::printf("Connection error: %s.", errorString); + connectionToServerLost( errorString ); + } + else + { + Con::printf("Client %d packet error: %s.", getId(), errorString); + setDisconnectReason("Packet Error."); + } + deleteObject(); +} + + +//----------------------------------------------------------------------------- +// Public functions +//----------------------------------------------------------------------------- + +void dispatchInit() +{ + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + gNumPendingConnects = 0; +} + +void clientNetProcess() +{ + NetConnection *con = NetConnection::getServerConnection(); + if (con) + con->checkPacketSend(); +} + +void serverNetProcess() +{ + NetObject::collapseDirtyList(); // collapse all the mask bits... + + SimGroup *clientGroup = Sim::getClientGroup(); + for(SimGroup::iterator i = clientGroup->begin(); i != clientGroup->end(); i++) + { + NetConnection *con = (NetConnection *)(*i); + if(con->isLocalConnection() || con->isNetworkConnection()) + con->checkPacketSend(); + } +} + +BitStream gPacketStream(NULL, 0); + +void DemoGame::processPacketReceiveEvent(PacketReceiveEvent * prEvent) +{ + U32 dataSize = prEvent->size - PacketReceiveEventHeaderSize; + gPacketStream.setBuffer(prEvent->data, dataSize); + if(!(prEvent->data[0] & 0x01)) // it's a non-protocol packet of some sort... + { + U8 packetType; + gPacketStream.read(&packetType); + NetAddress *addr = &prEvent->sourceAddress; + + if(packetType <= GameHeartbeat) + handleInfoPacket(addr, packetType, &gPacketStream); + else + { + switch(packetType) + { + case ConnectChallengeRequest: + handleConnectChallengeRequest(addr, &gPacketStream); + break; + case ConnectRequest: + handleConnectRequest(addr, &gPacketStream); + break; + case ConnectChallengeResponse: + handleConnectChallengeResponse(addr, &gPacketStream); + break; + case ConnectAccept: + handleConnectAccept(addr, &gPacketStream); + break; + case Disconnect: + handleDisconnect(addr, &gPacketStream); + break; + case ConnectReject: + handleConnectReject(addr, &gPacketStream); + break; + case ConnectChallengeReject: + handleConnectChallengeReject(addr, &gPacketStream); + break; + } + } + } + else + { + // lookup the connection in the addressTable + NetConnection *conn = NetConnection::lookup(&prEvent->sourceAddress); + if(conn) + conn->processRawPacket(&gPacketStream); + } +} + +void dispatchCheckTimeouts() +{ + static U32 lastTimeoutCheckTime = 0; + U32 time = Platform::getVirtualMilliseconds(); + if(time > lastTimeoutCheckTime + TimeoutCheckInterval) + { + // check the connection state: + if(gConnectRequest.state == ConnectRequestStruct::Challenging && + time > gConnectRequest.lastSendTime + ChallengeRetryTime) + { + if(gConnectRequest.sendCount > ChallengeRetryCount) + connectRequestTimedOut(); + else + sendConnectChallengeRequest(); + } + else if(gConnectRequest.state == ConnectRequestStruct::Connecting && + time > gConnectRequest.lastSendTime + ConnectRetryTime) + { + if(gConnectRequest.sendCount > ConnectRetryCount) + connectRequestTimedOut(); + else + sendConnectRequest(); + } + + lastTimeoutCheckTime = time; + NetConnection *walk = NetConnection::getConnectionList(); + + while(walk) + { + NetConnection *next = walk->getNext(); + if(walk->checkTimeout(time)) + { + // this baddie timed out + if(walk->isServerConnection()) + { + Con::printf("Connection to server timed out"); + connectionToServerTimedOut(); + } + else + { + Con::printf("Client %d timed out.", walk->getId()); + ((GameConnection *) walk)->setDisconnectReason("TimedOut"); + } + walk->deleteObject(); + } + walk = next; + } + } +} + + +//----------------------------------------------------------------------------- +// DNet external Game function declarations +//----------------------------------------------------------------------------- + +void GameConnectionRejected(NetConnectionId id, BitStream *stream) +{ + GameConnection *conn = (GameConnection *) Sim::findObject(id); + if(conn) + conn->deleteObject(); + Con::printf("Connection rejected: %s", stream->getBuffer()); +} + + +void GameConnectionEstablished(NetConnectionId id) +{ + Con::printf("Connection established %d", id); +} + +void GameClientAdded(GameConnection *newClient, S32 argc, const char **argv) +{ + SimGroup *g = Sim::getClientGroup(); + g->addObject(newClient); + const char *vargs[MaxConnectArgs + 2]; + vargs[0] = "onConnect"; + vargs[1] = NULL; + if(argc > MaxConnectArgs) + argc = MaxConnectArgs; + for(S32 i = 0; i < argc; i++) + vargs[i + 2] = argv[i]; + + Con::execute(newClient, argc + 2, vargs); +} + +//void GameConnectionRemoved(GameConnection *conn) +//{ +// if(conn->isNetworkConnection()) +// { +// Con::printf("Issuing Disconnect packet."); +// +// // send a disconnect packet... +// U32 serverConnectSequence, clientConnectSequence; +// conn->getSequences(&clientConnectSequence, &serverConnectSequence); +// +// BitStream *out = BitStream::getPacketStream(); +// out->write(U8(Disconnect)); +// out->write(serverConnectSequence); +// out->write(clientConnectSequence); +// out->writeString(""); +// +// BitStream::sendPacketStream(conn->getNetAddress()); +// } +//} + + +//----------------------------------------------------------------------------- +// Connection initiation console commands +//----------------------------------------------------------------------------- + +ConsoleFunction(allowConnections,void,2,2,"allowConnections(bool);") +{ + argc; + if(!validateAuthenticatedServer()) + return; + gAllowConnections = dAtob(argv[1]); +} + +ConsoleFunction(connect,void,2,17,"connect(addr,[password],[arg1..n]);") +{ + if(!validateAuthenticatedClient()) + return; + gConnectRequest.state = ConnectRequestStruct::Challenging; + gConnectRequest.clientConnectSequence = Platform::getVirtualMilliseconds(); + + if(!Net::stringToAddress(argv[1], &gConnectRequest.serverAddress)) + return; + + argc -= 2; + if (argc > MaxConnectArgs) + argc = MaxConnectArgs; + + // First optional arg is the password + if (argc > 1) { + dStrncpy( gConnectRequest.password, argv[2], MaxPasswordLength ); + } + else + gConnectRequest.password[0] = 0; + + // Get the rest of the optional args.... + U32 i, bufPos = 0; + for(i = 0; i < argc; i++) + { + const char *str = argv[i + 3]; + U32 len = dStrlen(str); + + if(bufPos + len + 1 > MaxPacketDataSize) + break; + dStrcpy((char *) gConnectRequest.argBuffer + bufPos, str); + gConnectRequest.argv[i] = (char *) gConnectRequest.argBuffer + bufPos; + bufPos += len + 1; + } + gConnectRequest.argc = i; + + // Ship off the request... + // Authentication removed... + gConnectRequest.sendCount = 0; + gConnectRequest.protocolVersion = CurrentProtocolVersion; + sendConnectChallengeRequest(); +} + +ConsoleFunction(localConnect,void,1,16,"localConnect();") +{ + if(!validateAuthenticatedClient()) + return; + + GameConnection *clientConnection = new GameConnection(false, true, true); + GameConnection *serverConnection = new GameConnection(true, false, true); + + clientConnection->registerObject(); + Sim::getRootGroup()->addObject(clientConnection, gServerConnectionName); + + serverConnection->registerObject(); + serverConnection->assignName(gLocalClientConnectionName); + NetConnection::setServerConnection(clientConnection); + NetConnection::setLocalClientConnection(serverConnection); + + clientConnection->setRemoteConnectionObjectId(serverConnection->getId()); + serverConnection->setRemoteConnectionObjectId(clientConnection->getId()); + + clientConnection->setProtocolVersion(CurrentProtocolVersion); + serverConnection->setProtocolVersion(CurrentProtocolVersion); + clientConnection->setConnectSequence(0); + serverConnection->setConnectSequence(0); + + GameClientAdded(serverConnection, argc - 1, argv + 1); + Con::executef(clientConnection, 2, "onConnectionAccepted", Con::getIntArg(clientConnection->getId())); +} + +ConsoleFunction(startRecord, void, 2, 2, "startRecord(fileName)") +{ + argc; + NetConnection *conn = NetConnection::getServerConnection(); + if(!conn) + return; + char filename[1024]; + Con::expandScriptFilename(filename, sizeof(filename), argv[1]); + conn->startDemoRecord(filename); +} + +ConsoleFunction(stopRecord, void, 1, 1, "stopRecord();") +{ + argc; argv; + NetConnection *conn = NetConnection::getServerConnection(); + if(!conn) + return; + conn->stopRecording(); +} + +ConsoleFunction(playDemo, void, 2, 2, "playDemo(recFileName)") +{ + argc; + GameConnection *conn = new GameConnection(false, true, false); + conn->registerObject(); + NetConnection::setServerConnection(conn); + + Sim::getRootGroup()->addObject(conn, gServerConnectionName); + + char filename[1024]; + Con::expandScriptFilename(filename, sizeof(filename), argv[1]); + if(!conn->replayDemoRecord(filename)) + { + Con::printf("Unable to open demo file %s.", filename); + conn->deleteObject(); + } +} + diff --git a/game/net/netDispatch.h b/game/net/netDispatch.h new file mode 100644 index 0000000..9a222d8 --- /dev/null +++ b/game/net/netDispatch.h @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _NETDISPATCH_H_ +#define _NETDISPATCH_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +extern void dispatchInit(); +extern void dispatchCheckTimeouts(); + +enum PacketType +{ + MasterServerGameTypesRequest = 2, + MasterServerGameTypesResponse = 4, + MasterServerListRequest = 6, + MasterServerListResponse = 8, + GameMasterInfoRequest = 10, + GameMasterInfoResponse = 12, + GamePingRequest = 14, + GamePingResponse = 16, + GameInfoRequest = 18, + GameInfoResponse = 20, + GameHeartbeat = 22, + + ConnectChallengeRequest = 26, + ConnectChallengeReject = 28, + ConnectChallengeResponse = 30, + ConnectRequest = 32, + ConnectReject = 34, + ConnectAccept = 36, + Disconnect = 38, +}; + +const U32 CurrentProtocolVersion = 1; +const U32 MinRequiredProtocolVersion = 1; + +bool isServerOnline(); +bool isClientOnline(); + +class NetAddress; +class BitStream; +extern void handleInfoPacket( const NetAddress* address, U8 packetType, BitStream* stream ); + +#endif diff --git a/game/net/netTest.cc b/game/net/netTest.cc new file mode 100644 index 0000000..cefa22e --- /dev/null +++ b/game/net/netTest.cc @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simBase.h" +#include "platform/event.h" +#include "sim/netConnection.h" +#include "core/bitStream.h" +#include "sim/netObject.h" + +//#pragma message "MDF: Make sure this file gets taken out before we go gold" + +class SimpleMessageEvent : public NetEvent +{ + 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 +{ +public: + char message[256]; + SimpleNetObject() + { + mNetFlags.set(ScopeAlways | Ghostable); + dStrcpy(message, "Hello World!"); + } + U32 packUpdate(NetConnection *, U32 /*mask*/, BitStream *stream) + { + stream->writeString(message); + return 0; + } + void unpackUpdate(NetConnection *, BitStream *stream) + { + stream->readString(message); + Con::printf("Got message: %s", message); + } + void setMessage(const char *msg) + { + setMaskBits(1); + dStrcpy(message, msg); + } + static void consoleInit(); + + DECLARE_CONOBJECT(SimpleNetObject); +}; + +IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject); + +static void cSNOSetMessage(SimObject *sno, S32, const char **argv) +{ + ((SimpleNetObject *) sno)->setMessage(argv[2]); +} + +static void cMsg(SimObject *, S32, const char **argv) +{ + NetConnection *con = (NetConnection *) Sim::findObject(argv[1]); + if(con) + con->postNetEvent(new SimpleMessageEvent(argv[2])); +} + +void SimpleNetObject::consoleInit() +{ + Con::addCommand("SimpleNetObject", "setMessage", cSNOSetMessage, "obj.setMessage(msg)", 3, 3); + Con::addCommand("msg", cMsg, "msg(id,msg);", 3, 3); +} + diff --git a/game/net/serverQuery.cc b/game/net/serverQuery.cc new file mode 100644 index 0000000..3b5a4fd --- /dev/null +++ b/game/net/serverQuery.cc @@ -0,0 +1,2023 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 "game/net/serverQuery.h" + +#include "platform/platform.h" +#include "platform/event.h" +#include "core/dnet.h" +#include "core/tVector.h" +#include "core/resManager.h" +#include "core/bitStream.h" +#include "console/console.h" +#include "console/simBase.h" +#include "game/banList.h" +#include "game/version.h" +#include "game/auth.h" +#include "game/net/netDispatch.h" + +// This is basically the server query protocol version now: +static const char* versionString = "VER1"; + +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; + +extern bool gAllowConnections; + +//----------------------------------------------------------------------------- + +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( U8 _index, S32 _key, U32 _time ) + { + index = _index; + key = _key; + time = _time; + tryCount = gPacketRetryCount; + } +}; + +static Vector gPacketStatusList(__FILE__, __LINE__); +static U8 sendPacketData[MaxPacketDataSize]; + +//----------------------------------------------------------------------------- + +struct ServerFilter +{ + enum Type + { + Normal = 0, + Buddy = 1, + Offline = 2, + Favorites = 3, + }; + + Type type; + char* rulesSet; + 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; + rulesSet = NULL; + missionType = NULL; + minPlayers = 0; + maxPlayers = 255; + maxBots = 16; + regionMask = 0xFFFFFFFF; + maxPing = 0; + filterFlags = 0; + minCPU = 0; + buddyCount = 0; + buddyList = NULL; + } + + ~ServerFilter() + { + if ( rulesSet ) + dFree( rulesSet ); + 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* ) + { + processMasterServerQuery( session ); + } +}; + +//---------------------------------------------------------------- +class ProcessPingEvent : public SimEvent +{ + U32 session; + public: + ProcessPingEvent( U32 _session ) + { + session = _session; + } + void process( SimObject* ) + { + processPingsAndQueries( session ); + } +}; + +//---------------------------------------------------------------- +class ProcessPacketEvent : public SimEvent +{ + U32 session; + public: + ProcessPacketEvent( U32 _session ) + { + session = _session; + } + + void process( SimObject* ) + { + processServerListPackets( session ); + } +}; + +//---------------------------------------------------------------- +class HeartbeatEvent : public SimEvent +{ + U32 mSeq; + public: + HeartbeatEvent(U32 seq) + { + mSeq = seq; + } + void process( SimObject* ) + { + processHeartbeat(mSeq); + } +}; + + +//----------------------------------------------------------------------------- +// Public query methods +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +void queryLanServers( U32 port, U8 /*flags*/, bool offline ) +{ + sgServerQueryActive = true; + clearServerList(); + pushServerFavorites(); + + if ( offline ) + { + sActiveFilter.type = ServerFilter::Offline; + + // Clear the filter: + if ( !sActiveFilter.rulesSet || dStricmp( sActiveFilter.rulesSet, "Any" ) != 0 ) + { + sActiveFilter.rulesSet = (char*) dRealloc( sActiveFilter.rulesSet, 4 ); + dStrcpy( sActiveFilter.rulesSet, "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 = 0; + sActiveFilter.maxPlayers = 255; + sActiveFilter.maxBots = 16; + sActiveFilter.regionMask = 0xFFFFFFFF; + sActiveFilter.maxPing = 0; + sActiveFilter.minCPU = 0; + sActiveFilter.filterFlags = 0; + sActiveFilter.buddyCount = 0; + if ( sActiveFilter.buddyList ) + { + dFree( sActiveFilter.buddyList ); + sActiveFilter.buddyList = NULL; + } + } + + NetAddress addr; + char addrText[256]; + dSprintf( addrText, sizeof( addrText ), "IP:BROADCAST:%d", port ); + Net::stringToAddress( addrText, &addr ); + pushPingBroadcast( &addr ); +#if WIN32 || TARG_NET_IPX + dSprintf( addrText, sizeof( addrText ), "IPX:BROADCAST:%d", port ); + Net::stringToAddress( addrText, &addr ); + pushPingBroadcast( &addr ); +#endif + + if (offline) + Con::executef( 4, "onServerQueryStatus", "start", "Querying LAN servers", "0"); + processPingsAndQueries( gPingSession, offline ); +} + +//----------------------------------------------------------------------------- + +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( MasterServerGameTypesRequest, &(*masterList)[master].address, gKey, gPingSession, 0 ); + } +} + +//----------------------------------------------------------------------------- + +void queryMasterServer( + U32 lanPort, + U8 flags, + const char* rulesSet, + 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; + + Con::executef( 4, "onServerQueryStatus", "start", "Querying master server", "0"); + + // Don't query LAN servers if this is a buddy search: + if ( buddyCount == 0 ) + { + sActiveFilter.type = ServerFilter::Normal; + + // Update the active filter: + if ( !sActiveFilter.rulesSet || dStrcmp( sActiveFilter.rulesSet, rulesSet ) != 0 ) + { + sActiveFilter.rulesSet = (char*) dRealloc( sActiveFilter.rulesSet, dStrlen( rulesSet ) + 1 ); + dStrcpy( sActiveFilter.rulesSet, rulesSet ); + } + + 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; + + // The queryLanServers function clears the server lists and + // pushes the server favorites. + queryLanServers(lanPort, 0, false); + } + else + { + sActiveFilter.type = ServerFilter::Buddy; + sActiveFilter.buddyCount = buddyCount; + sActiveFilter.buddyList = (U32*) dRealloc( sActiveFilter.buddyList, buddyCount * 4 ); + dMemcpy( sActiveFilter.buddyList, buddyList, buddyCount * 4 ); + clearServerList(); + } + + //Con::errorf( "** queryMasterServer( %s, %s, %d, %d, %d, %d, %d ) **", rulesSet, missionType, minPlayers, maxPlayers, regionMask, maxPing, buddyCount ); + + // 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, 12, 12, "queryMasterServer(...);" ) +{ + 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* rulesSet = 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]); + U8 buddyCount = 0; + U32 buddyList = 0; + + queryMasterServer(lanPort,flags,rulesSet,missionType,minPlayers,maxPlayers, + maxBots,regionMask,maxPing,minCPU,filterFlags,0,&buddyList); + + dFree(rulesSet); + dFree(missionType); +} + + +//----------------------------------------------------------------------------- + +void queryFavoriteServers( U8 /*flags*/ ) +{ + sgServerQueryActive = true; + clearServerList(); + sActiveFilter.type = ServerFilter::Favorites; + pushServerFavorites(); + + Con::executef( 4, "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( 4, "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()" ) +{ + argc; 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()" ) +{ + argc; argv; + stopServerQuery(); +} + +//----------------------------------------------------------------------------- + +ConsoleFunction(startHeartbeat, void, 1, 1, "startHeartbeat()") +{ + argc; argv; + + if (validateAuthenticatedServer()) { + gHeartbeatSeq++; + processHeartbeat(gHeartbeatSeq); // thump-thump... + } +} + +ConsoleFunction(stopHeartbeat, void, 1, 1, "stopHeartbeat();") +{ + argc; argv; + gHeartbeatSeq++; +} + +//----------------------------------------------------------------------------- + +ConsoleFunction( getServerCount, int, 1, 1, "getServerCount();" ) +{ + argv,argc; + return gServerList.size(); +} + +ConsoleFunction( setServerInfo, bool, 2, 2, "setServerInfo(index);" ) +{ + 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]; + const char* regionString = NULL; + 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( 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( bool( 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; + } + } +} + +//----------------------------------------------------------------------------- + +#ifdef 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 = Platform::getRandom() * 64; + 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 = ( Platform::getRandom() * 200 ); + 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->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( 4, "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->write( U8( MasterServerListRequest ) ); + out->write( U8( sActiveFilter.queryFlags) ); + out->write( ( gMasterServerPing.session << 16 ) | ( gMasterServerPing.key & 0xFFFF ) ); + out->write( U8( 255 ) ); + writeCString( out, sActiveFilter.rulesSet ); + 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( 4, "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( 4, "onServerQueryStatus", "done", "No master servers found.", "0" ); + } + } +} + +//----------------------------------------------------------------------------- + +static void processPingsAndQueries( U32 session, bool schedule ) +{ + if( session != gPingSession ) + return; + + U32 i = 0; + U32 activePings = 0; + U32 time = Platform::getVirtualMilliseconds(); + char addressString[256]; + U8 flags = isClientOnline() ? ServerFilter::OnlineQuery : ServerFilter::OfflineQuery; + 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( 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( 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( 4, "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->write( U8( 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( 4, "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( 4, "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(1, "onClearGameTypes"); + for ( i = 0; i < U32( temp ); i++ ) + { + readCString( stream, stringBuf ); + Con::executef(2, "onAddGameType", stringBuf); + } + + stream->read( &temp ); + Con::executef(1, "onClearMissionTypes"); + for ( i = 0; i < U32( temp ); i++ ) + { + readCString( stream, stringBuf ); + Con::executef(2, "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 ( gAllowConnections ) + { + U8 temp8; + U32 temp32; + + Con::printf( "Received info request from the master server." ); + + BitStream *out = BitStream::getPacketStream(); + + out->write( U8( 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(__linux__) || defined(__OpenBSD__) + temp8 |= ServerInfo::Status_Linux; +#endif + if ( Con::getBoolVariable( "Server::Dedicated" ) ) + temp8 |= ServerInfo::Status_Dedicated; + if ( dStrlen( Con::getVariable( "Pre::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 ( gAllowConnections ) + { + // 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 ( isServerOnline() && ( flags & ServerFilter::OfflineQuery ) ) + return; + + // some banning code here (?) + + BitStream *out = BitStream::getPacketStream(); + + out->write( U8( GamePingResponse ) ); + out->write( flags ); + out->write( key ); + if ( flags & ServerFilter::NoStringCompress ) + writeCString( out, versionString ); + else + out->writeString( versionString ); + out->write( CurrentProtocolVersion ); + out->write( 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 ); + 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 < 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 ( 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 ( gAllowConnections ) + { + // Do not respond to offline queries if this is an online server: + if ( isServerOnline() && ( flags & ServerFilter::OfflineQuery ) ) + return; + + bool compressStrings = !( flags & ServerFilter::NoStringCompress ); + BitStream *out = BitStream::getPacketStream(); + + out->write( U8( 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(__linux__) || defined(__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.rulesSet, "any" ) != 0 + && dStricmp( si->gameType, sActiveFilter.rulesSet ) != 0 ) + { + Con::printf( "Server %s filtered out by rules set. (%s:%s)", addrString, sActiveFilter.rulesSet, 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 handleInfoPacket( const NetAddress* address, U8 packetType, BitStream* stream ) +{ + U8 flags; + U32 key; + + stream->read( &flags ); + stream->read( &key ); + + 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; + } +} + + diff --git a/game/net/serverQuery.h b/game/net/serverQuery.h new file mode 100644 index 0000000..667f44c --- /dev/null +++ b/game/net/serverQuery.h @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 + + +//----------------------------------------------------------------------------- +// Master server information + +struct MasterInfo +{ + NetAddress address; + U32 region; +}; + + +//----------------------------------------------------------------------------- +// 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, bool offline = true ); +extern void queryMasterGameTypes(); +extern void queryMasterServer( + U32 lanPort, + U8 flags, + const char* rulesSet, + 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 DEBUG +extern void addFakeServers( S32 howMany ); +#endif // DEBUG + +#endif diff --git a/game/net/tcpObject.cc b/game/net/tcpObject.cc new file mode 100644 index 0000000..03cd6a2 --- /dev/null +++ b/game/net/tcpObject.cc @@ -0,0 +1,318 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/net/tcpObject.h" + +#include "platform/platform.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "console/simBase.h" +#include "console/consoleInternal.h" +#include "game/demoGame.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; + } + } +} + +TCPObject::TCPObject() +{ + mBuffer = NULL; + mBufferSize = 0; + mPort = 0; + mTag = InvalidSocket; + mNext = NULL; + mState = Disconnected; +} + +TCPObject::~TCPObject() +{ + disconnect(); + dFree(mBuffer); +} + +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(temp); + dFree(temp); + } + } + else if(i != bufferLen) + { + line[len] = 0; + if(len && line[len-1] == '\r') + line[len-1] = 0; + processLine(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, 3, "onConnectRequest", addrBuf, idBuf); +} + +bool TCPObject::processLine(U8 *line) +{ + Con::executef(this, 2, "onLine", line); + return true; +} + +void TCPObject::onDNSResolved() +{ + mState = DNSResolved; + Con::executef(this, 1, "onDNSResolved"); +} + +void TCPObject::onDNSFailed() +{ + mState = Disconnected; + Con::executef(this, 1, "onDNSFailed"); +} + +void TCPObject::onConnected() +{ + mState = Connected; + Con::executef(this, 1, "onConnected"); +} + +void TCPObject::onConnectFailed() +{ + mState = Disconnected; + Con::executef(this, 1, "onConnectFailed"); +} + +void TCPObject::finishLastLine() +{ + if(mBufferSize) + { + mBuffer[mBufferSize] = 0; + processLine(mBuffer); + dFree(mBuffer); + mBuffer = 0; + mBufferSize = 0; + } +} + +void TCPObject::onDisconnect() +{ + finishLastLine(); + mState = Disconnected; + Con::executef(this, 1, "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)); +} + +static void cTCPObjectSend(SimObject *obj, S32 argc, const char **argv) +{ + TCPObject *tcpo = (TCPObject *) obj; + for(S32 i = 2; i < argc; i++) + tcpo->send((const U8 *) argv[i], dStrlen(argv[i])); +} + +static void cTCPObjectListen(SimObject *obj, S32, const char **argv) +{ + TCPObject *tcpo = (TCPObject *) obj; + tcpo->listen(U32(dAtoi(argv[2]))); +} + +static void cTCPObjectConnect(SimObject *obj, S32, const char **argv) +{ + TCPObject *tcpo = (TCPObject *) obj; + tcpo->connect(argv[2]); +} + +static void cTCPObjectDisconnect(SimObject *obj, S32, const char **) +{ + TCPObject *tcpo = (TCPObject *) obj; + tcpo->disconnect(); +} + +void TCPObject::consoleInit() +{ + Con::addCommand("TCPObject", "listen", cTCPObjectListen, "obj.listen(port)", 3, 3); + Con::addCommand("TCPObject", "send", cTCPObjectSend, "obj.send(string, ...)", 3, 0); + Con::addCommand("TCPObject", "connect", cTCPObjectConnect, "obj.connect(addr)", 3, 3); + Con::addCommand("TCPObject", "disconnect", cTCPObjectDisconnect, "obj.disconnect()", 2, 2); +} + + +void DemoGame::processConnectedReceiveEvent(ConnectedReceiveEvent* event ) +{ + TCPObject *tcpo = TCPObject::find(event->tag); + if(!tcpo) + { + Con::printf("Got bad connected receive event."); + return; + } + U32 size = U32(event->size - ConnectedReceiveEventHeaderSize); + U8 *buffer = event->data; + + while(size) + { + U32 ret = tcpo->onReceive(buffer, size); + AssertFatal(ret <= size, "Invalid return size"); + size -= ret; + buffer += ret; + } +} + +void DemoGame::processConnectedAcceptEvent( ConnectedAcceptEvent* event ) +{ + TCPObject *tcpo = TCPObject::find(event->portTag); + if(!tcpo) + return; + tcpo->onConnectionRequest(&event->address, event->connectionTag); +} + +void DemoGame::processConnectedNotifyEvent( ConnectedNotifyEvent* event ) +{ + TCPObject *tcpo = TCPObject::find(event->tag); + if(!tcpo) + return; + switch(event->state) + { + case ConnectedNotifyEvent::DNSResolved: + tcpo->onDNSResolved(); + break; + case ConnectedNotifyEvent::DNSFailed: + tcpo->onDNSFailed(); + break; + case ConnectedNotifyEvent::Connected: + tcpo->onConnected(); + break; + case ConnectedNotifyEvent::ConnectFailed: + tcpo->onConnectFailed(); + break; + case ConnectedNotifyEvent::Disconnected: + tcpo->onDisconnect(); + break; + } +} diff --git a/game/net/tcpObject.h b/game/net/tcpObject.h new file mode 100644 index 0000000..8028184 --- /dev/null +++ b/game/net/tcpObject.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TCPOBJECT_H_ +#define _TCPOBJECT_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +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(U8 *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); + + static void consoleInit(); +}; + + +#endif // _H_TCPOBJECT_ diff --git a/game/netDispatch.cc b/game/netDispatch.cc new file mode 100644 index 0000000..c817080 --- /dev/null +++ b/game/netDispatch.cc @@ -0,0 +1,980 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#pragma warning(disable:4786) // Remove STL warnings. + +#include "Platform/platform.h" +#include "Core/dnet.h" +#include "console/simBase.h" +#include "Sim/netConnection.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "Core/bitStream.h" +#include "Sim/netObject.h" +#include "game/gameConnection.h" +#include "game/serverQuery.h" +#include "game/targetManager.h" +#include "game/netDispatch.h" +#include "Platform/gameInterface.h" +#include "game/tribesGame.h" +#include "game/banList.h" + +const char *gServerConnectionName = "ServerConnection"; +const char *gLocalClientConnectionName = "LocalClientConnection"; + +bool gAllowConnections = false; + +bool isServerOnline() +{ + if(Game->isJournalReading()) + { + U32 res; + Game->journalRead(&res); + return bool(res); + } + else + { + bool ret = false; + // Authentication removed... + ret = true; + + if(Game->isJournalWriting()) + Game->journalWrite(U32(ret)); + return ret; + } +} + +bool isClientOnline() +{ + if(Game->isJournalReading()) + { + U32 res; + Game->journalRead(&res); + return bool(res); + } + else + { + bool ret = false; + // Authentication removed... + ret = true; + + if(Game->isJournalWriting()) + Game->journalWrite(U32(ret)); + return ret; + } +} + +bool validateAuthenticatedServer() +{ + return true; +} + +bool validateAuthenticatedClient() +{ + return true; +} + +//---------------------------------------------------------------- +ConsoleFunction(startHeartbeat, void, 1, 1, "startHeartbeat()") +{ + argc; argv; + + if(validateAuthenticatedServer()) + startHeartbeat(); +} + +enum { + MaxConnectArgs = 16, + MaxPendingConnects = 20, + PendingConnectTimeout = 7000, // 7 seconds + + ChallengeRetryCount = 4, + ChallengeRetryTime = 2500, // 2.5 seconds + + ConnectRetryCount = 4, + ConnectRetryTime = 2500, + TimeoutCheckInterval = 1500, // check for timeouts every 1.5 secs + MaxAuthInfoSize = 1024, + MaxPasswordLength = 16, +}; + +//---------------------------------------------------------------- +// Client add and drop management +//---------------------------------------------------------------- + +void GameClientAdded(GameConnection *newClient, S32 argc, const char **argv) +{ + SimGroup *g = Sim::getClientGroup(); + g->addObject(newClient); + const char *vargs[MaxConnectArgs + 2]; + vargs[0] = "onConnect"; + vargs[1] = NULL; + if(argc > MaxConnectArgs) + argc = MaxConnectArgs; + for(S32 i = 0; i < argc; i++) + vargs[i + 2] = argv[i]; + + Con::execute(newClient, argc + 2, vargs); +} + +//--------------------------------------------------------------- + +//---------------------------------------------------------------- +// Connect protocol message formats +//---------------------------------------------------------------- + +// ConnectChallengeRequest +// ProtocolVersion +// Certificate +// ClientConnectSequence +// Password + +// ConnectChallengeResponse +// ProtocolVersion +// ServerConnectSequence +// ClientConnectSequence + +// ConnectChallengeReject +// ClientConnectSequence +// Reason + +// ConnectRequest +// ServerConnectSequence +// ClientConnectSequence +// ProtocolVersion +// console args + +// ConnectAccept +// ServerConnectSequence +// ClientConnectSequence +// ProtocolVersion +// ClientID + +// ConnectReject +// ServerConnectSequence +// ClientConnectSequence +// Reason + +// Disconnect +// ServerConnectSequence +// ClientConnectSequence +// Reason string + +//---------------------------------------------------------------- +// Connect request and server response structures +//---------------------------------------------------------------- + +struct ConnectRequestStruct +{ + enum { + NotConnecting, + Challenging, + Connecting, + } state; + + U32 clientConnectSequence; + U32 serverConnectSequence; + U32 protocolVersion; + U32 lastSendTime; + U32 sendCount; + NetAddress serverAddress; + U32 challenge; + bool authenticated; + char password[MaxPasswordLength + 1]; + + U32 argc; + char *argv[MaxConnectArgs]; + U8 argBuffer[MaxPacketDataSize]; +}; + +struct ConnectRequestPending +{ + // Certificate needs to be stored here. + // V12: Challenge stuff was partially hacked out + U32 challenge1; + + NetAddress sourceAddress; + U32 clientProtocolVersion; + U32 clientConnectSequence; + U32 serverConnectSequence; + U32 lastRecvTime; +}; + +ConnectRequestStruct gConnectRequest; + +ConnectRequestPending gPendingConnects[MaxPendingConnects]; +U32 gNumPendingConnects; + +void connectRequestRejected(const char *buffer) +{ + Con::executef(2, "onConnectRequestRejected", buffer); +} + +void challengeRequestRejected(const char *buffer) +{ + Con::executef(2, "onChallengeRequestRejected", buffer); +} + +void connectRequestTimedOut() +{ + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + Con::executef(1, "onConnectRequestTimedOut"); +} + +void connectionToServerTimedOut() +{ + Con::executef(1, "onConnectionToServerTimedOut"); +} + +void connectionToServerLost(const char* msg) +{ + Con::executef(2, "onConnectionToServerLost", msg); +} + +//---------------------------------------------------------------- +// Connect request and server response functions +//---------------------------------------------------------------- + +void sendConnectChallengeRequest() +{ + BitStream *out = BitStream::getPacketStream(); + out->write(U8(ConnectChallengeRequest)); + out->write(CurrentProtocolVersion); + out->write(gConnectRequest.clientConnectSequence); + out->writeString(gConnectRequest.password); + // Authentication removed... + + gConnectRequest.sendCount++; + gConnectRequest.lastSendTime = Platform::getVirtualMilliseconds(); + + Con::printf("Connect Challenge Request: %d", gConnectRequest.sendCount); + + BitStream::sendPacketStream(&gConnectRequest.serverAddress); +} + +void sendConnectChallengeResponse(U32 index) +{ + ConnectRequestPending *p = gPendingConnects + index; + + BitStream *out = BitStream::getPacketStream(); + + out->write(U8(ConnectChallengeResponse)); + out->write(CurrentProtocolVersion); + out->write(p->serverConnectSequence); + out->write(p->clientConnectSequence); + + // Authentication removed... + + p->lastRecvTime = Platform::getVirtualMilliseconds(); + Con::printf("Sent challenge response"); + + BitStream::sendPacketStream(&p->sourceAddress); +} + +void rejectConnectChallengeRequest(const NetAddress *source, U32 clientConnectSequence, const char *reason) +{ + BitStream *out = BitStream::getPacketStream(); + + out->write(U8(ConnectChallengeReject)); + out->write(clientConnectSequence); + out->writeString(reason); + BitStream::sendPacketStream(source); +} + +void handleConnectChallengeRequest(const NetAddress *source, BitStream *stream) +{ + if(!gAllowConnections) + return; + + // reasons for rejection + // password wrong + // server full + // bad protocol version + // invalid certificate + // don't meet tribe/UID connect restrictions + + U32 protoVersion; + U32 clientConnectSequence; + const char *rejectString = NULL; + char joinPassword[MaxPasswordLength + 1]; + + stream->read(&protoVersion); + stream->read(&clientConnectSequence); + + if(protoVersion < MinRequiredProtocolVersion) + { + rejectConnectChallengeRequest(source, clientConnectSequence, "CHR_PROTOCOL"); + return; + } + + if(protoVersion > CurrentProtocolVersion) + protoVersion = CurrentProtocolVersion; + + // first time out any outstanding challenge requests: + U32 time = Platform::getVirtualMilliseconds(); + + for(U32 i = 0; i < gNumPendingConnects;) + { + ConnectRequestPending *p = gPendingConnects + i; + + // see if this is already in the list: + if(Net::compareAddresses(source, &p->sourceAddress) && p->clientConnectSequence == clientConnectSequence) + { + // just up the time and ping back + sendConnectChallengeResponse(i); + return; + } + if(time > p->lastRecvTime + PendingConnectTimeout) + *p = gPendingConnects[--gNumPendingConnects]; + else + i++; + } + + // Test the password: + stream->readString(joinPassword); + const char* password = Con::getVariable( "$Host::Password" ); + if ( password[0] ) + { + if ( dStrncmp( password, joinPassword, MaxPasswordLength ) != 0 ) + { + // IMPORTANT! Do NOT change the message here, it is used by the game. + rejectConnectChallengeRequest(source, clientConnectSequence, "PASSWORD"); + return; + } + } + + // V12: Challenge stuff was partially hacked out + U32 challenge1 = 0; + + if(stream->readFlag()) + { + U8 buffer[MaxAuthInfoSize]; + U32 size = stream->readInt(16); + + if(size > MaxAuthInfoSize) + { + rejectConnectChallengeRequest(source, clientConnectSequence, "CHR_INVALID_CHALLENGE_PACKET"); + return; + } + + // V12: Used to have WON stuff here... + } + + U32 index; + if(gNumPendingConnects == MaxPendingConnects) + index = mRandI(0, MaxPendingConnects-1); + else + index = gNumPendingConnects++; + + ConnectRequestPending *p = gPendingConnects + index; + p->challenge1 = challenge1; + p->clientConnectSequence = clientConnectSequence; + p->sourceAddress = *source; + p->clientProtocolVersion = protoVersion; + p->serverConnectSequence = Platform::getVirtualMilliseconds(); + + Con::printf("Got challenge request"); + sendConnectChallengeResponse(index); +} + +void sendConnectRequest() +{ + gConnectRequest.sendCount++; + gConnectRequest.lastSendTime = Platform::getVirtualMilliseconds(); + + BitStream *out = BitStream::getPacketStream(); + + out->write(U8(ConnectRequest)); + out->write(gConnectRequest.serverConnectSequence); + out->write(gConnectRequest.clientConnectSequence); + out->write(gConnectRequest.protocolVersion); + if(out->writeFlag(gConnectRequest.authenticated)) + { + out->write(gConnectRequest.challenge); + } + out->write(gConnectRequest.argc); + for(U32 i = 0; i < gConnectRequest.argc; i++) + out->writeString(gConnectRequest.argv[i]); + + Con::printf("Sent connect request"); + BitStream::sendPacketStream(&gConnectRequest.serverAddress); +} + +void handleConnectChallengeResponse(const NetAddress *addr, BitStream *stream) +{ + if(gConnectRequest.state != ConnectRequestStruct::Challenging) + return; + if(!Net::compareAddresses(&gConnectRequest.serverAddress, addr)) + return; + U32 serverProtocol; + U32 serverConnectSequence; + U32 clientConnectSequence; + + stream->read(&serverProtocol); + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + + // must be an old connect request... + if(clientConnectSequence != gConnectRequest.clientConnectSequence) + return; + + // check if it's authenticated + gConnectRequest.authenticated = stream->readFlag(); + if(gConnectRequest.authenticated) + { + // V12: Authentication removed... + // V12: Used to have WON stuff here... + } + + gConnectRequest.state = ConnectRequestStruct::Connecting; + gConnectRequest.serverConnectSequence = serverConnectSequence; + gConnectRequest.sendCount = 0; + gConnectRequest.protocolVersion = serverProtocol < CurrentProtocolVersion ? serverProtocol : CurrentProtocolVersion; + Con::printf("Got challenge response"); + + sendConnectRequest(); +} + +void GameConnection::connectionError(const char *errorString) +{ + if(isServerConnection()) + { + Con::printf("Connection error: %s.", errorString); + connectionToServerLost( errorString ); + } + else + { + Con::printf("Client %d packet error: %s.", getId(), errorString); + setDisconnectReason("Packet Error."); + } + deleteObject(); +} + +void handleDisconnect(const NetAddress *addr, BitStream *stream) +{ + Con::printf("Got disconnect packet."); + + NetConnection *conn = NetConnection::lookup(addr); + U32 serverConnectSequence, clientConnectSequence; + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + char reason[256]; + stream->readString(reason); + + U32 cServerConnectSequence, cClientConnectSequence; + if(!conn) + return; + conn->getSequences(&cClientConnectSequence, &cServerConnectSequence); + if(cClientConnectSequence != clientConnectSequence || + cServerConnectSequence != serverConnectSequence) + return; + + // ok, it's gone. + if(conn->isServerConnection()) + { + Con::printf("Connection with server lost."); + connectionToServerLost( reason ); + } + else + { + Con::printf("Client %d disconnected.", conn->getId()); + ((GameConnection *) conn)->setDisconnectReason(reason); + } + conn->deleteObject(); +} + +void handleConnectAccept(const NetAddress *addr, BitStream *stream) +{ + if(gConnectRequest.state != ConnectRequestStruct::Connecting) + return; + if(!Net::compareAddresses(&gConnectRequest.serverAddress, addr)) + return; + + U32 serverConnectSequence, clientConnectSequence; + U32 protocol, id; + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + stream->read(&protocol); + stream->read(&id); + + if(serverConnectSequence != gConnectRequest.serverConnectSequence || + clientConnectSequence != gConnectRequest.clientConnectSequence || + protocol > CurrentProtocolVersion || + protocol < MinRequiredProtocolVersion) + return; + + // ok, we can connect up now: + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + + GameConnection *conn = new GameConnection(false, true, true); + conn->registerObject(); + NetConnection::setServerConnection(conn); + + conn->setProtocolVersion(protocol); + conn->setSequences(clientConnectSequence, serverConnectSequence); + conn->setConnectSequence(clientConnectSequence ^ serverConnectSequence); + conn->setNetAddress(addr); + conn->setNetworkConnection(true); + + Sim::getRootGroup()->addObject(conn, gServerConnectionName); + Con::executef(1, "ServerConnectionAccepted"); + Con::printf("Connection accepted - id %d protocol %d", id, protocol); +} + +void sendConnectAccept(NetConnection *conn) +{ + BitStream *out = BitStream::getPacketStream(); + + out->write(U8(ConnectAccept)); + U32 clientConnectSeq, serverConnectSeq; + U32 protocol; + + conn->getSequences(&clientConnectSeq, &serverConnectSeq); + protocol = conn->getProtocolVersion(); + + out->write(serverConnectSeq); + out->write(clientConnectSeq); + out->write(protocol); + out->write(U32(conn->getId())); + Con::printf("Sending connect accept: %d", conn->getId()); + + BitStream::sendPacketStream(conn->getNetAddress()); +} + +void handleConnectReject(const NetAddress *address, BitStream *stream) +{ + if(gConnectRequest.state != ConnectRequestStruct::Connecting) + return; + U32 clientConnectSequence, serverConnectSequence; + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + + if(!Net::compareAddresses(address, &gConnectRequest.serverAddress) || + clientConnectSequence != gConnectRequest.clientConnectSequence || + serverConnectSequence != gConnectRequest.serverConnectSequence) + return; + char buffer[256]; + stream->readString(buffer); + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + connectRequestRejected(buffer); +} + +void handleConnectChallengeReject(const NetAddress *address, BitStream *stream) +{ + if(gConnectRequest.state != ConnectRequestStruct::Challenging) + return; + U32 clientConnectSequence; + stream->read(&clientConnectSequence); + + if(!Net::compareAddresses(address, &gConnectRequest.serverAddress) || + clientConnectSequence != gConnectRequest.clientConnectSequence) + return; + char buffer[256]; + stream->readString(buffer); + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + challengeRequestRejected(buffer); +} + +void handleConnectRequest(const NetAddress *address, BitStream *stream) +{ + if(!gAllowConnections) + return; + + char addrString[256]; + + Net::addressToString(address, addrString); + Con::printf("Connection request from: %s", addrString); + + U32 serverConnectSequence; + U32 clientConnectSequence; + U32 protocolRequestVersion; + + stream->read(&serverConnectSequence); + stream->read(&clientConnectSequence); + stream->read(&protocolRequestVersion); + + const char *rejectString = NULL; + + if(protocolRequestVersion < MinRequiredProtocolVersion || + protocolRequestVersion > CurrentProtocolVersion) + rejectString = "CR_INVALID_PROTOCOL_VERSION"; + + U32 i; + for(i = 0; i < gNumPendingConnects; i++) + { + ConnectRequestPending *p = gPendingConnects + i; + + if(Net::compareAddresses(address, &p->sourceAddress) && + p->clientConnectSequence == clientConnectSequence && + p->serverConnectSequence == serverConnectSequence) + break; + } + if(i == gNumPendingConnects) + { + // if it's not in the pending list, check if + // we're already connected to this guy: + NetConnection *connect = NetConnection::lookup(address); + if(connect) + { + U32 clientConSeq, serverConSeq; + connect->getSequences(&clientConSeq, &serverConSeq); + + // we already accepted this connection: + if(clientConSeq == clientConnectSequence && + serverConSeq == serverConnectSequence) + sendConnectAccept(connect); + } + return; + } + U32 clientId = 0; + + // check the peer auth... + if(stream->readFlag()) + { + // V12: Authentication removed... + // V12: Also used to have WON stuff here... + rejectString = "CR_INVALID_CONNECT_PACKET"; + } + + if ( !rejectString ) + { + if ( gBanList.isBanned( 0 /* V12: Was Won net ID */, addrString ) ) + rejectString = "CR_YOUAREBANNED"; + else if ( Con::getIntVariable( "$HostGamePlayerCount" ) >= Con::getIntVariable( "$Host::MaxPlayers" ) ) + rejectString = "CR_SERVERFULL"; + } + + // erase the request from the pending list + gPendingConnects[i] = gPendingConnects[--gNumPendingConnects]; + gPendingConnects[gNumPendingConnects].challenge1 = NULL; // kill the byte buffer. + + const char *argv[MaxConnectArgs]; + char argbuf[MaxConnectArgs][256]; + U32 argc; + + stream->read(&argc); + if(argc > MaxConnectArgs) + rejectString = "CR_INVALID_CONNECT_PACKET"; + else + { + for(U32 i = 0; i < argc; i++) + { + argv[i] = argbuf[i]; + stream->readString(argbuf[i]); + } + } + + if(rejectString) + { + BitStream *out = BitStream::getPacketStream(); + out->write(U8(ConnectReject)); + out->write(serverConnectSequence); + out->write(clientConnectSequence); + out->writeString(rejectString); + + + BitStream::sendPacketStream(address); + } + else + { + Con::printf("Accepting connect... CLIENT ID = %d", clientId); + // let this guy into the game: + GameConnection *conn = new GameConnection(true, false, true); + + conn->registerObject(); + conn->setProtocolVersion(protocolRequestVersion); + conn->setNetAddress(address); + conn->setNetworkConnection(true); + + conn->setSequences(clientConnectSequence, serverConnectSequence); + conn->setConnectSequence(clientConnectSequence ^ serverConnectSequence); + sendConnectAccept(conn); + GameClientAdded(conn, argc, argv); + } +} + +static void cConnect(SimObject *, S32 argc, const char **argv) +{ + if(!validateAuthenticatedClient()) + return; + gConnectRequest.state = ConnectRequestStruct::Challenging; + gConnectRequest.clientConnectSequence = Platform::getVirtualMilliseconds(); + + if(!Net::stringToAddress(argv[1], &gConnectRequest.serverAddress)) + return; + + gConnectRequest.protocolVersion = CurrentProtocolVersion; + dStrncpy( gConnectRequest.password, argv[2], MaxPasswordLength ); + + argc -= 3; + if(argc > MaxConnectArgs) + argc = MaxConnectArgs; + + U32 bufPos = 0; + U32 i; + for(i = 0; i < argc; i++) + { + const char *str = argv[i + 3]; + U32 len = dStrlen(str); + + if(bufPos + len + 1 > MaxPacketDataSize) + break; + dStrcpy((char *) gConnectRequest.argBuffer + bufPos, str); + gConnectRequest.argv[i] = (char *) gConnectRequest.argBuffer + bufPos; + bufPos += len + 1; + } + gConnectRequest.argc = i; + gConnectRequest.sendCount = 0; + // Authentication removed... + sendConnectChallengeRequest(); +} + + +//---------------------------------------------------------------- +// Connection initiation console commands +//---------------------------------------------------------------- + +static void cAllowConnections(SimObject *, S32, const char **argv) +{ + if(!validateAuthenticatedServer()) + return; + gAllowConnections = dAtob(argv[1]); +} + +static void cLocalConnect(SimObject *, S32 argc, const char** argv) +{ + if(!validateAuthenticatedClient()) + return; + + GameConnection *clientConnection = new GameConnection(false, true, true); + GameConnection *serverConnection = new GameConnection(true, false, true); + + clientConnection->registerObject(); + Sim::getRootGroup()->addObject(clientConnection, gServerConnectionName); + + serverConnection->registerObject(); + serverConnection->assignName(gLocalClientConnectionName); + NetConnection::setServerConnection(clientConnection); + NetConnection::setLocalClientConnection(serverConnection); + + clientConnection->setRemoteConnectionObjectId(serverConnection->getId()); + serverConnection->setRemoteConnectionObjectId(clientConnection->getId()); + + // V12: Used to have WON stuff here... + + clientConnection->setProtocolVersion(CurrentProtocolVersion); + serverConnection->setProtocolVersion(CurrentProtocolVersion); + clientConnection->setConnectSequence(0); + serverConnection->setConnectSequence(0); + + GameClientAdded(serverConnection, argc - 1, argv + 1); + Con::executef( 1, "LocalConnectionAccepted" ); +} + +void clientNetProcess() +{ + NetConnection *con = NetConnection::getServerConnection(); + if (con) + con->checkPacketSend(); +} + +void serverNetProcess() +{ + NetObject::collapseDirtyList(); // collapse all the mask bits... + + SimGroup *clientGroup = Sim::getClientGroup(); + for(SimGroup::iterator i = clientGroup->begin(); i != clientGroup->end(); i++) + { + NetConnection *con = (NetConnection *)(*i); + if(con->isLocalConnection() || con->isNetworkConnection()) + con->checkPacketSend(); + } +} + +BitStream gPacketStream(NULL, 0); + +void TribesGame::processPacketReceiveEvent(PacketReceiveEvent * prEvent) +{ + U32 dataSize = prEvent->size - PacketReceiveEventHeaderSize; + gPacketStream.setBuffer(prEvent->data, dataSize); + if(!(prEvent->data[0] & 0x01)) // it's a non-protocol packet of some sort... + { + U8 packetType; + gPacketStream.read(&packetType); + NetAddress *addr = &prEvent->sourceAddress; + + if(packetType <= GameHeartbeat) + handleInfoPacket(addr, packetType, &gPacketStream); + else + { + switch(packetType) + { + case ConnectChallengeRequest: + handleConnectChallengeRequest(addr, &gPacketStream); + break; + case ConnectRequest: + handleConnectRequest(addr, &gPacketStream); + break; + case ConnectChallengeResponse: + handleConnectChallengeResponse(addr, &gPacketStream); + break; + case ConnectAccept: + handleConnectAccept(addr, &gPacketStream); + break; + case Disconnect: + handleDisconnect(addr, &gPacketStream); + break; + case ConnectReject: + handleConnectReject(addr, &gPacketStream); + break; + case ConnectChallengeReject: + handleConnectChallengeReject(addr, &gPacketStream); + break; + } + } + } + else + { + // lookup the connection in the addressTable + NetConnection *conn = NetConnection::lookup(&prEvent->sourceAddress); + if(conn) + conn->processRawPacket(&gPacketStream); + } +} + +void dispatchInit() +{ + gConnectRequest.state = ConnectRequestStruct::NotConnecting; + gNumPendingConnects = 0; + + Con::addCommand("allowConnections", cAllowConnections, "allowConnections(bool);", 2, 2); + Con::addCommand("localConnect", cLocalConnect, "localConnect();", 1, 16); + Con::addCommand("connect", cConnect, "connect(addr);", 2, 17); +} + +//void GameConnectionRemoved(GameConnection *conn) +//{ +// if(conn->isNetworkConnection()) +// { +// Con::printf("Issuing Disconnect packet."); +// +// // send a disconnect packet... +// U32 serverConnectSequence, clientConnectSequence; +// conn->getSequences(&clientConnectSequence, &serverConnectSequence); +// +// BitStream *out = BitStream::getPacketStream(); +// out->write(U8(Disconnect)); +// out->write(serverConnectSequence); +// out->write(clientConnectSequence); +// out->writeString(""); +// +// BitStream::sendPacketStream(conn->getNetAddress()); +// } +//} + +void dispatchCheckTimeouts() +{ + static U32 lastTimeoutCheckTime = 0; + U32 time = Platform::getVirtualMilliseconds(); + if(time > lastTimeoutCheckTime + TimeoutCheckInterval) + { + // check the connection state: + if(gConnectRequest.state == ConnectRequestStruct::Challenging && + time > gConnectRequest.lastSendTime + ChallengeRetryTime) + { + if(gConnectRequest.sendCount > ChallengeRetryCount) + connectRequestTimedOut(); + else + sendConnectChallengeRequest(); + } + else if(gConnectRequest.state == ConnectRequestStruct::Connecting && + time > gConnectRequest.lastSendTime + ConnectRetryTime) + { + if(gConnectRequest.sendCount > ConnectRetryCount) + connectRequestTimedOut(); + else + sendConnectRequest(); + } + + lastTimeoutCheckTime = time; + NetConnection *walk = NetConnection::getConnectionList(); + + while(walk) + { + NetConnection *next = walk->getNext(); + if(walk->checkTimeout(time)) + { + // this baddie timed out + if(walk->isServerConnection()) + { + Con::printf("Connection to server timed out"); + connectionToServerTimedOut(); + } + else + { + Con::printf("Client %d timed out.", walk->getId()); + ((GameConnection *) walk)->setDisconnectReason("TimedOut"); + } + walk->deleteObject(); + } + walk = next; + } + } +} + + +//---------------------------------------------------------------- +// DNet external function declarations +//---------------------------------------------------------------- + +void GameConnectionRejected(NetConnectionId id, BitStream *stream) +{ + GameConnection *conn = (GameConnection *) Sim::findObject(id); + if(conn) + conn->deleteObject(); + Con::printf("Connection rejected: %s", stream->getBuffer()); +} + + +void GameConnectionEstablished(NetConnectionId id) +{ + Con::printf("Connection established %d", id); +} + +ConsoleFunction(startRecord, void, 2, 2, "startRecord(fileName)") +{ + argc; + NetConnection *conn = NetConnection::getServerConnection(); + if(!conn) + return; + conn->startDemoRecord(argv[1]); +} + +ConsoleFunction(stopRecord, void, 1, 1, "stopRecord();") +{ + argc; argv; + NetConnection *conn = NetConnection::getServerConnection(); + if(!conn) + return; + conn->stopRecording(); +} + +ConsoleFunction(playDemo, void, 2, 2, "playDemo(recFileName)") +{ + argc; + GameConnection *conn = new GameConnection(false, true, false); + conn->registerObject(); + NetConnection::setServerConnection(conn); + + Sim::getRootGroup()->addObject(conn, gServerConnectionName); + if(!conn->replayDemoRecord(argv[1])) + { + Con::printf("Unable to open demo file %s.", argv[1]); + conn->deleteObject(); + } +} + diff --git a/game/netDispatch.h b/game/netDispatch.h new file mode 100644 index 0000000..b8985e9 --- /dev/null +++ b/game/netDispatch.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _NETDISPATCH_H_ +#define _NETDISPATCH_H_ + +extern void dispatchInit(); +extern void dispatchCheckTimeouts(); + +enum PacketType +{ + MasterServerGameTypesRequest = 2, + MasterServerGameTypesResponse = 4, + MasterServerListRequest = 6, + MasterServerListResponse = 8, + GameMasterInfoRequest = 10, + GameMasterInfoResponse = 12, + GamePingRequest = 14, + GamePingResponse = 16, + GameInfoRequest = 18, + GameInfoResponse = 20, + GameHeartbeat = 22, + + ConnectChallengeRequest = 26, + ConnectChallengeReject = 28, + ConnectChallengeResponse = 30, + ConnectRequest = 32, + ConnectReject = 34, + ConnectAccept = 36, + Disconnect = 38, +}; + +const U32 CurrentProtocolVersion = 34; + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// - if this is bumped (currently 34) then let BradH know so +// he can change some server query things.. then you can delete +// this message. +const U32 MinRequiredProtocolVersion = 34; +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +// Connect protocol is as follows: +// Client issues ConnectChallengeRequest +// Server responds with ConnectChallengeReject or ConnectChallengeResponse +// Client issues ConnectRequest +// Server responds with with ConnectReject or ConnectAccept + +bool isServerOnline(); +bool isClientOnline(); +extern void handleInfoPacket( const NetAddress* address, U8 packetType, BitStream* stream ); + +#endif diff --git a/game/netTest.cc b/game/netTest.cc new file mode 100644 index 0000000..e8241a9 --- /dev/null +++ b/game/netTest.cc @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/simBase.h" +#include "Platform/event.h" +#include "Sim/netConnection.h" +#include "Core/bitStream.h" +#include "Sim/netObject.h" + +//#pragma message "MDF: Make sure this file gets taken out before we go gold" + +class SimpleMessageEvent : public NetEvent +{ + 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 +{ +public: + char message[256]; + SimpleNetObject() + { + mNetFlags.set(ScopeAlways | Ghostable); + dStrcpy(message, "Hello World!"); + } + U32 packUpdate(NetConnection *, U32 /*mask*/, BitStream *stream) + { + stream->writeString(message); + return 0; + } + void unpackUpdate(NetConnection *, BitStream *stream) + { + stream->readString(message); + Con::printf("Got message: %s", message); + } + void setMessage(const char *msg) + { + setMaskBits(1); + dStrcpy(message, msg); + } + static void consoleInit(); + + DECLARE_CONOBJECT(SimpleNetObject); +}; + +IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject); + +static void cSNOSetMessage(SimObject *sno, S32, const char **argv) +{ + ((SimpleNetObject *) sno)->setMessage(argv[2]); +} + +static void cMsg(SimObject *, S32, const char **argv) +{ + NetConnection *con = (NetConnection *) Sim::findObject(argv[1]); + if(con) + con->postNetEvent(new SimpleMessageEvent(argv[2])); +} + +void SimpleNetObject::consoleInit() +{ + Con::addCommand("SimpleNetObject", "setMessage", cSNOSetMessage, "obj.setMessage(msg)", 3, 3); + Con::addCommand("msg", cMsg, "msg(id,msg);", 3, 3); +} + diff --git a/game/objectTypes.h b/game/objectTypes.h new file mode 100644 index 0000000..7c3f1a0 --- /dev/null +++ b/game/objectTypes.h @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _OBJECTTYPES_H_ +#define _OBJECTTYPES_H_ + +// Types used for SimObject type masks (SimObject::mTypeMask) +// + +/* NB! If a new object type is added, don't forget to add it to the + * consoleInit function in simBase.cc + */ + +enum SimObjectTypes +{ + #define bit(x) (1 << (x)) + + // Types used by the SceneObject class + DefaultObjectType = 0, + StaticObjectType = bit(0), + + // Basic Engine Types + EnvironmentObjectType = bit(1), + TerrainObjectType = bit(2), + InteriorObjectType = bit(3), + WaterObjectType = bit(4), + TriggerObjectType = bit(5), + MarkerObjectType = bit(6), + UNUSED_AVAILABLE = bit(7), + ForceFieldObjectType = bit(8), + DecalManagerObjectType = bit(9), + + // Game Types + GameBaseObjectType = bit(10), + ShapeBaseObjectType = bit(11), + CameraObjectType = bit(12), + StaticShapeObjectType = bit(13), + PlayerObjectType = bit(14), + ItemObjectType = bit(15), + VehicleObjectType = bit(16), + VehicleBlockerObjectType = bit(17), + ProjectileObjectType = bit(18), + ExplosionObjectType = bit(19), + CorpseObjectType = bit(20), + TurretObjectType = bit(21), + DebrisObjectType = bit(22), + PhysicalZoneObjectType = bit(23), + StaticTSObjectType = bit(24), + GuiControlObjectType = bit(25), + + StaticRenderedObjectType = bit(26), + + // The following are allowed types that can be set on datablocks for static shapes + // + DamagableItemObjectType = bit(27), + SensorObjectType = bit(28), + StationObjectType = bit(29), + GeneratorObjectType = bit(30) +}; + +#define STATIC_COLLISION_MASK ( TerrainObjectType | \ + InteriorObjectType | \ + ForceFieldObjectType | \ + StaticObjectType ) \ + +#define DAMAGEABLE_MASK ( PlayerObjectType | \ + VehicleObjectType | \ + StationObjectType | \ + GeneratorObjectType | \ + SensorObjectType | \ + DamagableItemObjectType | \ + TurretObjectType ) \ + +#endif diff --git a/game/particleEmitter.cc b/game/particleEmitter.cc new file mode 100644 index 0000000..bf3f1ac --- /dev/null +++ b/game/particleEmitter.cc @@ -0,0 +1,226 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/particleEmitter.h" +#include "game/particleEngine.h" +#include "Core/bitStream.h" +#include "console/consoleTypes.h" +#include "console/objectTypes.h" +#include "Math/mathIO.h" + +IMPLEMENT_CO_DATABLOCK_V1(ParticleEmissionDummyData); +IMPLEMENT_CO_NETOBJECT_V1(ParticleEmissionDummy); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ParticleEmissionDummyData::ParticleEmissionDummyData() +{ + timeMultiple = 1.0; +} + +ParticleEmissionDummyData::~ParticleEmissionDummyData() +{ + +} + + +//-------------------------------------------------------------------------- +void ParticleEmissionDummyData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("timeMultiple", TypeF32, Offset(timeMultiple, ParticleEmissionDummyData)); +} + + +//-------------------------------------------------------------------------- +bool ParticleEmissionDummyData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (timeMultiple < 0.01 || timeMultiple > 100) { + Con::warnf("ParticleEmissionDummyData::onAdd(%s): timeMultiple must be between 0.01 and 100", getName()); + timeMultiple = timeMultiple < 0.01 ? 0.01 : 100; + } + + return true; +} + + +bool ParticleEmissionDummyData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + return true; +} + + +//-------------------------------------------------------------------------- +void ParticleEmissionDummyData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(timeMultiple); +} + +void ParticleEmissionDummyData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&timeMultiple); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ParticleEmissionDummy::ParticleEmissionDummy() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + mTypeMask |= EnvironmentObjectType; + + mEmitterDatablock = NULL; + mEmitterDatablockId = 0; + mEmitter = NULL; + mVelocity = 1.0; +} + +ParticleEmissionDummy::~ParticleEmissionDummy() +{ + // +} + +//-------------------------------------------------------------------------- +void ParticleEmissionDummy::initPersistFields() +{ + Parent::initPersistFields(); + + addField("emitter", TypeParticleEmitterDataPtr, Offset(mEmitterDatablock, ParticleEmissionDummy)); + addField("velocity", TypeF32, Offset(mVelocity, ParticleEmissionDummy)); +} + + +void ParticleEmissionDummy::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +bool ParticleEmissionDummy::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (!mEmitterDatablock && mEmitterDatablockId != 0) { + if (Sim::findObject(mEmitterDatablockId, mEmitterDatablock) == false) + Con::errorf(ConsoleLogEntry::General, "ParticleEmissionDummy::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", mDataBlock->getName()); + delete pEmitter; + return false; + } + mEmitter = pEmitter; + } + + mObjBox.min.set(-0.5, -0.5, -0.5); + mObjBox.max.set( 0.5, 0.5, 0.5); + resetWorldBox(); + addToScene(); + + return true; +} + + +void ParticleEmissionDummy::onRemove() +{ + removeFromScene(); + if (isClientObject()) { + mEmitter->deleteWhenEmpty(); + mEmitter = NULL; + } + + Parent::onRemove(); +} + + +bool ParticleEmissionDummy::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; +} + + +//-------------------------------------------------------------------------- +void ParticleEmissionDummy::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + Point3F emitPoint, emitVelocity; + Point3F emitAxis(0, 0, 1); + getTransform().mulV(emitAxis); + getTransform().getColumn(3, &emitPoint); + emitVelocity = emitAxis * mVelocity; + + mEmitter->emitParticles(emitPoint, emitPoint, + emitAxis, + emitVelocity, (dt * mDataBlock->timeMultiple * 1000.0f)); +} + + +//-------------------------------------------------------------------------- +U32 ParticleEmissionDummy::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + if (stream->writeFlag(mEmitterDatablock != NULL)) { + stream->writeRangedU32(mEmitterDatablock->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + return retMask; +} + +void ParticleEmissionDummy::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + 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); +} + diff --git a/game/particleEmitter.h b/game/particleEmitter.h new file mode 100644 index 0000000..9ae789f --- /dev/null +++ b/game/particleEmitter.h @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_PARTICLEEMISSIONDUMMY +#define _H_PARTICLEEMISSIONDUMMY + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif + +class ParticleEmitterData; +class ParticleEmitter; + +// ------------------------------------------------------------------------- +class ParticleEmissionDummyData : public GameBaseData +{ + typedef GameBaseData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + F32 timeMultiple; + + //-------------------------------------- load set variables + public: + + public: + ParticleEmissionDummyData(); + ~ParticleEmissionDummyData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(ParticleEmissionDummyData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class ParticleEmissionDummy : public GameBase +{ + typedef GameBase Parent; + + private: + ParticleEmissionDummyData* mDataBlock; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + ParticleEmitterData* mEmitterDatablock; + S32 mEmitterDatablockId; + + ParticleEmitter* mEmitter; + F32 mVelocity; + + public: + ParticleEmissionDummy(); + ~ParticleEmissionDummy(); + + // Time/Move Management + public: + void advanceTime(F32); + + DECLARE_CONOBJECT(ParticleEmissionDummy); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_PARTICLEEMISSIONDUMMY + diff --git a/game/particleEngine.cc b/game/particleEngine.cc new file mode 100644 index 0000000..19120f8 --- /dev/null +++ b/game/particleEngine.cc @@ -0,0 +1,1477 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "platformWIN32/platformGL.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sceneState.h" +#include "console/consoleTypes.h" +#include "core/bitStream.h" +#include "math/mRandom.h" +#include "game/particleEngine.h" +#include "dgl/gTexManager.h" + +//-------------------------------------------------------------------------- +//-------------------------------------- Internal global data +// +namespace { + +PEngine* sgParticleEngine = NULL; +MRandomLCG sgRandom(0x1); + +} // namespace {} + + + +//-------------------------------------------------------------------------- +//-------------------------------------- Internal classes +// +struct Particle; +class PEngine +{ + // Interface for emitters. + static const U32 csmBlockSize; + Vector mAllocatedBlocks; + Particle* mFreeList; + + public: + void updateParticles(Particle* particles, ParticleEmitter &emitter, const U32 ms); + void updateSingleParticle(Particle* particle, ParticleEmitter &emitter, const U32 ms); + + Particle* allocateParticle(ParticleEmitter*); + void releaseParticle(Particle*); + + public: + PEngine(); + ~PEngine(); +}; + +const U32 PEngine::csmBlockSize = 512; + +#define MaxParticleSize 50.0 +//-------------------------------------- +class ParticleData : public SimDataBlock +{ + typedef SimDataBlock Parent; + + enum PDConst + { + PDC_MAX_TEX = 50, + }; + + public: + 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[ParticleEngine::PC_COLOR_KEYS]; + F32 sizes[ParticleEngine::PC_SIZE_KEYS]; + F32 times[4]; + + StringTableEntry textureNameList[PDC_MAX_TEX]; + TextureHandle textureList[PDC_MAX_TEX]; + + public: + ParticleData(); + ~ParticleData(); + + void initializeParticle(Particle*, const Point3F&); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + DECLARE_CONOBJECT(ParticleData); + static void initPersistFields(); +}; + + +//-------------------------------------- +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 + + Particle* nextInList; // Managed by the current owning emitter + U32 currentAge; + + Particle* nextInEngine; // Managed by the global engine object + ParticleEmitter* currentOwner; + ColorF color; + F32 size; + F32 spinSpeed; +}; + + + +//-------------------------------------------------------------------------- +//-------------------------------------- Datablock implementation +IMPLEMENT_CO_DATABLOCK_V1(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; + + lifetimeMS = 0; + lifetimeVarianceMS = 0; + + overrideAdvance = false; + orientParticles = false; + orientOnVelocity = true; + useEmitterSizes = false; + useEmitterColors = false; + particleString = NULL; +} + + +IMPLEMENT_GETDATATYPE(ParticleEmitterData) +IMPLEMENT_SETDATATYPE(ParticleEmitterData) + +void ParticleEmitterData::initPersistFields() +{ + Parent::initPersistFields(); + + Con::registerType(TypeParticleEmitterDataPtr, sizeof(ParticleEmitterData*), + REF_GETDATATYPE(ParticleEmitterData), + REF_SETDATATYPE(ParticleEmitterData)); + + 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("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)); +} + +static ParticleEmitterData gDefaultEmitterData; + +void ParticleEmitterData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeInt(ejectionPeriodMS, 10); + stream->writeInt(periodVarianceMS, 10); + stream->writeInt(ejectionVelocity * 100, 16); + stream->writeInt(velocityVariance * 100, 14); + if(stream->writeFlag(ejectionOffset != gDefaultEmitterData.ejectionOffset)) + stream->writeInt(ejectionOffset * 100, 16); + stream->writeRangedU32(thetaMin, 0, 180); + stream->writeRangedU32(thetaMax, 0, 180); + if(stream->writeFlag(phiReferenceVel != gDefaultEmitterData.phiReferenceVel)) + stream->writeRangedU32(phiReferenceVel, 0, 360); + if(stream->writeFlag(phiVariance != gDefaultEmitterData.phiVariance)) + stream->writeRangedU32(phiVariance, 0, 360); + stream->writeFlag(overrideAdvance); + stream->writeFlag(orientParticles); + stream->writeFlag(orientOnVelocity); + stream->writeInt(lifetimeMS >> 5, 10); + stream->writeInt(lifetimeVarianceMS >> 5, 10); + stream->writeFlag(useEmitterSizes); + stream->writeFlag(useEmitterColors); + + stream->write(dataBlockIds.size()); + for (U32 i = 0; i < dataBlockIds.size(); i++) + stream->write(dataBlockIds[i]); +} + +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 = stream->readRangedU32(0, 180); + thetaMax = stream->readRangedU32(0, 180); + if(stream->readFlag()) + phiReferenceVel = stream->readRangedU32(0, 360); + else + phiReferenceVel = gDefaultEmitterData.phiReferenceVel; + + if(stream->readFlag()) + phiVariance = stream->readRangedU32(0, 360); + else + phiVariance = gDefaultEmitterData.phiVariance; + + overrideAdvance = stream->readFlag(); + orientParticles = stream->readFlag(); + orientOnVelocity = stream->readFlag(); + lifetimeMS = stream->readInt(10) << 5; + lifetimeVarianceMS = stream->readInt(10) << 5; + 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]); +} + +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 (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; + } + + + // Tokenize and load the particle datablocks... + // + if (particleString != NULL) { + 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; + } + 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()); + } + } + delete [] tokCopy; + if (particleDataBlocks.size() == 0) { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName()); + return false; + } + } + + return true; +} + +bool ParticleEmitterData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == 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); + } + + return true; +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +IMPLEMENT_CO_DATABLOCK_V1(ParticleData); + +ParticleData::ParticleData() +{ + dragCoefficient = 0.0f; + windCoefficient = 1.0f; + gravityCoefficient = 0.0f; + inheritedVelFactor = 0.0f; + constantAcceleration = 0.0f; + lifetimeMS = 1000; + lifetimeVarianceMS = 0; + spinSpeed = 0.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); + stream->writeSignedFloat(gravityCoefficient / 10, 12); + stream->writeFloat(inheritedVelFactor, 9); + if(stream->writeFlag(constantAcceleration != gDefaultParticleData.constantAcceleration)) + stream->write(constantAcceleration); + + stream->writeInt(lifetimeMS >> 5, 10); + stream->writeInt(lifetimeVarianceMS >> 5,10); + if(stream->writeFlag(spinSpeed != gDefaultParticleData.spinSpeed)) + stream->write(spinSpeed); + if(stream->writeFlag(spinRandomMin != gDefaultParticleData.spinRandomMin || spinRandomMax != gDefaultParticleData.spinRandomMax)) + { + stream->writeInt(spinRandomMin + 1000, 11); + stream->writeInt(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); + } + + for( count=0; countwriteInt(count, 6); + for(i = 0; i < count; i++) + stream->writeString( textureNameList[i] ); +} + +void ParticleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + dragCoefficient = stream->readFloat(10) * 5; + if(stream->readFlag()) + stream->read(&windCoefficient); + else + windCoefficient = gDefaultParticleData.windCoefficient; + gravityCoefficient = stream->readSignedFloat(12) * 10; + inheritedVelFactor = stream->readFloat(9); + if(stream->readFlag()) + stream->read(&constantAcceleration); + else + constantAcceleration = gDefaultParticleData.constantAcceleration; + + lifetimeMS = stream->readInt(10) << 5; + lifetimeVarianceMS = stream->readInt(10) << 5; + if(stream->readFlag()) + stream->read(&spinSpeed); + else + spinSpeed = gDefaultParticleData.spinSpeed; + + if(stream->readFlag()) + { + spinRandomMin = stream->readInt(11) - 1000; + spinRandomMax = 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); + } + count = stream->readInt(6); + for(i = 0; i < count;i ++) + textureNameList[i] = stream->readSTString(); +} + +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 (numFrames > PDC_MAX_TEX){ + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) numFrames invalid", getName()); + numFrames = PDC_MAX_TEX; + return false; + } + if (framesPerSec > 200){ + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) framesPerSec invalid", getName()); + framesPerSec = 20; + 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]; + } + } + + return true; +} + +bool ParticleData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if(!server) + { + numFrames = 0; + for( int i=0; idataBlock = 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(sgRandom.randI() % (2 * lifetimeVarianceMS + 1)) - S32(lifetimeVarianceMS); + + // assign spin amount + init->spinSpeed = spinSpeed + sgRandom.randF( spinRandomMin, spinRandomMax ); +} + + +//---------------------------------------------------------------------------- +//-------------------------------------- Emitter implementation +// +ParticleEmitter::ParticleEmitter() +{ + mDeleteWhenEmpty = false; + mDeleteOnTick = false; + + mParticleListHead = NULL; + + mInternalClock = 0; + mNextParticleTime = 0; + + mLastPosition.set(0, 0, 0); + mHasLastPosition = false; + + mLifetimeMS = 0; + mElapsedTimeMS = 0; +} + +ParticleEmitter::~ParticleEmitter() +{ + AssertFatal(mParticleListHead == NULL, "Error, particles remain in emitter after remove?"); +} + +//-------------------------------------------------------------------------- +bool ParticleEmitter::onAdd() +{ + if(!Parent::onAdd()) + return false; + + removeFromProcessList(); + + mLifetimeMS = mDataBlock->lifetimeMS; + if( mDataBlock->lifetimeVarianceMS ) + { + mLifetimeMS += S32( sgRandom.randI() % (2 * mDataBlock->lifetimeVarianceMS + 1)) - S32(mDataBlock->lifetimeVarianceMS ); + } + + return true; +} + + +//-------------------------------------------------------------------------- +void ParticleEmitter::onRemove() +{ + Particle* pProbe = mParticleListHead; + while (pProbe != NULL) { + Particle* pRemove = pProbe; + pProbe = pProbe->nextInList; + + pRemove->nextInList = NULL; + sgParticleEngine->releaseParticle(pRemove); + } + mParticleListHead = NULL; + + if (mSceneManager != NULL) { + gClientContainer.removeObject(this); + gClientSceneGraph->removeObjectFromScene(this); + } + + Parent::onRemove(); +} + + +bool ParticleEmitter::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +bool ParticleEmitter::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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + + state->insertRenderImage(image); + } + + return false; +} + + +struct SortParticle +{ + Particle* p; + F32 k; +}; + +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::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + + MatrixF modelview; + dglGetModelview(&modelview); + Point3F x; + Point3F y; + modelview.getRow(0, &x); + modelview.getRow(2, &y); + + Point3F viewvec; + modelview.getRow(1, &viewvec); + + MatrixF camView; + modelview.transposeTo( (F32*) &camView ); + + // DMMFIX: slow! + // + Vector orderedVector(__FILE__, __LINE__); + Particle* pProbe = mParticleListHead; + while (pProbe) { + orderedVector.increment(); + orderedVector.last().p = pProbe; + orderedVector.last().k = mDot(pProbe->pos, viewvec); + pProbe = pProbe->nextInList; + } +// if (orderedVector.size() != 0) +// dQsort(orderedVector.address(), orderedVector.size(), sizeof(SortParticle), cmpSortParticles); + + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glDepthMask(GL_FALSE); + + + if( orderedVector.size() > 0 && orderedVector[0].p->dataBlock->useInvAlpha ) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + + 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); + + F32 spinFactor = (1.0/1000.0) * (1.0/360.0) * M_PI * 2.0; + + + for (U32 i = 0; i < orderedVector.size(); i++) { + Particle* particle = orderedVector[i].p; + + if( particle->dataBlock->animateTexture ) + { + U32 texNum = particle->currentAge * (1.0/1000.0) * particle->dataBlock->framesPerSec; + texNum %= particle->dataBlock->numFrames; + glBindTexture(GL_TEXTURE_2D, particle->dataBlock->textureList[texNum].getGLName()); + } + else + { + glBindTexture(GL_TEXTURE_2D, particle->dataBlock->textureList[0].getGLName()); + } + + if( mDataBlock->orientParticles ) + { + renderOrientedParticle( *particle, state->getCameraPosition() ); + } + else + { + renderBillboardParticle( *particle, basePoints, camView, spinFactor ); + } + + } + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +inline void ParticleEmitter::renderBillboardParticle( Particle &part, Point3F *basePnts, + MatrixF &camView, F32 spinFactor ) +{ + + glBegin(GL_QUADS); + + glColor4f(part.color.red, + part.color.green, + part.color.blue, + part.color.alpha); + + + F32 width = part.size * 0.5; + F32 spinAngle = part.spinSpeed * part.currentAge * spinFactor; + + F32 sy, cy; + mSinCos(spinAngle, sy, cy); + Point3F points[4]; + + for( int i=0; i<4; i++ ) + { + points[i].x = cy * basePnts[i].x - sy * basePnts[i].z; + points[i].y = basePnts[i].y; + points[i].z = sy * basePnts[i].x + cy * basePnts[i].z; + camView.mulP( points[i] ); + points[i] *= width; + points[i] += part.pos; + } + + glTexCoord2f(0, 1); + glVertex3fv(points[0]); + glTexCoord2f(1, 1); + glVertex3fv(points[1]); + glTexCoord2f(1, 0); + glVertex3fv(points[2]); + glTexCoord2f(0, 0); + glVertex3fv(points[3]); + + glEnd(); +} + +//-------------------------------------------------------------------------- +inline void ParticleEmitter::renderOrientedParticle( Particle &part, const Point3F &camPos ) +{ + 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(); + + glBegin(GL_QUADS); + + glColor4f(part.color.red, + part.color.green, + part.color.blue, + part.color.alpha); + + + F32 width = part.size * 0.5; + + dir *= width; + crossDir *= width; + Point3F start = part.pos - dir; + Point3F end = part.pos + dir; + + + glTexCoord2f(0, 0); + glVertex3fv( start + crossDir ); + + glTexCoord2f(0, 1); + glVertex3fv( start - crossDir ); + + glTexCoord2f(1, 1); + glVertex3fv( end - crossDir ); + + glTexCoord2f(1, 0); + glVertex3fv( end + crossDir ); + + glEnd(); +} + + +//-------------------------------------------------------------------------- +void ParticleEmitter::stealParticle(Particle* steal) +{ + Particle** ppParticle = &mParticleListHead; + while (*ppParticle) { + if (*ppParticle == steal) { + *ppParticle = (*ppParticle)->nextInList; + steal->nextInList = NULL; + return; + } + + ppParticle = &((*ppParticle)->nextInList); + } + AssertFatal(false, "Trying to steal a particle that doesn't belong to this emitter!"); +} + +//-------------------------------------------------------------------------- +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); +} + +void ParticleEmitter::emitParticles(const Point3F& start, + const Point3F& end, + const Point3F& axis, + const Point3F& velocity, + const U32 numMilliseconds) +{ + // 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; + + U32 advanceMS = numMilliseconds - currTime; + if (advanceMS > mParticleListHead->totalLifetime) { + // Well, shoot, why did we create this in the first place? + Particle* old = mParticleListHead; + mParticleListHead = old->nextInList; + old->nextInList = NULL; + sgParticleEngine->releaseParticle(old); + } else { + if (advanceMS != 0) + sgParticleEngine->updateSingleParticle(mParticleListHead, *this, advanceMS); + } + mNextParticleTime = 0; + } + } + + while (currTime < numMilliseconds) { + S32 nextTime = mDataBlock->ejectionPeriodMS; + if (mDataBlock->periodVarianceMS != 0) { + nextTime += S32(sgRandom.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; + + // 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) { + if (advanceMS > mParticleListHead->totalLifetime) { + // Well, shoot, why did we create this in the first place? + Particle* old = mParticleListHead; + mParticleListHead = old->nextInList; + old->nextInList = NULL; + sgParticleEngine->releaseParticle(old); + } else { + if (advanceMS != 0) + sgParticleEngine->updateSingleParticle(mParticleListHead, *this, advanceMS); + } + } + } + + // DMMFIX: Lame and slow... + if (particlesAdded == true) + updateBBox(); + if (mParticleListHead != NULL && mSceneManager == NULL) { + gClientSceneGraph->addObjectToScene(this); + gClientContainer.addObject(this); + gClientProcessList.addObject(this); + } + + mLastPosition = end; + mHasLastPosition = true; +} + +void ParticleEmitter::updateBBox() +{ + Point3F min(1e10, 1e10, 1e10); + Point3F max(-1e10, -1e10, -1e10); + + Particle* pProbe = mParticleListHead; + while (pProbe != NULL) { + min.setMin(pProbe->pos); + max.setMax(pProbe->pos); + + pProbe = pProbe->nextInList; + } + + mObjBox = Box3F(min, max); + MatrixF temp = getTransform(); + setTransform(temp); +} + + +//-------------------------------------------------------------------------- + +void ParticleEmitter::emitParticles(const Point3F& rCenter, + const Point3F& rNormal, + const F32 radius, + const Point3F& velocity, + S32 count) +{ + // 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 * sgRandom.randF()))); + pos += axisy * (radius * (1 - (2 * sgRandom.randF()))); + pos += axisz * (radius * sgRandom.randF()); + + Point3F axis = pos; + axis.normalize(); + pos += rCenter; + + addParticle(pos, axis, velocity, axisz); + } + + // Set world bounding box + mObjBox.min = rCenter - Point3F(radius, radius, radius); + mObjBox.max = rCenter + Point3F(radius, radius, radius); + resetWorldBox(); + + // Make sure we're part of the world + if (mParticleListHead != NULL && mSceneManager == NULL) { + gClientSceneGraph->addObjectToScene(this); + gClientContainer.addObject(this); + gClientProcessList.addObject(this); + } + mHasLastPosition = false; +} + + +//-------------------------------------------------------------------------- +void ParticleEmitter::addParticle(const Point3F& pos, + const Point3F& axis, + const Point3F& vel, + const Point3F& axisx) +{ + Particle* pNew = sgParticleEngine->allocateParticle(this); + pNew->nextInList = mParticleListHead; + mParticleListHead = pNew; + + Point3F ejectionAxis = axis; + F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * sgRandom.randF() + + mDataBlock->thetaMin; + + F32 ref = (F32(mInternalClock) / 1000.0) * mDataBlock->phiReferenceVel; + F32 phi = ref + sgRandom.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 * sgRandom.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; + + // Select a datablock for this particle + U32 dBlockIndex = (U32)(mCeil(sgRandom.randF() * F32(mDataBlock->particleDataBlocks.size())) - 1); + mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel); +} + + +//-------------------------------------------------------------------------- +void ParticleEmitter::processTick(const Move*) +{ + if (mDeleteOnTick == true) + deleteObject(); +} + + +void ParticleEmitter::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + mElapsedTimeMS += dt * 1000.0f; + + U32 numMSToUpdate = (U32)(dt * 1000.0f); + if (numMSToUpdate == 0) + return; + + Particle** ppProbe = &mParticleListHead; + while (*ppProbe != NULL) { + (*ppProbe)->currentAge += numMSToUpdate; + if ((*ppProbe)->currentAge >= (*ppProbe)->totalLifetime) { + // Remove this particle + Particle* remove = *ppProbe; + *ppProbe = remove->nextInList; + remove->nextInList = NULL; + sgParticleEngine->releaseParticle(remove); + } else { + ppProbe = &((*ppProbe)->nextInList); + } + } + + if (mParticleListHead == NULL && mDeleteWhenEmpty) { + mDeleteOnTick = true; + } else { + if (numMSToUpdate != 0 && mParticleListHead) + sgParticleEngine->updateParticles(mParticleListHead, *this, numMSToUpdate); + } +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +namespace ParticleEngine { + +Point3F windVelocity(0.f, 0.f, 0.f); + +void init() +{ + AssertFatal(sgParticleEngine == NULL, "ParticleEngine::init: engine already initialized"); + + sgParticleEngine = new PEngine; +} + +void destroy() +{ + AssertFatal(sgParticleEngine != NULL, "ParticleEngine::destroy: engine not initialized"); + + delete sgParticleEngine; + sgParticleEngine = NULL; +} + +} // namespace ParticleEngine + +//-------------------------------------------------------------------------- +PEngine::PEngine() +{ + mFreeList = NULL; +} + + +PEngine::~PEngine() +{ + mFreeList = NULL; + for (U32 i = 0; i < mAllocatedBlocks.size(); i++) + { + delete [] mAllocatedBlocks[i]; + mAllocatedBlocks[i] = NULL; + } +} + +//-------------------------------------------------------------------------- +Particle* PEngine::allocateParticle(ParticleEmitter* emitter) +{ + if (mFreeList == NULL) + { + // Add a new block to the free list... + mAllocatedBlocks.push_back(new Particle[csmBlockSize]); + Particle* pArray = mAllocatedBlocks.last(); + for (U32 i = 0; i < csmBlockSize - 1; i++) + pArray[i].nextInEngine = &pArray[i + 1]; + pArray[csmBlockSize - 1].nextInEngine = NULL; + mFreeList = &pArray[0]; + } + AssertFatal(mFreeList != NULL, "Error, must have a free list here!"); + + Particle* pParticle = mFreeList; + mFreeList = pParticle->nextInEngine; + + dMemset(pParticle, 0, sizeof(Particle)); + pParticle->nextInEngine = NULL; + pParticle->currentOwner = emitter; + + return pParticle; +} + +void PEngine::releaseParticle(Particle* release) +{ + release->nextInEngine = mFreeList; + mFreeList = release; +} + + +//-------------------------------------------------------------------------- +void PEngine::updateParticles(Particle* particles, ParticleEmitter &emitter, const U32 ms) +{ + AssertFatal(particles != NULL, "PEngine::updateParticles: Error, must have particles to process in this function"); + AssertFatal(ms != 0, "PEngine::updateParticles: error, no time to update?"); + + Particle* pProbe = particles; + while (pProbe != NULL) { + updateSingleParticle(pProbe, emitter, ms); + pProbe = pProbe->nextInList; + } +} + +void PEngine::updateSingleParticle(Particle* particle, ParticleEmitter &emitter, const U32 ms) +{ + AssertFatal(particle != NULL, "PEngine::updateSingleParticle: Error, must have a particle to process in this function"); + AssertFatal(ms != 0, "PEngine::updateSingleParticle: error, no time to update?"); + + F32 t = F32(ms) / 1000.0; + + Point3F a = particle->acc; + a -= particle->vel * particle->dataBlock->dragCoefficient; + a -= ParticleEngine::windVelocity * particle->dataBlock->windCoefficient; + a += Point3F(0, 0, -9.81) * particle->dataBlock->gravityCoefficient; + + particle->vel += a * t; + particle->pos += particle->vel * t; + + // Now update the particle's color + t = F32(particle->currentAge) / F32(particle->totalLifetime); + AssertFatal(t <= 1.0f, "Out out bounds filter function for particle."); + + for (U32 i = 1; i < 4; i++) { + if (particle->dataBlock->times[i] >= t) { + F32 firstPart = t - particle->dataBlock->times[i-1]; + F32 total = particle->dataBlock->times[i] - + particle->dataBlock->times[i-1]; + + firstPart /= total; + + if( emitter.getDataBlock()->useEmitterColors ) + { + particle->color.interpolate(emitter.colors[i-1], emitter.colors[i], firstPart); + } + else + { + particle->color.interpolate(particle->dataBlock->colors[i-1], + particle->dataBlock->colors[i], + firstPart); + } + + if( emitter.getDataBlock()->useEmitterSizes ) + { + particle->size = (emitter.sizes[i-1] * (1.0 - firstPart)) + + (emitter.sizes[i] * firstPart); + } + else + { + particle->size = (particle->dataBlock->sizes[i-1] * (1.0 - firstPart)) + + (particle->dataBlock->sizes[i] * firstPart); + } + + return; + } + } + particle->color = particle->dataBlock->colors[ParticleEngine::PC_COLOR_KEYS - 1]; + particle->size = particle->dataBlock->sizes[ParticleEngine::PC_COLOR_KEYS - 1]; +} + + diff --git a/game/particleEngine.h b/game/particleEngine.h new file mode 100644 index 0000000..4b6ab34 --- /dev/null +++ b/game/particleEngine.h @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_PARTICLEEMITTER +#define _H_PARTICLEEMITTER + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +//-------------------------------------- Engine initialization... +// +namespace ParticleEngine { + + enum ParticleConsts + { + PC_COLOR_KEYS = 4, + PC_SIZE_KEYS = 4, + }; + + void init(); + void destroy(); + + extern Point3F windVelocity; + inline void setWindVelocity(const Point3F & vel) { windVelocity = vel; } + inline Point3F getWindVelocity() { return windVelocity; } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- The data and the Emitter class +// are all that the game should deal +// with (other than initializing the +// global engine pointer of course) +// +struct Particle; +class ParticleData; + +//-------------------------------------- +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, char errorBuffer[256]); + + bool onAdd(); + + public: + S32 ejectionPeriodMS; + S32 periodVarianceMS; + + F32 ejectionVelocity; + F32 velocityVariance; + F32 ejectionOffset; + + F32 thetaMin; + F32 thetaMax; + + F32 phiReferenceVel; + F32 phiVariance; + + U32 lifetimeMS; + U32 lifetimeVarianceMS; + + bool overrideAdvance; + bool orientParticles; + bool orientOnVelocity; + bool useEmitterSizes; + bool useEmitterColors; + + StringTableEntry particleString; + Vector particleDataBlocks; + Vector dataBlockIds; +}; + + +//-------------------------------------- +class ParticleEmitter : public GameBase +{ + typedef GameBase Parent; + friend class PEngine; + + public: + ParticleEmitter(); + ~ParticleEmitter(); + + void setSizes( F32 *sizeList ); + 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(); + + // 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); + + // Internal interface + protected: + void addParticle(const Point3F&, const Point3F&, const Point3F&, const Point3F&); + void renderBillboardParticle( Particle &part, Point3F *basePnts, MatrixF &camView, F32 spinFactor ); + void renderOrientedParticle( Particle &part, const Point3F &camPos ); + void updateBBox(); + + protected: + bool onAdd(); + void onRemove(); + + void processTick(const Move*); + void advanceTime(F32); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + // PEngine interface + private: + void stealParticle(Particle*); + + private: + ParticleEmitterData* mDataBlock; + + Particle* mParticleListHead; + + U32 mInternalClock; + + U32 mNextParticleTime; + + Point3F mLastPosition; + bool mHasLastPosition; + + bool mDeleteWhenEmpty; + bool mDeleteOnTick; + + S32 mLifetimeMS; + S32 mElapsedTimeMS; + + F32 sizes[ParticleEngine::PC_SIZE_KEYS]; + ColorF colors[ParticleEngine::PC_COLOR_KEYS]; +}; + +#endif // _H_PARTICLEEMITTER + diff --git a/game/physicalZone.cc b/game/physicalZone.cc new file mode 100644 index 0000000..19e06e2 --- /dev/null +++ b/game/physicalZone.cc @@ -0,0 +1,345 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/physicalZone.h" +#include "Core/bitStream.h" +#include "Collision/boxConvex.h" +#include "Collision/clippedPolyList.h" +#include "console/consoleTypes.h" +#include "Math/mathIO.h" +#include "dgl/dgl.h" +#include "sceneGraph/sceneState.h" + +IMPLEMENT_CO_NETOBJECT_V1(PhysicalZone); + +namespace { + +static void cActivate(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-forcefield get in here?"); + PhysicalZone* pZone = static_cast(obj); + + if (pZone->isClientObject()) + return; + + pZone->activate(); +} + +static void cDeactivate(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-forcefield get in here?"); + PhysicalZone* pZone = static_cast(obj); + + if (pZone->isClientObject()) + return; + + pZone->deactivate(); +} + +} // namespace {} + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +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::initPersistFields() +{ + Parent::initPersistFields(); + + addField("velocityMod", TypeF32, Offset(mVelocityMod, PhysicalZone)); + addField("gravityMod", TypeF32, Offset(mGravityMod, PhysicalZone)); + addField("appliedForce", TypePoint3F, Offset(mAppliedForce, PhysicalZone)); + addField("polyhedron", TypeTriggerPolyhedron, Offset(mPolyhedron, PhysicalZone)); +} + + +void PhysicalZone::consoleInit() +{ + Con::addCommand("PhysicalZone", "activate", cActivate, "obj.activate()", 2, 2); + Con::addCommand("PhysicalZone", "deactivate", cDeactivate, "obj.deactivate()", 2, 2); +} + + +//-------------------------------------------------------------------------- +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::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); +} + + +//-------------------------------------------------------------------------- +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.min.set(1e10, 1e10, 1e10); + mObjBox.max.set(-1e10, -1e10, -1e10); + for (U32 i = 0; i < mPolyhedron.pointList.size(); i++) { + mObjBox.min.setMin(mPolyhedron.pointList[i]); + mObjBox.max.setMax(mPolyhedron.pointList[i]); + } + } else { + mObjBox.min.set(-0.5, -0.5, -0.5); + mObjBox.max.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.min.convolveInverse(mObjScale); + realBox.max.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) +{ + if (mPolyhedron.pointList.size() == 0) + return false; + + mClippedList.clear(); + + SphereF sphere; + sphere.center = (mWorldBox.min + mWorldBox.max) * 0.5; + VectorF bv = mWorldBox.max - sphere.center; + sphere.radius = bv.len(); + + enter->buildPolyList(&mClippedList, mWorldBox, sphere); + return mClippedList.isEmpty() == false; +} + + +//-------------------------------------- +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/game/physicalZone.h b/game/physicalZone.h new file mode 100644 index 0000000..2cbb28c --- /dev/null +++ b/game/physicalZone.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_PHYSICALZONE +#define _H_PHYSICALZONE + +#ifndef _SCENEOBJECT_H_ +#include "Sim/sceneObject.h" +#endif +#ifndef _EARLYOUTPOLYLIST_H_ +#include "Collision/earlyOutPolyList.h" +#endif + + +class Convex; + +// ------------------------------------------------------------------------- +class PhysicalZone : public SceneObject +{ + typedef SceneObject Parent; + + protected: + bool onAdd(); + void onRemove(); + + enum UpdateMasks { + InitialUpdateMask = 1 << 0, + ActiveMask = 1 << 1 + }; + + public: + void setTransform(const MatrixF&); + + protected: + 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(); + + F32 getVelocityMod() const { return mVelocityMod; } + F32 getGravityMod() const { return mGravityMod; } + const Point3F& getForce() const { return mAppliedForce; } + + void setPolyhedron(const Polyhedron&); + bool testObject(SceneObject*); + + void activate(); + void deactivate(); + bool isActive() const { return mActive; } + + DECLARE_CONOBJECT(PhysicalZone); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_PHYSICALZONE + diff --git a/game/platTest.cc b/game/platTest.cc new file mode 100644 index 0000000..c6f979d --- /dev/null +++ b/game/platTest.cc @@ -0,0 +1,710 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformVideo.h" +#include "platform/platformInput.h" +#include "platform/platformAudio.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "core/tVector.h" +#include "math/mMath.h" +#include "dgl/dgl.h" +#include "dgl/gBitmap.h" +#include "core/resManager.h" +#include "core/fileStream.h" +#include "dgl/gTexManager.h" +#include "dgl/gFont.h" +#include "console/console.h" +#include "console/simBase.h" +#include "gui/guiCanvas.h" +#include "sim/actionMap.h" +#include "core/dnet.h" +#include "game/game.h" +#include "core/bitStream.h" +#include "console/telnetConsole.h" +#include "console/telnetDebugger.h" +#include "console/consoleTypes.h" +#include "math/mathTypes.h" +#include "dgl/gTexManager.h" +#include "core/resManager.h" +#include "interior/interiorRes.h" +#include "interior/interiorInstance.h" +#include "ts/tsShapeInstance.h" +#include "terrain/terrData.h" +#include "terrain/terrRender.h" +#include "editor/terraformer.h" +#include "scenegraph/sceneGraph.h" +#include "dgl/materialList.h" +#include "scenegraph/sceneRoot.h" +#include "game/moveManager.h" +#include "platform/platformVideo.h" +#include "dgl/materialPropertyMap.h" +#include "sim/netStringTable.h" +#include "sim/pathManager.h" +#include "game/GameFunctions.h" +#include "game/particleEngine.h" +#include "game/targetManager.h" +#include "platform/platformRedBook.h" +#include "game/tribesGame.h" +#include "game/netDispatch.h" +#include "sim/decalManager.h" +#include "sim/frameAllocator.h" +#include "scenegraph/detailManager.h" +#include "interior/interiorLMManager.h" +#include "game/version.h" +#include "game/badWordFilter.h" +#include "platform/profiler.h" +#include "game/shapeBase.h" +//#define IHVBUILD +#ifdef IHVBUILD +#include "ihvSignature.h" +#include "crypt/cryptSHA1.h" +#endif + +#ifndef BUILD_TOOLS +TribesGame GameObject; +#endif + +extern ResourceInstance *constructTerrainFile(Stream &stream); +extern ResourceInstance *constructTSShape(Stream &); + +IMPLEMENT_CONOBJECT(Terraformer); + +static void cLockMouse(SimObject *, S32, const char **argv) +{ + Platform::setWindowLocked(dAtob(argv[1])); +} + +static void cSetNetPort(SimObject *, S32, const char **argv) +{ + Net::openPort(dAtoi(argv[1])); +} + +static bool cCreateCanvas(SimObject *, S32, const char **) +{ + AssertISV(!Canvas, "cCreateCanvas: canvas has already been instantiated"); + +#if !defined(DEBUG) && !defined(INTERNAL_RELEASE) + if(!Platform::excludeOtherInstances("Tribes2Game")) + return false; +#endif + Platform::initWindow(Point2I(800, 600), "Tribes 2"); + + // create the canvas, and add it to the manager + Canvas = new GuiCanvas(); + Canvas->registerObject("Canvas"); // automatically adds to GuiGroup + return true; +} + +static void cSaveJournal(SimObject *, S32, const char **argv) +{ + Game->saveJournal(argv[1]); +} + +static void cLoadJournal(SimObject *, S32, const char **argv) +{ + Game->loadJournal(argv[1]); +} + +static void cPlayJournal(SimObject *, S32, const char **argv) +{ + Game->playJournal(argv[1]); +} + +extern void AIInit(); +extern void netInit(); +extern void clientNetProcess(); +extern void serverNetProcess(); +extern void processConnectedReceiveEvent( ConnectedReceiveEvent * event ); +extern void processConnectedNotifyEvent( ConnectedNotifyEvent * event ); +extern void processConnectedAcceptEvent( ConnectedAcceptEvent * event ); +extern void ShowInit(); + +static bool initLibraries() +{ + if(!Net::init()) + { + Platform::AlertOK("Network Error", "Unable to initialize the network... aborting."); + return false; + } + + // asserts should be created FIRST + PlatformAssert::create(); + + FrameAllocator::init(3 << 20); // 3 meg frame allocator buffer + +// // Cryptographic pool next +// CryptRandomPool::init(); + + _StringTable::create(); + TextureManager::create(); + ResManager::create(); + + // Register known file types here + ResourceManager->registerExtension(".jpg", constructBitmapJPEG); + ResourceManager->registerExtension(".png", constructBitmapPNG); + ResourceManager->registerExtension(".gif", constructBitmapGIF); + ResourceManager->registerExtension(".dbm", constructBitmapDBM); + ResourceManager->registerExtension(".bmp", constructBitmapBMP); + ResourceManager->registerExtension(".bm8", constructBitmapBM8); + ResourceManager->registerExtension(".gft", constructFont); + ResourceManager->registerExtension(".dif", constructInteriorDIF); + ResourceManager->registerExtension(".ter", constructTerrainFile); + ResourceManager->registerExtension(".dts", constructTSShape); + ResourceManager->registerExtension(".dml", constructMaterialList); + + RegisterCoreTypes(); + RegisterMathTypes(); + RegisterGuiTypes(); + + Con::init(); + NetStringTable::create(); + BadWordFilter::create(); + + RegisterMathFunctions(); + RegisterGameFunctions(); + TelnetConsole::create(); + TelnetDebugger::create(); + + Processor::init(); + Math::init(); + Platform::init(); // platform specific initialization + Audio::init(); + MathConsoleInit(); + InteriorLMManager::init(); + InteriorInstance::init(); + TSShapeInstance::init(); + RedBook::init(); + + return true; +} + + +// shut down +static void shutdownLibraries() +{ + // Purge any resources on the timeout list... + if (ResourceManager) + ResourceManager->purge(); + + RedBook::destroy(); + TSShapeInstance::destroy(); + InteriorInstance::destroy(); + InteriorLMManager::destroy(); + + Audio::destroy(); + TextureManager::preDestroy(); + + Platform::shutdown(); + TelnetDebugger::destroy(); + TelnetConsole::destroy(); + + BadWordFilter::destroy(); + NetStringTable::destroy(); + Con::shutdown(); + + ResManager::destroy(); + TextureManager::destroy(); + + _StringTable::destroy(); + +// CryptRandomPool::destroy(); + + // asserts should be destroyed LAST + FrameAllocator::destroy(); + + PlatformAssert::destroy(); + Net::shutdown(); +} + +const char *defaultPaths[] = { // searched left to right +"base" +}; + +static void cRebuildModPaths(SimObject *, S32, const char **) +{ + ResourceManager->setModPaths(sizeof(defaultPaths)/sizeof(char*), defaultPaths); +} + +static void cSetModPaths(SimObject*, S32, const char** argv) +{ + char* buf = new char[dStrlen( argv[1] ) + 6]; + dStrcpy( buf, argv[1] ); + dStrcat( buf, ";base" ); + Vector paths; + char* temp = dStrtok( buf, ";" ); + while ( temp ) + { + if ( temp[0] ) + { + char* path = (char*) dMalloc( dStrlen( temp ) + 1 ); + dStrcpy( path, temp ); + paths.push_back( path ); + } + temp = dStrtok( NULL, ";" ); + } + + ResourceManager->setModPaths( paths.size(), (const char**) paths.address() ); + delete [] buf; +} + +static const char* cGetModPaths(SimObject*, S32, const char**) +{ + return( ResourceManager->getModPaths() ); +} + +static S32 cGetVersionNumber(SimObject *, S32, const char **) +{ + return getVersionNumber(); +} + +static S32 cGetSimTime(SimObject *, S32, const char **) +{ + return Sim::getCurrentTime(); +} + +static S32 cGetRealTime( SimObject*, S32, const char** ) +{ + return Platform::getRealMilliseconds(); +} + +static F32 gTimeScale = 1.0; +static U32 gTimeAdvance = 0; +static U32 gFrameSkip = 0; +static U32 gFrameCount = 0; + + +bool initGame() +{ + Con::addCommand("getVersionNumber", cGetVersionNumber, "getVersionNumber()", 1, 1); + + Con::addCommand("getSimTime", cGetSimTime, "getSimTime();", 1, 1); + Con::addCommand("getRealTime", cGetRealTime, "getRealTime()'", 1, 1); + Con::addCommand("setNetPort", cSetNetPort, "setNetPort(port);", 2, 2); + Con::addCommand("lockMouse", cLockMouse, "lockMouse(isLocked);", 2, 2); + Con::addCommand("rebuildModPaths", cRebuildModPaths, "rebuildModPaths();", 1, 1); + Con::addCommand("setModPaths", cSetModPaths, "setModPaths( paths )", 2, 2 ); + Con::addCommand("getModPaths", cGetModPaths, "getModPaths()", 1, 1 ); + Con::addCommand("createCanvas", cCreateCanvas, "createCanvas();", 1, 1); + + Con::addCommand("saveJournal", cSaveJournal, "saveJournal(jname);", 2, 2); + Con::addCommand("loadJournal", cLoadJournal, "loadJournal(jname);", 2, 2); +// Con::addCommand("playJournal", cPlayJournal, "playJournal(jname);", 2, 2); + + Con::setFloatVariable("Video::texResidentPercentage", -1.0f); + Con::setIntVariable("Video::textureCacheMisses", -1); + Con::addVariable("timeScale", TypeF32, &gTimeScale); + Con::addVariable("timeAdvance", TypeS32, &gTimeAdvance); + Con::addVariable("frameSkip", TypeS32, &gFrameSkip); + +#ifdef GATHER_METRICS + Con::addVariable("Video::numTexelsLoaded", TypeS32, &TextureManager::smTextureSpaceLoaded); +#else + static U32 sBogusNTL = 0; + Con::addVariable("Video::numTexelsLoaded", TypeS32, &sBogusNTL); +#endif + + ResourceManager->setModPaths(sizeof(defaultPaths)/sizeof(char*), defaultPaths); + TerrainRender::init(); + + netInit(); + dispatchInit(); + GameInit(); + ShowInit(); + AIInit(); + MoveManager::init(); + + Sim::init(); + + ActionMap* globalMap = new ActionMap; + globalMap->registerObject("GlobalActionMap"); + Sim::getActiveActionMapSet()->pushObject(globalMap); + + MaterialPropertyMap *map = new MaterialPropertyMap; + + map->registerObject("MaterialPropertyMap"); + Sim::getRootGroup()->addObject(map); + + gClientSceneGraph = new SceneGraph(true); + gClientSceneRoot = new SceneRoot; + gClientSceneGraph->addObjectToScene(gClientSceneRoot); + gServerSceneGraph = new SceneGraph(false); + gServerSceneRoot = new SceneRoot; + gServerSceneGraph->addObjectToScene(gServerSceneRoot); + gDecalManager = new DecalManager; + gClientContainer.addObject(gDecalManager); + gClientSceneGraph->addObjectToScene(gDecalManager); + + DetailManager::init(); + PathManager::init(); + ParticleEngine::init(); + TargetManager::create(); + + //execute the console.cs script + FileStream str; + if(!str.open("main.cs", FileStream::Read)) + return false; + + U32 size = str.getStreamSize(); + char *script = new char[size + 1]; + str.read(size, script); + str.close(); + + script[size] = 0; + Con::executef(2, "eval", script); + delete[] script; + return true; +} + +void shutdownGame() +{ + //exec the script onExit() function + Con::executef(1, "onExit"); + + ParticleEngine::destroy(); + PathManager::destroy(); + DetailManager::shutdown(); + + // 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(); + + gClientSceneGraph->removeObjectFromScene(gDecalManager); + gClientContainer.removeObject(gDecalManager); + gClientSceneGraph->removeObjectFromScene(gClientSceneRoot); + gServerSceneGraph->removeObjectFromScene(gServerSceneRoot); + delete gClientSceneRoot; + delete gServerSceneRoot; + delete gClientSceneGraph; + delete gServerSceneGraph; + delete gDecalManager; + gClientSceneRoot = NULL; + gServerSceneRoot = NULL; + gClientSceneGraph = NULL; + gServerSceneGraph = NULL; + gDecalManager = NULL; + + TargetManager::destroy(); + TerrainRender::shutdown(); +} + +extern bool gDGLRender; +bool gShuttingDown = false; + +S32 TribesGame::main(S32 argc, const char **argv) +{ +// if (argc == 1) { +// static const char* argvFake[] = { "dtest.exe", "-jload", "test.jrn" }; +// argc = 3; +// argv = argvFake; +// } + +// Memory::enableLogging("testMem.log"); +// Memory::setBreakAlloc(104717); + + if(!initLibraries()) + return 0; + +#ifdef IHVBUILD + char* pVer = new char[sgVerStringLen + 1]; + U32 hi; + for (hi = 0; hi < sgVerStringLen; hi++) + pVer[hi] = sgVerString[hi] ^ 0xFF; + pVer[hi] = '\0'; + + SHA1Context hashCTX; + hashCTX.init(); + hashCTX.hashBytes(pVer, sgVerStringLen); + hashCTX.finalize(); + + U8 hash[20]; + hashCTX.getHash(hash); + + for (hi = 0; hi < 20; hi++) + if (U8(hash[hi]) != U8(sgHashVer[hi])) + return 0; +#endif + + // 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]); + if (initGame() == false) + return 0; + +#ifdef IHVBUILD + char* pPrint = new char[dStrlen(sgVerPrintString) + 1]; + for (U32 pi = 0; pi < dStrlen(sgVerPrintString); pi++) + pPrint[pi] = sgVerPrintString[pi] ^ 0xff; + pPrint[dStrlen(sgVerPrintString)] = '\0'; + + Con::printf(""); + Con::errorf(ConsoleLogEntry::General, pPrint, pVer); + delete [] pVer; +#endif + + +// extern void QGL_EnableLogging(bool); +// QGL_EnableLogging(true); + + while(Game->isRunning()) + { + PROFILE_START(MainLoop); + Game->journalProcess(); + //if(Game->getJournalMode() != GameInterface::JournalLoad) + //{ + Net::process(); // read in all events + Platform::process(); // keys, etc. + TelConsole->process(); + TelDebugger->process(); + TimeManager::process(); // guaranteed to produce an event + //} + PROFILE_END(); + } + shutdownGame(); + shutdownLibraries(); + + gShuttingDown = true; +// QGL_EnableLogging(false); + +#if 0 +// tg: Argh! This should OS version check should be part of Platform, not here... +// + // check os + OSVERSIONINFO osInfo; + dMemset(&osInfo, 0, sizeof(OSVERSIONINFO)); + osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + // see if osversioninfoex fails + if(!GetVersionEx((OSVERSIONINFO*)&osInfo)) + { + osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if(!GetVersionEx((OSVERSIONINFO*)&osInfo)) + return 0; + } + + // terminate the process if win95 only! + if((osInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && // 95, 98, ME + (osInfo.dwMajorVersion == 4) && // 95, 98, ME, NT + (osInfo.dwMinorVersion == 0)) // 95 + { + AssertWarn(0, "Forcing termination of app (Win95)! Upgrade your OS now!"); + TerminateProcess(GetCurrentProcess(), 0xffffffff); + } +#endif + + return 0; +} + + +static bool serverTick = false; + + +static F32 fpsRealStart; +static F32 fpsRealLast; +//static F32 fpsRealTotal; +static F32 fpsReal; +static F32 fpsVirtualStart; +static F32 fpsVirtualLast; +//static F32 fpsVirtualTotal; +static F32 fpsVirtual; +static F32 fpsFrames; +static F32 fpsNext; +static bool fpsInit = false; +const F32 UPDATE_INTERVAL = 0.25f; + +//-------------------------------------- +void fpsReset() +{ + fpsRealStart = (F32)Platform::getRealMilliseconds()/1000.0f; // Real-World Tick Count + fpsVirtualStart = (F32)Platform::getVirtualMilliseconds()/1000.0f; // Engine Tick Count (does not vary between frames) + fpsNext = fpsRealStart + UPDATE_INTERVAL; + +// fpsRealTotal= 0.0f; + fpsRealLast = 0.0f; + fpsReal = 0.0f; +// fpsVirtualTotal = 0.0f; + fpsVirtualLast = 0.0f; + fpsVirtual = 0.0f; + fpsFrames = 0; + fpsInit = true; +} + +//-------------------------------------- +void fpsUpdate() +{ + if (!fpsInit) + fpsReset(); + + const float alpha = 0.07f; + F32 realSeconds = (F32)Platform::getRealMilliseconds()/1000.0f; + F32 virtualSeconds = (F32)Platform::getVirtualMilliseconds()/1000.0f; + + fpsFrames++; + if (fpsFrames > 1) + { + fpsReal = fpsReal*(1.0-alpha) + (realSeconds-fpsRealLast)*alpha; + fpsVirtual = fpsVirtual*(1.0-alpha) + (virtualSeconds-fpsVirtualLast)*alpha; + } +// fpsRealTotal = fpsFrames/(realSeconds - fpsRealStart); +// fpsVirtualTotal = fpsFrames/(virtualSeconds - fpsVirtualStart); + + fpsRealLast = realSeconds; + fpsVirtualLast = virtualSeconds; + + // update variables every few frames + F32 update = fpsRealLast - fpsNext; + if (update > 0.5f) + { +// Con::setVariable("fps::realTotal", avar("%4.1f", fpsRealTotal)); +// Con::setVariable("fps::virtualTotal", avar("%4.1f", fpsVirtualTotal)); + Con::setVariable("fps::real", avar("%4.1f", 1.0f/fpsReal)); + Con::setVariable("fps::virtual", avar("%4.1f", 1.0f/fpsVirtual)); + if (update > UPDATE_INTERVAL) + fpsNext = fpsRealLast + UPDATE_INTERVAL; + else + fpsNext += UPDATE_INTERVAL; + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- NOTE: Entropy distilling occurs in this +// function. Talk to dmoore for details. +// + + +void TribesGame::processMouseMoveEvent(MouseMoveEvent * mEvent) +{ +// CryptRandomPool::submitEntropy(mEvent->xPos, 2); // Take the least significant 2 bits of the mouse pos +// CryptRandomPool::submitEntropy(mEvent->yPos, 2); + if (Canvas) + Canvas->processMouseMoveEvent(mEvent); +} + +void TribesGame::processInputEvent(InputEvent *event) +{ + PROFILE_START(ProcessInputEvent); + if (!ActionMap::handleEventGlobal(event)) + { + // Other input consumers here... + if (!(Canvas && Canvas->processInputEvent(event))) + ActionMap::handleEvent(event); + } + PROFILE_END(); +} + +void TribesGame::processQuitEvent() +{ + setRunning(false); +} + +void TribesGame::refreshWindow() +{ + if(Canvas) + Canvas->resetUpdateRegions(); +} + +void TribesGame::processConsoleEvent(ConsoleEvent *event) +{ + char *argv[2]; + argv[0] = "eval"; + argv[1] = event->data; + Sim::postCurrentEvent(Sim::getRootGroup(), new SimConsoleEvent(2, const_cast(argv), false)); +} + +void TribesGame::processTimeEvent(TimeEvent *event) +{ + PROFILE_START(ProcessTimeEvent); + U32 elapsedTime = event->elapsedTime; + // 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(gTimeAdvance) + timeDelta = gTimeAdvance; + else + timeDelta = elapsedTime * gTimeScale; + + Platform::advanceTime(elapsedTime); + + PROFILE_START(ServerProcess); + serverProcess(timeDelta); + PROFILE_END(); + PROFILE_START(ServerNetProcess); + serverNetProcess(); + PROFILE_END(); + + PROFILE_START(SimAdvanceTime); + Sim::advanceTime(timeDelta); + PROFILE_END(); + + PROFILE_START(ClientProcess); + clientProcess(timeDelta); + PROFILE_END(); + PROFILE_START(ClientNetProcess); + clientNetProcess(); + PROFILE_END(); + + if(Canvas && gDGLRender) + { + bool preRenderOnly = false; + if(gFrameSkip && gFrameCount % gFrameSkip) + preRenderOnly = true; + + PROFILE_START(RenderFrame); + ShapeBase::sLastRenderFrame++; + Canvas->renderFrame(preRenderOnly); + PROFILE_END(); + gFrameCount++; + } + dispatchCheckTimeouts(); + fpsUpdate(); + PROFILE_END(); +} + +void GameReactivate() +{ + if ( !Input::isEnabled() ) + Input::enable(); + + if ( !Input::isActive() ) + Input::reactivate(); + + gDGLRender = true; + if ( Canvas ) + Canvas->resetUpdateRegions(); +} + +void GameDeactivate( bool noRender ) +{ + if ( Input::isActive() ) + Input::deactivate(); + + if ( Input::isEnabled() ) + Input::disable(); + + if ( noRender ) + gDGLRender = false; +} + +void TribesGame::textureKill() +{ + TextureManager::makeZombie(); +} + +void TribesGame::textureResurrect() +{ + TextureManager::resurrect(); +} + diff --git a/game/player.cc b/game/player.cc new file mode 100644 index 0000000..b86c321 --- /dev/null +++ b/game/player.cc @@ -0,0 +1,4383 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "game/game.h" +#include "math/mMath.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/stringTable.h" +#include "game/moveManager.h" +#include "core/bitStream.h" +#include "core/dnet.h" +#include "collision/extrudedPolyList.h" +#include "collision/clippedPolyList.h" +#include "ts/tsShapeInstance.h" +#include "core/resManager.h" +#include "audio/audio.h" +#include "game/player.h" +#include "game/particleEngine.h" +#include "game/gameConnection.h" +#include "terrain/terrData.h" +#include "terrain/terrRender.h" +#include "terrain/waterBlock.h" +#include "game/trigger.h" +#include "game/physicalZone.h" +#include "game/item.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sceneState.h" +#include "sim/decalManager.h" +#include "game/forceFieldBare.h" +#include "game/shadow.h" +#include "ai/graphLOS.h" +#include "game/missionArea.h" +#include "game/splash.h" +#include "scenegraph/detailManager.h" +#include "game/cameraFXMgr.h" +#include "collision/earlyOutPolyList.h" +#include "dgl/materialPropertyMap.h" +#include "platform/profiler.h" + +// S32 bingy = 0; +// S32 boingy = 1; + +//---------------------------------------------------------------------------- + +// Amount we try to stay out of walls by... +static F32 sWeaponPushBack = 0.03; + +// Amount of time if takes to transition to a new action sequence. +static F32 sAnimationTransitionTime = 0.25; +static bool sUseAnimationTransitions = true; +static F32 sLandReverseScale = 0.25; +static F32 sStandingJumpSpeed = 2.0; +static F32 sJumpingThreshold = 4.0; +static F32 sSlowStandThreshSquared = 1.69; +static S32 sRenderMyPlayer = true; +static S32 sRenderMyItems = true; + +// Chooses new action animations every n ticks. +static const F32 sNewAnimationTickTime = 4; +static const F32 sMountPendingTickWait = (13 * 32); + +// Number of ticks before we pick non-contact animations +static const S32 sContactTickTime = 30; + +// Downward velocity at which we consider the player falling +static const F32 sFallingThreshold = -10; + +// Movement constants +static F32 sVerticalStepDot = 0.173; // 80 +static F32 sMinFaceDistance = 0.01; +static F32 sTractionDistance = 0.03; +static F32 sNormalElasticity = 0.01; +static U32 sMoveRetryCount = 5; + +// Client prediction +static F32 sMaxLatencyTicks = 0; // Max latency prediction +static F32 sMinWarpTicks = 0.5; // Fraction of tick at which instant warp occures +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; + +// +static U32 sCollisionMoveMask = (TerrainObjectType | InteriorObjectType | + WaterObjectType | PlayerObjectType | + StaticShapeObjectType | VehicleObjectType | + ForceFieldObjectType | + PhysicalZoneObjectType | StaticTSObjectType); + +static U32 sServerCollisionContactMask = (sCollisionMoveMask | + (ItemObjectType | + TriggerObjectType | + CorpseObjectType)); + +static U32 sClientCollisionContactMask = sCollisionMoveMask | PhysicalZoneObjectType; +static U32 sDirtySetMask = (ForceFieldObjectType | + PlayerObjectType | VehicleObjectType | + TurretObjectType | + StationObjectType | SensorObjectType); + +enum { + JumpSkipContactsMax = 8 +}; + +//---------------------------------------------------------------------------- +// Player shape animation sequences: + +// look Used to contol 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 useing the enum values defined in player.h + // The first five are selected in the move state based on velocity + { "root" }, // RootAnim, + { "run" }, // RunForwardAnim, + { "back" }, // BackBackwardAnim + { "side" }, // SideLeftAnim, + + // These are set explicitly based on player actions + { "fall" }, // FallAnim + { "jump" }, // JumpAnim + { "land" }, // LandAnim +}; + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(PlayerData); + +PlayerData::PlayerData() +{ + pickupRadius = 0; + minLookAngle = -1.4; + maxLookAngle = 1.4; + maxFreelookAngle = 3.0; + maxTimeScale = 1.5; + + mass = 9; + maxEnergy = 60; + + runForce = 40 * 9; + runEnergyDrain = 0; + minRunEnergy = 0; + maxForwardSpeed = 10; + maxBackwardSpeed = 10; + maxSideSpeed = 10; + maxUnderwaterForwardSpeed = 10; + maxUnderwaterBackwardSpeed = 10; + maxUnderwaterSideSpeed = 10; + + maxStepHeight = 1; + runSurfaceAngle = 80; + + recoverDelay = 30; + recoverRunForceScale = 1; + + jumpForce = 75; + jumpEnergyDrain = 0; + minJumpEnergy = 0; + jumpSurfaceAngle = 78; + jumpDelay = 30; + minJumpSpeed = 500; + maxJumpSpeed = 2 * minJumpSpeed; + + horizMaxSpeed = 80; + horizResistSpeed = 38; + horizResistFactor = 1; + + upMaxSpeed = 80; + upResistSpeed = 38; + upResistFactor = 1; + + minImpactSpeed = 25; + + decalData = NULL; + decalID = 0; + decalOffset = 0.0f; + + lookAction = 0; + + // size of bounding box + boxSize.set(1,1,2.3); + + // location of head, torso, legs + boxHeadPercentage = 0.85; + boxTorsoPercentage = 0.55; + + // damage locations + boxHeadLeftPercentage = 0; + boxHeadRightPercentage = 1; + boxHeadBackPercentage = 0; + boxHeadFrontPercentage = 1; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = 0; + + footPuffEmitter = NULL; + footPuffID = 0; + footPuffNumParts = 15; + footPuffRadius = .25; + + dustEmitter = NULL; + dustID = 0; + + splash = NULL; + splashId = 0; + splashVelocity = 1.0; + splashAngle = 45.0; + splashFreqMod = 300.0; + splashVelEpsilon = 0.25; + bubbleEmitTime = 0.4; + + medSplashSoundVel = 2.0; + hardSplashSoundVel = 3.0; + exitSplashSoundVel = 2.0; + footSplashHeight = 0.1; + + dMemset( splashEmitterList, 0, sizeof( splashEmitterList ) ); + dMemset( splashEmitterIDList, 0, sizeof( splashEmitterIDList ) ); + + genericShadowLevel = Player_GenericShadowLevel; + noShadowLevel = Player_NoShadowLevel; + + groundImpactMinSpeed = 10.0; + groundImpactShakeFreq.set( 10.0, 10.0, 10.0 ); + groundImpactShakeAmp.set( 20.0, 20.0, 20.0 ); + groundImpactShakeDuration = 1.0; + groundImpactShakeFalloff = 10.0; +} + +bool PlayerData::preload(bool server, char errorBuffer[256]) +{ + if(!Parent::preload(server, errorBuffer)) + 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; + + // 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); + } + + // Go ahead a pre-load the player shape + TSShapeInstance* si = new TSShapeInstance(shape, 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]; + U32 i; + for ( i = 0; i < NumTableActionAnims; i++,dp++) { + ActionAnimationDef *sp = &ActionAnimationList[i]; + dp->name = sp->name; + dp->death = false; + dp->sequence = shape->findSequence(sp->name); + if (dp->sequence != -1) + getGroundInfo(si,thread,dp); + } + for (int b = 0; b < shape->sequences.size(); b++) + { + if (!isTableSequence(b)) { + dp->sequence = b; + dp->name = shape->getName(shape->sequences[b].nameIndex); + getGroundInfo(si,thread,dp++); + } + } + actionCount = dp - actionList; + AssertFatal(actionCount <= NumActionAnims, "Too many action animations!"); + delete si; + + // Resolve lookAction index + dp = &actionList[0]; + const char *lookName = StringTable->insert("look"); + const char *skiName = StringTable->insert("ski"); + const char *standJumpName = StringTable->insert("standjump"); + for (int c = 0; c < actionCount; c++,dp++) + { + if (dp->name == lookName) + lookAction = c; + else if (dp->name == skiName) + skiAction = c; + else if (dp->name == standJumpName) + standJumpAction = c; + } + + // Resolve spine + spineNode[0] = shape->findNode("Bip01 Pelvis"); + spineNode[1] = shape->findNode("Bip01 Spine"); + spineNode[2] = shape->findNode("Bip01 Spine1"); + spineNode[3] = shape->findNode("Bip01 Spine2"); + spineNode[4] = shape->findNode("Bip01 Neck"); + spineNode[5] = shape->findNode("Bip01 Head"); + + // Recoil animations + recoilSequence[0] = shape->findSequence("light_recoil"); + recoilSequence[1] = shape->findSequence("medium_recoil"); + recoilSequence[2] = shape->findSequence("heavy_recoil"); + + // Lookup shadow node (shadow center moves in synch with this node) + shadowNode = spineNode[0]; + + // Convert pickupRadius to a delta of boundingBox + F32 dr = (boxSize.x > boxSize.y)? boxSize.x: boxSize.y; + if (pickupRadius < dr) + pickupRadius = dr; + else + if (pickupRadius > 2 * dr) + pickupRadius = 2 * dr; + pickupDelta = (S32)(pickupRadius - dr); + + // + if (maxJumpSpeed <= minJumpSpeed) + maxJumpSpeed = minJumpSpeed + 0.1; + + + 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( 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; + dp->dir.set(0,0,0); + } + else { + 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.01) + dp->speed = 0; + else + dp->dir *= 1 / 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 == standJumpAction); +} + +void PlayerData::consoleInit() +{ + +} + +void PlayerData::initPersistFields() +{ + Parent::initPersistFields(); + 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("maxUnderwaterForwardSpeed", TypeF32, Offset(maxUnderwaterForwardSpeed, PlayerData)); + addField("maxUnderwaterBackwardSpeed", TypeF32, Offset(maxUnderwaterBackwardSpeed, PlayerData)); + addField("maxUnderwaterSideSpeed", TypeF32, Offset(maxUnderwaterSideSpeed, PlayerData)); + + addField("runSurfaceAngle", TypeF32, Offset(runSurfaceAngle, 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)); + + addField("minImpactSpeed", TypeF32, Offset(minImpactSpeed, PlayerData)); + + addField("boundingBox", TypePoint3F, Offset(boxSize, 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("LFootSoftSound", TypeAudioProfilePtr, Offset(sound[LFootSoft], PlayerData)); + addField("RFootSoftSound", TypeAudioProfilePtr, Offset(sound[RFootSoft], PlayerData)); + addField("LFootHardSound", TypeAudioProfilePtr, Offset(sound[LFootHard], PlayerData)); + addField("RFootHardSound", TypeAudioProfilePtr, Offset(sound[RFootHard], PlayerData)); + addField("LFootMetalSound", TypeAudioProfilePtr, Offset(sound[LFootMetal], PlayerData)); + addField("RFootMetalSound", TypeAudioProfilePtr, Offset(sound[RFootMetal], PlayerData)); + addField("LFootSnowSound", TypeAudioProfilePtr, Offset(sound[LFootSnow], PlayerData)); + addField("RFootSnowSound", TypeAudioProfilePtr, Offset(sound[RFootSnow], PlayerData)); + addField("LFootShallowSound", TypeAudioProfilePtr, Offset(sound[LFootShallowSplash], PlayerData)); + addField("RFootShallowSound", TypeAudioProfilePtr, Offset(sound[RFootShallowSplash], PlayerData)); + addField("LFootWadingSound", TypeAudioProfilePtr, Offset(sound[LFootWading], PlayerData)); + addField("RFootWadingSound", TypeAudioProfilePtr, Offset(sound[RFootWading], PlayerData)); + addField("LFootUnderwaterSound", TypeAudioProfilePtr, Offset(sound[LFootUnderWater], PlayerData)); + addField("RFootUnderwaterSound", TypeAudioProfilePtr, Offset(sound[RFootUnderWater], PlayerData)); + addField("LFootBubblesSound", TypeAudioProfilePtr, Offset(sound[LFootBubbles], PlayerData)); + addField("RFootBubblesSound", TypeAudioProfilePtr, Offset(sound[RFootBubbles], PlayerData)); + addField("movingBubblesSound", TypeAudioProfilePtr, Offset(sound[MoveBubbles], PlayerData)); + addField("waterBreathSound", TypeAudioProfilePtr, Offset(sound[WaterBreath], PlayerData)); + + addField("impactSoftSound", TypeAudioProfilePtr, Offset(sound[ImpactSoft], PlayerData)); + addField("impactHardSound", TypeAudioProfilePtr, Offset(sound[ImpactHard], PlayerData)); + addField("impactMetalSound", TypeAudioProfilePtr, Offset(sound[ImpactMetal], PlayerData)); + addField("impactSnowSound", TypeAudioProfilePtr, Offset(sound[ImpactSnow], PlayerData)); + + addField("impactWaterEasy", TypeAudioProfilePtr, Offset(sound[ImpactWaterEasy], PlayerData)); + addField("impactWaterMedium", TypeAudioProfilePtr, Offset(sound[ImpactWaterMedium], PlayerData)); + addField("impactWaterHard", TypeAudioProfilePtr, Offset(sound[ImpactWaterHard], PlayerData)); + addField("exitingWater", TypeAudioProfilePtr, 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("mediumSplashSoundVelocity", TypeF32, Offset(medSplashSoundVel, PlayerData)); + addField("hardSplashSoundVelocity", TypeF32, Offset(hardSplashSoundVel, PlayerData)); + addField("exitSplashSoundVelocity", TypeF32, Offset(exitSplashSoundVel, PlayerData)); + 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)); +} + +void PlayerData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(minLookAngle); + stream->write(maxLookAngle); + stream->write(maxFreelookAngle); + stream->write(maxTimeScale); + + stream->write(maxStepHeight); + + stream->write(runForce); + stream->write(runEnergyDrain); + stream->write(minRunEnergy); + stream->write(maxForwardSpeed); + stream->write(maxBackwardSpeed); + stream->write(maxSideSpeed); + stream->write(maxUnderwaterForwardSpeed); + stream->write(maxUnderwaterBackwardSpeed); + stream->write(maxUnderwaterSideSpeed); + stream->write(runSurfaceAngle); + + stream->write(recoverDelay); + stream->write(recoverRunForceScale); + + stream->write(jumpForce); + stream->write(jumpEnergyDrain); + stream->write(minJumpEnergy); + stream->write(minJumpSpeed); + stream->write(maxJumpSpeed); + stream->write(jumpSurfaceAngle); + stream->writeInt(jumpDelay,JumpDelayBits); + + 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); + + stream->write(boxSize.x); + stream->write(boxSize.y); + stream->write(boxSize.z); + + 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); +} + +void PlayerData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&minLookAngle); + stream->read(&maxLookAngle); + stream->read(&maxFreelookAngle); + stream->read(&maxTimeScale); + + stream->read(&maxStepHeight); + + stream->read(&runForce); + stream->read(&runEnergyDrain); + stream->read(&minRunEnergy); + stream->read(&maxForwardSpeed); + stream->read(&maxBackwardSpeed); + stream->read(&maxSideSpeed); + stream->read(&maxUnderwaterForwardSpeed); + stream->read(&maxUnderwaterBackwardSpeed); + stream->read(&maxUnderwaterSideSpeed); + stream->read(&runSurfaceAngle); + + stream->read(&recoverDelay); + stream->read(&recoverRunForceScale); + + stream->read(&jumpForce); + stream->read(&jumpEnergyDrain); + stream->read(&minJumpEnergy); + stream->read(&minJumpSpeed); + stream->read(&maxJumpSpeed); + stream->read(&jumpSurfaceAngle); + jumpDelay = stream->readInt(JumpDelayBits); + + 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] = (AudioProfile*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + stream->read(&boxSize.x); + stream->read(&boxSize.y); + stream->read(&boxSize.z); + + 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); +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +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 = delta.warpOffset = Point3F(0,0,0); + delta.posVec = delta.rotVec = delta.headVec = VectorF(0,0,0); + delta.warpTicks = 0; + delta.dt = 1; + delta.move = NullMove; + mPredictionCount = sMaxPredictionTicks; + mObjToWorld.setColumn(3,delta.pos); + mRot = delta.rot; + mHead = delta.head; + mVelocity.set(0,0,0); + 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 = ActionAnimation::Scale; + mActionAnimation.waitForEnd = false; + mActionAnimation.holdAtEnd = false; + mActionAnimation.animateOnServer = false; + mActionAnimation.atEnd = false; + mState = MoveState; + mFalling = false; + mContactTimer = 0; + mJumpDelay = 0; + mJumpSurfaceLastContact = 0; + mJumpSurfaceNormal.set(0,0,1); + mControlObject = 0; + mGenerateShadow = true; + dMemset( mSplashEmitter, 0, sizeof( mSplashEmitter ) ); + + mImpactSound = 0; + // mClientImpactSound = 0; + mRecoverTicks = 0; + mReversePending = 0; + + mLastPos.set( 0.0, 0.0, 0.0 ); + + mMoveBubbleHandle = 0; + mWaterBreathHandle = 0; + inLiquid = false; + + mConvex.init(this); + mWorkingQueryBox.min.set(-1e9, -1e9, -1e9); + mWorkingQueryBox.max.set(-1e9, -1e9, -1e9); + + mWeaponBackFraction = 0.0; + + mDisableMove = false; + mInMissionArea = true; + + mBubbleEmitterTime = 10.0; + mLastWaterPos.set( 0.0, 0.0, 0.0 ); + + mPilot = false; + mMountPending = 0; +} + +Player::~Player() +{ +} + + +//---------------------------------------------------------------------------- +bool Player::onAdd() +{ + ActionAnimation serverAnim = mActionAnimation; + if(!Parent::onAdd() || !mDataBlock) + return false; + + mWorkingQueryBox.min.set(-1e9, -1e9, -1e9); + mWorkingQueryBox.max.set(-1e9, -1e9, -1e9); + + addToScene(); + setHeat(0.0); + + // Make sure any state and animation passed from the server + // in the initial update is set correctly. + ActionState state = mState; + mState = NullState; + setState(state); + 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: 0); + if (inDeathAnim()) + mDeath.lastPos = 1.0; + } + + // 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 = sMountPendingTickWait; + else + mMountPending = 0; + } + + // + if (isServerObject()) + { + scriptOnAdd(); + } + else + { + U32 i; + for( i=0; ionNewDataBlock( mDataBlock->splashEmitterList[i] ); + if( !mSplashEmitter[i]->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() ); + delete mSplashEmitter[i]; + mSplashEmitter[i] = NULL; + } + } + mLastWaterPos = getPosition(); + + // clear out all camera effects + gCamFXMgr.clear(); + } + + return true; +} + +void Player::onRemove() +{ + setControlObject(0); + scriptOnRemove(); + removeFromScene(); + + U32 i; + for( i=0; ideleteWhenEmpty(); + mSplashEmitter[i] = NULL; + } + } + + mWorkingQueryBox.min.set(-1e9, -1e9, -1e9); + mWorkingQueryBox.max.set(-1e9, -1e9, -1e9); + + Parent::onRemove(); +} + +//---------------------------------------------------------------------------- + +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()); + + mObjBox.max.x = mDataBlock->boxSize.x * 0.5; + mObjBox.max.y = mDataBlock->boxSize.y * 0.5; + mObjBox.max.z = mDataBlock->boxSize.z; + mObjBox.min.x = -mObjBox.max.x; + mObjBox.min.y = -mObjBox.max.y; + mObjBox.min.z = 0; + + // 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; + + 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::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 * 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.1) + 0.1; // from Convex::updateWorkingList + convexBox.min -= Point3F(l, l, l); + convexBox.max += Point3F(l, l, l); + + // Check containment + { + if (mWorkingQueryBox.min.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.min -= Point3F(2 * l, 2 * l, 2 * l); + mWorkingQueryBox.max += Point3F(2 * l, 2 * l, 2 * l); + + disableCollision(); + mConvex.updateWorkingList(mWorkingQueryBox, + isGhost() ? sClientCollisionContactMask : sServerCollisionContactMask); + enableCollision(); + } +} + + +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.min += pos; + wBox.max += pos; + + EarlyOutPolyList polyList; + polyList.mNormal.set(0,0,0); + polyList.mPlaneList.clear(); + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(wBox.min,VectorF(-1,0,0)); + polyList.mPlaneList[1].set(wBox.max,VectorF(0,1,0)); + polyList.mPlaneList[2].set(wBox.max,VectorF(1,0,0)); + polyList.mPlaneList[3].set(wBox.min,VectorF(0,-1,0)); + polyList.mPlaneList[4].set(wBox.min,VectorF(0,0,-1)); + polyList.mPlaneList[5].set(wBox.max,VectorF(0,0,1)); + + if (getContainer()->buildPolyList(wBox, sCollisionMoveMask, &polyList)) + { + enableCollision(); + getObjectMount()->enableCollision(); + return false; + } + + enableCollision(); + getObjectMount()->enableCollision(); + return true; +} + + +void Player::processTick(const Move* move) +{ + PROFILE_START(Player_ProcessTick); + + // Manage the control object and filter moves for the player + Move pMove,cMove; + ShapeBase * conObj = this; + 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; + } + mControlObject->processTick((mDamageState == Enabled)? &cMove: &NullMove); + move = &pMove; + conObj = mControlObject; + } + } + + 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) + { + PROFILE_END(); + return; + } + move = &delta.move; + } + else + move = &NullMove; + } + if (!isGhost()) + updateAnimation(TickSec); + + PROFILE_START(Player_PhysicsSection); + if(isServerObject() || (didRenderLastRender() || getControllingClient())) + { + 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); + } + } + if(isClientObject()) { + if(conObj->mWaterCoverage >= 1.0f && mDamageState == Enabled) + { + if(mVelocity.len() > 1.0f) + { + if(!mMoveBubbleHandle) + mMoveBubbleHandle = alxPlay(mDataBlock->sound[PlayerData::MoveBubbles], &getTransform()); + alxSourceMatrixF(mMoveBubbleHandle, &getTransform()); + } + else if(mMoveBubbleHandle) + { + alxStop(mMoveBubbleHandle); + mMoveBubbleHandle = 0; + } + + if(!mWaterBreathHandle) + mWaterBreathHandle = alxPlay(mDataBlock->sound[PlayerData::WaterBreath], &getTransform()); + alxSourceMatrixF(mWaterBreathHandle, &getTransform()); + } + else + { + if(mMoveBubbleHandle) + { + alxStop(mMoveBubbleHandle); + mMoveBubbleHandle = 0; + } + if(mWaterBreathHandle) + { + alxStop(mWaterBreathHandle); + mWaterBreathHandle = 0; + } + } + // if(mImpactSound > mClientImpactSound) + if(mImpactSound) + playImpactSound(); + } + else { + //Checks to see if the player is in the Mission Area... + Point3F pos; + MissionArea * obj = dynamic_cast(Sim::findObject("MissionArea")); + 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,3,"onLeaveMissionArea",scriptThis()); + } + } + else if(!mInMissionArea) + { + mInMissionArea = true; + Con::executef(mDataBlock,3,"onEnterMissionArea",scriptThis()); + } + } + + PROFILE_END(); +} + +void Player::interpolateTick(F32 dt) +{ + if (mControlObject) + mControlObject->interpolateTick(dt); + + // Client side interpolation + Parent::interpolateTick(dt); + if(dt != 0.0f) + { + Point3F pos = delta.pos + delta.posVec * dt; + Point3F rot = delta.rot + delta.rotVec * dt; + + mHead = delta.head + delta.headVec * dt; + setRenderPosition(pos,rot,dt); + + // apply camera effects - is this the best place? - bramage + GameConnection* connection = GameConnection::getServerConnection(); + if( connection->isFirstPerson() ) + { + ShapeBase *obj = connection->getControlObject(); + if( obj == this ) + { + MatrixF curTrans = getRenderTransform(); + curTrans.mul( gCamFXMgr.getTrans() ); + Parent::setRenderTransform( curTrans ); + } + } + + } + else + { + mHead = delta.head; + setRenderPosition(delta.pos, delta.rot, 0); + } + updateLookAnimation(); + delta.dt = dt; +} + +void Player::advanceTime(F32 dt) +{ + // Client side animations + Parent::advanceTime(dt); + updateActionThread(); + updateAnimation(dt); + updateSplash(); + updateFroth(dt); + + mLastPos = getPosition(); + + // 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(); + } + + gCamFXMgr.update( dt ); + } + +} + + +//---------------------------------------------------------------------------- + +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; + } + } + } + + mState = state; + } +} + +void Player::updateState() +{ + switch (mState) { + case RecoverState: + if (mRecoverTicks-- == 0) { + if (mReversePending) { // 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; + } +} + +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) +{ + Point3F newPoint; + mWorldToObj.mulP(in_rPos, &newPoint); + + F32 zHeight = mDataBlock->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 = mDataBlock->boxSize.x; + F32 leftToRight = mDataBlock->boxSize.y; + + F32 backPoint = backToFront * (mDataBlock->boxHeadBackPercentage - 0.5); + F32 frontPoint = backToFront * (mDataBlock->boxHeadFrontPercentage - 0.5); + F32 leftPoint = leftToRight * (mDataBlock->boxHeadLeftPercentage - 0.5); + F32 rightPoint = leftToRight * (mDataBlock->boxHeadRightPercentage - 0.5); + + 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::updateMove(const Move* move) +{ + delta.move = *move; + + // Image Triggers + if (mDamageState == Enabled) { + setImageTriggerState(0,move->trigger[0]); + setImageTriggerState(1,move->trigger[1]); + } + + // Update current orientation + if (mDamageState == Enabled) { + F32 prevZRot = mRot.z; + delta.headVec = mHead; + + F32 p = move->pitch; + if (p > M_PI) p -= M_2PI; + mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle, + mDataBlock->maxLookAngle); + + F32 y = move->yaw; + if (y > M_PI) y -= M_2PI; + + 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; + mHead.z *= 0.5; + if (mControlObject) + mHead.x *= 0.5; + } + + // constrain the range of mRot.z + while (mRot.z < 0) + mRot.z += M_2PI; + while (mRot.z > M_2PI) + mRot.z -= M_2PI; + + delta.rot = mRot; + delta.rotVec.x = delta.rotVec.y = 0; + delta.rotVec.z = prevZRot - mRot.z; + if (delta.rotVec.z > M_PI) + delta.rotVec.z -= M_2PI; + else if (delta.rotVec.z < -M_PI) + delta.rotVec.z += M_2PI; + + delta.head = mHead; + delta.headVec -= mHead; + } + MatrixF zRot; + zRot.set(EulerF(0, 0, mRot.z)); + + // Desired move direction & speed + VectorF moveVec; + F32 moveSpeed; + if (mState == MoveState && mDamageState == Enabled && !mDisableMove) + { + zRot.getColumn(0,&moveVec); + moveVec *= move->x; + VectorF tv; + zRot.getColumn(1,&tv); + moveVec += tv * move->y; + + if (move->y > 0) + { + if( mWaterCoverage >= 0.9 ) + { + moveSpeed = getMax(mDataBlock->maxUnderwaterForwardSpeed * move->y, + mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x)); + } + else + { + moveSpeed = getMax(mDataBlock->maxForwardSpeed * move->y, + mDataBlock->maxSideSpeed * mFabs(move->x)); + } + } + else + { + if( mWaterCoverage >= 0.9 ) + { + moveSpeed = getMax(mDataBlock->maxUnderwaterBackwardSpeed * mFabs(move->y), + mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x)); + } + else + { + 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 && + (mActionAnimation.action >= PlayerData::NumTableActionAnims + || mActionAnimation.action == PlayerData::LandAnim)) + mActionAnimation.action = PlayerData::NullAnimation; + } + else + { + moveVec.set(0,0,0); + moveSpeed = 0; + } + + // Acceleration due to gravity + VectorF acc(0,0,mGravity * mGravityMod * TickSec); + + // Determin ground contact normal. Only look for contacts if + // we can move. + VectorF contactNormal; + bool jumpSurface = false, runSurface = false; + if (!isMounted() && !mDisableMove) + findContact(&runSurface,&jumpSurface,&contactNormal); + if(jumpSurface) + mJumpSurfaceNormal = 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) { + VectorF dv = contactNormal * (vd + 0.002); + acc += dv; + if (acc.len() < 0.0001) + acc.set(0,0,0); + } + + // Force a 0 move if there is no energy + VectorF pv; + if (mEnergy >= mDataBlock->minRunEnergy) { + mEnergy -= mDataBlock->runEnergyDrain; + pv = moveVec; + } + else + pv.set(0,0,0); + + // Adjust the players's requested dir. to be parallel + // to the contact surface. + F32 pvl = pv.len(); + if (pvl) { + VectorF nn; + mCross(pv,VectorF(0,0,1),&nn); + nn *= 1 / 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 acceleratin, player also accelerates faster when + // in his hard landing recover state. + F32 maxAcc = (mDataBlock->runForce / mMass) * 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 + 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; + + // If we are facing into the surface jump up, otherwise + // jump away from surface. + F32 dot = mDot(pv,mJumpSurfaceNormal); + F32 impulse = mDataBlock->jumpForce / mMass; + 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; + } + + mJumpDelay = mDataBlock->jumpDelay; + mEnergy -= mDataBlock->jumpEnergyDrain; + + F32 speed = mVelocity.len(); + bool doAction = true; + U32 jumpAction; + + if (speed > sStandingJumpSpeed) + jumpAction = PlayerData::JumpAnim; + else + jumpAction = mDataBlock->standJumpAction; + + if (doAction) + setActionThread(jumpAction, true, false, true); + mJumpSurfaceLastContact = JumpSkipContactsMax; + } + } + else + if (jumpSurface) { + if (mJumpDelay > 0) + mJumpDelay--; + mJumpSurfaceLastContact = 0; + } + else + mJumpSurfaceLastContact++; + + + // Add in force from physical zones... + acc += (mAppliedForce / mMass) * 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) + mVelocity.z -= mBuoyancy * mGravity * mGravityMod * TickSec; + } + mVelocity -= mVelocity * mDrag * TickSec; + + if(mDisableMove) + { + mVelocity.x = 0.0f; + mVelocity.y = 0.0f; + } + + // 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; + } + + if (!isGhost()) { + // Vehicle Dismount + if(move->trigger[2] && isMounted()) + Con::executef(mDataBlock,2,"doDismount",scriptThis()); + + if(!inLiquid && mWaterCoverage != 0.0f) { + Con::executef(mDataBlock,4,"onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), Con::getIntArg(mLiquidType)); + inLiquid = true; + } + else if(inLiquid && mWaterCoverage == 0.0f) { + Con::executef(mDataBlock,3,"onLeaveLiquid",scriptThis(), Con::getIntArg(mLiquidType)); + inLiquid = false; + } + } + else { + if(!inLiquid && mWaterCoverage >= 1.0f) { + + inLiquid = true; + } + else if(inLiquid && mWaterCoverage < 0.8f) { + if(getVelocity().len() >= mDataBlock->exitSplashSoundVel && !isMounted()) + alxPlay(mDataBlock->sound[PlayerData::ExitWater], &getTransform()); + inLiquid = false; + } + } +} + + +//---------------------------------------------------------------------------- + +bool Player::canJump() +{ + return mState == MoveState && mDamageState == Enabled && !isMounted() && !mJumpDelay && mEnergy >= mDataBlock->minJumpEnergy && mJumpSurfaceLastContact < JumpSkipContactsMax; +} + +//---------------------------------------------------------------------------- + +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() +{ + // 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: + (mHead.x - mArmRange.min) / mArmRange.delta; + mShapeInstance->setPos(mArmAnimation.thread,mClampF(tp,0,1)); + } + if (mHeadVThread) { + F32 tp = (mHead.x - mHeadVRange.min) / mHeadVRange.delta; + mShapeInstance->setPos(mHeadVThread,mClampF(tp,0,1)); + } + if (mHeadHThread) { + F32 dt = 2 * mDataBlock->maxLookAngle; + F32 tp = (mHead.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. + F32 pos = deathDelta(mDeath.posAdd); + mDeath.lastPos = pos; + } + 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.0; + RayInfo coll; + bool conformToStairs = false; + Point3F pos(loc.x, loc.y, loc.z + 0.1); + Point3F below(pos.x, pos.y, loc.z - sConformCheckDown); + MatrixF * retVal = NULL; + + PROFILE_START(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) + 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.995) + { + 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.0); + if (c) // add (0,boxWidth) and (boxWidth,0) + corners[c][c - 1] += (boxRad * 2.0); + downpts[c].set(corners[c].x, corners[c].y, loc.z - sConformCheckDown); + } + + // Do the three casts- + for (S32 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()"); + 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.25); + + if (mFabs(height) < 2.2 && !normal.isZero() && desNormal.z > 0.01) + { + VectorF upY(0, 1, 0), ahead; + VectorF sideVec; + MatrixF mat(true); + + normal.normalize(); + mat.set(EulerF (0, 0, 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.5; + + accel(sink, desiredSink, adjust * 0.15); + + Point3F position(pos); + position.z -= sink; + resMat.setColumn(3, position); + + if (dt < 0) + { // we're moving, so update normal and sink amount + curNormal = normal; + curSink = sink; + } + + retVal = &resMat; + } + } + PROFILE_END(); + 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 (action != mArmAnimation.action && anim.sequence != -1) { + 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.time = ActionAnimation::Scale; + 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.15; + + mShapeInstance->setTimeScale(mActionAnimation.thread, + mActionAnimation.forward? 1: -1); + mShapeInstance->transitionToSequence(mActionAnimation.thread,anim.sequence, + mActionAnimation.forward? 0: 1, transTime, true); + } + else + mShapeInstance->setSequence(mActionAnimation.thread,anim.sequence, + mActionAnimation.forward? 0: 1); + } +} + +void Player::updateActionThread() +{ + PROFILE_START(UpdateActionThread); + + // Select an action animation sequence, this assumes that + // this function is called once per tick. + if (mActionAnimation.forward) + mActionAnimation.atEnd = mShapeInstance->getPos(mActionAnimation.thread) == 1; + else + mActionAnimation.atEnd = mShapeInstance->getPos(mActionAnimation.thread) == 0; + + 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 ), TerrainObjectType | InteriorObjectType | VehicleObjectType, &rInfo)) + { + S32 sound = -1; + // Create foot puff on ground. New emitter every time for visibility reasons + if( rInfo.object->getTypeMask() & TerrainObjectType) + { + TerrainBlock* tBlock = static_cast(rInfo.object); + S32 mapIndex = tBlock->mMPMIndex[0]; + if (mapIndex != -1) { + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntryFromIndex(mapIndex); + if(pEntry) + { + sound = pEntry->sound; + if( rInfo.t <= 0.5 && mWaterCoverage == 0.0f) + { + ParticleEmitter * emitter = new ParticleEmitter; + emitter->onNewDataBlock( mDataBlock->footPuffEmitter ); + + S32 x; + ColorF colorList[ParticleEngine::PC_COLOR_KEYS]; + + for(x = 0; x < 2; ++x) + colorList[x].set( pEntry->puffColor[x].red, pEntry->puffColor[x].green, pEntry->puffColor[x].blue, pEntry->puffColor[x].alpha ); + for(x = 2; x < ParticleEngine::PC_COLOR_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(); + } + + if (mDataBlock->decalData != NULL) { + mSceneManager->getCurrentDecalManager()->addDecal(rInfo.point, rot, Point3F(rInfo.normal), + mDataBlock->decalData); + } + } + } + } + } + else if( rInfo.object->getTypeMask() & VehicleObjectType) + sound = 2; // Play metal sound + //play footstep sounds + if(isClientObject()) + playFootstepSound(triggeredLeft, sound); + } + } + + // 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,3,"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 (mActionAnimation.time == ActionAnimation::Scale && anim.speed) { + VectorF vel; + mWorldToObj.mulV(mVelocity,&vel); + F32 scale = 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; + if (mFalling) { + // Not in contact with any surface and falling + action = PlayerData::FallAnim; + } + else + { + 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.1; + 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) { + F32 d = mDot(vel, anim.dir); + 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; + } + } + } + } + } + } + setActionThread(action,forward,false,false); +} + +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,0,-mAtan(-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) + 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) +{ + Box3F box; + VectorF offset = mVelocity * time; + box.min = mObjBox.min + offset + *pos; + box.max = mObjBox.max + offset + *pos; + box.max.z += mDataBlock->maxStepHeight + sMinFaceDistance; + + SphereF sphere; + sphere.center = (box.min + box.max) * 0.5; + VectorF bv = box.max - sphere.center; + sphere.radius = bv.len(); + + ClippedPolyList polyList; + polyList.mPlaneList.clear(); + polyList.mNormal.set(0,0,0); + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(box.min,VectorF(-1,0,0)); + polyList.mPlaneList[1].set(box.max,VectorF(0,1,0)); + polyList.mPlaneList[2].set(box.max,VectorF(1,0,0)); + polyList.mPlaneList[3].set(box.min,VectorF(0,-1,0)); + polyList.mPlaneList[4].set(box.min,VectorF(0,0,-1)); + polyList.mPlaneList[5].set(box.max,VectorF(0,0,1)); + + CollisionWorkingList& rList = mConvex.getWorkingList(); + CollisionWorkingList* pList = rList.wLink.mNext; + while (pList != &rList) { + Convex* pConvex = pList->mConvex; + if ((pConvex->getObject()->getType() & StaticObjectType) != 0) + { + 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; +} + + +bool Player::updatePos(const F32 travelTime) +{ + PROFILE_START(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,0,0),mRot); + setMaskBits(MoveMask); + PROFILE_END(); + return true; + } + + // Try and move to new pos + F32 totalMotion = 0; + F32 initialSpeed = mVelocity.len(); + + Point3F start; + Point3F initialPosition; + getTransform().getColumn(3,&start); + initialPosition = start; + CollisionList collisionList; + CollisionList physZoneCollisionList; + + MatrixF collisionMatrix(true); + collisionMatrix.setColumn(3, start); + + VectorF firstNormal; + F32 maxStep = mDataBlock->maxStepHeight; + F32 time = travelTime; + U32 count = 0; + + 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; + +// boingy++; + 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 = mObjBox; + wBox.min += end; + wBox.max += end; + + static EarlyOutPolyList eaPolyList; + eaPolyList.clear(); + eaPolyList.mNormal.set(0,0,0); + eaPolyList.mPlaneList.clear(); + eaPolyList.mPlaneList.setSize(6); + eaPolyList.mPlaneList[0].set(wBox.min,VectorF(-1,0,0)); + eaPolyList.mPlaneList[1].set(wBox.max,VectorF(0,1,0)); + eaPolyList.mPlaneList[2].set(wBox.max,VectorF(1,0,0)); + eaPolyList.mPlaneList[3].set(wBox.min,VectorF(0,-1,0)); + eaPolyList.mPlaneList[4].set(wBox.min,VectorF(0,0,-1)); + eaPolyList.mPlaneList[5].set(wBox.max,VectorF(0,0,1)); + + // 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)) + { + if (pConvex->getObject()->getTypeMask() & ForceFieldObjectType) + { + ForceFieldBare* pField = dynamic_cast(pConvex->getObject()); + if (pField == NULL || pField->isPermiableTo(this) == false) + pConvex->getPolyList(&eaPolyList); + } + else + { + // No need to seperate 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; +// bingy++; + break; + } + } + + collisionMatrix.setColumn(3, start); + sBoxPolyhedron.buildBox(collisionMatrix, mObjBox); + + // Setup the bounding box for the extrudedPolyList + Box3F plistBox = mObjBox; + collisionMatrix.mul(plistBox); + Point3F oldMin = plistBox.min; + Point3F oldMax = plistBox.max; + plistBox.min.setMin(oldMin + (mVelocity * time) - Point3F(0.1, 0.1, 0.1)); + plistBox.max.setMax(oldMax + (mVelocity * time) + Point3F(0.1, 0.1, 0.1)); + + // 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() & ForceFieldObjectType) + { + ForceFieldBare* pField = dynamic_cast(pConvex->getObject()); + if (pField == NULL || pField->isPermiableTo(this) == false) + pConvex->getPolyList(&sExtrudedPolyList); + } else + { + 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.count; j++) { + AssertFatal(dynamic_cast(physZoneCollisionList.collision[j].object), "Bad phys zone!"); + PhysicalZone* pZone = (PhysicalZone*)physZoneCollisionList.collision[j].object; + if (pZone->isActive()) + mVelocity *= pZone->getVelocityMod(); + } + + if (collisionList.count != 0 && collisionList.t < 1.0) { + // Set to collision point + F32 velLen = mVelocity.len(); + + F32 dt = time * getMin(collisionList.t, 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.maxHeight < start.z + mDataBlock->maxStepHeight) { + bool stepped = false; + for (U32 c = 0; c < collisionList.count; c++) { + Collision& cp = collisionList.collision[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. + Collision* collision = &collisionList.collision[0]; + Collision* cp = collision + 1; + Collision *ep = collision + collisionList.count; + for (; cp != ep; cp++) + { + if (cp->faceDot > collision->faceDot) + collision = cp; + } + + 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.9); + 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 = %f, this one = %f", mDataBlock->minImpactSpeed, bd); + setState(RecoverState, recover); + } + } + if (isServerObject() && bd > (mDataBlock->minImpactSpeed / 3.0f)) { + mImpactSound = PlayerData::ImpactNormal; + setMaskBits(ImpactMask); + } + + // 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 && + mDot(collision->normal,firstNormal) < 0) + { + VectorF nv; + mCross(collision->normal,firstNormal,&nv); + F32 nvl = nv.len(); + if (nvl) + { + if (mDot(nv,mVelocity) < 0) + nvl = -nvl; + nv *= mVelocity.len() / nvl; + mVelocity = nv; + } + } + } + } + + // Track collisions + if (!isGhost() && collision->object->getTypeMask() & ShapeBaseObjectType) + queueCollision(static_cast(collision->object)); + } + else + { + totalMotion += (end - start).len(); + start = end; + break; + } + } + + if (count == sMoveRetryCount) + { + // Failed to move + start = initialPosition; + mVelocity.set(0,0,0); + } + + // Set new position + // If on the client, calc delta for backstepping + if (isClientObject()) + { + delta.pos = start; + delta.posVec = delta.posVec - delta.pos; + delta.dt = 1; + } + + setPosition(start,mRot); + setMaskBits(MoveMask); + updateContainer(); + + // Collisions are only queued on the server and can be + // generated by either updateMove or updatePos + if (!isGhost()) + notifyCollision(); + + PROFILE_END(); + + // Check the totaldistance moved. If it is more than 1000th of the velocity, then + // we moved a fair amount... + if (totalMotion >= (0.001 * initialSpeed)) + return true; + else + return false; +} + + +//---------------------------------------------------------------------------- +void Player::findContact(bool* run,bool* jump,VectorF* contactNormal) +{ + Point3F pos; + getTransform().getColumn(3,&pos); + + Box3F wBox; + Point3F exp(0,0,sTractionDistance); + wBox.min = pos + mObjBox.min - exp; + wBox.max.x = pos.x + mObjBox.max.x; + wBox.max.y = pos.y + mObjBox.max.y; + wBox.max.z = pos.z + mObjBox.min.z + sTractionDistance; + + static ClippedPolyList polyList; + polyList.clear(); + polyList.doConstruct(); + polyList.mNormal.set(0,0,0); + polyList.setInterestNormal(Point3F(0, 0, -1)); + + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].setYZ(wBox.min, -1); + polyList.mPlaneList[1].setXZ(wBox.max, 1); + polyList.mPlaneList[2].setYZ(wBox.max, 1); + polyList.mPlaneList[3].setXZ(wBox.min, -1); + polyList.mPlaneList[4].setXY(wBox.min, -1); + polyList.mPlaneList[5].setXY(wBox.max, 1); + 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 = mDataBlock->pickupDelta; + wBox.min.x -= pd; wBox.min.y -= pd; + wBox.max.x += pd; wBox.max.y += pd; + wBox.max.z = pos.z + mObjBox.max.z; + + Player * serverParent = NULL; + if (bool(mServerObject)) { + serverParent = dynamic_cast((NetObject *)mServerObject); + GameConnection * con = serverParent->getControllingClient(); + if (con && !con->isAIControlled()) + serverParent = NULL; + } + + // 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(); + + if(objectMask & sDirtySetMask) + { + setControlDirty(); + if(objectMask & ShapeBaseObjectType) + static_cast(pConvex->getObject())->setControlDirty(); + } + + // 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())) + queueCollision(static_cast(pConvex->getObject())); + } + 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); + } + else if ((objectMask & mask) && !(objectMask & PhysicalZoneObjectType)) { + Box3F convexBox = pConvex->getBoundingBox(); + if (plistBox.isOverlapped(convexBox)) + { + if (serverParent == NULL) { + if (objectMask & ForceFieldObjectType) + { + ForceFieldBare* pField = dynamic_cast(pConvex->getObject()); + if (pField == NULL || pField->isPermiableTo(this) == false) + pConvex->getPolyList(&polyList); + } + else + { + pConvex->getPolyList(&polyList); + } + } + } + } + + pList = pList->wLink.mNext; + } + + if (serverParent) { + // Just grab the info- + const ContactInfo & info = serverParent->mContactInfo; + *jump = info.jump; + *run = info.run; + if (info.contacted) + *contactNormal = info.contactNormal; + return; + } + + if (polyList.isEmpty() == false) { + // Pick flatest surface + F32 bestVd = -1; + 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; + *contactNormal = poly->plane; + } + } + *run = bestVd > mDataBlock->runSurfaceCos; + *jump = bestVd > mDataBlock->jumpSurfaceCos; + } + else + *jump = *run = false; + + // Save the info for client peeking hack- + mContactInfo.clear(); + if (!(mContactInfo.contacted = !polyList.isEmpty())) + mContactInfo.contactNormal = *contactNormal; + mContactInfo.run = *run; + mContactInfo.jump = *jump; +} + + +bool Player::isDisplacable() const +{ + return true; +} + +Point3F Player::getMomentum() const +{ + return mVelocity * mMass; +} + +void Player::setMomentum(const Point3F& newMomentum) +{ + Point3F newVelocity = newMomentum / mMass; + mVelocity = newVelocity; +} + +F32 Player::getMass() const +{ + return mMass; +} + +#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.001 || sBalance > 16) { + mVelocity.set(0, 0, 0); + return false; + } + + F32 dt = displacement.len() / vellen; + + sBalance++; + + bool result = updatePos(dt); + + sBalance--; + + getTransform().getColumn(3, &delta.pos); + delta.posVec.set(0, 0, 0); + + return result; +} + +#else + +bool Player::displaceObject(const Point3F& displacement) +{ + F32 vellen = mVelocity.len(); + if (vellen < 0.001) { + mVelocity.set(0, 0, 0); + return false; + } + + F32 dt = displacement.len() / vellen; + + bool result = updatePos(dt); + + mObjToWorld.getColumn(3, &delta.pos); + delta.posVec.set(0, 0, 0); + + 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, 0, rot.z)); + mat.mul(nmat,zrot); + } + else { + mat.set(EulerF(0, 0, rot.z)); + mat.setColumn(3,pos); + } + Parent::setTransform(mat); + mRot = rot; +} + + +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, 0, rot.z)); + mat.mul(nmat,zrot); + } + else { + EulerF orient(0, 0, rot.z); + + mat.set(orient); + mat.setColumn(3, pos); + + if (inDeathAnim()) { + F32 boxRad = (mDataBlock->boxSize.x * 0.5); + 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,0,-mAtan(-vec.x,vec.y)); + setPosition(pos,rot); + setMaskBits(MoveMask | NoWarpMask); + setControlDirty(); +} + +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, 0)); + zmat.set(EulerF(0, 0, mHead.z)); + pmat.mul(zmat,xmat); + + F32 *dp = pmat,*sp = mShapeInstance->mNodeTransforms[mDataBlock->eyeNode]; + dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11]; + 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(mHead.x, 0, 0)); + zmat.set(EulerF(0, 0, mHead.z)); + pmat.mul(zmat,xmat); + + F32 *dp = pmat,*sp = mShapeInstance->mNodeTransforms[mDataBlock->eyeNode]; + dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11]; + 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, -1, &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, 0)); + mat->mul(getTransform(),xmat); + F32 *sp = nmat,*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, -1, &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, 0)); + mat->mul(getTransform(),xmat); + F32 *sp = nmat,*dp = *mat; + dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11]; + } + else + *mat = nmat; +} + + +// 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 && mControlObject == getObjectMount()) { + mControlObject->getCameraParameters(min,max,off,rot); + return; + } + *min = mDataBlock->cameraMinDist; + *max = mDataBlock->cameraMaxDist; + off->set(0,0,0); + rot->identity(); +} + + +//---------------------------------------------------------------------------- + +Point3F Player::getVelocity() const +{ + return mVelocity; +} + +void Player::setVelocity(const VectorF& vel) +{ + mVelocity = vel; + setMaskBits(MoveMask); +} + +void Player::applyImpulse(const Point3F&,const VectorF& vec) +{ + // Players ignore angular velocity + VectorF vel; + vel.x = vec.x / mMass; + vel.y = vec.y / mMass; + vel.z = vec.z / mMass; + setVelocity(mVelocity + vel); +} + + +//---------------------------------------------------------------------------- + +bool Player::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +{ + if (getDamageState() == Enabled) + { + // Collide against bounding box. Need at least this for the editor. + F32 st,et,fst = 0,fet = 1; + F32 *bmin = &mObjBox.min.x; + F32 *bmax = &mObjBox.max.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; + } + else + { + return false; + } +} + + +//---------------------------------------------------------------------------- + +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,1,1)); + 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.min.convolveInverse(mObjScale); + realBox.max.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; +} + + +//---------------------------------------------------------------------------- + +bool Player::writePacketData(GameConnection *connection, BitStream *stream) +{ + bool ret = Parent::writePacketData(connection, stream); + + stream->writeInt(mState,NumStateBits); + if (stream->writeFlag(mState == RecoverState)) + stream->writeInt(mRecoverTicks,PlayerData::RecoverDelayBits); + if (stream->writeFlag(mJumpDelay)) + stream->writeInt(mJumpDelay,PlayerData::JumpDelayBits); + + Point3F pos; + getTransform().getColumn(3,&pos); + if (stream->writeFlag(!isMounted())) { + // Will get position from mount + connection->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,10); + ret = mControlObject->writePacketData(connection, stream); + } + else // the control object exists, but not on the other side... + ret = false; + } + else + stream->writeFlag(false); + + stream->writeFlag(mDisableMove); + stream->writeFlag(mPilot); + + return ret; +} + + +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); + connection->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(10); + ShapeBase* obj = static_cast(connection->resolveGhost(gIndex)); + setControlObject(obj); + obj->readPacketData(connection, stream); + } + else + setControlObject(0); + + mDisableMove = stream->readFlag(); + mPilot = stream->readFlag(); +} + +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); + con->writeCompressed(stream, pos); + F32 len = mVelocity.len(); + if(stream->writeFlag(len > 0.02)) + { + Point3F outVel = mVelocity; + outVel *= 1/len; + stream->writeNormalVector(outVel, 10); + len *= 32.0; // 5 bits of fraction + if(len > 8191) + len = 8191; + stream->writeInt(len, 13); + } + stream->writeFloat(mRot.z / M_2PI, 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.0; + 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.0; + } + 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 = (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; + con->readCompressed(stream, &pos); + F32 speed = mVelocity.len(); + if(stream->readFlag()) + { + stream->readNormalVector(&mVelocity, 10); + mVelocity *= stream->readInt(13) / 32.0f; + } + else + mVelocity.set(0,0,0); + rot.y = rot.x = 0; + rot.z = stream->readFloat(7) * M_2PI; + 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,0,0); + + if (stream->readFlag() && isProperlyAdded()) { + // Warp to the server's position over time + // First advance pos to estimated server time. + S32 ticks = mCeil(con->getRoundTripTime() / (2 * TickMs)); + if (ticks > sMaxLatencyTicks) { + ticks = (S32)sMaxLatencyTicks; + } + if (ticks > 0) { + Point3F orgPos,orgRot = mRot; + getTransform().getColumn(3,&orgPos); + + setPosition(pos,rot); + while (ticks--) { + updateState(); + updateMove(&delta.move); + updatePos(); + } + getTransform().getColumn(3,&pos); + + setPosition(orgPos,orgRot); + } + + // 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.5 * TickSec; + F32 dt = (as > 0.00001f) ? delta.warpOffset.len() / as: sMaxWarpTicks; + delta.warpTicks = (S32)((dt > sMinWarpTicks) ? getMax(mFloor(dt + 0.5), 1.0f) : 0.0f); + if (delta.warpTicks) { + if (delta.warpTicks > sMaxWarpTicks) + delta.warpTicks = sMaxWarpTicks; + delta.warpOffset /= delta.warpTicks; + + delta.rotOffset = rot - delta.rot; + if(delta.rotOffset.z < - M_PI) + delta.rotOffset.z += M_2PI; + else if(delta.rotOffset.z > M_PI) + delta.rotOffset.z -= M_2PI; + delta.rotOffset /= 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; + getTransform().getColumn(3,&cp); + if (delta.dt == 0) { + delta.posVec.set(0,0,0); + delta.rotVec.set(0,0,0); + } + else { + F32 dti = 1 / delta.dt; + delta.posVec = (cp - pos) * dti; + delta.rotVec.z = mRot.z - rot.z; + + if(delta.rotVec.z > M_PI) + delta.rotVec.z -= M_2PI; + else if(delta.rotVec.z < -M_PI) + delta.rotVec.z += M_2PI; + + 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,0,0); + delta.rotVec.set(0,0,0); + delta.warpTicks = 0; + delta.dt = 0; + setPosition(pos,rot); + } + } + F32 energy = stream->readFloat(EnergyLevelBits) * mDataBlock->maxEnergy; + setEnergyLevel(energy); +} + + +//---------------------------------------------------------------------------- + +static const char* cGetState(SimObject *ptr, S32, const char **) +{ + Player* obj = static_cast(ptr); + return obj->getStateName(); +} + +static const char* cGetDamageLocation(SimObject *ptr, S32, const char **argv) +{ + Player *obj = static_cast(ptr); + + const char *buffer1; + const char *buffer2; + char *buff = Con::getReturnBuffer(128); + + Point3F pos; + dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z); + obj->getDamageLocation(pos, buffer1, buffer2); + + dSprintf(buff, 128, "%s %s", buffer1, buffer2); + return buff; +} + +static bool cSetArmThread(SimObject *ptr, S32, const char **argv) +{ + Player* obj = static_cast(ptr); + return obj->setArmThread(argv[2]); +} + +static bool cSetActionThread(SimObject *ptr, S32 argc, const char **argv) +{ + Player* obj = static_cast(ptr); + bool hold = (argc > 3)? dAtob(argv[3]): false; + bool fsp = (argc > 4)? dAtob(argv[4]): true; + return obj->setActionThread(argv[2],hold,true,fsp); +} + +static bool cSetControlObject(SimObject *ptr, S32, const char **argv) +{ + Player *obj = static_cast(ptr); + obj->setControlDirty(); + ShapeBase* controlObject; + if (Sim::findObject(argv[2],controlObject)) { + obj->setControlObject(controlObject); + return true; + } + else + obj->setControlObject(0); + return false; +} + +static S32 cGetControlObject(SimObject *ptr, S32, const char **) +{ + Player *obj = static_cast(ptr); + ShapeBase* controlObject = obj->getControlObject(); + return controlObject? controlObject->getId(): 0; +} + +static void cClearControlObject(SimObject *ptr, S32, const char **) +{ + Player *obj = static_cast(ptr); + obj->setControlObject(0); +} + +static void cDisableMove(SimObject *ptr, S32, const char **argv) +{ + Player *obj = static_cast(ptr); + obj->disableMove(dAtob(argv[2])); +} + +static void cSetPilot(SimObject *ptr, S32, const char **argv) +{ + Player *obj = static_cast(ptr); + obj->setPilot(dAtob(argv[2])); +} + +static bool cCheckDismountPoint(SimObject *ptr, S32, const char **argv) +{ + Player *obj = static_cast(ptr); + + Point3F oldPos; + Point3F pos; + dSscanf(argv[2], "%f %f %f", + &oldPos.x, + &oldPos.y, + &oldPos.z); + dSscanf(argv[3], "%f %f %f", + &pos.x, + &pos.y, + &pos.z); + MatrixF oldPosMat(true); + oldPosMat.setColumn(3, oldPos); + MatrixF posMat(true); + posMat.setColumn(3, pos); + return obj->checkDismountPosition(oldPosMat, posMat); +} + +//---------------------------------------------------------------------------- +void Player::initPersistFields() +{ + Parent::initPersistFields(); +} + +void Player::consoleInit() +{ +// Con::addVariable("bingy", TypeS32, &bingy); +// Con::addVariable("boingy", TypeS32, &boingy); + + 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::maxLatencyTicks",TypeF32,&sMaxLatencyTicks); + + Con::addCommand("Player", "getState", cGetState, "obj.getState()", 2, 2); + Con::addCommand("Player", "setArmThread", cSetArmThread, "obj.setArmThread(sequenceName);", 3, 3); + Con::addCommand("Player", "setActionThread", cSetActionThread, "obj.setActionThread(sequenceName,,)", 3, 5); + Con::addCommand("Player", "setControlObject", cSetControlObject, "obj.setControlObject(obj)", 3, 3); + Con::addCommand("Player", "getControlObject", cGetControlObject, "obj.getControlObject()", 2, 2); + Con::addCommand("Player", "clearControlObject", cClearControlObject, "obj.clearControlObject()", 2, 2); + Con::addCommand("Player", "disableMove", cDisableMove, "obj.disableMove(bool)", 3, 3); + Con::addCommand("Player", "setPilot", cSetPilot, "obj.setPilot(bool)", 3, 3); + Con::addCommand("Player", "getDamageLocation", cGetDamageLocation, "obj.getDamageLocation(pos)", 3, 3); + + Con::addCommand("Player", "checkDismountPoint", cCheckDismountPoint, "obj.checkDismountPoint(\"x y z\", \"x y z\")", 4, 4); +} + + + +//-------------------------------------------------------------------------- +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.0) + mWeaponBackFraction = 1.0f - rinfo.t; + else + mWeaponBackFraction = 0.0f; + } else { + mWeaponBackFraction = 0.0f; + } + enableCollision(); +} + + +void Player::renderMountedImage(SceneState* state, ShapeImageRenderImage* rimage) +{ + AssertFatal(rimage->mSBase == this, "Error, wrong image"); + GameConnection *con = GameConnection::getServerConnection(); + bool renderMounts = true; + if(con && con->getControlObject() == this && con->isFirstPerson()) + renderMounts = sRenderMyItems; + if (renderMounts == false) + return; + + bool fogExemption = false; + if(con && con->getControlObject() == this && con->isFirstPerson() == true) + fogExemption = true; + F32 fogAmount = 0.0f; + if (fogExemption == false) + { + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 dist = cameraOffset.len(); + fogAmount = state->getHazeAndFog(dist,cameraOffset.z); + } + + // Mounted items + PROFILE_START(PlayerRenderMounted); + MountedImage& image = mMountedImageList[rimage->mIndex]; + if (image.dataBlock && image.shapeInstance && DetailManager::selectCurrentDetail(image.shapeInstance)) { + MatrixF mat; + getRenderImageTransform(rimage->mIndex, &mat); + glPushMatrix(); + + if (rimage->mIndex == 0 && mWeaponBackFraction != 0.0 && getDamageState() == Enabled) { + MatrixF nmat; + MatrixF smat; + Parent::getRenderMuzzleTransform(0,&nmat); + Parent::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; + glTranslatef(displace.x, displace.y, displace.z); + } + dglMultMatrix(&mat); + + if (image.dataBlock->cloakable && mCloakLevel != 0.0) + image.shapeInstance->setAlphaAlways(0.04 + (1 - mCloakLevel) * 0.96); + else + image.shapeInstance->setAlphaAlways(1.0); + + if (image.dataBlock->cloakable && mCloakLevel == 0.0 && + (image.dataBlock->emap && gRenderEnvMaps) && + state->getEnvironmentMap().getGLName() != 0) { + image.shapeInstance->setEnvironmentMap(state->getEnvironmentMap()); + image.shapeInstance->setEnvironmentMapOn(true, 1.0); + } else { + image.shapeInstance->setEnvironmentMapOn(false, 1.0); + } + + image.shapeInstance->setupFog(fogAmount,state->getFogColor()); + image.shapeInstance->animate(); + image.shapeInstance->render(); + + // easiest just to shut it off here. If we're cloaked on the next frame, + // we don't want envmaps... + image.shapeInstance->setEnvironmentMapOn(false, 1.0); + + glPopMatrix(); + } + PROFILE_END(); +} + + +void Player::renderImage(SceneState* state, SceneRenderImage* image) +{ + glMatrixMode(GL_MODELVIEW); + + // Base shape + F32 fogAmount = 0; + F32 dist = 0; + PROFILE_START(PlayerRenderPrimary); + + GameConnection *con = GameConnection::getServerConnection(); + bool renderPlayer = true; + bool renderMounts = true; + if(con && con->getControlObject() == this && con->isFirstPerson()) + { + renderPlayer = sRenderMyPlayer; + renderMounts = sRenderMyItems; + } + if (mShapeInstance && renderPlayer && DetailManager::selectCurrentDetail(mShapeInstance)) { + glPushMatrix(); + dglMultMatrix(&getRenderTransform()); + glScalef(mObjScale.x,mObjScale.y,mObjScale.z); + + if (mCloakLevel != 0.0) { + glMatrixMode(GL_TEXTURE); + glPushMatrix(); + + static U32 shiftX = 0; + static U32 shiftY = 0; + + shiftX = (shiftX + 1) % 128; + shiftY = (shiftY + 1) % 127; + glTranslatef(F32(shiftX) / 127.0, F32(shiftY)/126.0, 0); + glMatrixMode(GL_MODELVIEW); + + mShapeInstance->smRenderData.renderDecals = false; + mShapeInstance->setAlphaAlways(0.04 + (1 - mCloakLevel) * 0.96); + mShapeInstance->setOverrideTexture(mCloakTexture); + } + else { + mShapeInstance->setAlphaAlways(1.0); + } + + if (mCloakLevel == 0.0 && (mDataBlock->emap && gRenderEnvMaps) && state->getEnvironmentMap().getGLName() != 0) { + mShapeInstance->setEnvironmentMap(state->getEnvironmentMap()); + mShapeInstance->setEnvironmentMapOn(true, 1.0); + } else { + mShapeInstance->setEnvironmentMapOn(false, 1.0); + } + + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + dist = cameraOffset.len(); + + bool fogExemption = false; + GameConnection *con = GameConnection::getServerConnection(); + if(con && con->getControlObject() == this && con->isFirstPerson() == true) + fogExemption = true; + fogAmount = fogExemption ? 0.0 : state->getHazeAndFog(dist,cameraOffset.z); + + if( mCloakLevel > 0.0 ) + { + fogAmount = 0.0; + } + + TSMesh::setOverrideFade( mFadeVal ); + + mShapeInstance->setupFog(fogAmount,state->getFogColor()); + mShapeInstance->animate(); + mShapeInstance->render(); + + TSMesh::setOverrideFade( 1.0 ); + + mShapeInstance->setEnvironmentMapOn(false, 1.0); + + if (mCloakLevel != 0.0) { + glMatrixMode(GL_TEXTURE); + glPopMatrix(); + + mShapeInstance->clearOverrideTexture(); + mShapeInstance->smRenderData.renderDecals = true; + } + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + PROFILE_END(); + + // draw the shadow... + // only render shadow if 1) we have one, 2) we are close enough to be visible + // 3) we are not the very last visible detail level (stop rendering shadow a little before shape) + + PROFILE_START(PlayerRenderShadow); + TSMesh::setOverrideFade( mFadeVal ); + + if (mShapeInstance && renderPlayer && mCloakLevel == 0.0 && + mMount.object == NULL && image->isTranslucent == true) + { + renderShadow(dist,fogAmount); + } + + TSMesh::setOverrideFade( 1.0 ); + PROFILE_END(); + + dglSetCanonicalState(); + + // Debugging Bounding Box + if (!mShapeInstance || gShowBoundingBox) { + Point3F box; + glPushMatrix(); + box = (mWorkingQueryBox.min + mWorkingQueryBox.max) * 0.5; + glTranslatef(box.x,box.y,box.z); + box = (mWorkingQueryBox.max - mWorkingQueryBox.min) * 0.5; + glScalef(box.x,box.y,box.z); + glColor3f(1, 1, 0); + wireCube(Point3F(1,1,1),Point3F(0,0,0)); + glPopMatrix(); + + Box3F convexBox = mConvex.getBoundingBox(getRenderTransform(), getScale()); + glPushMatrix(); + box = (convexBox.min + convexBox.max) * 0.5; + glTranslatef(box.x,box.y,box.z); + box = (convexBox.max - convexBox.min) * 0.5; + glScalef(box.x,box.y,box.z); + glColor3f(1, 1, 1); + wireCube(Point3F(1,1,1),Point3F(0,0,0)); + glPopMatrix(); + + glEnable(GL_DEPTH_TEST); + } +} + +void Player::playFootstepSound(bool triggeredLeft, S32 sound) +{ + MatrixF footMat = getTransform(); + + if(mWaterCoverage == 0.0f) { + + switch (sound) { + case 0: + //Soft + if(triggeredLeft) + alxPlay(mDataBlock->sound[PlayerData::LFootSoft], &footMat); + else + alxPlay(mDataBlock->sound[PlayerData::RFootSoft], &footMat); + break; + case 1: + //Hard + if(triggeredLeft) + alxPlay(mDataBlock->sound[PlayerData::LFootHard], &footMat); + else + alxPlay(mDataBlock->sound[PlayerData::RFootHard], &footMat); + break; + case 2: + //Metal + if(triggeredLeft) + alxPlay(mDataBlock->sound[PlayerData::LFootMetal], &footMat); + else + alxPlay(mDataBlock->sound[PlayerData::RFootMetal], &footMat); + break; + case 3: + //Snow + if(triggeredLeft) + alxPlay(mDataBlock->sound[PlayerData::LFootSnow], &footMat); + else + alxPlay(mDataBlock->sound[PlayerData::RFootSnow], &footMat); + break; + default: + //Hard + if(triggeredLeft) + alxPlay(mDataBlock->sound[PlayerData::LFootHard], &footMat); + else + alxPlay(mDataBlock->sound[PlayerData::RFootHard], &footMat); + break; + } + } + else { + if(mWaterCoverage < mDataBlock->footSplashHeight) { + if(triggeredLeft) + alxPlay(mDataBlock->sound[PlayerData::LFootShallowSplash], &footMat); + else + alxPlay(mDataBlock->sound[PlayerData::RFootShallowSplash], &footMat); + } + else if(mWaterCoverage < 1.0) { + if(triggeredLeft) + alxPlay(mDataBlock->sound[PlayerData::LFootWading], &footMat); + else + alxPlay(mDataBlock->sound[PlayerData::RFootWading], &footMat); + } + else { + if(triggeredLeft) { + alxPlay(mDataBlock->sound[PlayerData::LFootUnderWater], &footMat); + alxPlay(mDataBlock->sound[PlayerData::LFootBubbles], &footMat); + } + else { + alxPlay(mDataBlock->sound[PlayerData::RFootUnderWater], &footMat); + alxPlay(mDataBlock->sound[PlayerData::RFootBubbles], &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 ), TerrainObjectType | InteriorObjectType | VehicleObjectType, &rInfo)) + { + S32 sound = -1; + if( rInfo.object->getTypeMask() & TerrainObjectType) + { + TerrainBlock* tBlock = static_cast(rInfo.object); + S32 mapIndex = tBlock->mMPMIndex[0]; + if (mapIndex != -1) { + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntryFromIndex(mapIndex); + if(pEntry) + sound = pEntry->sound; + } + } + else if( rInfo.object->getTypeMask() & VehicleObjectType) + sound = 2; // Play metal sound + + switch(sound) { + case 0: + //Soft + alxPlay(mDataBlock->sound[PlayerData::ImpactSoft], &getTransform()); + break; + case 1: + //Hard + alxPlay(mDataBlock->sound[PlayerData::ImpactHard], &getTransform()); + break; + case 2: + //Metal + alxPlay(mDataBlock->sound[PlayerData::ImpactMetal], &getTransform()); + break; + case 3: + //Snow + alxPlay(mDataBlock->sound[PlayerData::ImpactSnow], &getTransform()); + break; + default: + //Hard + alxPlay(mDataBlock->sound[PlayerData::ImpactHard], &getTransform()); + break; + } + } + } + mImpactSound = 0; + // mClientImpactSound++; +} + +//-------------------------------------------------------------------------- +// 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, dt * 1000.0 ); + } + } + + Point3F contactPoint; + if( !collidingWithWater( contactPoint ) ) + { + mLastWaterPos = mLastPos; + return; + } + + F32 speed = moveDir.len(); + if( speed < mDataBlock->splashVelEpsilon ) speed = 0.0; + + U32 emitRate = speed * mDataBlock->splashFreqMod * dt; + + U32 i; + for( i=0; iemitParticles( mLastWaterPos, contactPoint, Point3F( 0.0, 0.0, 1.0 ), + moveDir, emitRate ); + } + } + + mLastWaterPos = contactPoint; +} + + +//-------------------------------------------------------------------------- +// Returns true if player is intersecting a water surface +//-------------------------------------------------------------------------- +bool Player::collidingWithWater( Point3F &waterHeight ) +{ + Point3F curPos = getPosition(); + + F32 height = mFabs( mObjBox.max.z - mObjBox.min.z ); + + RayInfo rInfo; + if( gClientContainer.castRay( curPos + Point3F(0.0, 0.0, height), curPos, WaterObjectType, &rInfo) ) + { + WaterBlock* pBlock = dynamic_cast(rInfo.object); + + if( !pBlock ) + return false; + + if( !pBlock->isWater( pBlock->getLiquidType() )) + return false; + + waterHeight = rInfo.point; + return true; + } + + return false; +} + +//-------------------------------------------------------------------------- +bool Player::pointInWater( Point3F &point ) +{ + SimpleQueryList sql; + if (isServerObject()) + gServerSceneGraph->getWaterObjectList(sql); + else + gClientSceneGraph->getWaterObjectList(sql); + + for (U32 i = 0; i < sql.mList.size(); i++) + { + WaterBlock* pBlock = dynamic_cast(sql.mList[i]); + + if (pBlock && pBlock->isWater( pBlock->getLiquidType() )) + { + if (pBlock->isPointSubmergedSimple( point )) + return true; + } + + } + + return false; +} + +//-------------------------------------------------------------------------- +void Player::createSplash( Point3F &pos, F32 speed ) +{ + if(speed >= mDataBlock->hardSplashSoundVel) + alxPlay(mDataBlock->sound[PlayerData::ImpactWaterHard], &getTransform()); + else if(speed >= mDataBlock->medSplashSoundVel) + alxPlay(mDataBlock->sound[PlayerData::ImpactWaterMedium], &getTransform()); + else + alxPlay(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; + } +} + +void Player::disableMove(bool state) +{ + setControlDirty(); + mDisableMove = state; +} + +void Player::setPilot(bool state) +{ + mPilot = state; +} + +bool Player::isPilot() const +{ + return mPilot; +} + +bool Player::isControlObject() +{ + GameConnection* connection = GameConnection::getServerConnection(); + if( !connection ) return false; + ShapeBase *obj = connection->getControlObject(); + return ( obj == this ); +} + diff --git a/game/player.h b/game/player.h new file mode 100644 index 0000000..060bf34 --- /dev/null +++ b/game/player.h @@ -0,0 +1,489 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLAYER_H_ +#define _PLAYER_H_ + +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif +#ifndef _BOXCONVEX_H_ +#include "collision/boxConvex.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; +class DecalData; +class SplashData; + +//---------------------------------------------------------------------------- + +struct PlayerData: public ShapeBaseData { + typedef ShapeBaseData Parent; + enum Constants { + RecoverDelayBits = 7, + JumpDelayBits = 7, + NumSpineNodes = 6, + ImpactBits = 3, + NUM_SPLASH_EMITTERS = 3, + BUBBLE_EMITTER = 2, + }; + F32 pickupRadius; // Radius around player for items (on server) + F32 maxTimeScale; // Max timeScale for action animations + + F32 minLookAngle; + F32 maxLookAngle; + F32 maxFreelookAngle; + + F32 runForce; // Force used to accelerate player + F32 runEnergyDrain; // Energy drain/tick + F32 minRunEnergy; + F32 maxForwardSpeed; + F32 maxBackwardSpeed; + F32 maxSideSpeed; + F32 maxUnderwaterForwardSpeed; + F32 maxUnderwaterBackwardSpeed; + F32 maxUnderwaterSideSpeed; + + F32 maxStepHeight; + F32 runSurfaceAngle; // Angle from vertical in degrees + + F32 horizMaxSpeed; + F32 horizResistSpeed; + F32 horizResistFactor; + + F32 upMaxSpeed; + F32 upResistSpeed; + F32 upResistFactor; + + S32 recoverDelay; // # tick + F32 recoverRunForceScale; // RunForce multiplier in recover state + + F32 jumpForce; + F32 jumpEnergyDrain; // Energy per jump + F32 minJumpEnergy; + F32 minJumpSpeed; + F32 maxJumpSpeed; + F32 jumpSurfaceAngle; // Angle from vertical in degrees + S32 jumpDelay; // Delay time in ticks + + F32 boxHeadPercentage; + F32 boxTorsoPercentage; + + S32 boxHeadLeftPercentage; + S32 boxHeadRightPercentage; + S32 boxHeadBackPercentage; + S32 boxHeadFrontPercentage; + + F32 minImpactSpeed; + + F32 decalOffset; + + F32 groundImpactMinSpeed; + VectorF groundImpactShakeFreq; + VectorF groundImpactShakeAmp; + F32 groundImpactShakeDuration; + F32 groundImpactShakeFalloff; + + enum Sounds { + LFootSoft, + RFootSoft, + LFootHard, + RFootHard, + LFootMetal, + RFootMetal, + LFootSnow, + RFootSnow, + LFootShallowSplash, + RFootShallowSplash, + LFootWading, + RFootWading, + LFootUnderWater, + RFootUnderWater, + LFootBubbles, + RFootBubbles, + MoveBubbles, + WaterBreath, + ImpactSoft, + ImpactHard, + ImpactMetal, + ImpactSnow, + ImpactWaterEasy, + ImpactWaterMedium, + ImpactWaterHard, + ExitWater, + MaxSounds + }; + AudioProfile* sound[MaxSounds]; + + Point3F boxSize; // Width, depth, heigth + + // Animation and other data intialized in onAdd + struct ActionAnimationDef { + const char* name; // Sequence name + }; + struct ActionAnimation { + const char* name; // Sequence name + S32 sequence; + bool death; + VectorF dir; // Dir of animation ground transform + F32 speed; // Speed in m/s + }; + enum { + // *** WARNING *** + // These enum values are used to index the ActionAnimationList + // array instantiated in player.cc + // The first five are selected in the move state based on velocity + RootAnim, + RunForwardAnim, + BackBackwardAnim, + SideLeftAnim, + + // These are set explicitly based on player actions + FallAnim, + JumpAnim, + LandAnim, + + // + NumMoveActionAnims = SideLeftAnim + 1, + NumTableActionAnims = LandAnim + 1, + NumExtraActionAnims = 60, + NumActionAnims = NumTableActionAnims + NumExtraActionAnims, + ActionAnimBits = 8, + NullAnimation = (1 << ActionAnimBits) - 1 + }; + + static ActionAnimationDef ActionAnimationList[NumTableActionAnims]; + ActionAnimation actionList[NumActionAnims]; + U32 actionCount; + U32 lookAction; + U32 skiAction; + U32 standJumpAction; + 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]; + + 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; + + ParticleEmitterData* splashEmitterList[NUM_SPLASH_EMITTERS]; + S32 splashEmitterIDList[NUM_SPLASH_EMITTERS]; + + // + DECLARE_CONOBJECT(PlayerData); + PlayerData(); + bool preload(bool server, char errorBuffer[256]); + void getGroundInfo(TSShapeInstance*,TSThread*,ActionAnimation*); + bool isTableSequence(S32 seq); + bool isJumpAction(U32 action); + + static void consoleInit(); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class Player: public ShapeBase +{ + typedef ShapeBase Parent; + + //-------------------------------------- NOTE! DO NOT ADD ANY MORE MASK BITS TO THIS + //-------------------------------------- CLASS WITHOUT CHECKING WITH DMOORE OR MARKF + //-------------------------------------- We're totally out on the player. + 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; + }; + + ParticleEmitter *mSplashEmitter[PlayerData::NUM_SPLASH_EMITTERS]; + F32 mBubbleEmitterTime; + + // Client interpolation/warp data + struct StateDelta { + Move move; // Last move from server + F32 dt; // Last interpolation time + // Interpolation data + Point3F pos; + Point3F rot; + Point3F head; + VectorF posVec; + VectorF rotVec; + VectorF headVec; + // Warp data + S32 warpTicks; + Point3F warpOffset; + Point3F rotOffset; + }; + StateDelta delta; + 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; + Point3F mAnchorPoint; // Pos compression anchor + static F32 mGravity; + S32 mImpactSound; + S32 mMountPending; + + // Main player state + enum ActionState { + NullState, + MoveState, + RecoverState, + NumStateBits = 3 + }; + ActionState mState; + bool mFalling; // Falling in mid-air + S32 mJumpDelay; // Delay till next jump + S32 mContactTimer; // Ticks since last contact + + Point3F mJumpSurfaceNormal; + U32 mJumpSurfaceLastContact; + F32 mWeaponBackFraction; // Amount to slide the weapon back + + AUDIOHANDLE mMoveBubbleHandle; + AUDIOHANDLE mWaterBreathHandle; + + SimObjectPtr mControlObject; + + // Animation threads & data + struct ActionAnimation { + U32 action; + TSThread* thread; + S32 delayTicks; // before picking another. + bool forward; + bool firstPerson; + enum Advance { + Normal, + Scale, + Move + } time; + 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 mDisableMove; + bool mPilot; + bool mInMissionArea; + // + U32 mRecoverTicks; + U32 mReversePending; + U32 mVoiceTag; + U32 mDesiredVoiceTag; + + bool inLiquid; + // + PlayerData* mDataBlock; + + Point3F mLastPos; + Point3F mLastWaterPos; + + struct ContactInfo { + bool contacted, jump, run; + VectorF contactNormal; + void clear() {contacted=jump=run=false; 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; + + // New collision + public: + OrthoBoxConvex mConvex; + Box3F mWorkingQueryBox; + + protected: + void setState(ActionState state, U32 ticks=0); + void updateState(); + + void updateMove(const Move* move); + bool updatePos(const F32 travelTime = TickSec); + void updateLookAnimation(); + void updateAnimation(F32 dt); + void updateAnimationTree(bool firstPerson); + bool step(Point3F *pos,F32 *maxStep,F32 time); + + bool setArmThread(U32 action); + void setActionThread(U32 action,bool forward,bool hold = false,bool wait = false,bool fsp = false, bool forceSet = false); + void updateActionThread(); + void pickActionAnimation(); + 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(bool* run,bool* jump,VectorF* contactNormal); + virtual void onImageRecoil(U32 imageSlot,ShapeBaseImageData::StateData::RecoilState); + virtual void updateDamageLevel(); + virtual void updateDamageState(); + void setControllingClient(GameConnection* client); + + void calcClassRenderData(); + void renderMountedImage(SceneState* state, ShapeImageRenderImage* image); + void renderImage(SceneState* state, SceneRenderImage*); + void playFootstepSound(bool triggeredLeft, S32 sound); + void playImpactSound(); + + bool inDeathAnim(); + F32 deathDelta(Point3F &delta); + void updateDeathOffsets(); + bool inSittingAnim(); + + void updateSplash(); + void updateFroth( F32 dt ); + bool pointInWater( Point3F &point ); + void createSplash( Point3F &pos, F32 speed ); + bool collidingWithWater( Point3F &waterHeight ); + +public: + DECLARE_CONOBJECT(Player); + + Player(); + ~Player(); + static void initPersistFields(); + static void consoleInit(); + + // Transforms are all in object space + void setTransform(const MatrixF&); + void getEyeTransform(MatrixF* mat); + void getRenderEyeTransform(MatrixF* mat); + void getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot); + void getMuzzleTransform(U32 imageSlot,MatrixF* mat); + void getRenderMuzzleTransform(U32 imageSlot,MatrixF* mat); + Point3F getVelocity() const; + void setVelocity(const VectorF& vel); + void applyImpulse(const Point3F& pos,const VectorF& vec); + const Point3F& getRotation() { return mRot; } + const Point3F& getHeadRotation() { return mHead; } + void getDamageLocation(const Point3F& in_rPos, const char *&out_rpVert, const char *&out_rpQuad); + + bool canJump(); + F32 getJetAbility(F32& thrust, F32& duration, F32& jumpSpeed); + bool haveContact() {return !mContactTimer;} + void getMuzzlePointAI(U32 imageSlot, Point3F* point); + float getMaxForwardVelocity() { return (mDataBlock != NULL ? mDataBlock->maxForwardSpeed : 0); } + + virtual bool isDisplacable() const; + virtual Point3F getMomentum() const; + virtual void setMomentum(const Point3F&); + virtual F32 getMass() const; + virtual bool displaceObject(const Point3F& displaceVector); + + bool checkDismountPosition(const MatrixF& oldPos, const MatrixF& newPos); + + // + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData* dptr); + + // 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*); + ShapeBase* getControlObject(); + + // + void updateWorkingCollisionSet(); + void disableMove(bool); + void setPilot(bool); + bool isPilot() const; + void processTick(const Move*); + void interpolateTick(F32 dt); + 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 *); + bool writePacketData(GameConnection *, BitStream *stream); + void readPacketData(GameConnection *, BitStream *stream); + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); +}; + + +#endif diff --git a/game/precipitation.cc b/game/precipitation.cc new file mode 100644 index 0000000..c763a8f --- /dev/null +++ b/game/precipitation.cc @@ -0,0 +1,980 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "audio/audioDataBlock.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "game/precipitation.h" +#include "game/gameConnection.h" +#include "Math/mathIO.h" +#include "game/player.h" +#include "terrain/Sky.h" + +#define DML_DIR "textures/" +#define COLOR_OFFSET 0.25 + +bool Precipitation::smPrecipitationOn = true; +bool Precipitation::smPrecipitationPause = false; + +IMPLEMENT_CO_NETOBJECT_V1(Precipitation); +IMPLEMENT_CO_DATABLOCK_V1(PrecipitationData); + +namespace { + +MRandomLCG sgRandom(0xdeadbeef); + + +} // namespace {} + +//---------------------------------------------------------------------------- +//-------------------------------------- + + +PrecipitationData::PrecipitationData() +{ + soundProfile = NULL; + soundProfileId = 0; + mType = 0; + mMaxSize = 1.0f; + mMaterialListName = NULL; + mSizeX = 1.0; + mSizeY = 1.0; +} + +IMPLEMENT_GETDATATYPE(PrecipitationData) + IMPLEMENT_SETDATATYPE(PrecipitationData) + + void PrecipitationData::initPersistFields() +{ + Parent::initPersistFields(); + + Con::registerType(TypeGameBaseDataPtr, sizeof(PrecipitationData*), + REF_GETDATATYPE(PrecipitationData), + REF_SETDATATYPE(PrecipitationData)); + + addField("soundProfile", TypeAudioProfilePtr, Offset(soundProfile, PrecipitationData)); + addField("type", TypeS32, Offset(mType, PrecipitationData)); + addField("maxSize", TypeF32, Offset(mMaxSize, PrecipitationData)); + addField("materialList", TypeString, Offset(mMaterialListName,PrecipitationData)); + addField("sizeX", TypeF32, Offset(mSizeX, PrecipitationData)); + addField("sizeY", TypeF32, Offset(mSizeY, PrecipitationData)); + + ///////////////////////// + //JohnA Will remove + // Used to tweak the precipitation + ///////////////////////// + addField("movingBoxPer", TypeF32, Offset(tMoveingBoxPer, PrecipitationData)); + addField("divHeightVal", TypeF32, Offset(tDivHeightVal, PrecipitationData)); + addField("sizeBigBox", TypeF32, Offset(tSizeBigBox, PrecipitationData)); + addField("topBoxSpeed", TypeF32, Offset(tTopBoxSpeed, PrecipitationData)); + addField("frontBoxSpeed", TypeF32, Offset(tFrontBoxSpeed, PrecipitationData)); + addField("topBoxDrawPer", TypeF32, Offset(tTopBoxDrawPer, PrecipitationData)); + addField("bottomDrawHeight", TypeF32, Offset(tBottomDrawHeight, PrecipitationData)); + addField("skipIfPer", TypeF32, Offset(tSkipIfPer, PrecipitationData)); + addField("bottomSpeedPer", TypeF32, Offset(tBottomSpeedPer, PrecipitationData)); + addField("FrontSpeedPer", TypeF32, Offset(tFrontSpeedPer, PrecipitationData)); + addField("FrontRadiusPer", TypeF32, Offset(tFrontRadiusPer, PrecipitationData)); + ///////////////////////// + +} + +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"); + + if (mSizeX <= 0.0f || mSizeX > 20.0f) { + Con::warnf(ConsoleLogEntry::General, "PrecipitationData(%s)::onAdd: sizeX must be in the range [0 >, 20]", getName()); + mSizeX = 1.0; + } + + if (mSizeY <= 0.0f || mSizeY > 20.0f) { + Con::warnf(ConsoleLogEntry::General, "PrecipitationData(%s)::onAdd: sizeY must be in the range [0 >, 20]", getName()); + mSizeY = 1.0; + } + + return true; +} + +void PrecipitationData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if (stream->writeFlag(soundProfile != NULL)) + stream->writeRangedU32(soundProfile->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + stream->write(mType); + stream->write(mMaxSize); + stream->writeString(mMaterialListName); + stream->write(mSizeX); + stream->write(mSizeY); + + ///////////////////////// + //JohnA Will remove + // Used to tweak the precipitation + ///////////////////////// + stream->write(tMoveingBoxPer); + stream->write(tDivHeightVal); + stream->write(tSizeBigBox); + stream->write(tTopBoxSpeed); + stream->write(tFrontBoxSpeed); + stream->write(tTopBoxDrawPer); + stream->write(tBottomDrawHeight); + stream->write(tSkipIfPer); + stream->write(tBottomSpeedPer); + stream->write(tFrontSpeedPer); + stream->write(tFrontRadiusPer); + ///////////////////////// + +} + +void PrecipitationData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + if (stream->readFlag()) + soundProfileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + soundProfileId = 0; + + stream->read(&mType); + stream->read(&mMaxSize); + mMaterialListName = stream->readSTString(); + stream->read(&mSizeX); + stream->read(&mSizeY); + + ///////////////////////// + //JohnA Will remove + // Used to tweak the precipitation + ///////////////////////// + stream->read(&tMoveingBoxPer); + stream->read(&tDivHeightVal); + stream->read(&tSizeBigBox); + stream->read(&tTopBoxSpeed); + stream->read(&tFrontBoxSpeed); + stream->read(&tTopBoxDrawPer); + stream->read(&tBottomDrawHeight); + stream->read(&tSkipIfPer); + stream->read(&tBottomSpeedPer); + stream->read(&tFrontSpeedPer); + stream->read(&tFrontRadiusPer); + ///////////////////////// +} + +//-------------------------------------------------------------------------- +//-------------------------------------- + +Precipitation::Precipitation() +{ + mTypeMask |= ProjectileObjectType; + + mNetFlags.set(ScopeAlways); + mNumDrops = 0; + mCurrentTime = 0; + mAudioHandle = 0; + mPercentage = 1.0f; + mFirstTime = true; + mStormData.lastTime = 0.0f; + mStormData.endPercentage = -1.0f; + mStormData.time = 0.0f; + mStormData.speed = 0.0f; + mStormData.state = done; + mStormData.numDrops = 0.0f; + mStormData.currentTime = 0.0f; + mStormPrecipitationOn = true; + mLastPos.set(0.0f, 0.0f, 0.0f); + mRandomHeight = false; + mAverageSpeed = 0.0f; + mLastHeight = 0.0f; + mShiftPrecip.set(0.0f, 0.0f, 0.0f); + mColorCount = 0; + for(S32 x = 0; x < MAX_NUM_COLOR; ++x) + mColor[x].set(-1.0f, 0.0f, 0.0f); + + mMinVelocity = -1.0f; + mMaxVelocity = -1.0f; + mOffset.set(0.0f, 0.0f, 1.0f); + mOffsetSpeed = 0.25f; + mMaxDrops = -1; + mRadius = -1; +} + +Precipitation::~Precipitation() +{ + +} + +void Precipitation::inspectPostApply() +{ + setMaskBits(InitMask); +} + +//-------------------------------------------------------------------------- +void Precipitation::initPersistFields() +{ + Parent::initPersistFields(); + addField("percentage", TypeF32, Offset(mPercentage, Precipitation)); + addField("color1", TypeColorF, Offset(mColor[0], Precipitation)); + addField("color2", TypeColorF, Offset(mColor[1], Precipitation)); + addField("color3", TypeColorF, Offset(mColor[2], Precipitation)); + addField("offsetSpeed", TypeF32, Offset(mOffsetSpeed, Precipitation)); + addField("minVelocity", TypeF32, Offset(mMinVelocity, Precipitation)); + addField("maxVelocity", TypeF32, Offset(mMaxVelocity, Precipitation)); + addField("maxNumDrops", TypeS32, Offset(mMaxDrops, Precipitation)); + addField("maxRadius", TypeS32, Offset(mRadius, Precipitation)); +} + +void cSetPercentage(SimObject *obj, S32, const char **argv) +{ + Precipitation *ctrl = static_cast(obj); + ctrl->setPercentage(dAtof(argv[2])); +} + +static void cSetupStorm(SimObject *obj, S32, const char **argv) +{ + Precipitation *ctrl = static_cast(obj); + ctrl->setupStorm(dAtof(argv[2]), dAtof(argv[3])); +} + +static void cStormShow(SimObject *obj, S32, const char **argv) +{ + Precipitation *ctrl = static_cast(obj); + ctrl->stormShow(dAtob(argv[2])); +} + +void Precipitation::consoleInit() +{ + Con::addVariable("$pref::precipitationOn", TypeBool, &smPrecipitationOn); + Con::addVariable("$pref::prePause", TypeBool, &smPrecipitationPause); + Con::addCommand("Precipitation", "setPercentage", cSetPercentage, "precipitation.setPercentage(percentage <1.0 to 0.0>)", 3, 3); + Con::addCommand("Precipitation", "stormPrecipitation", cSetupStorm, "precipitation.stormPrecipitation(Percentage <0 to 1>, Time)", 4, 4); + Con::addCommand("Precipitation", "stormShow", cStormShow, "precipitation.stormShow(bool)",3, 3); +} + +//-------------------------------------------------------------------------- +bool Precipitation::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (mPercentage > 1.0f || mPercentage < 0.0f) { + Con::warnf(ConsoleLogEntry::General, "Precipitation::onAdd - Percentage is invalid. <= 1.0 or >= 0.0 "); + mPercentage = 1.0f; + } + + if (isClientObject()) { + mRandomHeight = true; + for (U32 i = 0; i < mMaxDrops; i++) + mFallingObj[i].notValid = true; + + if(mDataBlock->mMaterialListName[0]) + loadDml(); + + if (mDataBlock->soundProfile && smPrecipitationOn) + mAudioHandle = alxPlay(mDataBlock->soundProfile, &getTransform() ); + mNumDrops = mMaxDrops * mPercentage; + mStormData.numDrops = mNumDrops; + setupTexCoords(); + } + else + assignName("Precipitation"); + + mObjBox.min.set(-1e6, -1e6, -1e6); + mObjBox.max.set( 1e6, 1e6, 1e6); + + resetWorldBox(); + addToScene(); + setDefaultValues(); + + return true; +} + +void Precipitation::setDefaultValues() +{ + mColorCount = 0; + for(S32 x = 0; x < MAX_NUM_COLOR; ++x) + if(mColor[x].red >= 0.0f) + mColorCount++; + else + break; + if(mColorCount == 0) + { + if(mDataBlock->mType == 0) + mColor[0].set(0.6, 0.6, 0.6); + else if(mDataBlock->mType == 1) + mColor[0].set(1.0, 1.0, 1.0); + else + mColor[0].set(0.9, 0.8, 0.5); + mColorCount = 1; + } + if(mMinVelocity == -1.0f) + { + if(mDataBlock->mType == 0) + mMinVelocity = 1.25f; + else if(mDataBlock->mType == 1) + mMinVelocity = 0.25f; + else + mMinVelocity = 0.25f; + } + if(mMaxVelocity == -1.0f) + { + if(mDataBlock->mType == 0) + mMaxVelocity = 4.0f; + else if(mDataBlock->mType == 1) + mMaxVelocity = 1.5f; + else + mMaxVelocity = 1.0f; + } + + if(mRadius == -1.0f) + { + if(mDataBlock->mType == 0) + mRadius = 80; + else if(mDataBlock->mType == 1) + mRadius = 125; + else + mRadius = 80; + } + + if(mMaxDrops > MAX_NUM_DROPS || mMaxDrops < 0) + mMaxDrops = MAX_NUM_DROPS; +} + +U32 Precipitation::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + Parent::packUpdate(con, mask, stream); + + if(stream->writeFlag(mask & InitMask)) + { + stream->write(mPercentage); + stream->write(mColorCount); + for(S32 x = 0; x < mColorCount; ++x) + stream->write(mColor[x]); + stream->write(mOffsetSpeed); + stream->write(mMinVelocity); + stream->write(mMaxVelocity); + stream->write(mMaxDrops); + stream->write(mRadius); + + if(stream->writeFlag((mStormData.currentTime / 32.0f) < mStormData.time)) + { + stream->write(mStormData.currentTime); + stream->write(mStormData.endPercentage); + stream->write(mStormData.time); + } + } + + if(stream->writeFlag(mask & StormShowMask)) + stream->write(mStormPrecipitationOn); + + if(stream->writeFlag(mask & StormMask)) + { + stream->write(mStormData.endPercentage); + stream->write(mStormData.time); + mStormData.currentTime = 0.0f; + } + + if(stream->writeFlag(mask & PercentageMask)) + stream->write(mPercentage); + + return 0; +} + +void Precipitation::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if(stream->readFlag()) + { + stream->read(&mPercentage); + stream->read(&mColorCount); + for(S32 x = 0; x < mColorCount; ++x) + stream->read(&mColor[x]); + stream->read(&mOffsetSpeed); + stream->read(&mMinVelocity); + stream->read(&mMaxVelocity); + stream->read(&mMaxDrops); + stream->read(&mRadius); + + if(stream->readFlag()) + { + stream->read(&mStormData.currentTime); + stream->read(&mStormData.endPercentage); + stream->read(&mStormData.time); + + F32 percentage = mStormData.endPercentage; + + mStormData.speed = (percentage - mPercentage) / (mStormData.time * 32.0f); + mStormData.endPercentage = percentage; + mStormData.state = (mPercentage > percentage) ? out : in; + mPercentage += (mStormData.state == in) ? mStormData.speed * mStormData.currentTime : + -mStormData.speed * mStormData.currentTime; + mStormPrecipitationOn = true; + + } + } + if(stream->readFlag()) + { + stream->read(&mStormPrecipitationOn); + if(!mStormPrecipitationOn) + mPercentage = 0.0f; + } + + if(stream->readFlag()) + { + stream->read(&mStormData.endPercentage); + stream->read(&mStormData.time); + + if(mStormData.time) + startStorm(); + } + + if(stream->readFlag()) + { + stream->read(&mPercentage); + mNumDrops = mMaxDrops * mPercentage; + } +} + +void Precipitation::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +bool Precipitation::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + setDefaultValues(); + + mAverageSpeed = ((mMinVelocity + mMaxVelocity) / 2.0f) * 0.65f; + scriptOnNewDataBlock(); + return true; +} + +void Precipitation::loadDml() +{ + S32 x; + char dmlName[256]; + mNumTextures = 0; + dStrcpy(dmlName, DML_DIR); + dStrcat(dmlName, mDataBlock->mMaterialListName); + Stream *stream = ResourceManager->openStream(dmlName); + if(stream) + { + mMaterialList.read(*stream); + ResourceManager->closeStream(stream); + mMaterialList.load(); + for(x = 0; x < mMaterialList.size(); ++x, ++mNumTextures) + mTextures[x] = mMaterialList.getMaterial(x); + } +} + +//-------------------------------------------------------------------------- +bool Precipitation::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (!smPrecipitationOn) + { + if (mAudioHandle) + { + alxStop(mAudioHandle); + mAudioHandle = 0; + } + return false; + } + else + { + if(!mStormPrecipitationOn) + return false; + if (!mAudioHandle && mDataBlock->soundProfile) + mAudioHandle = alxPlay(mDataBlock->soundProfile, &getTransform()); + } + + 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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::EndSort; + state->insertRenderImage(image); + } + + return false; +} + +void Precipitation::renderObject(SceneState* state, SceneRenderImage*) +{ + if(smPrecipitationOn) + { + if(!mStormPrecipitationOn) + return; + } + else + return; + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + state->mModelview.getRow(0,&mRotX); + state->mModelview.getRow(2,&mRotZ); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + if(mStormData.state != done) + updateStorm(); + if(mDataBlock->mType == 0 || mDataBlock->mType == 1) + renderPrecip(state->getCameraPosition()); + else if(mDataBlock->mType == 2) + renderSand(); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + mFirstTime = false; + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +void Precipitation::advanceTime(F32 dt) +{ + if(!mFirstTime) + mCurrentTime += dt; +} + +void Precipitation::renderPrecip(Point3F camPos) +{ + Point3F point[4]; + F32 coordX, coordY; + F32 xRadius, yRadius; + mRotX *= mDataBlock->mSizeX; + mRotZ *= mDataBlock->mSizeY; + Point2F value; + F32 renderPer = 1.0f, minSpeed = 0.0f; + F32 theRadius, curZVelocity = 0.0f; + F32 height, speed, radVal; + F32 random1, random2, random3; + mOffsetVal.set(0.0f, 0.0f); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glEnable(GL_ALPHA_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glAlphaFunc(GL_GREATER, 0.1f); + glBindTexture(GL_TEXTURE_2D,mTextures[0].getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + calcVelocityBox(camPos, curZVelocity, renderPer); + if(curZVelocity < 0.0f) + { + if(mDataBlock->mType == 0) + minSpeed = ((mCurrentTime - mLastRenderTime) / (1.0f / curZVelocity)) - mMinVelocity; + } + glBegin(GL_QUADS); + { + for(S32 x=0; x < mNumDrops; ++x) + { + if(mStormData.numDrops > x || mFallingObj[x].stillFalling) + { + speed = mFallingObj[x].speed; + if(smPrecipitationPause) + speed = 0; + else if(minSpeed < 0.0f) + speed = (mFallingObj[x].speed > minSpeed) ? minSpeed : mFallingObj[x].speed; + + mFallingObj[x].curPos.set((mFallingObj[x].curPos.x + mShiftPrecip.x) + (mFallingObj[x].offset.x * speed), + (mFallingObj[x].curPos.y + mShiftPrecip.y) + (mFallingObj[x].offset.y * speed), + (mFallingObj[x].curPos.z + mShiftPrecip.z) + speed); + + if(mFallingObj[x].notValid || !mBox.isContained(mFallingObj[x].curPos)) + { + if(mStormData.numDrops > x) + { + xRadius = mRadius * ((mOffsetVal.x > 0.0f) ? 1.0f - mOffsetVal.x : 1.0f + mOffsetVal.x); + yRadius = mRadius * ((mOffsetVal.y > 0.0f) ? 1.0f - mOffsetVal.y : 1.0f + mOffsetVal.y); + + theRadius = (xRadius > yRadius) ? yRadius : xRadius; + + random1 = Platform::getRandom(); + random2 = Platform::getRandom(); + random3 = Platform::getRandom(); + + value.set( Platform::getRandom() * 2.0f - 1.0f, Platform::getRandom() * 2.0f - 1.0f); + value.normalize(); + + mFallingObj[x].speed = -mMinVelocity + (-(mMaxVelocity - mMinVelocity) * random3); + if(random1 > (1.0f - renderPer) || (Platform::getRandom() < mDataBlock->tTopBoxDrawPer && renderPer < - 0.4)) + { + //Start Drop on top of box + if(renderPer < -0.4) + mFallingObj[x].speed *= mDataBlock->tFrontSpeedPer * 2.5f; + if(mRandomHeight) + height = (mBox.max.z - 5.0f) * (Platform::getRandom() * 2.0f - 1.0f);// + Platform::getRandom() * 30; + else + height = mBox.max.z - 5.0f; + mFallingObj[x].dropType = 1; + radVal = theRadius * mClampF(((random2 < 0.2f) ? random3 : random2), + 0.01f, 1.0f); + } + else if(random1 > (0.0f - renderPer)) + { + //Start Drop on side of box + height = random3 * mBox.max.z; + radVal = theRadius * mDataBlock->tFrontRadiusPer; + mFallingObj[x].speed *= mDataBlock->tFrontSpeedPer; + mFallingObj[x].dropType = 2; + } + else + { + //Start Drop on bottom of box + height = mBox.min.z + mDataBlock->tBottomDrawHeight; + mFallingObj[x].dropType = 3; + radVal = theRadius * random3; + mFallingObj[x].speed *= mDataBlock->tBottomSpeedPer; + } + value *= radVal; + mFallingObj[x].colorIndex = (S32)(mCeil(mColorCount * random2) - 1.0f); + mFallingObj[x].startPos.set(camPos.x + mNewCenter.x + value.x, camPos.y + mNewCenter.y + value.y, height); + mFallingObj[x].curPos = mFallingObj[x].startPos; + mFallingObj[x].startDiff = mFallingObj[x].startPos.z - camPos.z + 20; + mFallingObj[x].offset = mOffset; + mFallingObj[x].texIndex = (S32)(mCeil((NUM_TEXTURES - 1) * getRandomVal()) - 1.0f); + mFallingObj[x].stillFalling = true; + mFallingObj[x].notValid = false; + } + else + { + mFallingObj[x].stillFalling = false; + } + } + + if(mFallingObj[x].stillFalling)// && mBox.isContained(mFallingObj[x].curPos)) + { + coordX = mTexCoord[mFallingObj[x].texIndex].x; + coordY = mTexCoord[mFallingObj[x].texIndex].y; + + point[1] = mFallingObj[x].curPos + mRotX - mRotZ; + point[2] = mFallingObj[x].curPos - mRotX - mRotZ; + if( mDataBlock->mType == 1 ) + { + point[0] = mFallingObj[x].curPos + mRotX + mRotZ; + point[3] = mFallingObj[x].curPos - mRotX + mRotZ; + } + else + { + point[0].set(point[1].x + (mFallingObj[x].offset.x * mDataBlock->mSizeY * 2), point[1].y + (mFallingObj[x].offset.y * mDataBlock->mSizeY * 2), point[1].z + (mFallingObj[x].offset.z * mDataBlock->mSizeY * 2)); + point[3].set(point[2].x + (mFallingObj[x].offset.x * mDataBlock->mSizeY * 2), point[2].y + (mFallingObj[x].offset.y * mDataBlock->mSizeY * 2), point[2].z + (mFallingObj[x].offset.z * mDataBlock->mSizeY * 2)); + } + + if(mFallingObj[x].dropType == 1) + { + F32 alphaVal = 1.0f - ((mFallingObj[x].curPos.z - camPos.z) / mFallingObj[x].startDiff); + glColor4f(mColor[mFallingObj[x].colorIndex].red, + mColor[mFallingObj[x].colorIndex].green, + mColor[mFallingObj[x].colorIndex].blue, + alphaVal); + } + else + glColor4f(mColor[mFallingObj[x].colorIndex].red, + mColor[mFallingObj[x].colorIndex].green, + mColor[mFallingObj[x].colorIndex].blue, + 1.0f); + + glTexCoord2f(coordX, coordY); + glVertex3f(point[0].x, point[0].y, point[0].z); + glTexCoord2f(coordX, coordY + 0.25); + glVertex3f(point[1].x, point[1].y, point[1].z); + glTexCoord2f(coordX + 0.25, coordY + 0.25); + glVertex3f(point[2].x, point[2].y, point[2].z); + glTexCoord2f(coordX + 0.25, coordY); + glVertex3f(point[3].x, point[3].y, point[3].z); + } + } + } + } + glEnd(); + glDisable(GL_TEXTURE_2D); + glDisable(GL_ALPHA_TEST); + glDisable(GL_BLEND); + mLastRenderTime = mCurrentTime; + mRandomHeight = false; +} + +void Precipitation::renderSand() +{ + Point3F objPos; + F32 dt, modVal; + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + for(S32 x=0; x < mNumDrops; ++x) + { + if(mStormData.numDrops > x || mFallingObj[x].stillFalling) + { + mFallingObj[x].offset.x += mOffset.x; + mFallingObj[x].offset.y += mOffset.y; + if(mFallingObj[x].endTime < mCurrentTime) + { + if(mStormData.numDrops > x) + { + modVal = mFmod(mCurrentTime - mFallingObj[x].endTime, mFallingObj[x].endTime - mFallingObj[x].startTime); + mFallingObj[x].startTime = mCurrentTime - modVal; + mFallingObj[x].endTime = (mCurrentTime + ((mMinVelocity * Platform::getRandom())+mMaxVelocity)) - modVal; + mFallingObj[x].size = (mDataBlock->mMaxSize * Platform::getRandom()); + mFallingObj[x].offset.set(0.0, 0.0, 0.0); + mFallingObj[x].colorIndex = (S32)(mCeil(mColorCount * Platform::getRandom()) - 1.0f); + mFallingObj[x].stillFalling = true; + } + else + mFallingObj[x].stillFalling = false; + } + dt = (mFallingObj[x].endTime - mCurrentTime) / + (mFallingObj[x].endTime - mFallingObj[x].startTime); + + glPointSize(mFallingObj[x].size); + glColor4f(mColor[mFallingObj[x].colorIndex].red, mColor[mFallingObj[x].colorIndex].green, mColor[mFallingObj[x].colorIndex].blue, 1.0f - dt); + objPos = Point3F((mFallingObj[x].startPos.x * mRadius)+mFallingObj[x].offset.x, + (mFallingObj[x].startPos.y * mRadius)+mFallingObj[x].offset.y, + (mFallingObj[x].startPos.z * (dt * mRadius)) - mRadius); + glBegin(GL_POINTS); + glVertex3f(objPos.x, objPos.y, objPos.z); + glEnd(); + } + } + glDisable(GL_BLEND); +} + +void Precipitation::calcVelocityBox(Point3F camPos, F32 &curZVelocity, F32 &renderPer) +{ + GameConnection * con = GameConnection::getServerConnection(); + if(!con) + return; + + Sky* pSky = mSceneManager->getCurrentSky(); + mOffset.set(0.0f, 0.0f, 1.0f); + if (pSky) + if(pSky->mEffectPrecip) + mOffset.set(pSky->mWindDir.x * mOffsetSpeed, pSky->mWindDir.y * mOffsetSpeed, 1.0f); + + F32 radZ, speed; + F32 randomVal, bottomHeight; + Player *player = NULL; + + mNewCenter.set(mOffset.x * mRadius, mOffset.y * mRadius, 0.0f); + mShiftPrecip.set(0.0f, 0.0f, 0.0f); + if((camPos - mLastPos).len() > mRadius / 2.0f) + mShiftPrecip = camPos - mLastPos; + + mLastPos = camPos; + bottomHeight = (camPos.z - (mRadius / mDataBlock->tDivHeightVal)); + + ShapeBase * conObj = con->getControlObject(); + if(conObj->mWaterCoverage) + bottomHeight = conObj->mLiquidHeight + mDataBlock->mSizeY; + + player = dynamic_cast(conObj); + if(!player) + { + mBox.min.set(camPos.x - mRadius, camPos.y - mRadius, bottomHeight); + mBox.max.set(camPos.x + mRadius, camPos.y + mRadius, camPos.z + (mRadius / mDataBlock->tDivHeightVal)); + return; + } + Point3F curVel(player->getVelocity()); + F32 percentage = 0.0f; + F32 movePer = mDataBlock->tMoveingBoxPer; + + //Calc the offset of the inner box along x and y + if(curVel.x) + { + percentage = curVel.x / 40.0f; + mOffsetVal.x = (curVel.x > 0.0f) ? + (percentage > movePer) ? movePer : percentage : + (percentage < -movePer) ? -movePer : percentage; + } + if(curVel.y) + { + percentage = curVel.y / 40.0f; + mOffsetVal.y = (curVel.y > 0.0f) ? + (percentage > movePer) ? movePer : percentage : + (percentage < -movePer) ? -movePer : percentage; + } + + curZVelocity = curVel.z; + speed = Point2F(curVel.x, curVel.y).len(); + radZ = mRadius; + if(speed > 1.0f) + { + F32 val = 40.0f / speed; + radZ *= (val > 1.0f) ? 1.0f : (val < MIN_ZRADIUS) ? MIN_ZRADIUS : val; + } + //Move the box up slow... Helps keep snow around the player... + F32 topHeight = (radZ / mDataBlock->tDivHeightVal); + if(topHeight > mLastHeight && (topHeight - mLastHeight) > 5.0f) + topHeight += 5.0f; + mLastHeight = topHeight; + + if(mDataBlock->tSizeBigBox) + { + mBox.min.set(camPos.x - (mRadius - (mRadius * mOffsetVal.x)), + camPos.y - (mRadius - (mRadius * mOffsetVal.y)), bottomHeight); + mBox.max.set(camPos.x + (mRadius + (mRadius * mOffsetVal.x)), + camPos.y + (mRadius + (mRadius * mOffsetVal.y)), camPos.z + topHeight); + } + else + { + mBox.min.set(camPos.x - mRadius, camPos.y - mRadius, camPos.z - (mRadius / mDataBlock->tDivHeightVal)); + mBox.max.set(camPos.x + mRadius, camPos.y + mRadius, camPos.z + (mRadius / mDataBlock->tDivHeightVal)); + } + + speed = Point3F(curVel.x, curVel.y, curVel.z).len(); + randomVal = Platform::getRandom(); + if(speed < mDataBlock->tTopBoxSpeed) + renderPer = 1.0f; + else if ( speed < mDataBlock->tFrontBoxSpeed ) + { + if(randomVal < 0.25) + renderPer = 1.0f; + else + renderPer = (curVel.z / speed) + (1.0f - ((speed - mDataBlock->tTopBoxSpeed)/ (mDataBlock->tFrontBoxSpeed - mDataBlock->tTopBoxSpeed))); + } + else + { + if(randomVal < 0.25) + renderPer = 1.0f; + else + renderPer = curVel.z / speed; + } + + if(speed) + curVel.normalize(); + else + curVel.set(0.0f, 0.0f, 0.0f); + + mNewCenter.set(curVel.x * mAbs(mOffsetVal.x * mRadius), curVel.y * mAbs(mOffsetVal.y * mRadius), curVel.z * mRadius); + + if(mAverageSpeed && mOffset.z && (mOffset.x || mOffset.y)) + mNewCenter.set(mNewCenter.x + ((mRadius / (mAverageSpeed * mOffset.z)) * mOffset.x), + mNewCenter.y + ((mRadius / (mAverageSpeed * mOffset.z)) * mOffset.y), + 0.0f); +} + +void Precipitation::setPercentage(F32 newPer) +{ + if(newPer <= 1.0f && newPer >= 0.0f) + { + mPercentage = newPer; + mNumDrops = mMaxDrops * mPercentage; + setMaskBits(PercentageMask); + } +} + +void Precipitation::stormShow(bool show) +{ + mStormPrecipitationOn = show; + setMaskBits(StormShowMask); +} + +void Precipitation::setupStorm(F32 percentage, F32 time) +{ + mStormData.time = time; + if(mStormData.endPercentage >= 0.0f) + { + mStormData.state = (mStormData.endPercentage > percentage) ? out : in; + mPercentage = mStormData.endPercentage; + } + else + mStormData.state = (mPercentage > percentage) ? out : in; + mStormData.endPercentage = percentage; + + setMaskBits(StormMask); +} + +void Precipitation::startStorm() +{ + F32 percentage = mStormData.endPercentage; + + mStormData.speed = (percentage - mPercentage) / (mStormData.time * 32.0f); + mStormData.endPercentage = percentage; + mStormData.state = (mPercentage > percentage) ? out : in; + mStormPrecipitationOn = true; +} + +void Precipitation::updateStorm() +{ + F32 offset = 1.0f; + U32 currentTime = Sim::getCurrentTime(); + if(mStormData.lastTime != 0) + offset = (currentTime - mStormData.lastTime) / 32.0f; + + mStormData.lastTime = currentTime; + + mPercentage += (mStormData.speed * offset); + if((mStormData.state == in && mPercentage >= mStormData.endPercentage) || + (mStormData.state == out && mPercentage <= mStormData.endPercentage)) + { + mPercentage = mStormData.endPercentage; + mStormData.state = done; + mStormData.lastTime = 0.0f; + } + mStormData.numDrops = mMaxDrops * mPercentage; +} + +void Precipitation::processTick(const Move* move) +{ + Parent::processTick(move); + mStormData.currentTime++; +} + +void Precipitation::setupTexCoords() +{ + S32 x, y, numTimes = NUM_TEXTURES / 4; + + for(y = 0; y < numTimes ; ++y) + for(x = 0; x < numTimes ; ++x) + mTexCoord[y * numTimes + x].set(x * 0.25f, y * 0.25f); +} + +F32 Precipitation::getRandomVal() +{ + F32 randVal = Platform::getRandom(); + if(randVal > 0.99995f && randVal < 0.99999f) + randVal = 1.01f; + return randVal; +} diff --git a/game/precipitation.h b/game/precipitation.h new file mode 100644 index 0000000..4874aad --- /dev/null +++ b/game/precipitation.h @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_precipitation +#define _H_precipitation + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif + +#define MAX_NUM_DROPS 2000 +#define MAX_NUM_COLOR 3 +#define NUM_TEXTURES 16 +#define MIN_ZRADIUS 0.01f + +class AudioProfile; + +//-------------------------------------------------------------------------- +class PrecipitationData : public GameBaseData { + typedef GameBaseData Parent; + + public: + AudioProfile* soundProfile; + S32 soundProfileId; + S32 mType; + F32 mMaxSize; + F32 mSizeX; + F32 mSizeY; + StringTableEntry mMaterialListName; + + ///////////////////////// + //JohnA Will remove + // Used to tweak the precipitation + ///////////////////////// + F32 tMoveingBoxPer; + F32 tDivHeightVal; + F32 tSizeBigBox; + F32 tTopBoxSpeed; + F32 tFrontBoxSpeed; + F32 tTopBoxDrawPer; + F32 tBottomDrawHeight; + F32 tSkipIfPer; + F32 tBottomSpeedPer; + F32 tFrontSpeedPer; + F32 tFrontRadiusPer; + ///////////////////////// + + PrecipitationData(); + DECLARE_CONOBJECT(PrecipitationData); + bool onAdd(); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +typedef struct +{ + F32 speed; + Point3F curPos; + F32 startTime; + F32 endTime; + Point3F startPos; + Point3F sPosOffset; + F32 size; + Point3F offset; + S32 colorIndex; + S32 texIndex; + ColorF colorOffset; + F32 rotation; + bool stillFalling; + F32 startDiff; + S32 dropType; + bool notValid; +}FallingInfo; + +//-------------------------------------------------------------------------- +enum State +{ + done = 0, + in = 1, + out = 2 +}; + +typedef struct +{ + F32 lastTime; + F32 endPercentage; + F32 time; + F32 speed; + S32 numDrops; + State state; + F32 currentTime; +}PrecipStormData; + +class Precipitation : public GameBase +{ + private: + typedef GameBase Parent; + PrecipitationData* mDataBlock; + + FallingInfo mFallingObj[MAX_NUM_DROPS]; + Point2F mTexCoord[NUM_TEXTURES]; + F32 mCurrentTime; + F32 mLastRenderTime; + Point3F mRotX, mRotZ; + Point2F mOffsetVal; + Point3F mNewCenter; + Point3F mLastPos; + bool mRandomHeight; + TextureHandle mTextures[20]; + MaterialList mMaterialList; + S32 mNumTextures; + bool mFirstTime; + AUDIOHANDLE mAudioHandle; + F32 mPercentage; + S32 mNumDrops; + PrecipStormData mStormData; + bool mStormPrecipitationOn; + Box3F mBox; + F32 mAverageSpeed; + F32 mLastHeight; + Point3F mOffset; + Point3F mShiftPrecip; + S32 mColorCount; + ColorF mColor[MAX_NUM_COLOR]; + F32 mMinVelocity; + F32 mMaxVelocity; + S32 mMaxDrops; + S32 mRadius; + F32 mOffsetSpeed; + + void processTick(const Move*); + protected: + bool onAdd(); + void onRemove(); + + void advanceTime(F32 dt); + + // Rendering + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + void renderPrecip(Point3F camPos); + void renderSand(); + void calcVelocityBox(Point3F camPos, F32 &curZVelocity, F32 &renderPer); + void loadDml(); + void setupTexCoords(); + F32 getRandomVal(); + public: + enum NetMaskBits { + InitMask = BIT(0), + PercentageMask = BIT(1), + StormMask = BIT(2), + StormShowMask = BIT(3) + }; + + Precipitation(); + ~Precipitation(); + void inspectPostApply(); + void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0); + void setPercentage(F32); + + bool onNewDataBlock(GameBaseData* dptr); + DECLARE_CONOBJECT(Precipitation); + static void initPersistFields(); + static void consoleInit(); + + static bool smPrecipitationOn; + static bool smPrecipitationPause; + + void setDefaultValues(); + void stormShow(bool show); + void setupStorm(F32 percentage, F32 time); + void startStorm(); + void updateStorm(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_precipitation + diff --git a/game/projBomb.cc b/game/projBomb.cc new file mode 100644 index 0000000..9131cd2 --- /dev/null +++ b/game/projBomb.cc @@ -0,0 +1,405 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projBomb.h" +#include "Core/bitStream.h" +#include "Sim/netConnection.h" +#include "game/shapeBase.h" +#include "Math/mathIO.h" +#include "console/consoleTypes.h" +#include "game/explosion.h" +#include "terrain/waterBlock.h" +#include "game/splash.h" +#include "game/particleEngine.h" +#include "dgl/dgl.h" +#include "sceneGraph/sceneState.h" +#include "Math/mathUtils.h" + +IMPLEMENT_CO_NETOBJECT_V1(BombProjectile); +IMPLEMENT_CO_DATABLOCK_V1(BombProjectileData); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +BombProjectileData::BombProjectileData() +{ + dMemset( textureName, 0, sizeof( textureName ) ); + dMemset( textureHandle, 0, sizeof( textureHandle ) ); + + minRotSpeed.set( 0.0, 0.0, 0.0 ); + maxRotSpeed.set( 0.0, 0.0, 0.0 ); +} + +//-------------------------------------------------------------------------- +BombProjectileData::~BombProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void BombProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("texture", TypeString, Offset( textureName, BombProjectileData), NUM_TEX); + addField("minRotSpeed", TypePoint3F, Offset( minRotSpeed, BombProjectileData)); + addField("maxRotSpeed", TypePoint3F, Offset( maxRotSpeed, BombProjectileData)); +} + + +//-------------------------------------------------------------------------- +// Preload data - load resources +//-------------------------------------------------------------------------- +bool BombProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (!server) { + U32 i; + for( i=0; iwrite(minRotSpeed.x); + stream->write(minRotSpeed.y); + stream->write(minRotSpeed.z); + stream->write(maxRotSpeed.x); + stream->write(maxRotSpeed.y); + stream->write(maxRotSpeed.z); + + U32 i; + for( i=0; iwriteString(textureName[i]); + } + +} + +//-------------------------------------------------------------------------- +void BombProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&minRotSpeed.x); + stream->read(&minRotSpeed.y); + stream->read(&minRotSpeed.z); + stream->read(&maxRotSpeed.x); + stream->read(&maxRotSpeed.y); + stream->read(&maxRotSpeed.z); + + for( U32 i=0; ireadSTString(); + } + +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +BombProjectile::BombProjectile() +{ + +} + +BombProjectile::~BombProjectile() +{ + // +} + +//-------------------------------------------------------------------------- +void BombProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void BombProjectile::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +bool BombProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mSourceIdTimeoutTicks = 1000; + + if( isClientObject() ) + { + mRotSpeed.x = gRandGen.randF( mDataBlock->minRotSpeed.x, mDataBlock->maxRotSpeed.x ); + mRotSpeed.y = gRandGen.randF( mDataBlock->minRotSpeed.y, mDataBlock->maxRotSpeed.y ); + mRotSpeed.z = gRandGen.randF( mDataBlock->minRotSpeed.z, mDataBlock->maxRotSpeed.z ); + + if( bool( mSourceObject ) ) + { + setTransform( mSourceObject->getTransform() ); + } + + } + + mObjBox = Box3F( Point3F( -1, -1, -1 ), Point3F( 1.0, 1.0, 1.0 ) ); + resetWorldBox(); + + return true; +} + + +//-------------------------------------------------------------------------- +void BombProjectile::onRemove() +{ + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +bool BombProjectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +// Process Tick +//-------------------------------------------------------------------------- +void BombProjectile::processTick(const Move* move) +{ + F32 timeLeft; + RayInfo rInfo; + Point3F oldPosition; + Point3F newPosition; + + Parent::Parent::processTick(move); + + if (isServerObject()) { + // server + if (mDeleteTick != -1 && mCurrTick >= mDeleteTick) { + deleteObject(); + return; + } else if (mHidden == true) { + // already exploded, back out + return; + } + + // Otherwise, we have to do some simulation work. + oldPosition = mCurrPosition; + computeNewState(&newPosition, &mCurrVelocity); + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->disableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->disableCollision(); + + timeLeft = 1.0; + + if (getContainer()->castRay(oldPosition, newPosition, + csmDynamicCollisionMask | csmStaticCollisionMask, + &rInfo) == true) + { + // explode + MatrixF xform(true); + xform.setColumn(3, rInfo.point); + setTransform(xform); + mCurrPosition = rInfo.point; + mCurrVelocity = Point3F(0, 0, 0); + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->enableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->enableCollision(); + + explode(rInfo.point, rInfo.normal); + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->disableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->disableCollision(); + + mDeleteTick = mCurrTick + DeleteWaitTicks; + + } + else + { + // No problems, just set the end position, and we're golden. + mCurrPosition = newPosition; + MatrixF xform(true); + xform.setColumn(3, mCurrPosition); + setTransform(xform); + } + + if( !mSentInitialUpdate && determineSplash( oldPosition, newPosition ) ) + { + mQuickSplash = true; + } + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->enableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->enableCollision(); + } + else + { + // client + if (mHidden == true) + return; + + // Otherwise, we have to do some simulation work. + oldPosition = mCurrPosition; + computeNewState(&newPosition, &mCurrVelocity); + + if (bool(mSourceObject)) + mSourceObject->disableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->disableCollision(); + + timeLeft = 1.0; + + if (getContainer()->castRay(oldPosition, newPosition, + csmDynamicCollisionMask | csmStaticCollisionMask, + &rInfo) == true) + { + // explode + MatrixF xform(true); + xform.setColumn(3, rInfo.point); + setTransform(xform); + mCurrPosition = rInfo.point; + mCurrVelocity = Point3F(0, 0, 0); + + explode(rInfo.point, rInfo.normal, rInfo.object->getType() ); + + } + else + { + // No problems, just set the end position, and we're golden. + mCurrDeltaBase = newPosition; + mCurrBackVector = mCurrPosition - newPosition; + + if( !mInWater ) + { + emitParticles(mCurrPosition, newPosition, mCurrVelocity, TickMs); + } + + mCurrPosition = newPosition; + } + + if( determineSplash( oldPosition, newPosition ) ) + { + createSplash( mSplashPos ); + } + + if (bool(mSourceObject)) + mSourceObject->enableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->enableCollision(); + } +} + + +//-------------------------------------------------------------------------- +// Advance time +//-------------------------------------------------------------------------- +void BombProjectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + rotate( dt ); +} + +//---------------------------------------------------------------------------- +// Rotate bomb +//---------------------------------------------------------------------------- +void BombProjectile::rotate( F32 dt ) +{ + MatrixF curTrans = getRenderTransform(); + curTrans.setPosition( Point3F(0.0, 0.0, 0.0) ); + + Point3F curAngles = mRotSpeed * dt * M_PI/180.0; + MatrixF rotMatrix( EulerF( curAngles.x, curAngles.y, curAngles.z ) ); + + curTrans.mul( rotMatrix ); + curTrans.setPosition( getRenderPosition() ); + setRenderTransform( curTrans ); +} + +//-------------------------------------------------------------------------- +// Prep render +//-------------------------------------------------------------------------- +bool BombProjectile::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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + + state->insertRenderImage(image); + } + + return false; +} + + +//-------------------------------------------------------------------------- +// Render energy bolt +//-------------------------------------------------------------------------- +void BombProjectile::renderObject(SceneState* state, SceneRenderImage *sri ) +{ + if( mHidden ) + return; + + if( mProjectileShape ) + { + Parent::renderObject( state, sri ); + return; + } +} + + diff --git a/game/projBomb.h b/game/projBomb.h new file mode 100644 index 0000000..98a0041 --- /dev/null +++ b/game/projBomb.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROJBOMB_H_ +#define _PROJBOMB_H_ + +#ifndef _PROJGRENADE_H_ +#include "game/projGrenade.h" +#endif + +//-------------------------------------------------------------------------- +// Bomb Projectile Data +//-------------------------------------------------------------------------- +class BombProjectileData : public GrenadeProjectileData +{ + typedef GrenadeProjectileData Parent; + + enum Constants + { + NUM_TEX = 2, + }; + + protected: + bool onAdd(); + + public: + + StringTableEntry textureName[NUM_TEX]; + TextureHandle textureHandle[NUM_TEX]; + VectorF minRotSpeed; + VectorF maxRotSpeed; + + + public: + BombProjectileData(); + ~BombProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + + + DECLARE_CONOBJECT(BombProjectileData); + static void initPersistFields(); + bool preload(bool server, char errorBuffer[256]); +}; + +//-------------------------------------------------------------------------- +// Bomb Projectile +//-------------------------------------------------------------------------- +class BombProjectile : public GrenadeProjectile +{ + typedef GrenadeProjectile Parent; + + private: + BombProjectileData* mDataBlock; + VectorF mRotSpeed; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + void processTick(const Move*); + void advanceTime(F32); + void renderObject(SceneState*, SceneRenderImage*); +// void renderProjectile( const Point3F &camPos ); +// void renderCrossSection( const Point3F &camPos ); + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void rotate( F32 dt ); + + public: + BombProjectile(); + ~BombProjectile(); + + static void initPersistFields(); + static void consoleInit(); + +// U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); +// void unpackUpdate(NetConnection*, BitStream* stream); + + DECLARE_CONOBJECT(BombProjectile); +}; + +#endif // _H_PROJBOMB + diff --git a/game/projELF.cc b/game/projELF.cc new file mode 100644 index 0000000..f35cadd --- /dev/null +++ b/game/projELF.cc @@ -0,0 +1,1011 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projELF.h" +#include "core/bitStream.h" +#include "platformWIN32/platformGL.h" +#include "dgl/dgl.h" +#include "scenegraph/sceneState.h" +#include "console/consoleTypes.h" +#include "game/shapeBase.h" +#include "game/gameConnection.h" +#include "math/mRandom.h" +#include "math/mQuadPatch.h" +#include "dgl/splineUtil.h" +#include "game/particleEngine.h" + +#define LIGHTNING_FREQ 30.0 + + +IMPLEMENT_CO_DATABLOCK_V1(ELFProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(ELFProjectile); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ELFProjectileData::ELFProjectileData() +{ + beamRange = 10.0f; + beamHitWidth = 30; + mainBeamWidth = 0.2; + mainBeamSpeed = 9.0; + mainBeamRepeat = 0.25; + lightningWidth = 0.15; + lightningDist = 0.15; + + textureNames[0] = ""; + textureNames[1] = ""; + textureNames[2] = ""; + + dMemset( emitterList, 0, sizeof( emitterList ) ); + dMemset( emitterIDList, 0, sizeof( emitterIDList ) ); +} + +ELFProjectileData::~ELFProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void ELFProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("beamRange", TypeF32, Offset(beamRange, ELFProjectileData)); + addField("beamHitWidth", TypeF32, Offset(beamHitWidth, ELFProjectileData)); + addField("textures", TypeString, Offset(textureNames, ELFProjectileData), ET_NUM_TEX); + addField("mainBeamWidth", TypeF32, Offset(mainBeamWidth, ELFProjectileData)); + addField("mainBeamSpeed", TypeF32, Offset(mainBeamSpeed, ELFProjectileData)); + addField("mainBeamRepeat", TypeF32, Offset(mainBeamRepeat, ELFProjectileData)); + addField("lightningWidth", TypeF32, Offset(lightningWidth, ELFProjectileData)); + addField("lightningDist", TypeF32, Offset(lightningDist, ELFProjectileData)); + addField("emitter", TypeParticleEmitterDataPtr, Offset(emitterList, ELFProjectileData), ELF_NUM_EMITTERS ); + +} + + +//-------------------------------------------------------------------------- +bool ELFProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (beamRange < 2.0) { + Con::warnf(ConsoleLogEntry::General, "ELFProjectileData(%s)::onAdd: beamRange must be >= 2", getName()); + beamRange = 2.0; + } + if (beamHitWidth < 0.0 || beamHitWidth > 90) { + Con::warnf(ConsoleLogEntry::General, "ELFProjectileData(%s)::onAdd: beamHitWidth must be in range [0, 90]", getName()); + beamHitWidth = beamHitWidth < 0 ? 0 : 90.0; + } + + cosBeamHitWidth = mCos(mDegToRad(beamHitWidth)); + + U32 i; + for( i=0; i beamRange) { + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + + return false; + } + + *outputVectorMin = targetPos - sourcePos; + *outputMinTime = 0.0; + outputVectorMin->normalize(); + *outputVectorMax = *outputVectorMin; + *outputMaxTime = *outputMinTime; + + return true; +} + + +//-------------------------------------------------------------------------- +void ELFProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(beamRange); + stream->write(mainBeamWidth); + stream->write(mainBeamSpeed); + stream->write(mainBeamRepeat); + stream->write(lightningWidth); + stream->write(lightningDist); + + S32 i; + for( i=0; iwriteString( textureNames[i] ); + } + + for( i=0; iwriteFlag( emitterList[i] != NULL ) ) + { + stream->writeRangedU32( emitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } +} + +void ELFProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&beamRange); + stream->read(&mainBeamWidth); + stream->read(&mainBeamSpeed); + stream->read(&mainBeamRepeat); + stream->read(&lightningWidth); + stream->read(&lightningDist); + + + U32 i; + for( i=0; ireadSTString(); + } + + for( i=0; ireadFlag() ) + { + emitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ELFProjectile::ELFProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable|ScopeAlways); + + mElapsedTime = 0.0; + mBoltTime = 0.0; + mNewBolt = true; + mWetFireHandle = 0; + mFireHandle = 0; + mEmittingParticles = false; + + dMemset( mEmitterList, 0, sizeof( mEmitterList ) ); +} + +ELFProjectile::~ELFProjectile() +{ +} + +//-------------------------------------------------------------------------- +void ELFProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +//-------------------------------------------------------------------------- +bool ELFProjectile::calculateImpact(float /*simTime*/, + Point3F& pointOfImpact, + float& impactTime) +{ + impactTime = 0; + + if (bool(mTargetObject)) { + mTargetObject->getWorldBox().getCenter(&pointOfImpact); + return true; + } else { + impactTime = 0; + pointOfImpact.set(0, 0, 0); + return false; + } +} + + +//-------------------------------------------------------------------------- +bool ELFProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.min.set(-1e7, -1e7, -1e7); + mObjBox.max.set( 1e7, 1e7, 1e7); + resetWorldBox(); + addToScene(); + + if (isServerObject()) + { + // The first order of business is to establish whether or not we really + // have a target... + findTarget(); + } + else + { + for( int i=0; ionNewDataBlock( mDataBlock->emitterList[i] ); + if( !mEmitterList[i]->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() ); + delete mEmitterList[i]; + mEmitterList[i] = NULL; + } + } + } + + + + return true; +} + + +void ELFProjectile::onRemove() +{ + if (isServerObject() && bool(mTargetObject)) + releaseTarget(mTargetObject); + + removeFromScene(); + if(mWetFireHandle) + alxStop(mWetFireHandle); + if(mFireHandle) + alxStop(mFireHandle); + + U32 i; + for( i=0; ideleteWhenEmpty(); + mEmitterList[i] = NULL; + } + } + + Parent::onRemove(); +} + + +bool ELFProjectile::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; +} + + +//-------------------------------------------------------------------------- +void ELFProjectile::processTick(const Move* move) +{ + Parent::processTick(move); + + if (isServerObject()) + { + findTarget(); + } +} + + +void ELFProjectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if (bool(mTargetObject) == false || bool(mSourceObject) == false) + return; + + mElapsedTime += dt; + mBoltTime += dt; + if( mBoltTime > 1.0/LIGHTNING_FREQ ) + { + mBoltTime -= 1.0/LIGHTNING_FREQ; + mNewBolt = true; + } + + updateEmitters( dt ); + + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mInitialPosition); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &mInitialDirection); + + // make sure mCurrEndPoint is current + ShapeBase *obj = mTargetObject; + calcSnapPoint( *obj, mCurrEndPoint ); +} + + +//-------------------------------------------------------------------------- +bool ELFProjectile::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + if (bool(mTargetObject) == false || bool(mSourceObject) == false) + return false; + + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::EndSort; + state->insertRenderImage(image); + + return false; +} + +void ELFProjectile::acquireTarget(ShapeBase* target) +{ + char buff[16]; + dSprintf(buff, sizeof(buff), "%d", mSourceObjectId); + Con::executef(mDataBlock, 4, "zapTarget", scriptThis(), target->scriptThis(), buff); +} + +void ELFProjectile::releaseTarget(ShapeBase* target) +{ + char buff[16]; + dSprintf(buff, sizeof(buff), "%d", mSourceObjectId); + Con::executef(mDataBlock, 4, "unzapTarget", scriptThis(), target->scriptThis(), buff); +} + +void ELFProjectile::findTarget() +{ + AssertFatal(bool(mSourceObject), "Error, can't find a target without a source!"); + AssertFatal(getContainer(), "Error, must have a container"); + AssertFatal(isServerObject(), "Clients may not find their own targets..."); + if (bool(mSourceObject) == false) + return; + + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mInitialPosition); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &mInitialDirection); + + // hack-o-rama : this fixes problem where client and server have different mInitialPosition + // possibly from player breathing animations. Without this, the player can shoot object + // through floor at certain angles. + mInitialPosition.z -= 0.4; + + MatrixF xform(true); + xform.setColumn(3, mInitialPosition); + + // See if there is something directly under our nose... + Point3F endPoint = mInitialPosition + mInitialDirection * mDataBlock->beamRange; + + mSourceObject->disableCollision(); + RayInfo rayInfo; + if (getContainer()->castRay(mInitialPosition, endPoint, csmStaticCollisionMask | csmDynamicCollisionMask, &rayInfo) == true) + { + // Yes, there is... + + U32 typeMask = rayInfo.object->getTypeMask(); + + if( typeMask &= U32(csmDamageableMask) ) + { + + ShapeBase* pSB = dynamic_cast(rayInfo.object); + if (pSB != NULL) { + + calcSnapPoint( *pSB, mCurrEndPoint ); + + QuadPatch qPatch; + SceneObject *contactObj; + SceneObject **contactObjPtr = &contactObj; + bool hitObj = setupSpline( qPatch, contactObjPtr ); + + if( !hitObj || contactObj == pSB ) + { + if( !pSB->isInvincible() ) + { + if(pSB->getDamageState() != ShapeBase::Destroyed) + { + if (bool(mTargetObject) == false || (pSB != (ShapeBase*)mTargetObject)) + { + if (bool(mTargetObject)) + { + releaseTarget(mTargetObject); + } + + setMaskBits(TargetChangedMask); + mTargetObject = pSB; + acquireTarget(mTargetObject); + } + } + + mSourceObject->enableCollision(); + return; + } + } + } + } + } + + // Nothing directly under our gun, query the container to see if anything is close... + // + U32 i; + Box3F queryBox; + queryBox.min = mInitialPosition; + queryBox.max = mInitialPosition; + queryBox.min.setMin(endPoint); + queryBox.max.setMax(endPoint); + + // This is really lame, but it fixes a bug that was present in T1. + static const Point3F sExtendArray[6] = { + Point3F(0, 0, 1), Point3F(0, 0, -1), + Point3F(0, 1, 0), Point3F(0, -1, 0), + Point3F(1, 0, 0), Point3F(-1, 0, 0) + }; + F32 extend = mDataBlock->beamRange * mSin(mDegToRad(mDataBlock->beamHitWidth)); + for (i = 0; i < 6; i++) { + queryBox.min.setMin(endPoint + sExtendArray[i]*extend); + queryBox.max.setMax(endPoint + sExtendArray[i]*extend); + } + + SimpleQueryList sql; + getContainer()->findObjects(queryBox, csmDamageableMask, + SimpleQueryList::insertionCallback, S32(&sql)); + mSourceObject->enableCollision(); + + for (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); + + Point3F difVector = objectCenter - mInitialPosition; + F32 len = difVector.len(); + if (len > mDataBlock->beamRange) + continue; + + disableCollision(); + bool hit = true; + if (getContainer()->castRay(mInitialPosition, objectCenter, csmStaticCollisionMask | csmDynamicCollisionMask, &rayInfo)) { + if (rayInfo.object != sql.mList[i]) + hit = false; + } + enableCollision(); + if (!hit) + continue; + + difVector /= len; + F32 dot = mDot(difVector, mInitialDirection); + if (dot >= mDataBlock->cosBeamHitWidth) { + + + // Hit the object! + ShapeBase* pSB = dynamic_cast(sql.mList[i]); + if (pSB != NULL) { + + calcSnapPoint( *pSB, mCurrEndPoint ); + + QuadPatch qPatch; + SceneObject *contactObj; + SceneObject **contactObjPtr = &contactObj; + bool hitObj = setupSpline( qPatch, contactObjPtr ); + + if( !hitObj || contactObj == pSB ) + { + if( !pSB->isInvincible() ) + { + + if(pSB->getDamageState() != ShapeBase::Destroyed) { + if (bool(mTargetObject) == false || (pSB != (ShapeBase*)mTargetObject)) { + if (bool(mTargetObject)) + releaseTarget(mTargetObject); + + setMaskBits(TargetChangedMask); + mTargetObject = pSB; + acquireTarget(mTargetObject); + } + } + return; + } + } + } + } + } + + // If we're here, we found nothing of note. Notify the clients if that is necessary, + // and clear our target... + if (bool(mTargetObject)) { + setMaskBits(TargetChangedMask); + releaseTarget(mTargetObject); + mTargetObject = NULL; + } +} + + +bool ELFProjectile::checkForFlare( const Point3F &camPos ) +{ + + if( !mTargetObject ) return false; + + // check if in first or third person mode + bool firstPerson = true; + + GameConnection *gc = mSourceObject->getControllingClient(); + if( gc ) + { + firstPerson = gc->isFirstPerson(); + } + + VectorF dirFromGun = mCurrEndPoint - mInitialPosition; + dirFromGun.normalizeSafe(); + + VectorF dirToFlare = mCurrEndPoint - camPos; + dirToFlare.normalizeSafe(); + Point3F tweakedCamPos = camPos + dirToFlare * 0.5; + + if( mDot( dirFromGun, dirToFlare ) < -0.75 ) return false; + + + bool rayHit = true; + + mTargetObject->disableCollision(); + + if( firstPerson ) + { + mSourceObject->disableCollision(); + + RayInfo rayInfo; + rayHit = getContainer()->castRay( tweakedCamPos, mCurrEndPoint, mTargetObject->getType(), &rayInfo ); + + mSourceObject->enableCollision(); + } + else + { + RayInfo rayInfo; + rayHit = getContainer()->castRay( tweakedCamPos, mCurrEndPoint, mTargetObject->getType(), &rayInfo ); + + } + + mTargetObject->enableCollision(); + + + return !rayHit; +} + + +bool ELFProjectile::findContactPoint( SplinePatch &spline, Point3F &point, SceneObject **contactObj ) +{ + Point3F startPoint; + Point3F endPoint; + + + mSourceObject->disableCollision(); + + RayInfo rayInfo; + bool rayHit = false; + + spline.calc( 0.0, endPoint ); + + for( int i=0; icastRay( startPoint, endPoint, + (csmStaticCollisionMask | csmDynamicCollisionMask | WaterObjectType), + &rayInfo); + + if( rayHit ) break; + } + + mSourceObject->enableCollision(); + + point = rayInfo.point; + + if( contactObj ) + { + *contactObj = rayInfo.object; + } + + return rayHit; +} + + +void ELFProjectile::renderFlare( F32 flareSize ) +{ + + Point3F cPoint = mCurrEndPoint; + + glDisable(GL_DEPTH_TEST); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureList[ELFProjectileData::ET_BALL].getGLName() ); + + + glColor4f( 1.0, 1.0, 1.0, 0.33333333 ); + + dglDrawBillboard( cPoint, flareSize * 2.0, mElapsedTime * .30 * M_PI * 2.0 ); + dglDrawBillboard( cPoint, flareSize * 1.7, -mElapsedTime * .20 * M_PI * 2.0 ); + dglDrawBillboard( cPoint, flareSize * 1.82, mElapsedTime * .10 * M_PI * 2.0 ); + + + glEnable(GL_DEPTH_TEST); + + +} + +void ELFProjectile::createBolt( LightningBolt &bolt, SplinePatch &spline ) +{ + + for( int i=0; igetRenderMuzzlePoint(mSourceObjectSlot, &mInitialPosition); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &mInitialDirection); + + ShapeBase *obj = mTargetObject; + calcSnapPoint( *obj, mCurrEndPoint ); + + QuadPatch qPatch; + SceneObject *contactObj; + SceneObject **contactObjPtr = &contactObj; + bool hitObj = setupSpline( qPatch, contactObjPtr ); + + if( hitObj && contactObj != mTargetObject ) + { + return; + } + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupBaseProjection(); + + // set gl state + glEnable(GL_BLEND); + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + glDepthMask(GL_FALSE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + // render collision flare + if( checkForFlare( state->getCameraPosition() ) ) + { + renderFlare( 0.5 ); + } + + + // render middle beam + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureList[ELFProjectileData::ET_WAVE].getGLName() ); + + glColor3f( 1.0, 1.0, 1.0 ); + + SplineUtil::drawSplineBeam( state->getCameraPosition(), 16, mDataBlock->mainBeamWidth * 2.0, + qPatch, -mElapsedTime * mDataBlock->mainBeamSpeed, mDataBlock->mainBeamRepeat ); + + + + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureList[ELFProjectileData::ET_BEAM].getGLName() ); + + + // render lightning + if( mNewBolt ) + { + createBoltRandPoints( mBoltList[0], mDataBlock->lightningDist ); + createBoltRandPoints( mBoltList[1], mDataBlock->lightningDist ); + createBoltRandPoints( mBoltList[2], mDataBlock->lightningDist ); + mNewBolt = false; + } + + createBolt( mBoltList[0], qPatch ); + createBolt( mBoltList[1], qPatch ); + createBolt( mBoltList[2], qPatch ); + renderBolt( mBoltList[0], state->getCameraPosition(), mDataBlock->lightningWidth ); + renderBolt( mBoltList[1], state->getCameraPosition(), mDataBlock->lightningWidth ); + renderBolt( mBoltList[2], state->getCameraPosition(), mDataBlock->lightningWidth ); + + + // restore gl state + glDisable(GL_TEXTURE_2D); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +U32 ELFProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(bool(mSourceObject) && bool(mTargetObject))) { + S32 ghostIndexSource = con->getGhostIndex(mSourceObject); + S32 ghostIndexDest = con->getGhostIndex(mTargetObject); + if (ghostIndexSource == -1 || ghostIndexDest == -1) { + stream->writeFlag(false); + } else { + stream->writeFlag(true); + + stream->writeRangedU32(U32(ghostIndexSource), 0, NetConnection::MaxGhostCount); + stream->writeRangedU32(U32(mSourceObjectSlot), + 0, ShapeBase::MaxMountedImages - 1); + stream->writeRangedU32(U32(ghostIndexDest), 0, NetConnection::MaxGhostCount); + } + } + + return retMask; +} + +void ELFProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) { + if (stream->readFlag()) { + mSourceObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + mSourceObjectSlot = stream->readRangedU32(0, ShapeBase::MaxMountedImages - 1); + mTargetObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + + NetObject* pObject = con->resolveGhost(mSourceObjectId); + if (pObject != NULL) + mSourceObject = dynamic_cast(pObject); + if (bool(mSourceObject) == false) + Con::errorf(ConsoleLogEntry::General, "ELFProjectile::unpackUpdate: could not resolve source ghost properly on initial update"); + + pObject = con->resolveGhost(mTargetObjectId); + if (pObject != NULL) { + mTargetObject = dynamic_cast(pObject); + } + if (bool(mTargetObject) == false) + Con::errorf(ConsoleLogEntry::General, "ELFProjectile::unpackUpdate: could not resolve dest ghost properly on initial update"); + } else { + mSourceObject = NULL; + mTargetObject = NULL; + } + } else { + mSourceObject = NULL; + mTargetObject = NULL; + } +} + + +void ELFProjectile::calcSnapPoint( ShapeBase &obj, Point3F &point ) +{ + // Check if object has polys at its center point. If not, then try and + // use a point near its bottom. ( For inventory stations and such ) + Point3F end; + obj.getObjBox().getCenter(&end); + Point3F start = end + Point3F( 0.0, 0.0, 1000.0 ); + + RayInfo rayInfo; + if( !obj.castRay( end, start, &rayInfo ) ) + { + + start = end; + end = start + Point3F( 0.0, 0.0, -1000.0 ); + + if( obj.castRay( start, end, &rayInfo ) ) + { + obj.getTransform().mulP( rayInfo.point ); + point = rayInfo.point; + return; + } + } + + obj.getObjBox().getCenter( &point ); + point.convolve( obj.getScale() ); + obj.getTransform().mulP( point ); + +} + +static bool cHasTarget(SimObject *obj, S32, const char **) +{ + ELFProjectile *proj = static_cast(obj); + return(proj->hasTarget()); +} + + +void ELFProjectile::consoleInit() +{ + Con::addCommand("ELFProjectile", "hasTarget", cHasTarget, "projectile.hasTarget()", 2, 2); +} + +void ELFProjectile::updateEmitters( F32 dt ) +{ + if( !mTargetObject ) + { + return; + } + + for( U32 i=0; iemitParticles( mCurrEndPoint, mCurrEndPoint, VectorF( 0.0, 0.0, 1.0 ), VectorF( 0.0, 0.0, 0.0 ), dt * 1000 ); + } +} diff --git a/game/projELF.h b/game/projELF.h new file mode 100644 index 0000000..30d933b --- /dev/null +++ b/game/projELF.h @@ -0,0 +1,201 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROJELF_H_ +#define _PROJELF_H_ + +#ifndef _PROJECTILE_H_ +#include "game/projectile.h" +#endif + +class SplinePatch; +class QuadPatch; +class ParticleEmitter; +class ParticleEmitterData; + +// ------------------------------------------------------------------------- +class ELFProjectileData : public ProjectileData +{ + typedef ProjectileData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + enum ELFTextures + { + ET_WAVE = 0, + ET_BEAM, + ET_BALL, + ET_NUM_TEX + }; + + enum ELFConst + { + ELF_NUM_EMITTERS = 1, + }; + + F32 beamRange; + F32 beamHitWidth; + + StringTableEntry textureNames[ET_NUM_TEX]; + + ParticleEmitterData* emitterList[ELF_NUM_EMITTERS]; + S32 emitterIDList[ELF_NUM_EMITTERS]; + + F32 mainBeamWidth; + F32 mainBeamSpeed; + F32 mainBeamRepeat; + F32 lightningWidth; + F32 lightningDist; + + //-------------------------------------- preload set variables + public: + F32 cosBeamHitWidth; + TextureHandle textureList[ET_NUM_TEX]; + + public: + ELFProjectileData(); + ~ELFProjectileData(); + + + bool calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& sourceVel, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(ELFProjectileData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class ELFProjectile : public Projectile +{ + typedef Projectile Parent; + + protected: + enum ELFNetMasks { + TargetChangedMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + private: + ELFProjectileData* mDataBlock; + + SimObjectPtr mTargetObject; + S32 mTargetObjectId; + + Point3F mCurrEndPoint; + AUDIOHANDLE mWetFireHandle; + AUDIOHANDLE mFireHandle; + + ParticleEmitter * mEmitterList[ELFProjectileData::ELF_NUM_EMITTERS]; + bool mEmittingParticles; + + void updateEmitters( F32 dt ); + + public: + + enum LightningConst + { + LC_NUM_POINTS = 16, + LC_NUM_BOLTS = 3 + }; + + + struct LightningBolt + { + Point3F points[LC_NUM_POINTS]; + Point3F randPoints[LC_NUM_POINTS]; + U32 numPoints; + F32 alpha; + F32 fadeTime; + F32 elapsedTime; + + LightningBolt() + { + dMemset( this, 0, sizeof( LightningBolt ) ); + numPoints = LC_NUM_POINTS; + fadeTime = 0.2; + } + + }; + + LightningBolt mBoltList[LC_NUM_BOLTS]; + + protected: + F32 mElapsedTime; + F32 mBoltTime; + bool mNewBolt; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + void findTarget(); + + bool findContactPoint( SplinePatch &spline, Point3F &point, SceneObject **contactObj=NULL ); + + void createBolt( LightningBolt &bolt, SplinePatch &spline ); + void createBoltRandPoints( LightningBolt &bolt, F32 sideMag ); + void renderBolt( LightningBolt &bolt, const Point3F &camPos, F32 width ); + + + void releaseTarget(ShapeBase*); + void acquireTarget(ShapeBase*); + + bool calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime); + + void renderFlare( F32 flareSize ); + bool checkForFlare( const Point3F &camPos ); + + bool setupSpline( QuadPatch &spline, SceneObject **contactObj=NULL ); + + void calcSnapPoint( ShapeBase &obj, Point3F &point ); + + public: + ELFProjectile(); + ~ELFProjectile(); + + // Time/Move Management + public: + void processTick(const Move*); + void advanceTime(F32); + bool hasTarget(); + + DECLARE_CONOBJECT(ELFProjectile); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +inline bool ELFProjectile::hasTarget() +{ + return !mTargetObject.isNull(); +} + +#endif // _H_ELFPROJECTILE diff --git a/game/projEnergy.cc b/game/projEnergy.cc new file mode 100644 index 0000000..e5e8415 --- /dev/null +++ b/game/projEnergy.cc @@ -0,0 +1,809 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projEnergy.h" +#include "core/bitStream.h" +#include "sim/netConnection.h" +#include "game/shapeBase.h" +#include "math/mathIO.h" +#include "console/consoleTypes.h" +#include "game/explosion.h" +#include "terrain/waterBlock.h" +#include "game/splash.h" +#include "game/particleEngine.h" +#include "dgl/dgl.h" +#include "scenegraph/sceneState.h" +#include "math/mathUtils.h" + +#define MAX_BOUNCE_COUNT 5 + +IMPLEMENT_CO_NETOBJECT_V1(EnergyProjectile); +IMPLEMENT_CO_DATABLOCK_V1(EnergyProjectileData); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +EnergyProjectileData::EnergyProjectileData() +{ + dMemset( textureName, 0, sizeof( textureName ) ); + dMemset( textureHandle, 0, sizeof( textureHandle ) ); + + crossViewAng = 0.98; + crossSize = 0.45; + blurLifetime = 0.5; + blurWidth = 0.25; + blurColor.set( 0.4, 0.0, 0.0, 1.0 ); +} + +//-------------------------------------------------------------------------- +EnergyProjectileData::~EnergyProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void EnergyProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("texture", TypeString, Offset( textureName, EnergyProjectileData), NUM_TEX); + addField("crossViewAng", TypeF32, Offset( crossViewAng, EnergyProjectileData)); + addField("crossSize", TypeF32, Offset( crossSize, EnergyProjectileData)); + addField("blurLifetime", TypeF32, Offset( blurLifetime, EnergyProjectileData)); + addField("blurWidth", TypeF32, Offset( blurWidth, EnergyProjectileData)); + addField("blurColor", TypeColorF, Offset( blurColor, EnergyProjectileData)); +} + + +//-------------------------------------------------------------------------- +// Preload data - load resources +//-------------------------------------------------------------------------- +bool EnergyProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (!server) { + U32 i; + for( i=0; iwrite(crossViewAng); + stream->write(crossSize); + stream->write(blurLifetime); + stream->write(blurWidth); + stream->write(blurColor.red); + stream->write(blurColor.green); + stream->write(blurColor.blue); + + + U32 i; + for( i=0; iwriteString(textureName[i]); + } + +} + +//-------------------------------------------------------------------------- +void EnergyProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&crossViewAng); + stream->read(&crossSize); + stream->read(&blurLifetime); + stream->read(&blurWidth); + stream->read(&blurColor.red); + stream->read(&blurColor.green); + stream->read(&blurColor.blue); + + + for( U32 i=0; ireadSTString(); + } + +} + +// same as ::linearProjectileData +bool EnergyProjectileData::calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& sourceVel, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime) +{ + // Not implemented: underwater shots... + + Point3F effTargetPos = targetPos - sourcePos; + Point3F effTargetVel = targetVel - sourceVel * velInheritFactor; + + // Without underwater aiming, this is a straightforward law of cosines + // calculation. We're not being especially efficient here, but hey. + // + Point3F normPos = effTargetPos; + Point3F normVel = effTargetVel; + if (normPos.isZero() == false) + normPos.normalize(); + if (normVel.isZero() == false) + normVel.normalize(); + + F32 a = effTargetVel.lenSquared() - (muzzleVelocity*muzzleVelocity); + F32 b = 2 * effTargetPos.len() * effTargetVel.len() * mDot(normPos, normVel); + F32 c = effTargetPos.lenSquared(); + + F32 det = b*b - 4*a*c; + if (det < 0.0) { + // No solution is possible in the real numbers + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + return false; + } + + F32 sol1 = (-b + mSqrt(det)) / (2 * a); + F32 sol2 = (-b - mSqrt(det)) / (2 * a); + + F32 t; + if (sol2 > 0.0) { + t = sol2; + } else { + t = sol1; + } + if (t < 0.0) { + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + return false; + } + + // Once we know how long the projectile's path will take, it's straightforward to + // find out where it should go... + Point3F finalAnswer = (effTargetPos / (muzzleVelocity * t)) + (effTargetVel / muzzleVelocity); + finalAnswer.normalize(); + + *outputVectorMin = finalAnswer; + *outputVectorMax = finalAnswer; + *outputMinTime = t; + *outputMaxTime = t; + + return (t * 1000.0) <= lifetimeMS; +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +EnergyProjectile::EnergyProjectile() +{ + +} + +EnergyProjectile::~EnergyProjectile() +{ + // +} + +//-------------------------------------------------------------------------- +void EnergyProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void EnergyProjectile::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +bool EnergyProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if( isClientObject() ) + { + mMotionBlur.setColor( mDataBlock->blurColor ); + mMotionBlur.setWidth( mDataBlock->blurWidth ); + mMotionBlur.setLifetime( mDataBlock->blurLifetime ); + + mLastPos = mCurrPosition; + } + + mObjBox = Box3F( Point3F( -0.5, -0.5, -0.5 ), Point3F( 0.5, 0.5, 0.5 ) ); + resetWorldBox(); + + return true; +} + + +//-------------------------------------------------------------------------- +void EnergyProjectile::onRemove() +{ + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +bool EnergyProjectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +void EnergyProjectile::processTick(const Move* move) +{ + Projectile::processTick(move); + + if (isServerObject()) { + // server + if (mDeleteTick != -1 && mCurrTick >= mDeleteTick) { + deleteObject(); + return; + } else if (mHidden == true) { + // already exploded, back out + return; + } + + bool hitDynamic = false; + bool reflected = false; + Point3F oldPosition; + Point3F newPosition; + + // Otherwise, we have to do some simulation work. + oldPosition = mCurrPosition; + computeNewState(&newPosition, &mCurrVelocity); + + + VectorF currDir = mCurrVelocity; + currDir.normalizeSafe(); + + F32 dot = mDot( currDir, mInitialDirection ); + reflected = (1.0 - dot) >= 0.1; + + if( !reflected ) + { + collisionOff(); + } + + + F32 timeLeft = 1.0; + S32 bounces = MAX_BOUNCE_COUNT; + while( bounces-- ) + { + + RayInfo rInfo; + if (getContainer()->castRay(oldPosition, newPosition, + csmDynamicCollisionMask | csmStaticCollisionMask, + &rInfo) == true) + { + // First order of business. Is what we hit a dynamic object? If so, + // we need to inform the client. + if( (rInfo.object->getType() & csmDynamicCollisionMask) ) + { + hitDynamic = true; + setMaskBits(BounceMask); + } + + // Next order of business: do we explode on this hit? + if( mCurrTick > mArmedTick || hitDynamic ) + { + MatrixF xform(true); + xform.setColumn(3, rInfo.point + rInfo.normal * 0.1); + setTransform(xform); + mCurrPosition = rInfo.point; + mCurrVelocity = Point3F(0, 0, 0); + + if( !reflected ) + { + collisionOn(); + } + + explode(rInfo.point, rInfo.normal); + onCollision( rInfo.point, rInfo.normal, rInfo.object ); + mDeleteTick = mCurrTick + mDataBlock->blurLifetime * 1000 / TickMs; + + if( !reflected ) + { + collisionOff(); + } + break; + } + + mCurrVelocity = mCurrVelocity - rInfo.normal * (mDot( mCurrVelocity, rInfo.normal ) * 2.0);; + timeLeft = timeLeft * (1.0 - rInfo.t); + oldPosition = rInfo.point + rInfo.normal * 0.05; + newPosition = oldPosition + (mCurrVelocity * ((timeLeft/1000.0) * TickMs)); + + if( !reflected ) + { + collisionOn(); + reflected = true; + } + + } else { + // No problems, just set the end position, and we're golden. + + mCurrPosition = newPosition; + MatrixF xform(true); + xform.setColumn(3, mCurrPosition); + setTransform(xform); + + mMotionBlur.addSegment( oldPosition, newPosition ); + mLastPos = newPosition; + + break; + } + } + + // remove object if it is stuck + if( bounces < 0 ) + { + deleteObject(); + return; + } + + if( !reflected ) + { + collisionOn(); + } + + mMotionBlur.update( TickMs ); + + } else { + // client + if (mHidden == true) + return; + + bool hitDynamic = false; + Point3F oldPosition; + Point3F newPosition; + + // Otherwise, we have to do some simulation work. + oldPosition = mCurrPosition; + computeNewState(&newPosition, &mCurrVelocity); + + collisionOff(); + + F32 timeLeft = 1.0; + S32 bounces = MAX_BOUNCE_COUNT; + while( bounces-- ) + { + + RayInfo rInfo; + if (getContainer()->castRay(oldPosition, newPosition, + csmStaticCollisionMask | csmDynamicCollisionMask, + &rInfo) == true) + { + // First order of business. Is what we hit a dynamic object? If so, + // we need to inform the client. + if( (rInfo.object->getType() & csmDynamicCollisionMask) ) + { + hitDynamic = true; + setMaskBits(BounceMask); + } + + // Next order of business: do we explode on this hit? + if( mCurrTick > mArmedTick || hitDynamic ) + { + MatrixF xform(true); + xform.setColumn(3, rInfo.point + rInfo.normal * 0.1 ); + setTransform(xform); + mCurrPosition = rInfo.point; + mCurrVelocity = Point3F(0, 0, 0); + + if( !oldPosition.equal( rInfo.point ) ) + { + mMotionBlur.addSegment( oldPosition, rInfo.point ); + } + explode( rInfo.point + rInfo.normal * 0.1, rInfo.normal); + break; + } + + mCurrVelocity = mCurrVelocity - rInfo.normal * (mDot( mCurrVelocity, rInfo.normal ) * 2.0);; + timeLeft = timeLeft * (1.0 - rInfo.t); + oldPosition = rInfo.point + rInfo.normal * 0.05; + newPosition = oldPosition + (mCurrVelocity * ((timeLeft/1000.0) * TickMs)); + + } + else + { + // No problems, just set the end position, and we're golden. + + mCurrDeltaBase = newPosition; + mCurrBackVector = mCurrPosition - newPosition; + + if( !mLastPos.equal( newPosition ) ) + { + mMotionBlur.addSegment( mLastPos, newPosition ); + mLastPos = newPosition; + } + + mCurrPosition = newPosition; + + break; + } + } + + + if( determineSplash( oldPosition, newPosition ) ) + { + createSplash( mSplashPos ); + } + + collisionOn(); + +// mObjBox = mMotionBlur.getBox( getPosition() ); + + MatrixF temp(true); + temp.setColumn(3, mCurrDeltaBase); + setTransform(temp); + } +} + + +//-------------------------------------------------------------------------- +// Advance time +//-------------------------------------------------------------------------- +void EnergyProjectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + mMotionBlur.update( dt ); +} + + +//-------------------------------------------------------------------------- +U32 EnergyProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + return retMask; +} + +void EnergyProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) + { + // Initial update + + + mathRead(*stream, &mCurrPosition); + mathRead(*stream, &mCurrVelocity); + mCurrTick = stream->readRangedU32(0, MaxLivingTicks); + + mInitialDirection = mCurrVelocity; + mInitialDirection.normalizeSafe(); + + if( stream->readFlag() ) + { + resolveInitialSplash(); + } + + + if (stream->readFlag()) + { + // explosion on the server... + Point3F explodePoint; + Point3F explodeNormal; + mathRead(*stream, &explodePoint); + mathRead(*stream, &explodeNormal); + + explode(explodePoint, explodeNormal); + } + + + if( pointInWater( mCurrPosition ) ) + { + mInWater = true; + mLastInWater = true; + } + + 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; + } + + if (stream->readFlag()) + { + mVehicleObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + NetObject* pObject = con->resolveGhost(mVehicleObjectId); + if (pObject != NULL) + mVehicleObject = dynamic_cast(pObject); + if (bool(mVehicleObject) == false) + Con::errorf(ConsoleLogEntry::General, "LinearProjectile::unpackUpdate: could not resolve source ghost properly on initial update"); + } + else + { + mVehicleObjectId = 0; + mVehicleObject = NULL; + } + } + else + { + if (stream->readFlag()) + { + // bounce against dynamic object on the server + // + mathRead(*stream, &mCurrPosition); + mathRead(*stream, &mCurrVelocity); + } + + if (stream->readFlag()) + { + // explosion on the server... + Point3F explodePoint; + Point3F explodeNormal; + mathRead(*stream, &explodePoint); + mathRead(*stream, &explodeNormal); + + explode(explodePoint, explodeNormal); + } + } +} + + +//-------------------------------------------------------------------------- +// Prep render +//-------------------------------------------------------------------------- +bool EnergyProjectile::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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + + state->insertRenderImage(image); + } + + return false; +} + + +//-------------------------------------------------------------------------- +// Render energy bolt +//-------------------------------------------------------------------------- +void EnergyProjectile::renderObject(SceneState* state, SceneRenderImage *sri ) +{ + + if( mProjectileShape ) + { + Parent::renderObject( state, sri ); + return; + } + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + mMotionBlur.render( state->getCameraPosition() ); + + if( !mHidden ) + { + renderProjectile( state->getCameraPosition() ); + renderCrossSection( state->getCameraPosition() ); + } + + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + dglSetCanonicalState(); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +// Render projectile +//-------------------------------------------------------------------------- +void EnergyProjectile::renderProjectile( const Point3F &camPos ) +{ + Point3F dir = getVelocity(); + if( dir.isZero() ) return; + + Point3F pos = getRenderPosition(); + Point3F dirFromCam = pos - camPos; + Point3F crossDir; + mCross( dirFromCam, dir, &crossDir ); + crossDir.normalizeSafe(); + dir.normalize(); + + + F32 width = mDataBlock->scale.x; + F32 length = mDataBlock->scale.y / 2.0; + + dir *= width; + crossDir *= width; + Point3F start = pos - length * dir; + Point3F end = pos + length * dir; + + + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_TEXTURE_2D); + + glColor4f( 1.0, 1.0, 1.0, 1.0 ); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[0].getGLName() ); + + glBegin(GL_QUADS); + + glTexCoord2f(0, 0); + glVertex3fv( start + crossDir ); + + glTexCoord2f(0, 1); + glVertex3fv( start - crossDir ); + + glTexCoord2f(1, 1); + glVertex3fv( end - crossDir ); + + glTexCoord2f(1, 0); + glVertex3fv( end + crossDir ); + + glEnd(); + +} + +//-------------------------------------------------------------------------- +// Render "cross section" so effect doesn't look flat when seen edge-on +//-------------------------------------------------------------------------- +void EnergyProjectile::renderCrossSection( const Point3F &camPos ) +{ + Point3F dir = getVelocity(); + if( dir.isZero() ) return; + + Point3F pos = getRenderPosition(); + Point3F dirFromCam = pos - camPos; + dirFromCam.normalizeSafe(); + dir.normalize(); + + F32 angle = mDot( dir, dirFromCam ); + if( angle > -mDataBlock->crossViewAng && angle < mDataBlock->crossViewAng ) return; + + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + MatrixF mat = MathUtils::createOrientFromDir( dir ); + mat.setPosition( getRenderPosition() ); + + dglMultMatrix( &mat ); + + + glColor4f( 1.0, 1.0, 1.0, 1.0 ); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[1].getGLName() ); + + glBegin(GL_QUADS); + + F32 width = mDataBlock->crossSize / 2.0; + + glTexCoord2f(0, 0); + glVertex3f( -width, 0, -width ); + + glTexCoord2f(0, 1); + glVertex3f( width, 0, -width ); + + glTexCoord2f(1, 1); + glVertex3f( width, 0, width ); + + glTexCoord2f(1, 0); + glVertex3f( -width, 0, width ); + + glEnd(); + + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + +} + + +void EnergyProjectile::collisionOn() +{ + + if (mSourceIdTimeoutTicks && bool(mSourceObject) ) + { + mSourceObject->enableCollision(); + } + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + { + mVehicleObject->enableCollision(); + } + +} + +void EnergyProjectile::collisionOff() +{ + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + { + mSourceObject->disableCollision(); + } + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + { + mVehicleObject->disableCollision(); + } + +} diff --git a/game/projEnergy.h b/game/projEnergy.h new file mode 100644 index 0000000..2f13042 --- /dev/null +++ b/game/projEnergy.h @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROJENERGY_H_ +#define _PROJENERGY_H_ + +#ifndef _PROJGRENADE_H_ +#include "game/projGrenade.h" +#endif +#ifndef _MOTIONBLURLINE_H_ +#include "game/motionBlurLine.h" +#endif + +//-------------------------------------------------------------------------- +// Energy Projectile Data +//-------------------------------------------------------------------------- +class EnergyProjectileData : public GrenadeProjectileData +{ + typedef GrenadeProjectileData Parent; + + enum Constants + { + NUM_TEX = 2, + }; + + protected: + bool onAdd(); + + public: + + StringTableEntry textureName[NUM_TEX]; + TextureHandle textureHandle[NUM_TEX]; + + F32 crossViewAng; + F32 crossSize; + F32 blurLifetime; + F32 blurWidth; + ColorF blurColor; + + + + public: + EnergyProjectileData(); + ~EnergyProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool calculateAim(const Point3F&, const Point3F&, const Point3F&, const Point3F&, Point3F*, F32*, Point3F*, F32*); + + DECLARE_CONOBJECT(EnergyProjectileData); + static void initPersistFields(); + bool preload(bool server, char errorBuffer[256]); +}; + +//-------------------------------------------------------------------------- +// Energy Projectile +//-------------------------------------------------------------------------- +class EnergyProjectile : public GrenadeProjectile +{ + typedef GrenadeProjectile Parent; + + private: + EnergyProjectileData* mDataBlock; + MotionBlurLine mMotionBlur; + Point3F mLastPos; + + protected: + void collisionOn(); + void collisionOff(); + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + void processTick(const Move*); + void advanceTime(F32); + void renderObject(SceneState*, SceneRenderImage*); + void renderProjectile( const Point3F &camPos ); + void renderCrossSection( const Point3F &camPos ); + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + + public: + EnergyProjectile(); + ~EnergyProjectile(); + + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); + + DECLARE_CONOBJECT(EnergyProjectile); +}; + +#endif // _H_PROJENERGY + diff --git a/game/projFlareGrenade.cc b/game/projFlareGrenade.cc new file mode 100644 index 0000000..846babd --- /dev/null +++ b/game/projFlareGrenade.cc @@ -0,0 +1,432 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projFlareGrenade.h" +#include "core/bitStream.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "scenegraph/sceneState.h" +#include "game/shapeBase.h" +#include "math/mathUtils.h" + +IMPLEMENT_CO_DATABLOCK_V1(FlareProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(FlareProjectile); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +FlareProjectileData::FlareProjectileData() +{ + dMemset( textureNameList, 0, sizeof( textureNameList ) ); + size = 50.0; + useLensFlare = true; +} + +FlareProjectileData::~FlareProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void FlareProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("texture", TypeString, Offset(textureNameList, FlareProjectileData), FPD_NUM_TEX); + addField("size", TypeF32, Offset(size, FlareProjectileData)); + addField("useLensFlare", TypeBool, Offset(useLensFlare, FlareProjectileData)); + +} + + +//-------------------------------------------------------------------------- +bool FlareProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + return true; +} + + +//-------------------------------------------------------------------------- +bool FlareProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (server == false) + { + for( int i=0; iwrite(size); + stream->write(useLensFlare); + + for( int i=0; iwriteString(textureNameList[i]); + } +} + +void FlareProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&size); + stream->read(&useLensFlare); + + for( int i=0; ireadSTString(); + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +FlareProjectile::FlareProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + + mLifetime = 10.0; + mElapsedTime = 0.0; +} + +FlareProjectile::~FlareProjectile() +{ + // +} + +//-------------------------------------------------------------------------- +void FlareProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void FlareProjectile::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +bool FlareProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + setupLensFlare(); + + return true; +} + + +void FlareProjectile::onRemove() +{ + // + + Parent::onRemove(); +} + + +bool FlareProjectile::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; +} + + +//-------------------------------------------------------------------------- +U32 FlareProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // + + return retMask; +} + +void FlareProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + // +} + + +//-------------------------------------------------------------------------- +void FlareProjectile::processTick(const Move* move) +{ + Parent::processTick(move); + + if (mLifetime <= 0.0) + { +// deleteObject(); + } +} + +//-------------------------------------------------------------------------- +void FlareProjectile::advanceTime( float dt ) +{ + mElapsedTime += dt; + + mLifetime -= dt; + if( mLifetime <= 0.0 ) + { + mLifetime = 0.0; + return; + } + + updateBlur(); +} + + + +//---------------------------------------------------------------------------- +// Render Object +//---------------------------------------------------------------------------- +void FlareProjectile::renderObject(SceneState* state, SceneRenderImage* ) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + F32 ang = mElapsedTime * 0.5; + + renderFlare( state->getCameraPosition(), ang ); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glPushMatrix(); + + renderFlare( state->getCameraPosition(), -ang * 0.5 ); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + dglSetCanonicalState(); + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//---------------------------------------------------------------------------- +// Render flare +//---------------------------------------------------------------------------- +void FlareProjectile::renderFlare( const Point3F &camPos, F32 ang ) +{ + Point3F pos = getRenderPosition(); + + MatrixF camTrans; + dglGetModelview(&camTrans); + camTrans.setPosition( camPos ); + Point3F camLookDir; + camTrans.getRow(1, &camLookDir); + + // find 2D screen points for flares + Point3F screenPoint; + dglPointToScreen( pos, screenPoint ); + + for( int j=0; jtextureList[FlareProjectileData::FPD_FLARE_TEX].getGLName()); + + MatrixF rotMatrix( true ); + rotMatrix.set( EulerF( 0.0, ang, 0.0 ) ); + + Point3F negCamDir = -camLookDir; + MatrixF billBoard = MathUtils::createOrientFromDir( negCamDir ); + billBoard.mul( rotMatrix ); + billBoard.setPosition( getRenderPosition() ); + + dglMultMatrix( &billBoard ); + + + glBegin(GL_QUADS); + + F32 width = mDataBlock->size * 0.5; + + glTexCoord2f(0, 0); + glVertex3f( -width, 0, -width ); + + glTexCoord2f(0, 1); + glVertex3f( width, 0, -width ); + + glTexCoord2f(1, 1); + glVertex3f( width, 0, width ); + + glTexCoord2f(1, 0); + glVertex3f( -width, 0, width ); + + glEnd(); + + if( mDataBlock->useLensFlare ) + { + // render lens flare + mLensFlare.render( camTrans, pos ); + } + + glDepthMask(GL_TRUE); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + +} + +//---------------------------------------------------------------------------- +// Update blur +//---------------------------------------------------------------------------- +void FlareProjectile::updateBlur() +{ + Point3F blurDir = -getVelocity(); + F32 velMag = blurDir.magnitudeSafe() * 0.5; + if( velMag > 3.0 ) + velMag = 3.0; + + blurDir.normalizeSafe(); + Point3F pos = getRenderPosition(); + + for( int i=0; itextureList[FlareProjectileData::FPD_LENS0_TEX]; + flare.color.set( 1.0, 0.0, 0.0, 0.5 ); + mLensFlare.addFlare( flare ); + + flare.offset = 0.3; + flare.size = 100.0; + flare.tex = mDataBlock->textureList[FlareProjectileData::FPD_LENS0_TEX]; + flare.color.set( 1.0, 0.0, 0.0, 0.7 ); + mLensFlare.addFlare( flare ); + + flare.offset = 0.45; + flare.size = 10.0; + flare.tex = mDataBlock->textureList[FlareProjectileData::FPD_LENS0_TEX]; + flare.color.set( 1.0, 0.0, 0.0, 1.0 ); + mLensFlare.addFlare( flare ); + + flare.offset = 0.75; + flare.size = 20.0; + flare.tex = mDataBlock->textureList[FlareProjectileData::FPD_LENS0_TEX]; + flare.color.set( 1.0, 0.0, 0.0, 0.8 ); + mLensFlare.addFlare( flare ); + + flare.offset = 1.0; + flare.size = 150.0; + flare.tex = mDataBlock->textureList[FlareProjectileData::FPD_LENS0_TEX]; + flare.color.set( 1.0, 0.0, 0.0, 1.0 ); + mLensFlare.addFlare( flare ); + + flare.offset = 1.5; + flare.size = 100.0; + flare.tex = mDataBlock->textureList[FlareProjectileData::FPD_LENS0_TEX]; + flare.color.set( 1.0, 0.0, 0.0, 0.4 ); + mLensFlare.addFlare( flare ); + + flare.offset = 0.17; + flare.size = 150.0; + flare.tex = mDataBlock->textureList[FlareProjectileData::FPD_LENS0_TEX]; + flare.color.set( 1.0, 0.0, 0.0, 0.4 ); + mLensFlare.addFlare( flare ); + + flare.offset = 0.23; + flare.size = 80.0; + flare.tex = mDataBlock->textureList[FlareProjectileData::FPD_LENS0_TEX]; + flare.color.set( 1.0, 0.0, 0.0, 1.0 ); + mLensFlare.addFlare( flare ); + + +} + +//---------------------------------------------------------------------------- +// Got Heat? +//---------------------------------------------------------------------------- +F32 FlareProjectile::getHeat() const +{ + return 1.0f; +} + diff --git a/game/projFlareGrenade.h b/game/projFlareGrenade.h new file mode 100644 index 0000000..7c660e1 --- /dev/null +++ b/game/projFlareGrenade.h @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_PROJFLAREGRENADE +#define _H_PROJFLAREGRENADE + +#ifndef _PROJGRENADE_H_ +#include "game/projGrenade.h" +#endif +#ifndef _LENSFLARE_H_ +#include "dgl/lensFlare.h" +#endif + +class LensFlare; + +// ------------------------------------------------------------------------- +class FlareProjectileData : public GrenadeProjectileData +{ + typedef GrenadeProjectileData Parent; + + public: + enum FPDConsts + { + FPD_FLARE_TEX=0, + FPD_LENS0_TEX, + FPD_NUM_TEX, + }; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + F32 size; + F32 delay; + bool useLensFlare; + + StringTableEntry textureNameList[FPD_NUM_TEX]; + + + //-------------------------------------- load set variables + public: + TextureHandle textureList[FPD_NUM_TEX]; + + + public: + FlareProjectileData(); + ~FlareProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(FlareProjectileData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class FlareProjectile : public GrenadeProjectile +{ + typedef GrenadeProjectile Parent; + + enum FPConsts + { + FP_NUM_BLUR_FLARES = 5, + }; + + struct BlurFlare + { + F32 alpha; + Point3F pos; + F32 startAlpha; + Point3F screenPoint; + + BlurFlare() + { + alpha = 0.0; + startAlpha = 0.5; + pos.set( 0.0, 0.0, 0.0 ); + } + + }; + + private: + FlareProjectileData* mDataBlock; + F32 mLifetime; + F32 mElapsedTime; + BlurFlare mBlurList[ FP_NUM_BLUR_FLARES ]; + LensFlare mLensFlare; + + void renderFlare( const Point3F &camPos, F32 ang ); + void setupLensFlare(); + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + void processTick(const Move*); + void advanceTime( F32 dt ); + void renderObject(SceneState*, SceneRenderImage*); + void updateBlur(); + + public: + FlareProjectile(); + ~FlareProjectile(); + + virtual F32 getHeat() const; + + DECLARE_CONOBJECT(FlareProjectile); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_PROJFLAREGRENADE + diff --git a/game/projGrenade.cc b/game/projGrenade.cc new file mode 100644 index 0000000..dc2ea43 --- /dev/null +++ b/game/projGrenade.cc @@ -0,0 +1,933 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projGrenade.h" +#include "core/bitStream.h" +#include "sim/netConnection.h" +#include "game/shapeBase.h" +#include "math/mathIO.h" +#include "console/consoleTypes.h" +#include "game/explosion.h" +#include "terrain/waterBlock.h" +#include "game/splash.h" +#include "game/particleEngine.h" + +IMPLEMENT_CO_NETOBJECT_V1(GrenadeProjectile); +IMPLEMENT_CO_DATABLOCK_V1(GrenadeProjectileData); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +GrenadeProjectileData::GrenadeProjectileData() +{ + grenadeElasticity = 0.999; + grenadeFriction = 0.3; + + gravityMod = 1.0; + + isBallistic = true; + + armingDelayMS = 3000; + muzzleVelocity = 75; + + drag = 0; + density = 1; + + lifetimeMS = 20000; // default 20 sec max lifetime +} + +GrenadeProjectileData::~GrenadeProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void GrenadeProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("armingDelayMS", TypeS32, Offset(armingDelayMS, GrenadeProjectileData)); + addField("muzzleVelocity", TypeF32, Offset(muzzleVelocity, GrenadeProjectileData)); + addField("grenadeElasticity", TypeF32, Offset(grenadeElasticity, GrenadeProjectileData)); + addField("grenadeFriction", TypeF32, Offset(grenadeFriction, GrenadeProjectileData)); + addField("drag", TypeF32, Offset(drag, GrenadeProjectileData)); + addField("density", TypeF32, Offset(density, GrenadeProjectileData)); + addField("gravityMod", TypeF32, Offset(gravityMod, GrenadeProjectileData)); + addField("lifetimeMS", TypeS32, Offset(lifetimeMS, GrenadeProjectileData)); +} + + +bool GrenadeProjectileData::calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& sourceVel, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime) +{ + // Ok, this is a quartic calculation to determine the time to target. TODO: + // get the gravity from the environment. TODO: handle water + // + Point3F p = targetPos - sourcePos; + Point3F v = targetVel - sourceVel*velInheritFactor; + + F32 g = 9.81f * gravityMod; + + F32 a = 0.25 * (g * g); + F32 b = v.z * g; + F32 c = p.z * g + v.lenSquared() - (muzzleVelocity * muzzleVelocity); + F32 d = 2 * mDot(p, v); + F32 e = p.lenSquared(); + + F32 x[4]; + U32 numRealSolutions = mSolveQuartic(a, b, c, d, e, x); + + if (numRealSolutions == 0) { + // No solution is possible in the real numbers + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + return false; + } + + F32 minPos[2] = { 1e8, 1e8 }; + for (U32 i = 0; i < numRealSolutions; i++) { + if (x[i] > 0.0) { + if (x[i] < minPos[0]) { + minPos[1] = minPos[0]; + minPos[0] = x[i]; + } else if (x[i] < minPos[1]) { + minPos[1] = x[i]; + } + } + } + if (minPos[0] == 1e8) { + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + return false; + } + if (minPos[1] == 1e8) + minPos[1] = minPos[0]; + AssertFatal(minPos[0] != 1e8 && minPos[1] != 1e8 && minPos[0] > 0.0 && minPos[1] > 0.0, + "Error, bad calculation of minimum positive times!"); + + // Ok, we have solutions! + *outputVectorMin = targetPos; + *outputVectorMin += targetVel * minPos[0]; + *outputVectorMin -= Point3F(0, 0, -g) * 0.5 * minPos[0] * minPos[0]; + *outputVectorMin -= sourcePos; + *outputVectorMin /= minPos[0]; + *outputVectorMin -= sourceVel * velInheritFactor; + *outputVectorMin /= muzzleVelocity; + outputVectorMin->normalize(); + + *outputVectorMax = targetPos; + *outputVectorMax += targetVel * minPos[1]; + *outputVectorMax -= Point3F(0, 0, -g) * 0.5 * minPos[1] * minPos[1]; + *outputVectorMax -= sourcePos; + *outputVectorMax /= minPos[1]; + *outputVectorMax -= sourceVel * velInheritFactor; + *outputVectorMax /= muzzleVelocity; + outputVectorMax->normalize(); + + *outputMinTime = minPos[0]; + *outputMaxTime = minPos[1]; + return (*outputMinTime * 1000.0) < lifetimeMS; +} + + +//-------------------------------------------------------------------------- +bool GrenadeProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (armingDelayMS < 250) { + Con::warnf(ConsoleLogEntry::General, "GrenadeProjectileData(%s)::onAdd: arming delay below minimum allowed (250 ms)", getName()); + armingDelayMS = 250; + } + if (muzzleVelocity < 0.1f) { + Con::warnf(ConsoleLogEntry::General, "GrenadeProjectileData(%s)::onAdd: muzzle velocity beneath threshold", getName()); + muzzleVelocity = 0.1f; + } + if (grenadeElasticity < 0.0 || grenadeElasticity > 0.999) { + Con::warnf(ConsoleLogEntry::General, "GrenadeProjectileData(%s)::onAdd: elasticity bounded by range [0, 0.999] to prevent FP errors from accumulating", getName()); + grenadeElasticity = grenadeElasticity < 0.0 ? 0.0 : 0.999; + } + if (grenadeFriction < 0.0 || grenadeFriction > 1) { + Con::warnf(ConsoleLogEntry::General, "GrenadeProjectileData(%s)::onAdd: friction bounded by range [0, 1]", getName()); + grenadeFriction = grenadeFriction < 0.0 ? 0.0 : 1; + } + if (drag < 0.0) { + Con::warnf(ConsoleLogEntry::General, "GrenadeProjectileData(%s)::onAdd: drag bounded by range [0, inf]", getName()); + drag = 0.0; + } + if (density < 0.1 || density > 10) { + Con::warnf(ConsoleLogEntry::General, "GrenadeProjectileData(%s)::onAdd: density bounded by range [0.1, 10]", getName()); + density = density < 0.1 ? 0.1 : 10; + } + + if( lifetimeMS < 50 ){ + Con::warnf(ConsoleLogEntry::General, "GrenadeProjectileData(%s)::onAdd: lifetimeMS < 50", getName()); + if( lifetimeMS == 0 ) + { + Con::warnf(ConsoleLogEntry::General, "GrenadeProjectileData(%s)::onAdd: lifetimeMS == 0", getName()); + lifetimeMS = 1000; + } + } + + // Make sure that arming delay is an even multiple of a tick + armingDelayMS = (armingDelayMS + TickMs - 1) & ~(TickMs - 1); + + return true; +} + +//-------------------------------------------------------------------------- +void GrenadeProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(armingDelayMS); + stream->write(muzzleVelocity); + stream->write(grenadeElasticity); + stream->write(grenadeFriction); + stream->write(drag); + stream->write(density); + stream->write(gravityMod); + stream->write(lifetimeMS); +} + +void GrenadeProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&armingDelayMS); + stream->read(&muzzleVelocity); + stream->read(&grenadeElasticity); + stream->read(&grenadeFriction); + stream->read(&drag); + stream->read(&density); + stream->read(&gravityMod); + stream->read(&lifetimeMS); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +GrenadeProjectile::GrenadeProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + + mDeleteTick = -1; + mInWater = false; + mLastInWater = false; + mQuickSplash = false; + mSentInitialUpdate = false; +} + +GrenadeProjectile::~GrenadeProjectile() +{ + // +} + +//-------------------------------------------------------------------------- +void GrenadeProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void GrenadeProjectile::consoleInit() +{ + // +} + + +bool GrenadeProjectile::calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime) +{ + if (mHidden == true) { + impactTime = 0; + pointOfImpact.set(0, 0, 0); + return false; + } + + const F32 timeSlice = 0.25; + + Point3F currPos = mCurrPosition; + Point3F currVel = mCurrVelocity; + + for (F32 currT = 0.0; currT <= simTime; currT += timeSlice) { + Point3F newVel = currVel + Point3F(0, 0, -9.81 * mDataBlock->gravityMod) * timeSlice; + Point3F newPos = currPos + newVel * timeSlice; + + U32 mask = PlayerObjectType | TerrainObjectType | InteriorObjectType | WaterObjectType | ForceFieldObjectType; + RayInfo rayInfo; + if (gServerContainer.castRay(currPos, newPos, mask, &rayInfo)) { + pointOfImpact = rayInfo.point; + impactTime = currT; + impactTime += ((currPos - pointOfImpact).len() / (currPos - newPos).len()) * timeSlice; + return true; + } + + currPos = newPos; + currVel = newVel; + } + + impactTime = 0; + pointOfImpact.set(0, 0, 0); + return false; +} + + +//-------------------------------------------------------------------------- +bool GrenadeProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mArmedTick = mDataBlock->armingDelayMS / TickMs; + mDeleteTick = mDataBlock->lifetimeMS / TickMs; + + if (isServerObject()) { + mCurrPosition = mInitialPosition; + mCurrVelocity = mInitialDirection * mDataBlock->muzzleVelocity; + mCurrVelocity += mExcessDir * mExcessVel; + + // Pull back the projectile a bit. This prevents problems that occur when the + // weapon is jammed against the wall and the projectile starts on the other side + // of it. - bramage + Point3F dir = mCurrVelocity; + dir.normalizeSafe(); + mCurrPosition -= dir * 0.15; + + } + else + { + Point3F newPos = mInitialPosition + mCurrVelocity * mCurrTick * TickMs / 1000.0; + + if( determineSplash( mInitialPosition, newPos) ) + { + createSplash( mSplashPos ); + } + } + + addToScene(); + + return true; +} + + +void GrenadeProjectile::onRemove() +{ + if (isClientObject()) + updateSound(Point3F(0, 0, 0), Point3F(0, 0, 0), false); + removeFromScene(); + + Parent::onRemove(); +} + + +bool GrenadeProjectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + +//-------------------------------------------------------------------------- +void GrenadeProjectile::explode(const Point3F& p, const Point3F& n, const U32 collideType ) +{ + // Make sure we don't explode twice... + if (mHidden == true) + return; + + if (isServerObject()) { + // Do what the server needs to do, damage the surrounding objects, etc. + mExplosionPosition = p + (n*0.01); + mExplosionNormal = n; + + char buffer[128]; + dSprintf(buffer, sizeof(buffer), "%f %f %f", mExplosionPosition.x, + mExplosionPosition.y, + mExplosionPosition.z); + Con::executef(mDataBlock, 4, "onExplode", scriptThis(), buffer, "1.0"); + + setMaskBits(ExplosionMask); + } else { + // Client just plays the explosion at the right place... + // + Explosion* pExplosion = NULL; + + F32 waterHeight; + if( pointInWater( (Point3F &)p, &waterHeight ) && mDataBlock->underwaterExplosion ) + { + pExplosion = new Explosion; + + F32 depth = waterHeight - p.z; + if( depth >= mDataBlock->depthTolerance ) + { + pExplosion->onNewDataBlock(mDataBlock->underwaterExplosion); + } + else + { + pExplosion->onNewDataBlock(mDataBlock->explosion); + } + } + 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, "LinearProjectile(%s)::explode: couldn't register explosion", + mDataBlock->getName() ); + delete pExplosion; + pExplosion = NULL; + } + } + + // Client object + updateSound(Point3F(0, 0, 0), Point3F(0, 0, 0), false); + } + + mHidden = true; +} + + +//-------------------------------------------------------------------------- +Point3F GrenadeProjectile::getVelocity() const +{ + return mCurrVelocity; +} + + +//-------------------------------------------------------------------------- +void GrenadeProjectile::processTick(const Move* move) +{ + F32 timeLeft; + RayInfo rInfo; + Point3F oldPosition; + Point3F newPosition; + + Parent::processTick(move); + + if (isServerObject()) { + // server + if (mDeleteTick != -1 && mCurrTick >= mDeleteTick) { + deleteObject(); + return; + } else if (mHidden == true) { + // already exploded, back out + return; + } + + // Otherwise, we have to do some simulation work. + oldPosition = mCurrPosition; + computeNewState(&newPosition, &mCurrVelocity); + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->disableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->disableCollision(); + + timeLeft = 1.0; + + // if we hit a lava block explode + if(getContainer()->castRay(oldPosition, newPosition, WaterObjectType, &rInfo) == true) + { + WaterBlock *wb = dynamic_cast(rInfo.object); + if(wb) + { + if(wb->isLava(wb->getLiquidType())) + { + MatrixF xform(true); + xform.setColumn(3, rInfo.point); + setTransform(xform); + mCurrPosition = rInfo.point; + mCurrVelocity = Point3F(0, 0, 0); + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->enableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->enableCollision(); + + explode(rInfo.point, rInfo.normal); + + mDeleteTick = mCurrTick + DeleteWaitTicks; + return; + } + } + } + + // Make sure we escape if we get stuck somehow... + static U32 sMaxBounceCount = 5; + U32 bounceCount = 0; + while (bounceCount++ < sMaxBounceCount) { + if (getContainer()->castRay(oldPosition, newPosition, + csmDynamicCollisionMask | csmStaticCollisionMask, + &rInfo) == true) + { + + + if((rInfo.object->getType() & csmStaticCollisionMask) == 0) + setMaskBits(BounceMask); + + // Next order of business: do we explode on this hit? + if (mCurrTick > mArmedTick) { + MatrixF xform(true); + xform.setColumn(3, rInfo.point); + setTransform(xform); + mCurrPosition = rInfo.point; + mCurrVelocity = Point3F(0, 0, 0); + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->enableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->enableCollision(); + + explode(rInfo.point, rInfo.normal); + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->disableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->disableCollision(); + + mDeleteTick = mCurrTick + DeleteWaitTicks; + break; + } + + // 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->grenadeFriction; + + // Now, take elasticity into account for modulating the speed of the grenade + mCurrVelocity *= mDataBlock->grenadeElasticity; + + timeLeft = timeLeft * (1.0 - rInfo.t); + oldPosition = rInfo.point + rInfo.normal * 0.05; + newPosition = oldPosition + (mCurrVelocity * ((timeLeft/1000.0) * TickMs)); + } else { + // No problems, just set the end position, and we're golden. + mCurrPosition = newPosition; + MatrixF xform(true); + xform.setColumn(3, mCurrPosition); + setTransform(xform); + + break; + } + } + + if( !mSentInitialUpdate && determineSplash( oldPosition, newPosition ) ) + { + mQuickSplash = true; + } + + if (mSourceIdTimeoutTicks && bool(mSourceObject)) + mSourceObject->enableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->enableCollision(); + } else { + // client + if (mHidden == true) + return; + + // Otherwise, we have to do some simulation work. + oldPosition = mCurrPosition; + computeNewState(&newPosition, &mCurrVelocity); + + if (bool(mSourceObject)) + mSourceObject->disableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->disableCollision(); + + timeLeft = 1.0; + + // Make sure we escape if we get stuck somehow... + static U32 sMaxBounceCount = 5; + U32 bounceCount = 0; + while (bounceCount++ < sMaxBounceCount) { + if (getContainer()->castRay(oldPosition, newPosition, + csmDynamicCollisionMask | csmStaticCollisionMask, + &rInfo) == true) { + + // Next order of business: do we explode on this hit? + if (mCurrTick > mArmedTick) { + MatrixF xform(true); + xform.setColumn(3, rInfo.point); + setTransform(xform); + mCurrPosition = rInfo.point; + mCurrVelocity = Point3F(0, 0, 0); + + explode(rInfo.point, rInfo.normal, rInfo.object->getType() ); + break; + } + + // 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->grenadeFriction; + + // Now, take elasticity into account for modulating the speed of the grenade + mCurrVelocity *= mDataBlock->grenadeElasticity; + + timeLeft = timeLeft * (1.0 - rInfo.t); + oldPosition = rInfo.point + rInfo.normal * 0.05; + newPosition = oldPosition + (mCurrVelocity * ((timeLeft/1000.0) * TickMs)); + } else { + // No problems, just set the end position, and we're golden. + break; + } + } + mCurrDeltaBase = newPosition; + mCurrBackVector = mCurrPosition - newPosition; + if( !mInWater ) + { + emitParticles(mCurrPosition, newPosition, mCurrVelocity, TickMs); + } + mCurrPosition = newPosition; + + if( determineSplash( oldPosition, newPosition ) ) + { + createSplash( mSplashPos ); + } + + if (bool(mSourceObject)) + mSourceObject->enableCollision(); + if (mSourceIdTimeoutTicks && bool(mVehicleObject)) + mVehicleObject->enableCollision(); + + MatrixF temp(true); + temp.setColumn(3, mCurrDeltaBase); + setTransform(temp); + } +} + + +void GrenadeProjectile::interpolateTick(F32 delta) +{ + Parent::interpolateTick(delta); + + Point3F interpPos = mCurrDeltaBase + mCurrBackVector * delta; + MatrixF xform = getTransform(); + xform.setColumn(3, interpPos); + setRenderTransform(xform); + + updateSound(interpPos, getVelocity(), true); +} + + +void GrenadeProjectile::advanceTime(F32 dt) +{ + updateBubbles( dt ); + Parent::advanceTime(dt); + + if (mInWater) + { + mUseUnderwaterLight = true; + } + else + { + mUseUnderwaterLight = false; + } + + if (mHidden == true || dt == 0.0) + return; +} + + +//-------------------------------------------------------------------------- +U32 GrenadeProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & GameBase::InitialUpdateMask)) + { + // Initial update + + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, mCurrVelocity); + stream->writeRangedU32(mCurrTick, 0, MaxLivingTicks); + + stream->writeFlag( mQuickSplash ); + + if (stream->writeFlag((mask & ExplosionMask) && mHidden)) + { + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, mExplosionNormal); + } + + + if (bool(mSourceObject)) + { + // 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 + { + stream->writeFlag(false); + } + + if (bool(mVehicleObject)) + { + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + S32 ghostIndex = con->getGhostIndex(mVehicleObject); + if (stream->writeFlag(ghostIndex != -1)) + { + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount); + } + } + else + { + stream->writeFlag(false); + } + + mSentInitialUpdate = true; + } + else + { + if (stream->writeFlag(mask & BounceMask)) { + // Bounce against dynamic object + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, mCurrVelocity); + } + + if (stream->writeFlag(mask & ExplosionMask)) { + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, mExplosionNormal); + } + } + + return retMask; +} + +void GrenadeProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) + { + // Initial update + + + mathRead(*stream, &mCurrPosition); + mathRead(*stream, &mCurrVelocity); + mCurrTick = stream->readRangedU32(0, MaxLivingTicks); + + if( stream->readFlag() ) + { + resolveInitialSplash(); + } + + + if (stream->readFlag()) + { + // explosion on the server... + Point3F explodePoint; + Point3F explodeNormal; + mathRead(*stream, &explodePoint); + mathRead(*stream, &explodeNormal); + + explode(explodePoint, explodeNormal); + } + + + if( pointInWater( mCurrPosition ) ) + { + mInWater = true; + mLastInWater = true; + } + + 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; + } + + if (stream->readFlag()) + { + mVehicleObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + NetObject* pObject = con->resolveGhost(mVehicleObjectId); + if (pObject != NULL) + mVehicleObject = dynamic_cast(pObject); + if (bool(mVehicleObject) == false) + Con::errorf(ConsoleLogEntry::General, "LinearProjectile::unpackUpdate: could not resolve source ghost properly on initial update"); + } + else + { + mVehicleObjectId = 0; + mVehicleObject = NULL; + } + } + else + { + if (stream->readFlag()) + { + mathRead(*stream, &mCurrPosition); + mathRead(*stream, &mCurrVelocity); + } + + if (stream->readFlag()) + { + // explosion on the server... + Point3F explodePoint; + Point3F explodeNormal; + mathRead(*stream, &explodePoint); + mathRead(*stream, &explodeNormal); + + explode(explodePoint, explodeNormal); + } + } +} + +//-------------------------------------------------------------------------- +void GrenadeProjectile::computeNewState(Point3F* newPosition, + Point3F* newVelocity) +{ + // We simulate forward TickMs milliseconds. Remember that we have to use + // dumb velocity to keep the server and the client in agreement. We return a + // fully precise velocity vector tho. + // DMMTODO: we need adjustable gravity in this function... + // DMMNOTE: we might want to step up to a RK solver here rather than the + // lame Euler solver below... + // + Point3F force = Point3F(0, 0, -9.81 * mDataBlock->gravityMod); + + if (mDataBlock->drag != 0.0 || mDataBlock->density != 1) { + + bool wetStart = pointInWater( mCurrPosition ); + + mLastInWater = mInWater; + mInWater = wetStart; + + if (wetStart) { + + // Compute new forces based on drag and density... + if (mDataBlock->drag != 0.0) + force += -mCurrVelocity * (mDataBlock->drag * 15); + if (mDataBlock->density != 0.0) { + + } + } + + } + + *newVelocity = mCurrVelocity; + *newVelocity += force * (F32(TickMs) / 1000.0f); + + *newPosition = mCurrPosition; + *newPosition += (*newVelocity) * (F32(TickMs) / 1000.0f); +} + +//-------------------------------------------------------------------------- +bool GrenadeProjectile::determineSplash( Point3F &oldPos, Point3F &newPos ) +{ + bool startInWater = pointInWater( oldPos ); + bool endInWater = pointInWater( newPos ); + bool noRet = (!startInWater && endInWater) || (startInWater && !endInWater); + if( !noRet ) return false; + + + Point3F dir = newPos - oldPos; + if( dir.isZero() ) return false; + dir.normalize(); + + Point3F start = oldPos - dir; + Point3F end = newPos + dir; + + RayInfo rInfo; + Container* pContainer = isServerObject() ? &gServerContainer : &gClientContainer; + if( pContainer->castRay( start, end, WaterObjectType, &rInfo) ) + { + mSplashPos = rInfo.point; + return true; + } + + return false; +} + +//-------------------------------------------------------------------------- +void GrenadeProjectile::updateBubbles( F32 dt ) +{ + // update bubbles + Point3F moveDir = getVelocity(); + + if( mInWater ) + { + if( mBubbleEmitter ) + { + Point3F emissionPoint = getPosition(); + mBubbleEmitter->emitParticles( mLastPos, emissionPoint, Point3F( 0.0, 0.0, 1.0 ), + moveDir, dt * 1000.0 ); + } + } +} + +//-------------------------------------------------------------------------- +// Server detected splash between mInitialPosition and the mCurrPosition that +// it sent to the client. This function finds the splash point and plays +// the animation +//-------------------------------------------------------------------------- +void GrenadeProjectile::resolveInitialSplash() +{ + VectorF dir = mCurrVelocity; + dir.normalize(); + Point3F oldPos = mCurrPosition - dir * 10.0; + determineSplash( oldPos, mCurrPosition ); + createSplash( mSplashPos ); +} diff --git a/game/projGrenade.h b/game/projGrenade.h new file mode 100644 index 0000000..755920b --- /dev/null +++ b/game/projGrenade.h @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GRENADEPROJECTILE_H_ +#define _GRENADEPROJECTILE_H_ + +#ifndef _PROJECTILE_H_ +#include "game/projectile.h" +#endif + +//-------------------------------------------------------------------------- +class GrenadeProjectileData : public ProjectileData +{ + typedef ProjectileData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + F32 grenadeElasticity; + F32 grenadeFriction; + + F32 gravityMod; + + F32 muzzleVelocity; + S32 armingDelayMS; + + F32 drag; + F32 density; + + U32 lifetimeMS; + + //-------------------------------------- load set variables + public: + + public: + GrenadeProjectileData(); + ~GrenadeProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + + bool calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& sourceVel, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime); + + DECLARE_CONOBJECT(GrenadeProjectileData); + static void initPersistFields(); +}; + + +class GrenadeProjectile : public Projectile +{ + typedef Projectile Parent; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + protected: + void processTick(const Move*); + void interpolateTick(F32); + void advanceTime(F32); + + Point3F getVelocity() const; + + public: + enum GrenConstants { + InitialDirectionBits = 11, + MaxLivingTicks = 4095 + }; + + enum GrenUpdateMasks { + BounceMask = Parent::NextFreeMask, + ExplosionMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + + private: + GrenadeProjectileData* mDataBlock; + + // splash / water related + protected: + Point3F mSplashPos; + bool mLastInWater; + bool mInWater; + bool mQuickSplash; + bool mSentInitialUpdate; + + bool determineSplash( Point3F &oldPos, Point3F &newPos ); + void resolveInitialSplash(); + void updateBubbles( F32 dt ); + + + protected: + S32 mDeleteTick; + S32 mArmedTick; + + Point3F mCurrPosition; + Point3F mCurrVelocity; + + Point3F mCurrDeltaBase; // only valid on the client + Point3F mCurrBackVector; + + Point3F mExplosionPosition; + Point3F mExplosionNormal; + + + void computeNewState(Point3F* newPosition, + Point3F* newVelocity); + void explode(const Point3F&, const Point3F&, const U32 collideType = 0 ); + + bool calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime); + + + public: + GrenadeProjectile(); + ~GrenadeProjectile(); + + DECLARE_CONOBJECT(GrenadeProjectile); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_GRENADEPROJECTILE + diff --git a/game/projLinearFlare.cc b/game/projLinearFlare.cc new file mode 100644 index 0000000..c19524e --- /dev/null +++ b/game/projLinearFlare.cc @@ -0,0 +1,469 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projLinearFlare.h" +#include "core/bitStream.h" +#include "console/consoleTypes.h" + +#include "dgl/dgl.h" +#include "platformWIN32/platformGL.h" +#include "scenegraph/sceneState.h" + +IMPLEMENT_CO_DATABLOCK_V1(LinearFlareProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(LinearFlareProjectile); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +LinearFlareProjectileData::LinearFlareProjectileData() +{ + numFlares = 25; + size[0] = .2; + size[1] = .25; + size[2] = 3.0; + flareColor = ColorF(0.25, 0.25, 1); + + flareModTexture = ""; + flareBaseTexture = ""; +} + +LinearFlareProjectileData::~LinearFlareProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void LinearFlareProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("numFlares", TypeS32, Offset(numFlares, LinearFlareProjectileData)); + addField("size", TypeF32, Offset(size, LinearFlareProjectileData), NUM_SIZES); + addField("flareColor", TypeColorF, Offset(flareColor, LinearFlareProjectileData)); + + addField("flareModTexture", TypeString, Offset(flareModTexture, LinearFlareProjectileData)); + addField("flareBaseTexture", TypeString, Offset(flareBaseTexture, LinearFlareProjectileData)); +} + + +//-------------------------------------------------------------------------- +bool LinearFlareProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (numFlares < 0) { + Con::warnf(ConsoleLogEntry::General, "LinearFlareProjectile(%s)::onAdd: must have at least no flares", getName()); + numFlares = 0; + } + flareColor.clamp(); + + if (!flareModTexture || !flareModTexture[0] || !flareBaseTexture || !flareBaseTexture[0]) { + Con::warnf(ConsoleLogEntry::General, "LinearFlareProjectile(%s)::onAdd: must have mod and base textures", getName()); + return false; + } + if (dStrlen(flareModTexture) >= 255 || dStrlen(flareBaseTexture) >= 255) { + Con::warnf(ConsoleLogEntry::General, "LinearFlareProjectile(%s)::onAdd: must have mod and base textures with strlen < 255", getName()); + return false; + } + + return true; +} + + +bool LinearFlareProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if(!server) + { + baseHandle = TextureHandle(flareBaseTexture, MeshTexture); + modHandle = TextureHandle(flareModTexture, MeshTexture); + if((TextureObject*)baseHandle == NULL) + { + dSprintf(errorBuffer, sizeof(errorBuffer), "Unable to load texture: %s", flareBaseTexture); + return false; + } + if((TextureObject*)modHandle == NULL) + { + dSprintf(errorBuffer, sizeof(errorBuffer), "Unable to load texture: %s", flareModTexture); + return false; + } + } + return true; +} + + +//-------------------------------------------------------------------------- +void LinearFlareProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(numFlares); + stream->write(flareColor); + stream->writeString(flareModTexture); + stream->writeString(flareBaseTexture); + + for( U32 i=0; iwrite( size[i] ); + } +} + +void LinearFlareProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&numFlares); + stream->read(&flareColor); + flareModTexture = stream->readSTString(); + flareBaseTexture = stream->readSTString(); + + for( U32 i=0; iread( &size[i] ); + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +LinearFlareProjectile::LinearFlareProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + + mFlares = NULL; +} + +LinearFlareProjectile::~LinearFlareProjectile() +{ + delete [] mFlares; + mFlares = NULL; +} + +//-------------------------------------------------------------------------- +void LinearFlareProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +//-------------------------------------------------------------------------- +bool LinearFlareProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + U32 numSizes = LinearFlareProjectileData::NUM_SIZES; + + mObjBox.min.set(mDataBlock->size[numSizes], mDataBlock->size[numSizes], mDataBlock->size[numSizes]); + mObjBox.max.set(mDataBlock->size[numSizes], mDataBlock->size[numSizes], mDataBlock->size[numSizes]); + mObjBox.min.neg(); + resetWorldBox(); + + if (isClientObject()) { + if (mDataBlock->numFlares > 0) + mFlares = new Flare[mDataBlock->numFlares]; + else + mFlares = NULL; + + for (U32 i = 0; i < mDataBlock->numFlares; i++) + mFlares[i].active = false; + } + + return true; +} + + +void LinearFlareProjectile::onRemove() +{ + Parent::onRemove(); +} + + +bool LinearFlareProjectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +void LinearFlareProjectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if (mDataBlock->activateDelayMS != -1 && + mCurrTick * TickMs < mDataBlock->activateDelayMS) + return; + + static MRandomLCG sRand(50817); + for (U32 i = 0; i < mDataBlock->numFlares; i++) { + if (mFlares[i].active) { + mFlares[i].currT += dt; + if (mFlares[i].currT > mFlares[i].t2) { + mFlares[i].active = false; + } + } + + if (mFlares[i].active == false) { + mFlares[i].minTheta = mDegToRad(87.0f); + mFlares[i].maxTheta = mDegToRad(65.0f + 20 * sRand.randF()); + mFlares[i].h0 = mDataBlock->size[0]; + mFlares[i].h1 = mDataBlock->size[1] + 0.25 * sRand.randF(); + mFlares[i].h2 = mDataBlock->size[2] + sRand.randF(); + + mFlares[i].a0 = 0.0; + mFlares[i].a1 = 1.0; + mFlares[i].a2 = 0.0; + mFlares[i].currT = 0.0; + mFlares[i].active = true; + mFlares[i].normal = Point3F(1.0 - 2*sRand.randF(), 1.0 - 2*sRand.randF(), 1.0 - 2*sRand.randF()); + mFlares[i].normal.normalize(); + + mFlares[i].t0 = 0.0; + mFlares[i].t1 = 0.15 + 0.225 * sRand.randF(); + mFlares[i].t2 = 0.4 + 0.595 * sRand.randF(); + } + + mFlares[i].generatePoints(); + } +} + + +//-------------------------------------------------------------------------- +void LinearFlareProjectile::renderObject(SceneState* state, SceneRenderImage *sri /*sceneImage*/) +{ + if( mHitWater && mPlayedSplash ) return; + + if( mProjectileShape ) + { + Parent::renderObject( state, sri ); + } + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 fogAmount = state->getHazeAndFog(cameraOffset.len(),cameraOffset.z); + + MatrixF mv; + dglGetModelview(&mv); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&getRenderTransform()); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, mDataBlock->modHandle.getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glEnable(GL_CULL_FACE); + glBlendFunc(GL_ONE, GL_ONE); + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + + + for (U32 i = 0; i < mDataBlock->numFlares; i++) { + if (mFlares[i].active) + mFlares[i].render(mDataBlock->flareColor, mFadeValue * (1.0 - fogAmount)); + } + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + + if( !mProjectileShape ) + { + Point3F ax0; + Point3F ax1; + Point3F center; + mv.getRow(0, &ax0); + mv.getRow(2, &ax1); + getRenderTransform().getColumn(3, ¢er); + + glDisable(GL_CULL_FACE); + glBindTexture(GL_TEXTURE_2D, mDataBlock->baseHandle.getGLName()); + glColor3f(mSqrt(mDataBlock->flareColor.red) * mFadeValue * (1.0 - fogAmount), + mSqrt(mDataBlock->flareColor.green) * mFadeValue * (1.0 - fogAmount), + mSqrt(mDataBlock->flareColor.blue) * mFadeValue * (1.0 - fogAmount)); + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f(0, 0); + glVertex3fv(center + ((ax0 * -0.6) + (ax1 * -0.6)) * mDataBlock->scale.x); + glTexCoord2f(0, 1); + glVertex3fv(center + ((ax0 * -0.6) + (ax1 * 0.6)) * mDataBlock->scale.x); + glTexCoord2f(1, 1); + glVertex3fv(center + ((ax0 * 0.6) + (ax1 * 0.6)) * mDataBlock->scale.x); + glTexCoord2f(1, 0); + glVertex3fv(center + ((ax0 * 0.6) + (ax1 * -0.6)) * mDataBlock->scale.x); + glEnd(); + glColor3f(mFadeValue * (1.0 - fogAmount), + mFadeValue * (1.0 - fogAmount), + mFadeValue * (1.0 - fogAmount)); + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f(0, 0); + glVertex3fv(center + ((ax0 * -0.3) + (ax1 * -0.3)) * mDataBlock->scale.x); + glTexCoord2f(0, 1); + glVertex3fv(center + ((ax0 * -0.3) + (ax1 * 0.3)) * mDataBlock->scale.x); + glTexCoord2f(1, 1); + glVertex3fv(center + ((ax0 * 0.3) + (ax1 * 0.3)) * mDataBlock->scale.x); + glTexCoord2f(1, 0); + glVertex3fv(center + ((ax0 * 0.3) + (ax1 * -0.3)) * mDataBlock->scale.x); + glEnd(); + } + + + + glDisable(GL_CULL_FACE); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +U32 LinearFlareProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // + + return retMask; +} + +void LinearFlareProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + // +} + + +//-------------------------------------------------------------------------- +void LinearFlareProjectile::Flare::generatePoints() +{ + Point3F axisz = normal; + Point3F axisx; + Point3F axisy; + + if (mFabs(axisz.x) < 0.9) { + mCross(axisz, Point3F(1, 0, 0), &axisy); + axisy.normalize(); + mCross(axisz, axisy, &axisx); + axisx.normalize(); + axisx.neg(); + } else { + mCross(axisz, Point3F(0, 1, 0), &axisy); + axisy.normalize(); + mCross(axisz, axisy, &axisx); + axisx.normalize(); + axisx.neg(); + } + + F32 currTheta; + F32 currH1; + if (currT < t1) { + currTheta = minTheta; + currH1 = h1 + (h2 - h1) * (currT / t1); + } else { + currTheta = minTheta + ((maxTheta - minTheta) * ((currT - t1) / (t2 - t1))); + currH1 = h2; + } + F32 zmul = 1 - mSin(currTheta); + + axisx *= mCos(currTheta); + axisy *= mCos(currTheta); + + points[0] = (axisz - axisx - axisy) * h0; + points[1] = (axisz - axisy ) * h0; + points[2] = (axisz + axisx - axisy) * h0; + points[3] = (axisz + axisx ) * h0; + points[4] = (axisz + axisx + axisy) * h0; + points[5] = (axisz + axisy ) * h0; + points[6] = (axisz - axisx + axisy) * h0; + points[7] = (axisz - axisx ) * h0; + + points[8] = ((axisz - axisx - axisy) * currH1) - axisz * zmul; + points[9] = ((axisz - axisy ) * currH1) - axisz * zmul; + points[10] = ((axisz + axisx - axisy) * currH1) - axisz * zmul; + points[11] = ((axisz + axisx ) * currH1) - axisz * zmul; + points[12] = ((axisz + axisx + axisy) * currH1) - axisz * zmul; + points[13] = ((axisz + axisy ) * currH1) - axisz * zmul; + points[14] = ((axisz - axisx + axisy) * currH1) - axisz * zmul; + points[15] = ((axisz - axisx ) * currH1) - axisz * zmul; +} + +void LinearFlareProjectile::Flare::render(const ColorF& color, const F32 currFade) +{ + F32 currA; + if (currT < t1) { + currA = a0 + (a1 - a0) * (currT / t1); + } else { + currA = a1 + (a2 - a1) * ((currT - t1) / (t2 - t1)); + } + currA *= currFade; + + ColorF base = color * currA * currFade; + ColorF bright = color * currA * 0.5; + ColorF dim = color * (0.25 * currA); + + for (U32 i = 0; i <= 6; i+=2) { + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f(0.5, 0.9); + glColor3f(base.red, base.green, base.blue); + glVertex3fv(points[i + 1]); + + glTexCoord2f(0, 0.9); + glColor3f(base.red, base.green, base.blue); + glVertex3fv(points[i + 0]); + + glTexCoord2f(0, 0.1); + glColor3f(bright.red, bright.green, bright.blue); + glVertex3fv(points[i + 8 + 0]); + + glTexCoord2f(0.5, 0.1); + glColor3f(dim.red, dim.green, dim.blue); + glVertex3fv(points[i + 8 + 1]); + + glTexCoord2f(1, 0.1); + glColor3f(bright.red, bright.green, bright.blue); + glVertex3fv(points[8 + ((i + 2) & 0x7)]); + + glTexCoord2f(1, 0.9); + glColor3f(base.red, base.green, base.blue); + glVertex3fv(points[(i + 2) & 0x7]); + glEnd(); + + } +} diff --git a/game/projLinearFlare.h b/game/projLinearFlare.h new file mode 100644 index 0000000..3fe0fde --- /dev/null +++ b/game/projLinearFlare.h @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_LINEARFLAREPROJECTILE +#define _H_LINEARFLAREPROJECTILE + +#ifndef _LINEARPROJECTILE_H_ +#include "game/linearProjectile.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif + +// ------------------------------------------------------------------------- +class LinearFlareProjectileData : public LinearProjectileData +{ + typedef LinearProjectileData Parent; + + public: + enum Consts + { + NUM_SIZES = 3, + }; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + S32 numFlares; + F32 size[NUM_SIZES]; + ColorF flareColor; + + StringTableEntry flareModTexture; + StringTableEntry flareBaseTexture; + + //-------------------------------------- load set variables + public: + TextureHandle baseHandle; + TextureHandle modHandle; + + public: + LinearFlareProjectileData(); + ~LinearFlareProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(LinearFlareProjectileData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class LinearFlareProjectile : public LinearProjectile +{ + typedef LinearProjectile Parent; + + private: + LinearFlareProjectileData* mDataBlock; + + struct Flare { + F32 minTheta; + F32 maxTheta; + + F32 h0; + F32 h1; + F32 h2; + + F32 t0; + F32 t1; + F32 t2; + + F32 a0; + F32 a1; + F32 a2; + + F32 currT; + bool active; + Point3F normal; + + Point3F points[16]; + void generatePoints(); + void render(const ColorF&, const F32); + }; + Flare* mFlares; + TextureHandle mBaseHandle; + TextureHandle mHandle; + + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + public: + LinearFlareProjectile(); + ~LinearFlareProjectile(); + + DECLARE_CONOBJECT(LinearFlareProjectile); + static void initPersistFields(); + + void advanceTime(F32); + void renderObject(SceneState*, SceneRenderImage*); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_LINEARFLAREPROJECTILE + diff --git a/game/projRepair.cc b/game/projRepair.cc new file mode 100644 index 0000000..1ae8d5b --- /dev/null +++ b/game/projRepair.cc @@ -0,0 +1,674 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projRepair.h" +#include "core/bitStream.h" +#include "console/consoleTypes.h" +#include "game/shapeBase.h" +#include "sim/netConnection.h" +#include "dgl/dgl.h" +#include "platformWIN32/platformGL.h" +#include "scenegraph/sceneState.h" +#include "math/mRandom.h" +#include "dgl/splineUtil.h" +#include "game/gameConnection.h" + +IMPLEMENT_CO_DATABLOCK_V1(RepairProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(RepairProjectile); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +RepairProjectileData::RepairProjectileData() +{ + beamRange = 10.0f; + beamWidth = 0.15; + numSegments = 20; + texRepeat = 1.0/5.0; + beamSpeed = 1.0; + blurFreq = 10.0; + blurLifetime = 1.0; + cutoffAngle = 40.0; + + textureNames[0] = "special/redbump2"; + textureNames[1] = "special/redflare"; + +} + +//-------------------------------------------------------------------------- +RepairProjectileData::~RepairProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +bool RepairProjectileData::calculateAim(const Point3F& targetPos, + const Point3F& /*targetVel*/, + const Point3F& sourcePos, + const Point3F& /*sourceVel*/, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime) +{ + //make sure the source and target points are within range + if ((targetPos - sourcePos).len() >= beamRange) + return false; + else + { + *outputVectorMin = targetPos - sourcePos; + *outputMinTime = 0.001f; + outputVectorMin->normalize(); + *outputVectorMax = *outputVectorMin; + *outputMaxTime = *outputMinTime; + + return true; + } +} + + +//-------------------------------------------------------------------------- +void RepairProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("beamRange", TypeF32, Offset(beamRange, RepairProjectileData)); + addField("beamWidth", TypeF32, Offset(beamWidth, RepairProjectileData)); + addField("beamSpeed", TypeF32, Offset(beamSpeed, RepairProjectileData)); + addField("texRepeat", TypeF32, Offset(texRepeat, RepairProjectileData)); + addField("textures", TypeString, Offset(textureNames, RepairProjectileData), RT_NUM_TEX); + addField("numSegments", TypeS32, Offset(numSegments, RepairProjectileData)); + addField("blurFreq", TypeF32, Offset(blurFreq, RepairProjectileData)); + addField("blurLifetime", TypeF32, Offset(blurLifetime, RepairProjectileData)); + addField("cutoffAngle", TypeF32, Offset(cutoffAngle, RepairProjectileData)); + + +} + + +//-------------------------------------------------------------------------- +bool RepairProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (beamRange < 2.0) { + Con::warnf(ConsoleLogEntry::General, "RepairProjectileData(%s)::onAdd: beamRange must be >= 2", getName()); + beamRange = 2.0; + } + + return true; +} + +//-------------------------------------------------------------------------- +bool RepairProjectileData::preload( bool server, char errorBuffer[256] ) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if( server == false ) + { + for( int i=0; iwrite(beamRange); + stream->write(beamWidth); + stream->write(numSegments); + stream->write(beamSpeed); + stream->write(texRepeat); + stream->write(blurFreq); + stream->write(blurLifetime); + stream->write(cutoffAngle); + + + for( int i=0; iwriteString( textureNames[i] ); + } +} + +//-------------------------------------------------------------------------- +void RepairProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&beamRange); + stream->read(&beamWidth); + stream->read(&numSegments); + stream->read(&beamSpeed); + stream->read(&texRepeat); + stream->read(&blurFreq); + stream->read(&blurLifetime); + stream->read(&cutoffAngle); + + + for( int i=0; ireadSTString(); + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +RepairProjectile::RepairProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + + mElapsedTime = 0.0; + mInitialHit = true; + mTimeSinceLastBlur = 0.00; +} + +//-------------------------------------------------------------------------- +RepairProjectile::~RepairProjectile() +{ +} + +//-------------------------------------------------------------------------- +void RepairProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + addField("targetObject", TypeS32, Offset(mRepairingObjectId, RepairProjectile)); +} + + +//-------------------------------------------------------------------------- +void RepairProjectile::consoleInit() +{ + // +} + + + +//-------------------------------------------------------------------------- +bool RepairProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isServerObject()) + { + AssertFatal(bool(mSourceObject), "Oh, crap."); + + ShapeBase* ptr; + if (Sim::findObject(mRepairingObjectId, ptr)) + { + mRepairingObject = ptr; + } + else + { + if (mRepairingObjectId != -1) + Con::errorf(ConsoleLogEntry::General, "Projectile::onAdd: mRepairingObjectId is invalid"); + mRepairingObject = NULL; + } + + mObjBox.min = mSourceObject->getPosition(); + mObjBox.max = mSourceObject->getPosition() + Point3F( 1.0, 1.0, 1.0 ); + resetWorldBox(); + + } + else + { + if( bool( mSourceObject ) && bool( mRepairingObject ) ) + { + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mCurrStartPoint); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &mCurrStartDir); + mDesiredEndPoint.set(0, 0, 0); + + RayInfo rayInfo; + if( gClientContainer.castRay(mCurrStartPoint, mCurrStartPoint + (mCurrStartDir*mDataBlock->beamRange), mRepairingObject->getType(), &rayInfo) == true ) + { + if (rayInfo.object == (ShapeBase*)mRepairingObject) + { + mRepairingObject->getWorldBox().getCenter(&mDesiredEndPoint); + mDesiredEndPoint = rayInfo.point - mDesiredEndPoint; + } + } + + mCurrEndPoint = mDesiredEndPoint; + + // resize object box of beam + mObjBox.min = mCurrEndPoint; + mObjBox.max = mCurrEndPoint; + mObjBox.min.setMin(mCurrStartPoint); + mObjBox.max.setMax(mCurrStartPoint); + resetWorldBox(); + } + } + + addToScene(); + + return true; +} + + +//-------------------------------------------------------------------------- +void RepairProjectile::onRemove() +{ + removeFromScene(); + // + + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +bool RepairProjectile::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; +} + + +//-------------------------------------------------------------------------- +U32 RepairProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & GameBase::InitialUpdateMask)) { + // Have to write the source and the dest to the client, let's make sure it has a + // ghost on the other side... + S32 ghostIndexSource = con->getGhostIndex(mSourceObject); + S32 ghostIndexDest = con->getGhostIndex(mRepairingObject); + if (ghostIndexSource == -1 || ghostIndexDest == -1) { + // One of the two isn't on the client. Crap. + stream->writeFlag(false); + } else { + stream->writeFlag(true); + + stream->writeRangedU32(U32(ghostIndexSource), 0, NetConnection::MaxGhostCount); + stream->writeRangedU32(U32(mSourceObjectSlot), + 0, ShapeBase::MaxMountedImages - 1); + stream->writeRangedU32(U32(ghostIndexDest), 0, NetConnection::MaxGhostCount); + } + } else { + + } + + return retMask; +} + +void RepairProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) { + if (stream->readFlag()) { + mSourceObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + mSourceObjectSlot = stream->readRangedU32(0, ShapeBase::MaxMountedImages - 1); + mRepairingObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + + NetObject* pObject = con->resolveGhost(mSourceObjectId); + if (pObject != NULL) + mSourceObject = dynamic_cast(pObject); + if (bool(mSourceObject) == false) + Con::errorf(ConsoleLogEntry::General, "RepairProjectile::unpackUpdate: could not resolve source ghost properly on initial update"); + + pObject = con->resolveGhost(mRepairingObjectId); + if (pObject != NULL) + mRepairingObject = dynamic_cast(pObject); + if (bool(mRepairingObject) == false) + Con::errorf(ConsoleLogEntry::General, "RepairProjectile::unpackUpdate: could not resolve dest ghost properly on initial update"); + } else { + mSourceObjectId = 0; + mSourceObjectSlot = 0; + mRepairingObjectId = 0; + + mSourceObject = NULL; + mRepairingObject = NULL; + } + } else { + // Other + } +} + +//-------------------------------------------------------------------------- +void RepairProjectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if (!bool(mSourceObject) || !bool(mRepairingObject)) + return; + + mElapsedTime += dt; + mTimeSinceLastBlur += dt; + + + + // We start out with a good relative end point. + // Our goal is to not invalidate that, but to update it if the object + // points in a new direction... + + if( bool(mSourceObject) && bool(mRepairingObject) ) + { + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mCurrStartPoint); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &mCurrStartDir); + + RayInfo rayInfo; + if (getContainer()->castRay(mCurrStartPoint, mCurrStartPoint + (mCurrStartDir*mDataBlock->beamRange), mRepairingObject->getType(), &rayInfo) == true) + { + if (rayInfo.object == (ShapeBase*)mRepairingObject) + { + mDesiredEndPoint = rayInfo.point; + if( mInitialHit ) + { + mInitialHit = false; + mCurrEndPoint = mDesiredEndPoint; + } + } + } + else + { + // No hit. Just use the old endpoint. + } + + // resize object box of beam + mObjBox.min = mCurrEndPoint; + mObjBox.max = mCurrEndPoint; + mObjBox.min.setMin(mCurrStartPoint); + mObjBox.max.setMax(mCurrStartPoint); + resetWorldBox(); + } + else + { + mCurrStartPoint.set(0, 0, 0); + mCurrEndPoint.set(0, 0, 0); + mObjBox.min.set(0, 0, 0); + mObjBox.max.set(0, 0, 0); + } + + MatrixF xform = getTransform(); + setTransform(xform); + + Point3F endMoveVec = mDesiredEndPoint - mCurrEndPoint; + endMoveVec *= 2.0 * dt; + mCurrEndPoint += endMoveVec; + + updateBlur( dt ); + + +} + +//-------------------------------------------------------------------------- +// Update blur +//-------------------------------------------------------------------------- +void RepairProjectile::updateBlur( F32 dt ) +{ + if( isServerObject() ) return; + + // update blur + for( int j=0; j 1.0/mDataBlock->blurFreq ) + { + mTimeSinceLastBlur -= (1.0 / mDataBlock->blurFreq); + + Point3F cPoints[3]; + cPoints[0] = mCurrStartPoint; + cPoints[1] = mCurrStartPoint + mCurrStartDir * (mCurrStartPoint - mCurrEndPoint).len(); + cPoints[2] = mCurrEndPoint; + + SplCtrlPts ctrlPoints; + ctrlPoints.submitPoints( cPoints, 3 ); + + QuadPatch qPatch; + qPatch.submitControlPoints( ctrlPoints ); + + + for( int i=0; i mBlurList[i].lifetime ) + { + mBlurList[i].qPatch.submitControlPoints( ctrlPoints ); + mBlurList[i].elapsedTime = 0.0; + mBlurList[i].lifetime = mDataBlock->blurLifetime; + mBlurList[i].startAlpha = 0.5;; + break; + } + } + + } + +} + +//-------------------------------------------------------------------------- +bool RepairProjectile::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + if (!bool(mSourceObject) || !bool(mRepairingObject)) + 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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + + image->sortType = SceneRenderImage::EndSort; + state->setImageRefPoint(this, image); + + state->insertRenderImage(image); + } + + return false; +} + +//-------------------------------------------------------------------------- +// Render the effect +//-------------------------------------------------------------------------- +void RepairProjectile::renderObject(SceneState* state, SceneRenderImage* /*sceneImage*/) +{ + if( mCurrStartPoint.equal( mCurrEndPoint ) ) return; + + // make sure beam doesn't twist up in odd fashion if player turns rapidly + VectorF actionDir = mCurrEndPoint - mCurrStartPoint; + actionDir.normalize(); + F32 angDiff = 90.0 - mDot( actionDir, mCurrStartDir ) * 90.0; + if( angDiff > mDataBlock->cutoffAngle ) return; + + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + + glMatrixMode(GL_MODELVIEW); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_TEXTURE_2D); + + glColor4f( 1.0, 1.0, 1.0, 1.0 ); + + if( canRenderFlare( state->getCameraPosition() ) ) + { + renderFlare( 0.6 ); + } + + QuadPatch qPatch; + + + Point3F cPoints[3]; + cPoints[0] = mCurrStartPoint; + cPoints[1] = mCurrStartPoint + mCurrStartDir * (mCurrStartPoint - mCurrEndPoint).len(); + cPoints[2] = mCurrEndPoint; + + SplCtrlPts ctrlPoints; + ctrlPoints.submitPoints( cPoints, 3 ); + + qPatch.submitControlPoints( ctrlPoints ); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureList[RepairProjectileData::RT_BEAM].getGLName() ); + glColor4f( 1.0, 1.0, 1.0, 0.75 ); + + SplineUtil::drawSplineBeam( state->getCameraPosition(), 20, 0.2, qPatch, -mElapsedTime * mDataBlock->beamSpeed, mDataBlock->texRepeat ); + + + SplineUtil::SplineBeamInfo sbi; + + Point3F camPos = state->getCameraPosition(); + sbi.camPos = &camPos; + sbi.zeroAlphaStart = true; + sbi.numSegments = mDataBlock->numSegments; + sbi.uvOffset = -mElapsedTime * mDataBlock->beamSpeed; + sbi.numTexRep = mDataBlock->texRepeat; + + for( int i=0; igetControllingClient(); + if( gc ) + { + firstPerson = gc->isFirstPerson(); + } + + VectorF dirFromGun = mCurrEndPoint - mCurrStartPoint; + dirFromGun.normalizeSafe(); + + VectorF dirToFlare = mCurrEndPoint - camPos; + dirToFlare.normalizeSafe(); + Point3F tweakedCamPos = camPos + dirToFlare * 0.5; + + if( mDot( dirFromGun, dirToFlare ) < -0.75 ) return false; + + bool rayHit = true; + + mRepairingObject->disableCollision(); + + if( firstPerson ) + { + mSourceObject->disableCollision(); + + RayInfo rayInfo; + rayHit = getContainer()->castRay( tweakedCamPos, mCurrEndPoint, mRepairingObject->getType(), &rayInfo ); + + mSourceObject->enableCollision(); + } + else + { + RayInfo rayInfo; + rayHit = getContainer()->castRay( tweakedCamPos, mCurrEndPoint, mRepairingObject->getType(), &rayInfo ); + + } + + mRepairingObject->enableCollision(); + + + return !rayHit; +} + + +//-------------------------------------------------------------------------- +// Render flare +//-------------------------------------------------------------------------- +void RepairProjectile::renderFlare( F32 flareSize ) +{ + + Point3F pos = mCurrEndPoint; + + glDisable(GL_DEPTH_TEST); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureList[RepairProjectileData::RT_FLARE].getGLName() ); + + glColor3f( 0.3333, 0.3333, 0.3333 ); + + dglDrawBillboard( pos, flareSize * 2.0, mElapsedTime * .15 * M_PI * 2.0 ); + dglDrawBillboard( pos, flareSize * 1.7, -mElapsedTime * .10 * M_PI * 2.0 ); + dglDrawBillboard( pos, flareSize * 1.82, mElapsedTime * .05 * M_PI * 2.0 ); + + glEnable(GL_DEPTH_TEST); +} + diff --git a/game/projRepair.h b/game/projRepair.h new file mode 100644 index 0000000..a69b818 --- /dev/null +++ b/game/projRepair.h @@ -0,0 +1,173 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_REPAIRPROJECTILE +#define _H_REPAIRPROJECTILE + +#ifndef _PROJECTILE_H_ +#include "game/projectile.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif +#ifndef _MRANDOM_H_ +#include "Math/mRandom.h" +#endif +#ifndef _MQUADPATCH_H_ +#include "Math/mQuadPatch.h" +#endif + +class Point3F; + +//****************************************************************************** +// Repair projectile data +//****************************************************************************** +class RepairProjectileData : public ProjectileData +{ + typedef ProjectileData Parent; + + protected: + bool onAdd(); + + public: + enum Constants { + MaxLines = 8 + }; + + //-------------------------------------- Console set variables + public: + + enum RepairTextures + { + RT_BEAM = 0, + RT_FLARE, + RT_NUM_TEX + }; + + + F32 beamRange; + F32 beamWidth; + U32 numSegments; + F32 texRepeat; + F32 beamSpeed; + F32 blurFreq; + F32 blurLifetime; + F32 cutoffAngle; + + StringTableEntry textureNames[RT_NUM_TEX]; + + + + //-------------------------------------- preload set variables + public: + TextureHandle textureList[RT_NUM_TEX]; + + public: + RepairProjectileData(); + ~RepairProjectileData(); + + //aiming used by AI + bool calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& /*sourceVel*/, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(RepairProjectileData); + static void initPersistFields(); +}; + + +//****************************************************************************** +// Repair Projectile +//****************************************************************************** +class RepairProjectile : public Projectile +{ + typedef Projectile Parent; + + enum RPConst + { + RP_NUM_BLUR = 5, + }; + + struct BlurLine + { + F32 elapsedTime; + F32 lifetime; + F32 startAlpha; + ColorF color; + QuadPatch qPatch; + + BlurLine() + { + elapsedTime = 1.1; + lifetime = 1.0; + startAlpha = 0.5; + } + }; + + + private: + RepairProjectileData* mDataBlock; + + SimObjectPtr mRepairingObject; + S32 mRepairingObjectId; + + BlurLine mBlurList[RP_NUM_BLUR]; + + F32 mElapsedTime; + bool mInitialHit; + F32 mTimeSinceLastBlur; + + Point3F mCurrStartPoint; + Point3F mCurrStartDir; + Point3F mDesiredEndPoint; + Point3F mCurrEndPoint; // Endpoint is relative to the center of + // the objects bounding box... + + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + void renderFlare( F32 flareSize ); + bool canRenderFlare( const Point3F &camPos ); + + void renderBeam( const Point3F &campPos); + void updateBlur( F32 dt ); + + + public: + RepairProjectile(); + ~RepairProjectile(); + + void advanceTime(F32); + + DECLARE_CONOBJECT(RepairProjectile); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_REPAIRPROJECTILE + diff --git a/game/projSeeker.cc b/game/projSeeker.cc new file mode 100644 index 0000000..cdc5639 --- /dev/null +++ b/game/projSeeker.cc @@ -0,0 +1,1600 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projSeeker.h" +#include "console/consoleTypes.h" +#include "core/bitStream.h" +#include "game/shapeBase.h" +#include "math/mathIO.h" +#include "sim/netConnection.h" +#include "game/explosion.h" +#include "terrain/terrData.h" +#include "terrain/waterBlock.h" +#include "game/Debris.h" +#include "ts/tsShapeInstance.h" +#include "dgl/dgl.h" +#include "scenegraph/sceneState.h" +#include "math/mathUtils.h" +#include "scenegraph/sceneGraph.h" + +IMPLEMENT_CO_DATABLOCK_V1(SeekerProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(SeekerProjectile); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +SeekerProjectileData::SeekerProjectileData() +{ + lifetimeMS = 3000; + + muzzleVelocity = 100.0f; + turningSpeed = 180.0f; + maxVelocity = 65.0; + acceleration = 10.0; + + proximityRadius = 10.0f; + + terrainAvoidanceSpeed = 180; + terrainScanAhead = 25; + terrainHeightFail = 2; + terrainAvoidanceRadius = 50; + + flareDistance = 200; + flareAngle = 20; + + useFlechette = false; + flechetteDelayMs = 300; + + puffEmitter = NULL; + puffEmitterID = 0; + + exhaustEmitter = NULL; + exhaustEmitterID = 0; + exhaustTimeMs = 1000; + exhaustNodeName = NULL; + + casingDeb = NULL; + casingDebID = 0; + casingShapeName = NULL; +} + +SeekerProjectileData::~SeekerProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void SeekerProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("lifetimeMS", TypeS32, Offset(lifetimeMS, SeekerProjectileData)); + addField("muzzleVelocity", TypeF32, Offset(muzzleVelocity, SeekerProjectileData)); + addField("turningSpeed", TypeF32, Offset(turningSpeed, SeekerProjectileData)); + addField("terrainAvoidanceSpeed", TypeF32, Offset(terrainAvoidanceSpeed, SeekerProjectileData)); + addField("terrainScanAhead", TypeF32, Offset(terrainScanAhead, SeekerProjectileData)); + addField("terrainHeightFail", TypeF32, Offset(terrainHeightFail, SeekerProjectileData)); + addField("terrainAvoidanceRadius", TypeF32, Offset(terrainAvoidanceRadius, SeekerProjectileData)); + addField("flareDistance", TypeF32, Offset(flareDistance, SeekerProjectileData)); + addField("flareAngle", TypeF32, Offset(flareAngle, SeekerProjectileData)); + addField("useFlechette", TypeBool, Offset(useFlechette, SeekerProjectileData)); + addField("maxVelocity", TypeF32, Offset(maxVelocity, SeekerProjectileData)); + addField("acceleration", TypeF32, Offset(acceleration, SeekerProjectileData)); + addField("flechetteDelayMs", TypeS32, Offset(flechetteDelayMs, SeekerProjectileData)); + addField("exhaustTimeMs", TypeS32, Offset(exhaustTimeMs, SeekerProjectileData)); + addField("exhaustNodeName", TypeString, Offset(exhaustNodeName, SeekerProjectileData)); + addField("casingShapeName", TypeString, Offset(casingShapeName, SeekerProjectileData)); + + addField("puffEmitter", TypeParticleEmitterDataPtr, Offset(puffEmitter, SeekerProjectileData)); + addField("exhaustEmitter", TypeParticleEmitterDataPtr, Offset(exhaustEmitter, SeekerProjectileData)); + addField("casingDeb", TypeDebrisDataPtr, Offset(casingDeb, SeekerProjectileData)); + +} + + +//--------------------------------------------------- +bool SeekerProjectileData::calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& /*sourceVel*/, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime) +{ + if (((targetPos - sourcePos) + targetVel * (lifetimeMS/1000.0)).len() > (lifetimeMS/1000.0)*maxVelocity) { + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + + return false; + } + + *outputVectorMin = targetPos - sourcePos; + *outputMinTime = outputVectorMin->len() / maxVelocity; + outputVectorMin->normalize(); + *outputVectorMax = *outputVectorMin; + *outputMaxTime = *outputMinTime; + + return true; +} + + +//-------------------------------------------------------------------------- +bool SeekerProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (lifetimeMS < 500 || lifetimeMS > 30000) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: lifetimeMS out of bounds [500, 30000]", getName()); + lifetimeMS = lifetimeMS < 500 ? 500 : 30000; + } + if (muzzleVelocity < 0.1) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: muzzleVelocity < 0.1", getName()); + muzzleVelocity = 0.1; + } + if (turningSpeed < 0.0f || turningSpeed > 2000) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: turningSpeed out of range [0, 2000]", getName()); + turningSpeed = turningSpeed < 0.0 ? 0.0 : 2000.0; + } + if (terrainAvoidanceSpeed < 0 || terrainAvoidanceSpeed > 2000) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: terrainAvoidanceSpeed out of range [0, 2000]", getName()); + terrainAvoidanceSpeed = terrainAvoidanceSpeed < 0.0 ? 0.0 : 2000.0; + } + if (terrainScanAhead < 0 || terrainScanAhead > 200) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: terrainScanAhead out of range [0, 200]", getName()); + terrainScanAhead = terrainScanAhead < 0.0 ? 0.0 : 200.0; + } + if (terrainHeightFail < 0) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: terrainHeightFail < 0", getName()); + terrainHeightFail = 0.0; + } + if (terrainAvoidanceRadius < 0) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: terrainAvoidanceRadius < 0", getName()); + terrainAvoidanceRadius = 0.0; + } + if (flareDistance < 0) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: flareDistance must be >= 0. (0 Indicates missile unaffected by flares", getName()); + flareDistance = 0; + } + if (flareAngle < 0) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: flareAngle must be >= 0. (0 Indicates missile unaffected by flares", getName()); + flareAngle = 0; + } + if (maxVelocity < 0.1 ) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: maxVelocity < 0.1", getName()); + maxVelocity = muzzleVelocity + 1.0; + } + if (flechetteDelayMs > 30000) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: flechetteDelayMs > 30000", getName()); + flechetteDelayMs = 500; + } + if (acceleration < 0.0 || acceleration > 30000 ) { + Con::warnf(ConsoleLogEntry::General, "SeekerProjectileData(%s)::onAdd: acceleration out of range [0.0, 30000]", getName()); + acceleration = 0.0; + } + + if (!puffEmitter && puffEmitterID != 0) + if (Sim::findObject(puffEmitterID, puffEmitter) == false) + Con::errorf(ConsoleLogEntry::General, "SeekerProjectileData::onAdd: Invalid packet, bad datablockId(puffEmitter): %d", puffEmitterID); + + if (!exhaustEmitter && exhaustEmitterID != 0) + if (Sim::findObject(exhaustEmitterID, exhaustEmitter) == false) + Con::errorf(ConsoleLogEntry::General, "SeekerProjectileData::onAdd: Invalid packet, bad datablockId(exhaustEmitter): %d", exhaustEmitterID); + + + if( !casingDeb && casingDebID != 0 ) + { + if( !Sim::findObject( SimObjectId( casingDebID ), casingDeb ) ) + { + Con::errorf( ConsoleLogEntry::General, "SeekerProjectileData::onAdd: Invalid packet, bad datablockId(casingDeb): 0x%x", casingDebID ); + } + } + + if (casingShapeName && casingShapeName[0] != '\0') { + char fullName[256]; + char errorBuffer[256]; + dSprintf(fullName, sizeof(fullName), "shapes/%s", casingShapeName); + + casingShape = ResourceManager->load(fullName); + if (bool(casingShape) == false) { + dSprintf(errorBuffer, sizeof(errorBuffer), "SeekerProjectileData::onAdd: Couldn't load shape \"%s\"", casingShapeName); + return false; + } + } + + + // Make sure the lifetime is a multiple of TickMs; + lifetimeMS = (lifetimeMS + TickMs - 1) & ~(TickMs - 1); + + F32 turnPerTick = turningSpeed * (TickMs / 1000.0f); + AssertFatal(turnPerTick < 90.0f, "Error, bad boundary constraints on SeekerProjectileData::turningSpeed"); + cosTurningSpeed = mCos(mDegToRad(turnPerTick)); + cosAvoidSpeed = mCos(mDegToRad(terrainAvoidanceSpeed * (TickMs / 1000.0f))); + cosFlareAngle = mCos(mDegToRad(flareAngle)); + + return true; +} + +//-------------------------------------------------------------------------- +void SeekerProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(lifetimeMS); + stream->write(muzzleVelocity); + stream->write(turningSpeed); + stream->write(proximityRadius); + stream->write(terrainAvoidanceSpeed); + stream->write(terrainScanAhead); + stream->write(terrainHeightFail); + stream->write(terrainAvoidanceRadius); + stream->write(flareDistance); + stream->write(flareAngle); + stream->write(useFlechette); + stream->write(maxVelocity); + stream->write(acceleration); + stream->write(flechetteDelayMs); + stream->write(exhaustTimeMs); + + stream->writeString( exhaustNodeName ); + stream->writeString( casingShapeName ); + + if( stream->writeFlag( casingDeb ) ) + { + stream->writeRangedU32(packed? SimObjectId(casingDeb): + casingDeb->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + + if (stream->writeFlag(puffEmitter != NULL)) + stream->writeRangedU32(puffEmitter->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + + if (stream->writeFlag(exhaustEmitter != NULL)) + stream->writeRangedU32(exhaustEmitter->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); +} + +void SeekerProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&lifetimeMS); + stream->read(&muzzleVelocity); + stream->read(&turningSpeed); + stream->read(&proximityRadius); + stream->read(&terrainAvoidanceSpeed); + stream->read(&terrainScanAhead); + stream->read(&terrainHeightFail); + stream->read(&terrainAvoidanceRadius); + stream->read(&flareDistance); + stream->read(&flareAngle); + stream->read(&useFlechette); + stream->read(&maxVelocity); + stream->read(&acceleration); + stream->read(&flechetteDelayMs); + stream->read(&exhaustTimeMs); + + exhaustNodeName = stream->readSTString(); + casingShapeName = stream->readSTString(); + + if(stream->readFlag()) + { + casingDebID = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + + if (stream->readFlag()) + puffEmitterID = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + puffEmitterID = 0; + + + if (stream->readFlag()) + exhaustEmitterID = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + exhaustEmitterID = 0; +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +SeekerProjectile::SeekerProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + + mTargetId = 0; + mTargetPosition.set(0, 0, 0); + mTargetingString = "none"; + mTargetingMode = None; + + mNumWarpTicks = 0; + + mPlayerVel.set( 0.0, 0.0, 0.0 ); + + mExhaustEmitter = NULL; + mPuffEmitter = NULL; + mFlechettePuff = false; + + mCurrVelocity.set( 0.0, 0.0, 0.0 ); + mLastServerVel = mCurrVelocity; + + mCasingShape = NULL; + mLastPos.set( 0.0, 0.0, 0.0 ); + + mOriginalTargetPtr = NULL; + mTargetPtr = NULL; + + mExplosionPending = false; + mHitWater = false; + + mFlechetteGhostTick = 0xFFFFFFFF; +} + +SeekerProjectile::~SeekerProjectile() +{ + delete mCasingShape; + mCasingShape = NULL; +} + +//-------------------------------------------------------------------------- +static S32 cGetTargetObject(SimObject *obj, S32, const char **) +{ + SeekerProjectile *proj = static_cast(obj); + return proj->getTargetObjectId(); +} + +ConsoleMethod(SeekerProjectile,setObjectTarget,void,3,3,"obj.setObjectTarget(targetObj);") +{ + argc; + SeekerProjectile *proj = static_cast(object); + GameBase *pTarget; + if(!Sim::findObject(argv[2], pTarget)) + return; + proj->mTargetId = pTarget->getId(); + proj->mOriginalTargetPtr = proj->mTargetPtr = pTarget; + proj->mTargetingMode = SeekerProjectile::Object; + + pTarget->incHomingCount(); +} + +ConsoleMethod(SeekerProjectile,setNoTarget,void,2,2,"obj.setNoTarget();") +{ + argc; + SeekerProjectile *proj = static_cast(object); + + proj->mOriginalTargetPtr = proj->mTargetPtr = NULL; + proj->mTargetingMode = SeekerProjectile::None; +} + +ConsoleMethod(SeekerProjectile,setPositionTarget,void,3,3,"obj.setPositionTarget(targetPos);") +{ + argc; + SeekerProjectile *proj = static_cast(object); + Point3F pos; + dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z); + + proj->mOriginalTargetPtr = proj->mTargetPtr = NULL; + proj->mTargetPosition = pos; + proj->mTargetingMode = SeekerProjectile::Position; +} + +void SeekerProjectile::consoleInit() +{ + Con::addCommand("SeekerProjectile", "getTargetObject", cGetTargetObject, "proj.getTargetObject()", 2, 2); +} + +//-------------------------------------------------------------------------- +bool SeekerProjectile::calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime) +{ + if (mHidden == true) { + impactTime = 0; + pointOfImpact.set(0, 0, 0); + return false; + } + + Point3F startPt; + getTransform().getColumn(3, &startPt); + + if (mTargetingMode == Object && (bool)mTargetPtr) { + mTargetPtr->getWorldBox().getCenter(&pointOfImpact); + impactTime = (pointOfImpact - startPt).len() / mDataBlock->muzzleVelocity; + + return (impactTime <= simTime); + } else if (mTargetingMode == Position) { + AssertFatal(false, "not yet allowed!"); + return false; + } else { + // Ray cast forward just like a linear projectile... + // + Point3F endPt = startPt + mCurrVelocity * simTime; + + U32 mask = PlayerObjectType | TerrainObjectType | InteriorObjectType | WaterObjectType; + RayInfo rayInfo; + if (!gServerContainer.castRay(startPt, endPt, mask, &rayInfo)) { + impactTime = 0; + pointOfImpact.set(0, 0, 0); + return false; + } + else { + pointOfImpact = rayInfo.point; + float distToImpact = (pointOfImpact - startPt).len(); + impactTime = distToImpact / mDataBlock->muzzleVelocity; + return true; + } + } +} + + +//-------------------------------------------------------------------------- +bool SeekerProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isServerObject()) { + bool wetStart = false; + + mCurrPosition = mInitialPosition; + mCurrVelocity = mInitialDirection * mDataBlock->muzzleVelocity; + + Point3F excessVel = mExcessDir * mExcessVel; + Point3F eDir = mExcessDir; + mExcessVel = F32(mExcessVel) * mFabs( mDot( eDir, mInitialDirection ) ); + mCurrVelocity += mExcessDir * mExcessVel; + + mDeathTick = mDataBlock->lifetimeMS / TickMs; + mDeleteTick = mDeathTick + DeleteWaitTicks; + } else { + if (bool(mSourceObject)) { + // If we have a source object, we have to warp to the server position... + // + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mCurrPosition); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &mInitialDirection); + setupWarp(); + } else { + // Otherwise, we can just start the sim... + mCurrPosition = mLastServerPos; + mCurrVelocity = mLastServerVel; + mWarpTicksLeft = 0; + } + + if( mDataBlock->puffEmitter != NULL ) + { + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock( mDataBlock->puffEmitter ); + if( pEmitter->registerObject() == false ) + { + Con::warnf(ConsoleLogEntry::General, "Could not register base emitter for particle of class: %s", mDataBlock->getName()); + delete pEmitter; + pEmitter = NULL; + } + mPuffEmitter = pEmitter; + } + if( mDataBlock->exhaustEmitter != NULL ) + { + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock( mDataBlock->exhaustEmitter ); + if( pEmitter->registerObject() == false ) + { + Con::warnf(ConsoleLogEntry::General, "Could not register base emitter for particle of class: %s", mDataBlock->getName()); + delete pEmitter; + pEmitter = NULL; + } + mExhaustEmitter = pEmitter; + } + + if( bool(mDataBlock->casingShape) ) + { + mCasingShape = new TSShapeInstance(mDataBlock->casingShape, isClientObject()); + } + + mLastPos = mCurrPosition; + } + + mObjBox = Box3F(Point3F(-0.25, -0.25, -0.25), Point3F(0.25, 0.25, 0.25)); + MatrixF xform(true); + xform.setColumn(3, mCurrPosition); + setTransform(xform); + resetWorldBox(); + + addToScene(); + + return true; +} + + +void SeekerProjectile::onRemove() +{ + + if (bool(mPuffEmitter)) { + mPuffEmitter->deleteWhenEmpty(); + mPuffEmitter = NULL; + } + + if (bool(mExhaustEmitter)) { + mExhaustEmitter->deleteWhenEmpty(); + mExhaustEmitter = NULL; + } + + if (isClientObject()) + updateSound(Point3F(0, 0, 0), Point3F(0, 0, 0), false); + removeFromScene(); + + Parent::onRemove(); +} + + +bool SeekerProjectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +bool SeekerProjectile::getTargetPosition(Point3F* target) +{ + if (mTargetingMode == None) + return false; + + if (mTargetingMode == Position) { + *target = mTargetPosition; + return true; + } + + // Otherwise, we're targeting an object... + if (bool(mTargetPtr) == false) { + if(bool(mOriginalTargetPtr) == false) + return false; + else { + mTargetPtr = mOriginalTargetPtr; + mTargetPtr->incHomingCount(); + } + } + // Ok, we need to do a quick scan for any flares in our path. We define + // this as flares that are within flareDistance, and within flareAngle + // degrees of the seeker head. If a flare meeting these conditions is + // detected, we choose the closest one, and seek at it. We only do this + // scan on the server... + // + if((GameBase*)mTargetPtr == (GameBase*)mOriginalTargetPtr) + { + if (isServerObject() && mDataBlock->flareDistance > 0 && mDataBlock->flareAngle > 0) { + SimSet* flareSet = Sim::getFlareSet(); + AssertFatal(flareSet, "No Flare set!"); + + F32 minDist = 1e8; + GameBase* pMin = NULL; + + for (SimSet::iterator itr = flareSet->begin(); itr != flareSet->end(); itr++) { + GameBase* pFlare = dynamic_cast(*itr); + if (pFlare == NULL || pFlare->getHomingCount() != 0) + continue; + + Point3F boxCenterDif; + pFlare->getWorldBox().getCenter(&boxCenterDif); + boxCenterDif -= mCurrPosition; + F32 dist = boxCenterDif.len(); + if (dist > mDataBlock->flareDistance || dist > minDist) + continue; + + boxCenterDif.normalize(); + Point3F velNormal = mCurrVelocity; + velNormal.normalize(); + + F32 dot = mDot(boxCenterDif, velNormal); + if (dot > mDataBlock->cosFlareAngle) { + minDist = dist; + pMin = pFlare; + } + } + + // Flared! + if (pMin != NULL) { + if (pMin == (GameBase*)mTargetPtr) { + // Same target, do nothing... + } else { + // New flare... + if (bool(mTargetPtr)) + mTargetPtr->decHomingCount(); + + mTargetPtr = pMin; + mTargetPtr->incHomingCount(); + setMaskBits(SeekerUpdateMask); + } + } + } + } + mTargetPtr->getWorldBox().getCenter(target); + return true; +} + + +Point3F SeekerProjectile::getVelocity() const +{ + return mCurrVelocity; +} + + +S32 SeekerProjectile::getTargetObjectId() +{ + if (bool(mTargetPtr)) + return mTargetPtr->getId(); + else + return -1; +} +//-------------------------------------------------------------------------- +void SeekerProjectile::explode(const Point3F& p, const Point3F& n) +{ + // Already blown up + if (mHidden == true) + return; + mHidden = true; + + if (isServerObject()) { + // If we're seeking on a flare, delete it when we are finished... + // + SimSet* flareSet = Sim::getFlareSet(); + AssertFatal(flareSet, "No Flare set!"); + if (flareSet != NULL && bool(mTargetPtr)) + { + for (U32 i = 0; i < flareSet->size(); i++) + { + GameBase* pGBase = dynamic_cast((*flareSet)[i]); + if (pGBase != NULL && mTargetPtr == pGBase) + { + pGBase->deleteObject(); + } + } + } + + // do radius damage if appropriate + mExplosionPosition = p + (n*0.01); + mExplosionNormal = n; + + char buffer[128]; + dSprintf(buffer, sizeof(buffer), "%f %f %f", + mExplosionPosition.x, + mExplosionPosition.y, + mExplosionPosition.z); + Con::executef(mDataBlock, 4, "onExplode", scriptThis(), buffer, "1.0"); + + // Todo: event over explosion + } else { + // Client just plays the explosion at the right place... + // + if (mDataBlock->explosion) { + Explosion* pExplosion = new Explosion; + pExplosion->onNewDataBlock(mDataBlock->explosion); + + MatrixF xform(true); + xform.setColumn(3, p); + pExplosion->setTransform(xform); + pExplosion->setInitialState(p, n); + if (pExplosion->registerObject() == false) { + Con::errorf(ConsoleLogEntry::General, "LinearProjectile(%s)::explode: couldn't register explosion(%s)", + mDataBlock->getName(), mDataBlock->explosion->getName()); + delete pExplosion; + pExplosion = NULL; + } + } + + updateSound(Point3F(0, 0, 0), Point3F(0, 0, 0), false); + } +} + + +//---------------------------------------------------------------------------- +bool SeekerProjectile::updatePos(const Point3F& oldPos, + const Point3F& newPos, + const bool includeDynamics, + F32* hitDelta, + Point3F* hitPos, + Point3F* hitNormal, + SceneObject*& hitObject) +{ + if (bool(mSourceObject)) + mSourceObject->disableCollision(); + + RayInfo rInfo; + U32 mask = csmStaticCollisionMask | (includeDynamics ? csmDynamicCollisionMask : 0); + + if( mDataBlock->explodeOnWaterImpact ) + { + mask |= WaterObjectType; + } + + if (mContainer->castRay(oldPos, newPos, mask, &rInfo) == true) { + *hitPos = rInfo.point; + *hitNormal = rInfo.normal; + *hitDelta = rInfo.t; + hitObject = rInfo.object; + + + // shoot another ray to see if it hit water + RayInfo waterRayInf; + if( mContainer->castRay( oldPos, newPos, WaterObjectType, &waterRayInf ) ) + { + mHitWater = true; + } + + + if (bool(mSourceObject)) + mSourceObject->enableCollision(); + + return false; + } else { + *hitDelta = 1.0; + hitObject = NULL; + + if (bool(mSourceObject)) + mSourceObject->enableCollision(); + + return true; + } +} + + +//-------------------------------------------------------------------------- +bool SeekerProjectile::getNewVelocity(const Point3F& currPosition, + const Point3F& currVelocity, + const Point3F& targetPosition, + Point3F* newVelocity) +{ + // This is fairly simple. We have to determine two things: + // 1. Have we passed the target? If so, newV = currV, return false + // 2. Otherwise, turn newV from currV as much as allowed by the + // datablock parameters... + // + + Point3F vecToTarget = targetPosition - currPosition; + Point3F normVelocity = currVelocity; + + // Oh, god. I feel so dirty. + // + F32 vecLen = vecToTarget.len(); + F32 normLen = normVelocity.len(); + if (vecLen > 1e-9) + vecToTarget /= vecLen; + else + vecToTarget.set(0, 0, 1); + if (normLen > 1e-9) + normVelocity /= normLen; + else + normVelocity.set(0, 0, 1); + // + ///// + + + bool status = true; + F32 dotProd = mDot(vecToTarget, normVelocity); + if (dotProd < 0.0) + status = false; + + if (dotProd < 0.99985) { + // Ok, if we're here, then the above dot product represents the angle between + // the current direction and the direction we want to go. Let's find the axis + // to turn along... + Point3F turnAxis; + mCross(vecToTarget, normVelocity, &turnAxis); + turnAxis.normalize(); + + // And now, let's determine the final cos(theta) we'll be using. + F32 cosTheta = dotProd; + if (dotProd < mDataBlock->cosTurningSpeed) + cosTheta = mDataBlock->cosTurningSpeed; + + F32 theta = mAcos(cosTheta); + if (mTargetingMode == Object) + { + F32 heat = mTargetPtr->getHeat(); + if (theta > mDataBlock->turningSpeed * heat) + theta = mDataBlock->turningSpeed * heat; + + if( theta == 0 ) + { + *newVelocity = currVelocity; + return status; + } + } + + QuatF turn(AngAxisF(turnAxis, theta)); + + // Ok, we know have a quaternion that represents our desired rotation. Lets construct + // a transform from it... + MatrixF rot(true); + turn.setMatrix(&rot); + rot.mulV(currVelocity, newVelocity); + } else { + // We're already pointed straight at it essentially, skip + *newVelocity = currVelocity; + } + + if (mDataBlock->terrainScanAhead <= 0.0 || mContainer == NULL) + return status; + + U32 mask = mTargetingMode == Object ? TerrainObjectType | mTargetPtr->getType() : TerrainObjectType; + + RayInfo rinfo; + F32 dist = (currPosition - targetPosition).len(); + if (dist < mDataBlock->terrainAvoidanceRadius) { + // Make sure we don't engage the terrain following logic unless the terrain obstructs + // our view of the target... + if (mContainer->castRay(currPosition, targetPosition, mask, &rinfo)) { + // clear shot? + if (dynamic_cast(rinfo.object) == NULL) + return status; + } else { + return status; + } + } + + // Now that we have the new velocity, let's see if we can avoid crashing into the terrain + Point3F end = *newVelocity; + end.normalize(); + end *= mDataBlock->terrainScanAhead; + end += currPosition - Point3F(0, 0, mDataBlock->terrainHeightFail); + + if (mContainer->castRay(currPosition, end, TerrainObjectType, &rinfo) || + mContainer->castRay(currPosition, currPosition - Point3F(0, 0, mDataBlock->terrainHeightFail), TerrainObjectType, &rinfo)) + { + // Otherwise, we have to raise the velocity to avoid the terrain. It is assumed that + // the missile won't get here if the velocity vector points straight up. + Point3F stor = *newVelocity; + stor.normalize(); + F32 dot = mDot(Point3F(0, 0, 1), stor); + Point3F axis; + mCross(Point3F(0, 0, 1), stor, &axis); + axis.normalize(); + + if (dot < mDataBlock->cosAvoidSpeed) + dot = mDataBlock->cosAvoidSpeed; + + F32 theta = mAcos(dot); + QuatF newturn(AngAxisF(axis, theta)); + + MatrixF newrot(true); + newturn.setMatrix(&newrot); + stor = *newVelocity; + newrot.mulV(stor, newVelocity); + + return status; + } + + // No terrain collision predicted + return status; +} + + +//-------------------------------------------------------------------------- +void SeekerProjectile::setupWarp() +{ + // This is a little tricky. Here's the deal. LastServerPos and LastServerVel + // should be simulated forward to find the new position after smProjectileWarpTicks + // We have to check to see if we collide with any static objects in the interim, + // in which case, we'll be warping over a reduced number of ticks toward that + // position. mCurrVelocity will be set based on tick distance travelled during + // the warp, and set to the warp end velocity at the end of the warp. + + Point3F simCurrPos = mLastServerPos; + Point3F simCurrVel = mLastServerVel; + + U32 simTicksLeft = smProjectileWarpTicks; + mNumWarpTicks = 0; + + while (simTicksLeft-- != 0) { + mNumWarpTicks++; + + Point3F targPos; + if (getTargetPosition(&targPos) == true) { + Point3F newVelocity; + if (getNewVelocity(simCurrPos, simCurrVel, targPos, &newVelocity) == false) { + // Go unguided... + mTargetingMode = None; + } + simCurrVel = newVelocity; + } else { + if (mTargetingMode == Object) + mTargetPtr = NULL; + mTargetingMode = None; + } + + simCurrPos += simCurrVel * (TickMs / 1000.0); +// F32 hitDelta; +// Point3F hitPos; +// Point3F hitNormal; +// SceneObject* hitObject; +// if (updatePos(simCurrPos, newSimPos, false, &hitDelta, &hitPos, &hitNormal, hitObject) == false) { +// // Bang! Hit something. Need to predict the explosion here? +// +// } + } + + mWarpTicksLeft = mNumWarpTicks; + mWarpStart = mCurrPosition; + mWarpEnd = simCurrPos; + mWarpEndVelocity = simCurrVel; +} + + +//-------------------------------------------------------------------------- +void SeekerProjectile::processTick(const Move* move) +{ + Parent::processTick(move); + + if (isServerObject()) { + // Check out the early exit conditions... + // + if (mCurrTick >= mDeleteTick) { + deleteObject(); + return; + } + else if (mHidden == true) { + // Already exploded + return; + } + else if (mCurrTick >= mDeathTick) { + // Past our lifetime, blow up + Point3F normal = mCurrVelocity * -1; + normal.normalize(); + if (bool(mTargetPtr)) + mTargetPtr->decHomingCount(); + + explode(mCurrPosition, normal); + + if (bool(mTargetPtr)) + mOriginalTargetPtr = mTargetPtr = NULL; + + setMaskBits(ExplosionMask); + return; + } + + Point3F targetPos; + if (getTargetPosition(&targetPos) == true) { + // We're tracking ok still, see if we modify our velocity... + // + Point3F newVelocity; + if (getNewVelocity(mCurrPosition, mCurrVelocity, targetPos, &newVelocity)) { + mCurrVelocity = newVelocity; + } else { + mCurrVelocity = newVelocity; + + F32 distToTarget = (mCurrPosition - targetPos).len(); + if (distToTarget < mDataBlock->proximityRadius) { + // Close enough to blow up the target... + Point3F normal = mCurrVelocity * -1; + normal.normalize(); + if (bool(mTargetPtr)) + mTargetPtr->decHomingCount(); + + explode(mCurrPosition, normal); + + if (bool(mTargetPtr)) + mOriginalTargetPtr = mTargetPtr = NULL; + mDeleteTick = mCurrTick + DeleteWaitTicks; + setMaskBits(ExplosionMask); + return; + } + } + } else { + if (mTargetingMode != None) { + mTargetingMode = None; + setMaskBits(SeekerUpdateMask); + } + } + + + // accelerate missile + bool activeWarhead = mCurrTick >= (mDataBlock->flechetteDelayMs/TickMs); + if( (mCurrTick >= (mDataBlock->flechetteDelayMs/TickMs)) && + (mCurrVelocity.len() < mDataBlock->maxVelocity) ) + { + Point3F excessVel = mExcessDir * mExcessVel; + mCurrVelocity -= excessVel; + + + Point3F curDir = mCurrVelocity; + curDir.normalizeSafe(); + mCurrVelocity += curDir * mDataBlock->acceleration * (TickMs / 1000.0); + + mCurrVelocity += excessVel; + } + + + Point3F newPos = mCurrPosition + mCurrVelocity * (TickMs / 1000.0); + + F32 hitDelta; + Point3F hitPos; + Point3F hitNormal; + SceneObject* hitObject; + + if (updatePos(mCurrPosition, newPos, true, &hitDelta, &hitPos, &hitNormal, hitObject) == false) { + // Collided with something here... + // + if (bool(mTargetPtr)) + mTargetPtr->decHomingCount(); + + if( mHitWater || activeWarhead == false) + { + mHidden = true; + } + else + { + explode(hitPos, hitNormal); + setMaskBits(ExplosionMask); + } + + if (bool(mTargetPtr)) + mOriginalTargetPtr = mTargetPtr = NULL; + + mTargetingMode = None; + + // Need to reset our delete wait time... + mDeleteTick = mCurrTick + DeleteWaitTicks; + return; + } else { + // No collision, set the new position + mCurrPosition = newPos; + MatrixF xform(true); + xform.setColumn(3, mCurrPosition); + setTransform(xform); + } + + // If we're in object mode, we need to udpate frequently to account for + // objects going in and out of scope... + if (mTargetingMode == Object) + setMaskBits(SeekerUpdateMask); + } else { + // Client process tick... + // + if (mHidden == true) { + // Already exploded + return; + } + + if( mPlayedSplash ) + return; + + if (mTargetingMode == Object) { + if (bool(mTargetPtr) == false) { + // The ghost of our target was deleted. Switch to tracking the + // last known position + mTargetingMode = Position; + } else { + // Otherwise, update the last known position if we're in object mode so we + // have a good record of position + mTargetPtr->getWorldBox().getCenter(&mTargetPosition); + } + } + + // accelerate missile + bool activeWarhead = mCurrTick >= (mDataBlock->flechetteDelayMs/TickMs); + if( (mCurrTick >= (mDataBlock->flechetteDelayMs/TickMs)) && + (mCurrVelocity.len() < mDataBlock->maxVelocity) ) + { + mCurrVelocity -= mPlayerVel; + Point3F curDir = mCurrVelocity; + curDir.normalizeSafe(); + mCurrVelocity += curDir * mDataBlock->acceleration * (TickMs / 1000.0); + mCurrVelocity += mPlayerVel; + } + + + Point3F newPosition; + if (mWarpTicksLeft != 0) { + // Do a warp rather than a prediction... + mWarpTicksLeft--; + F32 interpFraction = F32(mNumWarpTicks - mWarpTicksLeft) / F32(mNumWarpTicks); + + newPosition = mWarpStart * (1.0 - interpFraction) + mWarpEnd * interpFraction; + + if (mWarpTicksLeft == 0) + mCurrVelocity = mWarpEndVelocity; + else + mCurrVelocity = (mWarpEnd - mWarpStart) * (1000.0 / F32(mNumWarpTicks * TickMs)); + + } else { + mNumWarpTicks = 0; + + // Prediction... + Point3F targetPos; + if (getTargetPosition(&targetPos) == true) { + Point3F newVelocity; + if (getNewVelocity(mCurrPosition, mCurrVelocity, targetPos, &newVelocity) == false) { + // Unguided. Prediction explosion? + mTargetingMode = None; + } + mCurrVelocity = newVelocity; + } + + newPosition = mCurrPosition + mCurrVelocity * (TickMs / 1000.0); + } + + updateFlechette(newPosition); + + // play exhaust particles out the back of the missile launcher + if( mExhaustEmitter && mCurrTick < mDataBlock->exhaustTimeMs / TickMs ) + { + if( bool(mSourceObject) ) + { + MatrixF imageTrans; + mSourceObject->getImageTransform( mSourceObjectSlot, mDataBlock->exhaustNodeName, &imageTrans ); + + Point3F pos = imageTrans.getPosition(); + Point3F dir; + imageTrans.getColumn( 1, &dir ); + mExhaustEmitter->emitParticles( pos, pos, dir, -mSourceObject->getVelocity(), TickMs ); + } + } + + F32 hitDelta; + Point3F hitPos; + Point3F hitNormal; + SceneObject* hitObject; + if (updatePos(mCurrPosition, newPosition, false, &hitDelta, &hitPos, &hitNormal, hitObject) == false) { + // Collided with something here... + // + U32 flechetteTime = (F32)mDataBlock->flechetteDelayMs/(F32)TickMs * 0.5; + if ((mFlechetteGhostTick != 0xFFFFFFFF) && (mCurrTick - mFlechetteGhostTick) >= flechetteTime) + explode(hitPos, hitNormal); + else + mHidden = true; + + // Need to reset our delete wait time... + mDeleteTick = mCurrTick + DeleteWaitTicks; + setMaskBits(ExplosionMask); + return; + } else { + // No collision, set the new position + mCurrPosition = newPosition; + MatrixF xform(true); + xform.setColumn(3, mCurrPosition); + setTransform(xform); + } + + + // test for splash + if( bool(mSourceObject) ) + mSourceObject->disableCollision(); + + RayInfo rInfo; + if( mContainer->castRay( mCurrPosition, newPosition, WaterObjectType, &rInfo ) ) + { + createSplash( rInfo.point ); + mHidden = true; + } + + if( bool(mSourceObject) ) + mSourceObject->enableCollision(); + + + if( !mPlayedSplash ) + { + mCurrDelta = mCurrPosition - newPosition; + mCurrPosition = newPosition; + mCurrDeltaBase = newPosition; + } + + } +} + + +void SeekerProjectile::interpolateTick(F32 delta) +{ + Parent::interpolateTick(delta); + + if (mHidden == true ) + return; + + Point3F newPos = mCurrDeltaBase + mCurrDelta * delta; + VectorF dir; + + U32 flechetteTime = (F32)mDataBlock->flechetteDelayMs/(F32)TickMs * 0.5; + if( (mCurrTick - mFlechetteGhostTick) >= flechetteTime ) + { + dir = mCurrVelocity; + } + else + { + dir = mInitialDirection; + } + + if( dir.len() > 0.0001 ) + { + dir.normalize(); + + MatrixF xform; + xform = MathUtils::createOrientFromDir( dir ); + xform.setPosition(newPos); + setRenderTransform(xform); + } + + updateSound(newPos, getVelocity(), true); +} + + +//-------------------------------------------------------------------------- +U32 SeekerProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + Point3F tarPos; + + if (stream->writeFlag(mask & InitialUpdateMask)) { + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, mCurrVelocity); + + mathWrite(*stream, mExcessDir * mExcessVel); + + if (bool(mSourceObject)) { + // 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 { + stream->writeFlag(false); + } + + if (stream->writeFlag(getTargetPosition(&tarPos))) { + S32 gIndex = -1; + if (mTargetingMode == Object && bool(mTargetPtr)) + gIndex = con->getGhostIndex(mTargetPtr); + + if (stream->writeFlag(gIndex != -1)) { + // Write ghost index + stream->writeRangedU32(gIndex, 0, NetConnection::MaxGhostCount); + } else { + // Write target position + mathWrite(*stream, tarPos); + } + } + + stream->writeFlag(mCurrTick < (mDataBlock->flechetteDelayMs/TickMs)); + } else { + if (stream->writeFlag(mask & ExplosionMask)) { + // We've exploded. All we need to do is write the position and the normal + Point3F normal = mCurrVelocity * -1; + normal.normalize(); + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, normal); + } else { + // Normal update + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, mCurrVelocity); + if (stream->writeFlag(getTargetPosition(&tarPos))) { + S32 gIndex = -1; + if (mTargetingMode == Object && bool(mTargetPtr)) + gIndex = con->getGhostIndex(mTargetPtr); + + if (stream->writeFlag(gIndex != -1)) { + // Write ghost index + stream->writeRangedU32(gIndex, 0, NetConnection::MaxGhostCount); + } else { + // Write target position + mathWrite(*stream, tarPos); + } + } + } + } + + return retMask; +} + +void SeekerProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) { + mathRead(*stream, &mLastServerPos); + mathRead(*stream, &mLastServerVel); + + mathRead(*stream, &mPlayerVel); + + 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); + + if (bool(mSourceObject) == false) + Con::errorf(ConsoleLogEntry::General, "LinearProjectile::unpackUpdate: could not resolve source ghost properly on initial update"); + } else { + mSourceObjectId = -1; + mSourceObjectSlot = -1; + mSourceObject = NULL; + } + + if (stream->readFlag()) { + if (stream->readFlag()) { + U32 id = stream->readRangedU32(0, NetConnection::MaxGhostCount); + + NetObject* pObject = con->resolveGhost(id); + if (pObject != NULL) { + GameBase* pSB = dynamic_cast(pObject); + if (pSB != NULL) { + mTargetPtr = pSB; + mTargetPtr->getWorldBox().getCenter(&mTargetPosition); + mTargetingMode = Object; + } else { + Con::errorf(ConsoleLogEntry::General, "Error in SeekerProjectile::unpackUpdate: resolved ghost isn't a gamebase"); + mTargetingMode = None; + } + } else { + Con::errorf(ConsoleLogEntry::General, "Error in SeekerProjectile::unpackUpdate: could not resolve ghost properly"); + mTargetingMode = None; + } + } else { + mathRead(*stream, &mTargetPosition); + mTargetingMode = Position; + } + } else { + mTargetingMode = None; + } + + if (stream->readFlag()) { + mFlechetteGhostTick = 0; + } else { + mFlechetteGhostTick = 0xFFFFFFFF; + } + } else { + if (stream->readFlag()) { + // Explosion... + Point3F point, normal; + mathRead(*stream, &point); + mathRead(*stream, &normal); + explode(point, normal); + return; + } else { + // Targeting update... + mathRead(*stream, &mLastServerPos); + mathRead(*stream, &mLastServerVel); + + if (stream->readFlag()) { + if (stream->readFlag()) { + U32 id = stream->readRangedU32(0, NetConnection::MaxGhostCount); + + NetObject* pObject = con->resolveGhost(id); + if (pObject != NULL) { + GameBase* pSB = dynamic_cast(pObject); + if (pSB != NULL) { + mTargetPtr = pSB; + mTargetPtr->getWorldBox().getCenter(&mTargetPosition); + mTargetingMode = Object; + } else { + Con::errorf(ConsoleLogEntry::General, "Error in SeekerProjectile::unpackUpdate: resolved ghost isn't a shapebase"); + mTargetingMode = None; + } + } else { + Con::errorf(ConsoleLogEntry::General, "Error in SeekerProjectile::unpackUpdate: could not resolve ghost properly"); + mTargetingMode = None; + } + } else { + mathRead(*stream, &mTargetPosition); + mTargetingMode = Position; + } + } else { + mTargetingMode = None; + } + + setupWarp(); + } + } +} + +//-------------------------------------------------------------------------- +// Update flechette +//-------------------------------------------------------------------------- +void SeekerProjectile::updateFlechette(const Point3F& pos) +{ + if (!mDataBlock->useFlechette) return; + if (mFlechetteGhostTick == 0xFFFFFFFF) return; + if (mFlechettePuff) return; + + U32 flechetteTime = (F32)mDataBlock->flechetteDelayMs/(F32)TickMs * 0.5; + if( (mCurrTick - mFlechetteGhostTick) >= flechetteTime ) + { + if( !mFlechettePuff ) + { + mFlechettePuff = true; + + if( mPuffEmitter ) + { + mPuffEmitter->emitParticles( mCurrPosition, mCurrPosition, Point3F( 0.0, 0.0, 1.0 ), Point3F( 0.0, 0.0, 0.0 ), 1000 ); + } + + MatrixF trans = getRenderTransform(); + trans.setPosition( pos ); + + Debris *debList[4]; + + for( int i=0; i<4; i++ ) + { + debList[i] = new Debris; + debList[i]->onNewDataBlock( mDataBlock->casingDeb ); + + if( !debList[i]->registerObject() ) + { + delete debList[i]; + debList[i] = NULL; + } + else + { + debList[i]->setTransform( trans ); + } + } + + // ughhh, hard code the velocities and rotations for flechettes for now. + // if more tweaking is necessary, move to script - bramage + F32 debVel = 8.0; + + if( debList[0] ) + { + Point3F dir( -gRandGen.randF( 1.0, 2.0 ), 5.0, -gRandGen.randF( 1.0, 2.0 ) ); + dir.normalize(); + dir *= debVel; + trans.mulV( dir ); + debList[0]->init( pos, dir ); + + debList[0]->setRotAngles( Point3F(450.0, 00.0, -450.0) ); + } + + if( debList[1] ) + { + Point3F dir( gRandGen.randF( 1.0, 2.0 ), 5.0, -gRandGen.randF( 1.0, 2.0 ) ); + dir.normalize(); + dir *= debVel; + trans.mulV( dir ); + debList[1]->init( pos, dir ); + + debList[1]->setRotAngles( Point3F(450.0, 00.0, 450.0) ); + } + + if( debList[2] ) + { + Point3F dir( gRandGen.randF( 1.0, 2.0 ), 5.0, gRandGen.randF( 1.0, 2.0 ) ); + dir.normalize(); + dir *= debVel; + trans.mulV( dir ); + debList[2]->init( pos, dir ); + + debList[2]->setRotAngles( Point3F(-450.0, 00.0, 450.0) ); + } + + if( debList[3] ) + { + Point3F dir( -gRandGen.randF( 1.0, 2.0 ), 5.0, gRandGen.randF( 1.0, 2.0 ) ); + dir.normalize(); + dir *= debVel; + trans.mulV( dir ); + debList[3]->init( pos, dir ); + + debList[3]->setRotAngles( Point3F(-450.0, 00.0, -450.0) ); + } + + } + } + +} + +//-------------------------------------------------------------------------- +// RENDER +//-------------------------------------------------------------------------- +void SeekerProjectile::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mRenderObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + TSShapeInstance *shape; + + if( mDataBlock->useFlechette && !mFlechettePuff ) + { + shape = mCasingShape; + } + else + { + shape = mProjectileShape; + } + + + shape->selectCurrentDetail(); + shape->animate(); + + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 fogAmount = state->getHazeAndFog(cameraOffset.len(),cameraOffset.z); + + if (mFadeValue == 1.0) { + shape->setupFog(fogAmount, state->getFogColor()); + } else { + shape->setupFog(0.0, state->getFogColor()); + shape->setAlphaAlways(mFadeValue * (1.0 - fogAmount)); + } + shape->render(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +// Advance time +//-------------------------------------------------------------------------- +void SeekerProjectile::advanceTime( F32 dt ) +{ + Parent::advanceTime(dt); + + if( mPlayedSplash ) return; + + // delay emitting smoke and fire from projectile until after it is done ejecting + if( !mDataBlock->useFlechette || mCurrTick >= (mDataBlock->flechetteDelayMs/TickMs) ) + { + emitParticles( getRenderPosition(), mLastPos, mCurrVelocity, dt*1000.0 ); + } + + mLastPos = getRenderPosition(); +} + +//-------------------------------------------------------------------------- +// Register lights +//-------------------------------------------------------------------------- +void SeekerProjectile::registerLights( LightManager * lightManager, bool lightingScene ) +{ + if(lightingScene) + return; + + if( !mDataBlock->useFlechette || mCurrTick >= (mDataBlock->flechetteDelayMs/TickMs) ) + { + if (mDataBlock->hasLight && mHidden == false) { + mLight.mType = LightInfo::Point; + getRenderTransform().getColumn(3, &mLight.mPos); + mLight.mRadius = mDataBlock->lightRadius; + mLight.mColor = mDataBlock->lightColor; + lightManager->addLight(&mLight); + } + } +} diff --git a/game/projSeeker.h b/game/projSeeker.h new file mode 100644 index 0000000..3510726 --- /dev/null +++ b/game/projSeeker.h @@ -0,0 +1,205 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_SEEKERPROJECTILE +#define _H_SEEKERPROJECTILE + +#ifndef _PROJECTILE_H_ +#include "game/projectile.h" +#endif +#ifndef _PARTICLEENGINE_H_ +#include "game/particleEngine.h" +#endif + +struct DebrisData; +class TSShapeInstance; +class TSShape; + +//-------------------------------------------------------------------------- +class SeekerProjectileData : public ProjectileData +{ + typedef ProjectileData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + U32 lifetimeMS; + + F32 muzzleVelocity; + F32 turningSpeed; + F32 maxVelocity; + F32 acceleration; + + F32 proximityRadius; + + F32 terrainAvoidanceSpeed; + F32 terrainScanAhead; + F32 terrainHeightFail; + F32 terrainAvoidanceRadius; + + F32 flareDistance; + F32 flareAngle; + + bool useFlechette; + U32 flechetteDelayMs; + + ParticleEmitterData* puffEmitter; + S32 puffEmitterID; + + ParticleEmitterData* exhaustEmitter; + S32 exhaustEmitterID; + S32 exhaustTimeMs; + StringTableEntry exhaustNodeName; + + DebrisData * casingDeb; + S32 casingDebID; + + StringTableEntry casingShapeName; + + + //-------------------------------------- load set variables + public: + Resource casingShape; + + F32 cosTurningSpeed; + F32 cosAvoidSpeed; + F32 cosFlareAngle; + + public: + SeekerProjectileData(); + ~SeekerProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + + bool calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& sourceVel, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime); + + DECLARE_CONOBJECT(SeekerProjectileData); + static void initPersistFields(); +}; + + +class SeekerProjectile : public Projectile +{ + typedef Projectile Parent; + friend void cSetTarget(SimObject*, S32, const char**); + + private: + SeekerProjectileData* mDataBlock; + TSShapeInstance* mCasingShape; + + public: + enum LPUpdateMasks { + SeekerUpdateMask = Parent::NextFreeMask, + ExplosionMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + enum TargetingMode { + Object, + Position, + None + }; + + Point3F mTargetPosition; + S32 mTargetId; + TargetingMode mTargetingMode; + StringTableEntry mTargetingString; + + SimObjectPtr mTargetPtr; + SimObjectPtr mOriginalTargetPtr; + + protected: + bool mExplosionPending; + bool mHitWater; + S32 mDeleteTick; + S32 mDeathTick; + + Point3F mCurrPosition; + Point3F mCurrVelocity; + Point3F mPlayerVel; + Point3F mLastPos; + + // Client side only variables + Point3F mLastServerPos; + Point3F mLastServerVel; + Point3F mServerTarget; + bool mServerTargetValid; + + U32 mWarpTicksLeft; + U32 mNumWarpTicks; + Point3F mWarpStart; + Point3F mWarpEnd; + Point3F mWarpEndVelocity; + + Point3F mCurrDelta; + Point3F mCurrDeltaBase; + + ParticleEmitter* mPuffEmitter; + ParticleEmitter* mExhaustEmitter; + + bool mFlechettePuff; + + U32 mFlechetteGhostTick; + + Point3F mExplosionPosition; + Point3F mExplosionNormal; + + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + void advanceTime( F32 dt ); + void registerLights(LightManager * lightManager, bool lightingScene ); + + protected: + bool getTargetPosition(Point3F*); + Point3F getVelocity() const; + void explode(const Point3F&, const Point3F&); + bool updatePos(const Point3F&, const Point3F&, const bool, + F32*, Point3F*, Point3F*, SceneObject*&); + bool getNewVelocity(const Point3F& currPosition, + const Point3F& currVelocity, + const Point3F& targetPosition, + Point3F* newVelocity); + void setupWarp(); + + + bool calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime); + + void updateFlechette(const Point3F&); + void renderObject(SceneState*, SceneRenderImage*); + + public: + SeekerProjectile(); + ~SeekerProjectile(); + + void processTick(const Move*); + void interpolateTick(F32); + + DECLARE_CONOBJECT(SeekerProjectile); + static void consoleInit(); + + S32 getTargetObjectId(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_SEEKERPROJECTILE + diff --git a/game/projShockLance.cc b/game/projShockLance.cc new file mode 100644 index 0000000..b6efd9f --- /dev/null +++ b/game/projShockLance.cc @@ -0,0 +1,1179 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projShockLance.h" +#include "Core/bitStream.h" +#include "game/shapeBase.h" +#include "PlatformWin32/platformGL.h" +#include "dgl/dgl.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "console/consoleTypes.h" +#include "ts/tsShapeInstance.h" +#include "Sim/netConnection.h" +#include "Math/mathIO.h" +#include "game/particleEngine.h" +#include "Math/mRandom.h" +#include "game/shockwave.h" +#include "game/gameConnection.h" +#include "Math/mathUtils.h" + +// Not worth the effort, much less the effort to comment, but if the draw types +// are consecutive use addition rather than a table to go from index to command value... +#if ((GL_TRIANGLES+1==GL_TRIANGLE_STRIP) && (GL_TRIANGLE_STRIP+1==GL_TRIANGLE_FAN)) + #define getDrawType(a) (GL_TRIANGLES+(a)) +#else + U32 drawTypes[] = { GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN }; + #define getDrawType(a) (drawTypes[a]) +#endif + +#define NO_HIT_LENGTH (0.2) + +IMPLEMENT_CO_DATABLOCK_V1(ShockLanceProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(ShockLanceProjectile); + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +ShockLanceProjectileData::ShockLanceProjectileData() +{ + zapDuration = 0.5; + boltLength = 2.0; + + startWidth[0] = 0.2; + startWidth[1] = 0.2; + endWidth[0] = 1.0; + endWidth[1] = 1.0; + boltSpeed[0] = 1.0; + boltSpeed[1] = 1.2; + texWrap[0] = 1.0; + texWrap[1] = 1.0; + numParts = 25; + lightningFreq = 10.0; + lightningDensity = 3.0; + lightningAmp = 0.5; + lightningWidth = 0.1; + shockwave = NULL; + shockwaveID = 0; + + dMemset( textureName, 0, sizeof( textureName ) ); + dMemset( emitterList, 0, sizeof( emitterList ) ); + dMemset( emitterIDList, 0, sizeof( emitterIDList ) ); +} + +ShockLanceProjectileData::~ShockLanceProjectileData() +{ + +} + +bool ShockLanceProjectileData::calculateAim(const Point3F& targetPos, + const Point3F& /*targetVel*/, + const Point3F& sourcePos, + const Point3F& /*sourceVel*/, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime) +{ + //make sure the source and target points are within range + if ((targetPos - sourcePos).len() >= boltLength + 2.0f) + return false; + else + { + *outputVectorMin = targetPos - sourcePos; + *outputMinTime = 0.001f; + outputVectorMin->normalizeSafe(); + *outputVectorMax = *outputVectorMin; + *outputMaxTime = *outputMinTime; + + return true; + } +} + + +//-------------------------------------------------------------------------- +void ShockLanceProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("zapDuration", TypeF32, Offset(zapDuration, ShockLanceProjectileData)); + addField("boltLength", TypeF32, Offset(boltLength, ShockLanceProjectileData)); + addField("texture", TypeString, Offset(textureName, ShockLanceProjectileData), NUM_TEX ); + addField("startWidth", TypeF32, Offset(startWidth, ShockLanceProjectileData), NUM_BOLTS ); + addField("endWidth", TypeF32, Offset(endWidth, ShockLanceProjectileData), NUM_BOLTS ); + addField("boltSpeed", TypeF32, Offset(boltSpeed, ShockLanceProjectileData), NUM_BOLTS ); + addField("texWrap", TypeF32, Offset(texWrap, ShockLanceProjectileData), NUM_BOLTS ); + addField("numParts", TypeS32, Offset(numParts, ShockLanceProjectileData)); + addField("lightningFreq", TypeF32, Offset(lightningFreq, ShockLanceProjectileData)); + addField("lightningDensity", TypeF32, Offset(lightningDensity,ShockLanceProjectileData)); + addField("lightningAmp", TypeF32, Offset(lightningAmp, ShockLanceProjectileData)); + addField("lightningWidth", TypeF32, Offset(lightningWidth, ShockLanceProjectileData)); + addField("emitter", TypeParticleEmitterDataPtr, Offset(emitterList, ShockLanceProjectileData), NUM_EMITTERS); + addField("shockwave", TypeShockwaveDataPtr, Offset(shockwave, ShockLanceProjectileData)); +} + + +//-------------------------------------------------------------------------- +bool ShockLanceProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + + if (zapDuration < 0.05 || zapDuration >= 30.0) { + Con::warnf(ConsoleLogEntry::General, + "ShockLanceProjectileData(%s)::onAdd: zapduration must be in range [0.05, 2.0]", + getName()); + zapDuration = zapDuration < 0.05 ? 0.05 : 2.0; + } + + + if (boltLength < 0.5 || boltLength >= 50) { + Con::warnf(ConsoleLogEntry::General, + "ShockLanceProjectileData(%s)::onAdd: boltLength must be in range [0.5, 50.0]", + getName()); + boltLength = boltLength < 0.5 ? 0.5 : 5.0; + } + + + return true; +} + + +bool ShockLanceProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (server == false) + { + + U32 i; + for( i=0; iwrite(zapDuration); + stream->write(boltLength); + stream->write(numParts); + stream->write(lightningFreq); + stream->write(lightningDensity); + stream->write(lightningAmp); + stream->write(lightningWidth); + + if( stream->writeFlag( shockwave ) ) + { + stream->writeRangedU32( shockwave->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + U32 i; + for( i=0; iwrite( startWidth[i] ); + stream->write( endWidth[i] ); + stream->write( boltSpeed[i] ); + stream->write( texWrap[i] ); + } + + for( i=0; iwriteString(textureName[i]); + } + + for( i=0; iwriteFlag( emitterList[i] != NULL ) ) + { + stream->writeRangedU32( emitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + +} + +void ShockLanceProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&zapDuration); + stream->read(&boltLength); + stream->read(&numParts); + stream->read(&lightningFreq); + stream->read(&lightningDensity); + stream->read(&lightningAmp); + stream->read(&lightningWidth); + + if( stream->readFlag() ) + { + shockwaveID = (S32) stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + U32 i; + for( i=0; iread( &startWidth[i] ); + stream->read( &endWidth[i] ); + stream->read( &boltSpeed[i] ); + stream->read( &texWrap[i] ); + } + + for( i=0; ireadSTString(); + } + + for( i=0; ireadFlag() ) + { + emitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ShockLanceProjectile::ShockLanceProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + mElapsedTime = 0; + mTargetId = 0; + mElectricity = NULL; + mTimeSinceLastLightningBolt = 0.0; + mHitObject = false; + + dMemset( mEmitterList, 0, sizeof( mEmitterList ) ); +} + +ShockLanceProjectile::~ShockLanceProjectile() +{ + // +} + +//-------------------------------------------------------------------------- +void ShockLanceProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + addField("targetId", TypeS32, Offset(mTargetId, ShockLanceProjectile)); +} + + +void ShockLanceProjectile::consoleInit() +{ + +} + + +//-------------------------------------------------------------------------- +bool ShockLanceProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isServerObject()) + { + ShapeBase* pTarget; + if (mTargetId != 0 && Sim::findObject(mTargetId, pTarget)) { + mTargetPtr = pTarget; + } + + mDeleteWaitTicks = ((mDataBlock->zapDuration) * 1000.0f) / TickMs; + + mStart = mInitialPosition; + mEnd = mInitialPosition + mInitialDirection * mDataBlock->boltLength; + + RayInfo rInfo; + if( gServerContainer.castRay( mStart, mEnd, csmStaticCollisionMask | csmDynamicCollisionMask, &rInfo) == true) + { + + U32 typeMask = rInfo.object->getTypeMask(); + if( typeMask &= U32(csmDamageableMask) ) + { + + if( mTargetPtr && !mTargetPtr->isInvincible() ) + { + mEnd = rInfo.point; + mHitObject = true; + } + } + } + } + else + { + if (bool(mTargetPtr) && mHitObject ) { + // Need to spawn electricity + ShockLanceElectricity* pElectricity = new ShockLanceElectricity; + pElectricity->mTarget = mTargetPtr; + pElectricity->registerObject(); + mElectricity = pElectricity; + mElectricity->setDataBlock( mDataBlock ); + } + + if( mDataBlock->shockwave && mHitObject ) + { + VectorF normal = mStart - mEnd; + normal.normalizeSafe(); + + MatrixF trans = getTransform(); + trans.setPosition( mEnd ); + Shockwave* shockwave = new Shockwave; + shockwave->onNewDataBlock( mDataBlock->shockwave ); + shockwave->setTransform( trans ); + shockwave->setInitialState( trans.getPosition(), normal ); + if (!shockwave->registerObject()) + delete shockwave; + } + + + U32 i; + for( i=0; iendWidth[i] - mDataBlock->startWidth[i] ) / mDataBlock->zapDuration; + } + + for( 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; + } + } + + if( mHitObject ) + { + VectorF dir = mEnd - mStart; + dir.normalizeSafe(); + + if( dir.magnitudeSafe() > 0.0 && mEmitterList[0] ) + { + mEmitterList[0]->emitParticles( mStart, mEnd, dir, Point3F( 0.0, 0.0, 0.0 ), mDataBlock->numParts ); + } + } + } + + mObjBox.min = mEnd; + mObjBox.max = mEnd; + mObjBox.min.setMin(mStart); + mObjBox.max.setMax(mStart); + + resetWorldBox(); + + addToScene(); + + return true; +} + + +void ShockLanceProjectile::onRemove() +{ + removeFromScene(); + + if (bool(mElectricity)) + mElectricity->deleteObject(); + + for( int i=0; ideleteWhenEmpty(); + mEmitterList[i] = NULL; + } + } + + Parent::onRemove(); +} + + +bool ShockLanceProjectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + + +//-------------------------------------------------------------------------- +void ShockLanceProjectile::processTick(const Move* move) +{ + Parent::processTick(move); + + if (isServerObject()) { + if (--mDeleteWaitTicks <= 0) + deleteObject(); + } +} + + +void ShockLanceProjectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if( mElectricity ) + { + mElectricity->advanceTime( dt ); + } + + mElapsedTime += dt; + + mTimeSinceLastLightningBolt += dt; + + F32 freqRecip = (1.0/mDataBlock->lightningFreq); + if( mTimeSinceLastLightningBolt >= freqRecip ) + { + mTimeSinceLastLightningBolt -= freqRecip; + + for( U32 i=0; ilightningDensity; + F32 amp = mDataBlock->lightningAmp; + if( !mHitObject ) + { + density = 20.0; + amp = 0.1; + + updateLightningPos(); + } + createBolt( mBoltList[i], density, amp ); + } + } +} + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +void ShockLanceProjectile::updateLightningPos() +{ + + VectorF muzzleDir; + if( mSourceObject ) + { + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mStart); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &muzzleDir); + muzzleDir.normalizeSafe(); + mEnd = mStart + muzzleDir * NO_HIT_LENGTH; + + mObjBox.min = mEnd; + mObjBox.max = mEnd; + mObjBox.min.setMin(mStart); + mObjBox.max.setMax(mStart); + + resetWorldBox(); + } +} + + +//-------------------------------------------------------------------------- +bool ShockLanceProjectile::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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + image->tieBreaker = true; + + state->insertRenderImage(image); + } + + return false; +} + + +void ShockLanceProjectile::renderObject(SceneState* state, + SceneRenderImage* /*sceneImage*/) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + if( !mHitObject ) + { + updateLightningPos(); + } + else + { + renderBeam( state->getCameraPosition(), 0 ); + renderBeam( state->getCameraPosition(), 1 ); + } + + renderLightning( state->getCameraPosition() ); + + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + + dglSetCanonicalState(); + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +// Render lightning +//-------------------------------------------------------------------------- +void ShockLanceProjectile::renderLightning( const Point3F &camPos ) +{ + + F32 lightningAlpha = 1.0 - (mElapsedTime / mDataBlock->zapDuration); + + glDepthMask(GL_FALSE); + glEnable( GL_DEPTH_TEST ); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glEnable(GL_TEXTURE_2D); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[ShockLanceProjectileData::PROJECTILE_TEX].getGLName() ); + glColor4f( 1.0, 1.0, 1.0, lightningAlpha ); + + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + VectorF dir = mEnd - mStart; + dir.normalizeSafe(); + MatrixF m = MathUtils::createOrientFromDir( dir ); + m.setPosition( mStart ); + + dglMultMatrix( &m ); + + for( U32 i=0; ilightningWidth ); + } + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + +} + +//-------------------------------------------------------------------------- +void ShockLanceProjectile::renderBeam( const Point3F &camPos, U32 boltNum ) +{ + Point3F dir = mEnd - mStart; + dir.normalizeSafe(); + + Point3F pos = mStart; + Point3F dirFromCam = pos - camPos; + Point3F crossDir; + mCross( dirFromCam, dir, &crossDir ); + crossDir.normalizeSafe(); + + + F32 width = (mDataBlock->startWidth[boltNum] + (mBeamWidthVel[boltNum] * mElapsedTime)) * 0.5; + crossDir *= width; + + + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + glEnable( GL_DEPTH_TEST ); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glEnable(GL_TEXTURE_2D); + + F32 endPointAlpha = 1.0 - (mElapsedTime / mDataBlock->zapDuration); + F32 startPointAlpha = 0.0; + + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[ShockLanceProjectileData::PROJECTILE_TEX].getGLName() ); + + glBegin(GL_QUADS); + + glColor4f( 1.0, 1.0, 1.0, startPointAlpha ); + glTexCoord2f(mDataBlock->boltSpeed[boltNum] * mElapsedTime, 0); + glVertex3fv( mStart + crossDir ); + + glTexCoord2f(mDataBlock->boltSpeed[boltNum] * mElapsedTime, 1); + glVertex3fv( mStart - crossDir ); + + glColor4f( 1.0, 1.0, 1.0, endPointAlpha ); + glTexCoord2f(mDataBlock->boltSpeed[boltNum] * mElapsedTime - (1.0 * mDataBlock->texWrap[boltNum]), 1); + glVertex3fv( mEnd - crossDir ); + + glTexCoord2f(mDataBlock->boltSpeed[boltNum] * mElapsedTime - (1.0 * mDataBlock->texWrap[boltNum]), 0); + glVertex3fv( mEnd + crossDir ); + + glEnd(); + +} + + +//-------------------------------------------------------------------------- +U32 ShockLanceProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (bool(mTargetPtr)) { + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + S32 ghostIndex = con->getGhostIndex(mTargetPtr); + if (stream->writeFlag(ghostIndex != -1)) + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount); + } else { + stream->writeFlag(false); + } + + if (stream->writeFlag(mask & GameBase::InitialUpdateMask)) + { + mathWrite(*stream, mStart); + mathWrite(*stream, mEnd); + stream->writeFlag( mHitObject ); + + if (bool(mSourceObject)) { + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + bool isFiredOnClient = mSourceObject->getControllingClient() == con; + + 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 { + stream->writeFlag(false); + } + + } + + return retMask; +} + +//-------------------------------------------------------------------------- +void ShockLanceProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) { + mTargetId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + + NetObject* pObject = con->resolveGhost(mTargetId); + if (pObject != NULL) + mTargetPtr = dynamic_cast(pObject); + + if (bool(mTargetPtr) == false) + Con::errorf(ConsoleLogEntry::General, "LinearProjectile::unpackUpdate: could not resolve source ghost properly on initial update"); + } else { + mTargetId = 0; + mTargetPtr = NULL; + } + + // initial update + if( stream->readFlag() ) + { + mathRead(*stream, &mStart); + mathRead(*stream, &mEnd); + mHitObject = stream->readFlag(); + + 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; + } + } + +} + +//-------------------------------------------------------------------------- +// Create bolt +//-------------------------------------------------------------------------- +void ShockLanceProjectile::createBolt( LightningBolt &bolt, U32 density, F32 amplitude ) +{ + + F32 lanceLength = Point3F( mEnd - mStart ).magnitudeSafe(); + U32 numPoints = lanceLength * density; + F32 pointInc = lanceLength / numPoints; + VectorF dir( 0.0, 1.0, 0.0 ); + + + bolt.numPoints = numPoints; + + if( numPoints > NUM_POINTS ) + { + numPoints = NUM_POINTS; + } + + for( U32 i=0; igetShapeInstance(); + if (pInstance == NULL) + return false; + + TSMaterialList* pBase = pInstance->getMaterialList(); + mCopiedList = new TSMaterialList(pBase); + + mObjBox = mTarget->getObjBox(); + resetWorldBox(); + MatrixF transform = mTarget->getTransform(); + setTransform(transform); + + mStartTime = Sim::getCurrentTime(); + + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + + NetConnection* pNC = NetConnection::getServerConnection(); + AssertFatal(pNC != NULL, "Error, must have a connection to the server!"); + pNC->addObject(this); + + if( isClientObject() ) + { + TSShapeInstance* pInstance = mTarget->getShapeInstance(); + mShapeDetail = pInstance->getNumDetails(); + } + + return true; +} + +//-------------------------------------------------------------------------- +void ShockLanceElectricity::onRemove() +{ + while( mTexCoordList.size() ) + { + Point2F *ptr = mTexCoordList[ mTexCoordList.size() - 1 ]; + mTexCoordList.pop_back(); + delete [] ptr; + } + + + delete mCopiedList; + mCopiedList = NULL; + mTarget = NULL; + + if (mSceneManager != NULL) + mSceneManager->removeObjectFromScene(this); + if (getContainer() != NULL) + getContainer()->removeObject(this); + + + Parent::onRemove(); +} + + + +//-------------------------------------------------------------------------- +bool ShockLanceElectricity::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + if (bool(mTarget) == false ) + 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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + + state->insertRenderImage(image); + } + + return false; +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void ShockLanceElectricity::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + renderElectricity(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + dglSetCanonicalState(); + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +void ShockLanceElectricity::processTick( const Move* ) +{ + if (bool(mTarget)) + { + MatrixF transform = mTarget->getTransform(); + setTransform(transform); + } +} + +void ShockLanceElectricity::advanceTime( F32 dt ) +{ + mElapsedTime += dt; +} + +//-------------------------------------------------------------------------- +void ShockLanceElectricity::renderElectricity() +{ + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + + dglMultMatrix( &mTarget->getRenderTransform() ); + glScalef( 1.05, 1.05, 1.05 ); + + TSShapeInstance* pInstance = mTarget->getShapeInstance(); + + pInstance->setStatics(mShapeDetail); + + // setup texture + F32 flickerPerSec = 10.0; + F32 numFrames = ShockLanceProjectileData::NUM_ELECTRIC_TEX - 0.0001; + U32 texNum = mFmod( mElapsedTime, 1.0 / flickerPerSec ) * flickerPerSec * (numFrames); + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE); // should leave alpha alone since emap has no alpha + glEnable(GL_TEXTURE_2D); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[texNum].getGLName() ); + + glEnable( GL_CULL_FACE ); + glDepthMask(GL_FALSE); + glEnable( GL_DEPTH_TEST ); + + // set up texture motion + F32 x = mElapsedTime * 2.0; + F32 y = mElapsedTime * 1.0; + + glMatrixMode(GL_TEXTURE); + glPushMatrix(); + glScalef(1, 1, 1); + glTranslatef(x, y, 0); + glMatrixMode(GL_MODELVIEW); + + + + glEnable(GL_TEXTURE_GEN_S); + glEnable(GL_TEXTURE_GEN_T); + glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + + F32 SBase[4]; + F32 TBase[4]; + + calcTexPlanes( SBase, TBase ); + + glTexGenfv(GL_S, GL_OBJECT_PLANE, SBase ); + glTexGenfv(GL_T, GL_OBJECT_PLANE, TBase ); + + + glColor4f(1.0, 1.0, 1.0, 1.0 - (mElapsedTime / mDataBlock->zapDuration) ); + + + const TSDetail * detail = &pInstance->getShape()->details[mShapeDetail]; + S32 ss = detail->subShapeNum; + + S32 start = pInstance->smNoRenderNonTranslucent ? pInstance->getShape()->subShapeFirstTranslucentObject[ss] : pInstance->getShape()->subShapeFirstObject[ss]; + S32 end = pInstance->smNoRenderTranslucent ? pInstance->getShape()->subShapeFirstTranslucentObject[ss] : pInstance->getShape()->subShapeFirstObject[ss] + pInstance->getShape()->subShapeNumObjects[ss]; + + U32 texPtr = 0; + U32 i=0; + for (i=start; imMeshObjects[i].getMesh(mShapeDetail); + if( !curMesh ) continue; + if( pInstance->mMeshObjects[i].visible <= 0.01f ) continue; + + glPushMatrix(); + + MatrixF *trans = pInstance->mMeshObjects[i].getTransform(); + + dglMultMatrix( trans ); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + S32 firstVert = curMesh->vertsPerFrame * pInstance->mMeshObjects[i].frame; + glVertexPointer(3,GL_FLOAT,0,&curMesh->verts[firstVert]); + glNormalPointer(GL_FLOAT,0,&curMesh->norms[firstVert]); + + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE); + + for (S32 j=0; jprimitives.size(); j++) + { + TSDrawPrimitive & draw = curMesh->primitives[j]; + S32 drawType = getDrawType(draw.matIndex>>30); + glDrawElements(drawType,draw.numElements,GL_UNSIGNED_SHORT,&curMesh->indices[draw.start]); + } + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + + glPopMatrix(); + } + + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + + glMatrixMode(GL_TEXTURE); + glPopMatrix(); + + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glDepthMask(GL_TRUE); + +} + +//-------------------------------------------------------------------------- +// +//-------------------------------------------------------------------------- +void ShockLanceElectricity::calcTexPlanes( F32 *S, F32 *T ) +{ + TSShapeInstance* pInstance = mTarget->getShapeInstance(); + + Box3F bounds; + pInstance->computeBounds( mShapeDetail, bounds ); + VectorF bNorm = bounds.max - bounds.min; + + // you MUST set this after calling computeBounds! + pInstance->setStatics(mShapeDetail); + + if( fabs(bNorm.x) > fabs(bNorm.y) && fabs(bNorm.x) > fabs(bNorm.z) ) + { + F32 size = (1.0 / bNorm.x) * bNorm.x * 0.25; + + S[0] = size; + S[1] = 0.0; + S[2] = 0.0; + S[3] = 0.0; + + T[0] = 0.0; + T[1] = 0.0; + T[2] = size; + T[3] = 0.0; + } + else + { + if( fabs(bNorm.y) > fabs(bNorm.x) && fabs(bNorm.y) > fabs(bNorm.z) ) + { + F32 size = (1.0 / bNorm.y) * bNorm.y * 0.25; + + S[0] = 0.0; + S[1] = size; + S[2] = 0.0; + S[3] = 0.0; + + T[0] = 0.0; + T[1] = 0.0; + T[2] = size; + T[3] = 0.0; + } + else + { + if( fabs(bNorm.z) > fabs(bNorm.x) && fabs(bNorm.z) > fabs(bNorm.y) ) + { + F32 size = (1.0 / bNorm.z) * bNorm.z * 0.25; + + S[0] = size; + S[1] = 0.0; + S[2] = 0.0; + S[3] = 0.0; + + T[0] = 0.0; + T[1] = 0.0; + T[2] = size; + T[3] = 0.0; + } + } + } + +} + diff --git a/game/projShockLance.h b/game/projShockLance.h new file mode 100644 index 0000000..fb10594 --- /dev/null +++ b/game/projShockLance.h @@ -0,0 +1,205 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_SHOCKLANCEPROJECTILE +#define _H_SHOCKLANCEPROJECTILE + +#ifndef _PROJECTILE_H_ +#include "game/projectile.h" +#endif + +class ShockLanceProjectile; +class ShockLanceProjectileData; +class ShockwaveData; + +// ------------------------------------------------------------------------- +class ShockLanceElectricity : public SceneObject +{ + typedef SceneObject Parent; + friend class ShockLanceProjectile; + + ShockLanceProjectileData * mDataBlock; + + Vector < Point2F * > mTexCoordList; + + protected: + SimObjectPtr mTarget; + TSMaterialList* mCopiedList; + + U32 mStartTime; + F32 mElapsedTime; + U32 mShapeDetail; + + protected: + bool onAdd(); + void onRemove(); + void calcTexPlanes( F32 *S, F32 *T ); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + void renderElectricity(); + + + public: + ShockLanceElectricity(); + ~ShockLanceElectricity(); + + void processTick( const Move* ); + void advanceTime( F32 dt ); + void setDataBlock( ShockLanceProjectileData *block ){ mDataBlock = block; } +}; + + +//-------------------------------------------------------------------------- +class ShockLanceProjectileData : public ProjectileData +{ + typedef ProjectileData Parent; + + public: + enum Constants + { + NUM_ELECTRIC_TEX = 3, + NUM_TEX = 4, + PROJECTILE_TEX = 3, + NUM_BOLTS = 2, + NUM_EMITTERS = 1, + }; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + F32 zapDuration; + F32 boltLength; + F32 startWidth[NUM_BOLTS]; + F32 endWidth[NUM_BOLTS]; + F32 boltSpeed[NUM_BOLTS]; + F32 texWrap[NUM_BOLTS]; + U32 numParts; + F32 lightningFreq; + F32 lightningDensity; + F32 lightningAmp; + F32 lightningWidth; + + StringTableEntry textureName[NUM_TEX]; + ParticleEmitterData* emitterList[NUM_EMITTERS]; + S32 emitterIDList[NUM_EMITTERS]; + ShockwaveData * shockwave; + S32 shockwaveID; + + //-------------------------------------- load set variables + public: + TextureHandle textureHandle[NUM_TEX]; + + public: + ShockLanceProjectileData(); + ~ShockLanceProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + bool calculateAim(const Point3F& targetPos, + const Point3F& /*targetVel*/, + const Point3F& sourcePos, + const Point3F& /*sourceVel*/, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime); + + DECLARE_CONOBJECT(ShockLanceProjectileData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class ShockLanceProjectile : public Projectile +{ + typedef Projectile Parent; + + public: + enum Constants + { + NUM_LIGHTNING_BOLTS = 2, + NUM_POINTS = 50, + }; + + private: + ShockLanceProjectileData* mDataBlock; + ParticleEmitter * mEmitterList[ ShockLanceProjectileData::NUM_EMITTERS ]; + + struct LightningBolt + { + Point3F points[NUM_POINTS]; + U32 numPoints; + + LightningBolt() + { + dMemset( this, 0, sizeof( LightningBolt ) ); + numPoints = 0; + } + + }; + + LightningBolt mBoltList[NUM_LIGHTNING_BOLTS]; + F32 mTimeSinceLastLightningBolt; + bool mHitObject; + + protected: + S32 mDeleteWaitTicks; + + S32 mTargetId; + SimObjectPtr mTargetPtr; + SimObjectPtr mElectricity; + + Point3F mStart; + Point3F mEnd; + + F32 mBeamWidthVel[ ShockLanceProjectileData::NUM_BOLTS ]; + F32 mElapsedTime; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + void updateLightningPos(); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderBeam( const Point3F &camPos, U32 boltNum ); + void renderObject(SceneState*, SceneRenderImage*); + + void createBolt( LightningBolt &bolt, U32 density, F32 amplitude ); + void renderBolt( LightningBolt &bolt, const Point3F &camPos, F32 width ); + void renderLightning( const Point3F &camPos ); + + + // Time/Move Management + public: + void processTick(const Move*); + void advanceTime(F32); + + public: + ShockLanceProjectile(); + ~ShockLanceProjectile(); + + DECLARE_CONOBJECT(ShockLanceProjectile); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); + +}; + +#endif // _H_SHOCKLANCEPROJECTILE + diff --git a/game/projSniper.cc b/game/projSniper.cc new file mode 100644 index 0000000..3a2ab4f --- /dev/null +++ b/game/projSniper.cc @@ -0,0 +1,933 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projSniper.h" +#include "Core/bitStream.h" +#include "console/consoleTypes.h" +#include "Math/mathIO.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "game/explosion.h" +#include "terrain/waterBlock.h" + +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "dgl/dgl.h" +#include "PlatformWin32/platformGL.h" +#include "game/splash.h" + + +#define COUPLE_BEAM 1 + +IMPLEMENT_CO_DATABLOCK_V1(SniperProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(SniperProjectile); + + +//************************************************************************** +// Sniper Projectile Data +//************************************************************************** +SniperProjectileData::SniperProjectileData() +{ + maxRifleRange = 1000; + rifleHeadMultiplier = 1.2; + beamColor.set(1, 0.25, 0.25); + fadeTime = 1; + + textureName[ST_FLARE] = "special/flare"; + textureName[ST_BEAM] = "special/nonlingradient"; + textureName[ST_RIP1] = "special/laserrip01"; + textureName[ST_RIP2] = "special/laserrip02"; + textureName[ST_RIP3] = "special/laserrip03"; + textureName[ST_RIP4] = "special/laserrip04"; + textureName[ST_RIP5] = "special/laserrip05"; + textureName[ST_RIP6] = "special/laserrip06"; + textureName[ST_RIP7] = "special/laserrip07"; + textureName[ST_RIP8] = "special/laserrip08"; + textureName[ST_RIP9] = "special/laserrip09"; + + textureName[ST_BEAM2] = "special/sniper00"; + + startBeamWidth = 0.25; + endBeamWidth = 0.5; + pulseBeamWidth = 0.5; + beamFlareAngle = 3.0; + minFlareSize = 0.0; + maxFlareSize = 400.0; + pulseSpeed = 6.0; + pulseLength = 0.150; + lightRadius = 1.0; + lightColor.set( 0.4, 0.0, 0.0 ); +} + +//-------------------------------------------------------------------------- +SniperProjectileData::~SniperProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void SniperProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("maxRifleRange", TypeF32, Offset(maxRifleRange, SniperProjectileData)); + addField("rifleHeadMultiplier", TypeF32, Offset(rifleHeadMultiplier, SniperProjectileData)); + addField("beamColor", TypeColorF, Offset(beamColor, SniperProjectileData)); + addField("fadeTime", TypeF32, Offset(fadeTime, SniperProjectileData)); + addField("textureName", TypeString, Offset(textureName, SniperProjectileData), ST_NUM_TEX); + addField("startBeamWidth", TypeF32, Offset(startBeamWidth, SniperProjectileData)); + addField("endBeamWidth", TypeF32, Offset(endBeamWidth, SniperProjectileData)); + addField("pulseBeamWidth", TypeF32, Offset(pulseBeamWidth, SniperProjectileData)); + addField("beamFlareAngle", TypeF32, Offset(beamFlareAngle, SniperProjectileData)); + addField("minFlareSize", TypeF32, Offset(minFlareSize, SniperProjectileData)); + addField("maxFlareSize", TypeF32, Offset(maxFlareSize, SniperProjectileData)); + addField("pulseSpeed", TypeF32, Offset(pulseSpeed, SniperProjectileData)); + addField("pulseLength", TypeF32, Offset(pulseLength, SniperProjectileData)); + addField("lightColor", TypeColorF, Offset(lightColor, SniperProjectileData)); + addField("lightRadius", TypeF32, Offset(lightRadius, SniperProjectileData)); + +} + + +//-------------------------------------------------------------------------- +bool SniperProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (maxRifleRange < 10.0 || maxRifleRange > 2000.0f) { + Con::warnf(ConsoleLogEntry::General, "SniperProjectileData(%s)::onAdd: sniper rifle maxRange must be have range [10, 2000]", getName()); + maxRifleRange = maxRifleRange < 10.0 ? 10.0 : 2000; + } + if (rifleHeadMultiplier < 1.0) { + Con::warnf(ConsoleLogEntry::General, "SniperProjectileData(%s)::onAdd: sniper rifle head multiplier must be >= 1", getName()); + rifleHeadMultiplier = 1.0; + } + if (fadeTime < 0.25) { + Con::warnf(ConsoleLogEntry::General, "SniperProjectileData(%s)::onAdd: sniper rifle fade time must be >= 0.25", getName()); + fadeTime = 0.25; + } + beamColor.clamp(); + + return true; +} + +//-------------------------------------------------------------------------- +bool SniperProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (server == false) + { + for( int i=0; i maxRifleRange) { + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + + return false; + } + + *outputVectorMin = targetPos - sourcePos; + *outputMinTime = 0.0; + outputVectorMin->normalizeSafe(); + *outputVectorMax = *outputVectorMin; + *outputMaxTime = *outputMinTime; + + return true; +} + + +//-------------------------------------------------------------------------- +void SniperProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(maxRifleRange); + stream->write(rifleHeadMultiplier); + stream->write(beamColor); + stream->write(fadeTime); + stream->write(startBeamWidth); + stream->write(endBeamWidth); + stream->write(pulseBeamWidth); + stream->write(beamFlareAngle); + stream->write(minFlareSize); + stream->write(maxFlareSize); + stream->write(pulseSpeed); + stream->write(pulseLength); + stream->write(lightColor); + stream->write(lightRadius); + + for( int i=0; iwriteString(textureName[i]); + } +} + +//-------------------------------------------------------------------------- +void SniperProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&maxRifleRange); + stream->read(&rifleHeadMultiplier); + stream->read(&beamColor); + stream->read(&fadeTime); + stream->read(&startBeamWidth); + stream->read(&endBeamWidth); + stream->read(&pulseBeamWidth); + stream->read(&beamFlareAngle); + stream->read(&minFlareSize); + stream->read(&maxFlareSize); + stream->read(&pulseSpeed); + stream->read(&pulseLength); + stream->read(&lightColor); + stream->read(&lightRadius); + + for( int i=0; ireadSTString(); + } + +} + + +//************************************************************************** +// Sniper Projectile +//************************************************************************** +SniperProjectile::SniperProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable|ScopeAlways); + + mClientTotalWarpTicks = 0; + mClientWarpTicks = 0; + mHitWater = false; + mEnergyPercentage = 0; +} + +//-------------------------------------------------------------------------- +SniperProjectile::~SniperProjectile() +{ + // +} + +//-------------------------------------------------------------------------- + +void SniperProjectile::setEnergyPercentage(F32 val) +{ + mEnergyPercentage = val; +} + +ConsoleMethod(SniperProjectile,setEnergyPercentage,void,3,3,"proj.setEnergyPercentage(pct)") +{ + argc; + ((SniperProjectile *)object)->setEnergyPercentage(dAtof(argv[2])); +} + +//-------------------------------------------------------------------------- +bool SniperProjectile::calculateImpact(float /*simTime*/, + Point3F& pointOfImpact, + float& impactTime) +{ + impactTime = 0; + + if (mTruncated) { + pointOfImpact = mBeam.mData.endPos; + return true; + } else { + pointOfImpact.set(0, 0, 0); + return false; + } +} + + +//-------------------------------------------------------------------------- +bool SniperProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isServerObject()) { + MatrixF xform(true); + xform.setColumn(3, mInitialPosition); + + mBeam.mData.endPos = mInitialPosition + mInitialDirection * mDataBlock->maxRifleRange; + RayInfo rayInfo; + + if( mSourceObject ) + { + mSourceObject->disableCollision(); + } + + bool rayHit = gServerContainer.castRay(mInitialPosition, mBeam.mData.endPos, + (csmStaticCollisionMask | csmDynamicCollisionMask | WaterObjectType), + &rayInfo); + if( mSourceObject ) + { + mSourceObject->enableCollision(); + } + + if( rayHit ) + { + // Damage the object + if (rayInfo.object->getType() & csmDamageableMask) + onCollision(rayInfo.point, rayInfo.normal, rayInfo.object); + + // shoot ray again to see if it hit water + RayInfo waterRayInfo; + + if( mSourceObject ) + { + mSourceObject->disableCollision(); + } + mHitWater = gClientContainer.castRay(mInitialPosition, mBeam.mData.endPos, WaterObjectType, &waterRayInfo); + + if( mSourceObject ) + { + mSourceObject->enableCollision(); + } + + + mBeam.mData.endPos = rayInfo.point; + mTruncated = true; + } else { + mTruncated = false; + } + + if( mSourceObject ) + { + mSourceObject->enableCollision(); + } + + mDeleteWaitTicks = (S32)((mDataBlock->fadeTime * (1000.0 / TickMs)) * 2); + } + else { + mClientTime = mDataBlock->fadeTime * (1.0 - mEnergyPercentage); + + if (mTruncated) { + + if (mDataBlock->explosion && !mHitWater) { + Point3F n = mBeam.mData.endPos - mInitialPosition; + n.normalizeSafe(); + + Explosion* pExplosion = new Explosion; + pExplosion->onNewDataBlock(mDataBlock->explosion); + + MatrixF xform(true); + xform.setColumn(3, mBeam.mData.endPos); + pExplosion->setTransform(xform); + pExplosion->setInitialState(mBeam.mData.endPos, n, mFadeValue); + if (pExplosion->registerObject() == false) { + Con::errorf(ConsoleLogEntry::General, "SniperProjectile(%s)::explode: couldn't register explosion(%s)", + mDataBlock->getName(), mDataBlock->explosion->getName()); + delete pExplosion; + pExplosion = NULL; + } + } + + if( mHitWater && mDataBlock->splash ) + { + MatrixF trans = getTransform(); + trans.setPosition( mBeam.mData.endPos ); + 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; + } + + Sim::getLightSet()->addObject(this); + } + } + + mObjBox.min.set(-1e7, -1e7, -1e7); + mObjBox.max.set( 1e7, 1e7, 1e7); + resetWorldBox(); + addToScene(); + + + return true; +} + +F32 SniperProjectile::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips) +{ + if(updateMask & InitialUpdateMask) + return 9.9; + return Parent::getUpdatePriority(camInfo, updateMask, updateSkips); +} + +//-------------------------------------------------------------------------- +void SniperProjectile::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +bool SniperProjectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +bool SniperProjectile::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + if (mClientTime > mDataBlock->fadeTime) + return false; + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::EndSort; + state->insertRenderImage(image); + + return false; +} + + +//-------------------------------------------------------------------------- +void SniperProjectile::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + state->setupBaseProjection(); + + F32 percentDone = mClientTime / mDataBlock->fadeTime; + F32 invPercentDone = 1.0 - percentDone; + + + updateBeamData( mBeam.mData, state->getCameraPosition() ); + + // tweak end position so it doesn't intersect camera + mBeam.adjustEdge( mBeam.mData ); + + // render the flare + F32 flareScale = 0.0; + bool result = checkForFlare( flareScale, state->getCameraPosition() ); + + if( result ) + { + ColorF flareColor = mDataBlock->beamColor; + flareColor.alpha = invPercentDone; + renderFlare( flareColor, flareScale ); + } + + renderBeam( mBeam, percentDone ); + + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +// Returns true if need to render flare. +//-------------------------------------------------------------------------- +bool SniperProjectile::checkForFlare( F32 &flareScale, const Point3F &camPos ) +{ + if( ( mBeam.mData.angToCamPos < -0.5 ) && ( mBeam.mData.angToCamDir < -0.5 ) ) + { + + if( mSourceObject ) + { + mSourceObject->disableCollision(); + } + + // cast ray to make sure beam source is visible + RayInfo rayInfo; + bool rayHit = gClientContainer.castRay( camPos, mInitialPosition, + (csmStaticCollisionMask | csmDynamicCollisionMask | WaterObjectType), + &rayInfo); + + if( mSourceObject ) + { + mSourceObject->enableCollision(); + } + + if( !rayHit ) + { + + flareScale = 0.0; + F32 flareAngle = -mCos( mDegToRad( mDataBlock->beamFlareAngle ) ); + + if( mBeam.mData.angToCamPos >= flareAngle ) + { + flareScale = mDataBlock->minFlareSize / mDataBlock->maxFlareSize; + } + else + { + + F32 angScale = 1.0 / (-1.0 - flareAngle); + F32 angDiff = mBeam.mData.angToCamPos - flareAngle; + F32 camPosToBeamColScale = 1.0 - (angDiff * angScale); // 0.0 - 1.0, 1.0 if beam is directly at camera, 0.0 if at beamFlareAngle + + flareScale = 1.0 - camPosToBeamColScale; + + } + return true; + } + + return false; + } + + return false; +} + +//-------------------------------------------------------------------------- +void SniperProjectile::updateBeamData( BeamData &beamData, const Point3F &camPos ) +{ + beamData.startPos = mInitialPosition; + beamData.endRenderPos = beamData.endPos; + beamData.dirFromCam = beamData.startPos - camPos; + beamData.dirFromCam.normalizeSafe(); + + MatrixF mv; + dglGetModelview(&mv); + Point3F camLookDir; + mv.getRow(1, &camLookDir); + + + mCross( beamData.dirFromCam, beamData.direction, &beamData.axis); + + if( beamData.axis.isZero() ) + { + Point3D dirFromCam( beamData.dirFromCam.x, beamData.dirFromCam.y, beamData.dirFromCam.z ); + Point3D direction( beamData.direction.x, beamData.direction.y, beamData.direction.z ); + + Point3D newAxis; + mCross( dirFromCam, direction, &newAxis); + + if( newAxis.isZero() ) + { + beamData.axis.set( 0.0, 0.0, 1.0 ); + } + else + { + newAxis.normalize(); + + beamData.axis.x = newAxis.x; + beamData.axis.y = newAxis.y; + beamData.axis.z = newAxis.z; + } + } + else + { + beamData.axis.normalize(); + } + + beamData.angToCamPos = mDot( beamData.dirFromCam, beamData.direction ); + beamData.angToCamDir = mDot( camLookDir, beamData.direction ); + beamData.length = (beamData.endPos - beamData.startPos).magnitudeSafe(); +} + +//-------------------------------------------------------------------------- +void SniperProjectile::renderBeam( WeaponBeam &beam, F32 percentDone ) +{ + + if( !beam.mData.onEdge ) + { + + // set render state + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_FALSE); + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + + // render main beam + F32 width = mDataBlock->startBeamWidth; + width += (mDataBlock->endBeamWidth - width) * percentDone; + + + glColor4f( 1.0, 1.0, 1.0, 1.0 - percentDone ); + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[SniperProjectileData::ST_BEAM2].getGLName()); + + beam.render( width ); + + + + // render pulse + + ColorF beamColor = mDataBlock->beamColor; + beamColor.alpha = 1.0 - percentDone; + glColor4fv( beamColor ); + + S32 texNum = (S32)(F32)( ((F32)(SniperProjectileData::ST_BEAM)) + 10.0 * percentDone); + F32 pulseOffset = mClientTime * -mDataBlock->pulseSpeed * 0.5; + F32 numWrap = beam.mData.length * mDataBlock->pulseLength; + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[texNum].getGLName()); + beam.render( width * 1.25, pulseOffset, numWrap ); + + + + // restore state + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + + } + +} + +//-------------------------------------------------------------------------- +void SniperProjectile::renderFlare( ColorF flareColor, F32 flareScale ) +{ + + F32 flareSize = mDataBlock->maxFlareSize; + flareSize *= flareScale; + if( flareSize == 0.0 ) return; + + + Point3F screenPoint; + dglPointToScreen( mInitialPosition, screenPoint ); + + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + RectI viewport; + dglGetViewport(&viewport); + dglSetClipRect( viewport ); + + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_TRUE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glColor4fv(flareColor); + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[SniperProjectileData::ST_FLARE].getGLName()); + + F32 ang = mClientTime; + + dglDraw2DSquare( Point2F( screenPoint.x, screenPoint.y ), flareSize, ang ); + dglDraw2DSquare( Point2F( screenPoint.x, screenPoint.y ), flareSize * .75, -ang ); + + + glEnable(GL_DEPTH_TEST); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + +} + +//-------------------------------------------------------------------------- +void SniperProjectile::processTick(const Move* move) +{ + Parent::processTick(move); + + if (isClientObject() && mClientWarpTicks != 0) + { + mClientWarpTicks--; + } + else + { + if (isServerObject()) + { + if (--mDeleteWaitTicks <= 0) + { + deleteObject(); + } + } + } +} + + +//-------------------------------------------------------------------------- +void SniperProjectile::interpolateTick(F32 delta) +{ + Parent::interpolateTick(delta); + + if (mClientWarpTicks != 0 && mClientOwned == false) + { + // Warp the end point + F32 factor = 1.0 - (F32(mClientWarpTicks) / F32(mClientTotalWarpTicks)); + + #if COUPLE_BEAM + mBeam.mData.endPos.interpolate(mClientWarpFrom, mClientWarpTo, factor); + #endif + + } + else + { + if (mClientOwned && bool(mSourceObject)) + { + #if COUPLE_BEAM + // We're the shooter recast the end point... + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mInitialPosition); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &mInitialDirection); + mBeam.mData.endPos = mInitialPosition + mInitialDirection * mDataBlock->maxRifleRange; + + #endif + + mSourceObject->disableCollision(); + + RayInfo rayInfo; + bool rayHit = gClientContainer.castRay(mInitialPosition, mBeam.mData.endPos, + (csmStaticCollisionMask | csmDynamicCollisionMask | WaterObjectType), + &rayInfo); + + mSourceObject->enableCollision(); + + if( rayHit ) + { + #if COUPLE_BEAM + mBeam.mData.endPos = rayInfo.point; + #endif + + mTruncated = true; + } + else + { + mTruncated = false; + } + } + else + { + + #if COUPLE_BEAM + // Just set the last position... + mBeam.mData.endPos = mClientWarpTo; + #endif + } + } + + mBeam.mData.direction = mBeam.mData.endPos - mInitialPosition; + mBeam.mData.direction.normalizeSafe(); +} + + +//-------------------------------------------------------------------------- +void SniperProjectile::advanceTime(F32 dt) +{ + mClientTime += dt; +} + + +//-------------------------------------------------------------------------- +void SniperProjectile::setClientWarp(const Point3F& newEndPoint) +{ + mClientWarpFrom = mBeam.mData.endPos; + mClientWarpTo = newEndPoint; + mClientTotalWarpTicks = 10; + mClientWarpTicks = 10; +} + + +//-------------------------------------------------------------------------- +U32 SniperProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag((mask & GameBase::InitialUpdateMask) != 0)) { + + stream->writeFloat( mEnergyPercentage, 7 ); + + mathWrite(*stream, mInitialPosition); + mathWrite(*stream, mBeam.mData.endPos); + stream->writeFlag(mTruncated); + stream->writeFlag(mHitWater); + + if (bool(mSourceObject)) { + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + bool isFiredOnClient = mSourceObject->getControllingClient() == con; + + 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); + stream->writeFlag(isFiredOnClient); + } + } else { + stream->writeFlag(false); + } + } else { + // Swinging around... + if (bool(mSourceObject)) { + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + bool isFiredOnClient = mSourceObject->getControllingClient() == con; + S32 ghostIndex = con->getGhostIndex(mSourceObject); + if (ghostIndex != -1) { + stream->writeFlag(true); + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount); + stream->writeRangedU32(U32(mSourceObjectSlot), + 0, ShapeBase::MaxMountedImages - 1); + stream->writeFlag(isFiredOnClient); + } else { + stream->writeFlag(false); + mathWrite(*stream, mInitialPosition); + } + } else { + stream->writeFlag(false); + mathWrite(*stream, mInitialPosition); + } + + mathWrite(*stream, mBeam.mData.endPos); + stream->writeFlag(mTruncated); + } + + return retMask; +} + +//-------------------------------------------------------------------------- +void SniperProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + + if (stream->readFlag()) { + + mEnergyPercentage = stream->readFloat( 7 ); + + mathRead(*stream, &mInitialPosition); + mathRead(*stream, &mBeam.mData.endPos); + + mClientWarpFrom = mBeam.mData.endPos; + mClientWarpTo = mBeam.mData.endPos; + mClientTotalWarpTicks = 0; + mClientWarpTicks = 0; + + mTruncated = stream->readFlag(); + mHitWater = stream->readFlag(); + + mClientOwned = false; + if (stream->readFlag()) { + mSourceObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + mSourceObjectSlot = stream->readRangedU32(0, ShapeBase::MaxMountedImages - 1); + mClientOwned = stream->readFlag(); + + NetObject* pObject = con->resolveGhost(mSourceObjectId); + if (pObject != NULL) + mSourceObject = dynamic_cast(pObject); + } else { + mSourceObjectId = -1; + mSourceObjectSlot = -1; + mSourceObject = NULL; + } + } else { + // Swinging around... + mClientOwned = false; + if (stream->readFlag()) { + mSourceObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + mSourceObjectSlot = stream->readRangedU32(0, ShapeBase::MaxMountedImages - 1); + mClientOwned = stream->readFlag(); + + NetObject* pObject = con->resolveGhost(mSourceObjectId); + if (pObject != NULL) + mSourceObject = dynamic_cast(pObject); + } else { + mSourceObjectId = -1; + mSourceObjectSlot = -1; + mSourceObject = NULL; + + mathRead(*stream, &mInitialPosition); + } + + Point3F newEndPoint; + mathRead(*stream, &newEndPoint); + mTruncated = stream->readFlag(); + + if (mClientOwned == false) + setClientWarp(newEndPoint); + } + + mBeam.mData.direction = mBeam.mData.endPos - mInitialPosition; + mBeam.mData.direction.normalizeSafe(); + +} + +//-------------------------------------------------------------------------- +// For light at end of sniper beam +//-------------------------------------------------------------------------- +void SniperProjectile::registerLights(LightManager * lightManager, bool lightingScene) +{ + if(lightingScene) + return; + + if (mHidden == false) { + + F32 percentDone = mClientTime / mDataBlock->fadeTime; + + mLight.mColor = mDataBlock->lightColor; + mLight.mColor *= 1.0 - percentDone; + mLight.mType = LightInfo::Point; + mLight.mPos = mBeam.mData.endPos; + mLight.mRadius = mDataBlock->lightRadius; + lightManager->addLight(&mLight); + } +} diff --git a/game/projSniper.h b/game/projSniper.h new file mode 100644 index 0000000..190a9f1 --- /dev/null +++ b/game/projSniper.h @@ -0,0 +1,167 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_SNIPERPROJECTILE +#define _H_SNIPERPROJECTILE + +#ifndef _PROJECTILE_H_ +#include "game/projectile.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif +#ifndef _WEAPONBEAM_H_ +#include "game/weaponBeam.h" +#endif + +class SplashData; + + +// ------------------------------------------------------------------------- +class SniperProjectileData : public ProjectileData +{ + typedef ProjectileData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + + enum SnipeTextures + { + ST_FLARE = 0, + ST_BEAM, + ST_RIP1, + ST_RIP2, + ST_RIP3, + ST_RIP4, + ST_RIP5, + ST_RIP6, + ST_RIP7, + ST_RIP8, + ST_RIP9, + ST_BEAM2, + ST_NUM_TEX + }; + + F32 maxRifleRange; + F32 rifleHeadMultiplier; + ColorF beamColor; + F32 fadeTime; + F32 startBeamWidth; + F32 endBeamWidth; + F32 beamFlareAngle; + F32 minFlareSize; + F32 maxFlareSize; + F32 pulseBeamWidth; + F32 pulseSpeed; + F32 pulseLength; + F32 lightRadius; + ColorF lightColor; + + StringTableEntry textureName[ST_NUM_TEX]; + + //-------------------------------------- load set variables + public: + TextureHandle textureHandle[ST_NUM_TEX]; + + + public: + SniperProjectileData(); + ~SniperProjectileData(); + + + + bool calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& sourceVel, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(SniperProjectileData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class SniperProjectile : public Projectile +{ + typedef Projectile Parent; + + private: + SniperProjectileData* mDataBlock; + + protected: + + WeaponBeam mBeam; + + S32 mDeleteWaitTicks; + F32 mEnergyPercentage; + + bool mHitWater; + bool mTruncated; + bool mClientOwned; // True if this ghost is on the client + // that fired the targeting laser + + // Client warping variables + U32 mClientTotalWarpTicks; + U32 mClientWarpTicks; + Point3F mClientWarpFrom; + Point3F mClientWarpTo; + void setClientWarp(const Point3F&); + + F32 mClientTime; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + void registerLights(LightManager * lightManager, bool lightingScene); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderBeam( WeaponBeam &beam, F32 percentDone ); + void renderObject(SceneState*, SceneRenderImage*); + void renderFlare( ColorF, F32 flareScale ); + bool calculateImpact(F32 simTime, + Point3F& pointOfImpact, + F32& impactTime); + + void updateBeamData( BeamData &beamData, const Point3F &camPos ); + bool checkForFlare( F32 &flareScale, const Point3F &camPos ); + F32 getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips); + + public: + + + SniperProjectile(); + ~SniperProjectile(); + + // Time/Move Management + public: + void setEnergyPercentage(F32); + void processTick(const Move*); + void interpolateTick(F32); + virtual void advanceTime(F32); + + DECLARE_CONOBJECT(SniperProjectile); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_SNIPERPROJECTILE + diff --git a/game/projTargeting.cc b/game/projTargeting.cc new file mode 100644 index 0000000..0d0e9d5 --- /dev/null +++ b/game/projTargeting.cc @@ -0,0 +1,914 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projTargeting.h" +#include "core/bitStream.h" +#include "console/consoleTypes.h" +#include "platformWIN32/platformGL.h" +#include "dgl/dgl.h" +#include "scenegraph/sceneState.h" +#include "math/mathIO.h" +#include "game/shapeBase.h" +#include "game/gameConnection.h" + +IMPLEMENT_CO_DATABLOCK_V1(TargetProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(TargetProjectile); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +TargetProjectileData::TargetProjectileData() +{ + maxRifleRange = 1000; + beamColor.set( 0.1, 1.0, 0.1 ); + coupleBeam = true; + + startBeamWidth = 1.0; + beamFlareAngle = 3.0; + minFlareSize = 0.0; + maxFlareSize = 400.0; + pulseBeamWidth = 0.5; + pulseSpeed = 6.0; + pulseLength = 0.150; + + dMemset( textureName, 0, sizeof( textureName ) ); +} + +TargetProjectileData::~TargetProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void TargetProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("maxRifleRange", TypeF32, Offset(maxRifleRange, TargetProjectileData)); + addField("beamColor", TypeColorF, Offset(beamColor, TargetProjectileData)); + addField("textureName", TypeString, Offset(textureName, TargetProjectileData), TT_NUM_TEX); + addField("startBeamWidth", TypeF32, Offset(startBeamWidth, TargetProjectileData)); + addField("pulseBeamWidth", TypeF32, Offset(pulseBeamWidth, TargetProjectileData)); + addField("beamFlareAngle", TypeF32, Offset(beamFlareAngle, TargetProjectileData)); + addField("minFlareSize", TypeF32, Offset(minFlareSize, TargetProjectileData)); + addField("maxFlareSize", TypeF32, Offset(maxFlareSize, TargetProjectileData)); + addField("pulseSpeed", TypeF32, Offset(pulseSpeed, TargetProjectileData)); + addField("pulseLength", TypeF32, Offset(pulseLength, TargetProjectileData)); + addField("coupleBeam", TypeBool, Offset(coupleBeam, TargetProjectileData)); +} + + +//-------------------------------------------------------------------------- +bool TargetProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (maxRifleRange < 10.0 || maxRifleRange > 2000.0f) { + Con::warnf(ConsoleLogEntry::General, "TargetProjectileData(%s)::onAdd: sniper rifle maxRange must be have range [10, 2000]", getName()); + maxRifleRange = maxRifleRange < 10.0 ? 10.0 : 2000; + } + beamColor.clamp(); + + return true; +} + + +//-------------------------------------------------------------------------- +bool TargetProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (server == false) + { + for( int i=0; i maxRifleRange) { + outputVectorMin->set(0, 0, 1); + outputVectorMax->set(0, 0, 1); + *outputMinTime = -1; + *outputMaxTime = -1; + + return false; + } + + *outputVectorMin = targetPos - sourcePos; + *outputMinTime = 0.0; + outputVectorMin->normalizeSafe(); + *outputVectorMax = *outputVectorMin; + *outputMaxTime = *outputMinTime; + + return true; +} + + +//-------------------------------------------------------------------------- +void TargetProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(maxRifleRange); + stream->write(beamColor); + stream->write(startBeamWidth); + stream->write(pulseBeamWidth); + stream->write(beamFlareAngle); + stream->write(minFlareSize); + stream->write(maxFlareSize); + stream->write(pulseSpeed); + stream->write(pulseLength); + + for( int i=0; iwriteString(textureName[i]); + } + +} + +//-------------------------------------------------------------------------- +void TargetProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&maxRifleRange); + stream->read(&beamColor); + stream->read(&startBeamWidth); + stream->read(&pulseBeamWidth); + stream->read(&beamFlareAngle); + stream->read(&minFlareSize); + stream->read(&maxFlareSize); + stream->read(&pulseSpeed); + stream->read(&pulseLength); + + for( int i=0; ireadSTString(); + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +TargetProjectile::TargetProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable|ScopeAlways); + + mClientTotalWarpTicks = 0; + mClientWarpTicks = 0; + mElapsedTime = 0.0; +} + +TargetProjectile::~TargetProjectile() +{ + // +} + +//-------------------------------------------------------------------------- +void TargetProjectile::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +static const char* cGetTargetPoint(SimObject *obj, S32, const char **) +{ + TargetProjectile *targ = static_cast(obj); + + //find the target + Point3F targPoint; + U32 team; + + char* returnBuffer = Con::getReturnBuffer(256); + if (targ->getTarget(&targPoint, &team)) + dSprintf(returnBuffer, 256, "%f %f %f %d", targPoint.x, targPoint.y, targPoint.z, team); + else + dSprintf(returnBuffer, 256, "0 0 0 -1"); + return returnBuffer; +} + +void TargetProjectile::consoleInit() +{ + Con::addCommand("TargetProjectile", "getTargetPoint", cGetTargetPoint, "targ.getTargetPoint()", 2, 2); +} + + +//-------------------------------------------------------------------------- +bool TargetProjectile::calculateImpact(float /*simTime*/, + Point3F& pointOfImpact, + float& impactTime) +{ + impactTime = 0; + + if (mTruncated) { + pointOfImpact = mBeam.mData.endPos; + return true; + } else { + pointOfImpact.set(0, 0, 0); + return false; + } +} + + +//-------------------------------------------------------------------------- +bool TargetProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isServerObject()) { + if (bool(mSourceObject)) + mSourceObject->disableCollision(); + + mBeam.mData.endPos = mInitialPosition + mInitialDirection * mDataBlock->maxRifleRange; + RayInfo rayInfo; + if (gServerContainer.castRay(mInitialPosition, mBeam.mData.endPos, + (csmStaticCollisionMask | csmDynamicCollisionMask | WaterObjectType), + &rayInfo) == true) { + mBeam.mData.endPos = rayInfo.point; + mTruncated = true; + } else { + mTruncated = false; + } + + if (bool(mSourceObject)) + mSourceObject->enableCollision(); + + addToSet("ServerTargetSet"); + } else { + addToSet("ClientTargetSet"); + } + + mObjBox.min.set(-1e7, -1e7, -1e7); + mObjBox.max.set( 1e7, 1e7, 1e7); + + MatrixF temp(true); + setTransform(temp); + setRenderTransform(temp); + + addToScene(); + + return true; +} + + +void TargetProjectile::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + + +bool TargetProjectile::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; +} + +//-------------------------------------------------------------------------- +bool TargetProjectile::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::EndSort; + state->insertRenderImage(image); + + return false; +} + + +//-------------------------------------------------------------------------- +void TargetProjectile::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + state->setupBaseProjection(); + updateBeamData( mBeam.mData, state->getCameraPosition() ); + + // tweak end position so it doesn't intersect camera + mBeam.adjustEdge( mBeam.mData ); + + // render the flare + F32 flareScale = 0.0; + bool result = checkForFlare( flareScale, state->getCameraPosition() ); + + + if( result ) + { + ColorF flareColor = mDataBlock->beamColor; + flareColor.alpha = 1.0; + renderFlare( flareColor, flareScale ); + } + + if( canRenderEndFlare( state->getCameraPosition() ) ) + { + renderEndFlare( 0.3 ); + } + + + renderBeam( mBeam, 0.0 ); + + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +// Returns true if need to render flare. +//-------------------------------------------------------------------------- +bool TargetProjectile::checkForFlare( F32 &flareScale, const Point3F &camPos ) +{ + + if( ( mBeam.mData.angToCamPos < -0.5 ) && ( mBeam.mData.angToCamDir < -0.5 ) ) + { + + if( mSourceObject ) + { + mSourceObject->disableCollision(); + } + + // cast ray to make sure beam source is visible + RayInfo rayInfo; + bool rayHit = gClientContainer.castRay( camPos, mInitialPosition, + (csmStaticCollisionMask | csmDynamicCollisionMask | WaterObjectType), + &rayInfo); + + if( mSourceObject ) + { + mSourceObject->enableCollision(); + } + + if( !rayHit ) + { + + flareScale = 0.0; + F32 flareAngle = -mCos( mDegToRad( mDataBlock->beamFlareAngle ) ); + + if( mBeam.mData.angToCamPos >= flareAngle ) + { + flareScale = mDataBlock->minFlareSize / mDataBlock->maxFlareSize; + } + else + { + + F32 angScale = 1.0 / (-1.0 - flareAngle); + F32 angDiff = mBeam.mData.angToCamPos - flareAngle; + F32 camPosToBeamColScale = 1.0 - (angDiff * angScale); // 0.0 - 1.0, 1.0 if beam is directly at camera, 0.0 if at beamFlareAngle + + flareScale = 1.0 - camPosToBeamColScale; + + } + return true; + } + + return false; + } + + return false; +} + + +//-------------------------------------------------------------------------- +// Checks to see if the end flare is visible - returns true if it is +//-------------------------------------------------------------------------- +bool TargetProjectile::canRenderEndFlare( const Point3F &camPos ) +{ + // check if in first or third person mode + bool firstPerson = true; + + GameConnection * gc = mSourceObject ? mSourceObject->getControllingClient() : NULL; + if( gc ) + { + firstPerson = gc->isFirstPerson(); + } + + bool rayHit = true; + + Point3F endPos = mBeam.mData.endPos + mBeam.mData.direction * 0.2; + + VectorF dirToFlare = endPos - camPos; + dirToFlare.normalizeSafe(); + + Point3F tweakedCamPos = camPos + dirToFlare * 0.5; + + + if( firstPerson && mSourceObject ) + { + mSourceObject->disableCollision(); + } + + RayInfo rayInfo; + rayHit = getContainer()->castRay( mBeam.mData.startPos, endPos, -1, &rayInfo ); + + if( rayHit ) + { + SceneObject *obj = rayInfo.object; + + obj->disableCollision(); + + rayHit = getContainer()->castRay( tweakedCamPos, endPos, -1, &rayInfo ); + + obj->enableCollision(); + } + + + if( firstPerson && mSourceObject ) + { + mSourceObject->enableCollision(); + } + + + return !rayHit; +} + + +//-------------------------------------------------------------------------- +void TargetProjectile::updateBeamData( BeamData &beamData, const Point3F &camPos ) +{ + beamData.startPos = mInitialPosition; + beamData.endRenderPos = beamData.endPos; + beamData.dirFromCam = beamData.startPos - camPos; + beamData.dirFromCam.normalizeSafe(); + + MatrixF mv; + dglGetModelview(&mv); + Point3F camLookDir; + mv.getRow(1, &camLookDir); + + + mCross( beamData.dirFromCam, beamData.direction, &beamData.axis); + + if( beamData.axis.isZero() ) + { + Point3D dirFromCam( beamData.dirFromCam.x, beamData.dirFromCam.y, beamData.dirFromCam.z ); + Point3D direction( beamData.direction.x, beamData.direction.y, beamData.direction.z ); + + Point3D newAxis; + mCross( dirFromCam, direction, &newAxis); + + if( newAxis.isZero() ) + { + beamData.axis.set( 0.0, 0.0, 1.0 ); + } + else + { + newAxis.normalize(); + + beamData.axis.x = newAxis.x; + beamData.axis.y = newAxis.y; + beamData.axis.z = newAxis.z; + } + } + else + { + beamData.axis.normalize(); + } + + beamData.angToCamPos = mDot( beamData.dirFromCam, beamData.direction ); + beamData.angToCamDir = mDot( camLookDir, beamData.direction ); + beamData.length = (beamData.endPos - beamData.startPos).magnitudeSafe(); +} + +//-------------------------------------------------------------------------- +void TargetProjectile::renderBeam( WeaponBeam &beam, F32 percentDone ) +{ + + if( !beam.mData.onEdge ) + { + + // set render state + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_FALSE); + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // set color + ColorF beamColor = mDataBlock->beamColor; + beamColor.alpha = 1.0 - percentDone; + glColor4fv( beamColor ); + + // render pulse beam + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[TargetProjectileData::TT_PULSE].getGLName()); + + F32 width = mDataBlock->pulseBeamWidth; + F32 pulseOffset = mElapsedTime * -mDataBlock->pulseSpeed; + + F32 beamLength = (beam.mData.endRenderPos - mInitialPosition).magnitudeSafe(); + F32 numWrap = beamLength * mDataBlock->pulseLength; + + beam.render( width, pulseOffset, numWrap ); + + + // render main beam + width = mDataBlock->startBeamWidth; + width += (1.0 - width) * percentDone; + + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[TargetProjectileData::TT_BEAM].getGLName()); + + beam.render( width ); + + + // restore state + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + + } + +} + +//-------------------------------------------------------------------------- +void TargetProjectile::renderFlare( ColorF flareColor, F32 flareScale ) +{ + + F32 flareSize = mDataBlock->maxFlareSize; + flareSize *= flareScale; + if( flareSize == 0.0 ) return; + + Point3F screenPoint; + dglPointToScreen( mInitialPosition, screenPoint ); + + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + RectI viewport; + dglGetViewport(&viewport); + dglSetClipRect( viewport ); + + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_TRUE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glColor4fv(flareColor); + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[TargetProjectileData::TT_FLARE].getGLName()); + + F32 ang = mElapsedTime; + + dglDraw2DSquare( Point2F( screenPoint.x, screenPoint.y ), flareSize, ang ); + dglDraw2DSquare( Point2F( screenPoint.x, screenPoint.y ), flareSize * .75, -ang ); + + + glEnable(GL_DEPTH_TEST); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + +} + +//-------------------------------------------------------------------------- +// Render flare +//-------------------------------------------------------------------------- +void TargetProjectile::renderEndFlare( F32 flareSize ) +{ + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + + Point3F pos = mBeam.mData.endPos; + + + glDisable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[TargetProjectileData::TT_END_FLARE].getGLName() ); + + glColor4f( 0.0, 1.0, 0.0, 1.0 ); + + dglDrawBillboard( pos, flareSize * 2.0, 0.0 ); + + glEnable(GL_DEPTH_TEST); +} + +//-------------------------------------------------------------------------- +void TargetProjectile::processTick(const Move* move) +{ + Parent::processTick(move); + + if (isClientObject() && mClientWarpTicks != 0) + { + mClientWarpTicks--; + if( mSourceObject ) + { + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mInitialPosition); + } + } + else + { + if (isServerObject()) + { + if (bool(mSourceObject)) + { + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mInitialPosition); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &mInitialDirection); + mSourceObject->disableCollision(); + + mBeam.mData.endPos = mInitialPosition + mInitialDirection * mDataBlock->maxRifleRange; + RayInfo rayInfo; + if (gServerContainer.castRay(mInitialPosition, mBeam.mData.endPos, + (csmStaticCollisionMask | csmDynamicCollisionMask | WaterObjectType), + &rayInfo) == true) { + mBeam.mData.endPos = rayInfo.point; + mTruncated = true; + } else { + mTruncated = false; + } + mSourceObject->enableCollision(); + + if ((mCurrTick % 8) == 0) + setMaskBits(ResetEndPointMask); + + MatrixF temp(true); + temp.setPosition( mBeam.mData.endPos ); + setTransform(temp); + } + } + } +} + + +//-------------------------------------------------------------------------- +void TargetProjectile::interpolateTick(F32 delta) +{ + Parent::interpolateTick(delta); + + if (mClientWarpTicks != 0 && mClientOwned == false) + { + // Warp the end point + F32 factor = 1.0 - (F32(mClientWarpTicks) / F32(mClientTotalWarpTicks)); + + if( mDataBlock->coupleBeam ) + { + mBeam.mData.endPos.interpolate(mClientWarpFrom, mClientWarpTo, factor); + } + + } + else + { + if (mClientOwned && bool(mSourceObject)) + { + + mSourceObject->disableCollision(); + + if( mDataBlock->coupleBeam ) + { + // We're the shooter recast the end point... + mSourceObject->getRenderMuzzlePoint(mSourceObjectSlot, &mInitialPosition); + mSourceObject->getRenderMuzzleVector(mSourceObjectSlot, &mInitialDirection); + + mBeam.mData.endPos = mInitialPosition + mInitialDirection * mDataBlock->maxRifleRange; + } + + RayInfo rayInfo; + bool rayHit = gClientContainer.castRay(mInitialPosition, mBeam.mData.endPos, + (csmStaticCollisionMask | csmDynamicCollisionMask | WaterObjectType), + &rayInfo); + if( rayHit ) + { + if( mDataBlock->coupleBeam ) + { + mBeam.mData.endPos = rayInfo.point; + } + mTruncated = true; + } + else + { + mTruncated = false; + } + mSourceObject->enableCollision(); + } + else + { + // Just set the last position... + if( mDataBlock->coupleBeam ) + { + mBeam.mData.endPos = mClientWarpTo; + } + } + } + + MatrixF temp(true); + temp.setPosition( mBeam.mData.endPos ); + setTransform(temp); + + mBeam.mData.direction = mBeam.mData.endPos - mInitialPosition; + mBeam.mData.direction.normalizeSafe(); +} + +//-------------------------------------------------------------------------- +void TargetProjectile::advanceTime(F32 dt) +{ + mElapsedTime += dt; +} + + +//-------------------------------------------------------------------------- +void TargetProjectile::setClientWarp(const Point3F& newEndPoint) +{ + mClientWarpFrom = mBeam.mData.endPos; + mClientWarpTo = newEndPoint; + mClientTotalWarpTicks = 10; + mClientWarpTicks = 10; +} + + +//-------------------------------------------------------------------------- +bool TargetProjectile::getTarget(Point3F* pTarget, U32* pTeamId) +{ + if (mTruncated == true) { + *pTarget = mBeam.mData.endPos; + *pTeamId = 0; + return true; + } else { + return false; + } +} + + +//-------------------------------------------------------------------------- +U32 TargetProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag((mask & GameBase::InitialUpdateMask) != 0)) { + mathWrite(*stream, mInitialPosition); + mathWrite(*stream, mBeam.mData.endPos); + stream->writeFlag(mTruncated); + + if (bool(mSourceObject)) { + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + bool isFiredOnClient = mSourceObject->getControllingClient() == con; + + 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); + stream->writeFlag(isFiredOnClient); + } + } else { + stream->writeFlag(false); + } + } else { + // Swinging around... + if (bool(mSourceObject)) { + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + bool isFiredOnClient = mSourceObject->getControllingClient() == con; + S32 ghostIndex = con->getGhostIndex(mSourceObject); + if (ghostIndex != -1) { + stream->writeFlag(true); + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount); + stream->writeRangedU32(U32(mSourceObjectSlot), + 0, ShapeBase::MaxMountedImages - 1); + stream->writeFlag(isFiredOnClient); + } else { + stream->writeFlag(false); + mathWrite(*stream, mInitialPosition); + } + } else { + stream->writeFlag(false); + mathWrite(*stream, mInitialPosition); + } + + mathWrite(*stream, mBeam.mData.endPos); + stream->writeFlag(mTruncated); + } + + return retMask; +} + +//-------------------------------------------------------------------------- +void TargetProjectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) { + mathRead(*stream, &mInitialPosition); + mathRead(*stream, &mBeam.mData.endPos); + mClientWarpFrom = mBeam.mData.endPos; + mClientWarpTo = mBeam.mData.endPos; + mClientTotalWarpTicks = 0; + mClientWarpTicks = 0; + + + mTruncated = stream->readFlag(); + + mClientOwned = false; + if (stream->readFlag()) { + mSourceObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + mSourceObjectSlot = stream->readRangedU32(0, ShapeBase::MaxMountedImages - 1); + mClientOwned = stream->readFlag(); + + NetObject* pObject = con->resolveGhost(mSourceObjectId); + if (pObject != NULL) + mSourceObject = dynamic_cast(pObject); + } else { + mSourceObjectId = -1; + mSourceObjectSlot = -1; + mSourceObject = NULL; + } + } + else + { + // Swinging around... + mClientOwned = false; + if (stream->readFlag()) { + mSourceObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + mSourceObjectSlot = stream->readRangedU32(0, ShapeBase::MaxMountedImages - 1); + mClientOwned = stream->readFlag(); + + NetObject* pObject = con->resolveGhost(mSourceObjectId); + if (pObject != NULL) + mSourceObject = dynamic_cast(pObject); + } else { + mSourceObjectId = -1; + mSourceObjectSlot = -1; + mSourceObject = NULL; + + mathRead(*stream, &mInitialPosition); + } + + Point3F newEndPoint; + mathRead(*stream, &newEndPoint); + mTruncated = stream->readFlag(); + + if (mClientOwned == false) + setClientWarp(newEndPoint); + } + + mBeam.mData.direction = mBeam.mData.endPos - mInitialPosition; + mBeam.mData.direction.normalizeSafe(); +} + diff --git a/game/projTargeting.h b/game/projTargeting.h new file mode 100644 index 0000000..a553d05 --- /dev/null +++ b/game/projTargeting.h @@ -0,0 +1,158 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_TARGETPROJECTILE +#define _H_TARGETPROJECTILE + +#ifndef _PROJECTILE_H_ +#include "game/projectile.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif +#ifndef _WEAPONBEAM_H_ +#include "game/weaponBeam.h" +#endif + +// ------------------------------------------------------------------------- +class TargetProjectileData : public ProjectileData +{ + typedef ProjectileData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + + enum TargetTextures + { + TT_BEAM = 0, + TT_FLARE, + TT_PULSE, + TT_END_FLARE, + TT_NUM_TEX + }; + + F32 maxRifleRange; + ColorF beamColor; + + F32 startBeamWidth; + F32 beamFlareAngle; + F32 minFlareSize; + F32 maxFlareSize; + F32 pulseBeamWidth; + F32 pulseSpeed; + F32 pulseLength; + bool coupleBeam; + + StringTableEntry textureName[TT_NUM_TEX]; + + + //-------------------------------------- load set variables + public: + TextureHandle textureHandle[TT_NUM_TEX]; + + public: + TargetProjectileData(); + ~TargetProjectileData(); + + //aiming used by AI + bool calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& /*sourceVel*/, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(TargetProjectileData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class TargetProjectile : public Projectile +{ + typedef Projectile Parent; + + private: + TargetProjectileData* mDataBlock; + + protected: + enum TargetProjNetMasks { + ResetEndPointMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + protected: + + WeaponBeam mBeam; + + F32 mElapsedTime; + bool mTruncated; + bool mClientOwned; // True if this ghost is on the client + // that fired the targeting laser + + + // Client warping variables + U32 mClientTotalWarpTicks; + U32 mClientWarpTicks; + Point3F mClientWarpFrom; + Point3F mClientWarpTo; + void setClientWarp(const Point3F&); + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + // Rendering + protected: + bool checkForFlare( F32 &flareScale, const Point3F &camPos ); + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderBeam( WeaponBeam &beam, F32 percentDone ); + void renderFlare( ColorF flareColor, F32 flareScale ); + void renderObject(SceneState*, SceneRenderImage*); + + bool calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime); + + void updateBeamData( BeamData &beamData, const Point3F &camPos ); + void renderEndFlare( F32 flareSize ); + bool canRenderEndFlare( const Point3F &camPos ); + + + + public: + TargetProjectile(); + ~TargetProjectile(); + + bool getTarget(Point3F* pTarget, U32* pTeamId); + + // Time/Move Management + public: + void processTick(const Move*); + void interpolateTick(F32); + virtual void advanceTime(F32); + + DECLARE_CONOBJECT(TargetProjectile); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_TARGETPROJECTILE + diff --git a/game/projTracer.cc b/game/projTracer.cc new file mode 100644 index 0000000..3fb476d --- /dev/null +++ b/game/projTracer.cc @@ -0,0 +1,406 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/projTracer.h" +#include "Core/bitStream.h" +#include "console/consoleTypes.h" +#include "game/Debris.h" + +#include "dgl/dgl.h" +#include "PlatformWin32/platformGL.h" +#include "sceneGraph/sceneState.h" +#include "Math/mathUtils.h" + +IMPLEMENT_CO_DATABLOCK_V1(TracerProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(TracerProjectile); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +TracerProjectileData::TracerProjectileData() +{ + tracerLength = 10; + tracerMinPixels = 3; + tracerAlpha = false; + tracerColor.set(1, 1, 1, 1); + tracerWidth = 0.5; + crossViewAng = 0.98; + crossSize = 0.45; + renderCross = true; + + dMemset( textureName, 0, sizeof( textureName ) ); + dMemset( textureHandle, 0, sizeof( textureHandle ) ); +} + +TracerProjectileData::~TracerProjectileData() +{ + +} + + +//-------------------------------------------------------------------------- +void TracerProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("tracerLength", TypeF32, Offset(tracerLength, TracerProjectileData)); + addField("tracerMinPixels", TypeF32, Offset(tracerMinPixels, TracerProjectileData)); + addField("tracerAlpha", TypeBool, Offset(tracerAlpha, TracerProjectileData)); + addField("tracerColor", TypeColorF, Offset(tracerColor, TracerProjectileData)); + addField("tracerTex", TypeString, Offset(textureName, TracerProjectileData), NUM_TEX ); + addField("tracerWidth", TypeF32, Offset(tracerWidth, TracerProjectileData)); + addField("crossViewAng", TypeF32, Offset(crossViewAng, TracerProjectileData)); + addField("crossSize", TypeF32, Offset(crossSize, TracerProjectileData)); + addField("renderCross", TypeBool, Offset(renderCross, TracerProjectileData)); +} + + +//-------------------------------------------------------------------------- +bool TracerProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (tracerLength < 1.0 || tracerLength > 50) { + Con::warnf(ConsoleLogEntry::General, "TracerProjectileData(%s)::onAdd: tracerLength must be in the range [1, 50]", getName()); + tracerLength = tracerLength < 1.0 ? 1.0 : 50; + } + if (tracerMinPixels < 1.0 || tracerMinPixels > 20) { + Con::warnf(ConsoleLogEntry::General, "TracerProjectileData(%s)::onAdd: tracerMinPixels must be in the range [1, 20]", getName()); + tracerMinPixels = tracerMinPixels < 1.0 ? 1.0 : 20; + } + + tracerColor.clamp(); + + return true; +} + + +//-------------------------------------------------------------------------- +bool TracerProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + + if( server == false && textureName[0] ) + { + U32 i; + for( i=0; iwrite(tracerLength); + stream->write(tracerWidth); + stream->write(tracerMinPixels); + stream->write(tracerAlpha); + stream->write(tracerColor); + stream->write(crossViewAng); + stream->write(crossSize); + stream->write(renderCross); + + U32 i; + for( i=0; iwriteString(textureName[i]); + } +} + +void TracerProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&tracerLength); + stream->read(&tracerWidth); + stream->read(&tracerMinPixels); + stream->read(&tracerAlpha); + stream->read(&tracerColor); + stream->read(&crossViewAng); + stream->read(&crossSize); + stream->read(&renderCross); + + for( U32 i=0; ireadSTString(); + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +TracerProjectile::TracerProjectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + + // +} + +TracerProjectile::~TracerProjectile() +{ + // +} + + +//-------------------------------------------------------------------------- +bool TracerProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isClientObject()) { + mCurrLength = 0; + } + + Point3F min( -mDataBlock->tracerWidth * 0.5, -mDataBlock->tracerLength * 0.5, -mDataBlock->tracerWidth * 0.5 ); + mObjBox = Box3F( min, -min ); + + return true; +} + + +void TracerProjectile::onRemove() +{ + // + + Parent::onRemove(); +} + + +bool TracerProjectile::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; +} + + +//-------------------------------------------------------------------------- +void TracerProjectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + mCurrLength += getVelocity().len() * dt * (2.0 / 3.0); + if (mCurrLength > mDataBlock->tracerLength) + mCurrLength = mDataBlock->tracerLength; +} + + +//-------------------------------------------------------------------------- +class TracerRenderImage : public SceneRenderImage { + public: + F32 length; +}; + +bool TracerProjectile::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)) { + TracerRenderImage* image = new TracerRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + + // Calculate image length... + image->length = mCurrLength; + + state->insertRenderImage(image); + } + + return false; +} + + +//-------------------------------------------------------------------------- +void TracerProjectile::renderObject(SceneState* state, SceneRenderImage*) +{ + if( mCurrTick >= mDeleteTick ) return; + + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + renderProjectile( state->getCameraPosition() ); + + if( mDataBlock->renderCross ) + { + renderCrossSection( state->getCameraPosition() ); + } + + // restore state + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +// Render projectile +//-------------------------------------------------------------------------- +void TracerProjectile::renderProjectile( const Point3F &camPos ) +{ + Point3F vel = getVelocity(); + + if( vel.isZero() ) + { + return; + } + + Point3F dir = mInitialDirection; + dir.normalize(); + + Point3F pos = getRenderPosition(); + Point3F dirFromCam = pos - camPos; + Point3F crossDir; + mCross( dirFromCam, dir, &crossDir ); + crossDir.normalize(); + + + F32 width = mDataBlock->tracerWidth; + F32 length = mDataBlock->tracerLength / 2.0; + + crossDir *= width; + Point3F start = pos - length * dir; + Point3F end = pos + length * dir; + + + // clip start point + Point3F p1 = start - mInitialPosition; + Point3F p2 = end - mInitialPosition; + if( mDot( p1, p2 ) < 0.0 ) + { + start = mInitialPosition; + } + + + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_TEXTURE_2D); + + glColor4f( 1.0, 1.0, 1.0, 1.0 ); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[0].getGLName() ); + + glBegin(GL_QUADS); + + glTexCoord2f(0, 0); + glVertex3fv( start + crossDir ); + + glTexCoord2f(0, 1); + glVertex3fv( start - crossDir ); + + glTexCoord2f(1, 1); + glVertex3fv( end - crossDir ); + + glTexCoord2f(1, 0); + glVertex3fv( end + crossDir ); + + glEnd(); + +} + +//-------------------------------------------------------------------------- +// Render "cross section" so effect doesn't look flat when seen edge-on +//-------------------------------------------------------------------------- +void TracerProjectile::renderCrossSection( const Point3F &camPos ) +{ + + Point3F dir = getVelocity(); + + if( dir.isZero() ) + { + return; + } + + dir = mInitialDirection; + + Point3F pos = getRenderPosition(); + Point3F dirFromCam = pos - camPos; + dirFromCam.normalize(); + dir.normalize(); + + F32 angle = mDot( dir, dirFromCam ); + if( angle > -mDataBlock->crossViewAng && angle < mDataBlock->crossViewAng ) return; + + F32 width = mDataBlock->crossSize / 2.0; + + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + MatrixF mat = MathUtils::createOrientFromDir( dir ); + mat.setPosition( getRenderPosition() ); + dglMultMatrix( &mat ); + + glColor4f( 1.0, 1.0, 1.0, 1.0 ); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[1].getGLName() ); + + glBegin(GL_QUADS); + + glTexCoord2f(0, 0); + glVertex3f( -width, 0, -width ); + + glTexCoord2f(0, 1); + glVertex3f( width, 0, -width ); + + glTexCoord2f(1, 1); + glVertex3f( width, 0, width ); + + glTexCoord2f(1, 0); + glVertex3f( -width, 0, width ); + + glEnd(); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} diff --git a/game/projTracer.h b/game/projTracer.h new file mode 100644 index 0000000..22e663c --- /dev/null +++ b/game/projTracer.h @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_TRACERPROJECTILE +#define _H_TRACERPROJECTILE + +#ifndef _LINEARPROJECTILE_H_ +#include "game/linearProjectile.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +struct DebrisData; + +// ------------------------------------------------------------------------- +class TracerProjectileData : public LinearProjectileData +{ + typedef LinearProjectileData Parent; + + enum Constants + { + NUM_TEX = 2, + }; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + F32 tracerLength; + bool tracerAlpha; + F32 tracerMinPixels; + ColorF tracerColor; + F32 tracerWidth; + F32 crossViewAng; + F32 crossSize; + bool renderCross; + + StringTableEntry textureName[NUM_TEX]; + TextureHandle textureHandle[NUM_TEX]; + + public: + TracerProjectileData(); + ~TracerProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(TracerProjectileData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class TracerProjectile : public LinearProjectile +{ + typedef LinearProjectile Parent; + + private: + TracerProjectileData* mDataBlock; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + void renderProjectile( const Point3F &camPos ); + void renderCrossSection( const Point3F &camPos ); + + F32 mCurrLength; + + public: + TracerProjectile(); + ~TracerProjectile(); + + // Time/Move Management + public: + void advanceTime(F32); + + DECLARE_CONOBJECT(TracerProjectile); +}; + +#endif // _H_TRACERPROJECTILE + diff --git a/game/projectile.cc b/game/projectile.cc new file mode 100644 index 0000000..8fca2b2 --- /dev/null +++ b/game/projectile.cc @@ -0,0 +1,924 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "scenegraph/sceneState.h" +#include "scenegraph/sceneGraph.h" +#include "console/consoleTypes.h" +#include "core/bitStream.h" +#include "game/particleEngine.h" +#include "game/explosion.h" +#include "game/shapeBase.h" +#include "ts/tsShapeInstance.h" +#include "game/projectile.h" +#include "audio/audio.h" +#include "sim/decalManager.h" +#include "game/splash.h" +#include "terrain/waterBlock.h" +#include "math/mathUtils.h" + +IMPLEMENT_CO_DATABLOCK_V1(ProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(Projectile); + +const U32 Projectile::csmStaticCollisionMask = TerrainObjectType | + InteriorObjectType | + ForceFieldObjectType | + StaticObjectType; + +const U32 Projectile::csmDynamicCollisionMask = PlayerObjectType | + VehicleObjectType | + StationObjectType | + GeneratorObjectType | + SensorObjectType | + DamagableItemObjectType | + TurretObjectType; +const U32 Projectile::csmDamageableMask = Projectile::csmDynamicCollisionMask; + +U32 Projectile::smProjectileWarpTicks = 5; + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ProjectileData::ProjectileData() +{ + projectileShapeName = NULL; + emitterDelay = -1; + + velInheritFactor = 1.0; + isBallistic = false; + + directDamage = 0.0f; + hasDamageRadius = false; + indirectDamage = 0.0f; + damageRadius = 0.0f; + radiusDamageType = 0; + + kickBackStrength = 0.0; + + baseEmitter = NULL; + delayEmitter = NULL; + bubbleEmitter = NULL; + explosion = NULL; + underwaterExplosion = NULL; + splash = NULL; + sound = NULL; + wetFireSound = NULL; + fireSound = NULL; + baseEmitterId = 0; + delayEmitterId = 0; + bubbleEmitterId = 0; + explosionId = 0; + underwaterExplosionId = 0; + splashId = 0; + soundId = 0; + wetFireSoundId = 0; + fireSoundId = 0; + + hasLight = false; + lightRadius = 1; + lightColor.set(1, 1, 1); + + hasLightUnderwaterColor = false; + underWaterLightColor.set(1, 1, 1); + + explodeOnWaterImpact = false; + depthTolerance = 5.0; + bubbleEmitTime = 0.5; + + for (U32 i = 0; i < 6; i++) { + decalData[i] = NULL; + decalID[i] = 0; + } + numDecals = 0; + faceViewer = false; + scale.set( 1.0, 1.0, 1.0 ); + +} + +ProjectileData::~ProjectileData() +{ + // +} + +//-------------------------------------------------------------------------- +IMPLEMENT_GETDATATYPE(ProjectileData) +IMPLEMENT_SETDATATYPE(ProjectileData) + +void ProjectileData::initPersistFields() +{ + Parent::initPersistFields(); + + Con::registerType(TypeProjectileDataPtr, sizeof(ProjectileData*), + REF_GETDATATYPE(ProjectileData), + REF_SETDATATYPE(ProjectileData)); + + addField("projectileShapeName", TypeString, Offset(projectileShapeName, ProjectileData)); + addField("emitterDelay", TypeS32, Offset(emitterDelay, ProjectileData)); + + addField("velInheritFactor", TypeF32, Offset(velInheritFactor, ProjectileData)); + + addField("directDamage", TypeF32, Offset(directDamage, ProjectileData)); + addField("hasDamageRadius", TypeBool, Offset(hasDamageRadius, ProjectileData)); + addField("indirectDamage", TypeF32, Offset(indirectDamage, ProjectileData)); + addField("damageRadius", TypeF32, Offset(damageRadius, ProjectileData)); + addField("radiusDamageType", TypeS32, Offset(radiusDamageType, ProjectileData)); + addField("kickBackStrength", TypeF32, Offset(kickBackStrength, ProjectileData)); + + addField("baseEmitter", TypeParticleEmitterDataPtr, Offset(baseEmitter, ProjectileData)); + addField("delayEmitter", TypeParticleEmitterDataPtr, Offset(delayEmitter, ProjectileData)); + addField("bubbleEmitter", TypeParticleEmitterDataPtr, Offset(bubbleEmitter, ProjectileData)); + addField("explosion", TypeExplosionDataPtr, Offset(explosion, ProjectileData)); + addField("underwaterExplosion", TypeExplosionDataPtr, Offset(underwaterExplosion, ProjectileData)); + addField("splash", TypeSplashDataPtr, Offset(splash, ProjectileData)); + addField("sound", TypeAudioProfilePtr, Offset(sound, ProjectileData)); + addField("wetFireSound", TypeAudioProfilePtr, Offset(wetFireSound, ProjectileData)); + addField("fireSound", TypeAudioProfilePtr, Offset(fireSound, ProjectileData)); + + addField("hasLight", TypeBool, Offset(hasLight, ProjectileData)); + addField("lightRadius", TypeF32, Offset(lightRadius, ProjectileData)); + addField("lightColor", TypeColorF, Offset(lightColor, ProjectileData)); + + addField("hasLightUnderwaterColor", TypeBool, Offset(hasLightUnderwaterColor, ProjectileData)); + addField("underWaterLightColor", TypeColorF, Offset(underWaterLightColor, ProjectileData)); + + addField("explodeOnWaterImpact", TypeBool, Offset(explodeOnWaterImpact, ProjectileData)); + addField("depthTolerance", TypeF32, Offset(depthTolerance, ProjectileData)); + + addField("decalData", TypeDecalDataPtr, Offset(decalData, ProjectileData), 6); + addField("bubbleEmitTime", TypeF32, Offset(bubbleEmitTime, ProjectileData)); + addField("faceViewer", TypeBool, Offset(faceViewer, ProjectileData)); + addField("scale", TypePoint3F, Offset(scale, ProjectileData)); + +} + + +//-------------------------------------------------------------------------- +bool ProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (!baseEmitter && baseEmitterId != 0) + if (Sim::findObject(baseEmitterId, baseEmitter) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(baseEmitter): %d", baseEmitterId); + if (!delayEmitter && delayEmitterId != 0) + if (Sim::findObject(delayEmitterId, delayEmitter) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(delayEmitter): %d", delayEmitterId); + if (!bubbleEmitter && bubbleEmitterId != 0) + if (Sim::findObject(bubbleEmitterId, bubbleEmitter) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(bubbleEmitter): %d", bubbleEmitterId); + if (!explosion && explosionId != 0) + if (Sim::findObject(explosionId, explosion) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(explosion): %d", explosionId); + if (!underwaterExplosion && underwaterExplosionId != 0) + if (Sim::findObject(underwaterExplosionId, underwaterExplosion) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(underwaterExplosion): %d", underwaterExplosionId); + if (!splash && splashId != 0) + if (Sim::findObject(splashId, splash) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(splash): %d", splashId); + if (!sound && soundId != 0) + if (Sim::findObject(soundId, sound) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(sound): %d", soundId); + if (!wetFireSound && wetFireSoundId != 0) + if (Sim::findObject(wetFireSoundId, wetFireSound) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(wetFireSound): %d", wetFireSoundId); + if (!fireSound && fireSoundId != 0) + if (Sim::findObject(fireSoundId, fireSound) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(fireSound): %d", fireSoundId); + + if (emitterDelay < 0) + emitterDelay = -1; + if (directDamage < 0.0) { + Con::warnf(ConsoleLogEntry::General, "ProjectileData(%s)::onAdd: directDamage < 0.0", getName()); + directDamage = 0.0; + } + if (damageRadius < 0.0) { + Con::warnf(ConsoleLogEntry::General, "ProjectileData(%s)::onAdd: damageRadius < 0.0", getName()); + damageRadius = 0.0; + } + if (indirectDamage < 0.0) { + Con::warnf(ConsoleLogEntry::General, "ProjectileData(%s)::onAdd: indirectDamage < 0.0", getName()); + indirectDamage = 0.0; + } + if ((damageRadius == 0.0 || indirectDamage == 0.0) && hasDamageRadius == true) { + Con::warnf(ConsoleLogEntry::General, "ProjectileData(%s)::onAdd: (damageRadius || indirectDamage) == 0 && hasDamageRadius == true", getName()); + hasDamageRadius = false; + } + if (velInheritFactor < 0.0f || velInheritFactor > 1.0f) { + Con::warnf(ConsoleLogEntry::General, "ProjectileData(%s)::onAdd: velInheritFactor out of range", getName()); + velInheritFactor = velInheritFactor < 0.0f ? 0.0f : 1.0f; + } + if (kickBackStrength < 0.0f) { + Con::warnf(ConsoleLogEntry::General, "ProjectileData(%s)::onAdd: kickBackStrength must be >= 0", getName()); + kickBackStrength = 0.0f; + } + + if (hasLight == true) { + if (lightRadius < 1 || lightRadius > 20) { + Con::warnf(ConsoleLogEntry::General, "ProjectileData(%s)::onAdd: lightRadius must be in range [1, 20]", getName()); + lightRadius = lightRadius < 1 ? 1.0 : 20.0; + } + } + + lightColor.clamp(); + underWaterLightColor.clamp(); + + return true; +} + + +bool ProjectileData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (projectileShapeName && projectileShapeName[0] != '\0') { + char fullName[256]; + dSprintf(fullName, sizeof(fullName), "shapes/%s", projectileShapeName); + + projectileShape = ResourceManager->load(fullName); + if (bool(projectileShape) == false) { + dSprintf(errorBuffer, sizeof(errorBuffer), "ProjectileData::load: Couldn't load shape \"%s\"", projectileShapeName); + return false; + } + + ambientSeq = projectileShape->findSequence("ambient"); + } + + if (bool(projectileShape)) { + TSShapeInstance* pDummy = new TSShapeInstance(projectileShape, !server); + delete pDummy; + } + + for (U32 i = 0; i < 6; i++) { + if( !decalData && decalID != 0 ) + { + if( !Sim::findObject( decalID[i], decalData[i] ) ) + { + Con::errorf( ConsoleLogEntry::General, "ProjectileData::preload Invalid packet, bad datablockId(decalData): 0x%x", decalID[i]); + } + + } + if (!server && decalData[i]) + numDecals++; + } + if (!server) { + // DMM: order the decals so the non-null ones are in the front... + } + + return true; +} + + +bool ProjectileData::calculateAim(const Point3F&, + const Point3F&, + const Point3F&, + const Point3F&, + Point3F*, F32*, + Point3F*, F32*) +{ + //AssertFatal(false, "Projectile::calculateAim: this function is (essentially) pure virtual. Should never be called"); + Con::warnf(ConsoleLogEntry::General, "Projectile::calculateAim: this function is (essentially) pure virtual. Should never be called"); + return false; +} + + +//-------------------------------------------------------------------------- +void ProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(projectileShapeName); + stream->write(emitterDelay); + stream->write(bubbleEmitTime); + 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(baseEmitter != NULL)) + stream->writeRangedU32(baseEmitter->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + if (stream->writeFlag(delayEmitter != NULL)) + stream->writeRangedU32(delayEmitter->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + if (stream->writeFlag(bubbleEmitter != NULL)) + stream->writeRangedU32(bubbleEmitter->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + if (stream->writeFlag(explosion != NULL)) + stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(underwaterExplosion != NULL)) + stream->writeRangedU32(underwaterExplosion->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(splash != NULL)) + stream->writeRangedU32(splash->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + if (stream->writeFlag(sound != NULL)) + stream->writeRangedU32(sound->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + if (stream->writeFlag(wetFireSound != NULL)) + stream->writeRangedU32(wetFireSound->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + if (stream->writeFlag(fireSound != NULL)) + stream->writeRangedU32(fireSound->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + for (U32 i = 0; i < 6; i++) { + if (stream->writeFlag(decalData[i] != NULL)) + stream->writeRangedU32(decalData[i]->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + if(stream->writeFlag(hasLight)) + { + stream->writeFloat(lightRadius/20.0, 8); + stream->writeFloat(lightColor.red,7); + stream->writeFloat(lightColor.green,7); + stream->writeFloat(lightColor.blue,7); + } + if(stream->writeFlag(hasLightUnderwaterColor)) + { + stream->writeFloat(underWaterLightColor.red,7); + stream->writeFloat(underWaterLightColor.green,7); + stream->writeFloat(underWaterLightColor.blue,7); + } + stream->write(explodeOnWaterImpact); + stream->write(depthTolerance); +} + +void ProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + projectileShapeName = stream->readSTString(); + stream->read(&emitterDelay); + + stream->read(&bubbleEmitTime); + 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()) + baseEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + baseEmitterId = 0; + + if (stream->readFlag()) + delayEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + delayEmitterId = 0; + + if (stream->readFlag()) + bubbleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + bubbleEmitterId = 0; + + if (stream->readFlag()) + explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + explosionId = 0; + + if (stream->readFlag()) + underwaterExplosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + underwaterExplosionId = 0; + + if (stream->readFlag()) + splashId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + splashId = 0; + + if (stream->readFlag()) + soundId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + soundId = 0; + if (stream->readFlag()) + wetFireSoundId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + wetFireSoundId = 0; + if (stream->readFlag()) + fireSoundId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + fireSoundId = 0; + + for (U32 i = 0; i < 6; i++) { + if (stream->readFlag()) { + decalID[i] = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + } + + hasLight = stream->readFlag(); + if(hasLight) + { + lightRadius = stream->readFloat(8) * 20; + lightColor.red = stream->readFloat(7); + lightColor.green = stream->readFloat(7); + lightColor.blue = stream->readFloat(7); + } + hasLightUnderwaterColor = stream->readFlag(); + if(hasLightUnderwaterColor) + { + underWaterLightColor.red = stream->readFloat(7); + underWaterLightColor.green = stream->readFloat(7); + underWaterLightColor.blue = stream->readFloat(7); + } + stream->read(&explodeOnWaterImpact); + stream->read(&depthTolerance); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +Projectile::Projectile() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + mTypeMask |= ProjectileObjectType; + + mInitialPosition.set(0, 0, 0); + mInitialDirection.set(0, 0, 1); + mSourceObjectId = -1; + mVehicleObjectId = -1; + mSourceObjectSlot = -1; + + mCurrTick = 0; + + mProjectileShape = NULL; + mAmbientThread = NULL; + + mHidden = false; + mFadeValue = 1.0; + mPlayedSplash = false; + + mBaseEmitter = NULL; + mDelayEmitter = NULL; + mBubbleEmitter = NULL; + + mBubbleEmitterTime = U32( -1 ); + + mUseUnderwaterLight = false; + + mProjectileSound = NULL_AUDIOHANDLE; +} + +Projectile::~Projectile() +{ + delete mProjectileShape; + mProjectileShape = NULL; +} + +//-------------------------------------------------------------------------- +void Projectile::initPersistFields() +{ + Parent::initPersistFields(); + + addField("initialPosition", TypePoint3F, Offset(mInitialPosition, Projectile)); + addField("initialDirection", TypePoint3F, Offset(mInitialDirection, Projectile)); + addField("sourceObject", TypeS32, Offset(mSourceObjectId, Projectile)); + addField("vehicleObject", TypeS32, Offset(mVehicleObjectId, Projectile)); + addField("sourceSlot", TypeS32, Offset(mSourceObjectSlot, Projectile)); +} + +void Projectile::consoleInit() +{ + // +} + + +bool Projectile::calculateImpact(float, + Point3F& pointOfImpact, + float& impactTime) +{ + Con::warnf(ConsoleLogEntry::General, "Projectile::calculateImpact: this function is (essentially) pure virtual. 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 || mVehicleObject == 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; + } + + mVehicleObject = (Sim::findObject(mVehicleObjectId, ptr)) ? ptr : NULL; + + // If we're on the server, we need to inherit some of our parent's velocity + // + Point3F sourceObjectVel(0, 0, 0); + if (bool(mSourceObject)) + { + Point3F velocity = mSourceObject->getVelocity(); + if(bool(mVehicleObject)) + velocity = mVehicleObject->getVelocity(); + + sourceObjectVel = velocity * mDataBlock->velInheritFactor; + } + F32 len = sourceObjectVel.len(); + mExcessVel = U32(len + 0.5f); + if (mExcessVel != 0) + mExcessDir = sourceObjectVel / len; + else + mExcessDir.set(0, 0, 1); + mExcessDirDumb = BitStream::dumbDownNormal(mExcessDir, ExcessVelDirBits); + + mCurrTick = 0; + } else { + if (bool(mDataBlock->projectileShape)) { + mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, isClientObject()); + + if (mDataBlock->ambientSeq != -1) { + mAmbientThread = mProjectileShape->addThread(); + mProjectileShape->setTimeScale(mAmbientThread, 1); + mProjectileShape->setSequence(mAmbientThread, mDataBlock->ambientSeq, 0); + } + } + + if (mDataBlock->baseEmitter != NULL) { + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock(mDataBlock->baseEmitter); + if (pEmitter->registerObject() == false) { + Con::warnf(ConsoleLogEntry::General, "Could not register base emitter for particle of class: %s", mDataBlock->getName()); + delete pEmitter; + pEmitter = NULL; + } + mBaseEmitter = pEmitter; + } + if (mDataBlock->delayEmitter != NULL) { + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock(mDataBlock->delayEmitter); + if (pEmitter->registerObject() == false) { + Con::warnf(ConsoleLogEntry::General, "Could not register delay emitter for particle of class: %s", mDataBlock->getName()); + delete pEmitter; + pEmitter = NULL; + } + mDelayEmitter = pEmitter; + } + if (mDataBlock->bubbleEmitter != NULL) { + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock(mDataBlock->bubbleEmitter); + if (pEmitter->registerObject() == false) { + Con::warnf(ConsoleLogEntry::General, "Could not register delay emitter for particle of class: %s", mDataBlock->getName()); + delete pEmitter; + pEmitter = NULL; + } + mBubbleEmitter = pEmitter; + } + + if (mDataBlock->hasLight == true) + Sim::getLightSet()->addObject(this); + + mBubbleEmitterTime = mDataBlock->bubbleEmitTime; + } + mSourceIdTimeoutTicks = SourceIdTimeoutTicks; + + if (bool(mSourceObject)) + 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(); + + mLastPos = getPosition(); + + return true; +} + + +void Projectile::onRemove() +{ + if (bool(mBaseEmitter)) { + mBaseEmitter->deleteWhenEmpty(); + mBaseEmitter = NULL; + } + if (bool(mDelayEmitter)) { + mDelayEmitter->deleteWhenEmpty(); + mDelayEmitter = NULL; + } + if (bool(mBubbleEmitter)) { + mBubbleEmitter->deleteWhenEmpty(); + mBubbleEmitter = NULL; + } + + Parent::onRemove(); +} + + +bool Projectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + return true; +} + + +//-------------------------------------------------------------------------- +void Projectile::registerLights(LightManager * lightManager, bool lightingScene) +{ + if(lightingScene) + return; + + if (mDataBlock->hasLight && mHidden == false) { + mLight.mType = LightInfo::Point; + getRenderTransform().getColumn(3, &mLight.mPos); + mLight.mRadius = mDataBlock->lightRadius; + + if (mDataBlock->hasLightUnderwaterColor && mUseUnderwaterLight) + { + mLight.mColor = mDataBlock->underWaterLightColor; + } + else + { + mLight.mColor = mDataBlock->lightColor; + } + lightManager->addLight(&mLight); + } +} + +//---------------------------------------------------------------------------- +void Projectile::processTick(const Move* move) +{ + Parent::processTick(move); + + mCurrTick++; + if (mSourceIdTimeoutTicks) + mSourceIdTimeoutTicks--; +} + + +void Projectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + mLastPos = getRenderPosition(); + + if (mAmbientThread) + mProjectileShape->advanceTime(dt, mAmbientThread); +} + + +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(); + } + + if (bool(mBaseEmitter)) { + mBaseEmitter->emitParticles(from, to, + axis, vel, + ms); + } + if (bool(mDelayEmitter) && mCurrTick > (mDataBlock->emitterDelay / TickMs)) { + mDelayEmitter->emitParticles(from, to, + axis, vel, + ms); + } +} + + +void Projectile::updateSound(const Point3F& pos, const Point3F& vel, const bool active) +{ + AssertFatal(isClientObject(), "Error, server projectiles play no sounds!"); + if (mDataBlock->sound == NULL) + return; + + MatrixF xForm(true); + xForm.setColumn(3, pos); + + if (active == true && mProjectileSound == NULL_AUDIOHANDLE) { + mProjectileSound = alxPlay(mDataBlock->sound, &xForm, &vel); + return; + } else if (active == false && mProjectileSound != NULL_AUDIOHANDLE) { + alxStop(mProjectileSound); + mProjectileSound = NULL_AUDIOHANDLE; + } + + if (mProjectileSound != NULL_AUDIOHANDLE) { + alxSourceMatrixF(mProjectileSound, &xForm); +// alxSourcePoint3F(mProjectileSound, AL_VELOCITY, &vel); + } +} + + +//-------------------------------------------------------------------------- +void Projectile::onCollision(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, 6, "onCollision", + Con::getIntArg(getId()), + Con::getIntArg(hitObject->getId()), + Con::getFloatArg(mFadeValue), + posArg, + normalArg); + } +} + +//-------------------------------------------------------------------------- +void Projectile::prepModelView(SceneState* state) +{ + Point3F targetVector; + if( mDataBlock->faceViewer ) + { + targetVector = state->getCameraPosition() - getRenderPosition(); + targetVector.normalize(); + + MatrixF explOrient = MathUtils::createOrientFromDir( targetVector ); + explOrient.setPosition( getRenderPosition() ); + dglMultMatrix( &explOrient ); + } + else + { + dglMultMatrix( &getRenderTransform() ); + } +} + +//-------------------------------------------------------------------------- +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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + + // For projectiles, the datablock pointer is a good enough sort key, since they aren't + // skinned at all... + image->textureSortKey = U32(mDataBlock); + + state->insertRenderImage(image); + } + + return false; +} + + +void Projectile::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + prepModelView( state ); + + glScalef( mDataBlock->scale.x, mDataBlock->scale.y, mDataBlock->scale.z ); + + AssertFatal(mProjectileShape != NULL, + "Projectile::renderObject: Error, projectile shape should always be present in renderObject"); + mProjectileShape->selectCurrentDetail(); + mProjectileShape->animate(); + + Point3F cameraOffset; + mObjToWorld.getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 fogAmount = state->getHazeAndFog(cameraOffset.len(),cameraOffset.z); + + if (mFadeValue == 1.0) { + mProjectileShape->setupFog(fogAmount, state->getFogColor()); + } else { + mProjectileShape->setupFog(0.0, state->getFogColor()); + mProjectileShape->setAlphaAlways(mFadeValue * (1.0 - fogAmount)); + } + mProjectileShape->render(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +bool Projectile::pointInWater( Point3F &point, F32 *waterHeight ) +{ + SimpleQueryList sql; + if (isServerObject()) + gServerSceneGraph->getWaterObjectList(sql); + else + gClientSceneGraph->getWaterObjectList(sql); + + for (U32 i = 0; i < sql.mList.size(); i++) + { + WaterBlock* pBlock = dynamic_cast(sql.mList[i]); + if (pBlock && pBlock->isPointSubmergedSimple( point )) + { + if(pBlock->isLava(pBlock->getLiquidType())) + return false; + + if( waterHeight ) + { + *waterHeight = pBlock->getSurfaceHeight(); + } + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------- +void Projectile::createSplash( Point3F &pos ) +{ + if( mDataBlock->splash && !mPlayedSplash ) + { + 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; + + mPlayedSplash = true; + } + +} diff --git a/game/projectile.h b/game/projectile.h new file mode 100644 index 0000000..8b1c8f4 --- /dev/null +++ b/game/projectile.h @@ -0,0 +1,235 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROJECTILE_H_ +#define _PROJECTILE_H_ + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "scenegraph/lightManager.h" +#endif +#ifndef _PLATFORMAUDIO_H_ +#include "platform/platformAudio.h" +#endif + +class ParticleEmitterData; +class ParticleEmitter; +class ExplosionData; +class ShapeBase; +class TSShapeInstance; +class TSThread; +class DecalData; +class SplashData; + +//-------------------------------------------------------------------------- +class ProjectileData : public GameBaseData +{ + typedef GameBaseData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + // Shape related + const char* projectileShapeName; + S32 emitterDelay; + + DecalData* decalData[6]; + S32 decalID[6]; + U32 numDecals; + + // Physics related + F32 velInheritFactor; + bool isBallistic; + + // Damage related + F32 directDamage; + bool hasDamageRadius; + F32 indirectDamage; + F32 damageRadius; + S32 radiusDamageType; + + F32 kickBackStrength; + + bool hasLight; + F32 lightRadius; + ColorF lightColor; + + bool hasLightUnderwaterColor; + ColorF underWaterLightColor; + + bool explodeOnWaterImpact; + F32 bubbleEmitTime; + + bool faceViewer; + Point3F scale; + + F32 depthTolerance; + + // Particle emission + ParticleEmitterData* baseEmitter; + ParticleEmitterData* delayEmitter; + ParticleEmitterData* bubbleEmitter; + ExplosionData* explosion; + ExplosionData* underwaterExplosion; + SplashData* splash; + AudioProfile* sound; + AudioProfile* wetFireSound; + AudioProfile* fireSound; + + S32 baseEmitterId; + S32 delayEmitterId; + S32 bubbleEmitterId; + S32 explosionId; + S32 underwaterExplosionId; + S32 splashId; + S32 soundId; + S32 wetFireSoundId; + S32 fireSoundId; + + //-------------------------------------- load() set variables + public: + Resource projectileShape; + S32 ambientSeq; + + public: + ProjectileData(); + ~ProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + virtual bool calculateAim(const Point3F& targetPos, + const Point3F& targetVel, + const Point3F& sourcePos, + const Point3F& sourceVel, + Point3F* outputVectorMin, + F32* outputMinTime, + Point3F* outputVectorMax, + F32* outputMaxTime); + + static void initPersistFields(); + DECLARE_CONOBJECT(ProjectileData); +}; + + +//-------------------------------------------------------------------------- +class Projectile : public GameBase +{ + typedef GameBase Parent; + + protected: + enum ProjectileConstants { + SourceIdTimeoutTicks = 7, // = 231 ms + DeleteWaitTicks = 13, // = 416 ms + ExcessVelDirBits = 7 + }; + + private: + ProjectileData* mDataBlock; + + // Initial conditions + protected: + Point3F mInitialPosition; + Point3F mInitialDirection; + S32 mSourceObjectId; + S32 mVehicleObjectId; + S32 mSourceObjectSlot; + + // Time related variables common to all projectiles, managed by processTick + protected: + U32 mCurrTick; // Current time in ticks + + U32 mSourceIdTimeoutTicks; + SimObjectPtr mSourceObject; // Actual pointer to the source object, times out + // after SourceIdTimeoutTicks + SimObjectPtr mVehicleObject; // Actual pointer to player mounted to vehicle + + // Rendering related variables + protected: + TSShapeInstance* mProjectileShape; + TSThread* mAmbientThread; + + ParticleEmitter* mBaseEmitter; + ParticleEmitter* mDelayEmitter; + ParticleEmitter* mBubbleEmitter; + + AUDIOHANDLE mProjectileSound; + + bool mUseUnderwaterLight; + + void registerLights(LightManager * lightManager, bool lightingScene); + LightInfo mLight; + + void emitParticles(const Point3F&, const Point3F&, const Point3F&, const U32); + void updateSound(const Point3F& pos, const Point3F& vel, const bool active); + + bool mHidden; // set by the derived class, if true, projectile doesn't render + F32 mFadeValue; // " " , controls alpha fade out + bool mPlayedSplash; + F32 mBubbleEmitterTime; // time since projectile entered water + Point3F mLastPos; + + // A note about these variables. The server has the fully precise version + // of mExcessDir, the client has a "dumbed down" version, compressed for + // network space reasons. The server must dumb down its vector before using + // it for calculation... + Point3F mExcessDir; // Normalized vel, transmitted as 7 bit compressed + Point3F mExcessDirDumb; // Dumb version of the above + U32 mExcessVel; // Excess velocity inherited from firing object, ranges + // from 0 to 255, in 1 m/s increments + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + + void processTick(const Move*); + void advanceTime(F32); + + virtual void onCollision(const Point3F& p, const Point3F& n, SceneObject*); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void prepModelView(SceneState*); + void renderObject(SceneState*, SceneRenderImage*); + bool pointInWater( Point3F &point, F32 *waterHeight = NULL ); + void createSplash( Point3F &pos ); + + public: + F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips); + + Projectile(); + ~Projectile(); + + DECLARE_CONOBJECT(Projectile); + static void initPersistFields(); + static void consoleInit(); + + virtual bool calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime); + + public: + static U32 smProjectileWarpTicks; + + protected: + static const U32 csmStaticCollisionMask; + static const U32 csmDynamicCollisionMask; + static const U32 csmDamageableMask; +}; + +#endif // _H_PROJECTILE + diff --git a/game/resource.h b/game/resource.h new file mode 100644 index 0000000..e2daa9a --- /dev/null +++ b/game/resource.h @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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/game/rigid.cc b/game/rigid.cc new file mode 100644 index 0000000..939cd09 --- /dev/null +++ b/game/rigid.cc @@ -0,0 +1,262 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/rigid.h" +#include "console/console.h" + + +//---------------------------------------------------------------------------- + +Rigid::Rigid() +{ + state.force = state.torque = + state.linVelocity = + state.linPosition = + state.linMomentum = + state.angVelocity = + state.angMomentum = Point3F(0,0,0); + state.angPosition.identity(); + state.invWorldInertia.identity(); + + mass = oneOverMass = 1.0; + invObjectInertia.identity(); + restitution = 0.3; + friction = 0.5; + angDamping = 0; + linDamping = 0; + atRest = false; + microCount = 0; +} + +void Rigid::clearForces() +{ + state.force.set(0,0,0); + state.torque.set(0,0,0); +} + +void Rigid::integrate(State& t,F32 delta) +{ + lastDelta = delta; + + t.linPosition += t.linVelocity * delta; + t.linMomentum += t.force * delta; + t.linVelocity = t.linMomentum * oneOverMass; + +// // Linear momentum and velocity +// t.linPosition = state.linPosition + (state.linVelocity * delta); +// t.linMomentum = state.linMomentum + (state.force * delta); +// t.linVelocity = t.linMomentum * oneOverMass; + + // Angular position is average velocity * delta + F32 sinHalfAngle; + F32 angle = state.angVelocity.len(); + if (angle != 0.0f) { + QuatF dq; + mSinCos(angle * delta * -0.5, + sinHalfAngle, + dq.w); + sinHalfAngle *= 1 / angle; + dq.x = t.angVelocity.x * sinHalfAngle; + dq.y = t.angVelocity.y * sinHalfAngle; + dq.z = t.angVelocity.z * sinHalfAngle; + QuatF temp = t.angPosition; + t.angPosition.mul(temp, dq); + t.angPosition.normalize(); + } + else + { + //t.angPosition = state.angPosition; + } + + t.angMomentum += t.torque * delta; + + // Move angular momentum into world space + MatrixF iv,qmat; + t.angPosition.setMatrix(&qmat); + iv.mul(qmat,invObjectInertia); + qmat.transpose(); + t.invWorldInertia.mul(iv,qmat); + t.invWorldInertia.mulV(t.angMomentum, &t.angVelocity); +} + +void Rigid::updateVelocity(State& t) +{ + t.linVelocity.x = t.linMomentum.x * oneOverMass; + t.linVelocity.y = t.linMomentum.y * oneOverMass; + t.linVelocity.z = t.linMomentum.z * oneOverMass; + t.invWorldInertia.mulV(t.angMomentum,&t.angVelocity); +} + +void Rigid::applyImpulse(State& t, const Point3F &r, const Point3F &impulse) +{ + atRest = false; + + // Linear momentum and velocity + t.linMomentum += impulse; + t.linVelocity.x = t.linMomentum.x * oneOverMass; + t.linVelocity.y = t.linMomentum.y * oneOverMass; + t.linVelocity.z = t.linMomentum.z * oneOverMass; + + // Rotational momentum and velocity + Point3F tv; + mCross(r,impulse,&tv); + t.angMomentum += tv; + t.invWorldInertia.mulV(t.angMomentum, &t.angVelocity); +} + +// bool Rigid::resolveCollision(State& s, const Point3F& p, Point3F normal, Rigid* rigid) +// { +// atRest = false; +// Point3F v1,v2,r1,r2; +// r1 = p - s.linPosition; +// s.getVelocity(r1,&v1); +// r2 = p - rigid->state.linPosition; +// rigid->getVelocity(r2,&v2); + +// // Make sure they are converging +// F32 nv = mDot(v1,normal); +// nv -= mDot(v2,normal); +// if (nv > 0) +// return false; + +// // Compute impulse +// F32 d, n = -nv * (1 + restitution) * rigid->restitution; +// Point3F a1,b1,c1; +// mCross(r1,normal,&a1); +// s.invWorldInertia.mulV(a1,&b1); +// mCross(b1,r1,&c1); + +// Point3F a2,b2,c2; +// mCross(r2,normal,&a2); +// rigid->state.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(s, r1,impulse); +// impulse.neg(); +// rigid->applyImpulse(r2,impulse); +// return true; +// } + +bool Rigid::resolveCollision(State& s, const Point3F& p, Point3F normal) +{ + atRest = false; + Point3F v1,r1 = p - s.linPosition; + s.getVelocity(r1,&v1); + F32 n = -mDot(v1,normal); + if (n >= 0) { + + // Collision impulse + F32 d = getZeroImpulse(s,r1,normal); + F32 j = n * (1 + restitution) * d; + Point3F impulse = normal * j; + + // Friction impulse + Point3F uv = v1 + (normal * n); + F32 ul = uv.len(); + if (ul) { + uv /= -ul; + F32 u = n * d * friction * getZeroImpulse(s,r1,uv); + impulse += uv * u; + } + + // + applyImpulse(s, r1,impulse); + } + return true; +} + + +F32 Rigid::getZeroImpulse(State& s,const Point3F& r,const Point3F& normal) +{ + Point3F a,b,c; + mCross(r,normal,&a); + s.invWorldInertia.mulV(a,&b); + mCross(b,r,&c); + return 1 / (oneOverMass + mDot(c,normal)); +} + +void Rigid::getVelocity(const Point3F &r, Point3F *v) +{ + state.getVelocity(r, v); +} + +F32 Rigid::getKineticEnergy(const MatrixF& wto) +{ + Point3F w; + wto.mulV(state.angVelocity,&w); + const F32* f = invObjectInertia; + return 0.5 * ((mass * mDot(state.linVelocity,state.linVelocity)) + + w.x * w.x / f[0] + + w.y * w.y / f[5] + + w.z * w.z / f[10]); +} + +void Rigid::State::getVelocity(const Point3F& r, Point3F* v) +{ + mCross(angVelocity, r, v); + *v += linVelocity; +} + +void Rigid::State::getTransform(MatrixF* mat) +{ + angPosition.setMatrix(mat); + mat->setColumn(3,linPosition); +} + +void Rigid::State::setTransform(const MatrixF& mat) +{ + angPosition.set(mat); + mat.getColumn(3,&linPosition); +} + + +void Rigid::setObjectInertia(const Point3F& r) +{ + // Rotational moment of inertia of a box + F32 ot = mass/12; + F32 a = r.x * r.x; + F32 b = r.y * r.y; + F32 c = r.z * r.z; + + F32* f = invObjectInertia; + invObjectInertia.identity(); +// f[0] = 1 / (ot * (b + c)); +// f[5] = 1 / (ot * (c + a)); +// f[10] = 1 / (ot * (a + b)); + + MatrixF iv,qmat; + state.angPosition.setMatrix(&qmat); + iv.mul(qmat,invObjectInertia); + qmat.transpose(); + state.invWorldInertia.mul(iv,qmat); +} + + +//---------------------------------------------------------------------------- + +bool Rigid::checkRestCondition() +{ + if (state.linMomentum.len() < 0.1f && + state.angMomentum.len() < 0.05f) { + setAtRest(); + } + return atRest; +} + +void Rigid::setAtRest() +{ + atRest = true; + state.linVelocity = + state.linMomentum = + state.angVelocity = + state.angMomentum = + Point3F(0,0,0); +} diff --git a/game/rigid.h b/game/rigid.h new file mode 100644 index 0000000..a91c07d --- /dev/null +++ b/game/rigid.h @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RIGID_H_ +#define _RIGID_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MMATRIX_H_ +#include "Math/mMatrix.h" +#endif +#ifndef _MQUAT_H_ +#include "Math/mQuat.h" +#endif + +//---------------------------------------------------------------------------- + +class Rigid +{ +public: + struct Impulse { + Point3F force; + Point3F torque; + }; + struct State { + Point3F force; + Point3F torque; + + Point3F linVelocity; + Point3F linPosition; + Point3F linMomentum; + Point3F angVelocity; + QuatF angPosition; + Point3F angMomentum; + + MatrixF invWorldInertia; + + void getVelocity(const Point3F &r,Point3F* v); + void getTransform(MatrixF* mat); + void setTransform(const MatrixF& mat); + }; + struct Coeficients { + F32 e; + F32 u; + }; + + State state; + MatrixF invObjectInertia; + F32 oneOverMass; + F32 mass; + F32 restitution; + F32 friction; + F32 angDamping; + F32 linDamping; + + int microCount; + F32 lastDelta; + bool atRest; + + + Rigid(); + void clearForces(); + + void updateVelocity(State& t); + void integrate(State& t,F32 delta); + void applyImpulse(State& t, const Point3F &v,const Point3F &impulse); +// bool resolveCollision(State&,const Point3F& p,Point3F normal,Rigid*); + bool resolveCollision(State&,const Point3F& p,Point3F normal); + void getVelocity(const Point3F &r, Point3F* v); + F32 getZeroImpulse(State& t,const Point3F& r,const Point3F& normal); + void setObjectInertia(const Point3F& r); + F32 getKineticEnergy(const MatrixF& wto); + + bool checkRestCondition(); + void setAtRest(); +}; + + +#endif diff --git a/game/scopeAlwaysShape.cc b/game/scopeAlwaysShape.cc new file mode 100644 index 0000000..58b527a --- /dev/null +++ b/game/scopeAlwaysShape.cc @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/staticShape.h" +#include "game/gameConnection.h" + +class ScopeAlwaysShape : public StaticShape +{ + typedef StaticShape Parent; + + public: + ScopeAlwaysShape(); + static void initPersistFields(); + DECLARE_CONOBJECT(ScopeAlwaysShape); +}; + +ScopeAlwaysShape::ScopeAlwaysShape() +{ + mNetFlags.set(Ghostable|ScopeAlways); +} + +void ScopeAlwaysShape::initPersistFields() +{ + Parent::initPersistFields(); +} + +IMPLEMENT_CO_NETOBJECT_V1(ScopeAlwaysShape); diff --git a/game/sensor.cc b/game/sensor.cc new file mode 100644 index 0000000..b25e799 --- /dev/null +++ b/game/sensor.cc @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "game/gameBase.h" +#include "console/consoleTypes.h" +#include "console/consoleInternal.h" +#include "core/bitStream.h" +#include "sim/netConnection.h" +#include "game/targetManager.h" +#include "game/sensor.h" + +IMPLEMENT_CO_DATABLOCK_V1(SensorData); + + +SensorData::SensorData() +{ + detects = true; + detectsUsingLOS = true; + detectsPassiveJammed = false; + detectsActiveJammed = false; + detectsCloaked = false; + detectionPings = true; + detectMinVelocity = 0; + detectRadius = 250; + detectsFOVOnly = false; + detectFOV = 90.0; + jams = false; + jamsOnlyGroup = false; + jamsUsingLOS = false; + jamRadius = 0; + detectFOVPercent = 0.f; + useObjectFOV = false; +} + +bool SensorData::onAdd() +{ + if(!Parent::onAdd()) + return false; + halfFovCos = mCos(mDegToRad(detectFOV / 2.f)); + detectMinVSquared = detectMinVelocity * detectMinVelocity; + jamRSquared = jamRadius * jamRadius; + detectRSquared = detectRadius * detectRadius; + return true; +} + +void SensorData::initPersistFields() +{ + Parent::initPersistFields(); + addField("detects", TypeBool, Offset(detects, SensorData)); + addField("detectsUsingLOS", TypeBool, Offset(detectsUsingLOS, SensorData)); + addField("detectsActiveJammed", TypeBool, Offset(detectsActiveJammed, SensorData)); + addField("detectsPassiveJammed", TypeBool, Offset(detectsPassiveJammed, SensorData)); + addField("detectsCloaked", TypeBool, Offset(detectsCloaked, SensorData)); + addField("detectionPings", TypeBool, Offset(detectionPings, SensorData)); + addField("detectsFOVOnly", TypeBool, Offset(detectsFOVOnly, SensorData)); + addField("jams", TypeBool, Offset(jams, SensorData)); + addField("jamsOnlyGroup", TypeBool, Offset(jamsOnlyGroup, SensorData)); + addField("jamsUsingLOS", TypeBool, Offset(jamsUsingLOS, SensorData)); + addField("jamRadius", TypeF32, Offset(jamRadius, SensorData)); + addField("detectFOV", TypeF32, Offset(detectFOV, SensorData)); + addField("detectRadius", TypeF32, Offset(detectRadius, SensorData)); + addField("detectMinVelocity", TypeF32, Offset(detectMinVelocity, SensorData)); + addField("detectFOVPercent", TypeF32, Offset(detectFOVPercent, SensorData)); + addField("useObjectFOV", TypeBool, Offset(useObjectFOV, SensorData)); +} + +void SensorData::packData(BitStream* /*stream*/) +{ +} + +void SensorData::unpackData(BitStream* /*stream*/) +{ +} + + diff --git a/game/sensor.h b/game/sensor.h new file mode 100644 index 0000000..dc0e6e9 --- /dev/null +++ b/game/sensor.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SENSOR_H_ +#define _SENSOR_H_ + +struct SensorData : public SimDataBlock { + private: + typedef SimDataBlock Parent; + + public: + bool detects; + bool detectsUsingLOS; + bool detectsPassiveJammed; + bool detectsActiveJammed; + bool detectsCloaked; + bool detectionPings; + bool detectsFOVOnly; + F32 detectMinVelocity; + F32 detectMinVSquared; + F32 detectRadius; + F32 detectRSquared; + F32 detectFOV; + F32 halfFovCos; + + bool jams; + bool jamsOnlyGroup; + bool jamsUsingLOS; + F32 jamRadius; + F32 jamRSquared; + + F32 detectFOVPercent; + bool useObjectFOV; + + SensorData(); + DECLARE_CONOBJECT(SensorData); + static void initPersistFields(); + bool onAdd(); + void packData(BitStream* stream); + void unpackData(BitStream* stream); +}; + +#endif diff --git a/game/serverQuery.cc b/game/serverQuery.cc new file mode 100644 index 0000000..89c191b --- /dev/null +++ b/game/serverQuery.cc @@ -0,0 +1,1767 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "core/tVector.h" +#include "core/resManager.h" +#include "core/bitStream.h" +#include "platform/event.h" +#include "console/console.h" +#include "game/serverQuery.h" +#include "console/simBase.h" +#include "game/netDispatch.h" +#include "game/banList.h" +#include "game/version.h" +#include "game/auth.h" + +// This is basically the server query protocol version now: +static const char* versionString = "VER5"; +static const U32 HeartbeatInterval = 120000; + +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 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; + +static const U8 ExtendedFlag = 128; + +// 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; + +struct Ping +{ + NetAddress address; + S32 session; + S32 key; + U32 time; + U32 tryCount; + + enum + { + OnlineQuery = 0, + OfflineQuery = BIT(0), + NoStringCompress = BIT(1), + }; +}; + +static Ping gMasterServerPing; +static Vector gPingList(__FILE__, __LINE__); +static Vector gQueryList(__FILE__, __LINE__); + +extern bool gAllowConnections; + +struct PacketStatus +{ + U8 index; + S32 key; + U32 time; + U32 tryCount; + + PacketStatus( U8 _index, S32 _key, U32 _time ) + { + index = _index; + key = _key; + time = _time; + tryCount = gPacketRetryCount; + } +}; + +static Vector gPacketStatusList(__FILE__, __LINE__); + +static U8 sendPacketData[MaxPacketDataSize]; + +struct ServerFilter +{ + enum Type + { + Normal = 0, + Buddy = 1, + Offline = 2, + Favorites = 3, + }; + + Type type; + char* rulesSet; + char* missionType; + + // Filter flags: + enum + { + Dedicated = BIT(0), + NotPassworded = BIT(1), + Linux = BIT(2), + CurrentVersion = BIT(7), + }; + + U8 flags; + U8 minPlayers; + U8 maxPlayers; + U8 maxBots; + U32 regionMask; + U32 maxPing; + U8 filterFlags; + U16 minCPU; + U8 buddyCount; + U32* buddyList; + + ServerFilter() + { + rulesSet = NULL; + missionType = NULL; + flags = 0; + minPlayers = 0; + maxPlayers = 255; + maxBots = 16; + regionMask = 0xFFFFFFFF; + maxPing = 0; + filterFlags = 0; + minCPU = 0; + buddyCount = 0; + buddyList = NULL; + } + + ~ServerFilter() + { + if ( rulesSet ) + dFree( rulesSet ); + 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 ); +static void processServerListPackets( U32 session ); +static void processHeartbeat(U32); +static void updatePingProgress(); +static void updateQueryProgress(); + +//---------------------------------------------------------------- +class ProcessMasterQueryEvent : public SimEvent +{ + U32 session; + public: + ProcessMasterQueryEvent( U32 _session ) + { + session = _session; + } + void process( SimObject* ) + { + processMasterServerQuery( session ); + } +}; + +//---------------------------------------------------------------- +class ProcessPingEvent : public SimEvent +{ + U32 session; + public: + ProcessPingEvent( U32 _session ) + { + session = _session; + } + void process( SimObject* ) + { + processPingsAndQueries( session ); + } +}; + +//---------------------------------------------------------------- +class ProcessPacketEvent : public SimEvent +{ + U32 session; + public: + ProcessPacketEvent( U32 _session ) + { + session = _session; + } + + void process( SimObject* ) + { + processServerListPackets( session ); + } +}; + +//---------------------------------------------------------------- +class HeartbeatEvent : public SimEvent +{ + U32 mSeq; + public: + HeartbeatEvent(U32 seq) + { + mSeq = seq; + } + void process( SimObject* ) + { + processHeartbeat(mSeq); + } +}; + +//---------------------------------------------------------------- +//---------------------------------------------------------------- +ServerInfo::~ServerInfo() +{ + if ( name ) + dFree( name ); + if ( gameType ) + dFree( gameType ); + if ( missionType ) + dFree( missionType ); + if ( infoString ) + dFree( infoString ); + if ( contentString ) + dFree( contentString ); +} + +//---------------------------------------------------------------- +//---------------------------------------------------------------- + +Vector* getMasterServerList() +{ + // Master servers from somewhere (used to be WON.net) + // currently return an empty list until we rewrite the server stuff + static Vector stub_list; + return &stub_list; +} + + +//---------------------------------------------------------------- +void clearServerList() +{ + gPacketStatusList.clear(); + gServerList.clear(); + gFinishedList.clear(); + gPingList.clear(); + gQueryList.clear(); + gServerPingCount = gServerQueryCount = 0; + + gPingSession++; +} + +//---------------------------------------------------------------- +void queryLanServers( U32 port, U8 /*flags*/, bool offline ) +{ + sgServerQueryActive = true; + clearServerList(); + pushServerFavorites(); + + if ( offline ) + { + sActiveFilter.type = ServerFilter::Offline; + + // Clear the filter: + if ( !sActiveFilter.rulesSet || dStricmp( sActiveFilter.rulesSet, "Any" ) != 0 ) + { + sActiveFilter.rulesSet = (char*) dRealloc( sActiveFilter.rulesSet, 4 ); + dStrcpy( sActiveFilter.rulesSet, "Any" ); + } + if ( !sActiveFilter.missionType || dStricmp( sActiveFilter.missionType, "Any" ) != 0 ) + { + sActiveFilter.missionType = (char*) dRealloc( sActiveFilter.missionType, 4 ); + dStrcpy( sActiveFilter.missionType, "Any" ); + } + sActiveFilter.flags = 0; + sActiveFilter.minPlayers = 0; + sActiveFilter.maxPlayers = 255; + sActiveFilter.maxBots = 16; + sActiveFilter.regionMask = 0xFFFFFFFF; + sActiveFilter.maxPing = 0; + sActiveFilter.minCPU = 0; + sActiveFilter.filterFlags = 0; + sActiveFilter.buddyCount = 0; + if ( sActiveFilter.buddyList ) + { + dFree( sActiveFilter.buddyList ); + sActiveFilter.buddyList = NULL; + } + } + + NetAddress addr; + char addrText[256]; + dSprintf( addrText, sizeof( addrText ), "IP:BROADCAST:%d", port ); + Net::stringToAddress( addrText, &addr ); + pushPingBroadcast( &addr ); + dSprintf( addrText, sizeof( addrText ), "IPX:BROADCAST:%d", port ); + Net::stringToAddress( addrText, &addr ); + pushPingBroadcast( &addr ); + + processPingsAndQueries( gPingSession ); +} + +//---------------------------------------------------------------- +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( MasterServerGameTypesRequest, &(*masterList)[master].address, gKey, gPingSession, 0 ); + } +} + +//---------------------------------------------------------------- +bool pickMasterServer() +{ + // Reset the master server ping: + gMasterServerPing.time = 0; + gMasterServerPing.key = 0; + gMasterServerPing.tryCount = gMasterServerRetryCount; + gMasterServerPing.session = gPingSession; + + char addrString[256]; + const char* regionString = NULL; + 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 queryMasterServer( + U32 lanPort, + U8 flags, + const char* rulesSet, + 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; + + // Don't query LAN servers if this is a buddy search: + if ( buddyCount == 0 ) + { + sActiveFilter.type = ServerFilter::Normal; + + // Update the active filter: + if ( !sActiveFilter.rulesSet || dStrcmp( sActiveFilter.rulesSet, rulesSet ) != 0 ) + { + sActiveFilter.rulesSet = (char*) dRealloc( sActiveFilter.rulesSet, dStrlen( rulesSet ) + 1 ); + dStrcpy( sActiveFilter.rulesSet, rulesSet ); + } + + if ( !sActiveFilter.missionType || dStrcmp( sActiveFilter.missionType, missionType ) != 0 ) + { + sActiveFilter.missionType = (char*) dRealloc( sActiveFilter.missionType, dStrlen( missionType ) + 1 ); + dStrcpy( sActiveFilter.missionType, missionType ); + } + + sActiveFilter.flags = 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; + + // The queryLanServers function clears the server lists and + // pushes the server favorites. + queryLanServers(lanPort, 0, false); + } + else + { + sActiveFilter.type = ServerFilter::Buddy; + sActiveFilter.buddyCount = buddyCount; + sActiveFilter.buddyList = (U32*) dRealloc( sActiveFilter.buddyList, buddyCount * 4 ); + dMemcpy( sActiveFilter.buddyList, buddyList, buddyCount * 4 ); + clearServerList(); + } + + //Con::errorf( "** queryMasterServer( %s, %s, %d, %d, %d, %d, %d ) **", rulesSet, missionType, minPlayers, maxPlayers, regionMask, maxPing, buddyCount ); + + // 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 ); +} + +//---------------------------------------------------------------- +void queryFavoriteServers( U8 /*flags*/ ) +{ + sgServerQueryActive = true; + clearServerList(); + sActiveFilter.type = ServerFilter::Favorites; + pushServerFavorites(); + 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( 3, "updateServerBrowserStatus", "Refreshing server...", "0" ); + gServerPingCount = gServerQueryCount = 0; + pushPingRequest( addr ); + processPingsAndQueries( gPingSession ); +} + +//---------------------------------------------------------------- +void cancelServerQuery() +{ + 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()" ) +{ + argc; argv; + cancelServerQuery(); +} + +//---------------------------------------------------------------- +void stopServerQuery() +{ + 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()" ) +{ + argc; argv; + stopServerQuery(); +} + +//---------------------------------------------------------------- +void startHeartbeat() +{ + gHeartbeatSeq++; + processHeartbeat(gHeartbeatSeq); // thump-thump... +} + +ConsoleFunction(stopHeartbeat, void, 1, 1, "stopHeartbeat();") +{ + argc; argv; + gHeartbeatSeq++; +} + +//---------------------------------------------------------------- +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( 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; + 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 + gPingList.push_back( p ); + gServerPingCount++; // Just to keep things looking right... +} + +//---------------------------------------------------------------- +static void pushServerFavorites() +{ + S32 count = Con::getIntVariable( "$pref::ServerBrowser::FavoriteCount" ); + if ( count < 0 ) + { + Con::setIntVariable( "$pref::ServerBrowser::FavoriteCount", 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::ServerBrowser::Favorite%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( bool( 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; + } + } +} + +//---------------------------------------------------------------- +#ifdef 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 = Platform::getRandom() * 64; + 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 = ( Platform::getRandom() * 200 ); + 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->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; +} + +//---------------------------------------------------------------- +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( 3, "updateServerBrowserStatus", "Switching master servers...", "-1" ); + 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->write( U8( MasterServerListRequest ) ); + out->write( U8( sActiveFilter.flags | ExtendedFlag ) ); + out->write( ( gMasterServerPing.session << 16 ) | ( gMasterServerPing.key & 0xFFFF ) ); + out->write( U8( 255 ) ); + writeCString( out, sActiveFilter.rulesSet ); + 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( 3, "updateServerBrowserStatus", "Retrying the master server...", "-1" ); + } + } + + 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( 3, "updateServerBrowserStatus", "No master servers found.", "0" ); + } + } +} + +//---------------------------------------------------------------- +static void processPingsAndQueries( U32 session ) +{ + if( session != gPingSession ) + return; + + U32 i = 0; + U32 activePings = 0; + U32 time = Platform::getVirtualMilliseconds(); + char addressString[256]; + U8 flags = isClientOnline() ? Ping::OnlineQuery : Ping::OfflineQuery; + 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. + 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 ); + + Con::printf( "Pinging Server %s (%d)...", addressString, p.tryCount ); + sendPacket( 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( 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 ) + 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( 3, "updateServerBrowserStatus", msg, "0" ); + } +} + +//---------------------------------------------------------------- +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->write( U8( MasterServerListRequest ) ); + out->write( U8( sActiveFilter.flags | ExtendedFlag ) ); // 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; + + const char* masterPref = Con::getVariable( "$pref::Net::DisplayOnMaster" ); + if ( dStricmp( masterPref, "NotFull" ) != 0 + || Con::getIntVariable( "$HostGamePlayerCount" ) < Con::getIntVariable( "$Host::MaxPlayers" ) ) + sendHeartbeat( 0 ); + Sim::postEvent( Sim::getRootGroup(), new HeartbeatEvent(seq), Sim::getCurrentTime() + HeartbeatInterval ); +} + +//---------------------------------------------------------------- +static void updatePingProgress() +{ + if ( !gPingList.size() ) + { + updateQueryProgress(); + return; + } + + char msg[64]; + U32 pingsLeft = gPingList.size(); + dSprintf( msg, sizeof( msg ), "Pinging servers: %d left...", pingsLeft ); + 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( 3, "updateServerBrowserStatus", 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 ); + 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( 3, "updateServerBrowserStatus", 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(1, "clearGameTypes"); + for ( i = 0; i < U32( temp ); i++ ) + { + readCString( stream, stringBuf ); + Con::executef(2, "addGameType", stringBuf); + } + + stream->read( &temp ); + Con::executef(1, "clearMissionTypes"); + for ( i = 0; i < U32( temp ); i++ ) + { + readCString( stream, stringBuf ); + Con::executef(2, "addMissionType", 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 ( gAllowConnections ) + { + U8 temp8; + U32 temp32; + + Con::printf( "Received info request from the master server." ); + + BitStream *out = BitStream::getPacketStream(); + + out->write( U8( GameMasterInfoResponse ) ); + out->write( U8( flags | ExtendedFlag ) ); + out->write( key ); + + const char* paths = ResourceManager->getModPaths(); + const char* ptr = dStrstr( paths, ";base" ); + if ( ptr ) + { + // Strip the "base" from the string: + char* temp = new char[dStrlen( paths ) + 1]; + dStrncpy( temp, paths, ptr - paths ); + dStrcpy( temp + ( ptr - paths ), ptr + 5 ); + writeCString( out, temp ); + delete [] temp; + } + else + writeCString( out, paths ); + writeCString( out, Con::getVariable( "$MissionTypeDisplayName" ) ); + U8 playerCount = U8( Con::getIntVariable( "$HostGamePlayerCount" ) ); + out->write( playerCount ); + temp8 = U8( Con::getIntVariable( "$Host::MaxPlayers" ) ); + out->write( temp8 ); + temp32 = Con::getIntVariable( "$pref::Net::RegionMask" ); + out->write( temp32 ); + temp32 = getVersionNumber(); + out->write( temp32 ); + temp8 = 0; + if ( Con::getBoolVariable( "$Host::Dedicated" ) ) + temp8 |= ServerInfo::Status_Dedicated; + if ( dStrlen( Con::getVariable( "$Host::Password" ) ) > 0 ) + temp8 |= ServerInfo::Status_Passworded; +#ifdef __linux + temp8 |= ServerInfo::Status_Linux; +#endif + out->write( temp8 ); + temp8 = U8( Con::getIntVariable( "$HostGameBotCount" ) ); + out->write( temp8 ); + out->write( Platform::SystemInfo.processor.mhz ); + const char* guidList = Con::getVariable( "$HostGuidList" ); + char* buf = new char[dStrlen( guidList ) + 1]; + dStrcpy( buf, guidList ); + char* temp = dStrtok( buf, "\t" ); + temp8 = 0; + while ( temp ) + { + 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 ( gAllowConnections ) + { + // Do not respond if this is a single-player game: + if ( dStricmp( Con::getVariable( "$HostGameType" ), "SinglePlayer" ) == 0 ) + return; + + // Do not respond to offline queries if this is an online server: + if ( isServerOnline() && ( flags & Ping::OfflineQuery ) ) + return; + + // some banning code here (?) + + BitStream *out = BitStream::getPacketStream(); + + out->write( U8( GamePingResponse ) ); + out->write( flags ); + out->write( key ); + if ( flags & Ping::NoStringCompress ) + writeCString( out, versionString ); + else + out->writeString( versionString ); + out->write( CurrentProtocolVersion ); + out->write( MinRequiredProtocolVersion ); + out->write( getVersionNumber() ); + + // Enforce a 24-character limit on the server name: + char serverName[25]; + dStrncpy( serverName, Con::getVariable( "$ServerName" ), 24 ); + serverName[24] = 0; + if ( flags & Ping::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 ); + 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 < 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 ( 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.flags & 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 ( gAllowConnections ) + { + // Do not respond to offline queries if this is an online server: + if ( isServerOnline() && ( flags & Ping::OfflineQuery ) ) + return; + + bool compressStrings = !( flags & Ping::NoStringCompress ); + BitStream *out = BitStream::getPacketStream(); + + out->write( U8( GameInfoResponse ) ); + out->write( flags ); + out->write( key ); + + const char* paths = ResourceManager->getModPaths(); + const char* ptr = dStrstr( paths, ";base" ); + if ( ptr ) + { + // Strip the "base" from the string: + char* temp = new char[dStrlen( paths ) + 1]; + dStrncpy( temp, paths, ptr - paths ); + dStrcpy( temp + ( ptr - paths ), ptr + 5 ); + if ( compressStrings ) + out->writeString( temp ); + else + writeCString( out, temp ); + delete [] temp; + } + else if ( compressStrings ) + out->writeString( paths ); + else + writeCString( out, paths ); + + if ( compressStrings ) + { + out->writeString( Con::getVariable( "$MissionTypeDisplayName" ) ); + out->writeString( Con::getVariable( "$MissionDisplayName" ) ); + } + else + { + writeCString( out, Con::getVariable( "$MissionTypeDisplayName" ) ); + writeCString( out, Con::getVariable( "$MissionDisplayName" ) ); + } + + U8 status = 0; + if ( Con::getBoolVariable( "$Host::Dedicated" ) ) + status |= ServerInfo::Status_Dedicated; + if ( dStrlen( Con::getVariable( "$Host::Password" ) ) ) + status |= ServerInfo::Status_Passworded; + +#ifdef __linux + status |= ServerInfo::Status_Linux; +#endif + + if ( Con::getBoolVariable( "$Host::TournamentMode" ) ) + status |= ServerInfo::Status_Tournament; + if ( Con::getBoolVariable( "$Host::NoSmurfs" ) ) + status |= ServerInfo::Status_NoSmurfs; + out->write( status ); + out->write( U8( Con::getIntVariable( "$HostGamePlayerCount" ) ) ); + out->write( U8( Con::getIntVariable( "$Host::MaxPlayers" ) ) ); + out->write( U8( Con::getIntVariable( "$HostGameBotCount" ) ) ); + out->write( U16( Platform::SystemInfo.processor.mhz ) ); + if ( compressStrings ) + out->writeString( Con::getVariable( "$Host::Info" ) ); + else + writeCString( out, Con::getVariable( "$Host::Info" ) ); + writeLongCString( out, Con::evaluate( "getServerStatusString();" ) ); + + 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.rulesSet, "any" ) != 0 + && dStricmp( si->gameType, sActiveFilter.rulesSet ) != 0 ) + { + Con::printf( "Server %s filtered out by rules set. (%s:%s)", addrString, sActiveFilter.rulesSet, 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.flags & ServerFilter::Dedicated && !si->isDedicated() ) + { + Con::printf( "Server %s filtered out by dedicated flag.", addrString ); + removeServerInfo( address ); + return; + } + + if ( sActiveFilter.flags & 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->infoString || ( isUpdate && dStrcmp( si->infoString, 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->contentString || ( isUpdate && dStrcmp( si->contentString, stringBuf ) != 0 ) ) + { + si->contentString = (char*) dRealloc( (void*) si->contentString, dStrlen( stringBuf ) + 1 ); + dStrcpy( si->contentString, stringBuf ); + } + + // Update the server browser gui! + gServerBrowserDirty = true; +} + +//---------------------------------------------------------------- +//---------------------------------------------------------------- +void handleInfoPacket( const NetAddress* address, U8 packetType, BitStream* stream ) +{ + U8 flags; + U32 key; + + stream->read( &flags ); + stream->read( &key ); + + 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; + } +} + diff --git a/game/serverQuery.h b/game/serverQuery.h new file mode 100644 index 0000000..78c080e --- /dev/null +++ b/game/serverQuery.h @@ -0,0 +1,113 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SERVERQUERY_H_ +#define _SERVERQUERY_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif + +struct ServerInfo +{ + enum StatusFlags + { + // Info flags (0-7): + Status_Dedicated = BIT(0), + Status_Passworded = BIT(1), + Status_Linux = BIT(2), + Status_Tournament = BIT(3), + Status_NoSmurfs = BIT(4), + + // 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* missionType; + char* missionName; + char* infoString; + char* contentString; + 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; + infoString = NULL; + contentString = 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 isTournament() { return( status.test( Status_Tournament ) ); } + bool areSmurfsAllowed() { return( !status.test( Status_NoSmurfs ) ); } + bool isLinux() { return( status.test( Status_Linux ) ); } +}; + +extern Vector gServerList; +extern bool gServerBrowserDirty; +extern void clearServerList(); +extern void queryLanServers( U32 port, U8 flags, bool offline = true ); +extern void queryMasterGameTypes(); +extern void queryMasterServer( + U32 lanPort, + U8 flags, + const char* rulesSet, + 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 DEBUG +extern void addFakeServers( S32 howMany ); +#endif // DEBUG + +#endif diff --git a/game/shadow.cc b/game/shadow.cc new file mode 100644 index 0000000..f46402d --- /dev/null +++ b/game/shadow.cc @@ -0,0 +1,562 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/shadow.h" +#include "Sim/sceneObject.h" +#include "dgl/dgl.h" +#include "Core/fileStream.h" +#include "Core/bitRender.h" +#include "Platform/platform.h" + +DepthSortList Shadow::smDepthSortList; +TextureHandle* Shadow::smGenericShadowTexture = NULL; +S32 Shadow::smGenericShadowDim = 32; +S32 Shadow::smInstanceCount = 0; +U32 Shadow::smShadowMask = TerrainObjectType | InteriorObjectType; +F32 Shadow::smShapeDetailScale = 1.0f; +S32 Shadow::smShapeDetailMin = 2; // never select anything less than this detail unless other value supplied +F32 Shadow::smSmallestVisibleSize = 6.0f; // stop drawing shadows when less than 6 pixels in size +F32 Shadow::smLightDirScew = 0.0f; +F32 Shadow::smLightLenScew = 0.0f; +F32 Shadow::smGenericRadiusScew = 0.4f; // shrink radius of shape when it always uses generic shadow... +bool Shadow::smAlwaysUseGenericBmp = false; +F32 Shadow::smGlobalShadowDetail = 1.0f; + +Vector gShadowBits(__FILE__, __LINE__); +Box3F gShadowBox; +SphereF gShadowSphere; +Point3F gShadowPoly[4]; + +Shadow::DistanceDetail Shadow::smDefaultDistanceDetails[] = +{ + { 0.0f, 0.0f, 0.0f }, + { 100.0f, 0.0f, 0.0f }, + { 500.0f, 1.0f, 0.5f }, + { 1000.0f, 1.0f, 0.7f } +}; + +Shadow::PixelSizeDetail Shadow::smDefaultPixelSizeDetails[] = +{ + { 130.0f, 25, 64, 1, false }, + { 25.0f, 100, 64, 1, false }, + { 10.0f, 100, 32, 0, false }, + { 0.0f, 0, 0, 0, true } +}; + +void Shadow::setGlobalShadowDetailLevel(F32 d) +{ + smGlobalShadowDetail = d; + + smShapeDetailScale = 0.5f + d*0.5f; + smShapeDetailMin = d>0.49f ? 2 : 3; + smSmallestVisibleSize = d>0.7f ? 6 : (d>0.4f ? 7 : (d>0.3f ? 8 : (d>0.2f ? 9 : 10))); + smLightDirScew = 0.85f * (1.0f-d); + smLightLenScew = (1.0f-d) * 0.5f; +} + +//-------------------------------------------------------------- + +Shadow::Shadow() +{ + mBitmap = NULL; + mRadius = 0.0f; + mSettings.alwaysUseGenericBmp = false; + mSettings.noAnimate = false; + mSettings.noMove = false; + + setDefaultDetailTables(); + + if (smInstanceCount == 0) + { + GBitmap* bitmap = generateGenericShadowBitmap(smGenericShadowDim); + smGenericShadowTexture = new TextureHandle(NULL,bitmap); + // bitmap now owned by texture manager, so we don't delete it + } + smInstanceCount++; +} + +Shadow::~Shadow() +{ + AssertFatal(smInstanceCount > 0, "Error, more destructors than constructors?"); + smInstanceCount--; + if (smInstanceCount == 0) { + delete smGenericShadowTexture; + smGenericShadowTexture = NULL; + } +} + +GBitmap * Shadow::generateGenericShadowBitmap(S32 dim) +{ + GBitmap * bitmap = new GBitmap(dim,dim,false,GBitmap::Luminance); + U8 * bits = bitmap->getWritableBits(); + S32 center = dim >> 1; + F32 invRadiusSq = 1.0f / (F32)(center*center); + F32 tmpF; + for (S32 i=0; i0.99f ? 0 : 180 - 180.0f*tmpF); // 180 out of 255 max + } + return bitmap; +} + +//-------------------------------------------------------------- + +void Shadow::setDetailTables(const DistanceDetail * dd, S32 ddCount, const PixelSizeDetail * psd, S32 psdCount) +{ + mDistanceDetails = dd; + mPixelSizeDetails = psd; + mNumDistanceDetails = ddCount; + mNumPixelSizeDetails = psdCount; +} + +void Shadow::setDefaultDetailTables() +{ + setDetailTables(smDefaultDistanceDetails, + sizeof(smDefaultDistanceDetails)/sizeof(DistanceDetail), + smDefaultPixelSizeDetails, + sizeof(smDefaultPixelSizeDetails)/sizeof(PixelSizeDetail)); +} + +void Shadow::findDistanceDetail(F32 dist, DistanceDetail * dd) +{ + for (S32 i=0; idirectionScew = interp * mDistanceDetails[i-1].directionScew + (1.0f-interp) * mDistanceDetails[i].directionScew; + dd->lengthScew = interp * mDistanceDetails[i-1].lengthScew + (1.0f-interp) * mDistanceDetails[i].lengthScew; + } + return; + } + } + *dd = mDistanceDetails[mNumDistanceDetails-1]; +} + +void Shadow::findPixelSizeDetail(F32 pixelSize, const PixelSizeDetail ** psd) +{ + *psd = NULL; + for (S32 i=0; i=mPixelSizeDetails[i].size) + return; + } +} + +S32 Shadow::selectShapeDetail(TSShapeInstance * shapeInstance, F32 dist, F32 scale, S32 detailMin) +{ + if (detailMin<0) + detailMin = Shadow::smShapeDetailMin; + + S32 dl = shapeInstance->selectCurrentDetail( dglProjectRadius(dist,scale * shapeInstance->getShape()->radius * dglGetPixelScale() * TSShapeInstance::smDetailAdjust * Shadow::smShapeDetailScale)); + if (dl>=0 && dl < detailMin) + shapeInstance->setCurrentDetail(getMin(detailMin,shapeInstance->getShape()->mSmallestVisibleDL)); + return shapeInstance->getCurrentDetail(); +} + +//-------------------------------------------------------------- + +void Shadow::setLightMatrices(const Point3F & lightDir, const Point3F & pos) +{ + AssertFatal(mDot(lightDir,lightDir)>0.0001f,"Shadow::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 Shadow::setRadius(F32 radius) +{ + mRadius = radius; +} + +void Shadow::setRadius(TSShapeInstance * shapeInstance, const Point3F & scale) +{ + const Box3F & bounds = shapeInstance->getShape()->bounds; + F32 dx = 0.5f * (bounds.max.x-bounds.min.x) * scale.x; + F32 dy = 0.5f * (bounds.max.y-bounds.min.y) * scale.y; + F32 dz = 0.5f * (bounds.max.z-bounds.min.z) * scale.z; + mRadius = mSqrt(dx*dx+dy*dy+dz*dz); +} + +//-------------------------------------------------------------- + +void Shadow::beginRenderToBitmap() +{ + AssertFatal(mBitmap,"Shadow::beginRenderToBitmap"); + + // clean slate... + gShadowBits.setSize(mSettings.bmpDim * (mSettings.bmpDim>>5)); + dMemset(gShadowBits.address(),0,mSettings.bmpDim*(mSettings.bmpDim>>3)); // dMemset deals in bytes not words, hence the shift by 3 not 5 +} + +void Shadow::endRenderToBitmap() +{ + // copy to bitmap + if (mSettings.blur==1) + // blur + BitRender::bitTo8Bit_3(gShadowBits.address(),(U32*)mBitmap->getWritableBits(),mSettings.bmpDim); + else + // non-blur version: + BitRender::bitTo8Bit(gShadowBits.address(),(U32*)mBitmap->getWritableBits(),mSettings.bmpDim); + + mShadowTexture.refresh(); +} + +void Shadow::renderToBitmap(TSShapeInstance * shapeInstance, const MatrixF & transform, const Point3F & pos, Point3F scale) +{ + AssertFatal(mBitmap,"Shadow::renderToShadow: must call beginRenderToBitmap first"); + + MatrixF mat; + mat.mul(mWorldToLight,transform); + + // everything gets scaled by this amount so that we map onto the bitmap correctly + F32 k = ((F32)mSettings.bmpDim)/(2.0f*mRadius); + + // this scales everything but the last row of the matrix (we do that below) + scale *= k; + mat.scale(scale); + + // we want pos to map to bitmap center... + // the following is a bit convoluted...but it is correct... + Point3F p,p2; + mat.getColumn(3,&p); + mWorldToLight.mulP(pos,&p2); + p -= p2; + p *= k; + F32 halfDim = mSettings.bmpDim>>1; + p.x += halfDim; + p.z += halfDim; + mat.setColumn(3,p); // shape center now falls on bitmap center... + + shapeInstance->animate(); + shapeInstance->renderShadow(shapeInstance->getCurrentDetail(),mat,mSettings.bmpDim,gShadowBits.address()); +} + +//-------------------------------------------------------------- + +bool Shadow::prepare(const Point3F & pos, Point3F lightDir, F32 shadowLen, const Point3F & scale, F32 dist, F32 fogAmount, TSShapeInstance * shapeInstance) +{ + // 0. use pixel size to do early reject test + // 1. use distance from camera to do 1st pass detail selection (light direction, shadow volume distance) + // 2. get polys to project shadow onto, build shadow partition + // 3. use pixel size to do 2nd pass detail selection (shape detail level, bitmap dimension, blur routine, + // sample frequency, substitute generic shadow bitmap) + + // NOTES: + // If using cached partition, do 1&2 only if we don't have a partition yet... + // If non-animating shape, do step 3 and re-compute bitmap only if detail info changes... + + // -------------------------------------- + // 0. + F32 maxScale = getMax(scale.x,getMax(scale.y,scale.z)); + F32 pixelSize = dglProjectRadius(dist/maxScale,shapeInstance->getShape()->radius) * dglGetPixelScale() * TSShapeInstance::smDetailAdjust; + F32 smallest = getMax(Shadow::smSmallestVisibleSize,shapeInstance->getShape()->mSmallestVisibleSize); + if (pixelSize * Shadow::smShapeDetailScale < smallest) + return false; + + // fade over distance from viewer + F32 pseudoFog = smallest/(pixelSize * Shadow::smShapeDetailScale); + if (pseudoFog>0.5f) + pseudoFog = 2.0f * (pseudoFog-0.5f); + else + pseudoFog = 0.0f; + if (fogAmount=0.99f) + // shadow faded out + return false; + fogAmount = pseudoFog; + } + + // find detail information + DistanceDetail dd; + findDistanceDetail(mSettings.noMove ? 0.0f : dist,&dd); + const PixelSizeDetail * psd; + findPixelSizeDetail(pixelSize,&psd); + + if (!mSettings.noMove || mPartition.empty()) + { + // -------------------------------------- + // 1. + F32 dirMult = (1.0f - dd.directionScew) * (1.0f - smLightDirScew); + if (dirMult < 0.99f) + { + lightDir.z *= dirMult; + lightDir.z -= 1.0f - dirMult; + } + lightDir.normalize(); + shadowLen *= (1.0f - dd.lengthScew) * (1.0f - smLightLenScew); + + // -------------------------------------- + // 2. get polys + F32 radius = mRadius; + if (psd->genericShadowBmp || mSettings.alwaysUseGenericBmp || smAlwaysUseGenericBmp) + radius *= smGenericRadiusScew; + buildPartition(pos,lightDir,radius,shadowLen); + } + updatePartition(fogAmount); + if (mPartition.empty()) + // no need to draw shadow if nothing to cast it onto + return false; + + // -------------------------------------- + // 3. + // do we need a new bitmap? anim rate, bmp dim, generic vs generated + mSettings.needBmp = false; + if (mSettings.alwaysUseGenericBmp || smAlwaysUseGenericBmp || psd->genericShadowBmp) + // use generic bitmap -- get rid of old bmp if it's there + mBitmap = NULL; + else + { + U32 time = Platform::getVirtualMilliseconds(); + bool expired = time-mSettings.lastBmpTime > psd->frameExpiration; + bool propertyChange = !mBitmap || psd->bmpDim!=mSettings.bmpDim || psd->blur!=mSettings.blur; + if ( (expired && !mSettings.noAnimate) || propertyChange) + { + // need to generate a new bmp + mSettings.blur = psd->blur; + mSettings.lastBmpTime = Platform::getVirtualMilliseconds(); + mSettings.needBmp = true; + + if (mSettings.bmpDim!=psd->bmpDim || !mBitmap) + { + // allocate new bitmap...register texture (no need to delete old one, owned by texture handle) + mSettings.bmpDim = psd->bmpDim; + mBitmap = new GBitmap(mSettings.bmpDim,mSettings.bmpDim,false,GBitmap::Luminance); + mShadowTexture.set(NULL,mBitmap); + } + } + } + + return true; +} + +//-------------------------------------------------------------- + +void Shadow::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; + gShadowBox.max.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; + gShadowSphere.radius = gShadowBox.max.len(); + gShadowSphere.center = p + y; + gShadowBox.min = y + p - gShadowBox.max; + gShadowBox.max += y + p; + + // get polys + + gClientContainer.findObjects(smShadowMask,Shadow::collisionCallback,S32(this)); + + // setup partition list + gShadowPoly[0].set(-radius,0,-radius); + gShadowPoly[1].set(-radius,0, radius); + gShadowPoly[2].set( radius,0, radius); + gShadowPoly[3].set( radius,0,-radius); + + mPartition.clear(); + mPartitionVerts.clear(); + smDepthSortList.depthPartition(gShadowPoly,4,mPartition,mPartitionVerts); + + // now set up tverts & colors + mPartitionColors.setSize(mPartitionVerts.size()); + mPartitionTVerts.setSize(mPartitionVerts.size()); + F32 invRadius = 1.0f / radius; + for (S32 i=0; i(thisPtr); + if (obj->getWorldBox().isOverlapped(gShadowBox)) + obj->buildPolyList(&smDepthSortList,gShadowBox,gShadowSphere); +} + +//-------------------------------------------------------------- + +void Shadow::render() +{ + bool wasLit = glIsEnabled(GL_LIGHTING); + glDisable(GL_LIGHTING); + + // push light matrix + glPushMatrix(); + dglMultMatrix(&mLightToWorld); + + // set up texture environment + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + glBlendFunc(GL_ZERO,GL_ONE_MINUS_SRC_COLOR); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + if (mBitmap) + glBindTexture(GL_TEXTURE_2D, mShadowTexture.getGLName()); + else { + AssertFatal(smGenericShadowTexture != NULL, "Error, shadow texture not initialized!"); + glBindTexture(GL_TEXTURE_2D, smGenericShadowTexture->getGLName()); + } + + + // set up arrays + if( TSMesh::getOverrideFade() >= 1.0f ) + { + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4,GL_FLOAT,0,mPartitionColors.address()); + } + else + { + F32 color = TSMesh::getOverrideFade(); + glColor4f( color, color, color, color ); + } + + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3,GL_FLOAT,0,mPartitionVerts.address()); + glTexCoordPointer(2,GL_FLOAT,0,mPartitionTVerts.address()); + if (gGLState.suppLockedArrays) + glLockArraysEXT(0,mPartitionVerts.size()); + + // fight z-fighting + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-2,-2); + + // draw + for (S32 i=0; ivertexCount; j++) + { + Point3F p = smDepthSortList.mVertexList[smDepthSortList.mIndexList[poly->vertexStart+j]].point; + printDump(avar("(%5.3f, %5.3f, %5.3f) ",p.x,p.y,p.z)); + } + printDump("\r\n"); + } + smDepthSortList.sort(); + + printDump("\r\n\r\nPost-sort: \r\n\r\n"); + for (i=0; ivertexCount; j++) + { + Point3F p = smDepthSortList.mVertexList[smDepthSortList.mIndexList[poly->vertexStart+j]].point; + printDump(avar("(%5.3f, %5.3f, %5.3f) ",p.x,p.y,p.z)); + } + printDump("\r\n"); + } + + file.close(); +} +#endif + + + diff --git a/game/shadow.h b/game/shadow.h new file mode 100644 index 0000000..8f08405 --- /dev/null +++ b/game/shadow.h @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADOW_H_ +#define _SHADOW_H_ + +#ifndef _DEPTHSORTLIST_H_ +#include "Collision/depthSortList.h" +#endif +#ifndef _TSSHAPEINSTANCE_H_ +#include "ts/tsShapeInstance.h" +#endif + +class GBitmap; + +class Shadow +{ + GBitmap * mBitmap; + TextureHandle mShadowTexture; + F32 mRadius; + F32 mInvShadowDistance; + MatrixF mLightToWorld; + MatrixF mWorldToLight; + + Vector mPartition; + Vector mPartitionVerts; + Vector mPartitionTVerts; + Vector mPartitionColors; + + struct ShadowSettings + { + // set by user (probably just once) + bool alwaysUseGenericBmp; + bool noAnimate; + bool noMove; + + // determined frame to frame + S32 bmpDim; + S32 blur; + U32 lastBmpTime; + bool needBmp; + } mSettings; + + static U32 smShadowMask; + + static DepthSortList smDepthSortList; + static TextureHandle* smGenericShadowTexture; + static S32 smGenericShadowDim; + static S32 smInstanceCount; + static F32 smShapeDetailScale; + static S32 smShapeDetailMin; + static F32 smSmallestVisibleSize; + static F32 smGenericRadiusScew; + static bool smAlwaysUseGenericBmp; + static F32 smLightDirScew; + static F32 smLightLenScew; + + static F32 smGlobalShadowDetail; + + static void collisionCallback(SceneObject*,S32); + +public: + + struct DistanceDetail + { + F32 dist; + F32 directionScew; // 0-1, 0 means leave direction alone, 1 means light vector (0,0,-1) + F32 lengthScew; // 0-1, 0 means leave length alone, 1 means shadow volume has no depth + }; + + struct PixelSizeDetail + { + F32 size; + U32 frameExpiration; + S32 bmpDim; + S32 blur; + bool genericShadowBmp; + }; + + void setDetailTables(const DistanceDetail *, S32 ddCount, const PixelSizeDetail *, S32 psdCount); + void setDefaultDetailTables(); + + // this method changes several shadow detail parameters all at once -- range is 0-1, default value is 1 + static void setGlobalShadowDetailLevel(F32 d); + static F32 getGlobalShadowDetailLevel() { return smGlobalShadowDetail; } + + void setGeneric(bool b) { mSettings.alwaysUseGenericBmp = b; } + void setAnimating(bool b) { mSettings.noAnimate = !b; } + void setMoving(bool b) { mSettings.noMove = !b; } + + static DistanceDetail smDefaultDistanceDetails[]; + static PixelSizeDetail smDefaultPixelSizeDetails[]; + +private: + + const DistanceDetail * mDistanceDetails; + const PixelSizeDetail * mPixelSizeDetails; + + S32 mNumDistanceDetails; + S32 mNumPixelSizeDetails; + + void findDistanceDetail(F32 dist, DistanceDetail *); + void findPixelSizeDetail(F32 pixelSize, const PixelSizeDetail * *); + +private: + void setLightMatrices(const Point3F & lightDir, const Point3F & pos); + + void buildPartition(const Point3F & p, const Point3F & lightDir, F32 radius, F32 shadowLen); + void updatePartition(F32 fogAmount); + +public: + + Shadow(); + ~Shadow(); + + void beginRenderToBitmap(); + void endRenderToBitmap(); + void renderToBitmap(TSShapeInstance *, const MatrixF &, const Point3F & center, Point3F scale); + GBitmap * getBitmap() { return mBitmap; } + + void setRadius(F32 radius); + void setRadius(TSShapeInstance *, const Point3F & scale); + + bool prepare(const Point3F & pos, Point3F lightDir, F32 shadowLen, const Point3F & scale, F32 dist, F32 fogAmount, TSShapeInstance *); + bool needBitmap() { return mSettings.needBmp; } + S32 selectShapeDetail(TSShapeInstance*, F32 dist, F32 scale, S32 detailMin = -1); + + void render(); + + static GBitmap * generateGenericShadowBitmap(S32 dim); + +#ifdef DEBUG + void dumpSort(); +#endif +}; + +#endif // _SHADOW_H_ + + diff --git a/game/shapeBase.cc b/game/shapeBase.cc new file mode 100644 index 0000000..fc0f6e4 --- /dev/null +++ b/game/shapeBase.cc @@ -0,0 +1,5025 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "platform/platform.h" +#include "core/dnet.h" +#include "audio/audio.h" +#include "game/gameConnection.h" +#include "game/moveManager.h" +#include "console/consoleTypes.h" +#include "core/bitStream.h" +#include "ts/tsPartInstance.h" +#include "ts/tsShapeInstance.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sceneState.h" +#include "game/shadow.h" +#include "game/explosion.h" +#include "game/shapeBase.h" +#include "game/targetManager.h" +#include "terrain/waterBlock.h" +#include "game/Debris.h" +#include "terrain/Sky.h" +#include "game/physicalZone.h" +#include "game/shieldImpact.h" +#include "scenegraph/detailManager.h" +#include "math/mathUtils.h" +#include "math/mMatrix.h" +#include "math/mRandom.h" +#include "game/commanderMapIcon.h" +#include "game/player.h" +#include "platform/profiler.h" +#include "game/targetManager.h" +#include "game/projSeeker.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.007; +F32 ShapeBase::sDamageFlashDec = 0.007; +bool ShapeBase::sUsePrefSkins = false; +U32 ShapeBase::sLastRenderFrame = 0; + +static const char *sDamageStateName[] = +{ + // Index by enum ShapeBase::DamageState + "Enabled", + "Disabled", + "Destroyed" +}; + + +//---------------------------------------------------------------------------- + +ShapeBaseData::ShapeBaseData() +{ + shapeName = ""; + mass = 1; + drag = 0; + density = 1; + maxEnergy = 0; + maxDamage = 1.0; + disabledLevel = 1.0; + destroyedLevel = 1.0; + repairRate = 0.0033; + eyeNode = -1; + shadowNode = -1; + cameraNode = -1; + damageSequence = -1; + hulkSequence = -1; + cameraMaxDist = 0; + cameraMinDist = 0.2; + cameraDefaultFov = 90.f; + cameraMinFov = 5.f; + cameraMaxFov = 120.f; + emap = false; + aiAvoidThis = false; + isInvincible = false; + renderWhenDestroyed = true; + debris = NULL; + debrisID = 0; + debrisShapeName = NULL; + explosion = NULL; + explosionID = 0; + underwaterExplosion = NULL; + underwaterExplosionID = 0; + firstPersonOnly = false; + useEyePoint = false; + sensorRadius = 0.f; + sensorColor.set(255,0,0,200); + heat = 1.0; + shieldEffectLifetimeMS = 300; + + cmdCategory = 0; + cmdIcon = 0; + cmdIconId = 0; + cmdMiniIconName = 0; + canControl = false; + canObserve = false; + observeThroughObject = false; + computeCRC = false; + + for (U32 i = 0; i < MaxCollisionShapes; i++) { + collisionDetails[i] = -1; + LOSDetails[i] = -1; + } + + // no shadows by default + genericShadowLevel = 2.0f; + noShadowLevel = 2.0f; + + inheritEnergyFromMount = false; + + for(U32 j = 0; j < NumHudRenderImages; j++) + { + hudImageNameFriendly[j] = 0; + hudImageNameEnemy[j] = 0; + hudRenderCenter[j] = false; + hudRenderModulated[j] = false; + hudRenderAlways[j] = false; + hudRenderDistance[j] = false; + hudRenderName[j] = false; + } +} + +static ShapeBaseData gShapeBaseDataProto; + +ShapeBaseData::~ShapeBaseData() +{ + +} + +bool ShapeBaseData::preload(bool server, char errorBuffer[256]) +{ + if (!Parent::preload(server, errorBuffer)) + return 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) ) + { + char fullName[256]; + dSprintf(fullName, sizeof(fullName), "shapes/%s", debrisShapeName); + + debrisShape = ResourceManager->load(fullName); + if( bool(debrisShape) == false ) + { + dSprintf(errorBuffer, 256, "ShapeBaseData::load: Couldn't load shape \"%s\"", debrisShapeName); + return false; + } + else + { + TSShapeInstance* pDummy = new TSShapeInstance(debrisShape, !server); + delete pDummy; + } + } + } + + // + if (shapeName && shapeName[0]) { + S32 i; + + // Resolve shapename + char fullName[256]; + dSprintf(fullName,sizeof(fullName),"shapes/%s",shapeName); + shape = ResourceManager->load(fullName, computeCRC); + if (!bool(shape)) { + dSprintf(errorBuffer, 256, "ShapeBaseData: Couldn't load shape \"%s\"",shapeName); + return false; + } + if(computeCRC) + { + Con::printf("Validation required for shape: %s", shapeName); + if(server) + mCRC = shape.getCRC(); + else if(mCRC != shape.getCRC()) + { + dSprintf(errorBuffer, 256, "Shape \"%s\" does not match version on server.",shapeName); + return false; + } + } + // Resolve details and camera node indexes. + for (i = 0; i < MaxCollisionShapes; i++) { + char buff[128]; + dSprintf(buff, sizeof(buff), "Collision-%d", i + 1); + collisionDetails[i] = shape->findDetail(buff); + if (collisionDetails[i] != -1) { + shape->computeBounds(collisionDetails[i], collisionBounds[i]); + shape->getAccelerator(collisionDetails[i]); + + if (!shape->bounds.isContained(collisionBounds[i])) + { + Con::warnf("Warning: shape %s collision detail %d (Collision-%d) bounds exceed that of shape.", shapeName, i, collisionDetails[i]); + collisionBounds[i] = shape->bounds; + } + else if (collisionBounds[i].isValidBox() == false) + { + Con::errorf("Error: shape %s-collision detail %d (Collision-%d) bounds box invalid!", shapeName, i, collisionDetails[i]); + collisionBounds[i] = shape->bounds; + } + } + + dSprintf(buff, sizeof(buff), "LOS-%d", i + 1 + MaxCollisionShapes); + if ((LOSDetails[i] = shape->findDetail(buff)) == -1) + LOSDetails[i] = collisionDetails[i]; + } + + debrisDetail = shape->findDetail("Debris-17"); + eyeNode = shape->findNode("eye"); + cameraNode = shape->findNode("cam"); + if (cameraNode == -1) + cameraNode = eyeNode; + + // Resolve mount point node indexes + for (i = 0; i < NumMountPoints; i++) { + dSprintf(fullName,sizeof(fullName),"mount%d",i); + mountPointNode[i] = shape->findNode(fullName); + } + + // find the AIRepairNode - hardcoded to be the last node in the array... + mountPointNode[AIRepairNode] = shape->findNode("AIRepairNode"); + + // + hulkSequence = shape->findSequence("Visibility"); + damageSequence = shape->findSequence("Damage"); + + // + F32 w = shape->bounds.len_y() / 2; + if (cameraMaxDist < w) + cameraMaxDist = w; + } + + if(!server) + { + if(!cmdIcon && (cmdIconId != 0)) + cmdIcon = dynamic_cast(Sim::findObject(cmdIconId)); + + if(cmdMiniIconName && cmdMiniIconName[0]) + cmdMiniIcon = TextureHandle(cmdMiniIconName, BitmapTexture); + + // 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); + } + } + + return true; +} + + +void ShapeBaseData::initPersistFields() +{ + Parent::initPersistFields(); + addField("shapeFile", TypeCaseString, Offset(shapeName, ShapeBaseData)); + addField("explosion", TypeExplosionDataPtr, Offset(explosion, ShapeBaseData)); + addField("underwaterExplosion", TypeExplosionDataPtr, Offset(underwaterExplosion, ShapeBaseData)); + addField("debris", TypeDebrisDataPtr, Offset(debris, ShapeBaseData)); + addField("mass", TypeF32, Offset(mass, ShapeBaseData)); + addField("drag", TypeF32, Offset(drag, ShapeBaseData)); + addField("density", TypeF32, Offset(density, ShapeBaseData)); + 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("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("emap", TypeBool, Offset(emap, ShapeBaseData)); + addField("aiAvoidThis", TypeBool, Offset(aiAvoidThis, ShapeBaseData)); + addField("isInvincible", TypeBool, Offset(isInvincible, ShapeBaseData)); + addField("inheritEnergyFromMount", TypeBool, Offset(inheritEnergyFromMount, ShapeBaseData)); + addField("renderWhenDestroyed", TypeBool, Offset(renderWhenDestroyed, ShapeBaseData)); + addField("debrisShapeName", TypeString, Offset(debrisShapeName, ShapeBaseData)); + addField("firstPersonOnly", TypeBool, Offset(firstPersonOnly, ShapeBaseData)); + addField("useEyePoint", TypeBool, Offset(useEyePoint, ShapeBaseData)); + addField("sensorRadius", TypeF32, Offset(sensorRadius, ShapeBaseData)); + addField("sensorColor", TypeColorI, Offset(sensorColor, ShapeBaseData)); + addField("cmdCategory", TypeString, Offset(cmdCategory, ShapeBaseData)); + addField("cmdIcon", TypeCommanderIconDataPtr, Offset(cmdIcon, ShapeBaseData)); + addField("cmdMiniIconName",TypeString, Offset(cmdMiniIconName,ShapeBaseData)); + addField("canControl", TypeBool, Offset(canControl, ShapeBaseData)); + addField("canObserve", TypeBool, Offset(canObserve, ShapeBaseData)); + addField("observeThroughObject", TypeBool, Offset(observeThroughObject, ShapeBaseData)); + addField("computeCRC", TypeBool, Offset(computeCRC, ShapeBaseData)); + addField("heatSignature", TypeF32, Offset(heat, ShapeBaseData)); + addField("shieldEffectLifetimeMS", TypeS32, Offset(shieldEffectLifetimeMS, ShapeBaseData)); + + AssertFatal(NumHudRenderImages == TargetInfo::NumHudRenderImages, "ShapeBaseData: num hud render images mismatch"); + addField("hudImageName", TypeString, Offset(hudImageNameFriendly, ShapeBaseData), NumHudRenderImages); + addField("hudImageNameFriendly", TypeString, Offset(hudImageNameFriendly, ShapeBaseData), NumHudRenderImages); + addField("hudImageNameEnemy", TypeString, 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); +} + + +static bool cCheckDeployPos(SimObject* obj, S32, const char** argv) +{ + ShapeBaseData* pData = static_cast(obj); + if (bool(pData->shape) == false) + return false; + + Point3F pos(0, 0, 0); + AngAxisF aa(Point3F(0, 0, 1), 0); + dSscanf(argv[2],"%f %f %f %f %f %f %f", + &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 = pData->shape->bounds; + Point3F boxCenter = (objBox.min + objBox.max) * 0.5; + objBox.min = boxCenter + (objBox.min - boxCenter) * 0.9; + objBox.max = boxCenter + (objBox.max - boxCenter) * 0.9; + + 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.min,VectorF(-1,0,0)); + polyList.mPlaneList[1].set(objBox.max,VectorF(0,1,0)); + polyList.mPlaneList[2].set(objBox.max,VectorF(1,0,0)); + polyList.mPlaneList[3].set(objBox.min,VectorF(0,-1,0)); + polyList.mPlaneList[4].set(objBox.min,VectorF(0,0,-1)); + polyList.mPlaneList[5].set(objBox.max,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; +} + + +static const char* cGetDeployTransform(SimObject*, S32, const char** argv) +{ + Point3F normal; + Point3F position; + dSscanf(argv[2], "%f %f %f", &position.x, &position.y, &position.z); + dSscanf(argv[3], "%f %f %f", &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::consoleInit() +{ + Con::addCommand("ShapeBaseData", "checkDeployPos", cCheckDeployPos, "obj.checkDeployPos(xform)", 3, 3); + Con::addCommand("ShapeBaseData", "getDeployTransform", cGetDeployTransform, "obj.getDeployTransform(pos, normal)", 4, 4); +} + + +void ShapeBaseData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if(stream->writeFlag(computeCRC)) + stream->write(mCRC); + + stream->writeString(shapeName); + 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 ); + + if(stream->writeFlag(sensorRadius != 0.f)) + { + stream->writeInt(sensorRadius, 10); + stream->write(sensorColor.red); + stream->write(sensorColor.green); + stream->write(sensorColor.blue); + stream->write(sensorColor.alpha); + } + if(stream->writeFlag(heat != gShapeBaseDataProto.heat)) + stream->write(heat); + + stream->writeString(cmdCategory); + if(stream->writeFlag(cmdIcon)) + stream->writeRangedU32(cmdIcon->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + stream->writeString(cmdMiniIconName); + stream->writeFlag(canControl); + stream->writeFlag(canObserve); + stream->writeFlag(observeThroughObject); + + if( stream->writeFlag( debris != NULL ) ) + { + stream->writeRangedU32(packed? SimObjectId(debris): + debris->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + + stream->writeFlag(emap); + 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( shieldEffectLifetimeMS ); + + // hud images... + for(U32 i = 0; i < TargetInfo::NumHudRenderImages; i++) + { + // must at least have a friendly image.. (default if no enemy) + if(stream->writeFlag(hudImageNameFriendly[i] && hudImageNameFriendly[i][0])) + { + stream->writeString(hudImageNameFriendly[i]); + + if(stream->writeFlag(hudImageNameEnemy[i] && hudImageNameEnemy[i][0])) + stream->writeString(hudImageNameEnemy[i]); + + stream->writeFlag(hudRenderCenter[i]); + stream->writeFlag(hudRenderModulated[i]); + stream->writeFlag(hudRenderAlways[i]); + stream->writeFlag(hudRenderDistance[i]); + stream->writeFlag(hudRenderName[i]); + } + } +} + +void ShapeBaseData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + computeCRC = stream->readFlag(); + if(computeCRC) + stream->read(&mCRC); + + shapeName = 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(); + + if(stream->readFlag()) + { + sensorRadius = stream->readInt(10); + stream->read(&sensorColor.red); + stream->read(&sensorColor.green); + stream->read(&sensorColor.blue); + stream->read(&sensorColor.alpha); + } + else + { + sensorRadius = 0.f; + } + if(stream->readFlag()) + stream->read(&heat); + + cmdCategory = stream->readSTString(); + if(stream->readFlag()) + cmdIconId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + cmdMiniIconName = stream->readSTString(); + canControl = stream->readFlag(); + canObserve = stream->readFlag(); + observeThroughObject = stream->readFlag(); + + if( stream->readFlag() ) + { + debrisID = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + emap = stream->readFlag(); + 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( &shieldEffectLifetimeMS ); + + // hud images... + for(U32 i = 0; i < TargetInfo::NumHudRenderImages; i++) + { + if(stream->readFlag()) + { + hudImageNameFriendly[i] = stream->readSTString(); + + if(stream->readFlag()) + hudImageNameEnemy[i] = stream->readSTString(); + + hudRenderCenter[i] = stream->readFlag(); + hudRenderModulated[i] = stream->readFlag(); + hudRenderAlways[i] = stream->readFlag(); + hudRenderDistance[i] = stream->readFlag(); + hudRenderName[i] = stream->readFlag(); + } + } +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +Chunker sTimeoutChunker; +ShapeBase::CollisionTimeout* sFreeTimeoutList = 0; + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(ShapeBase); + +ShapeBase::ShapeBase() +{ + mTypeMask |= ShapeBaseObjectType; + + mDrag = 0; + mBuoyancy = 0; + mWaterCoverage = 0; + mLiquidType = 0; + mLiquidHeight = 0.0f; + mControllingClient = 0; + mControllingObject = 0; + + mGravityMod = 1.0; + mAppliedForce.set(0, 0, 0); + + mTimeoutList = 0; + mDataBlock = NULL; + mShapeInstance = 0; + mShadow = 0; + mGenerateShadow = false; + mEnergy = 0; + mRechargeRate = 0; + mDamage = 0; + mRepairRate = 0; + mRepairReserve = 0; + mDamageState = Enabled; + mDamageThread = 0; + mHulkThread = 0; + mLastRenderFrame = 0; + mLastRenderDistance = 0; + + mCloaked = false; + mCloakLevel = 0.0; + + mPassiveJammed = false; + + mMount.object = 0; + mMount.link = 0; + mMount.list = 0; + + mActiveImage = 0; + mHidden = false; + + for (int a = 0; a < MaxSoundThreads; a++) { + mSoundThread[a].play = false; + mSoundThread[a].profile = 0; + mSoundThread[a].sound = 0; + } + + S32 i; + 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].forward = true; + } + + for (i = 0; i < MaxTriggerKeys; i++) + mTrigger[i] = false; + + mSkinTag = 0; + mSkinPrefTag = 0; + + mDamageFlash = 0.0; + mWhiteOut = 0.0; + + mInvincibleEffect = 0.0f; + mInvincibleDelta = 0.0f; + mInvincibleCount = 0.0f; + mInvincibleSpeed = 0.0f; + mInvincibleTime = 0.0f; + mInvincibleFade = 0.1; + mInvincibleOn = false; + + mTracking = false; + mLockedOn = NotLocked; + + mPotentialTargets = NULL; + mIsControlled = false; + + mConvexList = new Convex; + mCameraFov = 90.f; + mShieldNormal.set(0, 0, 1); + + mFadeOut = true; + mFading = false; + mFadeVal = 1.0; + mFadeTime = 1.0; + mFadeElapsedTime = 0.0; + mFadeDelay = 0.0; + mFlipFadeVal = false; + mLightTime = 0; + blowApart = false; + damageDir.set(0, 0, 1); +} + + +ShapeBase::~ShapeBase() +{ + delete mConvexList; + mConvexList = NULL; + + AssertFatal(mMount.link == 0,"ShapeBase::~ShapeBase: An object is still mounted"); + if( mShapeInstance && (mShapeInstance->getDebrisRefCount() == 0) ) + { + delete mShapeInstance; + } + delete mShadow; + + CollisionTimeout* ptr = mTimeoutList; + while (ptr) { + CollisionTimeout* cur = ptr; + ptr = ptr->next; + cur->next = sFreeTimeoutList; + sFreeTimeoutList = cur; + } +} + + +//---------------------------------------------------------------------------- + +bool ShapeBase::onAdd() +{ + if(!Parent::onAdd()) + 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 (isClientObject()) + { + mCloakTexture = TextureHandle("special/cloakTexture", MeshTexture, false); + mShieldEffect.init(); + + //one of the mounted images must have a light source... + for (S32 i = 0; i < MaxMountedImages; i++) + { + ShapeBaseImageData* imageData = getMountedImage(i); + if (imageData != NULL && imageData->lightType != ShapeBaseImageData::NoLight) + { + Sim::getLightSet()->addObject(this); + break; + } + } + } + + setHeat(mDataBlock->heat); + + 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); +} + + +void ShapeBase::onSceneRemove() +{ + mConvexList->nukeList(); + Parent::onSceneRemove(); +} + + + +static void reSkin(TSShapeInstance *instance, const char *newBase, const char *prefBase) +{ + if (instance->ownMaterialList() == false) + instance->cloneMaterialList(); + + TSMaterialList* pMatList = instance->getMaterialList(); + + for (U32 j = 0; j < pMatList->mMaterialNames.size(); j++) { + const char* pName = pMatList->mMaterialNames[j]; + if (pName == NULL) + continue; + const U32 len = dStrlen(pName); + if (len < 6) + continue; + + const char* pReplace = dStrstr(pName, (const char*)"base."); + if (pReplace == NULL) + continue; + + char newName[256]; + AssertFatal(len < 200, "ShapeBase::checkSkin: Error, len exceeds allowed name length"); + TextureHandle test; + + if(ShapeBase::sUsePrefSkins && prefBase[0]) + { + dStrncpy(newName, pName, pReplace - pName); + newName[pReplace - pName] = '\0'; + dStrcat(newName, prefBase); + dStrcat(newName, "."); + dStrcat(newName, pName + 5 + (pReplace - pName)); + test = TextureHandle(newName, MeshTexture, false); + } + if(test.getGLName() == 0) + { + dStrncpy(newName, pName, pReplace - pName); + newName[pReplace - pName] = '\0'; + dStrcat(newName, newBase); + dStrcat(newName, "."); + dStrcat(newName, pName + 5 + (pReplace - pName)); + + test = TextureHandle(newName, MeshTexture, false); + } + if (test.getGLName() != 0) { + pMatList->mMaterials[j] = test; + } else { + pMatList->mMaterials[j] = TextureHandle(pName, MeshTexture, false); + } + } +} + +bool ShapeBase::onNewDataBlock(GameBaseData* dptr) +{ + 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->shape)) { + delete mShapeInstance; + mShapeInstance = new TSShapeInstance(mDataBlock->shape, isClientObject()); + if (isClientObject()) + mShapeInstance->cloneMaterialList(); + + mObjBox = mDataBlock->shape->bounds; + resetWorldBox(); + + // Initialize the threads + for (U32 i = 0; i < MaxScriptThreads; i++) { + Thread& st = mScriptThread[i]; + if (st.sequence != -1) { + // TG: Need to see about supressing non-cyclic sounds + // if the sequences were actived 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; + setThreadSequence(i,st.sequence,reset); + } + } + + // get rid of current shadow...we'll generate new one when needed + delete mShadow; + mShadow = NULL; + + 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() && mSkinTag) + { + reSkin(mShapeInstance, gNetStringTable->lookupString(mSkinTag), + mSkinPrefTag ? gNetStringTable->lookupString(mSkinPrefTag) : ""); + mSkinHash = (_StringTable::hashString(gNetStringTable->lookupString(mSkinTag)) ^ + _StringTable::hashString(mSkinPrefTag ? gNetStringTable->lookupString(mSkinPrefTag) : "")); + } + + // + mEnergy = 0; + mDamage = 0; + mDamageState = Enabled; + mRepairReserve = 0; + updateMass(); + updateDamageLevel(); + updateDamageState(); + + mCameraFov = mDataBlock->cameraDefaultFov; + return true; +} + +void ShapeBase::onDeleteNotify(SimObject* obj) +{ + if (obj == getProcessAfter()) + clearProcessAfter(); + Parent::onDeleteNotify(obj); + if (obj == mMount.object) + unmount(); +} + +void ShapeBase::onImpact(SceneObject* obj, VectorF vec) +{ + if (!isGhost()) { + char buff1[256]; + char buff2[32]; + + dSprintf(buff1,sizeof(buff1),"%f %f %f",vec.x, vec.y, vec.z); + dSprintf(buff2,sizeof(buff2),"%f",vec.len()); + Con::executef(mDataBlock,5,"onImpact",scriptThis(), obj->getIdString(), buff1, buff2); + } +} + +void ShapeBase::onImpact(VectorF vec) +{ + if (!isGhost()) { + char buff1[256]; + char buff2[32]; + + dSprintf(buff1,sizeof(buff1),"%f %f %f",vec.x, vec.y, vec.z); + dSprintf(buff2,sizeof(buff2),"%f",vec.len()); + Con::executef(mDataBlock,5,"onImpact",scriptThis(), "0", buff1, buff2); + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::processTick(const Move* move) +{ + // Energy management + if (mDamageState == Enabled && mDataBlock->inheritEnergyFromMount == false) { + mEnergy += mRechargeRate; + if (mEnergy > mDataBlock->maxEnergy) + mEnergy = mDataBlock->maxEnergy; + else if (mEnergy < 0) + mEnergy = 0; + + // set update mask on vehicles... + if(isServerObject() && getMountList()) + { + bool found = false; + for (ShapeBase* ptr = getMountList(); ptr; ptr = ptr->getMountLink()) + { + if(!dynamic_cast(ptr)) + continue; + + GameConnection * controllingClient = ptr->getControllingClient(); + if(!controllingClient || (controllingClient->getControlObject() == this)) + continue; + + found = true; + break; + } + + if(found) + 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()) { + setMaskBits(DamageMask); + Con::executef(mDataBlock,2,"onDamage",scriptThis()); + } + } + } + + if (isServerObject()) { + // Server only... + advanceThreads(TickSec); + updateServerAudio(); + + // update wet state + setImageWetState(0, mWaterCoverage > 0.4); // more than 40 percent covered + + if (mWaterCoverage < 0.4 && getContainer() && getMountedImage(0) != NULL && getMountedImage(0)->isSeeker == true && move != NULL) { + thinkAboutLocking(); + + if (mLockedOn == NotLocked && mPotentialTargets != NULL) + mTracking = true; + else + mTracking = false; + + setImageWetState(0, 0); + + // update the targeting for seeker images + setImageTargetState(0, mTracking || ((mLockedOn == LockObject) || (mLockedOn == LockPosition))); + } else { + mTracking = false; + if (mPotentialTargets != NULL) { + PotentialLock* walk = mPotentialTargets; + while (walk) { + PotentialLock* pGarbage = walk; + walk = walk->next; + delete pGarbage; + } + mPotentialTargets = NULL; + } + setLockedTarget(NULL); + } + + if(mFading) + { + F32 dt = TickMs / 1000.0; + F32 newFadeET = mFadeElapsedTime + dt; + if(mFadeElapsedTime < mFadeDelay && newFadeET >= mFadeDelay) + setMaskBits(CloakMask); + mFadeElapsedTime = newFadeET; + if(mFadeElapsedTime > mFadeTime + mFadeDelay) + { + mFadeVal = F32(!mFadeOut); + mFading = false; + } + } + } + + // 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,4,"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; + } + } + mShieldEffect.update( dt ); +} + + +void ShapeBase::thinkAboutLocking() +{ + AssertFatal(isServerObject(), "Error, must not call this on the client!"); + AssertFatal(getMountedImage(0) && getMountedImage(0)->isSeeker, "Error, no image, or a non-seeker image!"); + + // For right now, we're just going to check every tick. This should + // be slowed down to every third or fourth tick. + + Point3F muzzleVector; + Point3F muzzlePoint; + getMuzzleVector(0, &muzzleVector); + getMuzzlePoint(0, &muzzlePoint); + + Point3F coord0, coord1; + if (mFabs(muzzleVector.z) < 0.9) { + mCross(muzzleVector, Point3F(0, 0, 1), &coord0); + } else { + mCross(muzzleVector, Point3F(0, 1, 0), &coord0); + } + coord0.normalize(); + mCross(muzzleVector, coord0, &coord1); + coord1.normalize(); + + F32 seekAngle = getMountedImage(0)->maxSeekAngle / 2.0; + F32 sinSeekAngle = mSin(mDegToRad(seekAngle)); + F32 cosSeekAngle = mCos(mDegToRad(seekAngle)); + + Point3F end = muzzlePoint + muzzleVector * getMountedImage(0)->seekRadius; + coord0 *= getMountedImage(0)->seekRadius * sinSeekAngle; + coord1 *= getMountedImage(0)->seekRadius * sinSeekAngle; + + Box3F queryBox; + queryBox.min = muzzlePoint; + queryBox.max = muzzlePoint; + queryBox.min.setMin(end + coord0 + coord1); + queryBox.min.setMin(end + coord0 - coord1); + queryBox.min.setMin(end - coord0 + coord1); + queryBox.min.setMin(end - coord0 - coord1); + queryBox.max.setMax(end + coord0 + coord1); + queryBox.max.setMax(end + coord0 - coord1); + queryBox.max.setMax(end - coord0 + coord1); + queryBox.max.setMax(end - coord0 - coord1); + + disableCollision(); + SimpleQueryList sql; + U32 mask = SensorObjectType | TurretObjectType | PlayerObjectType | VehicleObjectType; + getContainer()->findObjects(queryBox, mask, SimpleQueryList::insertionCallback, U32(&sql)); + + static U32 sTag = 0; + sTag++; + for (U32 i = 0; i < sql.mList.size(); i++) { + AssertFatal(dynamic_cast(sql.mList[i]) != NULL, "Error, should only encounter shapebases in the locker!"); + ShapeBase* pSBase = static_cast(sql.mList[i]); + + F32 heatSig = pSBase->getHeat(); + F32 minHeat = getMountedImage(0)->minSeekHeat; + if(( heatSig < minHeat) || pSBase->isDestroyed() ) + continue; + + // require that beacon objects be processed through targetSet + if( pSBase->isBeacon() ) + continue; + + // players do not use the visible state of the target (heat only) + if((!gTargetManager->isTargetVisible(pSBase->getTarget(), getSensorGroup()) && !dynamic_cast(this)) || + gTargetManager->isTargetFriendly(getTarget(), pSBase->getSensorGroup())) + continue; + + Point3F centerBox; + sql.mList[i]->getWorldBox().getCenter(¢erBox); + + // must be within the targeting dist + F32 distance = Point3F(centerBox - muzzlePoint).len(); + if(distance <= getMountedImage(0)->targetingDist) + continue; + + // range = lifetime * maxVelocity + SeekerProjectileData * seekerData = dynamic_cast(getMountedImage(0)->projectile); + if(seekerData && (distance > ((F32(seekerData->lifetimeMS) / 1000.f) * seekerData->maxVelocity))) + continue; + + // make sure there is an los to this object... + pSBase->disableCollision(); + + RayInfo rInfo; + if (getContainer()->castRay(muzzlePoint, centerBox, + (TerrainObjectType | InteriorObjectType | + PlayerObjectType | VehicleObjectType), &rInfo)) + { + pSBase->enableCollision(); + continue; + } + pSBase->enableCollision(); + + Point3F dif = centerBox - muzzlePoint; + dif.normalize(); + + F32 dot = mDot(dif, muzzleVector); + if (dot < cosSeekAngle || dynamic_cast(sql.mList[i]) == NULL) + continue; + + // This one goes on the list if it's not there already... + bool found = false; + PotentialLock* walk = mPotentialTargets; + while (walk && !found) { + if (bool(walk->potentialTarget)) { + if (sql.mList[i]->getId() == walk->potentialTarget->getId()) { + walk->tag = sTag; + walk->numTicks++; + found = true; + } + } + walk = walk->next; + } + if (!found) { + PotentialLock* newLock = new PotentialLock; + newLock->potentialTarget = pSBase; + newLock->isTarget = false; + newLock->tag = sTag; + newLock->numTicks = 1; + newLock->next = mPotentialTargets; + mPotentialTargets = newLock; + } + } + + // grab all the beacon'd objects (probably just 'beacons' and target beams) + SimSet* pTargetSet = Sim::getServerTargetSet(); + for (SimSet::iterator itr = pTargetSet->begin(); itr != pTargetSet->end(); itr++) { + AssertFatal(dynamic_cast(*itr) != NULL, "Error, should only encounter shapebases in the locker!"); + GameBase* pGBase = static_cast(*itr); + + // only players allowed to fire on beacons/targets + if(!dynamic_cast(this)) + continue; + + // dont allow freind beacons to be locked (these are the marker ones) + if( pGBase->getBeaconType() == GameBase::friendBeacon ) + continue; + + // needs to be friendly + if(!gTargetManager->isTargetVisible(pGBase->getTarget(), getSensorGroup()) || + !gTargetManager->isTargetFriendly(getTarget(), pGBase->getSensorGroup())) + continue; + + Point3F target; + U32 teamId; + if( pGBase->getTarget( &target, &teamId ) == false ) + continue; + + // must be within the targeting dist + F32 distance = Point3F(target - muzzlePoint).len(); + if(distance <= getMountedImage(0)->targetingDist) + continue; + + SeekerProjectileData * seekerData = dynamic_cast(getMountedImage(0)->projectile); + if(seekerData && (distance > ((F32(seekerData->lifetimeMS) / 1000.f) * seekerData->maxVelocity))) + continue; + + Point3F dif = target - muzzlePoint; + dif.normalize(); + + F32 dot = mDot(dif, muzzleVector); + if (dot < cosSeekAngle) + continue; + + // This one goes on the list if it's not there already... + bool found = false; + PotentialLock* walk = mPotentialTargets; + while (walk && !found) { + if (bool(walk->potentialTarget)) { + if ((*itr)->getId() == walk->potentialTarget->getId()) { + walk->tag = sTag; + walk->numTicks++; + found = true; + } + } + walk = walk->next; + } + if (!found) { + PotentialLock* newLock = new PotentialLock; + newLock->potentialTarget = pGBase; + newLock->isTarget = true; + newLock->tag = sTag; + newLock->numTicks = 1; + newLock->next = mPotentialTargets; + mPotentialTargets = newLock; + } + } + + F32 maxDp = -2.0; + ShapeBase* pMin = NULL; + F32 maxDpGB = -2.0; + GameBase* pMinGameBase = NULL; + Point3F minPosition; + + // Choose best from mPotentialTargets... + PotentialLock** pWalk = &mPotentialTargets; + while (*pWalk != NULL) { + if ((*pWalk)->isTarget == false) { + F32 heatTime; + ShapeBase* pSBase = static_cast((GameBase*)(*pWalk)->potentialTarget); + + if ((*pWalk)->tag != sTag || pSBase->getHeat() < getMountedImage(0)->minSeekHeat) + { + // Need to be removed + PotentialLock* pGarbage = *pWalk; + *pWalk = pGarbage->next; + delete pGarbage; + continue; + } else if ((*pWalk)->numTicks * TickSec >= (heatTime = (2.0 - pSBase->getHeat()) * getMountedImage(0)->seekTime)) { + // Consider + PotentialLock* pConsider = *pWalk; + + Point3F centerBox; + pSBase->getWorldBox().getCenter(¢erBox); + + pSBase->disableCollision(); + RayInfo rInfo; + if (getContainer()->castRay(muzzlePoint, centerBox, + (TerrainObjectType | InteriorObjectType | + PlayerObjectType | VehicleObjectType), + &rInfo)) { + // Remove pConsider, it's occluded... + pSBase->enableCollision(); + *pWalk = pConsider->next; + delete pConsider; + continue; + } + + pSBase->enableCollision(); + + Point3F dif = centerBox - muzzlePoint; + dif.normalize(); + + F32 dot = mDot(dif, muzzleVector); + if (dot > maxDp) { + maxDp = dot; + pMin = pSBase; + } + } + } else { + GameBase* pGBase = (*pWalk)->potentialTarget; + Point3F centerBox; + U32 teamId; + if ((*pWalk)->tag != sTag || pGBase->getTarget(¢erBox, &teamId) == false) { + // Need to be removed + PotentialLock* pGarbage = *pWalk; + *pWalk = pGarbage->next; + delete pGarbage; + continue; + } else if ((*pWalk)->numTicks > 8) { + Point3F dif = centerBox - muzzlePoint; + dif.normalize(); + F32 dot = mDot(dif, muzzleVector); + if (dot > maxDp) { + maxDpGB = dot; + pMinGameBase = pGBase; + minPosition = centerBox; + } + } + } + + pWalk = &((*pWalk)->next); + } + + enableCollision(); + + if (pMin != NULL) + { + if (getLockedTargetId() != pMin->getId()) + { + setLockedTarget(pMin); + } + } + else if (pMinGameBase != NULL) + { + setLockedTargetPosition(minPosition); + } + else + { + setLockedTarget(NULL); + } +} + +//---------------------------------------------------------------------------- + +void ShapeBase::setControllingClient(GameConnection* client) +{ + mControllingClient = client; + + // piggybacks on the cloak update + setMaskBits(CloakMask); +} + +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, S32 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); + } + } + + // failed to query the camera info? + // if(grabEye) LH - always use eye as good enough, avoid camera animate + { + MatrixF eyeTransform; + getEyeTransform(&eyeTransform); + eyeTransform.getColumn(3, &query->pos); + eyeTransform.getColumn(1, &query->orientation); + } + + // grab the visible distance from the sky + Sky * sky = gClientSceneGraph->getCurrentSky(); + if(sky) + query->visibleDistance = sky->getVisibleDistance(); + else + query->visibleDistance = 1000.f; + + // 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(-1,scopeCallback,S32(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 == false) + return mEnergy; + else if (isMounted()) { + return getObjectMount()->getEnergyLevel(); + } else { + return 0.0f; + } +} + +F32 ShapeBase::getEnergyValue() +{ + if (mDataBlock->inheritEnergyFromMount == false) { + F32 maxEnergy = mDataBlock->maxEnergy; + if ( maxEnergy > 0.f ) + return (mEnergy / mDataBlock->maxEnergy); + } else if (isMounted()) { + F32 maxEnergy = getObjectMount()->mDataBlock->maxEnergy; + if ( maxEnergy > 0.f ) + return (getObjectMount()->getEnergyLevel() / maxEnergy); + } + return 0.0f; +} + +void ShapeBase::setEnergyLevel(F32 energy) +{ + if (mDataBlock->inheritEnergyFromMount == false) { + if (mDamageState == Enabled) { + mEnergy = (energy > mDataBlock->maxEnergy)? + mDataBlock->maxEnergy: (energy < 0)? 0: energy; + } + } else { + // Pass the set onto whatever we're mounted to... + if (isMounted()) + getObjectMount()->setEnergyLevel(energy); + } +} + +void ShapeBase::setDamageLevel(F32 damage) +{ + if (!mDataBlock->isInvincible && (damage != mDamage)) + { + mDamage = mClampF(damage, 0.f, mDataBlock->maxDamage); + + updateDamageLevel(); + if (!isGhost()) { + setMaskBits(DamageMask); + Con::executef(mDataBlock,2,"onDamage",scriptThis()); + } + } +} + +//---------------------------------------------------------------------------- + +static F32 sWaterDensity = 1; +static F32 sWaterViscosity = 15; +static F32 sWaterCoverage = 0; +static U32 sWaterType = 0; +static F32 sWaterHeight = 0.0f; + +static void waterFind(SceneObject* obj,S32 key) +{ + ShapeBase* shape = reinterpret_cast(key); + WaterBlock* wb = dynamic_cast(obj); + AssertFatal(wb != NULL, "Error, not a water block!"); + if (wb == NULL) { + sWaterCoverage = 0; + return; + } + + const Box3F& wbox = obj->getWorldBox(); + const Box3F& sbox = shape->getWorldBox(); + sWaterType = 0; + if (wbox.isOverlapped(sbox)) { + sWaterType = wb->getLiquidType(); + if (wbox.max.z < sbox.max.z) + sWaterCoverage = (wbox.max.z - sbox.min.z) / (sbox.max.z - sbox.min.z); + else + sWaterCoverage = 1; + + sWaterViscosity = wb->getViscosity(); + sWaterDensity = wb->getDensity(); + sWaterHeight = wb->getSurfaceHeight(); + } +} + +void physicalZoneFind(SceneObject* obj, S32 key) +{ + ShapeBase* shape = reinterpret_cast(key); + PhysicalZone* pz = dynamic_cast(obj); + AssertFatal(pz != NULL, "Error, not a water block!"); + if (pz == NULL || pz->testObject(shape) == false) { + return; + } + + if (pz->isActive()) { + shape->mGravityMod *= pz->getGravityMod(); + shape->mAppliedForce += pz->getForce(); + } +} + +void findRouter(SceneObject* obj, S32 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 ShapeBase::updateContainer() +{ + // Update container drag and buoyancy properties + mDrag = 0; + mBuoyancy = 0; + sWaterCoverage = 0; + mGravityMod = 1.0; + mAppliedForce.set(0, 0, 0); + mContainer->findObjects(getWorldBox(), WaterObjectType|PhysicalZoneObjectType,findRouter,S32(this)); + sWaterCoverage = mClampF(sWaterCoverage,0,1); + mWaterCoverage = sWaterCoverage; + mLiquidType = sWaterType; + mLiquidHeight = sWaterHeight; + if (mWaterCoverage >= 0.1f) { + mDrag = mDataBlock->drag * sWaterViscosity * sWaterCoverage; + mBuoyancy = (sWaterDensity / mDataBlock->density) * sWaterCoverage; + } +} + + +//---------------------------------------------------------------------------- + +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; + } + } + + 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,3,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: 0; + 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 ) + { + 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(ShapeBase* obj,U32 node) +{ +// if (obj->mMount.object == this) +// return; + if (obj->mMount.object) + obj->unmount(); + + // Since the object is mounting to us, nothing should be colliding with it for a while + obj->mConvexList->nukeList(); + + obj->mMount.object = this; + obj->mMount.node = (node >= 0 && node < ShapeBaseData::NumMountPoints)? node: 0; + obj->mMount.link = mMount.list; + mMount.list = obj; + if (obj != getControllingObject()) + obj->processAfter(this); + obj->deleteNotify(this); + obj->setMaskBits(MountedMask); + obj->onMount(this,node); +} + + +void ShapeBase::unmountObject(ShapeBase* obj) +{ + if (obj->mMount.object == this) { + + // Find and unlink the object + for(ShapeBase **ptr = & mMount.list; (*ptr); ptr = &((*ptr)->mMount.link) ) + { + if(*ptr == obj) + { + *ptr = obj->mMount.link; + break; + } + } + if (obj != getControllingObject()) + obj->clearProcessAfter(); + obj->clearNotify(this); + obj->mMount.object = 0; + obj->mMount.link = 0; + obj->setMaskBits(MountedMask); + obj->onUnmount(this,obj->mMount.node); + } +} + +void ShapeBase::unmount() +{ + if (mMount.object) + mMount.object->unmountObject(this); +} + +void ShapeBase::onMount(ShapeBase* obj,S32 node) +{ + if (!isGhost()) { + char buff1[32]; + dSprintf(buff1,sizeof(buff1),"%d",node); + Con::executef(mDataBlock,4,"onMount",scriptThis(),obj->scriptThis(),buff1); + } +} + +void ShapeBase::onUnmount(ShapeBase* obj,S32 node) +{ + if (!isGhost()) { + char buff1[32]; + dSprintf(buff1,sizeof(buff1),"%d",node); + Con::executef(mDataBlock,4,"onUnmount",scriptThis(),obj->scriptThis(),buff1); + } +} + +S32 ShapeBase::getMountedObjectCount() +{ + S32 count = 0; + for (ShapeBase* itr = mMount.list; itr; itr = itr->mMount.link) + count++; + return count; +} + +ShapeBase* ShapeBase::getMountedObject(S32 idx) +{ + if (idx >= 0) { + S32 count = 0; + for (ShapeBase* itr = mMount.list; itr; itr = itr->mMount.link) + if (count++ == idx) + return itr; + } + return 0; +} + +S32 ShapeBase::getMountedObjectNode(S32 idx) +{ + if (idx >= 0) { + S32 count = 0; + for (ShapeBase* itr = mMount.list; itr; itr = itr->mMount.link) + if (count++ == idx) + return itr->mMount.node; + } + return -1; +} + +ShapeBase* ShapeBase::getMountNodeObject(S32 node) +{ + for (ShapeBase* itr = mMount.list; itr; itr = itr->mMount.link) + if (itr->mMount.node == node) + return itr; + return 0; +} + +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); + 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->castRay(sp, ep, + (0xFFFFFFFF & ~(WaterObjectType | + ForceFieldObjectType | + GameBaseObjectType | + DefaultObjectType)), + &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,&ep); + else + ep = sp + offset + (vec * newPos); + } + mat->setColumn(3,ep); + if (isMounted()) + getObjectMount()->enableCollision(); + enableCollision(); + } + else + { + getRenderEyeTransform(mat); + } +} + +// 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; +} + + +//---------------------------------------------------------------------------- +void ShapeBase::playShieldEffect(const Point3F& normal, F32 strength ) +{ + mShieldNormal = normal; + if (isServerObject()) + { + setMaskBits(ShieldMask); + } + else + { + mShieldEffect.setLifetime( mDataBlock->shieldEffectLifetimeMS / 1000.0 ); + mShieldEffect.setShieldStrength( strength ); + mShieldEffect.setShieldedObj( this ); + mShieldEffect.start(); + } +} + + +//---------------------------------------------------------------------------- +F32 ShapeBase::getHeat() const +{ + return mHeat; +} + +bool ShapeBase::onlyFirstPerson() const +{ + return mDataBlock->firstPersonOnly; +} + +bool ShapeBase::useObjsEyePoint() const +{ + return mDataBlock->useEyePoint; +} + +void ShapeBase::setHeat(const F32 heat) +{ + mHeat = heat; + if (mHeat < 0.0) + mHeat = 0; + else if (mHeat > 1.0) + mHeat = 1.0; +} + + +//---------------------------------------------------------------------------- +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,AudioProfile* profile) +{ + AssertFatal(slot < MaxSoundThreads,"ShapeBase::playSound: Incorrect argument"); + 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::stopSound: Incorrect argument"); + 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) +{ + if (st.sound) { + alxStop(st.sound); + st.sound = 0; + } + if (st.play && st.profile) { + if (isGhost()) { + if (Sim::findObject(SimObjectId(st.profile), st.profile)) + st.sound = alxPlay(st.profile, &getTransform()); + else + st.play = false; + } + else { + // Non-looping sounds timeout on the server + st.timeout = st.profile->mDescriptionObject->mDescription.mIsLooping? 0: + Sim::getCurrentTime() + sAudioTimeout; + } + } + else + st.play = false; +} + +void ShapeBase::updateAudioPos() +{ + for (int i = 0; i < MaxSoundThreads; i++) + if (AUDIOHANDLE sh = mSoundThread[i].sound) + alxSourceMatrixF(sh, &getTransform()); +} + +//---------------------------------------------------------------------------- + +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.forward = true; + } + 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); + mShapeInstance->setPos(st.thread,0); + // Drop through to pause state + case Thread::Pause: + mShapeInstance->setTimeScale(st.thread,0); + stopThreadSound(st); + break; + case Thread::Play: + if (st.atEnd) { + mShapeInstance->setTimeScale(st.thread,1); + mShapeInstance->setPos(st.thread,st.forward? 1: 0); + mShapeInstance->setTimeScale(st.thread,0); + stopThreadSound(st); + } + else { + mShapeInstance->setTimeScale(st.thread,st.forward? 1: -1); + 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::setThreadDir(U32 slot,bool forward) +{ + Thread& st = mScriptThread[slot]; + if (st.sequence != -1) { + if (st.forward != forward) { + setMaskBits(ThreadMaskN << slot); + st.forward = forward; + st.atEnd = false; + 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.forward? 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,3,"onEndSequence",scriptThis(),slot); + } + } + mShapeInstance->advanceTime(dt,st.thread); + } + } +} + + +//---------------------------------------------------------------------------- + +TSShape const* ShapeBase::getShape() +{ + return mShapeInstance? mShapeInstance->getShape(): 0; +} + + +void ShapeBase::calcClassRenderData() +{ + // This is truly lame, but I didn't want to duplicate the whole preprender logic + // in the player as well as the renderImage logic. DMM +} + + +bool ShapeBase::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 startZone, const bool modifyBaseState) +{ + AssertFatal(modifyBaseState == false, "Error, should never be called with this parameter set"); + AssertFatal(startZone == 0xFFFFFFFF, "Error, startZone should indicate -1"); + + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + if(blowApart) + return false; + + if( ( getDamageState() == Destroyed ) && ( !mDataBlock->renderWhenDestroyed ) ) + return false; + + // Select detail levels on mounted items + // but... always draw the control object's mounted images + // in high detail (I can't believe I'm commenting this hack :) + F32 saveError = TSShapeInstance::smScreenError; + GameConnection *con = GameConnection::getServerConnection(); + bool fogExemption = false; + ShapeBase *co = NULL; + if(con && ( (co = con->getControlObject()) != NULL) ) + { + if(co == this || co->getObjectMount() == this) + { + TSShapeInstance::smScreenError = 0.001; + fogExemption = true; + } + } + + if (state->isObjectRendered(this)) + { + mLastRenderFrame = sLastRenderFrame; + // get shape detail and fog information...we might not even need to be drawn + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 dist = cameraOffset.len(); + if (dist < 0.01) + dist = 0.01; + F32 fogAmount = state->getHazeAndFog(dist,cameraOffset.z); + F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); + if (mShapeInstance) + DetailManager::selectPotentialDetails(mShapeInstance,dist,invScale); + + if ((fogAmount>0.99f && fogExemption == false) || + (mShapeInstance && mShapeInstance->getCurrentDetail()<0) || + (!mShapeInstance && !gShowBoundingBox)) { + // no, don't draw anything + return false; + } + + + for (U32 i = 0; i < MaxMountedImages; i++) + { + MountedImage& image = mMountedImageList[i]; + if (image.dataBlock && image.shapeInstance) + { + DetailManager::selectPotentialDetails(image.shapeInstance,dist,invScale); + + if (mCloakLevel == 0.0f && image.shapeInstance->hasSolid() && mFadeVal == 1.0f) + { + ShapeImageRenderImage* rimage = new ShapeImageRenderImage; + rimage->obj = this; + rimage->mSBase = this; + rimage->mIndex = i; + rimage->isTranslucent = false; + rimage->textureSortKey = U32(image.dataBlock); + state->insertRenderImage(rimage); + } + + if ((mCloakLevel != 0.0f || mFadeVal != 1.0f || mShapeInstance->hasTranslucency()) || + (mMount.object == NULL && mGenerateShadow == true)) + { + ShapeImageRenderImage* rimage = new ShapeImageRenderImage; + rimage->obj = this; + rimage->mSBase = this; + rimage->mIndex = i; + rimage->isTranslucent = true; + rimage->sortType = SceneRenderImage::Point; + rimage->textureSortKey = U32(image.dataBlock); + state->setImageRefPoint(this, rimage); + state->insertRenderImage(rimage); + } + } + } + TSShapeInstance::smScreenError = saveError; + + if (mCloakLevel == 0.0f && mShapeInstance->hasSolid() && mFadeVal == 1.0f) + { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = false; + image->textureSortKey = mSkinHash ^ U32(mDataBlock); + state->insertRenderImage(image); + } + + if ((mCloakLevel != 0.0f || mFadeVal != 1.0f || mShapeInstance->hasTranslucency()) || + (mMount.object == NULL && mGenerateShadow == true)) + { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + image->textureSortKey = mSkinHash ^ U32(mDataBlock); + state->setImageRefPoint(this, image); + state->insertRenderImage(image); + } + + if( mShieldEffect.isActive() ) + { + ShieldImpactRenderImage* image = new ShieldImpactRenderImage; + image->setup(this); + state->setImageRefPoint(this, image); + state->insertRenderImage(image); + } + + calcClassRenderData(); + } + + return false; +} + + +void ShapeBase::renderObject(SceneState* state, SceneRenderImage* image) +{ + PROFILE_START(ShapeBaseRenderObject); + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + dglGetViewport(&viewport); + + installLights(); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + state->setupObjectProjection(this); + + // This is something of a hack, but since the 3space objects don't have a + // clear conception of texels/meter like the interiors do, we're sorta + // stuck. I can't even claim this is anything more scientific than eyeball + // work. DMM + F32 axis = (getObjBox().len_x() + getObjBox().len_y() + getObjBox().len_z()) / 3.0; + F32 dist = (getRenderWorldBox().getClosestPoint(state->getCameraPosition()) - state->getCameraPosition()).len(); + if (dist != 0) + { + F32 projected = dglProjectRadius(dist, axis) / 350; + if (projected < (1.0 / 16.0)) + { + TextureManager::setSmallTexturesActive(true); + } + } + + // render shield effect + ShieldImpactRenderImage *siri = dynamic_cast< ShieldImpactRenderImage * > ( image ); + if( siri ) + { + mShieldEffect.renderObject( state, image ); + } + else + { + if (mCloakLevel == 0.0f && mFadeVal == 1.0f) + { + if (image->isTranslucent == true) + { + TSShapeInstance::smNoRenderNonTranslucent = true; + TSShapeInstance::smNoRenderTranslucent = false; + } + else + { + TSShapeInstance::smNoRenderNonTranslucent = false; + TSShapeInstance::smNoRenderTranslucent = true; + } + } + else + { + TSShapeInstance::smNoRenderNonTranslucent = false; + TSShapeInstance::smNoRenderTranslucent = false; + } + + TSMesh::setOverrideFade( mFadeVal ); + + ShapeImageRenderImage* shiri = dynamic_cast(image); + if (shiri != NULL) + { + renderMountedImage(state, shiri); + } + else + { + renderImage(state, image); + } + + TSMesh::setOverrideFade( 1.0 ); + + + TSShapeInstance::smNoRenderNonTranslucent = false; + TSShapeInstance::smNoRenderTranslucent = false; + } + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + uninstallLights(); + + // Debugging Bounding Box + if (!mShapeInstance || gShowBoundingBox) { + glDisable(GL_DEPTH_TEST); + Point3F box; + glPushMatrix(); + dglMultMatrix(&getRenderTransform()); + box = (mObjBox.min + mObjBox.max) * 0.5; + glTranslatef(box.x,box.y,box.z); + box = (mObjBox.max - mObjBox.min) * 0.5; + glScalef(box.x,box.y,box.z); + glColor3f(1, 0, 1); + wireCube(Point3F(1,1,1),Point3F(0,0,0)); + glPopMatrix(); + + glPushMatrix(); + box = (mWorldBox.min + mWorldBox.max) * 0.5; + glTranslatef(box.x,box.y,box.z); + box = (mWorldBox.max - mWorldBox.min) * 0.5; + glScalef(box.x,box.y,box.z); + glColor3f(0, 1, 1); + wireCube(Point3F(1,1,1),Point3F(0,0,0)); + glPopMatrix(); + + for (U32 i = 0; i < MaxMountedImages; i++) { + MountedImage& image = mMountedImageList[i]; + if (image.dataBlock && image.shapeInstance) { + MatrixF mat; + glPushMatrix(); + getRenderImageTransform(i,&mat); + dglMultMatrix(&mat); + glColor3f(1, 0, 1); + wireCube(Point3F(0.05,0.05,0.05),Point3F(0,0,0)); + glPopMatrix(); + + glPushMatrix(); + getRenderMountTransform(i,&mat); + dglMultMatrix(&mat); + glColor3f(1, 0, 1); + wireCube(Point3F(0.05,0.05,0.05),Point3F(0,0,0)); + glPopMatrix(); + + glPushMatrix(); + getRenderMuzzleTransform(i,&mat); + dglMultMatrix(&mat); + glColor3f(1, 0, 1); + wireCube(Point3F(0.05,0.05,0.05),Point3F(0,0,0)); + glPopMatrix(); + } + } + glEnable(GL_DEPTH_TEST); + } + + dglSetCanonicalState(); + TextureManager::setSmallTexturesActive(false); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); + PROFILE_END(); +} + + +void ShapeBase::renderMountedImage(SceneState* state, ShapeImageRenderImage* rimage) +{ + AssertFatal(rimage->mSBase == this, "Error, wrong image"); + + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 dist = cameraOffset.len(); + F32 fogAmount = state->getHazeAndFog(dist,cameraOffset.z); + + // Mounted items + PROFILE_START(ShapeBaseRenderMounted); + MountedImage& image = mMountedImageList[rimage->mIndex]; + if (image.dataBlock && image.shapeInstance && DetailManager::selectCurrentDetail(image.shapeInstance)) { + MatrixF mat; + getRenderImageTransform(rimage->mIndex, &mat); + glPushMatrix(); + dglMultMatrix(&mat); + + if (image.dataBlock->cloakable && mCloakLevel != 0.0) + image.shapeInstance->setAlphaAlways(0.15 + (1 - mCloakLevel) * 0.85); + else + image.shapeInstance->setAlphaAlways(1.0); + + if (mCloakLevel == 0.0 && (image.dataBlock->emap && gRenderEnvMaps) && state->getEnvironmentMap().getGLName() != 0) { + image.shapeInstance->setEnvironmentMap(state->getEnvironmentMap()); + image.shapeInstance->setEnvironmentMapOn(true, 1.0); + } else { + image.shapeInstance->setEnvironmentMapOn(false, 1.0); + } + + image.shapeInstance->setupFog(fogAmount,state->getFogColor()); + image.shapeInstance->animate(); + image.shapeInstance->render(); + + // easiest just to shut it off here. If we're cloaked on the next frame, + // we don't want envmaps... + image.shapeInstance->setEnvironmentMapOn(false, 1.0); + + glPopMatrix(); + } + PROFILE_END(); +} + + +void ShapeBase::renderImage(SceneState* state, SceneRenderImage* image) +{ + glMatrixMode(GL_MODELVIEW); + + // Base shape + F32 fogAmount = 0.0f; + F32 dist = 0.0f; + + PROFILE_START(ShapeBaseRenderPrimary); + if (mShapeInstance && DetailManager::selectCurrentDetail(mShapeInstance)) { + glPushMatrix(); + dglMultMatrix(&getRenderTransform()); + glScalef(mObjScale.x,mObjScale.y,mObjScale.z); + + if (mCloakLevel != 0.0) { + glMatrixMode(GL_TEXTURE); + glPushMatrix(); + + static U32 shiftX = 0; + static U32 shiftY = 0; + + shiftX = (shiftX + 1) % 128; + shiftY = (shiftY + 1) % 127; + glTranslatef(F32(shiftX) / 127.0, F32(shiftY)/126.0, 0); + glMatrixMode(GL_MODELVIEW); + + mShapeInstance->setAlphaAlways(0.125 + (1 - mCloakLevel) * 0.875); + mShapeInstance->setOverrideTexture(mCloakTexture); + } + else { + mShapeInstance->setAlphaAlways(1.0); + } + + if (mCloakLevel == 0.0 && (mDataBlock->emap && gRenderEnvMaps) && state->getEnvironmentMap().getGLName() != 0) { + mShapeInstance->setEnvironmentMap(state->getEnvironmentMap()); + mShapeInstance->setEnvironmentMapOn(true, 1.0); + } else { + mShapeInstance->setEnvironmentMapOn(false, 1.0); + } + + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + dist = cameraOffset.len(); + fogAmount = state->getHazeAndFog(dist,cameraOffset.z); + + mShapeInstance->setupFog(fogAmount,state->getFogColor()); + mShapeInstance->animate(); + mShapeInstance->render(); + + mShapeInstance->setEnvironmentMapOn(false, 1.0); + + if (mCloakLevel != 0.0) { + glMatrixMode(GL_TEXTURE); + glPopMatrix(); + + mShapeInstance->clearOverrideTexture(); + } + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + PROFILE_END(); + + PROFILE_START(ShapeBaseRenderShadow); + // Shadow... + if (mShapeInstance && mCloakLevel == 0.0 && + mMount.object == NULL && mGenerateShadow == true && + image->isTranslucent == true) + { + // we are shadow enabled... + renderShadow(dist,fogAmount); + } + PROFILE_END(); +} + +void ShapeBase::renderShadow(F32 dist, F32 fogAmount) +{ + if (Shadow::getGlobalShadowDetailLevel()noShadowLevel) + return; + if (mShapeInstance->getShape()->subShapeFirstTranslucentObject.empty() || + mShapeInstance->getShape()->subShapeFirstTranslucentObject[0] == 0) + return; + + if (!mShadow) + mShadow = new Shadow(); + + if (Shadow::getGlobalShadowDetailLevel() < mDataBlock->genericShadowLevel) + mShadow->setGeneric(true); + else + mShadow->setGeneric(false); + + Point3F lightDir(0.57f,0.57f,-0.57f); + 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 + if (Shadow::getGlobalShadowDetailLevel() < mDataBlock->genericShadowLevel) + pos *= 0.5f; + S32 shadowNode = mDataBlock->shadowNode; + if (shadowNode>=0) + { + // adjust for movement of shape outside of bounding box by tracking this node + Point3F offset; + mShapeInstance->mNodeTransforms[shadowNode].getColumn(3,&offset); + offset -= mShapeInstance->getShape()->defaultTranslations[shadowNode]; + offset.z = 0.0f; + pos += offset; + } + pos.convolve(mObjScale); + getRenderTransform().mulP(pos); + + // pos is where shadow will be centered (in world space) + mShadow->setRadius(mShapeInstance, mObjScale); + if (!mShadow->prepare(pos, lightDir, shadowLen, mObjScale, dist, fogAmount, mShapeInstance)) + return; + + F32 maxScale = getMax(mObjScale.x,getMax(mObjScale.y,mObjScale.z)); + + if (mShadow->needBitmap()) + { + mShadow->beginRenderToBitmap(); + mShadow->selectShapeDetail(mShapeInstance,dist,maxScale); + mShadow->renderToBitmap(mShapeInstance, getRenderTransform(), pos, mObjScale); + + // render mounted items to shadow bitmap + for (U32 i = 0; i < MaxMountedImages; i++) + { + MountedImage& image = mMountedImageList[i]; + if (image.dataBlock && image.shapeInstance) + { + MatrixF mat; + getRenderImageTransform(i,&mat); + mShadow->selectShapeDetail(image.shapeInstance,dist,maxScale); + mShadow->renderToBitmap(image.shapeInstance,mat,pos,Point3F(1,1,1)); + } + } + + // We only render the first mounted object for now... + if (mMount.link && mMount.link->mShapeInstance) + { + Point3F linkScale = mMount.link->getScale(); + maxScale = getMax(linkScale.x,getMax(linkScale.y,linkScale.z)); + mShadow->selectShapeDetail(mMount.link->mShapeInstance,dist,maxScale); + mShadow->renderToBitmap(mMount.link->mShapeInstance, + mMount.link->getRenderTransform(), + pos, + linkScale); + } + + mShadow->endRenderToBitmap(); + } + + mShadow->render(); +} + +//---------------------------------------------------------------------------- + +static ColorF cubeColors[8] = { + ColorF(0, 0, 0), ColorF(1, 0, 0), ColorF(0, 1, 0), ColorF(0, 0, 1), + ColorF(1, 1, 0), ColorF(1, 0, 1), ColorF(0, 1, 1), ColorF(1, 1, 1) +}; + +static 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 U32 cubeFaces[6][4] = { + { 0, 2, 6, 4 }, { 0, 2, 3, 1 }, { 0, 1, 5, 4 }, + { 3, 2, 6, 7 }, { 7, 6, 4, 5 }, { 3, 7, 5, 1 } +}; + +void ShapeBase::wireCube(const Point3F& size, const Point3F& pos) +{ + glDisable(GL_CULL_FACE); + + for(int i = 0; i < 6; i++) { + glBegin(GL_LINE_LOOP); + for(int vert = 0; vert < 4; vert++) { + int idx = cubeFaces[i][vert]; + glVertex3f(cubePoints[idx].x * size.x + pos.x, cubePoints[idx].y * size.y + pos.y, cubePoints[idx].z * size.z + pos.z); + } + glEnd(); + } +} + + +//---------------------------------------------------------------------------- + +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 < ShapeBaseData::MaxCollisionShapes; i++) { + if (mDataBlock->LOSDetails[i] != -1) { + 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::buildPolyList(AbstractPolyList* polyList, const Box3F &, const SphereF &) +{ + if (mShapeInstance) { + bool ret = false; + + polyList->setTransform(&mObjToWorld, mObjScale); + polyList->setObject(this); + for (U32 i = 0; i < ShapeBaseData::MaxCollisionShapes; i++) { + if (mDataBlock->collisionDetails[i] != -1) { + mShapeInstance->buildPolyList(polyList,mDataBlock->collisionDetails[i]); + ret = true; + } + } + + return ret; + } + + 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.min.convolveInverse(mObjScale); + realBox.max.convolveInverse(mObjScale); + + if (realBox.isOverlapped(getObjBox()) == false) + return; + + for (U32 i = 0; i < ShapeBaseData::MaxCollisionShapes; i++) { + if (mDataBlock->collisionDetails[i] != -1) { + Box3F newbox = mDataBlock->collisionBounds[i]; + newbox.min.convolve(mObjScale); + newbox.max.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]; + } + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::queueCollision(ShapeBase* obj, const F32 inData) +{ + // 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; + } + 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->expireTime = time + CollisionTimeoutValue; + ptr->next = mTimeoutList; + if (inData != -1e9) + { + ptr->useData = true; + ptr->data = inData; + } + else + { + ptr->useData = false; + } + + 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->object = 0; + + if(!bool(safeThis)) + return; + + if(bool(safePtr)) + safePtr->onCollision(this); + + if(!bool(safeThis)) + return; + } + } +} + +void ShapeBase::onCollision(ShapeBase* object) +{ + if (!isGhost()) + Con::executef(mDataBlock,3,"onCollision",scriptThis(),object->scriptThis()); +} + +//-------------------------------------------------------------------------- +bool ShapeBase::pointInWater( Point3F &point ) +{ + SimpleQueryList sql; + if (isServerObject()) + gServerSceneGraph->getWaterObjectList(sql); + else + gClientSceneGraph->getWaterObjectList(sql); + + for (U32 i = 0; i < sql.mList.size(); i++) + { + WaterBlock* pBlock = dynamic_cast(sql.mList[i]); + if (pBlock && pBlock->isPointSubmergedSimple( point )) + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- + +bool ShapeBase::writePacketData(GameConnection *connection, BitStream *stream) +{ + bool ret = Parent::writePacketData(connection, stream); + + stream->write(getEnergyLevel()); + stream->write(mRechargeRate); + return ret; +} + +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 & (DamageMask | SoundMask | ThreadMask | ImageMask | CloakMask | MountedMask | InvincibleMask | ShieldMask))) + return retMask; + + if (stream->writeFlag(mask & DamageMask)) { + stream->writeFloat(mClampF(mDamage / mDataBlock->maxDamage, 0.f, 1.f), DamageLevelBits); + stream->writeInt(mDamageState,NumDamageStateBits); + stream->writeFlag(blowApart); + 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->writeFlag(st.forward); + stream->writeFlag(st.atEnd); + } + } + } + + 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); + if (stream->writeFlag(image.skinTag)) + { + stream->writeInt(image.skinTag,NetStringTable::StringIdBitSize); + con->checkString(image.skinTag); + } + stream->writeFlag(image.wet); + stream->writeFlag(image.ammo); + stream->writeFlag(image.loaded); + stream->writeFlag(image.target); + stream->writeFlag(image.triggerDown); + stream->writeInt(image.fireCount,3); + if (mask & InitialUpdateMask) + stream->writeFlag(isImageFiring(i)); + } + } + + if (stream->writeFlag(mask & (ShieldMask | CloakMask | InvincibleMask))) + { + if(stream->writeFlag(mask & CloakMask)) + { + // 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 & ShieldMask)) + { + stream->writeNormalVector(mShieldNormal, ShieldNormalBits); + stream->writeFloat( getEnergyValue(), EnergyLevelBits ); + } + if (stream->writeFlag(mask & InvincibleMask)) + { + stream->write(mInvincibleTime); + stream->write(mInvincibleSpeed); + } + } + + if (mask & MountedMask) { + if (mMount.object) { + S32 gIndex = con->getGhostIndex(mMount.object); + if (stream->writeFlag(gIndex != -1)) { + stream->writeFlag(true); + stream->writeInt(gIndex,10); + 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)); + blowApart = stream->readFlag(); + stream->readNormalVector( &damageDir, 8 ); + if ((prevState != Destroyed && mDamageState == Destroyed || ( blowApart )) && 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); + st.forward = stream->readFlag(); + 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]; + if ((st.play = stream->readFlag()) == true) { + st.profile = (AudioProfile*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + if (isProperlyAdded()) + updateAudioState(st); + } + } + } + + bool checkSkinNeeded = false; + + 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; + } + } + U32 skinTag = 0; + if (stream->readFlag()) + { + skinTag = stream->readInt(NetStringTable::StringIdBitSize); + checkSkinNeeded = true; + } + image.wet = stream->readFlag(); + image.ammo = stream->readFlag(); + image.loaded = stream->readFlag(); + image.target = stream->readFlag(); + image.triggerDown = stream->readFlag(); + int count = stream->readInt(3); + if (image.dataBlock != imageData || image.desiredTag != skinTag) + setImage(i,imageData,skinTag,image.loaded,image.ammo,image.triggerDown); + + if (isProperlyAdded()) { + // Normal processing + if (count != image.fireCount) + { + image.fireCount = count; + setImageState(i,getImageFireState(i),true); + + if( imageData && imageData->lightType == ShapeBaseImageData::WeaponFireLight ) + { + mLightTime = 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()) // cloaked and control + { + + 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()) // shielded + { + // Cloaking, Shield, and invul masking + Point3F shieldNormal; + stream->readNormalVector(&shieldNormal, ShieldNormalBits); + F32 energyPercent = stream->readFloat(EnergyLevelBits); + if (isProperlyAdded()) + playShieldEffect(mShieldNormal,energyPercent); + } + if (stream->readFlag()) { + F32 time, speed; + stream->read(&time); + stream->read(&speed); + setupInvincibleEffect(time, speed); + } + } + + if(checkSkinNeeded) + checkSkin(); + + if (stream->readFlag()) { + if (stream->readFlag()) { + S32 gIndex = stream->readInt(10); + ShapeBase* 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(); + } +} + +class CheckSkinEvent : public SimEvent +{ + public: + void process(SimObject *object) + { + ((ShapeBase *) object)->checkSkin(); + } +}; + +void ShapeBase::targetInfoChanged(TargetInfo *info) +{ + Parent::targetInfoChanged(info); + + if(isServerObject() || !info) + return; + + U32 desiredPrefSkin = info->skinPrefTag; + U32 desiredSkin = info->skinTag; + + if((sUsePrefSkins && ((desiredPrefSkin != mSkinPrefTag) || !mSkinPrefTag)) + || (!sUsePrefSkins && (mSkinTag != desiredSkin))) + { + const char *newBase; + const char *newPref = ""; + mSkinTag = desiredSkin; + mSkinPrefTag = desiredPrefSkin; + + if(!mSkinTag) + newBase = "base"; + else + newBase = gNetStringTable->lookupString(mSkinTag); + if(mSkinPrefTag) + newPref = gNetStringTable->lookupString(mSkinPrefTag); + reSkin(mShapeInstance, newBase, newPref); + mSkinHash = _StringTable::hashString(newBase) ^ _StringTable::hashString(newPref); + } +} + +void ShapeBase::checkSkin() +{ + NetConnection *tosv = NetConnection::getServerConnection(); + + // now check the images + for(int i = 0; i < MaxMountedImages; i++) + { + MountedImage& image = mMountedImageList[i]; + if(image.dataBlock && image.skinTag != image.desiredTag) + { + const char *newBase = NULL; + if(image.desiredTag) + { + if(!tosv) + return; + U32 localString = tosv->translateRemoteStringId(image.desiredTag); + if(!localString) + { + Sim::postEvent( this, new CheckSkinEvent, Sim::getCurrentTime() + 1000 ); + return; + } + newBase = gNetStringTable->lookupString(localString); + } + else + newBase = "base"; + image.skinTag = image.desiredTag; + reSkin(image.shapeInstance, newBase, ""); + } + } +} + + +//-------------------------------------------------------------------------- +void ShapeBase::forceUncloak(const char * reason) +{ + AssertFatal(isServerObject(), "ShapeBase::forceUncloak: server only call"); + if(!mCloaked) + return; + + Con::executef(mDataBlock, 3, "onForceUncloak", scriptThis(), reason ? reason : ""); +} + +void ShapeBase::setCloakedState(bool cloaked) +{ + if (cloaked == mCloaked) + return; + + if (isServerObject()) + setMaskBits(CloakMask); + + // 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) { + // need to set a mask bit to make the ghost manager delete copies of this object + // hacky, but oh well. + setMaskBits(CloakMask); + if (mHidden) + addToScene(); + else + removeFromScene(); + + mHidden = hidden; + } +} + +//-------------------------------------------------------------------------- +void ShapeBase::setLockedTarget(ShapeBase* pSB) +{ + AssertFatal(isServerObject(), "Shouldn't be called on the client!"); + + if (pSB != NULL) { + if( mCurrLockTarget.isNull() == false && useTargetAudio() ) + { + mCurrLockTarget->decLockCount(); + } + + mCurrLockTarget = pSB; + mLockedOn = LockObject; + + if(useTargetAudio()) + pSB->incLockCount(); + } else { + + if( mCurrLockTarget && useTargetAudio() ) + mCurrLockTarget->decLockCount(); + + mCurrLockTarget = NULL; + mLockedOn = NotLocked; + } +} + + +//-------------------------------------------------------------------------- +void ShapeBase::setLockedTargetPosition(const Point3F& pos) +{ + mCurrLockTarget = NULL; + mCurrLockPosition = pos; + mLockedOn = LockPosition; +} + +//-------------------------------------------------------------------------- +bool ShapeBase::isLocked() const +{ + return mLockedOn != NotLocked; +} + +S32 ShapeBase::getLockedTargetId() const +{ + if (bool(mCurrLockTarget)) + return mCurrLockTarget->getId(); + else + return 0; +} + +const Point3F& ShapeBase::getLockedPosition() const +{ + return mCurrLockPosition; +} + +//-------------------------------------------------------------------------- +void ShapeBase::setControlDirty() +{ + if(mControllingClient) + mControllingClient->setControlObjectDirty(); +} + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +Box3F ShapeBaseConvex::getBoundingBox() const +{ + return getBoundingBox(mObject->getTransform(), mObject->getScale()); +} + +Box3F ShapeBaseConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const +{ + Box3F newBox = box; + newBox.min.convolve(scale); + newBox.max.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]); +} + + +//-------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +static void cSetHidden(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + obj->setHidden(dAtob(argv[2])); +} + +static bool cIsHidden(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->isHidden(); +} + +//---------------------------------------------------------------------------- + +static bool cPlayAudio(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + U32 slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + AudioProfile* profile; + if (Sim::findObject(argv[3],profile)) { + obj->playAudio(slot,profile); + return true; + } + } + return false; +} + +static bool cStopAudio(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + U32 slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + obj->stopAudio(slot); + return true; + } + return false; +} + + +//---------------------------------------------------------------------------- + +static bool cPlayThread(SimObject *ptr, S32 argc, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + U32 slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + if (argc == 4) { + if (obj->getShape()) { + S32 seq = obj->getShape()->findSequence(argv[3]); + if (seq != -1 && obj->setThreadSequence(slot,seq)) + return true; + } + } + else + if (obj->playThread(slot)) + return true; + } + return false; +} + +static bool cSetThreadDir(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + if (obj->setThreadDir(slot,dAtob(argv[3]))) + return true; + } + return false; +} + +static bool cStopThread(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + if (obj->stopThread(slot)) + return true; + } + return false; +} + +static bool cPauseThread(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + if (obj->pauseThread(slot)) + return true; + } + return false; +} + + +//---------------------------------------------------------------------------- + +static bool cMountObject(SimObject *ptr, S32, const char **argv) +{ + ShapeBase *target,*obj = static_cast(ptr); + if (Sim::findObject(argv[2],target)) { + S32 node = -1; + dSscanf(argv[3],"%d",&node); + if (node >= 0 && node < ShapeBaseData::NumMountPoints) + obj->mountObject(target,node); + return true; + } + return false; +} + +static bool cUnmountObject(SimObject *ptr, S32, const char **argv) +{ + ShapeBase *target,*obj = static_cast(ptr); + if (Sim::findObject(argv[2],target)) { + obj->unmountObject(target); + return true; + } + return false; +} + +static void cUnmount(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + obj->unmount(); +} + +static bool cIsMounted(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->isMounted(); +} + +static S32 cGetObjectMount(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->isMounted()? obj->getObjectMount()->getId(): 0; +} + +static S32 cGetMountedObjectCount(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getMountedObjectCount(); +} + +static S32 cGetMountedObject(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + ShapeBase* mobj = obj->getMountedObject(dAtoi(argv[2])); + return mobj? mobj->getId(): 0; +} + +static S32 cGetMountedObjectNode(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getMountedObjectNode(dAtoi(argv[2])); +} + +static S32 cGetMountNodeObject(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + ShapeBase* mobj = obj->getMountNodeObject(dAtoi(argv[2])); + return mobj? mobj->getId(): 0; +} + + +//---------------------------------------------------------------------------- + +static bool cMountImage(SimObject *ptr, S32 argc, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + ShapeBaseImageData* imageData; + if (Sim::findObject(argv[2],imageData)) { + U32 slot = dAtoi(argv[3]); + bool loaded = (argc == 5)? dAtob(argv[4]): true; + U32 team = 0; + if(argc == 6) + { + if(argv[5][0] == StringTagPrefixByte) + team = dAtoi(argv[5]+1); + } + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + obj->mountImage(imageData,slot,loaded,team); + } + return false; +} + +static bool cUnmountImage(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return obj->unmountImage(slot); + return false; +} + +static S32 cGetMountedImage(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + if (ShapeBaseImageData* data = obj->getMountedImage(slot)) + return data->getId(); + return 0; +} + +static S32 cGetPendingImage(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + if (ShapeBaseImageData* data = obj->getPendingImage(slot)) + return data->getId(); + return 0; +} + +static bool cIsImageFiring(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return obj->isImageFiring(slot); + return false; +} + +static bool cIsImageMounted(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + ShapeBaseImageData* imageData; + if (Sim::findObject(argv[2],imageData)) + return obj->isImageMounted(imageData); + return false; +} + +static S32 cGetMountSlot(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + ShapeBaseImageData* imageData; + if (Sim::findObject(argv[2],imageData)) + return obj->getMountSlot(imageData); + return -1; +} + +static S32 cGetImageSkinTag(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return obj->getImageSkinTag(slot); + return -1; +} + +static const char* cGetImageState(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return obj->getImageState(slot); + return "Error"; +} + +static bool cGetImageTrigger(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return obj->getImageTriggerState(slot); + return false; +} + +static bool cSetImageTrigger(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + obj->setImageTriggerState(slot,dAtob(argv[3])); + return obj->getImageTriggerState(slot); + } + return false; +} + +static bool cGetImageAmmo(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return obj->getImageAmmoState(slot); + return false; +} + +static bool cSetImageAmmo(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + bool ammo = dAtob(argv[3]); + obj->setImageAmmoState(slot,dAtob(argv[3])); + return ammo; + } + return false; +} + +static bool cGetImageTarget(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return obj->getImageTargetState(slot); + return false; +} + +static bool cSetImageTarget(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + bool target = dAtob(argv[3]); + obj->setImageTargetState(slot,dAtob(argv[3])); + return target; + } + return false; +} + +static bool cGetImageLoaded(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return obj->getImageLoadedState(slot); + return false; +} + +static bool cSetImageLoaded(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + bool loaded = dAtob(argv[3]); + obj->setImageLoadedState(slot, dAtob(argv[3])); + return loaded; + } + return false; +} + +static const char* cGetMuzzleVector(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + VectorF v; + obj->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"; +} + +static const char* cGetMuzzlePoint(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + Point3F p; + obj->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"; +} + +static const char* cGetSlotTransform(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + int slot = dAtoi(argv[2]); + MatrixF xf(true); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + obj->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; +} + +static const char* cGetAIRepairPoint(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + Point3F pos = obj->getAIRepairPoint(); + char* buff = Con::getReturnBuffer(200); + dSprintf(buff,200,"%g %g %g", pos.x,pos.y,pos.z); + return buff; +} + +static const char* cGetVelocity(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + const VectorF& vel = obj->getVelocity(); + char* buff = Con::getReturnBuffer(100); + dSprintf(buff,100,"%g %g %g",vel.x,vel.y,vel.z); + return buff; +} + +static bool cSetVelocity(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + VectorF vel(0,0,0); + dSscanf(argv[2],"%f %f %f",&vel.x,&vel.y,&vel.z); + obj->setVelocity(vel); + obj->setControlDirty(); + return true; +} + +static bool cApplyImpulse(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + Point3F pos(0,0,0); + VectorF vel(0,0,0); + dSscanf(argv[2],"%f %f %f",&pos.x,&pos.y,&pos.z); + dSscanf(argv[3],"%f %f %f",&vel.x,&vel.y,&vel.z); + obj->applyImpulse(pos,vel); + obj->setControlDirty(); + return true; +} + +static const char* cGetEyeVector(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + MatrixF mat; + obj->getEyeTransform(&mat); + VectorF v1(0,1,0),v2; + mat.mulV(v1,&v2); + char* buff = Con::getReturnBuffer(100); + dSprintf(buff, 100,"%g %g %g",v2.x,v2.y,v2.z); + return buff; +} + +static const char* cGetEyeTransform(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + MatrixF mat; + obj->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; +} + +static void cSetEnergyLevel(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + obj->setEnergyLevel(dAtof(argv[2])); + obj->setControlDirty(); +} + +static F32 cGetEnergyLevel(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getEnergyLevel(); +} + +static F32 cGetEnergyPercent(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getEnergyValue(); +} + +static void cSetDamageLevel(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + obj->setDamageLevel(dAtof(argv[2])); +} + +static F32 cGetDamageLevel(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getDamageLevel(); +} + +static F32 cGetDamagePercent(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getDamageValue(); +} + +static bool cSetDamageState(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + return obj->setDamageState(argv[2]); +} + +static const char* cGetDamageState(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getDamageStateName(); +} + +static bool cIsDestroyed(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->isDestroyed(); +} + +static bool cIsDisabled(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getDamageState() != ShapeBase::Enabled; +} + +static bool cIsEnabled(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getDamageState() == ShapeBase::Enabled; +} + +static void cApplyDamage(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + obj->applyDamage(dAtof(argv[2])); +} + +static void cApplyRepair(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + obj->applyRepair(dAtof(argv[2])); +} + +static void cSetRepairRate(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + F32 rate = dAtof(argv[2]); + if(rate < 0) + rate = 0; + obj->setRepairRate(rate); +} + +static F32 cGetRepairRate(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getRepairRate(); +} + +static void cSetRechargeRate(SimObject *ptr, S32, const char **argv) +{ + ShapeBase* obj = static_cast(ptr); + obj->setRechargeRate(dAtof(argv[2])); + obj->setControlDirty(); +} + +static F32 cGetRechargeRate(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + return obj->getRechargeRate(); +} + +static S32 cGetControllingClient(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + if (GameConnection* con = obj->getControllingClient()) + return con->getId(); + return 0; +} + +static S32 cGetControllingObject(SimObject *ptr, S32, const char **) +{ + ShapeBase* obj = static_cast(ptr); + if (ShapeBase* con = obj->getControllingObject()) + return con->getId(); + return 0; +} + +// return true if can cloak, otherwise the reason why object cannot cloak +static const char * cCanCloak(SimObject * obj, S32, const char **) +{ + ShapeBase * shape = static_cast(obj); + + // target? + TargetInfo * targInfo = shape->getTargetInfo(); + if(!targInfo) + return("true"); + + // target is sensorjammed? + if(targInfo->sensorFlags & TargetInfo::EnemySensorJammed) + return("jammed"); + + return("true"); +} + +static void cSetCloaked(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + bool cloaked = dAtob(argv[2]); + if (shape->isServerObject()) + shape->setCloakedState(cloaked); +} + +static bool cIsCloaked(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + return shape->getCloakedState(); +} + +static void cSetPassiveJammed(SimObject * obj, S32, const char ** argv) +{ + ShapeBase * shape = static_cast(obj); + if(shape->isServerObject()) + shape->setPassiveJamState(dAtob(argv[2])); +} + +static bool cIsPassiveJammed(SimObject * obj, S32, const char **) +{ + ShapeBase * shape = static_cast(obj); + return(shape->getPassiveJamState()); +} + +static void cSetDamageFlash(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + F32 flash = dAtof(argv[2]); + if (shape->isServerObject()) + shape->setDamageFlash(flash); +} + +static F32 cGetDamageFlash(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + return shape->getDamageFlash(); +} + +static void cSetWhiteOut(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + F32 flash = dAtof(argv[2]); + if (shape->isServerObject()) + shape->setWhiteOut(flash); +} + +static F32 cGetWhiteOut(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + return shape->getWhiteOut(); +} + +static void cSetHeat(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + F32 heat = dAtof(argv[2]); + if (heat < 0.0f || heat > 1.0f) + heat = heat < 0.0f ? 0.0 : 1.0; + + if (shape->isServerObject()) + shape->setHeat(heat); +} + +static F32 cGetCameraFov(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + if (shape->isServerObject()) + return shape->getCameraFov(); + return 0.0; +} + +static void cSetCameraFov(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + if (shape->isServerObject()) + shape->setCameraFov(dAtof(argv[2])); +} + +static F32 cGetHeat(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + if (shape->isServerObject()) + return shape->getHeat(); + return 0.0; +} + +static void cSetupInvincibleEffect(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + + shape->setupInvincibleEffect(dAtof(argv[2]), dAtof(argv[3])); +} + +static bool cSetLockedTarget(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + if (shape->isServerObject() == false) + return false; + + S32 id = dAtoi(argv[2]); + if (id == 0) { + shape->setLockedTarget(NULL); + return true; + } else { + ShapeBase* pObject; + if (Sim::findObject(id, pObject)) { + shape->setLockedTarget(pObject); + return true; + } else { + shape->setLockedTarget(NULL); + return false; + } + } +} + +static S32 cGetLockedTarget(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + if (shape->isServerObject() == false) + return 0; + + return shape->getLockedTargetId(); +} + + +static const char* cGetLockedPosition(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + if (shape->isServerObject() == false) + return "0 0 0"; + + char* returnBuffer = Con::getReturnBuffer(256); + Point3F center = shape->getLockedPosition(); + char *buff = Con::getReturnBuffer(128); + dSprintf(buff,128, "%f %f %f", center.x, center.y, center.z); + return buff; +} + +static bool cIsLocked(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + if (shape->isServerObject() == false) + return false; + + return shape->isLocked(); +} + +static bool cIsTracking(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + if (shape->isServerObject() == false) + return false; + + return shape->isTracking(); +} + + +static void cPlayShieldEffect( SimObject* obj, S32, const char** argv ) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-shapebase get here?"); + ShapeBase* shape = static_cast(obj); + if (shape->isServerObject() == false) + return; + + Point3F normal; + dSscanf(argv[2], "%f %f %f", &normal.x, &normal.y, &normal.z); + normal.normalize(); + + shape->playShieldEffect(normal); +} + +static void cScopeWhenSensorVisible(SimObject * obj, S32, const char ** argv) +{ + ShapeBase * shapeBase = static_cast(obj); + + if (dAtob(argv[2])) + Sim::getScopeSensorVisibleSet()->addObject(obj); + else + Sim::getScopeSensorVisibleSet()->removeObject(obj); +} + +ConsoleFunction(setShadowDetailLevel, void , 2, 2, "setShadowDetailLevel(val 0...1);") +{ + argc; + F32 val = dAtof(argv[1]); + if (val < 0.0f) + val = 0.0f; + else if (val > 1.0f) + val = 1.0f; + + if (mFabs(Shadow::getGlobalShadowDetailLevel()-val)<0.001f) + return; + + // shadow details determined in two places: + // 1. setGlobalShadowDetailLevel + // 2. static shape header has some #defines that determine + // at what level of shadow detail each type of + // object uses a generic shadow or no shadow at all + Shadow::setGlobalShadowDetailLevel(val); + Con::setFloatVariable("$pref::Shadows", val); +} + +static void cSetDeployRotation(SimObject *obj, S32, const char** argv) +{ + ShapeBase *SB = static_cast(obj); + if(!SB) + return; + + Point3F normal; + Point3F position; + dSscanf(argv[2], "%f %f %f", &position.x, &position.y, &position.z); + dSscanf(argv[3], "%f %f %f", &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 ); + + SB->setTransform( testMat ); +} + +static void cStartFade(SimObject *obj, S32, const char** argv) +{ + U32 fadeTime; + U32 fadeDelay; + bool fadeOut; + + dSscanf(argv[2], "%d", &fadeTime ); + dSscanf(argv[3], "%d", &fadeDelay ); + fadeOut = dAtob(argv[4]); + + if( obj ) + { + ShapeBase *sbo = static_cast< ShapeBase * >( obj ); + if( sbo ) + { + sbo->startFade( fadeTime / 1000.0, fadeDelay / 1000.0, fadeOut ); + } + } + +} + +static void cBlowup(SimObject * obj, S32, const char **) +{ + ShapeBase * shape = static_cast(obj); + + if(shape) + { + shape->blowApart = true; + } +} + +static void cSetMomVector(SimObject * obj, S32, const char **argv) +{ + ShapeBase * shape = static_cast(obj); + + if(shape) + { + VectorF normal; + dSscanf(argv[2], "%f %f %f", &normal.x, &normal.y, &normal.z); + normal.normalize(); + + shape->damageDir.set( normal.x, normal.y, normal.z ); + } +} + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +void ShapeBase::initPersistFields() +{ + Parent::initPersistFields(); +} + +void ShapeBase::consoleInit() +{ + Con::addVariable("SB::DFDec", TypeF32, &sDamageFlashDec); + Con::addVariable("SB::WODec", TypeF32, &sWhiteoutDec); + Con::addVariable("pref::environmentMaps", TypeBool, &gRenderEnvMaps); + Con::addVariable("pref::usePrefSkins", TypeBool, &sUsePrefSkins); + + // + Con::addCommand("ShapeBase", "playAudio", cPlayAudio, "obj.playAudio(slot,AudioProfile)", 4, 4); + Con::addCommand("ShapeBase", "stopAudio", cStopAudio, "obj.playAudio(slot)", 3, 3); + + Con::addCommand("ShapeBase", "playThread", cPlayThread, "obj.playThread(thread,)", 3, 4); + Con::addCommand("ShapeBase", "setThreadDir", cSetThreadDir, "obj.setThreadDir(thread,bool)", 4, 4); + Con::addCommand("ShapeBase", "stopThread", cStopThread, "obj.stopThread(thread)", 3, 3); + Con::addCommand("ShapeBase", "pauseThread", cPauseThread, "obj.pauseThread(thread)", 3, 3); + + Con::addCommand("ShapeBase", "mountObject", cMountObject, "obj.mountObject(object,node)", 4, 4); + Con::addCommand("ShapeBase", "unmountObject", cUnmountObject, "obj.unmountObject(object)", 3, 3); + Con::addCommand("ShapeBase", "unmount", cUnmount, "obj.unmount()", 2, 2); + Con::addCommand("ShapeBase", "isMounted", cIsMounted, "obj.isMounted()", 2, 2); + Con::addCommand("ShapeBase", "getObjectMount", cGetObjectMount, "obj.getObjectMount()", 2, 2); + Con::addCommand("ShapeBase", "getMountedObjectCount", cGetMountedObjectCount, "obj.getMountedObjectCount()", 2, 2); + Con::addCommand("ShapeBase", "getMountedObjectNode", cGetMountedObjectNode, "obj.getMountedObjectNode(index)", 3, 3); + Con::addCommand("ShapeBase", "getMountedObject", cGetMountedObject, "obj.getMountedObject(index)", 3, 3); + Con::addCommand("ShapeBase", "getMountNodeObject", cGetMountNodeObject, "obj.getMountNodeObject(node)", 3, 3); + + Con::addCommand("ShapeBase", "mountImage", cMountImage, "obj.mountImage(DataBlock,slot,[loaded=true],[skinTag])", 4, 6); + Con::addCommand("ShapeBase", "unmountImage", cUnmountImage, "obj.unmountImage(slot)", 3, 3); + Con::addCommand("ShapeBase", "getMountedImage", cGetMountedImage, "obj.getMountedImage(slot)", 3, 3); + Con::addCommand("ShapeBase", "getPendingImage", cGetPendingImage, "obj.getPendingImage(slot)", 3, 3); + Con::addCommand("ShapeBase", "isImageFiring", cIsImageFiring, "obj.isImageFiring(slot)", 3, 3); + Con::addCommand("ShapeBase", "isImageMounted", cIsImageMounted, "obj.isImageMounted(DataBlock)", 3, 3); + Con::addCommand("ShapeBase", "getMountSlot", cGetMountSlot, "obj.getMountSlot(DataBlock)", 3, 3); + Con::addCommand("ShapeBase", "getImageSkinTag", cGetImageSkinTag, "obj.getImageSkinTag(slot)", 3, 3); + Con::addCommand("ShapeBase", "getImageState", cGetImageState, "obj.getImageState(slot)", 3, 3); + Con::addCommand("ShapeBase", "getImageTrigger", cGetImageTrigger, "obj.getImageTrigger(slot)", 3, 3); + Con::addCommand("ShapeBase", "setImageTrigger", cSetImageTrigger, "obj.setImageTrigger(slot,bool)", 4, 4); + Con::addCommand("ShapeBase", "getImageAmmo", cGetImageAmmo, "obj.getImageAmmo(slot)", 3, 3); + Con::addCommand("ShapeBase", "setImageAmmo", cSetImageAmmo, "obj.setImageAmmo(slot,bool)", 4, 4); + Con::addCommand("ShapeBase", "getImageTarget", cGetImageTarget, "obj.getImageTarget(slot)", 3, 3); + Con::addCommand("ShapeBase", "setImageTarget", cSetImageTarget, "obj.setImageTarget(slot,bool)", 4, 4); + Con::addCommand("ShapeBase", "getImageLoaded", cGetImageLoaded, "obj.getImageLoaded(slot)", 3, 3); + Con::addCommand("ShapeBase", "setImageLoaded", cSetImageLoaded, "obj.setImageLoaded(slot,bool)", 4, 4); + Con::addCommand("ShapeBase", "getMuzzleVector", cGetMuzzleVector, "obj.getMuzzleVector(slot)", 3, 3); + Con::addCommand("ShapeBase", "getMuzzlePoint", cGetMuzzlePoint, "obj.getMuzzlePoint(slot)", 3, 3); + Con::addCommand("ShapeBase", "getSlotTransform", cGetSlotTransform, "obj.getSlotTransform(slot)", 3, 3); + Con::addCommand("ShapeBase", "getAIRepairPoint", cGetAIRepairPoint, "obj.getAIRepairPoint()", 2, 2); + + Con::addCommand("ShapeBase", "getVelocity", cGetVelocity, "obj.getVelocity()", 2, 2); + Con::addCommand("ShapeBase", "setVelocity", cSetVelocity, "obj.setVelocity(Vector)", 3, 3); + Con::addCommand("ShapeBase", "applyImpulse", cApplyImpulse, "obj.applyImpulse(Pos,Vector)", 4, 4); + Con::addCommand("ShapeBase", "getEyeVector", cGetEyeVector, "obj.getEyeVector()", 2, 2); + Con::addCommand("ShapeBase", "getEyeTransform", cGetEyeTransform, "obj.getEyeTransform()", 2, 2); + + Con::addCommand("ShapeBase", "setEnergyLevel", cSetEnergyLevel, "obj.setEnergyLevel(value)", 3, 3); + Con::addCommand("ShapeBase", "getEnergyLevel", cGetEnergyLevel, "obj.getEnergyLevel()", 2, 2); + Con::addCommand("ShapeBase", "getEnergyPercent", cGetEnergyPercent, "obj.getEnergyPercent()", 2, 2); + Con::addCommand("ShapeBase", "setDamageLevel", cSetDamageLevel, "obj.setDamageLevel(value)", 3, 3); + Con::addCommand("ShapeBase", "getDamageLevel", cGetDamageLevel, "obj.getDamageLevel()", 2, 2); + Con::addCommand("ShapeBase", "getDamagePercent", cGetDamagePercent, "obj.getDamagePercent()", 2, 2); + Con::addCommand("ShapeBase", "setDamageState", cSetDamageState, "obj.setDamageState(state)", 3, 3); + Con::addCommand("ShapeBase", "getDamageState", cGetDamageState, "obj.getDamageState()", 2, 2); + Con::addCommand("ShapeBase", "isDestroyed", cIsDestroyed, "obj.isDestroyed()", 2, 2); + Con::addCommand("ShapeBase", "isDisabled", cIsDisabled, "obj.isDisabled()", 2, 2); + Con::addCommand("ShapeBase", "isEnabled", cIsEnabled, "obj.isEnabled()", 2, 2); + + Con::addCommand("ShapeBase", "applyDamage", cApplyDamage, "obj.applyDamage(value)", 3, 3); + Con::addCommand("ShapeBase", "applyRepair", cApplyRepair, "obj.applyRepair(value)", 3, 3); + + Con::addCommand("ShapeBase", "setRepairRate", cSetRepairRate, "obj.setRepairRate(value)", 3, 3); + Con::addCommand("ShapeBase", "getRepairRate", cGetRepairRate, "obj.getRepairRate()", 2, 2); + Con::addCommand("ShapeBase", "setRechargeRate", cSetRechargeRate, "obj.setRechargeRate(value)", 3, 3); + Con::addCommand("ShapeBase", "getRechargeRate", cGetRechargeRate, "obj.getRechargeRate()", 2, 2); + + Con::addCommand("ShapeBase", "getControllingClient", cGetControllingClient, "obj.getControllingClient()", 2, 2); + Con::addCommand("ShapeBase", "getControllingObject", cGetControllingObject, "obj.getControllingObject()", 2, 2); + + Con::addCommand("ShapeBase", "canCloak", cCanCloak, "obj.canCloak()", 2, 2); + Con::addCommand("ShapeBase", "setCloaked", cSetCloaked, "obj.setCloaked(true|false)", 3, 3); + Con::addCommand("ShapeBase", "isCloaked", cIsCloaked, "obj.isCloaked()", 2, 2); + + Con::addCommand("ShapeBase", "setPassiveJammed", cSetPassiveJammed, "obj.setPassiveJammed(true|false)", 3, 3); + Con::addCommand("ShapeBase", "isPassiveJammed", cIsPassiveJammed, "obj.isPassiveJammed()", 2, 2); + + Con::addCommand("ShapeBase", "setDamageFlash", cSetDamageFlash, "obj.setDamageFlash(flash level)", 3, 3); + Con::addCommand("ShapeBase", "getDamageFlash", cGetDamageFlash, "obj.getDamageFlash()", 2, 2); + Con::addCommand("ShapeBase", "setWhiteOut", cSetWhiteOut, "obj.setWhiteOut(flash level)", 3, 3); + Con::addCommand("ShapeBase", "getWhiteOut", cGetWhiteOut, "obj.getWhiteOut()", 2, 2); + Con::addCommand("ShapeBase", "setInvincibleMode", cSetupInvincibleEffect, "obj.setInvincibleMode(time , speed)", 4, 4); + + Con::addCommand("ShapeBase", "getCameraFov", cGetCameraFov, "obj.getCameraFov()", 2, 2); + Con::addCommand("ShapeBase", "setCameraFov", cSetCameraFov, "obj.setCameraFov(fov)", 3, 3); + + Con::addCommand("ShapeBase", "getHeat", cGetHeat, "obj.getHeat()", 2, 2); + Con::addCommand("ShapeBase", "setHeat", cSetHeat, "obj.getHeat(heat [0..1])", 3, 3); + + Con::addCommand("ShapeBase", "setLockedTarget", cSetLockedTarget, "obj.setLockedTarget(id)", 3, 3); + Con::addCommand("ShapeBase", "getLockedTarget", cGetLockedTarget, "obj.getLockedTarget()", 2, 2); + Con::addCommand("ShapeBase", "getLockedPosition", cGetLockedPosition, "obj.getLockedPosition()", 2, 2); + Con::addCommand("ShapeBase", "isLocked", cIsLocked, "obj.isLocked()", 2, 2); + Con::addCommand("ShapeBase", "isTracking", cIsTracking, "obj.isTracking()", 2, 2); + + Con::addCommand("ShapeBase", "hide", cSetHidden, "obj.hide(bool)", 3, 3); + Con::addCommand("ShapeBase", "isHidden", cIsHidden, "obj.isHidden()", 2, 2); + + Con::addCommand("ShapeBase", "playShieldEffect", cPlayShieldEffect, "obj.playShieldEffect( Vector )", 3, 3 ); + Con::addCommand("ShapeBase", "scopeWhenSensorVisible", cScopeWhenSensorVisible, "obj.scopeWhenSensorVisible(bool)", 3, 3); + + Con::addCommand("ShapeBase", "setDeployRotation", cSetDeployRotation, "setDeployRotation( normal )", 4, 4); + Con::addCommand("ShapeBase", "startFade", cStartFade, "startFade( U32, U32, bool )", 5, 5); + Con::addCommand("ShapeBase", "setMomentumVector", cSetMomVector, "obj.setMomentumVector()", 3, 3); + Con::addCommand("ShapeBase", "blowup", cBlowup, "obj.blowup()", 2, 2); +} + +bool ShapeBase::isInvincible() +{ + if( mDataBlock ) + { + return mDataBlock->isInvincible; + } + return false; +} + +void ShapeBase::startFade( F32 fadeTime, F32 fadeDelay, bool fadeOut ) +{ + setMaskBits(CloakMask); + mFadeElapsedTime = 0; + mFading = true; + if(fadeDelay < 0) + fadeDelay = 0; + if(fadeTime < 0) + fadeTime = 0; + mFadeTime = fadeTime; + mFadeDelay = fadeDelay; + mFadeOut = fadeOut; + mFadeVal = F32(mFadeOut); +} + + +//-------------------------------------------------------------------------- +// Get node transform +//-------------------------------------------------------------------------- +MatrixF ShapeBase::getNodeTransform( StringTableEntry nodeName ) +{ + TSShape *shape = mShapeInstance->getShape(); + if( !shape ) + { + return MatrixF(true); + } + + S32 node = shape->findNode( nodeName ); + if( node < 0 ) + { + return MatrixF(true); + } + + return mShapeInstance->mNodeTransforms[node]; +} diff --git a/game/shapeBase.h b/game/shapeBase.h new file mode 100644 index 0000000..bc7767a --- /dev/null +++ b/game/shapeBase.h @@ -0,0 +1,984 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHAPEBASE_H_ +#define _SHAPEBASE_H_ + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _MATERIALLIST_H_ +#include "dgl/materialList.h" +#endif +#ifndef _PLATFORMAUDIO_H_ +#include "platform/platformAudio.h" +#endif +#ifndef _MOVEMANAGER_H_ +#include "game/moveManager.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "scenegraph/lightManager.h" +#endif +#ifndef _SHIELDIMPACT_H_ +#include "game/shieldImpact.h" +#endif + +class TSShapeInstance; +class Shadow; +class SceneState; +class SceneRenderImage; +class TSShape; +class TSThread; +class GameConnection; +struct CameraScopeQuery; +//class AudioProfile; +class ParticleEmitter; +class ParticleEmitterData; +class ProjectileData; +class ExplosionData; +struct DebrisData; +class CommanderIconData; + +typedef void* Light; + + +//-------------------------------------------------------------------------- + +extern void collisionFilter(SceneObject* object,S32 key); +extern void defaultFilter(SceneObject* object,S32 key); + + + +class ShapeImageRenderImage : public SceneRenderImage +{ + public: + ShapeBase* mSBase; + U32 mIndex; +}; + + + +//-------------------------------------------------------------------------- +class ShapeBaseConvex : public Convex +{ + typedef Convex Parent; + friend class ShapeBase; + friend class Vehicle; + + protected: + ShapeBase* pShapeBase; + public: + U32 hullId; + Box3F box; + + public: + ShapeBaseConvex() { mType = ShapeBaseConvexType; } + ShapeBaseConvex(const ShapeBaseConvex& cv) { + mObject = cv.mObject; + pShapeBase = cv.pShapeBase; + 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); +}; + +//-------------------------------------------------------------------------- + +struct ShapeBaseImageData: public GameBaseData { + private: + typedef GameBaseData Parent; + + public: + enum Constants { + MaxStates = 31, // Get one less that state bits because of + NumStateBits = 5, // the way data is packed. + NumPotentialTargets = 32 + }; + enum LightType { + NoLight = 0, + ConstantLight, + PulsingLight, + WeaponFireLight, + NumLightTypes + }; + struct StateData { + StateData(); + const char* name; // State 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 wet[2]; // wet/notWet + S32 timeout; // Transition after delay + } transition; + bool ignoreLoadedForReady; + + // State attributes + bool fire; // Can only have one fire state + bool ejectShell; // Shoot shell casing out + bool allowImageChange; + bool scaleAnimation; // Scale animation fit state timeout value + bool direction; // Animation direction + bool waitForTimeout; // Don't allow transitions if there is a timeout + F32 timeoutValue; // delay until next sequence + F32 energyDrain; // Drain energy during this state + enum LoadedState { + IgnoreLoaded, + Loaded, + NotLoaded, + NumLoadedBits = 3 + } loaded; // Is the image considered loaded + enum SpinState { + IgnoreSpin, + NoSpin, + SpinUp, + SpinDown, + FullSpin, + NumSpinBits = 3 + } spin; // Spin thread control + enum RecoilState { + NoRecoil, + LightRecoil, + MediumRecoil, + HeavyRecoil, + NumRecoilBits = 3 + } recoil; + bool flashSequence; + S32 sequence; // Main thread sequence + S32 sequenceVis; // Vis thread sequence + const char* script; + ParticleEmitterData* emitter; + AudioProfile* sound; + F32 emitterTime; + S32 emitterNode; + }; + + // Individual state data used to initialize struct array + 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* 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]; + + AudioProfile* stateSound[MaxStates]; + const char* stateScript[MaxStates]; + const char* fireStateName; + ParticleEmitterData* stateEmitter[MaxStates]; + F32 stateEmitterTime[MaxStates]; + const char* stateEmitterNode[MaxStates]; + + // + bool emap; + bool firstPersonOnly; + + StringTableEntry shapeName; // Max shape to render + U32 mountPoint; + MatrixF offsetTransform; + bool firstPerson; // Render the image when first person + + ProjectileData* projectile; // Projectile block fired by this image, + + bool isSeeker; + bool useTargetAudio; + F32 seekRadius; + F32 maxSeekAngle; + F32 seekTime; + F32 minSeekHeat; + F32 targetingDist; + + F32 mass; + bool usesEnergy; // Uses energy instead off ammo + F32 minEnergy; // Min amount for ammo state to be true + bool accuFire; // Converge with crosshair + bool cloakable; // is this image cloakable when mounted + + // Lighting + S32 lightType; + ColorF lightColor; + S32 lightTime; + F32 lightRadius; + LightInfo mLight; + + // Data initialized onAdd + Resource shape; // Shape handle + U32 mCRC; + bool computeCRC; + MatrixF mountTransform; // MountPoint node * offsetTransform + S32 retractNode; + S32 muzzleNode; + S32 emitterNode; + S32 spinSequence; + S32 ambientSequence; + bool isAnimated; // Contains at least one animated states + bool hasFlash; // Contains at least one flash animation state + S32 fireState; // The fire state set on the client + + // shell casing data + DebrisData * casing; + S32 casingID; + Point3F shellExitDir; + F32 shellExitVariance; // angle in degrees that the exit dir deviates + F32 shellVelocity; + + + // State array is initialized onAdd from the individual state + // struct array elements. + StateData state[MaxStates]; + bool statesLoaded; + + // + DECLARE_CONOBJECT(ShapeBaseImageData); + ShapeBaseImageData(); + ~ShapeBaseImageData(); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + S32 lookupState(const char* name); + static void consoleInit(); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + void registerImageLights(LightManager * lightManager, bool lightingScene, const Point3F &objectPosition, U32 startTime ); +}; + + +//-------------------------------------------------------------------------- + +struct ShapeBaseData : public GameBaseData { + private: + typedef GameBaseData Parent; + + public: + enum Constants { + NumMountPoints = 32, + NumMountPointBits = 5, + MaxCollisionShapes = 8, + AIRepairNode = 31 + }; + + StringTableEntry shapeName; + DebrisData * debris; + S32 debrisID; + StringTableEntry debrisShapeName; + Resource debrisShape; + F32 heat; + + + ExplosionData* explosion; + S32 explosionID; + + ExplosionData* underwaterExplosion; + S32 underwaterExplosionID; + + F32 mass; + F32 drag; + F32 density; + F32 maxEnergy; + F32 maxDamage; + F32 repairRate; // Rate per tick. + + F32 disabledLevel; + F32 destroyedLevel; + + S32 shieldEffectLifetimeMS; + + // First/Third person camera + F32 cameraMaxDist; // Distance from eye + F32 cameraMinDist; // Distance from eye + + // FOV + F32 cameraDefaultFov; // default fov (in degrees) + F32 cameraMinFov; // min fov allowed (in degrees) + F32 cameraMaxFov; // max fov for object (in degrees) + + // Data initialized on preload + Resource shape; // Shape handle + U32 mCRC; + bool computeCRC; + + S32 eyeNode; // Shape's eye node index + S32 cameraNode; // Shape's camera node index + S32 shadowNode; // Move shadow center as this node moves + S32 mountPointNode[NumMountPoints]; // Node index of mountPoint + S32 debrisDetail; // Detail level used to generate debris + S32 damageSequence; // Damage level decals + S32 hulkSequence; // Destroyed hulk + + CommanderIconData * cmdIcon; // commander icon + SimObjectId cmdIconId; // id of icon + F32 sensorRadius; // sensor radius (0 = not a sensor) + ColorI sensorColor; // color of sensor + StringTableEntry cmdCategory; // category this belongs in + StringTableEntry cmdMiniIconName; // list icon name + bool canControl; // can this object be controlled? + bool canObserve; // may look at object in commander map? + bool observeThroughObject; // observe this object through its camera transform and default fov + + TextureHandle cmdMiniIcon; // icon in commander list + + // hud images... + 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]; + + S32 collisionDetails[8]; // Detail level used to collide with + Box3F collisionBounds[8]; // Detail level bounding boxes + + S32 LOSDetails[8]; // Detail level used to collide with + + // these are set by derived classes, not by script file + // they control when shadows are rendered (and when generic shadows are substituted) + F32 genericShadowLevel; + F32 noShadowLevel; + + bool hackDisallowDamage; + bool emap; + bool firstPersonOnly; + bool useEyePoint; + bool aiAvoidThis; //if set, the AI's will try to walk around this object... + bool isInvincible; // if set, object cannot take damage (wont show up with damage bar either) + bool renderWhenDestroyed; // if set, will not render this obj when destroyed + + bool inheritEnergyFromMount; + + bool preload(bool server, char errorBuffer[256]); + void computeAccelerator(U32 i); + S32 findMountPoint(U32 n); + + // The derived class should provide the following: + DECLARE_CONOBJECT(ShapeBaseData); + ShapeBaseData(); + ~ShapeBaseData(); + static void consoleInit(); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + +//---------------------------------------------------------------------------- +class ShapeBase : public GameBase +{ + typedef GameBase Parent; + friend class ShapeBaseConvex; + friend void physicalZoneFind(SceneObject*, S32); + + void fade( F32 dt ); + + ShieldImpact mShieldEffect; + bool mFlipFadeVal; + U32 mLightTime; + + public: + F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips); + + enum PublicConstants { + ThreadSequenceBits = 5, + MaxSequenceIndex = (1 << ThreadSequenceBits) - 1, + EnergyLevelBits = 5, + DamageLevelBits = 6, + DamageStateBits = 2, + MaxSoundThreads = 4, // Should be a power of 2 + MaxScriptThreads = 4, // Should be a power of 2 + MaxMountedImages = 8, // Should be a power of 2 + MaxImageEmitters = 3, + NumImageBits = 3, + ShieldNormalBits = 8, + NumHeatBits = 5, + CollisionTimeoutValue = 250 // Timeout in Ms. + }; + + enum DamageState { + // These enums index into the sDamageStateName array + Enabled, + Disabled, + Destroyed, + NumDamageStates, + NumDamageStateBits = 2, + }; + + static bool sUsePrefSkins; + protected: + ShapeBaseData* mDataBlock; + GameConnection* mControllingClient; // Controlling client + ShapeBase* mControllingObject; + bool mTrigger[MaxTriggerKeys]; + + // Scripted Sound + struct Sound { + bool play; + SimTime timeout; + AudioProfile* profile; // Profile on server + AUDIOHANDLE sound; // Handle on client + }; + Sound mSoundThread[MaxSoundThreads]; + + // Scripted Animation Threads + struct Thread { + enum State { + Play, Stop, Pause + }; + TSThread* thread; + U32 state; + S32 sequence; + U32 sound; + bool atEnd; + bool forward; + }; + Thread mScriptThread[MaxScriptThreads]; + +public: + // Mounted Images. + struct MountedImage { + ShapeBaseImageData* dataBlock; + ShapeBaseImageData::StateData *state; + ShapeBaseImageData* nextImage; + U32 skinTag; + U32 desiredTag; + bool loaded; // Loaded with ammo + U32 nextTeam; + bool nextLoaded; + F32 delayTime; // Current state countdown + U32 fireCount; // Fire skip count + bool triggerDown; // Current trigger state + bool ammo; // Set internally if using energy + bool target; + bool wet; + + TSShapeInstance* shapeInstance; + TSThread *ambientThread; + TSThread *visThread; + TSThread *animThread; + TSThread *flashThread; + TSThread *spinThread; + + //TSLight light; + SimTime lightStart; + bool animLoopingSound; + AUDIOHANDLE animSound; + + struct ImageEmitter { + S32 node; + F32 time; + SimObjectPtr emitter; + }; + ImageEmitter emitter[MaxImageEmitters]; + + // + MountedImage(); + ~MountedImage(); + }; + +protected: + MountedImage mMountedImageList[MaxMountedImages]; + U32 mActiveImage; + + // Collision Notification + public: + struct CollisionTimeout { + CollisionTimeout* next; + ShapeBase* object; + U32 objectNumber; + SimTime expireTime; + bool useData; + F32 data; + }; + enum LockMode { + NotLocked = 0, + LockObject = 1, + LockPosition = 2 + }; + F32 mLiquidHeight; + F32 mWaterCoverage; + + bool blowApart; + VectorF damageDir; + + protected: + CollisionTimeout* mTimeoutList; + + void scriptCallback(U32 imageSlot,const char* function); + void updateMass(); + virtual void setImage(U32 imageSlot, ShapeBaseImageData* imageData, U32 teamTag = 0, + bool loaded = true, bool ammo = false, bool triggerDown = false, + bool target = false); + void resetImageSlot(U32 imageSlot); + U32 getImageFireState(U32 imageSlot); + void setImageState(U32 imageSlot, U32 state, bool force = false); + void updateImageAnimation(U32 imageSlot, F32 dt); + void updateImageState(U32 imageSlot,F32 dt); + void startImageEmitter(MountedImage&,ShapeBaseImageData::StateData&); + Light* getImageLight(U32 imageSlot); + void updateServerAudio(); + void updateAudioState(Sound& st); + void updateAudioPos(); + protected: + F32 mEnergy; + F32 mRechargeRate; // Energy/tick recharge rate + bool mChargeEnergy; + + F32 mMass; + F32 mOneOverMass; + F32 mDrag; // Container drag + F32 mBuoyancy; // Container buoyancy factor + U32 mLiquidType; + + Point3F mAppliedForce; + F32 mGravityMod; + + F32 mDamageFlash; + F32 mWhiteOut; + F32 mHeat; + + protected: + // Last shield direction + Point3F mShieldNormal; + + // Invincible + F32 mInvincibleCount; + F32 mInvincibleTime; + F32 mInvincibleSpeed; + F32 mInvincibleDelta; + F32 mInvincibleEffect; + F32 mInvincibleFade; + bool mInvincibleOn; + + // Mounted objects + struct MountInfo { + ShapeBase* list; // Objects mounted on this object + ShapeBase* object; // Object this object is mounted on. + ShapeBase* link; // Object link of next object mounted to this object's mount + U32 node; // Node point. + } mMount; + + // Damage + F32 mDamage; + F32 mRepairRate; + F32 mRepairReserve; + bool mRepairDamage; + DamageState mDamageState; + TSThread *mDamageThread; + TSThread *mHulkThread; + + bool mHidden; // in/out of world + + // Cloaking + bool mCloaked; + F32 mCloakLevel; + TextureHandle mCloakTexture; + + // Fading + bool mFadeOut; + bool mFading; + F32 mFadeVal; + F32 mFadeElapsedTime; + F32 mFadeTime; + F32 mFadeDelay; + + // Passive Jammed (active when cloaking pack is equiped) + bool mPassiveJammed; + + // Camera (in degrees) + F32 mCameraFov; + + // being controlled? + bool mIsControlled; + + // Locking and target data... + struct PotentialLock + { + SimObjectPtr potentialTarget; + bool isTarget; + U32 tag; + U32 numTicks; + PotentialLock* next; + }; + + bool mTracking; + SimObjectPtr mCurrLockTarget; + Point3F mCurrLockPosition; + + LockMode mLockedOn; + + PotentialLock* mPotentialTargets; + + public: + static U32 sLastRenderFrame; + U32 mLastRenderFrame; + F32 mLastRenderDistance; + + bool didRenderLastRender() { return mLastRenderFrame == sLastRenderFrame; } + + LockMode getLockMode() { return mLockedOn; } + ShapeBase *getLockedTarget() { return mCurrLockTarget; } + + void setLockedTarget(ShapeBase*); + void setLockedTargetPosition(const Point3F&); + bool isLocked() const; + bool isTracking() const; + S32 getLockedTargetId() const; + const Point3F& getLockedPosition() const; + void thinkAboutLocking(); + bool useTargetAudio(); + + virtual void setHidden(bool); + bool isHidden() { return mHidden; } + bool isControlled() { return(mIsControlled); } + bool isInvincible(); + + void startFade( F32 fadeTime, F32 fadeDelay = 0.0, bool fadeOut = true ); + + void registerLights(LightManager * lightManager, bool lightingScene); + + // + protected: + TSShapeInstance* mShapeInstance; + Shadow * mShadow; + bool mGenerateShadow; + U32 mSkinTag; + U32 mSkinPrefTag; + + S32 getNodeIndex(U32 imageSlot,StringTableEntry nodeName); + + void notifyCollision(); + void updateContainer(); + 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 onMount(ShapeBase* obj,S32 node); + virtual void onUnmount(ShapeBase* obj,S32 node); + virtual void onImpact(SceneObject* obj, VectorF vec); + virtual void onImpact(VectorF vec); + public: + ShapeBase(); + ~ShapeBase(); + + TSShapeInstance* getShapeInstance() { return mShapeInstance; } + + //-------------------------------------- NOTE! DO NOT ADD ANY MORE MASK BITS TO THIS + //-------------------------------------- CLASS WITHOUT CHECKING WITH DMOORE OR MARKF + //-------------------------------------- We're totally out on the player. + enum ShapeBaseMasks { + DamageMask = Parent::NextFreeMask, + NoWarpMask = Parent::NextFreeMask << 1, + MountedMask = Parent::NextFreeMask << 2, + CloakMask = Parent::NextFreeMask << 3, + ShieldMask = Parent::NextFreeMask << 4, + InvincibleMask = Parent::NextFreeMask << 5, + SoundMaskN = Parent::NextFreeMask << 6, // 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; + static F32 sWhiteoutDec; + static F32 sDamageFlashDec; + + // Init + bool onAdd(); + void onRemove(); + void onSceneRemove(); + static void consoleInit(); + static void initPersistFields(); + + bool onNewDataBlock(GameBaseData* dptr); + + // Name & Skin tags + U32 mSkinHash; + void targetInfoChanged(TargetInfo *); + void checkSkin(); + + // Basic attributes + void setControlDirty(); + + void setDamageLevel(F32 damage); + void setDamageState(DamageState state); + bool setDamageState(const char*); + const char* getDamageStateName(); + DamageState getDamageState() { return mDamageState; } + bool isDestroyed() { return mDamageState == Destroyed; } + void setRepairRate(F32 rate) { mRepairRate = rate; } + F32 getDamageLevel() { return mDamage; } + F32 getDamageValue(); + F32 getRepairRate() { return mRepairRate; } + void applyDamage(F32 amount); + void applyRepair(F32 amount); + + virtual void setEnergyLevel(F32 energy); + void setRechargeRate(F32 rate) { mRechargeRate = rate; } + F32 getEnergyLevel(); + F32 getEnergyValue(); + F32 getRechargeRate() { return mRechargeRate; } + + // Script sounds + void playAudio(U32 slot,AudioProfile* profile); + void stopAudio(U32 slot); + + // Script animation + bool setThreadSequence(U32 slot,S32 seq,bool reset = true); + void updateThread(Thread& st); + bool stopThread(U32 slot); + bool pauseThread(U32 slot); + bool playThread(U32 slot); + bool setThreadDir(U32 slot,bool forward); + void startSequenceSound(Thread& thread); + void stopThreadSound(Thread& thread); + void advanceThreads(F32 dt); + + // Cloaking + void forceUncloak(const char *); + void setCloakedState(bool); + bool getCloakedState(); + F32 getCloakLevel(); + + // passive jamming + void setPassiveJamState(bool val) {mPassiveJammed = val;} + bool getPassiveJamState() {return(mPassiveJammed);} + + // Mounted objects + virtual void mountObject(ShapeBase* obj,U32 node); + void unmountObject(ShapeBase *obj); + void unmount(); + ShapeBase* getObjectMount() { return mMount.object; } + ShapeBase* getMountLink() { return mMount.link; } + ShapeBase* getMountList() { return mMount.list; } + U32 getMountNode() { return mMount.node; } + bool isMounted() { return mMount.object != 0; } + S32 getMountedObjectCount(); + ShapeBase* getMountedObject(S32 idx); + S32 getMountedObjectNode(S32 idx); + ShapeBase* getMountNodeObject(S32 node); + Point3F getAIRepairPoint(); + + // Mounted images + virtual bool mountImage(ShapeBaseImageData* image,U32 imageSlot,bool loaded,S32 team); + virtual bool unmountImage(U32 imageSlot); + ShapeBaseImageData* getMountedImage(U32 imageSlot); + MountedImage* retrieveMountedImage(U32 imageSlot); + ShapeBaseImageData* getPendingImage(U32 imageSlot); + bool isImageFiring(U32 imageSlot); + bool isImageReady(U32 imageSlot,U32 ns = (U32)-1,U32 depth = 0); + bool isImageMounted(ShapeBaseImageData* image); + S32 getMountSlot(ShapeBaseImageData* image); + U32 getImageSkinTag(U32 imageSlot); + const char* getImageState(U32 imageSlot); + void setImageTriggerState(U32 imageSlot,bool trigger); + bool getImageTriggerState(U32 imageSlot); + void setImageAmmoState(U32 imageSlot,bool ammo); + bool getImageAmmoState(U32 imageSlot); + void setImageTargetState(U32 imageSlot,bool target); + bool getImageTargetState(U32 imageSlot); + void setImageWetState(U32 imageSlot,bool wet); + bool getImageWetState(U32 imageSlot); + void setImageLoadedState(U32 imageSlot,bool loaded); + bool getImageLoadedState(U32 imageSlot); + bool getCorrectedAim(const MatrixF& muzMat, VectorF* result); + virtual void getMuzzleVector(U32 imageSlot,VectorF* vec); + void getMuzzlePoint(U32 imageSlot,Point3F* pos); + + // Transforms in world space + virtual void getCameraParameters(F32 *min,F32* max,Point3F* offset,MatrixF* rot); + virtual void getCameraTransform(F32* pos,MatrixF* mat); + virtual void getEyeTransform(MatrixF* mat); + virtual void getRetractionTransform(U32 index,MatrixF* mat); + virtual void getMountTransform(U32 index,MatrixF* mat); + virtual void getMuzzleTransform(U32 index,MatrixF* mat); + virtual void getImageTransform(U32 imageSlot,MatrixF* mat); + virtual void getImageTransform(U32 index,S32 node, MatrixF* mat); + virtual void getImageTransform(U32 index, StringTableEntry nodeName, MatrixF* mat); + + virtual void getRenderRetractionTransform(U32 index,MatrixF* mat); + virtual void getRenderMountTransform(U32 index,MatrixF* mat); + virtual void getRenderMuzzleTransform(U32 index,MatrixF* mat); + virtual void getRenderImageTransform(U32 imageSlot,MatrixF* mat); + 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); + + MatrixF getNodeTransform( StringTableEntry nodeName ); + + virtual F32 getDamageFlash() const; + virtual void setDamageFlash(const F32); + virtual F32 getWhiteOut() const; + virtual void setWhiteOut(const F32); + + virtual void playShieldEffect(const Point3F& normal, F32 strength = 1.0 ); + + virtual void setHeat(const F32); + virtual F32 getHeat() const; + + // Invincible functions + virtual F32 getInvincibleEffect() const; + virtual void setupInvincibleEffect(F32 time, F32 speed); + virtual void updateInvincibleEffect(F32 dt); + + // Movement & velocity + virtual void setVelocity(const VectorF& vel); + virtual void applyImpulse(const Point3F& pos,const VectorF& vec); + + // User control + GameConnection* getControllingClient() { return mControllingClient; } + ShapeBase* getControllingObject() { return mControllingObject; } + virtual void setControllingClient(GameConnection* client); + virtual void setControllingObject(ShapeBase* obj); + virtual ShapeBase* getControlObject(); + virtual void setControlObject(ShapeBase*); + bool isFirstPerson(); + bool useObjsEyePoint() const; + bool onlyFirstPerson() const; + + void setActiveImage(U32 slot); + U32 getActiveImage() const { return(mActiveImage); } + + // camera: in degrees + virtual F32 getCameraFov(); + virtual F32 getDefaultCameraFov(); + virtual void setCameraFov(F32 fov); + virtual bool isValidCameraFov(F32 fov); + + // Processing + void processTick(const Move*); + void advanceTime(F32 dt); + + // Rendering + TSShape const* getShape(); + bool prepRenderImage(SceneState*, const U32 stateKey, const U32 startZone, const bool modifyBaseState); + void renderObject(SceneState* , SceneRenderImage*); + virtual void renderMountedImage(SceneState* state, ShapeImageRenderImage* image); + virtual void renderImage(SceneState* state, SceneRenderImage*); + void renderShadow(F32 dist, F32 fogAmount); + static void wireCube(const Point3F& size, const Point3F& pos); + virtual void calcClassRenderData(); + + // Control object scoping + void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo); + + // Collision + 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); + protected: + Convex* mConvexList; + + public: + virtual void onCollision(ShapeBase* object); + void queueCollision(ShapeBase* object, const F32 inData = -1e9); + bool pointInWater( Point3F &point ); + + // Network + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); + bool writePacketData(GameConnection *, BitStream *stream); + void readPacketData(GameConnection *, BitStream *stream); + + DECLARE_CONOBJECT(ShapeBase); +}; + +//------------------------------------------------------------------------------ +// inlines +//------------------------------------------------------------------------------ + +inline bool ShapeBase::useTargetAudio() +{ + return (getMountedImage(0) != 0 && getMountedImage(0)->useTargetAudio); +} + +inline bool ShapeBase::getCloakedState() +{ + return(mCloaked); +} + +inline F32 ShapeBase::getCloakLevel() +{ + return(mCloakLevel); +} + +inline bool ShapeBase::isTracking() const +{ + return mTracking; +} + +inline void ShapeBase::setActiveImage(U32 slot) +{ + AssertFatal(slot < MaxMountedImages, "ShapeBase::setActiveImage: slot out of range"); + mActiveImage = slot; +} + +// shadow detail numbers... +// the generic shadow level is the shadow detail at which +// a generic shadow is drawn (a disk) rather than a generated +// shadow...the no shadow level is the shadow level at which +// no shadow is drawn. (shadow level goes from 0 to 1, +// higher numbers result in more detailed shadows). +#define Player_GenericShadowLevel 0.4f +#define Player_NoShadowLevel 0.01f +#define Vehicle_GenericShadowLevel 0.7f +#define Vehicle_NoShadowLevel 0.2f +#define Item_GenericShadowLevel 0.4f +#define Item_NoShadowLevel 0.01f +#define StaticShape_GenericShadowLevel 2.0f +#define StaticShape_NoShadowLevel 2.0f +#define Turret_GenericShadowLevel 2.0f +#define Turret_NoShadowLevel 2.0f + +#endif // _H_SHAPEBASE_ diff --git a/game/shapeCollision.cc b/game/shapeCollision.cc new file mode 100644 index 0000000..5f93980 --- /dev/null +++ b/game/shapeCollision.cc @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/shapeBase.h" +#include "game/item.h" +#include "game/trigger.h" + +//---------------------------------------------------------------------------- + +void collisionFilter(SceneObject* object,S32 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); + } + 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... + ptr->queueCollision(static_cast(object)); + else + object->buildPolyList(info->polyList,info->boundingBox,info->boundingSphere); +} + +void defaultFilter(SceneObject* object,S32 key) +{ + Container::CallbackInfo* info = reinterpret_cast(key); + object->buildPolyList(info->polyList,info->boundingBox,info->boundingSphere); +} + diff --git a/game/shapeImage.cc b/game/shapeImage.cc new file mode 100644 index 0000000..9aca9e4 --- /dev/null +++ b/game/shapeImage.cc @@ -0,0 +1,1918 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/bitStream.h" +#include "ts/tsShapeInstance.h" +#include "console/consoleInternal.h" +#include "console/consoleTypes.h" +#include "game/particleEngine.h" +#include "audio/audio.h" +#include "game/shapeBase.h" +#include "game/projectile.h" +#include "game/gameConnection.h" +#include "math/mathIO.h" +#include "game/Debris.h" +#include "math/mathUtils.h" +#include "sim/netObject.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.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; +} + +static ShapeBaseImageData::StateData gDefaultStateData; + +//---------------------------------------------------------------------------- + +ShapeBaseImageData::ShapeBaseImageData() +{ + emap = false; + + mountPoint = 0; + offsetTransform.identity(); + firstPerson = true; + mass = 0; + + usesEnergy = false; + minEnergy = 2; + accuFire = false; + + projectile = NULL; + + isSeeker = false; + useTargetAudio = true; + seekRadius = 0.0; + maxSeekAngle = 0.0; + seekTime = 0.0; + minSeekHeat = 0.4; + targetingDist = 0; + + cloakable = true; + + lightType = ShapeBaseImageData::NoLight; + lightColor.set(1.f,1.f,1.f,1.f); + lightTime = 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; + 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; + + casing = NULL; + casingID = 0; + shellExitDir.set( 1.0, 0.0, 1.0 ); + shellExitDir.normalize(); + shellExitVariance = 20.0; + shellVelocity = 1.0; + +} + +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.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, char errorBuffer[256]) +{ + if (!Parent::preload(server, errorBuffer)) + 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"); + } + } + + // + if (shapeName && shapeName[0]) { + // Resolve shapename + char fullName[256]; + dSprintf(fullName,sizeof(fullName),"shapes/%s",shapeName); + shape = ResourceManager->load(fullName, computeCRC); + if (!bool(shape)) { + dSprintf(errorBuffer, 256, "Unable to load shape: %s", shapeName); + return false; + } + if(computeCRC) + { + Con::printf("Validation required for shape: %s", shapeName); + if(server) + mCRC = shape.getCRC(); + else if(mCRC != shape.getCRC()) + { + dSprintf(errorBuffer, 256, "Shape \"%s\" does not match version on server.",shapeName); + return false; + } + } + + // Resolve nodes & build mount transform + muzzleNode = shape->findNode("muzzlePoint"); + retractNode = shape->findNode("retractionPoint"); + mountTransform = offsetTransform; + 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 { + dSprintf(errorBuffer, sizeof(errorBuffer), "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; +} + +void ShapeBaseImageData::consoleInit() +{ +} + +static EnumTable::Enums imageLightEnum[] = +{ + { ShapeBaseImageData::NoLight, "NoLight" }, + { ShapeBaseImageData::ConstantLight, "ConstantLight" }, + { ShapeBaseImageData::PulsingLight, "PulsingLight" }, + { ShapeBaseImageData::WeaponFireLight, "WeaponFireLight" } +}; +static EnumTable gImageLightTypeTable(ShapeBaseImageData::NumLightTypes, &imageLightEnum[0]); + +void ShapeBaseImageData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("emap", TypeBool, Offset(emap, ShapeBaseImageData)); + addField("shapeFile", TypeCaseString, Offset(shapeName, ShapeBaseImageData)); + + addField("projectile", TypeProjectileDataPtr, Offset(projectile, ShapeBaseImageData)); + + addField("isSeeker", TypeBool, Offset(isSeeker, ShapeBaseImageData)); + addField("useTargetAudio", TypeBool, Offset(useTargetAudio, ShapeBaseImageData)); + addField("seekRadius", TypeF32, Offset(seekRadius, ShapeBaseImageData)); + addField("maxSeekAngle", TypeF32, Offset(maxSeekAngle, ShapeBaseImageData)); + addField("seekTime", TypeF32, Offset(seekTime, ShapeBaseImageData)); + addField("minSeekHeat", TypeF32, Offset(minSeekHeat, ShapeBaseImageData)); + addField("cloakable", TypeBool, Offset(cloakable, ShapeBaseImageData)); + + addField("mountPoint", TypeS32, Offset(mountPoint,ShapeBaseImageData)); + addField("offset", TypeMatrixPosition, Offset(offsetTransform,ShapeBaseImageData)); + addField("rotation", TypeMatrixRotation, Offset(offsetTransform,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("lightTime", TypeS32, Offset(lightTime, ShapeBaseImageData)); + 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("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("stateSound", TypeAudioProfilePtr, 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", TypeS32, Offset(stateEmitterNode, ShapeBaseImageData), MaxStates); + addField("stateIgnoreLoadedForReady", TypeBool, Offset(stateIgnoreLoadedForReady, ShapeBaseImageData), MaxStates); + addField("minTargetingDistance", TypeF32, Offset(targetingDist, ShapeBaseImageData)); + addField("computeCRC", TypeBool, Offset(computeCRC, ShapeBaseImageData)); +} + +void ShapeBaseImageData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if(stream->writeFlag(computeCRC)) + stream->write(mCRC); + + stream->writeString(shapeName); + stream->write(mountPoint); + if(!stream->writeFlag(offsetTransform.isIdentity())) + stream->writeAffineTransform(offsetTransform); + + 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); + + if(stream->writeFlag(isSeeker)) + { + stream->write(seekRadius); + stream->write(maxSeekAngle); + stream->write(seekTime); + stream->write(minSeekHeat); + stream->writeFlag(useTargetAudio); + stream->write(targetingDist); + } + stream->writeFlag(cloakable); + stream->writeRangedU32(lightType, 0, NumLightTypes-1); + if(lightType != NoLight) + { + stream->write(lightRadius); + stream->write(lightTime); + 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.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); + } +} + +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()) + offsetTransform.identity(); + else + stream->readAffineTransform(&offsetTransform); + + firstPerson = stream->readFlag(); + stream->read(&mass); + usesEnergy = stream->readFlag(); + stream->read(&minEnergy); + hasFlash = stream->readFlag(); + + projectile = (stream->readFlag() ? + (ProjectileData*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast) : 0); + + isSeeker = stream->readFlag(); + if(isSeeker) + { + stream->read(&seekRadius); + stream->read(&maxSeekAngle); + stream->read(&seekTime); + stream->read(&minSeekHeat); + useTargetAudio = stream->readFlag(); + stream->read(&targetingDist); + } + cloakable = stream->readFlag(); + lightType = stream->readRangedU32(0, NumLightTypes-1); + if(lightType != NoLight) + { + stream->read(&lightRadius); + stream->read(&lightTime); + 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.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()? (AudioProfile*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast): 0; + } + } + statesLoaded = true; +} + +void ShapeBaseImageData::registerImageLights(LightManager * lightManager, bool lightingScene, const Point3F &objectPosition, U32 startTime ) +{ + if(lightingScene) + return; + + F32 intensity; + + F32 delta = Sim::getCurrentTime() - startTime; + + switch(lightType) + { + case ConstantLight: + intensity = 1.f; + break; + + case PulsingLight: + { + intensity = 0.5f + 0.5f * mSin(M_PI * delta / F32(lightTime)); + intensity = 0.15f + intensity * 0.85f; + break; + } + + case WeaponFireLight: + { + if (delta > lightTime) + return; + intensity = 1.0 - F32(delta) / F32(lightTime); + break; + } + + default: + intensity = 1.0f; + return; + } + + mLight.mColor = lightColor * intensity; + mLight.mColor.clamp(); + mLight.mType = LightInfo::Point; + mLight.mRadius = lightRadius; + + //get the light source position + Point3F mountOffset; + mountTransform.getColumn(3, &mountOffset); + mLight.mPos = objectPosition + mountOffset; + + lightManager->addLight(&mLight); +} + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +ShapeBase::MountedImage::MountedImage() +{ + shapeInstance = 0; + state = 0; + dataBlock = 0; + nextImage = InvalidImagePtr; + skinTag = 0; + desiredTag = 0; + nextTeam = 0; + animSound = 0; + delayTime = 0; + ammo = false; + target = false; + triggerDown = false; + loaded = false; + fireCount = 0; + wet = false; +} + +ShapeBase::MountedImage::~MountedImage() +{ + delete shapeInstance; + + // stop sound + if(animLoopingSound && (animSound != NULL_AUDIOHANDLE)) + alxStop(animSound); + + for (S32 i = 0; i < MaxImageEmitters; i++) + if (bool(emitter[i].emitter)) + emitter[i].emitter->deleteWhenEmpty(); +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +// Any item with an item image is selectable + +bool ShapeBase::mountImage(ShapeBaseImageData* imageData,U32 imageSlot,bool loaded,S32 skinTag) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) { + if (image.dataBlock == imageData && image.desiredTag == skinTag) { + // Image already loaded + image.nextImage = InvalidImagePtr; + return true; + } + } + // + setImage(imageSlot,imageData,skinTag,loaded); + + //see if the image has a light source + if (imageData->lightType != ShapeBaseImageData::NoLight) + Sim::getLightSet()->addObject(this); + + return true; +} + +bool ShapeBase::unmountImage(U32 imageSlot) +{ + bool returnValue = false; + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) + { + setImage(imageSlot,0); + returnValue = true; + } + + //see if we're still part of the light group + bool found = false; + for (S32 i = 0; i < MaxMountedImages; i++) + { + ShapeBaseImageData* imageData = getMountedImage(i); + if (imageData != NULL && imageData->lightType != ShapeBaseImageData::NoLight) + { + found = true; + break; + } + } + if (!found) + Sim::getLightSet()->removeObject(this); + + + return returnValue; +} + + +//---------------------------------------------------------------------------- + +ShapeBaseImageData* ShapeBase::getMountedImage(U32 imageSlot) +{ + return mMountedImageList[imageSlot].dataBlock; +} + + +ShapeBase::MountedImage* ShapeBase::retrieveMountedImage(U32 imageSlot) +{ + return &mMountedImageList[imageSlot]; +} + + +ShapeBaseImageData* ShapeBase::getPendingImage(U32 imageSlot) +{ + ShapeBaseImageData* data = mMountedImageList[imageSlot].nextImage; + return (data == InvalidImagePtr)? 0: data; +} + +bool ShapeBase::isImageFiring(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + return image.dataBlock && image.state->fire; +} + +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.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; +} + +U32 ShapeBase::getImageSkinTag(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + return image.dataBlock? image.skinTag: 0; +} + +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::setImageTargetState(U32 imageSlot,bool target) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock && image.target != target) { + setMaskBits(ImageMaskN << imageSlot); + image.target = target; + } +} + +bool ShapeBase::getImageTargetState(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (!image.dataBlock) + return false; + return image.target; +} + +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); + + if (GameConnection * gc = getControllingClient()) + if (gc->isAIControlled() == false) + if (gc->isFirstPerson() == true) + if (getCorrectedAim(mat, vec)) + return; + + mat.mulV(VectorF(0,1,0), 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); + + if (GameConnection * gc = getControllingClient()) + if (gc->isAIControlled() == false) + if (gc->isFirstPerson() == true) + if (getCorrectedAim(mat, vec)) + return; + + mat.mulV(VectorF(0,1,0), 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, 3, 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) { + mat->mul(mObjToWorld,mShapeInstance->mNodeTransforms[ni]); + return; + } + } + *mat = mObjToWorld; +} + +void ShapeBase::getImageTransform(U32 imageSlot,MatrixF* mat) +{ + // Image transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) { + MatrixF nmat; + getMountTransform(image.dataBlock->mountPoint,&nmat); + mat->mul(nmat,image.dataBlock->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) { + mat->mul(getRenderTransform(),mShapeInstance->mNodeTransforms[ni]); + return; + } + } + *mat = getRenderTransform(); +} + + +void ShapeBase::getRenderImageTransform(U32 imageSlot,MatrixF* mat) +{ + // Image transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) { + MatrixF nmat; + getRenderMountTransform(image.dataBlock->mountPoint,&nmat); + mat->mul(nmat,image.dataBlock->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, -1, &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; + } + setControlDirty(); +} + +void ShapeBase::onImageRecoil(U32,ShapeBaseImageData::StateData::RecoilState) +{ +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::setImage(U32 imageSlot, ShapeBaseImageData* imageData,U32 skinTag,bool loaded,bool ammo,bool triggerDown, bool target) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock == imageData) { + image.nextImage = InvalidImagePtr; + if(!isGhost()) + { + if(image.skinTag != skinTag) + { + setMaskBits(ImageMaskN << imageSlot); + image.skinTag = skinTag; + } + } + else + image.desiredTag = skinTag; + return; + } + + // Delay image changes untill these states are through + if (!isGhost()) { + if (imageData && image.dataBlock && !image.state->allowImageChange) { + image.nextImage = imageData; + image.nextTeam = skinTag; + image.nextLoaded = loaded; + return; + } + } + setMaskBits(ImageMaskN << imageSlot); + + // Notify script unmount + if (image.dataBlock && !isGhost()) + scriptCallback(imageSlot,"onUnmount"); + + // No new type, just unselecting the current item + if (!imageData) { + resetImageSlot(imageSlot); + return; + } + + // Init new shape + resetImageSlot(imageSlot); + image.dataBlock = imageData; + image.state = &image.dataBlock->state[0]; + if(!isGhost()) + image.skinTag = skinTag; + else + { + image.desiredTag = skinTag; + image.skinTag = 0; + } + image.loaded = loaded; + image.ammo = ammo; + image.triggerDown = triggerDown; + image.target = target; + image.shapeInstance = new TSShapeInstance(image.dataBlock->shape, isClientObject()); + if (isClientObject()) + image.shapeInstance->cloneMaterialList(); + + // 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); + } + } + + setImageState(imageSlot,0,true); + updateMass(); + + // Notify script mount + if (!isGhost()) + scriptCallback(imageSlot,"onMount"); +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::resetImageSlot(U32 imageSlot) +{ + // Clear out current image + MountedImage& image = mMountedImageList[imageSlot]; + delete image.shapeInstance; + image.shapeInstance = 0; + if (image.animSound) { + alxStop(image.animSound); + image.animSound = 0; + } + 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.skinTag = 0; + image.nextTeam = 0; + image.state = 0; + image.delayTime = 0; + image.ammo = false; + image.triggerDown = false; + image.loaded = false; + image.lightStart = 0; +// image.light.fLight.fType = TSLight::LightInvalid; + 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); + } + } +} + + +//---------------------------------------------------------------------------- + +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; + + // Stop any looping sounds or animations use in the last state. + if (image.animSound && image.animLoopingSound) { + alxStop(image.animSound); + image.animSound = 0; + } + + // Mount pending images + if (image.nextImage != InvalidImagePtr && stateData.allowImageChange) { + setImage(imageSlot,image.nextImage,image.nextTeam,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.dataBlock->shape->sequences[image.shapeInstance->getSequence(image.animThread)].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; + } + + // + // 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()) { + // Silence warnings in CWarrior - DMM + Point3F vel = getVelocity(); + image.animSound = alxPlay(stateData.sound, &getTransform(), &vel); + ALint value; + alxGetSourcei(image.animSound, AL_SOURCE_LOOPING, &value); + image.animLoopingSound = (value == AL_TRUE); + } + + // Play animation + if (image.animThread && stateData.sequence != -1) { + image.shapeInstance->setSequence(image.animThread,stateData.sequence, + stateData.direction ? 0 : 1); + if (stateData.flashSequence == false) { + F32 timeScale = (stateData.scaleAnimation && stateData.timeoutValue) ? + image.shapeInstance->getDuration(image.animThread) / stateData.timeoutValue : + 1; + 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; + image.shapeInstance->setTimeScale(image.flashThread, timeScale); + } + } + + // Start particle emitter on the client + if (isGhost()) + 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 (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); + + // Position looping anim and spin sounds + if (image.animSound && image.animLoopingSound) + { + alxSourceMatrixF(image.animSound, &getTransform()); + +// // Silence warnings in CWarrior - DMM +// Point3F vel = getVelocity(); +// alxSourcePoint3F(image.animSound, AL_VELOCITY, &vel); + } + + // 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; + getImageTransform(imageSlot,em.node,&mat); + Point3F pos,axis; + mat.getColumn(3,&pos); + mat.getColumn(1,&axis); + em.emitter->emitParticles(pos,true,axis,getVelocity(),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); + bem->emitter->registerObject(); +} + + +// Lighting: ----------------------------------------------------------------- +Light* ShapeBase::getImageLight(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (!image.dataBlock) + return 0; + + ShapeBaseImageData& imageData = *image.dataBlock; + if (imageData.lightType == ShapeBaseImageData::NoLight) + return 0; + + F32 intensity; + F32 delta = Sim::getCurrentTime() - image.lightStart; + switch (imageData.lightType) { + case ShapeBaseImageData::ConstantLight: + intensity = 1.0; + break; + case ShapeBaseImageData::WeaponFireLight: { + if (delta > imageData.lightTime) + return 0; + intensity = 1.0 - delta / imageData.lightTime; + break; + } + case ShapeBaseImageData::PulsingLight: { + intensity = 0.5 + 0.5 * mSin(M_PI * delta / imageData.lightTime); + intensity = 0.15 + intensity * 0.85; + break; + } + default: + return 0; + } + + //image.light.setType(TSLight::LightPoint); + //image.light.setRange(imageData.lightRadius); + //image.light.setIntensity(imageData.lightColor.x * intensity, + // imageData.lightColor.y * intensity,imageData.lightColor.z * intensity); + + // If there is no muzzle node on the shape getMuzzleTransform + // returns the image origin. So should work fine for either + // fire or pulsing lights. + MatrixF mat; + getMuzzleTransform(imageSlot,&mat); + //image.light.setPosition(mat.p); + return 0; +} + +void ShapeBase::registerLights(LightManager * lightManager, bool lightingScene) +{ + //one of the mounted images must have a light source... + for (S32 i = 0; i < MaxMountedImages; i++) + { + ShapeBaseImageData* imageData = getMountedImage(i); + if (imageData != NULL && imageData->lightType != ShapeBaseImageData::NoLight) + imageData->registerImageLights(lightManager, lightingScene, getRenderPosition(), mLightTime); + } +} + +//---------------------------------------------------------------------------- +void ShapeBase::ejectShellCasing( U32 imageSlot ) +{ + MountedImage& image = mMountedImageList[imageSlot]; + ShapeBaseImageData* imageData = image.dataBlock; + + MatrixF ejectTrans; + getImageTransform( imageSlot, "mount0", &ejectTrans ); + + if( !imageData->casing ) return; + + 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/game/shieldImpact.cc b/game/shieldImpact.cc new file mode 100644 index 0000000..69511b9 --- /dev/null +++ b/game/shieldImpact.cc @@ -0,0 +1,238 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "PlatformWin32/platformGL.h" +#include "Platform/platformAudio.h" +#include "audio/audioDataBlock.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "Core/bitStream.h" +#include "game/particleEngine.h" +#include "Math/mathIO.h" +#include "terrain/terrData.h" +#include "game/explosion.h" +#include "game/shieldImpact.h" +#include "game/shapeBase.h" +#include "ts/tsShapeInstance.h" +#include "Math/mathUtils.h" +#include "Sim/netConnection.h" +#include "game/turret.h" + +//-------------------------------------------------------------------------- +// ShieldImpact +//-------------------------------------------------------------------------- +ShieldImpact::ShieldImpact() +{ + mCurScale = 1.0; + mShieldTexture = NULL; + mOverrideTex = NULL; + mShieldStrength = 1.0; + mLifetime = 0.3; + mStartScale = 1.0; + mEndScale = 1.1; + mElapsedTime = mLifetime; +} + +//-------------------------------------------------------------------------- +// Destructor +//-------------------------------------------------------------------------- +ShieldImpact::~ShieldImpact() +{ +} + +//-------------------------------------------------------------------------- +// Init +//-------------------------------------------------------------------------- +void ShieldImpact::init() +{ + // Ack - hardcode the shield map + mShieldTexture = TextureHandle("special/shieldenvmap", MeshTexture, false); + mOverrideTex = TextureHandle("special/whiteAlpha255", MeshTexture, false); +} + +//-------------------------------------------------------------------------- +// Start effect +//-------------------------------------------------------------------------- +void ShieldImpact::start() +{ + mElapsedTime = 0.0; + mCurScale = 1.0; +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void ShieldImpact::renderObject(SceneState* state, SceneRenderImage*) +{ + if( !bool( mShieldedObj ) ) return; + if( mElapsedTime >= mLifetime ) return; + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + F32 percentDone = mElapsedTime / mLifetime; + F32 alpha = (1.0 - percentDone) * (1.0 + percentDone ); + alpha *= mShieldStrength; + if( alpha > 1.0 ) alpha = 1.0; + + // render parent object + renderObj( mShieldedObj, state, alpha ); + + // render mounted OBJECTS + for (ShapeBase* ptr = mShieldedObj->getMountList(); ptr; ptr = ptr->getMountLink()) + { + renderObj( ptr, state, alpha ); + } + + // render mounted IMAGES + for( U32 i=0; iretrieveMountedImage( i ); + if( !image ) continue; + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + MatrixF imageTrans; + mShieldedObj->getRenderImageTransform( i, &imageTrans ); + + dglMultMatrix( &imageTrans ); + + glScalef(mShieldedObj->getScale().x * mCurScale, + mShieldedObj->getScale().y * mCurScale, + mShieldedObj->getScale().z * mCurScale); + + renderInstance( image->shapeInstance, state, alpha ); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + + // restore matrices + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + // restore states + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +} + +//-------------------------------------------------------------------------- +// Render shapeBase object +//-------------------------------------------------------------------------- +void ShieldImpact::renderObj( ShapeBase *obj, SceneState* state, F32 alpha ) +{ + + // render mounted IMAGES + for( U32 i=0; iretrieveMountedImage( i ); + if( !image ) continue; + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + MatrixF imageTrans; + obj->getRenderImageTransform( i, &imageTrans ); + + dglMultMatrix( &imageTrans ); + + glScalef(obj->getScale().x * mCurScale, + obj->getScale().y * mCurScale, + obj->getScale().z * mCurScale); + + renderInstance( image->shapeInstance, state, alpha ); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + + + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + dglMultMatrix(&obj->getRenderTransform()); + + glScalef(obj->getScale().x * mCurScale, + obj->getScale().y * mCurScale, + obj->getScale().z * mCurScale); + + // RENDER CODE HERE + TSShapeInstance* pInstance = obj->getShapeInstance(); + renderInstance( pInstance, state, alpha ); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +//-------------------------------------------------------------------------- +// Render shape instance +//-------------------------------------------------------------------------- +void ShieldImpact::renderInstance( TSShapeInstance *pInstance, SceneState* state, F32 alpha ) +{ + + if (pInstance) + { + Point3F cameraOffset = mShieldedObj->getPosition(); + cameraOffset -= state->getCameraPosition(); + F32 fogAmount = state->getHazeAndFog(cameraOffset.len(),cameraOffset.z); + pInstance->setupFog(0.0, state->getFogColor()); + pInstance->setAlphaAlways(alpha); + + glMatrixMode(GL_TEXTURE); + glPushMatrix(); + glTranslatef( mElapsedTime, mElapsedTime, 0); + glMatrixMode(GL_MODELVIEW); + + bool alphaIsReflect = pInstance->queryAlphaIsReflectanceMap(); + pInstance->setAlphaIsReflectanceMap( true ); + + pInstance->smRenderData.renderDecals = false; + pInstance->setOverrideTexture( mOverrideTex ); + pInstance->setEnvironmentMap( mShieldTexture ); + pInstance->setEnvironmentMapOn(true, 1.0); + + pInstance->render(0,1); + + pInstance->setAlphaAlways(1.0); + pInstance->setEnvironmentMapOn(false, 1.0); + pInstance->clearOverrideTexture(); + pInstance->smRenderData.renderDecals = true; + + pInstance->setAlphaIsReflectanceMap( alphaIsReflect ); + + glMatrixMode(GL_TEXTURE); + glPopMatrix(); + } + + +} + +//-------------------------------------------------------------------------- +// Advance time +//-------------------------------------------------------------------------- +void ShieldImpact::update(F32 dt) +{ + if (dt == 0.0) + return; + + mElapsedTime += dt; + + F32 percentDone = mElapsedTime / mLifetime; + + mCurScale = mStartScale + (mEndScale - mStartScale) * percentDone; + +} diff --git a/game/shieldImpact.h b/game/shieldImpact.h new file mode 100644 index 0000000..080a789 --- /dev/null +++ b/game/shieldImpact.h @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHIELDIMPACT_H_ +#define _SHIELDIMPACT_H_ + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _SCENESTATE_H_ +#include "sceneGraph/sceneState.h" +#endif + +class ShapeBase; +class TSShapeInstance; +class SceneState; + +//-------------------------------------------------------------------------- +// ShieldImpact +//-------------------------------------------------------------------------- +class ShieldImpact +{ + private: + TextureHandle mShieldTexture; + TextureHandle mOverrideTex; + + F32 mLifetime; + F32 mElapsedTime; + F32 mCurScale; + F32 mShieldStrength; + F32 mStartScale; + F32 mEndScale; + + protected: + SimObjectPtr mShieldedObj; + + + // Rendering + protected: + void renderObj( ShapeBase *obj, SceneState* state, F32 alpha ); + void renderInstance( TSShapeInstance *shapeInst, SceneState* state, F32 alpha ); + + public: + ShieldImpact(); + ~ShieldImpact(); + + void init(); + bool isActive(){ return (mElapsedTime <= mLifetime); } + void renderObject(SceneState*, SceneRenderImage*); + void start(); + void setShieldedObj( ShapeBase *obj ){ mShieldedObj = obj; } + void setShieldStrength( F32 strength ){ mShieldStrength = strength; } + void setLifetime( F32 lifetime ){ mLifetime = lifetime; } + void update(F32 dt); +}; + +//-------------------------------------------------------------------------- +// Scene Render Image for shield impact +//-------------------------------------------------------------------------- +class ShieldImpactRenderImage : public SceneRenderImage +{ + public: + void setup( SceneObject *object ) + { + obj = object; + isTranslucent = true; + sortType = SceneRenderImage::Point; + tieBreaker = true; + } + +}; + + +#endif // _H_SHIELDIMPACT diff --git a/game/shockwave.cc b/game/shockwave.cc new file mode 100644 index 0000000..03f4e36 --- /dev/null +++ b/game/shockwave.cc @@ -0,0 +1,963 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "platformWIN32/platformGL.h" +#include "platform/platformAudio.h" +#include "audio/audioDataBlock.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sceneState.h" +#include "core/bitStream.h" +#include "game/particleEngine.h" +#include "game/shockwave.h" +#include "math/mathIO.h" +#include "terrain/terrData.h" +#include "math/mathUtils.h" +#include "sim/netConnection.h" + +IMPLEMENT_CO_DATABLOCK_V1(ShockwaveData); +IMPLEMENT_CO_NETOBJECT_V1(Shockwave); + +namespace +{ + +MRandomLCG sgRandom(0xdeadbeef); + +} // namespace {} + +//---------------------------------------------------------------------------- +//-------------------------------------- +// + +ShockwaveData::ShockwaveData() +{ + 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 = 30.0; + height = 0.0; + numVertSegments = 1; + verticalCurve = 1.0; + acceleration = 0.0; + texWrap = 1.0; + is2D = false; + mapToTerrain = true; + orientToNormal = false; + renderBottom = false; + renderSquare = false; + + 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(numVertSegments); + stream->write(velocity); + stream->write(height); + stream->write(verticalCurve); + stream->write(acceleration); + stream->write(texWrap); + stream->write(is2D); + stream->write(orientToNormal); + stream->write(mapToTerrain); + stream->write(renderBottom); + stream->write(renderSquare); + + 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]); + } + } +} + +void ShockwaveData::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(&numVertSegments); + stream->read(&velocity); + stream->read(&height); + stream->read(&verticalCurve); + stream->read(&acceleration); + stream->read(&texWrap); + stream->read(&is2D); + stream->read(&orientToNormal); + stream->read(&mapToTerrain); + stream->read(&renderBottom); + stream->read(&renderSquare); + + 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(); + } +} + +bool ShockwaveData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (!server) { + S32 i; + for( i=0; i(obj); + if( !shock ) return; + + Point3F pos; + dSscanf( argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z ); + + Point3F dir; + dSscanf( argv[3], "%f %f %f", &dir.x, &dir.y, &dir.z ); + + shock->setInitialState( pos, dir ); +} + +//-------------------------------------------------------------------------- +// Console Init +//-------------------------------------------------------------------------- +void Shockwave::consoleInit() +{ + Con::addCommand("Shockwave", "setInitialState", cSetInitialState, "startFade( pos, normal )", 4, 4); +} + +//-------------------------------------------------------------------------- +// OnAdd +//-------------------------------------------------------------------------- +bool Shockwave::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mDelayMS = mDataBlock->delayMS + sgRandom.randI( -mDataBlock->delayVariance, mDataBlock->delayVariance ); + mEndingMS = mDataBlock->lifetimeMS + sgRandom.randI( -mDataBlock->lifetimeVariance, mDataBlock->lifetimeVariance ); + + mVelocity = mDataBlock->velocity; + mHeight = mDataBlock->height; + + mObjBox.min = Point3F( -1, -1, -1 ); + mObjBox.max = Point3F( 1, 1, 1 ); + resetWorldBox(); + + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + + removeFromProcessList(); + gClientProcessList.addObject(this); + + MatrixF trans(true); + trans.setPosition( mInitialPosition ); + setTransform( trans ); + + return true; +} + +//-------------------------------------------------------------------------- +// OnRemove +//-------------------------------------------------------------------------- +void Shockwave::onRemove() +{ + for( int i=0; ideleteWhenEmpty(); + mEmitterList[i] = NULL; + } + } + + mSceneManager->removeObjectFromScene(this); + getContainer()->removeObject(this); + + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +// On New Data Block +//-------------------------------------------------------------------------- +bool Shockwave::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +// Prep render image +//-------------------------------------------------------------------------- +bool Shockwave::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)) { + mFog = 0.0; + + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + state->insertRenderImage(image); + } + + return false; +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void Shockwave::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + if( mDataBlock->is2D ) + { + render2DWave(); + } + else + { + if( mDataBlock->renderSquare ) + { + renderSquare(); + } + else + { + renderWave(); + } + } + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +// Process tick +//-------------------------------------------------------------------------- +void Shockwave::processTick(const Move*) +{ + mCurrMS += TickMs; + + if( isServerObject() ) + { + if( mCurrMS >= mEndingMS ) + { + deleteObject(); + return; + } + } + + // set object box + F32 outerRad = mRadius + mDataBlock->width * 0.5; + mObjBox.min = Point3F( -outerRad, -outerRad, -outerRad ); + mObjBox.max = Point3F( outerRad, outerRad, outerRad ); + resetWorldBox(); + +} + +//-------------------------------------------------------------------------- +// Advance time +//-------------------------------------------------------------------------- +void Shockwave::advanceTime(F32 dt) +{ + if (dt == 0.0) + return; + + updateColor(); + updateWave( dt ); + updateEmitters( dt ); +} + +//---------------------------------------------------------------------------- +// Update emitters +//---------------------------------------------------------------------------- +void Shockwave::updateEmitters( F32 dt ) +{ + Point3F pos = getPosition(); + + for( int i=0; iemitParticles( pos, pos, mInitialNormal, Point3F( 0.0, 0.0, 0.0 ), dt * 1000 ); + } + } + +} + +//---------------------------------------------------------------------------- +// Update wave +//---------------------------------------------------------------------------- +void Shockwave::updateWave( F32 dt ) +{ + mVelocity += mDataBlock->acceleration * dt; + mRadius += mVelocity * dt; + +} + +//---------------------------------------------------------------------------- +// Render wave +//---------------------------------------------------------------------------- +void Shockwave::renderWave() +{ + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + MatrixF orient(true); + + if( mDataBlock->orientToNormal ) + { + MatrixF rotMatrix( EulerF( M_PI/2.0, 0.0, 0.0 ) ); + + // set up gl modelview matrix + orient = MathUtils::createOrientFromDir( mInitialNormal ); + orient.mul( rotMatrix ); + } + + orient.setPosition( mInitialPosition ); + dglMultMatrix( &orient ); + + + const F32 minGroundHeight = 0.1; + + F32 innerRad = mRadius - mDataBlock->width * 0.5; + F32 outerRad = mRadius + mDataBlock->width * 0.5; + + if( innerRad < 0.0 ) innerRad = 0.0; + + + glEnable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_TEXTURE_2D); + + + F32 texFactor = mDataBlock->texWrap; + + F32 pt = 0.0; + F32 t = 0.0; + + for( U32 i=1; inumSegments+1; i++ ) + { + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[0].getGLName()); + + pt = t; + t = F32(i) / F32(mDataBlock->numSegments); + + Point3F vert[2]; + vert[0].x = mSin( pt * M_PI * 2.0 ); + vert[0].y = mCos( pt * M_PI * 2.0 ); + vert[0].z = 0.0; + vert[1].x = mSin( t * M_PI * 2.0 ); + vert[1].y = mCos( t * M_PI * 2.0 ); + vert[1].z = 0.0; + + Point3F outerPts[2]; + Point3F innerPts[2]; + + glBegin( GL_QUADS ); + + glColor4fv( mColor ); + glTexCoord2f( t * texFactor, 0.95 ); + Point3F pnt = mInitialPosition + vert[1] * innerRad; + pnt.z = findHeight( pnt ) + minGroundHeight; + innerPts[0] = pnt; + glVertex3fv( pnt - mInitialPosition ); + + glColor4fv( mColor ); + glTexCoord2f( pt * texFactor, 0.95 ); + pnt = mInitialPosition + vert[0] * innerRad; + pnt.z = findHeight( pnt ) + minGroundHeight; + innerPts[1] = pnt; + glVertex3fv( pnt - mInitialPosition ); + + glColor4fv( mColor ); + glTexCoord2f( pt * texFactor, 0.05 ); + pnt = mInitialPosition + vert[0] * outerRad; + pnt.z = findHeight( pnt ) + minGroundHeight; + outerPts[0] = pnt; + pnt.z += mHeight; + glVertex3fv( pnt - mInitialPosition ); + + glColor4fv( mColor ); + glTexCoord2f( t * texFactor, 0.05 ); + pnt = mInitialPosition + vert[1] * outerRad; + pnt.z = findHeight( pnt ) + minGroundHeight; + outerPts[1] = pnt; + pnt.z += mHeight; + glVertex3fv( pnt - mInitialPosition ); + + if( mDataBlock->renderBottom ) + { + glTexCoord2f( t * texFactor, 0.05 ); + glVertex3fv( outerPts[1] - mInitialPosition ); + glTexCoord2f( pt * texFactor, 0.05 ); + glVertex3fv( outerPts[0] - mInitialPosition ); + glTexCoord2f( pt * texFactor, 0.95 ); + glVertex3fv( innerPts[1] - mInitialPosition ); + glTexCoord2f( t * texFactor, 0.95 ); + glVertex3fv( innerPts[0] - mInitialPosition ); + } + + glEnd(); + + renderVerticalWall( outerPts[1], outerPts[0] ); + + } + + + glDisable(GL_CULL_FACE); + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + +} + +//---------------------------------------------------------------------------- +// Render vertical wall +//---------------------------------------------------------------------------- +void Shockwave::renderVerticalWall( Point3F &pnt1, Point3F pnt2 ) +{ + Point3F p1 = pnt1; + Point3F p2 = pnt2; + + p1.z += mHeight * 0.5; + p2.z += mHeight * 0.5; + + Point3F dir1, dir2; + dir1 = p1 - mInitialPosition; + dir1.z = 0.0; + dir1.normalize(); + dir2 = p2 - mInitialPosition; + dir2.z = 0.0; + dir2.normalize(); + + F32 vertRadius = mHeight * 0.5; + + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[1].getGLName()); + + glBegin( GL_QUAD_STRIP ); + + for( U32 i=0; inumVertSegments+1; i++ ) + { + F32 t = F32(i) / F32(mDataBlock->numVertSegments); + + glColor4f( mColor.red, mColor.green, mColor.blue, mColor.alpha ); + + F32 v = t; + + if( mDataBlock->renderBottom ) + { + v = 0.0; + } + else + { + if( v < 0.05 ) v = 0.05; + if( v > .95 ) v = 0.95; + } + + glTexCoord2f( 0.0, v ); + + Point3F pnt = p1; + pnt.z += mCos( t * M_PI ) * vertRadius; + pnt += dir1 * mSin( t * M_PI ) * mDataBlock->verticalCurve; + glVertex3fv( pnt - mInitialPosition ); + + glTexCoord2f( 1.0, v ); + + pnt = p2; + pnt.z += mCos( t * M_PI ) * vertRadius; + pnt += dir2 * mSin( t * M_PI ) * mDataBlock->verticalCurve; + glVertex3fv( pnt - mInitialPosition ); + + } + + glEnd(); + +} + +//---------------------------------------------------------------------------- +// Find height +//---------------------------------------------------------------------------- +F32 Shockwave::findHeight( Point3F &point ) +{ + if( mDataBlock->mapToTerrain ) + { + TerrainBlock* pTerrain = mSceneManager->getCurrentTerrain(); + if( !pTerrain ) return 0.0; + + Point3F terrPt = point; + pTerrain->getWorldTransform().mulP(terrPt); + F32 h; + if (pTerrain->getHeight(Point2F(terrPt.x, terrPt.y), &h)) + { + return h; + } + } + else + { + return point.z; + } + + + return 0.0; +} + +//---------------------------------------------------------------------------- +// Update color +//---------------------------------------------------------------------------- +void Shockwave::updateColor() +{ + F32 t = F32(mCurrMS) / F32(mEndingMS); + + for( U32 i = 1; i < ShockwaveData::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; + + mColor.interpolate( mDataBlock->colors[i-1], + mDataBlock->colors[i], + firstPart); + + + return; + } + } +} + +//---------------------------------------------------------------------------- +// Render billboarded shockwave +//---------------------------------------------------------------------------- +void Shockwave::render2DWave() +{ + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + // set up gl modelview matrix + MatrixF camTrans; + dglGetModelview(&camTrans); + Point3F camLookDir; + camTrans.getRow(1, &camLookDir); + Point3F negCamDir = -camLookDir; + MatrixF billBoard = MathUtils::createOrientFromDir( negCamDir ); + billBoard.setPosition( getPosition() ); + dglMultMatrix( &billBoard ); + + + const F32 minGroundHeight = 0.1; + + F32 innerRad = mRadius - mDataBlock->width * 0.5; + F32 outerRad = mRadius + mDataBlock->width * 0.5; + + if( innerRad < 0.0 ) innerRad = 0.0; + + + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_TEXTURE_2D); + + + F32 texFactor = mDataBlock->texWrap; + + F32 pt = 0.0; + F32 t = 0.0; + + for( U32 i=1; inumSegments+1; i++ ) + { + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[0].getGLName()); + + pt = t; + t = F32(i) / F32(mDataBlock->numSegments); + + Point3F vert[2]; + vert[0].x = mSin( pt * M_PI * 2.0 ); + vert[0].z = mCos( pt * M_PI * 2.0 ); + vert[0].y = 0.0; + vert[1].x = mSin( t * M_PI * 2.0 ); + vert[1].z = mCos( t * M_PI * 2.0 ); + vert[1].y = 0.0; + + Point3F outerPts[2]; + + glBegin( GL_QUADS ); + + glColor4fv( mColor ); + glTexCoord2f( t * texFactor, 0.95 ); + Point3F pnt = vert[1] * innerRad; + glVertex3fv( pnt ); + + glColor4fv( mColor ); + glTexCoord2f( pt * texFactor, 0.95 ); + pnt = vert[0] * innerRad; + glVertex3fv( pnt ); + + glColor4fv( mColor ); + glTexCoord2f( pt * texFactor, 0.05 ); + pnt = vert[0] * outerRad; + outerPts[0] = pnt; + glVertex3fv( pnt ); + + glColor4fv( mColor ); + glTexCoord2f( t * texFactor, 0.05 ); + pnt = vert[1] * outerRad; + outerPts[1] = pnt; + glVertex3fv( pnt ); + + glEnd(); + + } + + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + +} + +//---------------------------------------------------------------------------- +// Render wave +//---------------------------------------------------------------------------- +void Shockwave::renderSquare() +{ + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + MatrixF orient(true); + + if( mDataBlock->orientToNormal ) + { + MatrixF rotMatrix( EulerF( M_PI/2.0, 0.0, 0.0 ) ); + + // set up gl modelview matrix + orient = MathUtils::createOrientFromDir( mInitialNormal ); + orient.mul( rotMatrix ); + } + + orient.setPosition( mInitialPosition ); + dglMultMatrix( &orient ); + + + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, mDataBlock->textureHandle[0].getGLName()); + + + glBegin( GL_QUADS ); + + glColor4fv( mColor ); + glTexCoord2f( 0.0, 0.0 ); + glVertex3f( -mRadius, -mRadius, 0.0 ); + + glColor4fv( mColor ); + glTexCoord2f( 0.0, 1.0 ); + glVertex3f( -mRadius, mRadius, 0.0 ); + + glColor4fv( mColor ); + glTexCoord2f( 1.0, 1.0 ); + glVertex3f( mRadius, mRadius, 0.0 ); + + glColor4fv( mColor ); + glTexCoord2f( 1.0, 0.0 ); + glVertex3f( mRadius, -mRadius, 0.0 ); + + glEnd(); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + +} + +//-------------------------------------------------------------------------- +// packUpdate +//-------------------------------------------------------------------------- +U32 Shockwave::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if( stream->writeFlag(mask & GameBase::InitialUpdateMask) ) + { + mathWrite(*stream, mInitialPosition); + mathWrite(*stream, mInitialNormal); + } + + return retMask; +} + +//-------------------------------------------------------------------------- +// unpackUpdate +//-------------------------------------------------------------------------- +void Shockwave::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if( stream->readFlag() ) + { + mathRead(*stream, &mInitialPosition); + mathRead(*stream, &mInitialNormal); + } +} diff --git a/game/shockwave.h b/game/shockwave.h new file mode 100644 index 0000000..7dd70cf --- /dev/null +++ b/game/shockwave.h @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_SHOCKWAVE +#define _H_SHOCKWAVE + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; +class AudioProfile; + +//-------------------------------------------------------------------------- +// Shockwave Data +//-------------------------------------------------------------------------- +class ShockwaveData : 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; + U32 numVertSegments; + F32 verticalCurve; + F32 acceleration; + F32 texWrap; + bool is2D; + bool mapToTerrain; + bool orientToNormal; + bool renderBottom; + bool renderSquare; + + F32 times[ NUM_TIME_KEYS ]; + ColorF colors[ NUM_TIME_KEYS ]; + + StringTableEntry textureName[NUM_TEX]; + TextureHandle textureHandle[NUM_TEX]; + + + ShockwaveData(); + DECLARE_CONOBJECT(ShockwaveData); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//-------------------------------------------------------------------------- +// Shockwave +//-------------------------------------------------------------------------- +class Shockwave : public GameBase +{ + typedef GameBase Parent; + + private: + ShockwaveData* mDataBlock; + + ParticleEmitter * mEmitterList[ ShockwaveData::NUM_EMITTERS ]; + + U32 mCurrMS; + U32 mEndingMS; + F32 mRandAngle; + F32 mRadius; + F32 mVelocity; + F32 mHeight; + ColorF mColor; + + protected: + Point3F mInitialPosition; + Point3F mInitialNormal; + F32 mFade; + F32 mFog; + bool mActive; + S32 mDelayMS; + + protected: + bool onAdd(); + void onRemove(); + + void processTick(const Move*); + void advanceTime(F32 dt); + void updateEmitters( F32 dt ); + void updateWave( F32 dt ); + void renderWave(); + void render2DWave(); + void renderSquare(); + F32 findHeight( Point3F &point ); + void renderVerticalWall( Point3F &pnt1, Point3F pnt2 ); + void updateColor(); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + public: + Shockwave(); + ~Shockwave(); + void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0); + + bool onNewDataBlock(GameBaseData* dptr); + DECLARE_CONOBJECT(Shockwave); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); + + static void consoleInit(); + static void initPersistFields(); +}; + + +#endif // _H_SHOCKWAVE diff --git a/game/showTSShape.cc b/game/showTSShape.cc new file mode 100644 index 0000000..368992e --- /dev/null +++ b/game/showTSShape.cc @@ -0,0 +1,1176 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/showTSShape.h" +#include "GUI/guiControl.h" +#include "GUI/guiTSControl.h" +#include "GUI/guiTextCtrl.h" +#include "GUI/guiTextListCtrl.h" +#include "GUI/guiSliderCtrl.h" +#include "GUI/guiTextEditCtrl.h" +#include "GUI/guiCanvas.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" + +#include "terrain/terrData.h" +#include "terrain/terrRender.h" + +ShowTSShape * ShowTSShape::currentShow = NULL; + +Vector ShowTSShape::loadQueue(__FILE__, __LINE__); + +const char * emapName[] = { "emap.bmp" }; + +MaterialList emapList(1,emapName); +F32 emapAlpha = 0.0f; +F32 ambR = 0.7f; +F32 ambB = 0.7f; +F32 ambG = 0.7f; +F32 diffR = 0.9f; +F32 diffB = 0.9f; +F32 diffG = 0.9f; +Point4F lightDir(0.57f,0.57f,-0.57f,0.0f); +bool gInitLightingSliders = false; + +//------------------------------------------------------------ +// simple camera methods and variables +//------------------------------------------------------------ + +Point3F camPos; +MatrixF cameraMatrix; +EulerF camRot; + +F32 gShowForwardAction = 0.0f; +F32 gShowBackwardAction = 0.0f; +F32 gShowUpAction = 0.0f; +F32 gShowDownAction = 0.0f; +F32 gShowLeftAction = 0.0f; +F32 gShowRightAction = 0.0f; +F32 gShowMovementSpeed = 1.0f; + +float initialShowDistance = 20.0f; +Point3F * orbitPos = NULL; +float orbitDist = 10.0f; +float minOrbitDist = 1.0f; +float maxOrbitASin = 0.2f; +bool keyboardControlsShape = false; + +float gShowShapeLeftSpeed = 0.0f; +float gShowShapeRightSpeed = 0.0f; + +void setOrbit(Point3F * orb, bool useCurrentDist) +{ + orbitPos = orb; + + if (!useCurrentDist) + { + Point3F vec; + cameraMatrix.getColumn(1, &vec); + orbitDist = mDot(vec,*orbitPos-camPos); + } + if (orbitDist(Sim::findObject("showDetailSlider")); + if (!slider) + return; + + // find current detail level and number of detail levels of currentShow + S32 dl = currentShow->getCurrentDetail(); + S32 numDL = currentShow->getNumDetails(); + + // set slider range to be correct + slider->setField("range",avar("0 %f",-0.01f + (F32)numDL)); + // set slider ticks to be correct + slider->setField("ticks",avar("%i",numDL)); + // set slider value to be correct + slider->setField("value",avar("%i",dl)); +} + +//------------------------------------------------------------ +// show gui related methods +//------------------------------------------------------------ + +void showUpdateThreadControl() +{ + char buffer[32]; + + // update dialogs + GuiTextListCtrl * threadList = dynamic_cast(Sim::findObject("threadList")); + GuiTextListCtrl * sequenceList = dynamic_cast(Sim::findObject("sequenceList")); + if (!threadList || !sequenceList) + { + // just for debugging, look up w/o dynamic cast... + threadList = threadList ? NULL : static_cast(Sim::findObject("threadList")); + sequenceList = sequenceList ? NULL : static_cast(Sim::findObject("sequenceList")); + AssertFatal(!threadList && !sequenceList,"showUpdateThreadControl: threadList or sequenceList of wrong gui type"); + // something wrong, just exit + return; + } + + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + { + // no current show...zero out lists + threadList->clear(); + sequenceList->clear(); + return; + } + + // the thread list: + + threadList->setCellSize(Point2I(threadList->mBounds.extent.x,16)); + U32 count = currentShow->getThreadCount(); + + if(count != threadList->getNumEntries()) + { + threadList->clear(); + for(U32 i = 0; i < count; i++) + { + dSprintf(buffer, sizeof(buffer), "%i", i); + threadList->addEntry(i, buffer); + } + } + S32 selectedThread = threadList->getSelectedCell().y; + if (threadList->getNumEntries()==0 && selectedThread>=0) + // no threads, select nothing... + threadList->deselectCells(); + else if (threadList->getNumEntries()>0 && selectedThread<0) + // we have threads but nothing selected... + threadList->setSelectedCell(Point2I(0,0)); + selectedThread = threadList->getSelectedCell().y; + + // the sequence list: + + sequenceList->setCellSize(Point2I(sequenceList->mBounds.extent.x,16)); + // only change the text if it needs changing ... this avoids infinite loops since + // changing text can result in this routine getting called again + S32 i; + for (i=0; igetSequenceCount() && igetNumEntries(); i++) + if (dStrcmp(currentShow->getSequenceName(i),sequenceList->mList[i].text)) + break; + while (igetNumEntries()) + sequenceList->removeEntryByIndex(i); + for (; igetSequenceCount(); i++) + sequenceList->addEntry(i, currentShow->getSequenceName(i)); + if (selectedThread==-1) + { + if (sequenceList->getSelectedCell().y >= 0) + sequenceList->deselectCells(); + } + else if (sequenceList->getSelectedCell().y != currentShow->getSequenceNum(selectedThread)) + sequenceList->setSelectedCell(Point2I(0,currentShow->getSequenceNum(selectedThread))); + + // update displayed scale value + GuiTextCtrl * text = dynamic_cast(Sim::findObject("showScaleValue")); + if (text && selectedThread>=0) + { + dSprintf(buffer,32,"scale = %2.2f",currentShow->getThreadScale(selectedThread)); + text->setText(buffer); + } +} + +Vector showFiles(__FILE__, __LINE__); + +void showSetFileList(const char * path, const char * ext, const char * command) +{ + // set up showFileList dialog + GuiTextListCtrl * list = static_cast(Sim::findObject("showFileList")); + if (!list) + return; + + showFiles.clear(); + Platform::dumpPath(path, showFiles); + + // get rid of files with the wrong extension + S32 i; + for (i=showFiles.size()-1;i>=0;i--) + { + // get extension from showFiles[i] + const char * fileExt = dStrrchr(showFiles[i].pFileName,'.'); + if (!fileExt || dStricmp(fileExt+1,ext)) + showFiles.erase(i); + } + + list->clear(); + for (i=0;iaddEntry(i, showFiles[i].pFileName); + if (showFiles.size()) + list->setSelectedCell(Point2I(0,0)); + else + list->deselectCells(); + + Con::setVariable("$showFileCommand",command); +} + +//------------------------------------------------------------ +// basic class for displaying a tsshape +//------------------------------------------------------------ + +ShowTSShape::ShowTSShape(const char * shapeName) +{ + mTypeMask = StaticObjectType; + + shapeInstance = NULL; + dStrcpy(shapeNameBuffer,shapeName); + timeScale = 1.0f; + addGround = false; + stayGrounded = true; + char fileBuffer[256]; + dStrcpy(fileBuffer,"shapes/"); + dStrcat(fileBuffer,shapeName); + + // before loading shape, execute associated script + char * ext = dStrstr( static_cast( fileBuffer ), const_cast( ".dts" ) ); + if (ext) + { + ext[1]='c'; + ext[2]='s'; + ext[3]='\0'; + Con::executef(2,"exec",fileBuffer); + ext[1]='d'; + ext[2]='t'; + ext[3]='s'; + } + + Resource hShape; + hShape = ResourceManager->load(fileBuffer); + if (!bool(hShape)) + return; + + shapeInstance = new TSShapeInstance(hShape, true); + emapList.load(MeshTexture,true); + shapeInstance->setEnvironmentMap(emapList.getMaterial(0)); + + newThread(); // does nothing if shape has no sequences + +// // TODO: fix this in exporter at some point... + Point3F & center = const_cast(shapeInstance->getShape()->center); + center = shapeInstance->getShape()->bounds.min + shapeInstance->getShape()->bounds.max; + center *= 0.5f; + + mat.identity(); + setPosition(camPos); +} + +//-------------------------------------------------------------------------- +bool ShowTSShape::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::EndSort; + state->insertRenderImage(image); + + return false; +} + +void ShowTSShape::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + render(); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + + +//-------------------------------------- +bool ShowTSShape::onAdd() +{ + mWorldToObj = mObjToWorld; + mWorldToObj.affineInverse(); + resetWorldBox(); + + if (!Parent::onAdd() || !shapeInstance) + // shape instantiation failed + return false; + + // make us the center of attention + orbitUs(); + + // We're always a client object... + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + + mObjBox = shapeInstance->getShape()->bounds; + resetWorldBox(); + + SimSet * showSet = static_cast(Sim::findObject("showSet")); + if (!showSet) + { + showSet = new SimSet; + showSet->registerObject("showSet"); + Sim::getRootGroup()->addObject(showSet); + } + showSet->addObject(this); + + currentShow = this; + + return true; +} + +void ShowTSShape::onRemove() +{ + gClientSceneGraph->removeObjectFromScene(this); + gClientContainer.removeObject(this); + + if (this == currentShow) + { + currentShow = NULL; + showUpdateThreadControl(); + } + Parent::onRemove(); +} + +ShowTSShape::~ShowTSShape() +{ + delete shapeInstance; + if (orbitPos == ¢erPos) + orbitPos = NULL; +} + +void ShowTSShape::newThread() +{ + if (shapeInstance->getShape()->sequences.empty()) + return; + + threads.increment(); + threads.last() = shapeInstance->addThread(); + play.push_back(true); + scale.push_back(1.0f); +} + +void ShowTSShape::deleteThread(S32 th) +{ + if (th<0 || th>=threads.size()) + return; + + shapeInstance->destroyThread(threads[th]); + threads.erase(th); + play.erase(th); + scale.erase(th); +} + +void ShowTSShape::setPosition(Point3F p) +{ + if (stayGrounded && gClientSceneGraph->getCurrentTerrain()) + // won't actually change p.z if on empty square + gClientSceneGraph->getCurrentTerrain()->getHeight(Point2F(p.x,p.y),&p.z); + + centerPos = p + shapeInstance->getShape()->center; + mat.setColumn(3,p); + setTransform(mat); +} + +void ShowTSShape::addGroundTransform(const MatrixF & ground) +{ + // add some ground transform to our current transform + MatrixF m; + m.mul(mat,ground); + mat = m; + Point3F p; + mat.getColumn(3,&p); + setPosition(p); +} + +void ShowTSShape::orbitUs() +{ + // make us the center of attention + minOrbitDist = shapeInstance->getShape()->radius; + setOrbit(¢erPos,false); +} + +bool ShowTSShape::handleMovement(float leftSpeed, float rightSpeed, + float forwardSpeed, float backwardSpeed, + float upSpeed, float downSpeed, + float delta) +{ + if (!currentShow) + return false; + + Point3F vec,p; + + // turn shape left/right + float turnLR = (gShowShapeRightSpeed-gShowShapeLeftSpeed) * delta * 0.1f; + MatrixF turn(EulerF(0,0,turnLR)); + currentShow->addGroundTransform(turn); + + if (keyboardControlsShape && currentShow) + { + currentShow->getFeetPosition(&p); + + // the following moves shape with camera orthoganol to world + // depends on camera not having roll + + cameraMatrix.getColumn(0, &vec); + vec.z = 0.0f; + vec.normalize(); + p += vec * (rightSpeed - leftSpeed) * delta; + if (!orbitPos) + // move camera with shape + camPos += vec * (rightSpeed - leftSpeed) * delta; + + cameraMatrix.getColumn(1, &vec); + vec.z = 0.0f; + vec.normalize(); + p += vec * (forwardSpeed - backwardSpeed) * delta; + if (!orbitPos) + // move camera with shape + camPos += vec * (forwardSpeed - backwardSpeed) * delta; + + vec.set(0,0,1.0f); + p += vec * (upSpeed - downSpeed) * delta; + if (!orbitPos) + // move camera with shape + camPos += vec * (upSpeed - downSpeed) * delta; + + currentShow->setPosition(p); + + // if orbit cam is on, zero out speeds and adjust like normal + // if not, let normal camera move us with these speeds... + if (orbitPos) + leftSpeed = rightSpeed = forwardSpeed = backwardSpeed = upSpeed = downSpeed = 0.0f; + } + + if (!orbitPos) + return false; + + // forward/backward = closer/farther + + orbitDist -= (forwardSpeed - backwardSpeed) * delta; + if (orbitDist0.001f ? 1.0f / orbitDist : 0.0f; + + // up/down & left/right = rotate + + float xr = (upSpeed-downSpeed) * invD * delta; // convert linear speed to angular + float zr = (leftSpeed-rightSpeed) * invD * delta; // convert linear speed to angular + + // cap rotation rate (for when you're real close to orbit point) + if (xr > maxOrbitASin) + xr = maxOrbitASin; + else if (xr < -maxOrbitASin) + xr = -maxOrbitASin; + if (zr > maxOrbitASin) + zr = maxOrbitASin; + else if (zr < -maxOrbitASin) + zr = -maxOrbitASin; + + camRot.x += mAsin(xr); + camRot.z += mAsin(zr); + + MatrixF xRot, zRot, newCameraRot; + xRot.set(EulerF(camRot.x, 0, 0)); + zRot.set(EulerF(0, 0, camRot.z)); + newCameraRot.mul(zRot,xRot); + + // adjust cameraPos so we still face orbitPos (and are still d units away) + newCameraRot.getColumn(1,&vec); + vec *= orbitDist; + camPos = *orbitPos - vec; + + return true; +} + +void ShowTSShape::render() +{ + bool wasLit = glIsEnabled(GL_LIGHTING); + bool wasLit0 = glIsEnabled(GL_LIGHT0); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + + glLightfv(GL_LIGHT0,GL_POSITION,(float*)&lightDir); + + GLfloat amb[] = {ambR,ambG,ambB,0.0f}; + GLfloat diff[] = {diffR,diffG,diffB,1.0f}; + GLfloat matProp[] = {1.0f,1.0f,1.0f,1.0f}; + GLfloat zeroColor[] = {0,0,0,0}; + + glLightModelfv(GL_LIGHT_MODEL_AMBIENT,amb); + glLightfv(GL_LIGHT0,GL_DIFFUSE,diff); + glLightfv(GL_LIGHT0,GL_AMBIENT,zeroColor); + glLightfv(GL_LIGHT0,GL_SPECULAR,zeroColor); + + glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,matProp); + glMaterialfv(GL_FRONT_AND_BACK,GL_EMISSION,zeroColor); + glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,zeroColor); + +// glPushMatrix(); +// dglMultMatrix(&mat); + + shapeInstance->setEnvironmentMapOn(true,emapAlpha); + + if (Con::getBoolVariable("$showAutoDetail",false)) + { + shapeInstance->selectCurrentDetail(); + GuiSliderCtrl * slider = static_cast(Sim::findObject("showDetailSlider")); + // set slider value to be correct + if (slider && slider->mAwake) + { + char buffer[32]; + dSprintf(buffer,32,"%f",(F32)shapeInstance->getCurrentDetail()+1.0f-shapeInstance->getCurrentIntraDetail()); + slider->setScriptValue(buffer); + } + } + else + { + GuiSliderCtrl * slider = static_cast(Sim::findObject("showDetailSlider")); + // set current detail level based on slider + if (slider) + shapeInstance->setCurrentDetail(slider->getValue(),1.0f-(F32)((F32)slider->getValue()-(S32)slider->getValue())); + } + GuiTextCtrl * detailText1 = static_cast(Sim::findObject("showDetailInfoText1")); + GuiTextCtrl * detailText2 = static_cast(Sim::findObject("showDetailInfoText2")); + if (detailText1 && detailText2) + { + char buffer[128]; + S32 dl = shapeInstance->getCurrentDetail(); + S32 dlSize = dl>=0 ? shapeInstance->getShape()->details[dl].size : 0; + S32 polys = dl>=0 ? shapeInstance->getShape()->details[dl].polyCount : 0; + Point3F p; + mObjToWorld.getColumn(3,&p); + p -= camPos; + F32 dist = p.len(); + F32 pixelSize = dglProjectRadius(dist,shapeInstance->getShape()->radius) * dglGetPixelScale() * TSShapeInstance::smDetailAdjust; + dSprintf(buffer,128,"detail leve: %i, detail size: %i",dl,dlSize); + detailText1->setText(buffer); + dSprintf(buffer,128,"size: %f, polys: %i, dist: %f",pixelSize,polys,dist); + detailText2->setText(buffer); + } + + shapeInstance->animate(); + + shapeInstance->render(); + +// glPopMatrix(); + + if (!wasLit) + glDisable(GL_LIGHTING); + if (!wasLit0) + glDisable(GL_LIGHT0); +} + +void ShowTSShape::reset() +{ + SimSet * set = static_cast(Sim::findObject("showSet")); + if (!set) + return; + + for (SimSet::iterator itr = set->begin(); itr!=set->end(); itr++) + static_cast(*itr)->resetInstance(); +} + +void ShowTSShape::resetInstance() +{ + // do nothing for now... + // eventually, may need to delete shape and then reload it, but seems to work fine now +} + +void ShowTSShape::advanceTime(U32 delta) +{ + // do we have items ready to be loaded? + if (!loadQueue.empty()) + { + while (!loadQueue.empty()) + { + char buffer[256]; + if (dStrstr( static_cast( loadQueue.front() ), static_cast( ".dts" ) )) + { + dSprintf(buffer,256,"showShapeLoad(\"%s\");",loadQueue.front()); + Con::evaluate(buffer); + } + else if (dStrstr( static_cast( loadQueue.front() ), static_cast( ".dsq" ))) + { + dSprintf(buffer,256,"showSequenceLoad(\"%s\");",loadQueue.front()); + Con::evaluatef(buffer); + } + else if (dStrstr( static_cast( loadQueue.front() ), static_cast( ".mis" ))) + Con::setVariable("$showMission",loadQueue.front()); + dFree(loadQueue.front()); + loadQueue.pop_front(); + } + Con::evaluate("startShow();"); + } + + // get the show set... + SimSet * set = static_cast(Sim::findObject("showSet")); + + // update the instances... + if (set) + for (SimSet::iterator itr = set->begin(); itr!=set->end(); itr++) + static_cast(*itr)->advanceTimeInstance(delta); + + // set thread position slider + GuiSliderCtrl * slider = static_cast(Sim::findObject("threadPosition")); + GuiTextListCtrl * threadList = static_cast(Sim::findObject("threadList")); + GuiTextCtrl * transitionSignal = static_cast(Sim::findObject("transitionSignal")); + bool inTransition = false; + if (currentShow && threadList && slider && slider->mAwake && threadList->getSelectedCell().y>=0) + { + S32 th = threadList->getSelectedCell().y; + if (currentShow->getPlay(th)) + { + char buffer[32]; + dSprintf(buffer,32,"%f",currentShow->getPos(th)); + slider->setScriptValue(buffer); + } + else + currentShow->setPos(th,slider->getValue()); + + inTransition = currentShow->isInTransition(th); + } + + if (transitionSignal) + { + if (inTransition) + transitionSignal->setText("T"); + else + transitionSignal->setText(" "); + } + + if (!gInitLightingSliders) + { + char buffer[32]; + slider = static_cast(Sim::findObject("emapAlpha")); + if (slider) + { + dSprintf(buffer,32,"%f",emapAlpha); + slider->setField("value",buffer); + } + slider = static_cast(Sim::findObject("ambR")); + if (slider) + { + dSprintf(buffer,32,"%f",ambR); + slider->setField("value",buffer); + } + slider = static_cast(Sim::findObject("ambG")); + if (slider) + { + dSprintf(buffer,32,"%f",ambG); + slider->setField("value",buffer); + } + slider = static_cast(Sim::findObject("ambB")); + if (slider) + { + dSprintf(buffer,32,"%f",ambB); + slider->setField("value",buffer); + } + slider = static_cast(Sim::findObject("diffR")); + if (slider) + { + dSprintf(buffer,32,"%f",diffR); + slider->setField("value",buffer); + } + slider = static_cast(Sim::findObject("diffG")); + if (slider) + { + dSprintf(buffer,32,"%f",diffG); + slider->setField("value",buffer); + } + slider = static_cast(Sim::findObject("diffB")); + if (slider) + { + dSprintf(buffer,32,"%f",diffB); + slider->setField("value",buffer); + } + gInitLightingSliders = true; + } + + // handle lighting sliders... + slider = static_cast(Sim::findObject("emapAlpha")); + if (slider) + emapAlpha = slider->getValue(); + slider = static_cast(Sim::findObject("ambR")); + if (slider) + ambR = slider->getValue(); + slider = static_cast(Sim::findObject("ambG")); + if (slider) + ambG = slider->getValue(); + slider = static_cast(Sim::findObject("ambB")); + if (slider) + ambB = slider->getValue(); + slider = static_cast(Sim::findObject("diffR")); + if (slider) + diffR = slider->getValue(); + slider = static_cast(Sim::findObject("diffG")); + if (slider) + diffG = slider->getValue(); + slider = static_cast(Sim::findObject("diffB")); + if (slider) + diffB = slider->getValue(); + + + // handle movement...a little weird because spliced from early version: + + F32 leftSpeed = gShowLeftAction; + F32 rightSpeed = gShowRightAction; + F32 forwardSpeed = gShowForwardAction; + F32 backwardSpeed = gShowBackwardAction; + F32 upSpeed = gShowUpAction; + F32 downSpeed = gShowDownAction; + + F32 timeScale = gShowMovementSpeed * 25.0f * ((F32)delta) / F32(1000); + if (!ShowTSShape::handleMovement(leftSpeed,rightSpeed,forwardSpeed,backwardSpeed,upSpeed,downSpeed,timeScale)) + { + Point3F vec; + cameraMatrix.getColumn(0, &vec); + camPos += vec * (rightSpeed - leftSpeed) * timeScale; + cameraMatrix.getColumn(1, &vec); + camPos += vec * (forwardSpeed - backwardSpeed) * timeScale; + cameraMatrix.getColumn(2, &vec); + camPos += vec * (upSpeed - downSpeed) * timeScale; + } +} + +void ShowTSShape::advanceTimeInstance(U32 delta) +{ + float dt = timeScale * 0.001f * (float)delta; + + for (S32 i=0; iadvanceTime(scale[i]*dt,threads[i]); + + if (addGround) + { + shapeInstance->animateGround(); + addGroundTransform(shapeInstance->getGroundTransform()); + } + + shapeInstance->animate(); +} + +//------------------------------------------------------------ +// TS Control for show objects +//------------------------------------------------------------ + +bool ShowProcessCameraQuery(CameraQuery *q) +{ + MatrixF xRot, zRot; + xRot.set(EulerF(camRot.x, 0, 0)); + zRot.set(EulerF(0, 0, camRot.z)); + + cameraMatrix.mul(zRot, xRot); + q->nearPlane = 0.1; + q->farPlane = 2100.0; + q->fov = 3.1415 / 2; + cameraMatrix.setColumn(3, camPos); + q->cameraMatrix = cameraMatrix; + return true; +} + +void ShowRenderWorld() +{ + glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT); + glDisable(GL_CULL_FACE); + glMatrixMode(GL_MODELVIEW); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + + dglSetCanonicalState(); + gClientSceneGraph->renderScene(); + + glDisable(GL_DEPTH_TEST); +} + +class ShowTSCtrl : public GuiTSCtrl +{ + typedef GuiTSCtrl Parent; +public: + bool processCameraQuery(CameraQuery *query); + void renderWorld(const RectI &updateRect); +// void onMouseMove(const GuiEvent &evt); + + DECLARE_CONOBJECT(ShowTSCtrl); +}; + +IMPLEMENT_CONOBJECT(ShowTSCtrl); + +bool ShowTSCtrl::processCameraQuery(CameraQuery * q) +{ + return ShowProcessCameraQuery(q); +} + +void ShowTSCtrl::renderWorld(const RectI &updateRect) +{ + ShowRenderWorld(); + dglSetClipRect(updateRect); +} + +//------------------------------------------------------------ +// console functions for show plugin +//------------------------------------------------------------ + +static void cShowLoad(SimObject *, S32 argc, const char **argv) +{ + argc; + + Point3F vec,pos; + cameraMatrix.getColumn(1,&vec); + cameraMatrix.getColumn(3,&pos); + vec *= initialShowDistance; + + ShowTSShape * show = new ShowTSShape(argv[1]); + + if (show->shapeLoaded()) + { + show->setPosition(pos+vec); + show->registerObject(); + Sim::getRootGroup()->addObject(show); + } + else + delete show; + + showUpdateThreadControl(); + + // make sure detail slider is set correctly + setDetailSlider(); +} + +static void cShowSequenceLoad(SimObject *, S32 argc, const char ** argv) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + return; + + char buffer[512]; + + if (argc==2) + dSprintf(buffer,512,"new TSShapeConstructor() { baseShape=\"%s\";sequence0=\"%s\"; };",currentShow->getShapeName(),argv[1]); + else + dSprintf(buffer,512,"new TSShapeConstructor() { baseShape=\"%s\";sequence0=\"%s %s\"; };",currentShow->getShapeName(),argv[1],argv[2]); + + Con::evaluate(buffer); + ShowTSShape::reset(); +} + +static void cShowSelectSequence(SimObject *, S32, const char **) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + return; + + GuiTextListCtrl * threadList = static_cast(Sim::findObject("threadList")); + if (!threadList) + return; + + GuiTextListCtrl * sequenceList = static_cast(Sim::findObject("sequenceList")); + if (!sequenceList) + return; + + S32 threadNum = threadList->getSelectedCell().y; + S32 seq = sequenceList->getSelectedCell().y; + + if (threadNum>=0 && seq>=0) + { + if (Con::getBoolVariable("$showTransition",false) && !currentShow->isBlend(seq)) + { + F32 pos; + F32 dur = Con::getFloatVariable("$showTransitionDuration",0.2f); + GuiSliderCtrl * tpos = static_cast(Sim::findObject("transitionPosition")); + if (Con::getBoolVariable("$showTransitionSynched",true) || !tpos) + pos = currentShow->getPos(threadNum); + else + pos = tpos->getValue(); + bool targetPlay = Con::getBoolVariable("$showTransitionTargetPlay",true); + currentShow->transitionToSequence(threadNum,seq,pos,dur,targetPlay); + } + else + currentShow->setSequence(threadNum,seq); + + GuiSliderCtrl * slider = static_cast(Sim::findObject("threadPosition")); + if (slider && slider->getRoot()) + { + char buffer[32]; + dSprintf(buffer,32,"%f",currentShow->getPos(threadNum)); + slider->setScriptValue(buffer); + } + } + + showUpdateThreadControl(); + return; +} + +static void cShowSetDetailSlider(SimObject *, S32, const char **) +{ + setDetailSlider(); +} + +static void cShowUpdateThreadControl(SimObject *, S32, const char **) +{ + showUpdateThreadControl(); +} + +static void cShowPlay(SimObject *, S32 argc, const char ** argv) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + return; + + bool val = !dStricmp(argv[0],"showPlay"); + + if (argc==1) + { + for (S32 i=0; i < currentShow->getThreadCount(); i++) + currentShow->setPlay(i,val); + } + else + { + S32 i = dAtoi(argv[1]); + if (i>=0) + currentShow->setPlay(i,val); + } +} + +static void cShowSetScale(SimObject *, S32, const char ** argv) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + return; + + S32 idx = dAtoi(argv[1]); + float s = dAtof(argv[2]); + if (idx>=0) + currentShow->setThreadScale(idx,s); + + showUpdateThreadControl(); +} + +static void cShowSetPos(SimObject *, S32, const char ** argv) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + return; + + S32 idx = dAtoi(argv[1]); + float s = dAtof(argv[2]); + if (idx>=0) + currentShow->setPos(idx,s); +} + +static void cShowNewThread(SimObject *, S32, const char **) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + return; + + currentShow->newThread(); + + showUpdateThreadControl(); +} + +static void cShowDeleteThread(SimObject *, S32, const char ** argv) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + return; + + S32 th = dAtoi(argv[1]); + if (th>=0) + currentShow->deleteThread(th); + + showUpdateThreadControl(); +} + +static void cShowSetFileList(SimObject *, S32, const char ** argv) +{ + showSetFileList(argv[1],argv[2],argv[3]); +} + +static void cShowToggleRoot(SimObject *, S32, const char **) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + return; + + currentShow->setAnimateRoot(!currentShow->getAnimateRoot()); +} + +static void cShowToggleStick(SimObject *, S32, const char **) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + if (!currentShow) + return; + + currentShow->setStickToGround(!currentShow->getStickToGround()); +} + +static void cShowSetCamera(SimObject *, S32, const char ** argv) +{ + ShowTSShape * currentShow = ShowTSShape::currentShow; + + if (argv[1][0]=='t' || argv[1][0]=='T' && currentShow) + // orbit + currentShow->orbitUs(); + else + // no orbit -- camera moves freely + orbitPos = NULL; +} + +static void cShowSetKeyboard(SimObject *, S32, const char ** argv) +{ + keyboardControlsShape = (argv[1][0]=='t' || argv[1][0]=='T'); +} + +static void cShowTurnLeft(SimObject *, S32, const char **argv) +{ + gShowShapeLeftSpeed = dAtof(argv[1]); +} + +static void cShowTurnRight(SimObject *, S32, const char **argv) +{ + gShowShapeRightSpeed = dAtof(argv[1]); +} + +static void cShowSetLight(SimObject *, S32, const char **) +{ + cameraMatrix.getColumn(1,(Point3F*)&lightDir); + lightDir.x *= -1.0f; + lightDir.y *= -1.0f; + lightDir.z *= -1.0f; + lightDir.w *= -1.0f; +} + +void ShowInit() +{ + Con::addCommand("showShapeLoad",cShowLoad,"showShapeLoad(shapeName,faceCamera);",2,3); + Con::addCommand("showSequenceLoad",cShowSequenceLoad,"showSequenceLoad(sequenceFile,[sequenceName]);",2,3); + + Con::addCommand("showTurnLeft",cShowTurnLeft,"showTurnLeft(amt);",2,2); + Con::addCommand("showTurnRight",cShowTurnRight,"showTurnRight(amt);",2,2); + + Con::addCommand("showUpdateThreadControl",cShowUpdateThreadControl,"showUpdateThreadControl();",1,1); + Con::addCommand("showSelectSequence",cShowSelectSequence,"showSelectSequence();",1,1); + + Con::addCommand("showPlay",cShowPlay,"showPlay([threadNum]);",1,2); + Con::addCommand("showStop",cShowPlay,"showPlay([threadNum]);",1,2); + + Con::addCommand("showSetScale",cShowSetScale,"showSetScale(threadNum,scale);",3,3); + Con::addCommand("showSetPos",cShowSetPos,"showSetPos(threadNum,pos);",2,2); + + Con::addCommand("showNewThread",cShowNewThread,"showNewThread();",1,1); + Con::addCommand("showDeleteThread",cShowDeleteThread,"showDeleteThread(threadNum);",2,2); + + Con::addCommand("showSetFileList",cShowSetFileList,"showSetFileList(path,ext,command);",4,4); + + Con::addCommand("showToggleRoot",cShowToggleRoot,"showToggleRoot();",1,1); + Con::addCommand("showToggleStick",cShowToggleStick,"showToggleStick();",1,1); + Con::addCommand("showSetCamera",cShowSetCamera,"showSetCamera(orbitShape);",2,2); + Con::addCommand("showSetKeyboard",cShowSetKeyboard,"showSetKeyboard(moveShape);",2,2); + + Con::addCommand("showSetLightDirection",cShowSetLight,"showSetLightDirection();",1,1); + Con::addCommand("showSetDetailSlider",cShowSetDetailSlider,"showSetDetailSlider();",1,1); + + Con::addVariable("showForwardAction", TypeF32, &gShowForwardAction); + Con::addVariable("showBackwardAction", TypeF32, &gShowBackwardAction); + Con::addVariable("showUpAction", TypeF32, &gShowUpAction); + Con::addVariable("showDownAction", TypeF32, &gShowDownAction); + Con::addVariable("showLeftAction", TypeF32, &gShowLeftAction); + Con::addVariable("showRightAction", TypeF32, &gShowRightAction); + + Con::addVariable("showMovementSpeed", TypeF32, &gShowMovementSpeed); + + Con::addVariable("showPitch", TypeF32, &camRot.x); + Con::addVariable("showYaw", TypeF32, &camRot.z); + + // anything on the command line we should know about... + // we stake out anything between -show and the next -* parameter + bool showSomething = false; + S32 count = Con::getIntVariable("Game::argc"); + for (S32 i=1; i threads; + Vector play; + Vector scale; + + MatrixF mat; + Point3F centerPos; // we want to be able to point here for orbit camera + + F32 timeScale; + bool addGround; + bool stayGrounded; + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + +public: + ShowTSShape(const char * shapeName); + ~ShowTSShape(); + bool onAdd(); + void onRemove(); + + static ShowTSShape * currentShow; + + static Vector loadQueue; + + static void reset(); // if a new sequence is loaded, re-load all the shapes... + void resetInstance(); + + static void advanceTime(U32 delta); + void advanceTimeInstance(U32 delta); + + static bool handleMovement(F32 leftSpeed, F32 rightSpeed, + F32 forwardSpeed, F32 backwardSpeed, + F32 upSpeed, F32 downSpeed, + F32 delta); + + MatrixF getMatrix() { return mat; } + void addGroundTransform(const MatrixF & ground); + void setPosition(Point3F p); + void getFeetPosition(Point3F *p) { mat.getColumn(3,p); } + void getCenterPosition(Point3F *p) { *p = centerPos; } + + void render(); + + void orbitUs(); + + // + void newThread(); + void deleteThread(S32 th); + + S32 getThreadCount() { return threads.size(); } + S32 getSequenceCount() { return shapeInstance->getShape()->sequences.size(); } + const char * getSequenceName(S32 idx) { return shapeInstance->getShape()->getName(shapeInstance->getShape()->sequences[idx].nameIndex); } + S32 getSequenceNum(S32 th) { return shapeInstance->getSequence(threads[th]); } + + void setSequence(S32 th, S32 seq) { shapeInstance->setSequence(threads[th],seq,shapeInstance->getPos(threads[th])); } + void setSequence(S32 th, S32 seq, F32 pos) { shapeInstance->setSequence(threads[th],seq,pos); } + void transitionToSequence(S32 th, S32 seq, F32 pos, F32 dur, bool play) { shapeInstance->transitionToSequence(threads[th],seq,pos,dur,play); } + + F32 getPos(S32 th) { return shapeInstance->getPos(threads[th]); } + void setPos(S32 th, F32 pos) { if (thsetPos(threads[th],pos); } + + void setPlay(S32 th, bool on) { if (th>=0 && thisInTransition(threads[th]); } + bool isBlend(S32 seq) { return shapeInstance->getShape()->sequences[seq].isBlend(); } + + void setThreadScale(S32 th, F32 s) { if (th>=0 && thgetShape()->details.size(); } + S32 getCurrentDetail() { return shapeInstance->getCurrentDetail(); } + void setCurrentDetail(S32 dl) { shapeInstance->setCurrentDetail(dl); } + + bool getAnimateRoot() { return addGround; } + void setAnimateRoot(bool nv) { addGround = nv; } + + bool getStickToGround() { return stayGrounded; } + void setStickToGround(bool nv) { stayGrounded = nv; } + + const char * getShapeName() { return shapeNameBuffer; } + bool shapeLoaded() { return shapeInstance; } + TSShapeInstance * getShapeInstance() { return shapeInstance; } +}; + +extern void ShowInit(); + +#endif diff --git a/game/sphere.cc b/game/sphere.cc new file mode 100644 index 0000000..6d7839d --- /dev/null +++ b/game/sphere.cc @@ -0,0 +1,235 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/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.5773502692; + + 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.8506508084; + const F32 one = 0.5257311121; + + 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/game/sphere.h b/game/sphere.h new file mode 100644 index 0000000..f66ccc9 --- /dev/null +++ b/game/sphere.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SPHERE_H_ +#define _SPHERE_H_ + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/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(Point3F a, Point3F b, 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/game/splash.cc b/game/splash.cc new file mode 100644 index 0000000..15f658e --- /dev/null +++ b/game/splash.cc @@ -0,0 +1,834 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "platformWIN32/platformGL.h" +#include "platform/platformAudio.h" +#include "audio/audioDataBlock.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sceneState.h" +#include "core/bitStream.h" +#include "game/particleEngine.h" +#include "game/splash.h" +#include "math/mathIO.h" +#include "terrain/terrData.h" +#include "game/explosion.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, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == 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; + + + if( isClientObject() ) + { + 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.min = Point3F( -1, -1, -1 ); + mObjBox.max = Point3F( 1, 1, 1 ); + resetWorldBox(); + + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + + removeFromProcessList(); + gClientProcessList.addObject(this); + + return true; +} + +//-------------------------------------------------------------------------- +// OnRemove +//-------------------------------------------------------------------------- +void Splash::onRemove() +{ + for( U32 i=0; ideleteWhenEmpty(); + mEmitterList[i] = NULL; + } + } + + ringList.free(); + + 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; +} + + +//-------------------------------------------------------------------------- +// Prep render image +//-------------------------------------------------------------------------- +bool Splash::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)) { + mFog = 0.0; + + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + image->textureSortKey = U32(mDataBlock); + state->setImageRefPoint(this, image); + state->insertRenderImage(image); + } + + return false; +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void Splash::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + state->setupObjectProjection(this); + + render(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------------------------------------------- +// 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 ), dt * 1000 ); + } + } + +} + +//---------------------------------------------------------------------------- +// Update wave +//---------------------------------------------------------------------------- +void Splash::updateWave( F32 dt ) +{ + mVelocity += mDataBlock->acceleration * dt; + mRadius += mVelocity * dt; + +} + +//---------------------------------------------------------------------------- +// Render splash +//---------------------------------------------------------------------------- +void Splash::render() +{ + + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_TEXTURE_2D); + + + SplashRing *ring = NULL; + + U32 i=0; + while( bool( ring = ringList.next( ring ) ) ) + { + SplashRing *next = ringList.next( ring ); + if( !next ) break; + + renderSegment( *ring, *next ); + i++; + } + + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + +} + +//---------------------------------------------------------------------------- +// Render horizontal segment created from 2 sets of points that are the +// top and bottom rings of the segment. +//---------------------------------------------------------------------------- +void Splash::renderSegment( SplashRing &top, SplashRing &bottom ) +{ + F32 texFactor = mDataBlock->texWrap; + + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[0].getGLName() ); + + + F32 topAlpha = top.elapsedTime / top.lifetime; + F32 bottomAlpha = bottom.elapsedTime / bottom.lifetime; + + if( topAlpha < 0.5 ) topAlpha *= 2.0; + else topAlpha = 1.0 - topAlpha; + + if( bottomAlpha < 0.5 ) bottomAlpha *= 2.0; + else bottomAlpha = 1.0 - bottomAlpha; + + top.color.alpha = topAlpha; + bottom.color.alpha = bottomAlpha; + + + glBegin( GL_QUAD_STRIP ); + { + for( U32 i=0; ielapsedTime) / 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 = 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.link( ring ); + } +} + +//---------------------------------------------------------------------------- +// Update rings +//---------------------------------------------------------------------------- +void Splash::updateRings( F32 dt ) +{ + + SplashRing *ring = NULL; + while( bool( ring = ringList.next( ring ) ) ) + { + ring->elapsedTime += dt; + + if( !ring->isActive() ) + { + SplashRing *inactiveRing = ring; + ring = ringList.prev( ring ); + ringList.free( inactiveRing ); + } + else + { + updateRing( ring, dt ); + } + } + +} + +//---------------------------------------------------------------------------- +// Update ring +//---------------------------------------------------------------------------- +void Splash::updateRing( SplashRing *ring, F32 dt ) +{ + + for( U32 i=0; ipoints.size(); i++ ) + { + if( mDead ) + { + Point3F vel = ring->points[i].velocity; + vel.normalize(); + vel *= mDataBlock->acceleration; + ring->points[i].velocity += vel * dt; + } + + ring->points[i].velocity += Point3F( 0.0, 0.0, -9.8 ) * 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/game/splash.h b/game/splash.h new file mode 100644 index 0000000..cb31424 --- /dev/null +++ b/game/splash.h @@ -0,0 +1,185 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_SPLASH +#define _H_SPLASH + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _LLIST_H_ +#include "Core/llist.h" +#endif + +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]; + TextureHandle textureHandle[NUM_TEX]; + + ExplosionData* explosion; + S32 explosionId; + + SplashData(); + DECLARE_CONOBJECT(SplashData); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//-------------------------------------------------------------------------- +// Splash +//-------------------------------------------------------------------------- +class Splash : public GameBase +{ + typedef GameBase Parent; + + private: + SplashData* mDataBlock; + + ParticleEmitter * mEmitterList[ SplashData::NUM_EMITTERS ]; + + LList 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*); + 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 render(); + void renderSegment( SplashRing &top, SplashRing &bottom ); + void spawnExplosion(); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + public: + Splash(); + ~Splash(); + void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); + + bool onNewDataBlock(GameBaseData* dptr); + DECLARE_CONOBJECT(Splash); + static void initPersistFields(); +}; + + +#endif // _H_SPLASH diff --git a/game/staticShape.cc b/game/staticShape.cc new file mode 100644 index 0000000..2e0a6e5 --- /dev/null +++ b/game/staticShape.cc @@ -0,0 +1,224 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "dgl/dgl.h" +#include "Core/dnet.h" +#include "Core/bitStream.h" +#include "game/game.h" +#include "Math/mMath.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "game/moveManager.h" +#include "ts/tsShapeInstance.h" +#include "Core/resManager.h" +#include "game/staticShape.h" +#include "Math/mathIO.h" +#include "game/shadow.h" + +extern void wireCube(F32 size,Point3F pos); + +static const U32 sgAllowedDynamicTypes = (StationObjectType | + GeneratorObjectType | + SensorObjectType); + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(StaticShapeData); + +StaticShapeData::StaticShapeData() +{ + dynamicTypeField = 0; + + genericShadowLevel = StaticShape_GenericShadowLevel; + noShadowLevel = StaticShape_NoShadowLevel; + noIndividualDamage = false; +} + +void StaticShapeData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("noIndividualDamage", TypeBool, Offset(noIndividualDamage, StaticShapeData)); + addField("dynamicType", TypeS32, Offset(dynamicTypeField, StaticShapeData)); +} + +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() +{ + mTypeMask |= StaticShapeObjectType | StaticObjectType; + mDataBlock = 0; +} + +StaticShape::~StaticShape() +{ +} + + +//---------------------------------------------------------------------------- + +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); + } + + if (mShadow) + { + mShadow->setMoving(false); + if (mDataBlock->shape && mDataBlock->shape->sequences.empty()) + // no sequences, can't animate... + mShadow->setAnimating(false); + } +} + +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)) { + bstream->writeAffineTransform(mObjToWorld); + mathWrite(*bstream, mObjScale); + } + + // powered? + bstream->writeFlag(mPowered); + + return retMask; +} + +void StaticShape::unpackUpdate(NetConnection *connection, BitStream *bstream) +{ + Parent::unpackUpdate(connection,bstream); + if (bstream->readFlag()) { + MatrixF mat; + bstream->readAffineTransform(&mat); + Parent::setTransform(mat); + Parent::setRenderTransform(mat); + + VectorF scale; + mathRead(*bstream, &scale); + setScale(scale); + } + + // powered? + mPowered = bstream->readFlag(); +} + + +//---------------------------------------------------------------------------- +static void cSetPowered(SimObject * obj, S32, const char ** argv) +{ + StaticShape * sObj = static_cast(obj); + if(!sObj->isServerObject()) + return; + sObj->setPowered(dAtob(argv[2])); +} + +static bool cIsPowered(SimObject * obj, S32, const char **) +{ + StaticShape * sObj = static_cast(obj); + if(!sObj->isServerObject()) + return(false); + return(sObj->isPowered()); +} + +void StaticShape::consoleInit() +{ + Con::addCommand("StaticShape", "setPoweredState", cSetPowered, "obj.setPoweredState(bool)", 3, 3); + Con::addCommand("StaticShape", "getPoweredState", cIsPowered, "obj.getPoweredState(bool)", 2, 2); +} + + diff --git a/game/staticShape.h b/game/staticShape.h new file mode 100644 index 0000000..89f5d4f --- /dev/null +++ b/game/staticShape.h @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STATICSHAPE_H_ +#define _STATICSHAPE_H_ + +#ifndef _SHAPEBASE_H_ +#include "game/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*); + void interpolateTick(F32 dt); + void setTransform(const MatrixF&); + + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); + + // power + void setPowered(bool power) {mPowered = power;} + bool isPowered() {return(mPowered);} + + static void consoleInit(); +}; + + +#endif diff --git a/game/stationFXPersonal.cc b/game/stationFXPersonal.cc new file mode 100644 index 0000000..d5973c0 --- /dev/null +++ b/game/stationFXPersonal.cc @@ -0,0 +1,418 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/StationFXPersonal.h" +#include "Core/bitStream.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneState.h" +#include "dgl/dgl.h" +#include "Sim/netConnection.h" +#include "game/shapeBase.h" +#include "sceneGraph/sceneGraph.h" +#include "ts/tsShapeInstance.h" + +IMPLEMENT_CO_DATABLOCK_V1(StationFXPersonalData); +IMPLEMENT_CO_NETOBJECT_V1(StationFXPersonal); + + +//************************************************************************** +// Station FX Personal Data +//************************************************************************** +StationFXPersonalData::StationFXPersonalData() +{ + delay = 0.0; + fadeDelay = 1.5; + lifetime = 2.0; + height = 2.5; + numArcSegments = 10.0; + numDegrees = 180.0; + trailFadeTime = 0.2; + leftRadius = 2.0; + rightRadius = 2.0; + leftNodeName = rightNodeName = NULL; + + dMemset( textureName, 0, sizeof( textureName ) ); + dMemset( textureHandle, 0, sizeof( textureHandle ) ); +} + +//-------------------------------------------------------------------------- +void StationFXPersonalData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("delay", TypeF32, Offset( delay, StationFXPersonalData ) ); + addField("fadeDelay", TypeF32, Offset( fadeDelay, StationFXPersonalData ) ); + addField("lifetime", TypeF32, Offset( lifetime, StationFXPersonalData ) ); + addField("height", TypeF32, Offset( height, StationFXPersonalData ) ); + addField("numArcSegments", TypeS32, Offset( numArcSegments, StationFXPersonalData ) ); + addField("numDegrees", TypeF32, Offset( numDegrees, StationFXPersonalData ) ); + addField("trailFadeTime", TypeF32, Offset( trailFadeTime, StationFXPersonalData ) ); + addField("leftRadius", TypeF32, Offset( leftRadius, StationFXPersonalData ) ); + addField("rightRadius", TypeF32, Offset( rightRadius, StationFXPersonalData ) ); + addField("leftNodeName", TypeString, Offset( leftNodeName,StationFXPersonalData ) ); + addField("rightNodeName", TypeString, Offset( rightNodeName,StationFXPersonalData ) ); + addField("texture", TypeString, Offset( textureName, StationFXPersonalData ), SFXC_NUM_TEX ); + +} + +//-------------------------------------------------------------------------- +bool StationFXPersonalData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + return true; +} + +//-------------------------------------------------------------------------- +bool StationFXPersonalData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (server == false) + { + for( int i=0; iwrite(delay); + stream->write(fadeDelay); + stream->write(lifetime); + stream->write(height); + stream->write(numArcSegments); + stream->write(numDegrees); + stream->write(trailFadeTime); + stream->write(leftRadius); + stream->write(rightRadius); + stream->write(numArcSegments); + stream->writeString(leftNodeName); + stream->writeString(rightNodeName); + + for( int i=0; iwriteString(textureName[i]); + } +} + +//-------------------------------------------------------------------------- +void StationFXPersonalData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&delay); + stream->read(&fadeDelay); + stream->read(&lifetime); + stream->read(&height); + stream->read(&numArcSegments); + stream->read(&numDegrees); + stream->read(&trailFadeTime); + stream->read(&leftRadius); + stream->read(&rightRadius); + stream->read(&numArcSegments); + leftNodeName = stream->readSTString(); + rightNodeName = stream->readSTString(); + + for( int i=0; ireadSTString(); + } +} + + +//************************************************************************** +// Station FX Personal +//************************************************************************** +StationFXPersonal::StationFXPersonal() +{ + mCurrMS = 0; + mStationObjectID = -1; + mDataBlock = NULL; + mElapsedTime = 0.0; +} + +//-------------------------------------------------------------------------- +void StationFXPersonal::initPersistFields() +{ + Parent::initPersistFields(); + + addField("stationObject", TypeS32, Offset( mStationObjectID, StationFXPersonal )); +} + +//-------------------------------------------------------------------------- +bool StationFXPersonal::onAdd() +{ + + if(!Parent::onAdd()) + return false; + + if( isServerObject() ) + { + ShapeBase* obj; + if( mStationObjectID != -1 ) + { + Sim::findObject( mStationObjectID, obj ); + mStationObject = obj; + } + } + + mObjBox.min = Point3F( -3, -3, -3 ); + mObjBox.max = Point3F( 3, 3, 3 ); + resetWorldBox(); + + if( mStationObject ) + { + setTransform( mStationObject->getTransform() ); + } + + addToScene(); + + return true; +} + +//-------------------------------------------------------------------------- +void StationFXPersonal::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +//-------------------------------------------------------------------------- +bool StationFXPersonal::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + +//-------------------------------------------------------------------------- +// Process tick +//-------------------------------------------------------------------------- +void StationFXPersonal::processTick(const Move*) +{ + mCurrMS += TickMs; + + if( isServerObject() ) + { + if( mCurrMS >= (mDataBlock->lifetime * 1000) ) + { + deleteObject(); + return; + } + } +} + +//-------------------------------------------------------------------------- +// Advance time +//-------------------------------------------------------------------------- +void StationFXPersonal::advanceTime(F32 dt) +{ + mElapsedTime += dt; +} + +//-------------------------------------------------------------------------- +// Prep render image +//-------------------------------------------------------------------------- +bool StationFXPersonal::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)) + { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + state->setImageRefPoint(this, image); + state->insertRenderImage(image); + } + + return false; +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void StationFXPersonal::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + state->setupObjectProjection(this); + + renderWall( mDataBlock->numArcSegments, mDataBlock->leftRadius, mDataBlock->height, + mDataBlock->leftNodeName, mDataBlock->numDegrees ); + + renderWall( mDataBlock->numArcSegments, mDataBlock->rightRadius, mDataBlock->height, + mDataBlock->rightNodeName, mDataBlock->numDegrees ); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); + +} + +//-------------------------------------------------------------------------- +// Render wall +//-------------------------------------------------------------------------- +void StationFXPersonal::renderWall( F32 numSegments, F32 radius, F32 height, + StringTableEntry nodeName, F32 numDegrees ) +{ + if( !mStationObject ) return; + + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + MatrixF stationTrans; + TSShapeInstance *shapeInstance = mStationObject->getShapeInstance(); + + shapeInstance->animate(); + + if( mElapsedTime < mDataBlock->trailFadeTime ) + { + F32 percent = mElapsedTime / mDataBlock->trailFadeTime; + numDegrees *= percent; + } + if( (mDataBlock->lifetime - mElapsedTime) < mDataBlock->trailFadeTime ) + { + F32 timeLeft = mDataBlock->lifetime - mElapsedTime; + numDegrees *= timeLeft / mDataBlock->trailFadeTime; + } + + TSShape *shape = shapeInstance->getShape(); + S32 node = shape->findNode( nodeName ); + + MatrixF nodeTrans = shapeInstance->mNodeTransforms[node]; + + stationTrans.mul( mStationObject->getTransform(), nodeTrans ); + dglMultMatrix( &stationTrans ); + + if( mElapsedTime > mDataBlock->fadeDelay ) + { + F32 timeLeft = mDataBlock->lifetime - mElapsedTime; + F32 fadeTime = mDataBlock->lifetime - mDataBlock->fadeDelay; + F32 fadePercent = (timeLeft/fadeTime); + glColor4f( 1.0, 1.0, 1.0, fadePercent ); + } + else + { + glColor4f( 1.0, 1.0, 1.0, 1.0 ); + } + + glDepthMask( GL_FALSE ); + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[0].getGLName() ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + + glBegin( GL_TRIANGLE_STRIP ); + + for( U32 i=0; iwriteFlag(mask & GameBase::InitialUpdateMask)) + { + if( bool(mStationObject) ) + { + S32 ghostIndex = con->getGhostIndex(mStationObject); + if (stream->writeFlag(ghostIndex != -1)) { + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount); + } + } + else + { + stream->writeFlag(false); + } + + } + + return retMask; +} + +//-------------------------------------------------------------------------- +// Unpack update +//-------------------------------------------------------------------------- +void StationFXPersonal::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + // initial update + if( stream->readFlag() ) + { + if (stream->readFlag()) + { + mStationObjectID = stream->readRangedU32(0, NetConnection::MaxGhostCount); + + NetObject* pObject = con->resolveGhost(mStationObjectID); + if (pObject != NULL) + { + mStationObject = dynamic_cast(pObject); + } + } + } + +} diff --git a/game/stationFXPersonal.h b/game/stationFXPersonal.h new file mode 100644 index 0000000..a0f2ce1 --- /dev/null +++ b/game/stationFXPersonal.h @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_STATIONFXPERSONAL +#define _H_STATIONFXPERSONAL + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif + +class ShapeBase; + + +//-------------------------------------------------------------------------- +// StationFXPersonalData +//-------------------------------------------------------------------------- +class StationFXPersonalData : public GameBaseData +{ + public: + + enum StationFXConsts + { + SFXC_NUM_TEX = 2, + }; + + + typedef GameBaseData Parent; + + F32 delay; + F32 fadeDelay; + F32 lifetime; + F32 height; + F32 leftRadius; + F32 rightRadius; + U32 numArcSegments; + F32 numDegrees; + F32 trailFadeTime; + + StringTableEntry leftNodeName; + StringTableEntry rightNodeName; + + StringTableEntry textureName[SFXC_NUM_TEX]; + TextureHandle textureHandle[SFXC_NUM_TEX]; + + + + StationFXPersonalData(); + DECLARE_CONOBJECT(StationFXPersonalData); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + + +//-------------------------------------------------------------------------- +// StationFXPersonal +//-------------------------------------------------------------------------- +class StationFXPersonal : public GameBase +{ + typedef GameBase Parent; + + private: + StationFXPersonalData * mDataBlock; + S32 mCurrMS; + S32 mStationObjectID; + SimObjectPtr mStationObject; + F32 mElapsedTime; + + void renderWall( F32 numSegments, F32 radius, F32 height, StringTableEntry nodeName, F32 numDegrees ); + + protected: + bool onAdd(); + void onRemove(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); + + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + void advanceTime(F32); + bool onNewDataBlock(GameBaseData* dptr); + void processTick(const Move*); + + public: + StationFXPersonal(); + + DECLARE_CONOBJECT(StationFXPersonal); + static void initPersistFields(); +}; + +#endif // _H_STATIONFXPERSONAL + diff --git a/game/stationFXVehicle.cc b/game/stationFXVehicle.cc new file mode 100644 index 0000000..85b7495 --- /dev/null +++ b/game/stationFXVehicle.cc @@ -0,0 +1,697 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/StationFXVehicle.h" +#include "core/bitStream.h" +#include "console/consoleTypes.h" +#include "scenegraph/sceneState.h" +#include "dgl/dgl.h" +#include "sim/netConnection.h" +#include "game/shapeBase.h" +#include "scenegraph/sceneGraph.h" +#include "ts/tsShapeInstance.h" +#include "math/mathUtils.h" +#include "math/mathIO.h" + +IMPLEMENT_CO_DATABLOCK_V1(StationFXVehicleData); +IMPLEMENT_CO_NETOBJECT_V1(StationFXVehicle); + + +//************************************************************************** +// Station FX Personal Data +//************************************************************************** +StationFXVehicleData::StationFXVehicleData() +{ + glowTopHeight = 0.5; + glowBottomHeight = 0.1; + glowTopRadius = 8.0; + glowBottomRadius = 7.5; + numGlowSegments = 20; + glowFadeTime = 1.0; + + armLightDelay = 2.0; + armLightLifetime = 5.0; + armLightFadeTime = 2.0; + + sphereColor.set( 0.1, 0.1, 0.5, 1.0 ); + spherePhiSegments = 13; + sphereThetaSegments = 5; + sphereRadius = 12.0; + sphereScale.set( 1.0, 1.0, 0.85 ); + + lifetime = 6.0; + numArcSegments = 10.0; + + glowNodeName = NULL; + dMemset( leftNodeName, 0, sizeof( leftNodeName ) ); + dMemset( rightNodeName, 0, sizeof( rightNodeName ) ); + + dMemset( textureName, 0, sizeof( textureName ) ); + dMemset( textureHandle, 0, sizeof( textureHandle ) ); +} + +//-------------------------------------------------------------------------- +void StationFXVehicleData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("glowTopHeight", TypeF32, Offset( glowTopHeight, StationFXVehicleData ) ); + addField("glowBottomHeight", TypeF32, Offset( glowBottomHeight, StationFXVehicleData ) ); + addField("glowTopRadius", TypeF32, Offset( glowTopRadius, StationFXVehicleData ) ); + addField("glowBottomRadius", TypeF32, Offset( glowBottomRadius, StationFXVehicleData ) ); + addField("numGlowSegments", TypeS32, Offset( numGlowSegments, StationFXVehicleData ) ); + addField("glowFadeTime", TypeF32, Offset( glowFadeTime, StationFXVehicleData ) ); + + addField("armLightDelay", TypeF32, Offset( armLightDelay, StationFXVehicleData ) ); + addField("armLightLifetime", TypeF32, Offset( armLightLifetime, StationFXVehicleData ) ); + addField("armLightFadeTime", TypeF32, Offset( armLightFadeTime, StationFXVehicleData ) ); + + addField("sphereColor", TypeColorF, Offset( sphereColor, StationFXVehicleData ) ); + addField("spherePhiSegments", TypeS32, Offset( spherePhiSegments, StationFXVehicleData ) ); + addField("sphereThetaSegments", TypeS32, Offset( sphereThetaSegments, StationFXVehicleData ) ); + addField("sphereRadius", TypeF32, Offset( sphereRadius, StationFXVehicleData ) ); + addField("sphereScale", TypePoint3F,Offset( sphereScale, StationFXVehicleData ) ); + + addField("lifetime", TypeF32, Offset( lifetime, StationFXVehicleData ) ); + addField("numArcSegments", TypeS32, Offset( numArcSegments, StationFXVehicleData ) ); + + addField("glowNodeName", TypeString, Offset( glowNodeName, StationFXVehicleData ) ); + addField("leftNodeName", TypeString, Offset( leftNodeName, StationFXVehicleData ), SFXC_NUM_NODES ); + addField("rightNodeName", TypeString, Offset( rightNodeName, StationFXVehicleData ), SFXC_NUM_NODES ); + addField("texture", TypeString, Offset( textureName, StationFXVehicleData ), SFXC_NUM_TEX ); + +} + +//-------------------------------------------------------------------------- +bool StationFXVehicleData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + return true; +} + +//-------------------------------------------------------------------------- +bool StationFXVehicleData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (server == false) + { + for( int i=0; iwrite(glowTopHeight); + stream->write(glowBottomHeight); + stream->write(glowTopRadius); + stream->write(glowBottomRadius); + stream->write(numGlowSegments); + stream->write(glowFadeTime); + + stream->write(armLightDelay); + stream->write(armLightLifetime); + stream->write(armLightFadeTime); + + stream->write(lifetime); + stream->write(numArcSegments); + + stream->write(sphereColor); + stream->write(spherePhiSegments); + stream->write(sphereThetaSegments); + stream->write(sphereRadius); + mathWrite(*stream, sphereScale ); + + stream->writeString(glowNodeName); + + U32 i=0; + for( i=0; iwriteString(leftNodeName[i]); + stream->writeString(rightNodeName[i]); + } + + for( i=0; iwriteString(textureName[i]); + } +} + +//-------------------------------------------------------------------------- +void StationFXVehicleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&glowTopHeight); + stream->read(&glowBottomHeight); + stream->read(&glowTopRadius); + stream->read(&glowBottomRadius); + stream->read(&numGlowSegments); + stream->read(&glowFadeTime); + + stream->read(&armLightDelay); + stream->read(&armLightLifetime); + stream->read(&armLightFadeTime); + + stream->read(&lifetime); + stream->read(&numArcSegments); + + stream->read(&sphereColor); + stream->read(&spherePhiSegments); + stream->read(&sphereThetaSegments); + stream->read(&sphereRadius); + mathRead( *stream, &sphereScale ); + + glowNodeName = stream->readSTString(); + + U32 i=0; + for( i=0; ireadSTString(); + rightNodeName[i] = stream->readSTString(); + } + + for( i=0; ireadSTString(); + } +} + + +//************************************************************************** +// Station FX Personal +//************************************************************************** +StationFXVehicle::StationFXVehicle() +{ + mCurrMS = 0; + mStationObjectID = -1; + mDataBlock = NULL; + mElapsedTime = 0.0; +} + +//-------------------------------------------------------------------------- +void StationFXVehicle::initPersistFields() +{ + Parent::initPersistFields(); + + addField("stationObject", TypeS32, Offset( mStationObjectID, StationFXVehicle )); +} + +//-------------------------------------------------------------------------- +bool StationFXVehicle::onAdd() +{ + + if(!Parent::onAdd()) + return false; + + if( isServerObject() ) + { + ShapeBase* obj; + if( mStationObjectID != -1 ) + { + Sim::findObject( mStationObjectID, obj ); + mStationObject = obj; + } + } + + mObjBox.min = Point3F( -12, -12, -0 ); + mObjBox.max = Point3F( 12, 12, 12 ); + resetWorldBox(); + + if( mStationObject ) + { + setTransform( mStationObject->getTransform() ); + } + + addToScene(); + + return true; +} + +//-------------------------------------------------------------------------- +void StationFXVehicle::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +//-------------------------------------------------------------------------- +bool StationFXVehicle::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + +//-------------------------------------------------------------------------- +// Process tick +//-------------------------------------------------------------------------- +void StationFXVehicle::processTick(const Move*) +{ + mCurrMS += TickMs; + + if( isServerObject() ) + { + if( mCurrMS >= (mDataBlock->lifetime * 1000) ) + { + deleteObject(); + return; + } + } +} + +//-------------------------------------------------------------------------- +// Advance time +//-------------------------------------------------------------------------- +void StationFXVehicle::advanceTime(F32 dt) +{ + mElapsedTime += dt; +} + +//-------------------------------------------------------------------------- +// Prep render image +//-------------------------------------------------------------------------- +bool StationFXVehicle::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)) + { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; +// image->sortType = SceneRenderImage::Point; + image->sortType = SceneRenderImage::EndSort; + state->setImageRefPoint(this, image); + state->insertRenderImage(image); + + } + + return false; +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void StationFXVehicle::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + state->setupObjectProjection(this); + + F32 radius[4]; + + radius[0] = findRadius( mDataBlock->leftNodeName[0], mDataBlock->rightNodeName[0] ); + radius[1] = findRadius( mDataBlock->leftNodeName[1], mDataBlock->rightNodeName[1] ); + radius[2] = findRadius( mDataBlock->leftNodeName[2], mDataBlock->rightNodeName[2] ); + radius[3] = findRadius( mDataBlock->leftNodeName[3], mDataBlock->rightNodeName[3] ); + + renderWall( radius[1], radius[0], 180.0, mDataBlock->leftNodeName[1], mDataBlock->leftNodeName[0] ); + renderWall( radius[1], radius[0], 180.0, mDataBlock->rightNodeName[1], mDataBlock->rightNodeName[0] ); + + renderWall( radius[3], radius[2], 180.0, mDataBlock->leftNodeName[3], mDataBlock->leftNodeName[2] ); + renderWall( radius[3], radius[2], 180.0, mDataBlock->rightNodeName[3], mDataBlock->rightNodeName[2] ); + + renderGlow(); + renderHemisphere( mDataBlock->spherePhiSegments, mDataBlock->sphereThetaSegments, mDataBlock->sphereRadius ); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); + +} + +//-------------------------------------------------------------------------- +// Get node transform +//-------------------------------------------------------------------------- +MatrixF StationFXVehicle::getNodeTransform( StringTableEntry nodeName ) +{ + TSShapeInstance *shapeInstance = mStationObject->getShapeInstance(); + TSShape *shape = shapeInstance->getShape(); + + S32 node = shape->findNode( nodeName ); + if( node < 0 ) + { + return MatrixF(true); + } + + return shapeInstance->mNodeTransforms[node]; +} + +//-------------------------------------------------------------------------- +// Find radius +//-------------------------------------------------------------------------- +F32 StationFXVehicle::findRadius( StringTableEntry node1, StringTableEntry node2 ) +{ + if( !mStationObject ) return 1.0; + + TSShapeInstance *shapeInstance = mStationObject->getShapeInstance(); + if( !shapeInstance ) return 1.0; + TSShape *shape = shapeInstance->getShape(); + if( !shape ) return 1.0; + + S32 node = shape->findNode( node1 ); + if( node < 0 ) return 1.0; + Point3F nodePos1 = shapeInstance->mNodeTransforms[node].getPosition(); + + node = shape->findNode( node2 ); + if( node < 0 ) return 1.0; + Point3F nodePos2 = shapeInstance->mNodeTransforms[node].getPosition(); + + VectorF diff = nodePos1 - nodePos2; + return ( diff.magnitudeSafe() * 0.5 ); +} + +//-------------------------------------------------------------------------- +// Render hemisphere +//-------------------------------------------------------------------------- +void StationFXVehicle::renderHemisphere( F32 numPhiSegments, F32 numThetaSegments, F32 radius ) +{ + if( !mStationObject ) return; + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + MatrixF sphereTrans = getNodeTransform( mDataBlock->glowNodeName ); + + MatrixF stationTrans; + stationTrans.mul( mStationObject->getTransform(), sphereTrans ); + dglMultMatrix( &stationTrans ); + + glScalef( mDataBlock->sphereScale.x, mDataBlock->sphereScale.y, mDataBlock->sphereScale.z ); + + glColor4f( 0.1, 0.1, 0.5, 1.0 ); + + + F32 armTime = mElapsedTime - mDataBlock->armLightDelay; + + if( armTime < mDataBlock->armLightFadeTime ) + { + F32 percent = armTime / mDataBlock->armLightFadeTime; + glColor4f( 0.1, 0.1, 0.5, percent ); + } + + if( (mDataBlock->armLightLifetime - armTime) < mDataBlock->armLightFadeTime ) + { + F32 timeLeft = mDataBlock->armLightLifetime - armTime; + F32 percent = timeLeft / mDataBlock->armLightFadeTime; + glColor4f( 0.1, 0.1, 0.5, percent ); + } + + + glEnable( GL_CULL_FACE ); + glDepthMask( GL_FALSE ); + glDisable( GL_TEXTURE_2D ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + + for( U32 i=1; iarmLightDelay ) return; + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + TSShapeInstance *shapeInstance = mStationObject->getShapeInstance(); + shapeInstance->animate(); + TSShape *shape = shapeInstance->getShape(); + + Point3F topNodePoint = getNodeTransform( topNode ).getPosition(); + Point3F bottomNodePoint = getNodeTransform( bottomNode ).getPosition(); + + MatrixF wallTrans = getNodeTransform( mDataBlock->glowNodeName ); + Point3F centerPos = wallTrans.getPosition(); + + Point3F dir = topNodePoint - centerPos; + dir.z = 0.0; + dir.normalize(); + MatrixF wallOrient = MathUtils::createOrientFromDir( dir ); + + centerPos.z = bottomNodePoint.z; + wallTrans.setPosition( centerPos ); + wallTrans.mul( wallOrient ); + + MatrixF stationTrans; + stationTrans.mul( mStationObject->getTransform(), wallTrans ); + dglMultMatrix( &stationTrans ); + + + glColor4f( 1.0, 1.0, 1.0, 1.0 ); + + F32 armTime = mElapsedTime - mDataBlock->armLightDelay; + + if( armTime < mDataBlock->armLightFadeTime ) + { + F32 percent = armTime / mDataBlock->armLightFadeTime; + glColor4f( 1.0, 1.0, 1.0, percent ); + numDegrees *= percent; + } + + if( (mDataBlock->armLightLifetime - armTime) < mDataBlock->armLightFadeTime ) + { + F32 timeLeft = mDataBlock->armLightLifetime - armTime; + F32 percent = timeLeft / mDataBlock->armLightFadeTime; + glColor4f( 1.0, 1.0, 1.0, percent ); + numDegrees *= percent; + } + + + glDepthMask( GL_FALSE ); + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[1].getGLName() ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + + + F32 height = topNodePoint.z - bottomNodePoint.z; + + glBegin( GL_TRIANGLE_STRIP ); + + for( U32 i=0; inumArcSegments; i++ ) + { + F32 percent = F32(i) / F32(mDataBlock->numArcSegments-1); + F32 angle = percent * mDegToRad( numDegrees ); + + if( percent == 1.0 ) + { + percent = 0.97; + } + + VectorF topPoint( mSin( angle ) * topRadius, mCos( angle ) * topRadius, height ); + glTexCoord2f( percent, 0.025 ); + glVertex3fv( topPoint ); + + VectorF bottomPoint( mSin( angle ) * bottomRadius, mCos( angle ) * bottomRadius, 0.0 ); + glTexCoord2f( percent, 1.0 ); + glVertex3fv( bottomPoint ); + + } + + glEnd(); + + glDisable( GL_BLEND ); + glDisable( GL_TEXTURE_2D ); + glDepthMask( GL_TRUE ); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + +} + +//-------------------------------------------------------------------------- +// Render glow on bottom of station +//-------------------------------------------------------------------------- +void StationFXVehicle::renderGlow() +{ + if( !mStationObject ) return; + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + MatrixF stationTrans; + TSShapeInstance *shapeInstance = mStationObject->getShapeInstance(); + + TSShape *shape = shapeInstance->getShape(); + + S32 node = shape->findNode( mDataBlock->glowNodeName ); + + MatrixF nodeTrans = shapeInstance->mNodeTransforms[node]; + + stationTrans.mul( mStationObject->getTransform(), nodeTrans ); + dglMultMatrix( &stationTrans ); + + glColor4f( 1.0, 1.0, 1.0, 1.0 ); + + if( mElapsedTime < mDataBlock->glowFadeTime ) + { + glColor4f( 1.0, 1.0, 1.0, mElapsedTime / mDataBlock->glowFadeTime ); + } + if( (mDataBlock->lifetime - mElapsedTime) < mDataBlock->glowFadeTime ) + { + F32 timeLeft = mDataBlock->lifetime - mElapsedTime; + glColor4f( 1.0, 1.0, 1.0, timeLeft / mDataBlock->glowFadeTime ); + } + + glDepthMask( GL_FALSE ); + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, mDataBlock->textureHandle[0].getGLName() ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + + glBegin( GL_TRIANGLE_STRIP ); + + for( U32 i=0; inumGlowSegments; i++ ) + { + F32 percent = F32(i) / F32(mDataBlock->numGlowSegments-1); + F32 angle = percent * mDegToRad( 360.0 ); + + F32 radius = mDataBlock->glowTopRadius; + VectorF topPoint( mSin( angle ) * radius, mCos( angle ) * radius, mDataBlock->glowTopHeight ); + glTexCoord2f( percent, 0.05 ); + glVertex3fv( topPoint ); + + radius = mDataBlock->glowBottomRadius; + VectorF bottomPoint( mSin( angle ) * radius, mCos( angle ) * radius, mDataBlock->glowBottomHeight ); + glTexCoord2f( percent, 1.0 ); + glVertex3fv( bottomPoint ); + + } + + glEnd(); + + glDisable( GL_BLEND ); + glDisable( GL_TEXTURE_2D ); + glDepthMask( GL_TRUE ); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +//-------------------------------------------------------------------------- +// Pack update +//-------------------------------------------------------------------------- +U32 StationFXVehicle::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & GameBase::InitialUpdateMask)) + { + if( bool(mStationObject) ) + { + S32 ghostIndex = con->getGhostIndex(mStationObject); + if (stream->writeFlag(ghostIndex != -1)) { + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount); + } + } + else + { + stream->writeFlag(false); + } + + } + + return retMask; +} + +//-------------------------------------------------------------------------- +// Unpack update +//-------------------------------------------------------------------------- +void StationFXVehicle::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + // initial update + if( stream->readFlag() ) + { + if (stream->readFlag()) + { + mStationObjectID = stream->readRangedU32(0, NetConnection::MaxGhostCount); + + NetObject* pObject = con->resolveGhost(mStationObjectID); + if (pObject != NULL) + { + mStationObject = dynamic_cast(pObject); + } + } + } + +} diff --git a/game/stationFXVehicle.h b/game/stationFXVehicle.h new file mode 100644 index 0000000..91ac5de --- /dev/null +++ b/game/stationFXVehicle.h @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_STATIONFXVEHICLE +#define _H_STATIONFXVEHICLE + +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +class ShapeBase; + + +//-------------------------------------------------------------------------- +// StationFXVehicleData +//-------------------------------------------------------------------------- +class StationFXVehicleData : public GameBaseData +{ + public: + + enum StationFXConsts + { + SFXC_NUM_TEX = 2, + SFXC_NUM_NODES = 4, + }; + + + typedef GameBaseData Parent; + + F32 glowTopHeight; + F32 glowBottomHeight; + F32 glowTopRadius; + F32 glowBottomRadius; + U32 numGlowSegments; + F32 glowFadeTime; + + F32 armLightDelay; + F32 armLightLifetime; + F32 armLightFadeTime; + + F32 lifetime; + U32 numArcSegments; + + ColorF sphereColor; + U32 spherePhiSegments; + U32 sphereThetaSegments; + F32 sphereRadius; + VectorF sphereScale; + + StringTableEntry glowNodeName; + StringTableEntry leftNodeName[SFXC_NUM_NODES]; + StringTableEntry rightNodeName[SFXC_NUM_NODES]; + + StringTableEntry textureName[SFXC_NUM_TEX]; + TextureHandle textureHandle[SFXC_NUM_TEX]; + + + + StationFXVehicleData(); + DECLARE_CONOBJECT(StationFXVehicleData); + bool onAdd(); + bool preload(bool server, char errorBuffer[256]); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + + +//-------------------------------------------------------------------------- +// StationFXVehicle +//-------------------------------------------------------------------------- +class StationFXVehicle : public GameBase +{ + typedef GameBase Parent; + + private: + StationFXVehicleData * mDataBlock; + S32 mCurrMS; + S32 mStationObjectID; + SimObjectPtr mStationObject; + F32 mElapsedTime; + + void renderGlow(); + void renderWall( F32 topRadius, F32 bottomRadius, F32 numDegrees, + StringTableEntry topNode, StringTableEntry bottomNode ); + + void renderHemisphere( F32 numPhiSegments, F32 numThetaSegments, F32 radius ); + + F32 findRadius( StringTableEntry node1, StringTableEntry node2 ); + MatrixF getNodeTransform( StringTableEntry nodeName ); + + protected: + bool onAdd(); + void onRemove(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); + + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + void advanceTime(F32); + bool onNewDataBlock(GameBaseData* dptr); + void processTick(const Move*); + + public: + StationFXVehicle(); + + DECLARE_CONOBJECT(StationFXVehicle); + static void initPersistFields(); +}; + +#endif // _H_STATIONFXVEHICLE + diff --git a/game/targetManager.cc b/game/targetManager.cc new file mode 100644 index 0000000..f40fa68 --- /dev/null +++ b/game/targetManager.cc @@ -0,0 +1,2877 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/targetManager.h" +#include "sim/netConnection.h" +#include "core/bitStream.h" +#include "game/gameConnection.h" +#include "game/sensor.h" +#include "game/shapeBase.h" +#include "console/consoleTypes.h" +#include "game/player.h" + +//------------------------------------------------------------------------------ +TargetManager * gTargetManager = NULL; +HUDTargetList * gTargetList = NULL; + +//------------------------------------------------------------------------------ +// Client target notification system: +//------------------------------------------------------------------------------ +TargetManagerNotify::TargetManagerNotify() +{ + AssertISV(gTargetManager, "TargetManagerNotify:: no target manager present!"); + gTargetManager->addNotify(this); +} + +TargetManagerNotify::~TargetManagerNotify() +{ + if(gTargetManager) + gTargetManager->removeNotify(this); +} + +void TargetManager::addNotify(TargetManagerNotify * notify) +{ + for(S32 i = notifyList.size() - 1; i >= 0; i--) + if(notifyList[i] == notify) + { + Con::errorf(ConsoleLogEntry::General, "TargetManager::addNotify: object already being notified!"); + return; + } + notifyList.push_back(notify); +} + +void TargetManager::removeNotify(TargetManagerNotify * notify) +{ + for(S32 i = notifyList.size() - 1; i >= 0; i--) + if(notifyList[i] == notify) + { + notifyList.erase(i); + return; + } + Con::errorf(ConsoleLogEntry::General, "TargetManager::removeNotify: object not found in notify list!"); +} + +void TargetManager::notifyTargetAdded(U32 target) +{ + for(S32 i = notifyList.size() - 1; i >= 0; i--) + notifyList[i]->targetAdded(target); +} + +void TargetManager::notifyTargetRemoved(U32 target) +{ + for(S32 i = notifyList.size() - 1; i >= 0; i--) + notifyList[i]->targetRemoved(target); +} + +void TargetManager::notifyTargetChanged(U32 target) +{ + for(S32 i = notifyList.size() - 1; i >= 0; i--) + notifyList[i]->targetChanged(target); +} + +void TargetManager::notifyTargetsCleared() +{ + for(S32 i = notifyList.size() - 1; i >= 0; i--) + notifyList[i]->targetsCleared(); +} + +//------------------------------------------------------------------------------ +// Client target free notification: +//------------------------------------------------------------------------------ +class TargetFreeEvent : public NetEvent +{ +public: + S32 mTarget; + TargetFreeEvent(S32 target = 0) + { + mTarget = target; + } + void pack(NetConnection *, BitStream *bstream) + { + bstream->writeInt(mTarget, TargetManager::TargetIdBitSize); + } + void write(NetConnection *conn, BitStream *bstream) + { + pack(conn, bstream); + } + void unpack(NetConnection *, BitStream *bstream) + { + mTarget = bstream->readInt(TargetManager::TargetIdBitSize); + } + void process(NetConnection *conn) + { + GameConnection *gc = (GameConnection *) conn; + TargetInfo *targ = gTargetManager->getClientTarget(mTarget); + + if(gTargetManager->mClientAudioHandles[mTarget] != NULL_AUDIOHANDLE) + { + alxStop(gTargetManager->mClientAudioHandles[mTarget]); + gTargetManager->mClientAudioHandles[mTarget] = 0; + } + + if(targ->allocated) + gTargetManager->notifyTargetRemoved(mTarget); + + if(bool(targ->targetObject)) + targ->targetObject->targetInfoChanged(0); + + targ->clear(false); + } + DECLARE_CONOBJECT(TargetFreeEvent); +}; +IMPLEMENT_CO_CLIENTEVENT_V1(TargetFreeEvent); + +//------------------------------------------------------------------------------ +// Client new/changed target info event: +//------------------------------------------------------------------------------ +class TargetInfoEvent : public NetEvent +{ +public: + S32 mTarget; + S32 mNameTag; + S32 mSkinTag; + S32 mVoiceTag; + S32 mTypeTag; + S32 mSkinPrefTag; + S32 mSensorGroup; + S32 mDataBlockId; + S32 mRenderFlags; + F32 mVoicePitch; + + TargetInfoEvent(S32 target = 0, S32 nameTag = 0, S32 skinTag = 0, S32 voiceTag = 0, + S32 typeTag = 0, S32 sensorGroup = 0, S32 dataBlockId = 0, S32 renderFlags = 0, F32 voicePitch = -1.0f, S32 prefSkin = -1) + { + mTarget = target; + mNameTag = nameTag; + mSkinTag = skinTag; + mVoiceTag = voiceTag; + mTypeTag = typeTag; + mSensorGroup = sensorGroup; + mDataBlockId = dataBlockId; + mSkinPrefTag = prefSkin; + mRenderFlags = renderFlags; + mVoicePitch = voicePitch; + } + + void pack(NetConnection *, BitStream *bstream) + { + bstream->writeInt(mTarget, TargetManager::TargetIdBitSize); + if(bstream->writeFlag(mNameTag != -1)) + if(bstream->writeFlag(mNameTag)) + bstream->writeInt(mNameTag, NetStringTable::StringIdBitSize); + if(bstream->writeFlag(mSkinTag != -1)) + if(bstream->writeFlag(mSkinTag)) + bstream->writeInt(mSkinTag, NetStringTable::StringIdBitSize); + if(bstream->writeFlag(mSkinPrefTag != -1)) + if(bstream->writeFlag(mSkinPrefTag)) + bstream->writeInt(mSkinPrefTag, NetStringTable::StringIdBitSize); + if(bstream->writeFlag(mVoiceTag != -1)) + if(bstream->writeFlag(mVoiceTag)) + bstream->writeInt(mVoiceTag, NetStringTable::StringIdBitSize); + if(bstream->writeFlag(mTypeTag != -1)) + if(bstream->writeFlag(mTypeTag)) + bstream->writeInt(mTypeTag, NetStringTable::StringIdBitSize); + if(bstream->writeFlag(mSensorGroup != -1)) + bstream->writeInt(mSensorGroup, 5); + if(bstream->writeFlag(mDataBlockId != -1)) + if(bstream->writeFlag(mDataBlockId)) + bstream->writeRangedU32(mDataBlockId, DataBlockObjectIdFirst, DataBlockObjectIdLast); + if(bstream->writeFlag(mRenderFlags != -1)) + bstream->writeInt(mRenderFlags, TargetInfo::NumRenderBits); + if(bstream->writeFlag(mVoicePitch != -1)) + { + //convert the voice pitch from range [0.5, 2.0] to [0.0, 1.0] + if (mVoicePitch < 0.5f || mVoicePitch > 2.0f) + mVoicePitch = 1.0f; + F32 packFloat = (mVoicePitch - 0.5f) / 1.5f; + bstream->writeFloat(packFloat, 7); + } + } + + void write(NetConnection *conn, BitStream *bstream) + { + pack(conn, bstream); + } + + void unpack(NetConnection *, BitStream *bstream) + { + mTarget = bstream->readInt(TargetManager::TargetIdBitSize); + mNameTag = mSkinTag = mVoiceTag = mTypeTag = mSensorGroup = mDataBlockId = mRenderFlags = -1; + mSkinPrefTag = -1; + mVoicePitch = -1.0f; + if(bstream->readFlag()) + { + if(bstream->readFlag()) + mNameTag = bstream->readInt(NetStringTable::StringIdBitSize); + else + mNameTag = 0; + } + if(bstream->readFlag()) + { + if(bstream->readFlag()) + mSkinTag = bstream->readInt(NetStringTable::StringIdBitSize); + else + mSkinTag = 0; + } + if(bstream->readFlag()) + { + if(bstream->readFlag()) + mSkinPrefTag = bstream->readInt(NetStringTable::StringIdBitSize); + else + mSkinPrefTag = 0; + } + if(bstream->readFlag()) + { + if(bstream->readFlag()) + mVoiceTag = bstream->readInt(NetStringTable::StringIdBitSize); + else + mVoiceTag = 0; + } + if(bstream->readFlag()) + { + if(bstream->readFlag()) + mTypeTag = bstream->readInt(NetStringTable::StringIdBitSize); + else + mTypeTag = 0; + } + if(bstream->readFlag()) + mSensorGroup = bstream->readInt(5); + + if(bstream->readFlag()) + if(bstream->readFlag()) + mDataBlockId = bstream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if(bstream->readFlag()) + mRenderFlags = bstream->readInt(TargetInfo::NumRenderBits); + + if(bstream->readFlag()) + { + //convert the voice pitch from range [0.0, 1.0] back to [0.5, 2.0] + F32 unpackFloat = bstream->readFloat(7); + mVoicePitch = (unpackFloat * 1.5f) + 0.5f; + } + } + + void process(NetConnection *conn) + { + GameConnection *gc = (GameConnection *) conn; + + // translate all the strings + TargetInfo *targ = gTargetManager->getClientTarget(mTarget); + if(mNameTag != -1) + { + targ->sNameTag = mNameTag; + targ->nameTag = conn->translateRemoteStringId(mNameTag); + } + if(mSkinTag != -1) + { + targ->sSkinTag = mSkinTag; + targ->skinTag = conn->translateRemoteStringId(mSkinTag); + } + if(mSkinPrefTag != -1) + { + targ->sSkinPrefTag = mSkinPrefTag; + targ->skinPrefTag = conn->translateRemoteStringId(mSkinPrefTag); + } + if(mVoiceTag != -1) + { + targ->sVoiceTag = mVoiceTag; + targ->voiceTag = conn->translateRemoteStringId(mVoiceTag); + } + if(mTypeTag != -1) + { + targ->sTypeTag = mTypeTag; + targ->typeTag = conn->translateRemoteStringId(mTypeTag); + } + + U32 oldGroup = targ->sensorGroup; + if(mSensorGroup != -1) + targ->sensorGroup = mSensorGroup; + + if(mRenderFlags != -1) + targ->renderFlags = mRenderFlags; + + // team targets do not get notifications or objects + if(mTarget >= 32) + { + if(mDataBlockId >= 0) + targ->shapeBaseData = mDataBlockId ? dynamic_cast(Sim::findObject(mDataBlockId)) : 0; + + if(targ->allocated) + { + gTargetManager->notifyTargetChanged(mTarget); + } + else + { + // created a new client target + targ->allocated = true; + gTargetManager->notifyTargetAdded(mTarget); + } + + if(bool(targ->targetObject)) + targ->targetObject->targetInfoChanged(targ); + } + + if(mVoicePitch != -1.0f) + targ->voicePitch = mVoicePitch; + } + DECLARE_CONOBJECT(TargetInfoEvent); +}; + +IMPLEMENT_CO_CLIENTEVENT_V1(TargetInfoEvent); + +//------------------------------------------------------------------------------ +// Targets get one audio handle associated with them +//------------------------------------------------------------------------------ +static F32 SoundPosAccuracy = 0.5; + +class SimTargetAudioEvent : public NetEvent +{ + private: + S32 mTargetId; + S32 mFileTag; + S32 mDescriptionId; + + bool mHasPosition; + Point3F mPosition; + + bool mUpdateSound; + + public: + SimTargetAudioEvent(S32 targetId = -1, S32 fileTag = 0, S32 descId = 0, Point3F * pos = 0, bool update = false); + void pack(NetConnection * con, BitStream * bstream); + void write(NetConnection * con, BitStream * bstream); + void unpack(NetConnection * con, BitStream * bstream); + void process(NetConnection *); + DECLARE_CONOBJECT(SimTargetAudioEvent); +}; +IMPLEMENT_CO_CLIENTEVENT_V1(SimTargetAudioEvent); + +SimTargetAudioEvent::SimTargetAudioEvent(S32 targetId, S32 fileTag, S32 descId, Point3F * pos, bool update) +{ + mTargetId = targetId; + mFileTag = fileTag; + mDescriptionId = descId; + mUpdateSound = update; + + if(pos) + { + mHasPosition = true; + mPosition = *pos; + } + else + mHasPosition = false; +} + +void SimTargetAudioEvent::pack(NetConnection * con, BitStream * bstream) +{ + bstream->writeInt(mTargetId, TargetManager::TargetIdBitSize); + bstream->writeInt(mFileTag, NetStringTable::StringIdBitSize); + bstream->writeRangedU32(mDescriptionId, DataBlockObjectIdFirst, DataBlockObjectIdLast); + if(bstream->writeFlag(mHasPosition)) + con->writeCompressed(bstream, mPosition, SoundPosAccuracy); + + bstream->writeFlag(mUpdateSound); +} + +void SimTargetAudioEvent::write(NetConnection * con, BitStream * bstream) +{ + pack(con, bstream); +} + +void SimTargetAudioEvent::unpack(NetConnection * con, BitStream * bstream) +{ + mTargetId = bstream->readInt(TargetManager::TargetIdBitSize); + mFileTag = bstream->readInt(NetStringTable::StringIdBitSize); + mDescriptionId = bstream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + mHasPosition = bstream->readFlag(); + if(mHasPosition) + con->readCompressed(bstream, &mPosition, SoundPosAccuracy); + + mUpdateSound = bstream->readFlag(); +} + +void SimTargetAudioEvent::process(NetConnection * con) +{ + TargetInfo * targInfo = gTargetManager->getClientTarget(mTargetId); + if(!targInfo) + return; + + AudioDescription * description = dynamic_cast(Sim::findObject(mDescriptionId)); + if(!description) + return; + + if(!mHasPosition) + { + if(!bool(targInfo->targetObject)) + return; + targInfo->targetObject->getTransform().getColumn(3, &mPosition); + } + + mFileTag = con->translateRemoteStringId(mFileTag); + + if(!mFileTag || !targInfo->voiceTag) + return; + + const char * name = gNetStringTable->lookupString(mFileTag); + const char * voice = gNetStringTable->lookupString(targInfo->voiceTag); + + if(!name || !voice || !name[0] || !voice[0]) + return; + + char buf[256]; + dSprintf(buf, sizeof(buf), "voice/%s/%s.wav", voice, name); + + MatrixF mat(true); + mat.setColumn(3, mPosition); + + if( mUpdateSound ) + { + AUDIOHANDLE & handle = gTargetManager->mClientAudioHandles[mTargetId]; + if(handle != NULL_AUDIOHANDLE) + alxStop(handle); + handle = alxCreateSource(description, buf, &mat); + + if (targInfo->voicePitch != 1.0f) + alxSourcef(handle, AL_PITCH, targInfo->voicePitch); + alxPlay(handle); + } + else + { + AUDIOHANDLE handle = alxCreateSource(description, buf, &mat); + if (targInfo->voicePitch != 1.0) + alxSourcef(handle, AL_PITCH, targInfo->voicePitch); + alxPlay(handle); + } +} + +bool TargetManager::playTargetAudio(S32 targetId, S32 fileTag, AudioDescription * description, bool update) +{ + // check args: must have target, file, description + if((targetId < 0) || (targetId >= MaxTargets)) + return(false); + + if(fileTag <= 0) + return(false); + + if(!description) + return(false); + + // get position of target + TargetInfo * targInfo = getServerTarget(targetId); + if(!targInfo) + return(false); + + if(!bool(targInfo->targetObject)) + return(false); + + Point3F pos; + targInfo->targetObject->getTransform().getColumn(3, &pos); + + // send to whomever can hear... + for(NetConnection * con = NetConnection::getConnectionList(); con; con = con->getNext()) + { + if(con->isServerConnection()) + continue; + + if (dynamic_cast(con) && static_cast(con)->isAIControlled()) + continue; + + ShapeBase * controlObj = static_cast(con)->getControlObject(); + if(!controlObj) + continue; + + Point3F ear; + controlObj->getTransform().getColumn(3, &ear); + if((ear - pos).magnitudeSafe() >= description->mDescription.mMaxDistance) + continue; + + // send position if not scoped on client + bool sendPos = con->getGhostIndex(targInfo->targetObject) == -1; + + // send it + con->checkString(fileTag); + con->postNetEvent(new SimTargetAudioEvent(targetId, fileTag, description->getId(), sendPos ? &pos : 0, update)); + } + + return(true); +} + +//-------------------------------------------------------------------------- +TargetManager::TargetManager() +{ + VECTOR_SET_ASSOCIATION(notifyList); + clear(); +} + +void TargetManager::clear() +{ + mFreeCount = MaxTargets; + mLastSensedObject = 0; + mSensorGroupCount = 0; + U32 i; + for(i = 0; i < TargetFreeMaskSize; i++) + mFreeMask[i] = 0; + for(i = 0; i < MaxTargets; i++) + { + mTargets[i].clear(); + mClientTargets[i].clear(); + mClientAudioHandles[i] = NULL_AUDIOHANDLE; + } + + // reserve team targets + mFreeCount -= 32; + mFreeMask[0] = 0xffffffff; + + // setup the sensorgroups and sensorgroup masks + for(i = 0; i < 32; i++) + { + mSensorInfoArray[i].clear(); + + // team targets + mTargets[i].sensorGroup = mClientTargets[i].sensorGroup = i; + + // default teams to be friendly, visible, and can listen to themselves + mSensorGroupListenMask[i] = mSensorGroupAlwaysVisMask[i] = mSensorGroupFriendlyMask[i] = (1 << i); + mSensorGroupNeverVisMask[i] = 0; + } +} + +//------------------------------------------------------------------------- +class SensorGroupColorEvent : public NetEvent +{ + public: + + U32 mUpdateMask; + U32 mSensorGroup; + ColorI mColors[32]; + + SensorGroupColorEvent(U32 sensorGroup = 0, U32 updateMask = 0) + { + AssertFatal(sensorGroup < 32, "SensorGroupColorEvent:: invalid sensor group"); + + mSensorGroup = sensorGroup; + mUpdateMask = updateMask; + dMemcpy(&mColors, &gTargetManager->mSensorInfoArray[mSensorGroup].groupColor, 32 * sizeof(ColorI)); + } + + void pack(NetConnection *, BitStream *bstream) + { + bstream->writeInt(mSensorGroup, 5); + bstream->write(mUpdateMask); + + for(U32 i = 0; i < 32; i++) + if((1<writeFlag(mColors[i] != TargetManager::SensorInfo::smDefaultColor)) + { + bstream->write(mColors[i].red); + bstream->write(mColors[i].green); + bstream->write(mColors[i].blue); + bstream->write(mColors[i].alpha); + } + } + + void write(NetConnection *conn, BitStream *bstream) + { + pack(conn, bstream); + } + + void unpack(NetConnection *, BitStream *bstream) + { + mSensorGroup = bstream->readInt(5); + bstream->read(&mUpdateMask); + + for(U32 i = 0; i < 32; i++) + if((1<readFlag()) + { + bstream->read(&mColors[i].red); + bstream->read(&mColors[i].green); + bstream->read(&mColors[i].blue); + bstream->read(&mColors[i].alpha); + } + else + mColors[i] = TargetManager::SensorInfo::smDefaultColor; + } + } + + void process(NetConnection *) + { + TargetManager::SensorInfo * sensorInfo = &gTargetManager->mSensorInfoArray[mSensorGroup]; + for(U32 i = 0; i < 32; i++) + if((1<groupColor[i] = mColors[i]; + } + + DECLARE_CONOBJECT(SensorGroupColorEvent); +}; + +IMPLEMENT_CO_CLIENTEVENT_V1(SensorGroupColorEvent); + +void TargetManager::clientSensorGroupChanged(NetConnection * client, U32 newGroup) +{ + if(dynamic_cast(client) && static_cast(client)->isAIControlled()) + return; + client->postNetEvent(new SensorGroupColorEvent(newGroup, 0xffffffff)); +} + +ColorI TargetManager::getSensorGroupColor(U32 sensorGroup, U32 colorGroup) +{ + AssertFatal((sensorGroup < 32) && (colorGroup < 32), "TagetManager:: invalidSensorGroup"); + return(mSensorInfoArray[sensorGroup].groupColor[colorGroup]); +} + +void TargetManager::setSensorGroupColor(U32 sensorGroup, U32 updateMask, ColorI & color) +{ + AssertFatal((sensorGroup >= 0) && (sensorGroup < 32), "TargetManager:: invalid sensor group"); + SensorInfo * sensorInfo = &mSensorInfoArray[sensorGroup]; + + for(U32 i = 0; i < 32; i++) + if((1 << i) & updateMask) + sensorInfo->groupColor[i] = color; + + for(NetConnection * con = NetConnection::getConnectionList(); con; con = con->getNext()) + { + if(con->isServerConnection()) + continue; + + GameConnection * gc = dynamic_cast(con); + if(!gc) + continue; + + if(gc->isAIControlled() || (gc->getSensorGroup() != sensorGroup)) + continue; + + gc->postNetEvent(new SensorGroupColorEvent(sensorGroup, updateMask)); + } +} + +//----------------------------------------------------------------------------- +// an '_' as the first char for the string excludes it +bool TargetManager::getGameName(S32 target, char * buf, S32 bufSize, bool server) +{ + if(target < 0 || target >= MaxTargets) + return(false); + + TargetInfo * targInfo = server ? gTargetManager->getServerTarget(target) : + gTargetManager->getClientTarget(target); + + if(!targInfo->allocated) + return(false); + + const char * name = gNetStringTable->lookupString(targInfo->nameTag); + const char * type = gNetStringTable->lookupString(targInfo->typeTag); + + // game name = 'name type' + if(name && name[0] && (name[0] != '_')) + { + if(type && type[0] && (type[0] != '_')) + dSprintf(buf, bufSize, "%s %s", name, type); + else + dSprintf(buf, bufSize, "%s", name); + } + else if(type && type[0] && (type[0] != '_')) + dSprintf(buf, bufSize, "%s", type); + + return(true); +} + +//----------------------------------------------------------------------------- +void TargetManager::newClient(NetConnection *client) +{ + if(dynamic_cast(client) && static_cast(client)->isAIControlled()) + return; + + for(U32 i = 0; i < TargetFreeMaskSize; i++) + { + if(mFreeMask[i] == 0) + continue; + + for(U32 j = 0; j < 32; j++) + { + if(mFreeMask[i] & (1 << j)) + { + TargetInfo &targ = mTargets[(i << 5) + j]; + client->checkString(targ.nameTag); + client->checkString(targ.skinTag); + client->checkString(targ.voiceTag); + client->checkString(targ.typeTag); + + client->postNetEvent(new TargetInfoEvent( (i << 5) + j, targ.nameTag, + targ.skinTag, targ.voiceTag, targ.typeTag, targ.sensorGroup, + bool(targ.shapeBaseData) ? targ.shapeBaseData->getId() : 0, targ.renderFlags, targ.voicePitch)); + } + } + } +} + +S32 TargetManager::allocTarget(U32 nameTag, U32 skinTag, U32 voiceTag, U32 typeTag, U32 sensorGroup, U32 dataBlockId, F32 voicePitch, U32 prefSkin) +{ + AssertFatal(sensorGroup < 32, "TargetManager::allocTarget: invalid sensorGroup"); + + // find a free mask + if(mFreeCount == 0) + return -1; + S32 targ = -1; + for(U32 i = 0; i < TargetFreeMaskSize; i++) + { + if(mFreeMask[i] == 0xFFFFFFFF) + continue; + for(U32 j = 0; j < 32; j++) + { + if(!( mFreeMask[i] & (1 << j) )) + { + targ = (i << 5) + j; + mFreeMask[i] |= (1 << j); + goto done; + } + } + } +done: + AssertFatal(targ != -1, "Invalid target index."); + mFreeCount--; + + mTargets[targ].allocated = true; + mTargets[targ].nameTag = nameTag; + mTargets[targ].skinTag = skinTag; + mTargets[targ].voiceTag = voiceTag; + mTargets[targ].typeTag = typeTag; + mTargets[targ].skinPrefTag = prefSkin; + mTargets[targ].sensorGroup = sensorGroup; + mTargets[targ].renderFlags = 0; + mTargets[targ].voicePitch = voicePitch; + + // vis/friend masks default to team values + mTargets[targ].sensorAlwaysVisMask = mSensorGroupAlwaysVisMask[sensorGroup]; + mTargets[targ].sensorNeverVisMask = mSensorGroupNeverVisMask[sensorGroup]; + mTargets[targ].sensorFriendlyMask = mSensorGroupFriendlyMask[sensorGroup]; + + mTargets[targ].sensorFlags = 0; + mTargets[targ].sensorVisMask = 0; + + mTargets[targ].shapeBaseData = dataBlockId ? dynamic_cast(Sim::findObject(dataBlockId)) : 0; + + updateTarget(targ, nameTag, skinTag, voiceTag, typeTag, sensorGroup, dataBlockId, 0, voicePitch, prefSkin); + return targ; +} + +void TargetManager::updateTarget(S32 targ, S32 nameTag, S32 skinTag, S32 voiceTag, S32 typeTag, S32 sensorGroup, S32 dataBlockId, S32 renderFlags, F32 voicePitch, U32 prefSkin) +{ + for(NetConnection *conn = NetConnection::getConnectionList(); conn; conn = conn->getNext()) + { + if(conn->isServerConnection()) + continue; + + if (dynamic_cast(conn) && static_cast(conn)->isAIControlled()) + continue; + + if(nameTag != -1) + conn->checkString(nameTag); + if(skinTag != -1) + conn->checkString(skinTag); + if(voiceTag != -1) + conn->checkString(voiceTag); + if(typeTag != -1) + conn->checkString(typeTag); + if(prefSkin != -1) + conn->checkString(prefSkin); + conn->postNetEvent(new TargetInfoEvent(targ, nameTag, skinTag, voiceTag, typeTag, sensorGroup, dataBlockId, renderFlags, voicePitch, prefSkin)); + } +} + +void TargetManager::freeTarget(S32 target) +{ + AssertFatal(target >= 32 && target < MaxTargets, "Invalid target id."); + + for(NetConnection *conn = NetConnection::getConnectionList(); conn; conn = conn->getNext()) + { + if(conn->isServerConnection()) + continue; + if (dynamic_cast(conn) && static_cast(conn)->isAIControlled()) + continue; + conn->postNetEvent(new TargetFreeEvent(target)); + } + + for(U32 i = 0; i < 32; i++) + mSensorInfoArray[i].setSensorVisible(target, false); + + // notify the target object + if(bool(mTargets[target].targetObject)) + mTargets[target].targetObject->targetInfoChanged(0); + + mTargets[target].clear(false); + + // update the free mask/count + U32 index = target >> 5; + U32 bit = target & 0x1F; + if(!(mFreeMask[index] & (1 << bit))) + return; + mFreeMask[index] &= ~(1 << bit); + mFreeCount++; +} + +//-------------------------------------------------------------------------- +// Class ResetClientTargetsEvent: HUDTargetList will be notified of clear +// - overloaded for HUDTargetList use as well +// - needs to reset the connections visible target list on pack because +// the connection gets packed (and can change the visibility of items) before +// the event goes through +//-------------------------------------------------------------------------- +class ResetClientTargetsEvent : public NetEvent +{ + private: + bool mClientTargetsOnly; + + public: + ResetClientTargetsEvent(bool clientTargetsOnly = false) { mClientTargetsOnly = clientTargetsOnly; } + void pack(NetConnection *con , BitStream * bstream) + { + bstream->writeFlag(mClientTargetsOnly); + if(!mClientTargetsOnly && dynamic_cast(con)) + static_cast(con)->resetVisibleMasks(); + } + void write(NetConnection * con, BitStream * bstream) { pack(con, bstream); } + void unpack(NetConnection *, BitStream * bstream) { mClientTargetsOnly = bstream->readFlag(); } + void process(NetConnection * con) { mClientTargetsOnly ? gTargetList->targetsCleared() : gTargetManager->resetClient(con); } + + DECLARE_CONOBJECT(ResetClientTargetsEvent); +}; +IMPLEMENT_CO_CLIENTEVENT_V1(ResetClientTargetsEvent); + +void TargetManager::resetClient(NetConnection * con) +{ + // client? + if(con->isServerConnection()) + { + // clear all the target infos + for(U32 i = 0; i < MaxTargets; i++) + { + // clear objects + if(bool(mClientTargets[i].targetObject)) + mClientTargets[i].targetObject->targetInfoChanged(0); + + // clear audio + if(mClientAudioHandles[i] != NULL_AUDIOHANDLE) + alxStop(mClientAudioHandles[i]); + mClientAudioHandles[i] = NULL_AUDIOHANDLE; + + mClientTargets[i].clear(); + } + + // notify objects that targets have been cleared + notifyTargetsCleared(); + } + else + con->postNetEvent( new ResetClientTargetsEvent(false) ); +} + +void TargetManager::reset() +{ + // notify the clients to clear their targets + for(NetConnection * con = NetConnection::getConnectionList(); con; con = con->getNext()) + { + if(con->isServerConnection()) + continue; + + if (dynamic_cast(con) && static_cast(con)->isAIControlled()) + continue; + + resetClient(con); + } + // clear objects + for(U32 i = 0; i < MaxTargets; i++) + if(bool(mTargets[i].targetObject)) + mTargets[i].targetObject->targetInfoChanged(0); + + // reset all the targetmanager info + clear(); +} + +//-------------------------------------------------------------------------- +TargetInfo *TargetManager::getClientTarget(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return mClientTargets + target; +} + +TargetInfo *TargetManager::getServerTarget(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return mTargets + target; +} + +// TargetObject: ------------------------------------------------------------ +// team targets can have multiple objects associtated with them (though, they +// will never have the target->obj set) +void TargetManager::setTargetObject(TargetInfo * target, GameBase *object) +{ + AssertFatal(target, "TargetManager::setTargetObject: invalid target object"); + + // don't notify current object that it's target has gone bye-bye + target->targetObject = object; +} + +// Name: -------------------------------------------------------------------- +S32 TargetManager::getTargetName(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return(mTargets[target].nameTag); +} + +void TargetManager::setTargetName(S32 target, U32 nameTag) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + if(mTargets[target].nameTag == nameTag) + return; + mTargets[target].nameTag = nameTag; + updateTarget(target, nameTag, -1, -1, -1, -1, -1, -1, -1, -1); +} + +// Skin: -------------------------------------------------------------------- +S32 TargetManager::getTargetSkin(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return(mTargets[target].skinTag); +} + +void TargetManager::setTargetSkin(S32 target, U32 skinTag) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + if(mTargets[target].skinTag == skinTag) + return; + mTargets[target].skinTag = skinTag; + updateTarget(target, -1, skinTag, -1, -1, -1, -1, -1, -1, -1); +} + +// Voice: -------------------------------------------------------------------- +S32 TargetManager::getTargetVoice(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return(mTargets[target].voiceTag); +} + +void TargetManager::setTargetVoice(S32 target, U32 voiceTag) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + if(mTargets[target].voiceTag == voiceTag) + return; + mTargets[target].voiceTag = voiceTag; + updateTarget(target, -1, -1, voiceTag, -1, -1, -1, -1, -1, -1); +} + +// Type: -------------------------------------------------------------------- +S32 TargetManager::getTargetType(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return(mTargets[target].typeTag); +} + +void TargetManager::setTargetType(S32 target, U32 typeTag) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + if(mTargets[target].typeTag == typeTag) + return; + mTargets[target].typeTag = typeTag; + updateTarget(target, -1, -1, -1, typeTag, -1, -1, -1, -1, -1); +} + +// SensorGroup: ------------------------------------------------------------- +S32 TargetManager::getTargetSensorGroup(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return(mTargets[target].sensorGroup); +} + +void TargetManager::setTargetSensorGroup(S32 target, U32 sensorGroup) +{ + AssertFatal(target >= 32 && target < MaxTargets, "Invalid target id."); + AssertFatal(sensorGroup < 32, "Invalid sensor group."); + if(mTargets[target].sensorGroup == sensorGroup) + return; + mTargets[target].sensorGroup = sensorGroup; + + mTargets[target].sensorAlwaysVisMask = mSensorGroupAlwaysVisMask[sensorGroup]; + mTargets[target].sensorNeverVisMask = mSensorGroupNeverVisMask[sensorGroup]; + mTargets[target].sensorFriendlyMask = mSensorGroupFriendlyMask[sensorGroup]; + updateTarget(target, -1, -1, -1, -1, sensorGroup, -1, -1, -1, -1); +} + +// voicePitch: -------------------------------------------------------------- +F32 TargetManager::getTargetVoicePitch(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return(mTargets[target].voicePitch); +} + +void TargetManager::setTargetVoicePitch(S32 target, F32 voicePitch) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + if(mTargets[target].voicePitch == voicePitch) + return; + if (voicePitch < 0.5f || voicePitch > 2.0f) + mTargets[target].voicePitch = 1.0f; + else + mTargets[target].voicePitch = voicePitch; + updateTarget(target, -1, -1, -1, -1, -1, -1, -1, voicePitch, -1); +} + +// Misc: -------------------------------------------------------------------- +void TargetManager::setTargetRenderMask(S32 target, U32 mask) +{ + AssertFatal(target >= 32 && target < MaxTargets, "Invalid target id."); + mTargets[target].renderFlags = mask & ((1 << TargetInfo::NumRenderBits) - 1); + updateTarget(target, -1, -1, -1, -1, -1, -1, mTargets[target].renderFlags, -1, -1); +} + +void TargetManager::setTargetShapeBaseData(S32 target, ShapeBaseData * data) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + mTargets[target].shapeBaseData = data; + updateTarget(target, -1, -1, -1, -1, -1, data ? data->getId() : 0, -1, -1, -1); +} + +// TargetAlwaysVisMask: ----------------------------------------------------- +U32 TargetManager::getTargetAlwaysVisMask(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return(mTargets[target].sensorAlwaysVisMask); +} + +void TargetManager::setTargetAlwaysVisMask(S32 target, U32 mask) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + mTargets[target].sensorAlwaysVisMask = mask; +} + +// TargetNeverVisMask: ------------------------------------------------------ +U32 TargetManager::getTargetNeverVisMask(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return(mTargets[target].sensorNeverVisMask); +} + +void TargetManager::setTargetNeverVisMask(S32 target, U32 mask) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + mTargets[target].sensorNeverVisMask = mask; +} + +// TargetFriendlyMask: ------------------------------------------------------ +U32 TargetManager::getTargetFriendlyMask(S32 target) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + return(mTargets[target].sensorFriendlyMask); +} + +void TargetManager::setTargetFriendlyMask(S32 target, U32 mask) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + mTargets[target].sensorFriendlyMask = mask; +} + +// all targets inherit group mask changes +void TargetManager::updateSensorGroupMask(U32 sensorGroup, U32 mask, U32 maskType) +{ + for(U32 i = 0; i < TargetFreeMaskSize; i++) + { + if(mFreeMask[i] == 0) + continue; + + for(U32 j = 0; j < 32; j++) + { + if(mFreeMask[i] & (1 << j)) + { + TargetInfo &targ = mTargets[(i << 5) + j]; + if(targ.sensorGroup == sensorGroup) + { + U32 * targMask = &targ.sensorAlwaysVisMask; + if(maskType == NeverVisMask) + targMask = &targ.sensorNeverVisMask; + else if(maskType == FriendlyMask) + targMask = &targ.sensorFriendlyMask; + *targMask = mask; + } + } + } + } +} + +// SensorGroupAlwaysVisMask: ------------------------------------------------ +U32 TargetManager::getSensorGroupAlwaysVisMask(U32 sensorGroup) +{ + AssertFatal(sensorGroup < 32, "TargetManager::getSensorGroupAlwaysVisMask: invalid sensor group"); + return(mSensorGroupAlwaysVisMask[sensorGroup]); +} + +void TargetManager::setSensorGroupAlwaysVisMask(U32 sensorGroup, U32 mask) +{ + AssertFatal(sensorGroup < 32, "TargetManager::setSensorGroupAlwaysVisMask: invalid sensor group"); + updateSensorGroupMask(sensorGroup, mask, AlwaysVisMask); + mSensorGroupAlwaysVisMask[sensorGroup] = mask; +} + +// SensorGroupNeverVisMask: ------------------------------------------------- +U32 TargetManager::getSensorGroupNeverVisMask(U32 sensorGroup) +{ + AssertFatal(sensorGroup < 32, "TargetManager::getSensorGroupNeverVisMask: invalid sensor group"); + return(mSensorGroupNeverVisMask[sensorGroup]); +} + +void TargetManager::setSensorGroupNeverVisMask(U32 sensorGroup, U32 mask) +{ + AssertFatal(sensorGroup < 32, "TargetManager::setSensorGroupNeverVisMask: invalid sensor group"); + updateSensorGroupMask(sensorGroup, mask, NeverVisMask); + mSensorGroupNeverVisMask[sensorGroup] = mask; +} + +// SensorGroupFriendlyMask: ------------------------------------------------- +U32 TargetManager::getSensorGroupFriendlyMask(U32 sensorGroup) +{ + AssertFatal(sensorGroup < 32, "TargetManager::getSensorGroupFriendlyMask: invalid sensor group"); + return(mSensorGroupFriendlyMask[sensorGroup]); +} + +void TargetManager::setSensorGroupFriendlyMask(U32 sensorGroup, U32 mask) +{ + AssertFatal(sensorGroup < 32, "TargetManager::setSensorGroupFriendlyMask: invalid sensor group"); + updateSensorGroupMask(sensorGroup, mask, FriendlyMask); + mSensorGroupFriendlyMask[sensorGroup] = mask; +} + +// Listen state: ------------------------------------------------------------ +U32 TargetManager::getSensorGroupListenMask(U32 sensorGroup) +{ + AssertFatal(sensorGroup < 32, "TargetManager::getSensorGroupListenMask: invalid sensor group"); + return(mSensorGroupListenMask[sensorGroup]); +} + +void TargetManager::setSensorGroupListenMask(U32 sensorGroup, U32 mask) +{ + AssertFatal(sensorGroup < 32, "TargetManager::setSensorGroupListenMask: invalid sensor group"); + mSensorGroupListenMask[sensorGroup] = mask; +} + +// IsTarget(...): ----------------------------------------------------------- +bool TargetManager::isTargetFriendly(S32 target, U32 sensorGroup) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + AssertFatal(sensorGroup < 32, "Invalid sensor group"); + return((mTargets[target].sensorFriendlyMask & (1 << sensorGroup)) != 0); +} + +bool TargetManager::isTargetVisible(S32 target, U32 sensorGroup) +{ + AssertFatal(target >= 0 && target < MaxTargets, "Invalid target id."); + AssertFatal(sensorGroup < 32, "Invalid sensor group"); + + U32 groupMask = (1 << sensorGroup); + + if(mTargets[target].sensorNeverVisMask & groupMask) + return(false); + + if(mTargets[target].sensorAlwaysVisMask & groupMask) + return(true); + + return((mTargets[target].sensorVisMask & groupMask) != 0); +} + +//--------------------------------------------------------------------------- +// TargetManager console access: +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Helpers... +static S32 getValidTarget(S32 target, const char * funcStr, bool excludeTeam) +{ + if((target < 0) || (target >= TargetManager::MaxTargets)) + { + Con::errorf(ConsoleLogEntry::General, "TargetManager::%s: invalid target index [%d]", funcStr, target); + return(-1); + } + + if(excludeTeam && target < 32) + { + Con::errorf(ConsoleLogEntry::General, "TargetManager::%s: cannot change attribute on team target [%d]", funcStr, target); + return(-1); + } + + TargetInfo * targ = gTargetManager->getServerTarget(target); + if(!targ->allocated && (target >= 32)) + { + Con::errorf(ConsoleLogEntry::General, "TargetManager::%s: cannot change attribute on unallocated target [%d]", funcStr, target); + return(-1); + } + + return(target); +} + +static S32 getValidSensorGroup(S32 group, const char * funcStr) +{ + if(group < 0 || group >= 32) + { + Con::errorf(ConsoleLogEntry::General, "TargetManager::%s: invalid sensor group [%d]", funcStr, group); + return(-1); + } + return(group); +} + +static F32 getValidVoicePitch(F32 pitch, const char * funcStr) +{ + if(pitch < 0.5f || pitch > 2.0f) + { + Con::errorf(ConsoleLogEntry::General, "TargetManager::%s: Invalid pitch [%d]", funcStr, pitch); + return (1.0f); + } + else + return(pitch); +} + +//--------------------------------------------------------------------------- +static void cReset(SimObject *, S32, const char **) +{ + gTargetManager->reset(); +} + +static void cResetClientTargets(SimObject *, S32, const char ** argv) +{ + NetConnection * client = dynamic_cast(Sim::findObject(dAtoi(argv[1]))); + if(client) + { + if(dynamic_cast(client) && static_cast(client)->isAIControlled()) + return; + + client->postNetEvent( new ResetClientTargetsEvent(dAtob(argv[2])) ); + } +} + +static void cSendTargetsToClient(SimObject *, S32, const char ** argv) +{ + NetConnection * client = dynamic_cast(Sim::findObject(dAtoi(argv[1]))); + if(client) + gTargetManager->newClient(client); +} + +static S32 cAllocTarget(SimObject *, S32 argc, const char **argv) +{ + U32 nameTag = 0; + U32 skinTag = 0; + U32 voiceTag = 0; + U32 typeTag = 0; + U32 prefSkinTag = 0; + + // check if tagprefix has already been removed + if(argv[1][0] == StringTagPrefixByte) + nameTag = dAtoi(argv[1] + 1); + else if(argv[1][0]) + nameTag = dAtoi(argv[1]); + if(argv[2][0] == StringTagPrefixByte) + skinTag = dAtoi(argv[2] + 1); + else if(argv[2][0]) + skinTag = dAtoi(argv[2]); + if(argv[3][0] == StringTagPrefixByte) + voiceTag = dAtoi(argv[3] + 1); + else if(argv[3][0]) + voiceTag = dAtoi(argv[3]); + if(argv[4][0] == StringTagPrefixByte) + typeTag = dAtoi(argv[4] + 1); + else if(argv[4][0]) + typeTag = dAtoi(argv[4]); + U32 sensorGroup = dAtoi(argv[5]); + U32 dataBlockId = dAtoi(argv[6]); + F32 voicePitch = dAtof(argv[7]); + if (voicePitch < 0.5 || voicePitch > 2.0) + voicePitch = 1.0; + if(argc == 9) + { + if(argv[8][0] == StringTagPrefixByte) + prefSkinTag = dAtoi(argv[8] + 1); + else if(argv[8][0]) + prefSkinTag = dAtoi(argv[8]); + } + return gTargetManager->allocTarget(nameTag, skinTag, voiceTag, typeTag, sensorGroup, dataBlockId, voicePitch, prefSkinTag); +} + +static void cFreeTarget(SimObject *, S32, const char **argv) +{ + if(!gTargetManager) + return; + S32 target = getValidTarget(dAtoi(argv[1]), "cFreeTarget", true); + if(target == -1) + return; + gTargetManager->freeTarget(target); +} + +// Name: -------------------------------------------------------------------- +// - queries the server's game name for this target +static const char * cGetTargetGameName(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetGameName", false); + if(target == -1) + return(""); + + char * ret = Con::getReturnBuffer(128); + if(!gTargetManager->getGameName(target, ret, 128, true)) + return(""); + + return(ret); +} + +static S32 cGetTargetName(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetName", false); + if(target == -1) + return(-1); + return(gTargetManager->getTargetName(target)); +} + +static void cSetTargetName(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetName", false); + if(target == -1) + return; + if ( argv[2][0] == StringTagPrefixByte ) + gTargetManager->setTargetName(target, dAtoi(argv[2] + 1)); + else + gTargetManager->setTargetName(target, dAtoi(argv[2])); +} + +// Skin: -------------------------------------------------------------------- +static S32 cGetTargetSkin(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetSkin", false); + if(target == -1) + return(-1); + return(gTargetManager->getTargetSkin(target)); +} + +static void cSetTargetSkin(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetSkin", false); + if(target == -1) + return; + if ( argv[2][0] == StringTagPrefixByte ) + gTargetManager->setTargetSkin(target, dAtoi(argv[2] + 1)); + else + gTargetManager->setTargetSkin(target, dAtoi(argv[2])); +} + +// Voice: ------------------------------------------------------------------- +static S32 cGetTargetVoice(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetVoice", false); + if(target == -1) + return(-1); + return(gTargetManager->getTargetVoice(target)); +} + +static void cSetTargetVoice(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetVoice", false); + if(target == -1) + return; + if ( argv[2][0] == StringTagPrefixByte ) + gTargetManager->setTargetVoice(target, dAtoi(argv[2] + 1)); + else + gTargetManager->setTargetVoice(target, dAtoi(argv[2])); +} + +// Type: -------------------------------------------------------------------- +static S32 cGetTargetType(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetType", false); + if(target == -1) + return(-1); + return(gTargetManager->getTargetType(target)); +} + +static void cSetTargetType(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetType", false); + if(target == -1) + return; + if ( argv[2][0] == StringTagPrefixByte ) + gTargetManager->setTargetType(target, dAtoi(argv[2] + 1)); + else + gTargetManager->setTargetType(target, dAtoi(argv[2])); +} + +// SensorGroup: ------------------------------------------------------------- +static S32 cGetTargetSensorGroup(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetSensorGroup", false); + if(target == -1) + return(-1); + return(gTargetManager->getTargetSensorGroup(target)); +} + +static void cSetTargetSensorGroup(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetSensorGroup", true); + if(target == -1) + return; + S32 group = getValidSensorGroup(dAtoi(argv[2]), "cSetTargetSensorGroup"); + if(group == -1) + return; + gTargetManager->setTargetSensorGroup(target, group); +} + +// VoicePitch: ------------------------------------------------------------- +static F32 cGetTargetVoicePitch(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetVoicePitch", false); + if(target == -1) + return(-1); + return(gTargetManager->getTargetVoicePitch(target)); +} + +static void cSetTargetVoicePitch(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetVoicePitch", true); + if(target == -1) + return; + F32 pitch = getValidVoicePitch(dAtof(argv[2]), "cSetTargetVoicePitch"); + gTargetManager->setTargetVoicePitch(target, pitch); +} + +// SensorGroupCount: -------------------------------------------------------- +static S32 cGetSensorGroupCount(SimObject *, S32, const char **) +{ + return(gTargetManager->getSensorGroupCount()); +} + +static void cSetSensorGroupCount(SimObject *, S32, const char ** argv) +{ + S32 groupCount = dAtoi(argv[1]); + if(groupCount < 0 || groupCount > 32) + { + Con::errorf(ConsoleLogEntry::General, "TargetManager::cSetSensorGroupCount: invalid group count [%d]", groupCount); + return; + } + gTargetManager->setSensorGroupCount(groupCount); +} + +// TargetAlwaysVisMask: ----------------------------------------------------- +static S32 cGetTargetAlwaysVisMask(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetAlwaysVisMask", false); + if(target == -1) + return(0); + return(gTargetManager->getTargetAlwaysVisMask(target)); +} + +static void cSetTargetAlwaysVisMask(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetAlwaysVisMask", false); + if(target == -1) + return; + gTargetManager->setTargetAlwaysVisMask(target, dAtoi(argv[2])); +} + + +// TargetNeverVisMask: ------------------------------------------------------ +static S32 cGetTargetNeverVisMask(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetNeverVisMask", false); + if(target == -1) + return(0); + return(gTargetManager->getTargetNeverVisMask(target)); +} + +static void cSetTargetNeverVisMask(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetNeverVisMask", false); + if(target == -1) + return; + gTargetManager->setTargetNeverVisMask(target, dAtoi(argv[2])); +} + +// TargetFriendlyMask: ------------------------------------------------------ +static S32 cGetTargetFriendlyMask(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetFriendlyMask", false); + if(target == -1) + return(0); + return(gTargetManager->getTargetFriendlyMask(target)); +} + +static void cSetTargetFriendlyMask(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetFriendlyMask", false); + if(target == -1) + return; + gTargetManager->setTargetFriendlyMask(target, dAtoi(argv[2])); +} + +// SensorGroupAlwaysVisMask: ------------------------------------------------ +static S32 cGetSensorGroupAlwaysVisMask(SimObject *, S32, const char ** argv) +{ + S32 group = getValidSensorGroup(dAtoi(argv[1]), "cGetSensorGroupAlwaysVisMask"); + if(group == -1) + return(0); + return(gTargetManager->getSensorGroupAlwaysVisMask(group)); +} + +static void cSetSensorGroupAlwaysVisMask(SimObject *, S32, const char ** argv) +{ + S32 group = getValidSensorGroup(dAtoi(argv[1]), "cSetSensorGroupAlwaysVisMask"); + if(group == -1) + return; + gTargetManager->setSensorGroupAlwaysVisMask(group, dAtoi(argv[2])); +} + +// SensorGroupNeverVisMask: ------------------------------------------------- +static S32 cGetSensorGroupNeverVisMask(SimObject *, S32, const char ** argv) +{ + S32 group = getValidSensorGroup(dAtoi(argv[1]), "cGetSensorGroupNeverVisMask"); + if(group == -1) + return(0); + return(gTargetManager->getSensorGroupNeverVisMask(group)); +} + +static void cSetSensorGroupNeverVisMask(SimObject *, S32, const char ** argv) +{ + S32 group = getValidSensorGroup(dAtoi(argv[1]), "cSetSensorGroupNeverVisMask"); + if(group == -1) + return; + gTargetManager->setSensorGroupNeverVisMask(group, dAtoi(argv[2])); +} + +// SensorGroupFriendlyMask: ------------------------------------------------- +static S32 cGetSensorGroupFriendlyMask(SimObject *, S32, const char ** argv) +{ + S32 group = getValidSensorGroup(dAtoi(argv[1]), "cGetSensorGroupFriendlyMask"); + if(group == -1) + return(0); + return(gTargetManager->getSensorGroupFriendlyMask(group)); +} + +static void cSetSensorGroupFriendlyMask(SimObject *, S32, const char ** argv) +{ + S32 group = getValidSensorGroup(dAtoi(argv[1]), "cSetSensorGroupFriendlyMask"); + if(group == -1) + return; + gTargetManager->setSensorGroupFriendlyMask(group, dAtoi(argv[2])); +} + +// SensorGroupListenMask: --------------------------------------------------- +static S32 cGetSensorGroupListenMask(SimObject *, S32, const char ** argv) +{ + S32 group = getValidSensorGroup(dAtoi(argv[1]), "cGetSensorGroupListenMask"); + if(group == -1) + return(0); + return(gTargetManager->getSensorGroupListenMask(group)); +} + +static void cSetSensorGroupListenMask(SimObject *, S32, const char ** argv) +{ + S32 group = getValidSensorGroup(dAtoi(argv[1]), "cSetSensorGroupListenMask"); + if(group == -1) + return; + gTargetManager->setSensorGroupListenMask(group, dAtoi(argv[2])); +} + +// IsTarget(...): ----------------------------------------------------------- +static bool cIsTargetFriendly(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cIsTargetFriendly", false); + if(target == -1) + return(false); + S32 group = getValidSensorGroup(dAtoi(argv[2]), "cIsTargetFriendly"); + if(group == -1) + return(false); + return(gTargetManager->isTargetFriendly(target, group)); +} + +static bool cIsTargetVisible(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cIsTargetVisible", false); + if(target == -1) + return(false); + S32 group = getValidSensorGroup(dAtoi(argv[2]), "cIsTargetVisible"); + if(group == -1) + return(false); + return(gTargetManager->isTargetVisible(target, group)); +} + +// Others: ------------------------------------------------------------------ +static void cSetTargetSensorData(SimObject *, S32, const char **argv) +{ + S32 target = dAtoi(argv[1]); + if(target < 0 || target >= TargetManager::MaxTargets) + return; + + SensorData * data = dynamic_cast(Sim::findObject(argv[2])); + TargetInfo *targ = gTargetManager->getServerTarget(target); + targ->sensorData = data; +} + +static S32 cGetTargetSensorData(SimObject *, S32, const char ** argv) +{ + S32 target = dAtoi(argv[1]); + if(target < 0 || target >= TargetManager::MaxTargets) + return(-1); + + TargetInfo * targ = gTargetManager->getServerTarget(target); + if(bool(targ->sensorData)) + return(targ->sensorData->getId()); + return(-1); +} + +static const char * cGetTargetObject(SimObject *, S32, const char **argv) +{ + char * buf = Con::getReturnBuffer(12); + S32 target = dAtoi(argv[1]); + if(target < 0 || target >= TargetManager::MaxTargets || !bool(gTargetManager->mTargets[target].targetObject)) + dStrcpy(buf, "-1"); + else + dSprintf(buf, 12, "%d", gTargetManager->mTargets[target].targetObject->getId()); + return(buf); +} + +// Color info: ------------------------------------------------------------ +static const char * cGetSensorGroupColor(SimObject *, S32, const char ** argv) +{ + S32 sensorGroup = getValidSensorGroup(dAtoi(argv[1]), "cGetSensorGroupColor"); + if(sensorGroup == -1) + return(""); + + S32 colorGroup = getValidSensorGroup(dAtoi(argv[2]), "cGetSensorGroupColor"); + if(colorGroup == -1) + return(""); + + char * buf = Con::getReturnBuffer(100); + + ColorI col = gTargetManager->getSensorGroupColor(sensorGroup, colorGroup); + dSprintf(buf, 100, "%d %d %d %d", col.red, col.green, col.blue, col.alpha); + return(buf); +} + +static void cSetSensorGroupColor(SimObject *, S32, const char ** argv) +{ + S32 sensorGroup = getValidSensorGroup(dAtoi(argv[1]), "cSetSensorGroupColor"); + if(sensorGroup == -1) + return; + + U32 r,g,b,a; + dSscanf(argv[3], "%d %d %d %d", &r, &g, &b, &a); + + ColorI col(r,g,b,a); + gTargetManager->setSensorGroupColor(sensorGroup, dAtoi(argv[2]), col); +} + +// DataBlock info: ---------------------------------------------------------- +static S32 cGetTargetDataBlock(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetDataBlock", true); + if(target == -1) + return(-1); + + TargetInfo * targ = gTargetManager->getServerTarget(target); + if(!bool(targ->shapeBaseData)) + return(0); + + return(targ->shapeBaseData->getId()); +} + +static void cSetTargetDataBlock(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetDataBlock", true); + if(target == -1) + return; + + ShapeBaseData * data = dynamic_cast(Sim::findObject(dAtoi(argv[2]))); + gTargetManager->setTargetShapeBaseData(target, data); +} + +// TargetRender: ------------------------------------------------------------ +static S32 cGetTargetRenderMask(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cGetTargetRender", false); + if(target == -1) + return(-1); + + return(gTargetManager->getServerTarget(target)->renderFlags); +} + +static void cSetTargetRenderMask(SimObject *, S32, const char ** argv) +{ + S32 target = getValidTarget(dAtoi(argv[1]), "cSetTargetRender", false); + if(target == -1) + return; + + gTargetManager->setTargetRenderMask(target, dAtoi(argv[2])); +} + +// Audio: ------------------------------------------------------------------- +static void cPlayTargetAudio(SimObject *, S32 argc, const char ** argv) +{ + AudioDescription * desc = dynamic_cast(Sim::findObject(argv[3])); + if(!desc || (desc->getId() < DataBlockObjectIdFirst) || (desc->getId() > DataBlockObjectIdLast)) + { + Con::warnf("Invalid audio description '%s'.", argv[5]); + return; + } + + bool update; + if(argc < 5) + update = false; + else + update = dAtob(argv[4]); + + S32 fileTag = (argv[2][0] == StringTagPrefixByte) ? dAtoi(argv[2] + 1) : dAtoi(argv[2]); + if(!gTargetManager->playTargetAudio(dAtoi(argv[1]), fileTag, desc, update)) + Con::warnf("Failed to send target audio event to clients"); +} + +void TargetManager::create() +{ + gTargetManager = new TargetManager; + gTargetList = new HUDTargetList; + + Con::addCommand("resetTargets", cReset, "resetTargets()", 1, 1); + Con::addCommand("resetClientTargets", cResetClientTargets, "resetClientTargets(connection, tasksOnly)", 3, 3); + + Con::addCommand("sendTargetsToClient", cSendTargetsToClient, "sendTargetsToClient(connection)", 2, 2); + Con::addCommand("allocTarget", cAllocTarget, "allocTarget(nameTag, skinTag, voiceTag, typeTag, sensorGroup, dataBlockId, voicePitch, [prefskin])", 8, 9); + Con::addCommand("freeTarget", cFreeTarget, "freeTarget(targetId)", 2, 2); + + Con::addCommand("getTargetGameName", cGetTargetGameName, "getTargetGameName(targetId)", 2, 2); + Con::addCommand("getTargetName", cGetTargetName, "getTargetName(targetId)", 2, 2); + Con::addCommand("setTargetName", cSetTargetName, "setTargetName(targetId, nameTag)", 3, 3); + Con::addCommand("getTargetSkin", cGetTargetSkin, "getTargetSkin(targetId)", 2, 2); + Con::addCommand("setTargetSkin", cSetTargetSkin, "setTargetSkin(targetId, skinTag)", 3, 3); + Con::addCommand("getTargetVoice", cGetTargetVoice, "getTargetVoice(targetId)", 2, 2); + Con::addCommand("setTargetVoice", cSetTargetVoice, "setTargetVoice(targetId, voiceTag)", 3, 3); + Con::addCommand("getTargetVoicePitch", cGetTargetVoicePitch, "getTargetVoicePitch(targetId)", 2, 2); + Con::addCommand("setTargetVoicePitch", cSetTargetVoicePitch, "setTargetVoice(targetId, voicePitch)", 3, 3); + Con::addCommand("getTargetType", cGetTargetType, "getTargetType(targetId)", 2, 2); + Con::addCommand("setTargetType", cSetTargetType, "setTargetType(targetId, typeTag)", 3, 3); + Con::addCommand("getTargetSensorGroup", cGetTargetSensorGroup, "getTargetSensorGroup(targetId)", 2, 2); + Con::addCommand("setTargetSensorGroup", cSetTargetSensorGroup, "setTargetSensorGroup(targetId, sensorGroup)", 3, 3); + + Con::addCommand("getTargetAlwaysVisMask", cGetTargetAlwaysVisMask, "getTargetAlwaysVisMask(target)", 2, 2); + Con::addCommand("setTargetAlwaysVisMask", cSetTargetAlwaysVisMask, "setTargetAlwaysVisMask(target, mask)", 3, 3); + Con::addCommand("getTargetNeverVisMask", cGetTargetNeverVisMask, "getTargetNeverVisMask(target)", 2, 2); + Con::addCommand("setTargetNeverVisMask", cSetTargetNeverVisMask, "setTargetNeverVisMask(target, mask)", 3, 3); + Con::addCommand("getTargetFriendlyMask", cGetTargetFriendlyMask, "getTargetFriendlyMask(target)", 2, 2); + Con::addCommand("setTargetFriendlyMask", cSetTargetFriendlyMask, "setTargetFriendlyMask(target, mask)", 3, 3); + + Con::addCommand("getSensorGroupAlwaysVisMask", cGetSensorGroupAlwaysVisMask, "getSensorGroupAlwaysVisMask(sensorGroup)", 2, 2); + Con::addCommand("setSensorGroupAlwaysVisMask", cSetSensorGroupAlwaysVisMask, "setSensorGroupAlwaysVisMask(sensorGroup, mask)", 3, 3); + Con::addCommand("getSensorGroupNeverVisMask", cGetSensorGroupNeverVisMask, "getSensorGroupNeverVisMask(sensorGroup)", 2, 2); + Con::addCommand("setSensorGroupNeverVisMask", cSetSensorGroupNeverVisMask, "setSensorGroupNeverVisMask(sensorGroup, mask)", 3, 3); + Con::addCommand("getSensorGroupFriendlyMask", cGetSensorGroupFriendlyMask, "getSensorGroupFriendlyMask(sensorGroup)", 2, 2); + Con::addCommand("setSensorGroupFriendlyMask", cSetSensorGroupFriendlyMask, "setSensorGroupFriendlyMask(sensorGroup, mask)", 3, 3); + + Con::addCommand("getSensorGroupListenMask", cGetSensorGroupListenMask, "getSensorGroupListenMask(sensorGroup)", 2, 2); + Con::addCommand("setSensorGroupListenMask", cSetSensorGroupListenMask, "setSensorGroupListenMask(sensorGroup, mask)", 3, 3); + + Con::addCommand("isTargetFriendly", cIsTargetFriendly, "isTargetFriendly(target, sensorGroup)", 3, 3); + Con::addCommand("isTargetVisible", cIsTargetVisible, "isTargetVisible(target, sensorGroup)", 3, 3); + + Con::addCommand("getSensorGroupCount", cGetSensorGroupCount, "getSensorGroupCount()", 1, 1); + Con::addCommand("setSensorGroupCount", cSetSensorGroupCount, "setSensorGroupCount(count)", 2, 2); + + Con::addCommand("setTargetSensorData", cSetTargetSensorData, "setTargetSensorData(targetId, sensorData)", 3, 3); + Con::addCommand("getTargetSensorData", cGetTargetSensorData, "getTargetSensorData(targetId)", 2, 2); + Con::addCommand("getTargetObject", cGetTargetObject, "getTargetObject(targetId)", 2, 2); + + Con::addCommand("getSensorGroupColor", cGetSensorGroupColor, "getSensorGroupColor(sensorGroup, colorGroup)", 3, 3); + Con::addCommand("setSensorGroupColor", cSetSensorGroupColor, "setSensorGroupColor(sensorGroup, groupMask, color)", 4, 4); + + Con::addCommand("setTargetDataBlock", cSetTargetDataBlock, "setTargetDataBlock(targetId, dataBlockId)", 3, 3); + Con::addCommand("getTargetDataBlock", cGetTargetDataBlock, "getTargetDataBlock(targetId)", 2, 2); + + Con::addCommand("getTargetRenderMask", cGetTargetRenderMask, "getTargetRender(targetId)", 2, 2); + Con::addCommand("setTargetRenderMask", cSetTargetRenderMask, "setTargetRender(targetId, mask)", 3, 3); + + Con::setIntVariable("$TargetInfo::HudRenderStart", TargetInfo::HudRenderStart); + Con::setIntVariable("$TargetInfo::NumHudRenderImages", TargetInfo::NumHudRenderImages); + Con::setIntVariable("$TargetInfo::CommanderListRender", TargetInfo::CommanderListRender); + + Con::addCommand("playTargetAudio", cPlayTargetAudio, "playTargetAudio(target, fileTag, desc, update)", 5, 5); +} + +void TargetManager::destroy() +{ + delete gTargetList; + gTargetList = 0; + + delete gTargetManager; + gTargetManager = 0; +} + +void TargetManager::writeDemoStartBlock(ResizeBitStream *stream, GameConnection *) +{ + TargetInfo *targ = mClientTargets; + for(U32 i = 0; i < MaxTargets; i++) + { + if(stream->writeFlag(targ->allocated)) + { + if(stream->writeFlag(targ->sNameTag)) + stream->write(targ->sNameTag); + if(stream->writeFlag(targ->sSkinTag)) + stream->write(targ->sSkinTag); + if(stream->writeFlag(targ->sSkinPrefTag)) + stream->write(targ->sSkinPrefTag); + if(stream->writeFlag(targ->sVoiceTag)) + stream->write(targ->sVoiceTag); + if(stream->writeFlag(targ->sTypeTag)) + stream->write(targ->sTypeTag); + stream->writeInt(targ->sensorGroup, 5); + stream->writeInt(targ->renderFlags, TargetInfo::NumRenderBits); + if(i >= 32) + { + if(stream->writeFlag(targ->shapeBaseData)) + stream->writeRangedU32(targ->shapeBaseData->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + //convert the voice pitch from range [0.5, 2.0] to [0.0, 1.0] + F32 packFloat; + if (targ->voicePitch < 0.5f || targ->voicePitch > 2.0f) + packFloat = (1.0f - 0.5f) / 1.5f; + else + packFloat = (targ->voicePitch - 0.5f) / 1.5f; + stream->writeFloat(packFloat, 7); + stream->validate(); + } + } +} + +void TargetManager::readDemoStartBlock(BitStream *stream, GameConnection *conn) +{ + TargetInfo *targ = mClientTargets; + for(U32 i = 0; i < MaxTargets; i++) + { + if(stream->readFlag()) + { + if(stream->readFlag()) + { + stream->read(&targ->sNameTag); + targ->nameTag = conn->translateRemoteStringId(targ->sNameTag); + } + if(stream->readFlag()) + { + stream->read(&targ->sSkinTag); + targ->skinTag = conn->translateRemoteStringId(targ->sSkinTag); + } + if(stream->readFlag()) + { + stream->read(&targ->sVoiceTag); + targ->voiceTag = conn->translateRemoteStringId(targ->sVoiceTag); + } + if(stream->readFlag()) + { + stream->read(&targ->sTypeTag); + targ->typeTag = conn->translateRemoteStringId(targ->sTypeTag); + } + targ->sensorGroup = stream->readInt(5); + + targ->renderFlags = stream->readInt(TargetInfo::NumRenderBits); + targ->allocated = true; + if(i >= 32) + { + if(stream->readFlag()) + { + U32 id; + id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + targ->shapeBaseData = dynamic_cast(Sim::findObject(id)); + } + gTargetManager->notifyTargetAdded(i); + if(bool(targ->targetObject)) + targ->targetObject->targetInfoChanged(targ); + } + + //convert the voice pitch from range [0.0, 1.0] back to [0.5, 2.0] + F32 unpackFloat = stream->readFloat(7); + targ->voicePitch = (unpackFloat * 1.5) + 0.5f; + if (targ->voicePitch < 0.5f) + targ->voicePitch = 0.5f; + else if (targ->voicePitch > 2.0f) + targ->voicePitch = 2.0f; + } + } +} + +//---------------------------------------------------------------------------- +// HUDTargetListNotify: client target notification system, passes handles +// to the actual entries +//---------------------------------------------------------------------------- +HUDTargetListNotify::HUDTargetListNotify() +{ + AssertFatal(gTargetList, "HUDTargetListNotify:: No target list present"); + gTargetList->addNotify(this); +} + +HUDTargetListNotify::~HUDTargetListNotify() +{ + if(gTargetList) + gTargetList->removeNotify(this); +} + +void HUDTargetList::addNotify(HUDTargetListNotify * notify) +{ + for(S32 i = hudNotifyList.size() - 1; i >= 0; i--) + if(hudNotifyList[i] == notify) + { + Con::errorf(ConsoleLogEntry::General, "HUDTargetList::addNotify: object already being notified!"); + return; + } + hudNotifyList.push_back(notify); +} + +void HUDTargetList::removeNotify(HUDTargetListNotify * notify) +{ + for(S32 i = hudNotifyList.size() - 1; i >= 0; i--) + if(hudNotifyList[i] == notify) + { + hudNotifyList.erase(i); + return; + } + Con::errorf(ConsoleLogEntry::General, "HUDTargetList::removeNotify: object not found in notify list!"); +} + +void HUDTargetList::notifyTargetAdded(U32 target) +{ + for(S32 i = hudNotifyList.size() - 1; i >= 0; i--) + hudNotifyList[i]->hudTargetAdded(target); +} + +void HUDTargetList::notifyTargetRemoved(U32 target) +{ + for(S32 i = hudNotifyList.size() - 1; i >= 0; i--) + hudNotifyList[i]->hudTargetRemoved(target); +} + +void HUDTargetList::notifyTargetsCleared() +{ + for(S32 i = hudNotifyList.size() - 1; i >= 0; i--) + hudNotifyList[i]->hudTargetsCleared(); +} + + +//---------------------------------------------------------------------------- +// HUDTargetList: +//---------------------------------------------------------------------------- +U32 HUDTargetList::smTargetTimeout = HUDTargetList::DefaultTimeout; + +//---------------------------------------------------------------------------- +HUDTargetList::HUDTargetList() +{ + VECTOR_SET_ASSOCIATION(hudNotifyList); + + mCount = 0; + + mNumAssignedTasks = 0; + mNumPotentialTasks = 0; + mNumWaypoints = 0; + + for(U32 i = 0; i < MaxTargets; i++) + mHandle[i] = FREE_HANDLE; +} + +//---------------------------------------------------------------------------- +S32 HUDTargetList::findOldestEntry(S32 type) +{ + AssertFatal(mCount, "HUDTargetList::findOldestEntry: should not have 0 count"); + + U32 oldTime = 0xffffffff; + S32 oldest = -1; + for(U32 i = 0; i < mCount; i++) + { + AssertFatal(bool(mList[i].target), "HUDTargetList::findOldestEntry: invalid target"); + if((mList[i].target->mType == type) && (mList[i].doneTime < oldTime)) + { + oldest = i; + oldTime = mList[i].doneTime; + } + } + return(oldest); +} + +//---------------------------------------------------------------------------- +S32 HUDTargetList::getAddSlot(ClientTarget * target) +{ + S32 slot = mCount; + switch(target->mType) + { + case ClientTarget::AssignedTask: + if(mNumAssignedTasks == MaxAssignedTasks) + slot = findOldestEntry(ClientTarget::AssignedTask); + break; + + case ClientTarget::PotentialTask: + if(mNumPotentialTasks == MaxPotentialTasks) + slot = findOldestEntry(ClientTarget::PotentialTask); + break; + + case ClientTarget::Waypoint: + if(mNumWaypoints == MaxWaypoints) + slot = findOldestEntry(ClientTarget::Waypoint); + break; + + default: + slot = -1; + break; + } + return(slot); +} + +S32 HUDTargetList::getFreeHandle() +{ + for(U32 i = 0; i < MaxTargets; i++) + if(mHandle[i] == FREE_HANDLE) + return(i); + return(MaxTargets); +} + +// Hudtarget ids are actually handles which are mapped above TargetManager::MaxTarget so +// that any target type still has a unique target id +S32 HUDTargetList::getHandleIndex(S32 handle) +{ + AssertFatal(handle >= TargetManager::MaxTargets && handle < TargetManager::MaxTargets + MaxTargets, "HUDTargetList::getNotifyEntry: invalid handle"); + return(mHandle[handle - TargetManager::MaxTargets]); +} + +bool HUDTargetList::addTarget(ClientTarget * target, bool canTimeout, U32 doneTime) +{ + S32 slot = getAddSlot(target); + if(slot == -1) + return(false); + + // something there? + if(slot != mCount) + { + AssertFatal(bool(mList[slot].target), "HUDTargetList::addTarget: invalid target in list"); + mList[slot].target->onDie(); + } + + // notify class' get a handle to this entry + S32 handle = getFreeHandle(); + AssertFatal(handle != MaxTargets, "HudTargetList::addTarget: failed to get free handle"); + mHandle[handle] = mCount; + + // always add at end + mList[mCount].target = target; + mList[mCount].canTimeout = canTimeout; + mList[mCount].doneTime = doneTime; + mList[mCount].handle = handle; + mCount++; + + // increment the type count + if(target->mType == ClientTarget::AssignedTask) + mNumAssignedTasks++; + else if(target->mType == ClientTarget::PotentialTask) + mNumPotentialTasks++; + else if(target->mType == ClientTarget::Waypoint) + mNumWaypoints++; + + // notify using unique target id's + notifyTargetAdded(handle + TargetManager::MaxTargets); + return(true); +} + +//-------------------------------------------------------------------------- +// Sent to remove a specific target type from the target list +//-------------------------------------------------------------------------- +class RemoveClientTargetTypeEvent : public NetEvent +{ + private: + U32 mTargetType; + + public: + RemoveClientTargetTypeEvent(U32 type = 0) { mTargetType = type; } + void pack(NetConnection *, BitStream * bstream) { bstream->writeRangedU32(mTargetType, 0, ClientTarget::NumTypes); } + void write(NetConnection *, BitStream * bstream) { bstream->writeRangedU32(mTargetType, 0, ClientTarget::NumTypes); } + void unpack(NetConnection *, BitStream * bstream) { mTargetType = bstream->readRangedU32(0, ClientTarget::NumTypes); } + void process(NetConnection *) { gTargetList->removeTargetsOfType(mTargetType); } + + DECLARE_CONOBJECT(RemoveClientTargetTypeEvent); +}; +IMPLEMENT_CO_CLIENTEVENT_V1(RemoveClientTargetTypeEvent); + +// remove any target with this type +void HUDTargetList::removeTargetsOfType(U32 type) +{ + AssertFatal(type < ClientTarget::NumTypes, "HudTargetList::removeTargetsOfType: invalid type id"); + + // mCount gets updated on removing of an entry + for(U32 i = 0; i < mCount; i++) + { + AssertFatal(bool(mList[i].target), "HUDTargetList::removeTargetsOfType: invalid target"); + if(mList[i].target->mType == type) + removeEntry(i); + } +} + +void HUDTargetList::removeEntry(S32 entry) +{ + AssertFatal(entry < mCount, "HUDTargetList::removeEntry: invalid entry"); + notifyTargetRemoved(mList[entry].handle + TargetManager::MaxTargets); + + // dec the type count + AssertFatal(bool(mList[entry].target), "HUDTargetList::removeEntry:: invalid target"); + ClientTarget * target = static_cast(mList[entry].target); + + if(target->mType == ClientTarget::AssignedTask) + mNumAssignedTasks--; + else if(target->mType == ClientTarget::PotentialTask) + mNumPotentialTasks--; + else if(target->mType == ClientTarget::Waypoint) + mNumWaypoints--; + + // update the notification handles as well + mCount--; + if(entry != mCount) + { + mHandle[mList[mCount].handle] = entry; + mHandle[mList[entry].handle] = FREE_HANDLE; + mList[entry] = mList[mCount]; + } + else + mHandle[mList[entry].handle] = FREE_HANDLE; + + mList[mCount].target = 0; +} + +//--------------------------------------------------------------------------- +// TargetManager notification: +//--------------------------------------------------------------------------- +void HUDTargetList::targetRemoved(U32 target) +{ + // multiple hudtargets could be mapped to this target... + for(S32 i = mCount - 1; i >= 0; i--) + { + AssertFatal(bool(mList[i].target), "HUDTargetList::targetRemoved: invalid target in list!"); + if(target == mList[i].target->mTargetId) + (static_cast(mList[i].target))->deleteObject(); + } +} + +void HUDTargetList::targetsCleared() +{ + for(S32 i = mCount - 1; i >= 0; i--) + { + AssertFatal(bool(mList[i].target), "HUDTargetList::targetRemoved: invalid target in list!"); + (static_cast(mList[i].target))->deleteObject(); + } + notifyTargetsCleared(); +} + +//--------------------------------------------------------------------------- +void HUDTargetList::update(U32 newTime) +{ + for(S32 i = mCount - 1; i >= 0; i--) + { + AssertFatal(bool(mList[i].target), "HUDTargetList::updateTime: invalid target"); + + // timeout + if(mList[i].canTimeout && (mList[i].doneTime < newTime)) + { + mList[i].target->onDie(); + continue; + } + + // skip location targets (their position is already known) + if(mList[i].target->mTargetId == -1) + continue; + + // update position + TargetInfo * targ = gTargetManager->getClientTarget(mList[i].target->mTargetId); + if(targ->sensorFlags & TargetInfo::VisibleToSensor) + if(bool(targ->targetObject)) + targ->targetObject->getRenderWorldBox().getCenter(&mList[i].target->mLastTargetPos); + } +} + +S32 HUDTargetList::getEntryByTarget(ClientTarget * target) +{ + if(!target) + return(-1); + + for(U32 i = 0; i < mCount; i++) + if(static_cast(mList[i].target) == target) + return(i); + + return(-1); +} + +void HUDTargetList::removeEntryByTarget(ClientTarget * target) +{ + S32 entry = getEntryByTarget(target); + if(entry == -1) + return; + + removeEntry(entry); +} + +HUDTargetList::Entry *HUDTargetList::getEntry(U32 index) +{ + if(index >= mCount) + return(0); + return(mList + index); +} + +//-------------------------------------------------------------------------- +ClientTarget::ClientTarget(S32 type, S32 targetId, Point3F targetPos) +{ + mType = type; + mTargetId = targetId; + mLastTargetPos = targetPos; + mText = 0; +} + +void ClientTarget::onRemove() +{ + if(gTargetList) + { + if(mType == Waypoint) + Con::executef(this, 1, "waypointRemoved"); + gTargetList->removeEntryByTarget(this); + } + Parent::onRemove(); +} + +// being removed by the target list +void ClientTarget::onDie() +{ + const char * typeStr = 0; + switch(mType) + { + case AssignedTask: + typeStr = "AssignedTask"; + break; + case PotentialTask: + typeStr = "PotentialTask"; + break; + + default: + deleteObject(); + return; + } + + SimObjectPtr safeThis = this; + + // give the console a chance to take possesion of this target + Con::executef(this, 2, "onDie", typeStr); + + if(!bool(safeThis)) + return; + + // removed now? + gTargetList->removeEntryByTarget(this); + if(!getGroup() || (getGroup() == NetConnection::getServerConnection())) + deleteObject(); +} + +//--------------------------------------------------------------------------- +// only ClientTarget created through event get processed (Tasks) +bool ClientTarget::process() +{ + S32 time = Sim::getCurrentTime() + (mType == PotentialTask ? HUDTargetList::smTargetTimeout : 0); + if(gTargetList->addTarget(this, mType == PotentialTask, time)) + { + Con::executef(this, 2, "onAdd", mType == AssignedTask ? "AssignedTask" : "PotentialTask"); + return(true); + } + return(false); +} + +static void cTargetSendToServer(SimObject *target, S32, const char **) +{ + ClientTarget *targ = static_cast(target); + GameConnection *gc = GameConnection::getServerConnection(); + if(gc) + gc->sendTargetToServer(targ->mTargetId, targ->mLastTargetPos); +} + +// waypoints are created by the client +static void cCreateWaypoint(SimObject * obj, S32, const char ** argv) +{ + ClientTarget * target = static_cast(obj); + + // can only create waypoints from untyped targets + if(target->mType != -1) + { + Con::errorf(ConsoleLogEntry::General, "ClientTarget::cCreateWaypoint: target already typed"); + return; + } + + target->mText = StringTable->insert(argv[2]); + target->mType = ClientTarget::Waypoint; + if(!gTargetList->addTarget(target, false, Sim::getCurrentTime())) + Con::errorf(ConsoleLogEntry::General, "ClientTarget::cCreateWaypoint: unable to create waypoint"); +} + +static void cAddPotentialTask(SimObject * obj, S32, const char **) +{ + ClientTarget * target = static_cast(obj); + if(target->mType != ClientTarget::PotentialTask) + return; + + // may already be in the list + S32 entry = gTargetList->getEntryByTarget(target); + if(entry != -1) + return; + + // add to the target-list + if(!gTargetList->addTarget(target, true, Sim::getCurrentTime() + HUDTargetList::smTargetTimeout)) + Con::errorf(ConsoleLogEntry::General, "ClientTarget::cAddPotentialTask: unable to add task"); +} + +static void cSetText(SimObject * obj, S32, const char ** argv) +{ + ClientTarget * target = static_cast(obj); + target->mText = StringTable->insert(argv[2]); +} + +static S32 cGetTargetId(SimObject * obj, S32, const char **) +{ + ClientTarget * target = static_cast(obj); + if(!gTargetList) + return(-1); + S32 entry = gTargetList->getEntryByTarget(target); + if(entry == -1) + return(-1); + return(TargetManager::MaxTargets + gTargetList->mList[entry].handle); +} + +static S32 cCreateClientTarget(SimObject *, S32 argc, const char ** argv) +{ + GameConnection * con = GameConnection::getServerConnection(); + if(!con) + return(-1); + + Point3F pos(0,0,0); + if(argc == 3) + dSscanf(argv[2],"%f %f %f", &pos.x, &pos.y, &pos.z); + + ClientTarget * target = new ClientTarget(-1, dAtoi(argv[1]), pos); + target->registerObject(); + con->addObject(target); + return(target->getId()); +} + +static U32 getTypeFromString(const char * str) +{ + if(!str) + return(ClientTarget::NumTypes); + + if(!dStricmp(str, "AssignedTask")) + return(ClientTarget::AssignedTask); + else if(!dStricmp(str, "PotentialTask")) + return(ClientTarget::PotentialTask); + else if(!dStricmp(str, "Waypoint")) + return(ClientTarget::Waypoint); + + return(ClientTarget::NumTypes); +} + +static void cRemoveClientTargetType(SimObject *, S32, const char ** argv) +{ + U32 type = getTypeFromString(argv[2]); + + // make sure type in range + if(type >= ClientTarget::NumTypes) + { + Con::errorf(ConsoleLogEntry::General, "HUDTargetList::removeClientTargetType: invalid type [%s]", argv[1]); + return; + } + + // only send to a non-ai connection + NetConnection * client = dynamic_cast(Sim::findObject(dAtoi(argv[1]))); + if(client && !client->isServerConnection()) + { + if(dynamic_cast(client) && static_cast(client)->isAIControlled()) + return; + client->postNetEvent( new RemoveClientTargetTypeEvent(type) ); + } + else + Con::errorf(ConsoleLogEntry::General, "HUDTargetList::removeClientTargetType: invalid connection [%s]", argv[1]); +} + +void ClientTarget::consoleInit() +{ + Con::addCommand("ClientTarget", "sendToServer", cTargetSendToServer, "target.sendToServer()", 2, 2); + Con::addCommand("ClientTarget", "createWaypoint", cCreateWaypoint, "target.createWaypoint(text)", 3, 3); + Con::addCommand("ClientTarget", "addPotentialTask", cAddPotentialTask, "target.addPotentialTask()", 2, 2); + Con::addCommand("ClientTarget", "setText", cSetText, "target.setText(text)", 3, 3); + Con::addCommand("ClientTarget", "getTargetId", cGetTargetId, "target.getTargetId()", 2, 2); + + Con::addCommand("createClientTarget", cCreateClientTarget, "createClientTarget(targetId, )", 2, 3); + Con::addCommand("removeClientTargetType", cRemoveClientTargetType, "removeClientTargetType(client, type)", 3, 3); + + Con::addVariable("clientTargetTimeout", TypeS32, &HUDTargetList::smTargetTimeout); +} + +IMPLEMENT_CONOBJECT(ClientTarget); + +//--------------------------------------------------------------------------- +// TargetManager::SensorInfo: +//--------------------------------------------------------------------------- +ColorI TargetManager::SensorInfo::smDefaultColor(255, 0, 0, 255); + +void TargetManager::SensorInfo::clear() +{ + U32 i; + for(i = 0; i < TargetManager::TargetFreeMaskSize; i++) + targetPingMask[i] = 0; + for(i = 0; i < 32; i++) + groupColor[i] = smDefaultColor; +} + +void TargetManager::SensorInfo::setSensorVisible(S32 targetId, bool vis) +{ + if(targetId < 0 || targetId >= TargetManager::MaxTargets) + return; + if(vis) + targetPingMask[targetId >> 5] |= (1 << (targetId & 0x1F)); + else + targetPingMask[targetId >> 5] &= ~(1 << (targetId & 0x1F)); +} + +bool TargetManager::SensorInfo::getSensorVisible(S32 targetId) +{ + if(targetId < 0 || targetId >= TargetManager::MaxTargets) + return false; + return targetPingMask[targetId >> 5] & (1 << (targetId & 0x1F)); +} + +//-------------------------------------------------------------------------- +static inline bool testLOS(GameBase * sensor, const Point3F & sensorPos, + GameBase * target, const Point3F & targetPos) +{ + static U32 losMask = TerrainObjectType | InteriorObjectType | ShapeBaseObjectType; + + RayInfo info; + + // disable collision for the target/sensor and possible mount + target->disableCollision(); + sensor->disableCollision(); + + ShapeBase * mount = 0; + if(target->getType() & ShapeBaseObjectType) + { + mount = static_cast(target)->getObjectMount(); + if(mount) + mount->disableCollision(); + } + + bool hasLOS = !gServerContainer.castRay(sensorPos, targetPos, losMask, &info); + + // enable the collisions for the objects.. + target->enableCollision(); + sensor->enableCollision(); + if(mount) + mount->enableCollision(); + + return(hasLOS); +} + +void TargetManager::tickSensorState() +{ + U32 objectCount = 0; + U32 totalCount = MaxTargets - mFreeCount; + U32 pingCount = (totalCount >> 5) + 1; // ping everything once a second + U32 lastSensed = mLastSensedObject; + + for(U32 i = mLastSensedObject + 1; i - mLastSensedObject < MaxTargets; i++) + { + U32 index = i & (MaxTargets - 1); + U32 maskPos = index >> 5; + U32 maskShift = index & 0x1F; + if((maskShift == 0) && mFreeMask[maskPos] == 0) + { + i += 31; + continue; + } + if(!(mFreeMask[maskPos] & (1 << maskShift))) + continue; + TargetInfo *targetInfo = mTargets + index; + + if(!bool(targetInfo->targetObject)) + continue; + + GameBase * target = targetInfo->targetObject; + + // ok, we have an object + // now loop through all the sensable objects + U32 baseVisMask = 0; + U32 activeJamVisMask = 0; + U32 passiveJamVisMask = 0; + U32 cloakVisMask = 0; + bool pinged = false; + bool jammed = false; + bool enemyJammed = false; + + Point3F targetPos; + + // grab eye point if player... + if(target->getType() & PlayerObjectType) + { + AssertFatal(dynamic_cast(target), "Invalid player object."); + Player * player = static_cast(target); + + MatrixF eye; + player->getEyeTransform(&eye); + eye.getColumn(3, &targetPos); + } + else + targetPos = target->getBoxCenter(); + + Point3F targetVec; + + for(U32 sens = 0; sens < MaxTargets; sens++) + { + bool testedLOS = false; + bool hasLOS = false; + U32 smaskPos = sens >> 5; + U32 smaskShift = sens & 0x1F; + if((smaskShift == 0) && mFreeMask[smaskPos] == 0) + { + sens += 31; + continue; + } + if(!(mFreeMask[smaskPos] & (1 << smaskShift))) + continue; + TargetInfo *sensorInfo = mTargets + sens; + if(!bool(sensorInfo->targetObject)) + continue; + GameBase *sensor = sensorInfo->targetObject; + SensorData *sensorData = sensorInfo->sensorData; + + if(!sensor || !sensorData) + continue; + + // can't detect its own bad self, but can jam... + if(sens == index) + { + if(sensorData->jams) + jammed = true; + continue; + } + // sensors must be shapebase items (need damage state) + if(!dynamic_cast(sensor)) + continue; + + ShapeBase * sensorShape = static_cast(sensor); + if(sensorShape->getDamageState() != ShapeBase::Enabled) + continue; + + Point3F sensorPos; + + bool jumpNoDetect = false; + + // jams? and is/not always visible? + U32 sensorMask = 1 << sensorInfo->sensorGroup; + if((targetInfo->sensorAlwaysVisMask | targetInfo->sensorNeverVisMask) & sensorMask) + { + if(sensorData->jams) + jumpNoDetect = true; + else + continue; + } + + // grab the sensor position (grab eye of players) + // if this is a player then grab its eye... + if(sensor->getType() & PlayerObjectType) + { + AssertFatal(dynamic_cast(sensor), "Invalid player object."); + + MatrixF eye; + sensorShape->getEyeTransform(&eye); + eye.getColumn(3, &sensorPos); + } + else + sensorPos = sensor->getBoxCenter(); + + // jams but is visible? + if(jumpNoDetect) + goto nodetect; + + // see if the sensor detects stuff: + if(!sensorData->detects) + goto nodetect; + + U32 *mask; + if(sensorData->detectsActiveJammed) // everything + mask = &activeJamVisMask; + else if(sensorData->detectsCloaked) // all but motion + mask = &cloakVisMask; + else if(sensorData->detectsPassiveJammed) // all but motion and los + mask = &passiveJamVisMask; + else + mask = &baseVisMask; + + // see if we can skip this one: + if((*mask & sensorMask) && (pinged == sensorData->detectionPings)) + goto nodetect; + + targetVec = targetPos - sensorPos; + + // check distance: + if(targetVec.isZero()) + continue; + + // uncapped cylinder + if(Point2F(targetVec.x, targetVec.y).lenSquared() > sensorData->detectRSquared) + goto nodetect; + +// normal sphere +// if(targetVec.lenSquared() > sensorData->detectRSquared) +// goto nodetect; + + // minvel: + if(sensorData->detectMinVelocity != 0.f) + if(target->getVelocity().lenSquared() < sensorData->detectMinVSquared) + goto nodetect; + + // fov: + if(sensorData->detectsFOVOnly) + { + MatrixF camMat; + sensorShape->getEyeTransform(&camMat); + + VectorF camDir; + camMat.mulV(VectorF(0,1,0), &camDir); + targetVec.normalize(); + + F32 dot = mClampF(mDot(targetVec, camDir), -1.f, 1.f); + + // check if interested in projected fov through this object + if(sensorData->useObjectFOV) + { + F32 objectFov = mDegToRad(sensorShape->getCameraFov()); + F32 halfFovCos = mCos(objectFov / 2.f); + if(dot < halfFovCos) + goto nodetect; + + if(sensorData->detectFOVPercent != 0.f) + { + F32 objRadius = target->getWorldSphere().radius; + F32 distance = Point3F(targetPos - sensorPos).len(); + + F32 projRadius = distance * mTan(objectFov / 2.f); + + if(((objRadius / projRadius) * 100.f) < sensorData->detectFOVPercent) + goto nodetect; + } + } + else + if(dot < sensorData->halfFovCos) + goto nodetect; + } + + // los: + if(sensorData->detectsUsingLOS) + { + testedLOS = true; + hasLOS = testLOS(sensor, sensorPos, target, targetPos); + if(!hasLOS) + goto nodetect; + } + + // it's detected + *mask |= sensorMask; + + // friendly do not ping + if(sensorData->detectionPings && !(sensorInfo->sensorFriendlyMask & (1 << targetInfo->sensorGroup))) + pinged = true; + +nodetect: + + // early out? + if(!sensorData->jams || (jammed && enemyJammed)) + continue; + + if(sensorInfo->sensorGroup == targetInfo->sensorGroup) + { + if(jammed) + continue; + } + else + { + if(enemyJammed && sensorData->jamsOnlyGroup) + continue; + } + + // normal sphere + if((targetPos - sensorPos).lenSquared() > sensorData->jamRSquared) + continue; + + // check los + if(sensorData->jamsUsingLOS) + { + if(!testedLOS) + hasLOS = testLOS(sensor, sensorPos, target, targetPos); + + if(!hasLOS) + continue; + } + + // set the jammed state + if(sensorInfo->sensorGroup == targetInfo->sensorGroup) + jammed = true; + else + { + jammed = !sensorData->jamsOnlyGroup; + enemyJammed = true; + } + } + + // check cloaked/passiveJammed: only ShapeBase objects + bool cloaked = false; + bool passiveJammed = false; + if(dynamic_cast(target)) + { + cloaked = (static_cast(target))->getCloakedState(); + passiveJammed = (static_cast(target))->getPassiveJamState(); + } + + // check what could detect it: active->cloaked->passive->base + U32 visMask; + if(jammed) + visMask = activeJamVisMask; + else if(cloaked) + visMask = activeJamVisMask | cloakVisMask; + else if(passiveJammed) + visMask = activeJamVisMask | cloakVisMask | passiveJamVisMask; + else + visMask = activeJamVisMask | cloakVisMask | passiveJamVisMask | baseVisMask; + + visMask |= targetInfo->sensorAlwaysVisMask; + visMask &= ~targetInfo->sensorNeverVisMask; + + targetInfo->sensorVisMask = visMask; + targetInfo->sensorFlags = 0; + if(pinged) + targetInfo->sensorFlags |= TargetInfo::SensorPinged; + + // if jammed, then notify the shapebase object + if(jammed || enemyJammed) + { + if(jammed) + targetInfo->sensorFlags |= TargetInfo::SensorJammed; + + if(enemyJammed) + { + targetInfo->sensorFlags |= TargetInfo::EnemySensorJammed; + + if(dynamic_cast(static_cast(targetInfo->targetObject))) + { + ShapeBase * shape = static_cast(static_cast(targetInfo->targetObject)); + + // reason gets passed down into script.. (shapebase actually doesnt do anything) + shape->forceUncloak("jammed"); + } + } + } + + for(U32 j = 0; j < mSensorGroupCount; j++, visMask >>= 1) + mSensorInfoArray[j].setSensorVisible(index, (visMask & 1)); + + objectCount++; + lastSensed = i; + if(objectCount >= pingCount) + break; + } + mLastSensedObject = lastSensed; +} + +//------------------------------------------------------------------------------ +// debug list control: fills with target info +//------------------------------------------------------------------------------ +#ifdef DEBUG +#include "gui/guiTextListCtrl.h" + +class GuiTargetManagerListCtrl : public GuiTextListCtrl +{ + private: + typedef GuiTextListCtrl Parent; + bool mServerTargets; + + public: + + enum { + NumCategories = 14 + }; + + GuiTargetManagerListCtrl(); + void onPreRender(); + + static void initPersistFields(); + static void consoleInit(); + + DECLARE_CONOBJECT(GuiTargetManagerListCtrl); +}; +IMPLEMENT_CONOBJECT(GuiTargetManagerListCtrl); + +GuiTargetManagerListCtrl::GuiTargetManagerListCtrl() +{ + mServerTargets = true; +} + +void GuiTargetManagerListCtrl::onPreRender() +{ + RectI bounds = mBounds; + + clear(); + + // add all the entries + addEntry(0, "ID\tName\tType\tVoice\tSkin\tPitch\tGroup\tObj\tData\tSensor\tVisMask\tAlways\tNever\tFriend\tFlags"); + + for(U32 i = 32; i < TargetManager::MaxTargets; i++) + { + TargetInfo * targInfo = mServerTargets ? gTargetManager->getServerTarget(i) : gTargetManager->getClientTarget(i); + + if(targInfo->allocated) + { + char buf[1024]; + buf[0] = 0; + + dSprintf(buf, sizeof(buf), + "%d\t%s\t%s\t%s\t%s\t%f\t%d\t%d\t%s\t%s\t%x\t%x\t%x\t%x\t%x", i, + gNetStringTable->lookupString(targInfo->nameTag), + gNetStringTable->lookupString(targInfo->typeTag), + gNetStringTable->lookupString(targInfo->voiceTag), + gNetStringTable->lookupString(targInfo->skinTag), + targInfo->voicePitch, + targInfo->sensorGroup, + bool(targInfo->targetObject) ? targInfo->targetObject->getId() : 0, + bool(targInfo->shapeBaseData) ? targInfo->shapeBaseData->getName() : "", + bool(targInfo->sensorData) ? targInfo->sensorData->getName() : "", + targInfo->sensorVisMask, + targInfo->sensorAlwaysVisMask, + targInfo->sensorNeverVisMask, + targInfo->sensorFriendlyMask, + targInfo->sensorFlags); + + addEntry(i, buf); + } + } + + resize(bounds.point, mBounds.extent); + Parent::onPreRender(); +} + +void GuiTargetManagerListCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("serverTargets", TypeBool, Offset(mServerTargets, GuiTargetManagerListCtrl)); +} + +static S32 cGetNumColumns(SimObject *, S32, const char **) +{ + return(GuiTargetManagerListCtrl::NumCategories); +} + +void GuiTargetManagerListCtrl::consoleInit() +{ + Con::addCommand("GuiTargetManagerListCtrl", "getNumColumns", cGetNumColumns, "ctrl.getNumColumns()", 2, 2); +} +#endif diff --git a/game/targetManager.h b/game/targetManager.h new file mode 100644 index 0000000..d4eeea6 --- /dev/null +++ b/game/targetManager.h @@ -0,0 +1,367 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TARGETMANAGER_H_ +#define _TARGETMANAGER_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _DNET_H_ +#include "core/dnet.h" +#endif +#ifndef _AUDIO_H_ +#include "audio/audio.h" +#endif + +struct SensorData; +class ResizeBitStream; +class BitStream; +struct ShapeBaseData; +class ShapeBase; + +struct TargetInfo +{ + void clear(bool clearFlags = true) { + nameTag = 0; + skinTag = 0; + skinPrefTag = 0; + voiceTag = 0; + typeTag = 0; + voicePitch = 1.0; + + sNameTag = 0; + sSkinTag = 0; + sSkinPrefTag = 0; + sVoiceTag = 0; + sTypeTag = 0; + + sensorGroup = 0; + allocated = false; + renderFlags = 0; + + sensorVisMask = 0; + sensorAlwaysVisMask = 0; + sensorNeverVisMask = 0; + sensorFriendlyMask = 0; + + // clients can get out of sync if these get cleared + // : a freed target can be reallocated and sensed before the connection + // : has a chance to update + if(clearFlags) + sensorFlags = 0; + + targetObject = 0; + sensorData = 0; + shapeBaseData = 0; + } + + enum { + HudRenderStart = 0, + NumHudRenderImages = 8, + HudRenderMask = BIT(NumHudRenderImages) - 1, + CommanderListRender = BIT(NumHudRenderImages), + NumRenderBits = NumHudRenderImages + 1, + }; + + enum { + VisibleToSensor = BIT(0), + SensorPinged = BIT(1), + SensorJammed = BIT(2), + EnemySensorJammed = BIT(3), + SensorCloaked = BIT(4), + }; + + // Client/Server + U16 nameTag; + U16 skinTag; + U16 skinPrefTag; + U16 voiceTag; + U16 typeTag; + F32 voicePitch; + + U16 sNameTag; // source tag (pre translate) + U16 sSkinTag; // used for writing the demo start + U16 sSkinPrefTag; + U16 sVoiceTag; // block + U16 sTypeTag; + + U32 sensorGroup; + U32 renderFlags; + + bool allocated; + + SimObjectPtr targetObject; + SimObjectPtr shapeBaseData; + + // Server + U32 sensorVisMask; + U32 sensorAlwaysVisMask; + U32 sensorNeverVisMask; + U32 sensorFriendlyMask; + U32 sensorFlags; + SimObjectPtr sensorData; +}; + +// Client target notification system +class TargetManagerNotify +{ + public: + TargetManagerNotify(); + ~TargetManagerNotify(); + + virtual void targetAdded(U32) {} + virtual void targetRemoved(U32) {} + virtual void targetChanged(U32) {} + virtual void targetsCleared() {} +}; + +struct TargetManager +{ + // client target notification system + Vector notifyList; + void addNotify(TargetManagerNotify*); + void removeNotify(TargetManagerNotify*); + void notifyTargetAdded(U32); + void notifyTargetRemoved(U32); + void notifyTargetChanged(U32); + void notifyTargetsCleared(); + + enum { + MaxTargets = 512, + TargetIdBitSize = 9, + TargetFreeMaskSize = MaxTargets >> 5, + }; + struct SensorInfo + { + U32 targetPingMask[TargetFreeMaskSize]; + ColorI groupColor[32]; + static ColorI smDefaultColor; + + void clear(); + void setSensorVisible(S32 targetId, bool vis); + bool getSensorVisible(S32 targetId); + }; + SensorInfo mSensorInfoArray[32]; + U32 mFreeCount; + U32 mLastSensedObject; + U32 mSensorGroupCount; + + U32 mSensorGroupAlwaysVisMask[32]; + U32 mSensorGroupNeverVisMask[32]; + U32 mSensorGroupFriendlyMask[32]; + U32 mSensorGroupListenMask[32]; + + U32 mFreeMask[TargetFreeMaskSize]; + TargetInfo mTargets[MaxTargets]; + + TargetInfo mClientTargets[MaxTargets]; + AUDIOHANDLE mClientAudioHandles[MaxTargets]; + + static void create(); + static void destroy(); + + TargetManager(); + void clear(); + + void writeDemoStartBlock(ResizeBitStream *stream, GameConnection *conn); + void readDemoStartBlock(BitStream *stream, GameConnection *conn); + + void newClient(NetConnection *client); + S32 allocTarget(U32 nameTag, U32 skinTag, U32 voiceTag, U32 typeTag, U32 sensorGroup, U32 dataBlockId, F32 voicePitch, U32 prefSkin); + void freeTarget(S32 targetId); + + void reset(); + void resetClient(NetConnection * con); + + void setTargetObject(TargetInfo * target, GameBase *object); + bool getGameName(S32 target, char * buf, S32 bufSize, bool server); + + S32 getTargetName(S32 target); + void setTargetName(S32 target, U32 nameTag); + S32 getTargetSkin(S32 target); + void setTargetSkin(S32 target, U32 skinTag); + S32 getTargetVoice(S32 target); + void setTargetVoice(S32 target, U32 voiceTag); + F32 getTargetVoicePitch(S32 target); + void setTargetVoicePitch(S32 target, F32 voicePitch); + S32 getTargetType(S32 target); + void setTargetType(S32 target, U32 typeTag); + S32 getTargetSensorGroup(S32 target); + void setTargetSensorGroup(S32 target, U32 sensorGroup); + + U32 getTargetAlwaysVisMask(S32 target); + void setTargetAlwaysVisMask(S32 target, U32 mask); + U32 getTargetNeverVisMask(S32 target); + void setTargetNeverVisMask(S32 target, U32 mask); + U32 getTargetFriendlyMask(S32 target); + void setTargetFriendlyMask(S32 target, U32 mask); + + enum MaskType { + AlwaysVisMask = 0, + NeverVisMask, + FriendlyMask + }; + void updateSensorGroupMask(U32 sensorGroup, U32 mask, U32 maskType); + + U32 getSensorGroupAlwaysVisMask(U32 sensorGroup); + void setSensorGroupAlwaysVisMask(U32 sensorGroup, U32 mask); + U32 getSensorGroupNeverVisMask(U32 sensorGroup); + void setSensorGroupNeverVisMask(U32 sensorGroup, U32 mask); + U32 getSensorGroupFriendlyMask(U32 sensorGroup); + void setSensorGroupFriendlyMask(U32 sensorGroup, U32 mask); + + U32 getSensorGroupListenMask(U32 sensorGroup); + void setSensorGroupListenMask(U32 sensorGroup, U32 mask); + + bool isTargetFriendly(S32 target, U32 sensorGroup); + bool isTargetVisible(S32 target, U32 sensorGroup); + + S32 getSensorGroupCount() {return(mSensorGroupCount);} + void setSensorGroupCount(S32 groupCount); + + void setTargetRenderMask(S32 target, U32 renderMask); + void setTargetShapeBaseData(S32 target, ShapeBaseData * data); + + TargetInfo *getClientTarget(S32 target); + TargetInfo *getServerTarget(S32 target); + void updateTarget(S32 targ, S32 nameTag, S32 skinTag, S32 voiceTag, S32 typeTag, S32 sensorGroup, S32 dataBlockId, S32 render, F32 voicePitch, U32 prefSkin); + void tickSensorState(); + + void clientSensorGroupChanged(NetConnection * con, U32 newGroup); + void setSensorGroupColor(U32 sensorGroup, U32 updateMask, ColorI & color); + ColorI getSensorGroupColor(U32 sensorGroup, U32 colorGroup); + + bool playTargetAudio(S32 targetId, S32 fileTag, AudioDescription * description, bool update); +}; + +inline void TargetManager::setSensorGroupCount(S32 groupCount) +{ + AssertFatal(groupCount <= 32, "TargetManager::setSensorGroupCount: invalid group count"); + mSensorGroupCount = groupCount; +} + +extern TargetManager *gTargetManager; + +//-------------------------------------------------------------------------- +class ClientTarget : public SimObject +{ + typedef SimObject Parent; +public: + enum + { + AssignedTask = 0, // what the player is currently doing + PotentialTask, // what people want the player to do + Waypoint, // waypoints + + NumTypes + }; + S32 mType; + S32 mTargetId; + Point3F mLastTargetPos; + StringTableEntry mText; + + ClientTarget(S32 type = -1, S32 targetId = -1, Point3F targetPos = Point3F(0,0,0)); + bool process(); + void setServerTarget(); + static void consoleInit(); + void onRemove(); + void onDie(); + + const char * getText() {return(mText);} + void setText(const char *); + + DECLARE_CONOBJECT(ClientTarget); +}; + +//-------------------------------------------------------------------------- +class HUDTargetListNotify +{ + public: + HUDTargetListNotify(); + ~HUDTargetListNotify(); + + virtual void hudTargetAdded(U32) {} + virtual void hudTargetRemoved(U32) {} + virtual void hudTargetsCleared() {} +}; + +struct HUDTargetList : public TargetManagerNotify +{ + struct Entry + { + SimObjectPtr target; + bool canTimeout; + U32 doneTime; + S32 handle; + }; + enum + { + DefaultTimeout = 2000, + MaxTargets = 32, + + MaxAssignedTasks = 1, + MaxPotentialTasks = 15, + MaxWaypoints = 16, + }; + U32 mNumAssignedTasks; + U32 mNumPotentialTasks; + U32 mNumWaypoints; + + Entry mList[MaxTargets]; + U32 mCount; + static U32 smTargetTimeout; + + HUDTargetList(); + + S32 getAddSlot(ClientTarget * target); + S32 findOldestEntry(S32 type); + void removeEntry(S32 entry); + + bool addTarget(ClientTarget * target, bool canTimeout, U32 doneTime); + S32 getEntryByTarget(ClientTarget *); + void removeEntryByTarget(ClientTarget *); + void update(U32 newTime); + Entry *getEntry(U32 index); + + void removeTargetsOfType(U32 type); + + // TargetManager notification + void targetRemoved(U32); + void targetsCleared(); + + // notification stuff + enum { + FREE_HANDLE = -1 + }; + S32 mHandle[MaxTargets]; + S32 getFreeHandle(); + S32 getHandleIndex(S32 handle); + + Vector hudNotifyList; + void addNotify(HUDTargetListNotify*); + void removeNotify(HUDTargetListNotify*); + void notifyTargetAdded(U32); + void notifyTargetRemoved(U32); + void notifyTargetsCleared(); +}; + +extern HUDTargetList * gTargetList; + +#endif diff --git a/game/tcpObject.cc b/game/tcpObject.cc new file mode 100644 index 0000000..9220863 --- /dev/null +++ b/game/tcpObject.cc @@ -0,0 +1,317 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/simBase.h" +#include "Platform/event.h" +#include "console/consoleInternal.h" +#include "game/tcpObject.h" +#include "Platform/gameInterface.h" +#include "game/tribesGame.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; + } + } +} + +TCPObject::TCPObject() +{ + mBuffer = NULL; + mBufferSize = 0; + mPort = 0; + mTag = InvalidSocket; + mNext = NULL; + mState = Disconnected; +} + +TCPObject::~TCPObject() +{ + disconnect(); + dFree(mBuffer); +} + +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(temp); + dFree(temp); + } + } + else if(i != bufferLen) + { + line[len] = 0; + if(len && line[len-1] == '\r') + line[len-1] = 0; + processLine(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, 3, "onConnectRequest", addrBuf, idBuf); +} + +bool TCPObject::processLine(U8 *line) +{ + Con::executef(this, 2, "onLine", line); + return true; +} + +void TCPObject::onDNSResolved() +{ + mState = DNSResolved; + Con::executef(this, 1, "onDNSResolved"); +} + +void TCPObject::onDNSFailed() +{ + mState = Disconnected; + Con::executef(this, 1, "onDNSFailed"); +} + +void TCPObject::onConnected() +{ + mState = Connected; + Con::executef(this, 1, "onConnected"); +} + +void TCPObject::onConnectFailed() +{ + mState = Disconnected; + Con::executef(this, 1, "onConnectFailed"); +} + +void TCPObject::finishLastLine() +{ + if(mBufferSize) + { + mBuffer[mBufferSize] = 0; + processLine(mBuffer); + dFree(mBuffer); + mBuffer = 0; + mBufferSize = 0; + } +} + +void TCPObject::onDisconnect() +{ + finishLastLine(); + mState = Disconnected; + Con::executef(this, 1, "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::sendTo(mTag, buffer, S32(len)); +} + +static void cTCPObjectSend(SimObject *obj, S32 argc, const char **argv) +{ + TCPObject *tcpo = (TCPObject *) obj; + for(S32 i = 2; i < argc; i++) + tcpo->send((const U8 *) argv[i], dStrlen(argv[i])); +} + +static void cTCPObjectListen(SimObject *obj, S32, const char **argv) +{ + TCPObject *tcpo = (TCPObject *) obj; + tcpo->listen(U32(dAtoi(argv[2]))); +} + +static void cTCPObjectConnect(SimObject *obj, S32, const char **argv) +{ + TCPObject *tcpo = (TCPObject *) obj; + tcpo->connect(argv[2]); +} + +static void cTCPObjectDisconnect(SimObject *obj, S32, const char **) +{ + TCPObject *tcpo = (TCPObject *) obj; + tcpo->disconnect(); +} + +void TCPObject::consoleInit() +{ + Con::addCommand("TCPObject", "listen", cTCPObjectListen, "obj.listen(port)", 3, 3); + Con::addCommand("TCPObject", "send", cTCPObjectSend, "obj.send(string, ...)", 3, 0); + Con::addCommand("TCPObject", "connect", cTCPObjectConnect, "obj.connect(addr)", 3, 3); + Con::addCommand("TCPObject", "disconnect", cTCPObjectDisconnect, "obj.disconnect()", 2, 2); +} + + +void TribesGame::processConnectedReceiveEvent(ConnectedReceiveEvent* event ) +{ + TCPObject *tcpo = TCPObject::find(event->tag); + if(!tcpo) + { + Con::printf("Got bad connected receive event."); + return; + } + U32 size = U32(event->size - ConnectedReceiveEventHeaderSize); + U8 *buffer = event->data; + + while(size) + { + U32 ret = tcpo->onReceive(buffer, size); + AssertFatal(ret <= size, "Invalid return size"); + size -= ret; + buffer += ret; + } +} + +void TribesGame::processConnectedAcceptEvent( ConnectedAcceptEvent* event ) +{ + TCPObject *tcpo = TCPObject::find(event->portTag); + if(!tcpo) + return; + tcpo->onConnectionRequest(&event->address, event->connectionTag); +} + +void TribesGame::processConnectedNotifyEvent( ConnectedNotifyEvent* event ) +{ + TCPObject *tcpo = TCPObject::find(event->tag); + if(!tcpo) + return; + switch(event->state) + { + case ConnectedNotifyEvent::DNSResolved: + tcpo->onDNSResolved(); + break; + case ConnectedNotifyEvent::DNSFailed: + tcpo->onDNSFailed(); + break; + case ConnectedNotifyEvent::Connected: + tcpo->onConnected(); + break; + case ConnectedNotifyEvent::ConnectFailed: + tcpo->onConnectFailed(); + break; + case ConnectedNotifyEvent::Disconnected: + tcpo->onDisconnect(); + break; + } +} diff --git a/game/tcpObject.h b/game/tcpObject.h new file mode 100644 index 0000000..63d543f --- /dev/null +++ b/game/tcpObject.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TCPOBJECT_H_ +#define _TCPOBJECT_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +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(U8 *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); + + static void consoleInit(); +}; + + +#endif // _H_TCPOBJECT_ diff --git a/game/tribes2.aps b/game/tribes2.aps new file mode 100644 index 0000000..b39f3a2 Binary files /dev/null and b/game/tribes2.aps differ diff --git a/game/tribes2.ico b/game/tribes2.ico new file mode 100644 index 0000000..353792a Binary files /dev/null and b/game/tribes2.ico differ diff --git a/game/tribes2.rc b/game/tribes2.rc new file mode 100644 index 0000000..19ec08b --- /dev/null +++ b/game/tribes2.rc @@ -0,0 +1,140 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,21570,0,0 + PRODUCTVERSION 0,21570,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "\0" + VALUE "FileDescription", "Tribes2 Launcher MFC Application\0" + VALUE "FileVersion", "0, 21570, 0, 0\0" + VALUE "InternalName", "Tribes2 Launcher\0" + VALUE "LegalCopyright", "Copyright (C) 2000\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "Tribes2.EXE\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Tribes2 Launcher Application\0" + VALUE "ProductVersion", "0, 21570, 0, 0\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON2 ICON DISCARDABLE "T2.ico" +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/game/tribesGame.h b/game/tribesGame.h new file mode 100644 index 0000000..c8f3036 --- /dev/null +++ b/game/tribesGame.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TRIBESGAME_H_ +#define _TRIBESGAME_H_ + +#ifndef _GAMEINTERFACE_H_ +#include "platform/gameInterface.h" +#endif + +class TribesGame : public GameInterface +{ +public: + void textureKill(); + void textureResurrect(); + void refreshWindow(); + + S32 main(S32 argc, const char **argv); + + void processPacketReceiveEvent(PacketReceiveEvent *event); + void processMouseMoveEvent(MouseMoveEvent *event); + void processInputEvent(InputEvent *event); + void processQuitEvent(); + void processTimeEvent(TimeEvent *event); + void processConsoleEvent(ConsoleEvent *event); + void processConnectedAcceptEvent(ConnectedAcceptEvent *event); + void processConnectedReceiveEvent(ConnectedReceiveEvent *event); + void processConnectedNotifyEvent(ConnectedNotifyEvent *event); +}; + +#endif diff --git a/game/trigger.cc b/game/trigger.cc new file mode 100644 index 0000000..b421a19 --- /dev/null +++ b/game/trigger.cc @@ -0,0 +1,614 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/sceneState.h" +#include "PlatformWin32/platformGL.h" +#include "dgl/dgl.h" +#include "dgl/gTexManager.h" +#include "console/consoleTypes.h" +#include "Collision/boxConvex.h" + +#include "Core/bitStream.h" +#include "Math/mathIO.h" + +#include "game/trigger.h" + +namespace { + +void cTriggerEnterTrigger(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-trigger get here?"); + TriggerData* triggerData = static_cast(obj); + + 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, 3, "onTrigger", Con::getIntArg(trigger->getId()), "1"); +} + +void cTriggerLeaveTrigger(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-triggerdata get here?"); + TriggerData* triggerData = static_cast(obj); + + 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, 3, "onTrigger", Con::getIntArg(trigger->getId()), "0"); + } +} + +void cTriggerTickTrigger(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-trigger get here?"); + TriggerData* triggerData = static_cast(obj); + + 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, 2, "onTriggerTick", Con::getIntArg(trigger->getId())); +} + +S32 cTriggerGetNumObjects(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-trigger get here?"); + Trigger* trigger = static_cast(obj); + + return trigger->getNumTriggeringObjects(); +} + +S32 cTriggerGetObject(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-trigger get here?"); + Trigger* trigger = static_cast(obj); + + S32 index = dAtoi(argv[1]); + + if (index >= trigger->getNumTriggeringObjects() || index < 0) + return -1; + else + return trigger->getObject(U32(index))->getId(); +} + + +} // namespace {} + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(TriggerData); + +TriggerData::TriggerData() +{ + tickPeriodMS = 100; +} + +bool TriggerData::onAdd() +{ + if (!Parent::onAdd()) + return false; + + return true; +} + +void TriggerData::consoleInit() +{ + // Triggering and onTick commands + Con::addCommand("TriggerData", "onEnterTrigger", cTriggerEnterTrigger, "[TriggerData].enterTrigger(Trigger, ObjectId)", 4, 4); + Con::addCommand("TriggerData", "onLeaveTrigger", cTriggerLeaveTrigger, "[TriggerData].leaveTrigger(Trigger, ObjectId)", 4, 4); + Con::addCommand("TriggerData", "onTickTrigger", cTriggerTickTrigger, "[TriggerData].tickTrigger(Trigger)", 3, 3); +} + + +void TriggerData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("tickPeriodMS", TypeS32, Offset(tickPeriodMS, TriggerData)); +} + + +//-------------------------------------------------------------------------- +void TriggerData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(tickPeriodMS); +} + +void TriggerData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&tickPeriodMS); +} + + +//-------------------------------------------------------------------------- +IMPLEMENT_CO_NETOBJECT_V1(Trigger); +Trigger::Trigger() +{ + mNetFlags.clear(Ghostable); + + 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; +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- Polyhedron type and persist field init +// +static const char *getDataTypeTriggerPolyhedron(void *dptr, EnumTable *, BitSet32) +{ + U32 i; + Polyhedron* pPoly = reinterpret_cast(dptr); + + Point3F origin = pPoly->pointList[0]; + U32 currVec = 0; + Point3F vecs[3]; + for (i = 0; i < pPoly->edgeList.size(); i++) { + if (pPoly->edgeList[i].vertex[0] == 0) + vecs[currVec++] = pPoly->pointList[pPoly->edgeList[i].vertex[1]] - pPoly->pointList[0]; + else if (pPoly->edgeList[i].vertex[1] == 0) + vecs[currVec++] = -(pPoly->pointList[0] - pPoly->pointList[pPoly->edgeList[i].vertex[0]]); + } + AssertFatal(currVec == 3, "Error, bad vectors!"); + + 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[1].x, vecs[1].y, vecs[1].z, + vecs[2].x, vecs[2].y, vecs[2].z); + + return retBuf; +} + +static void setDataTypeTriggerPolyhedron(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + if (argc != 1) { + Con::printf("(TypeTriggerPolyhedron) multiple args not supported for polyhedra"); + return; + } + + Point3F origin; + Point3F vecs[3]; + + U32 numArgs = dSscanf(argv[0], "%f %f %f %f %f %f %f %f %f %f %f %f", + &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::initPersistFields() +{ + Parent::initPersistFields(); + + Con::registerType(TypeTriggerPolyhedron, sizeof(Polyhedron), getDataTypeTriggerPolyhedron, setDataTypeTriggerPolyhedron); + addField("polyhedron", TypeTriggerPolyhedron, Offset(mTriggerPolyhedron, Trigger)); +} + + +void Trigger::consoleInit() +{ + //-------------------------------------- Object level commands + + // Manipulation commands + Con::addCommand("Trigger", "getNumObjects", cTriggerGetNumObjects, "[TriggerObject].getNumObjects()", 2, 2); + Con::addCommand("Trigger", "getObject", cTriggerGetObject, "[TriggerObject].getNumObjects(Object Index)", 3, 3); +} + + +//-------------------------------------------------------------------------- +bool Trigger::onAdd() +{ + if(!Parent::onAdd()) + return false; + + Polyhedron temp = mTriggerPolyhedron; + setTriggerPolyhedron(temp); + + addToScene(); + + return true; +} + + +void Trigger::onRemove() +{ + 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) { + for (U32 i = 0; i < mObjects.size(); i++) { + if (pScene == mObjects[i]) { + mObjects.erase(i); + Con::executef(mDataBlock, 3, "onLeaveTrigger", scriptThis(), Con::getIntArg(pScene->getId())); + break; + } + } + } + + Parent::onDeleteNotify(obj); +} + + +//-------------------------------------------------------------------------- +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.min.convolveInverse(mObjScale); + realBox.max.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(GameBase::InitialUpdateMask); + } +} + +void Trigger::setTriggerPolyhedron(const Polyhedron& rPolyhedron) +{ + mTriggerPolyhedron = rPolyhedron; + + if (mTriggerPolyhedron.pointList.size() != 0) { + mObjBox.min.set(1e10, 1e10, 1e10); + mObjBox.max.set(-1e10, -1e10, -1e10); + for (U32 i = 0; i < mTriggerPolyhedron.pointList.size(); i++) { + mObjBox.min.setMin(mTriggerPolyhedron.pointList[i]); + mObjBox.max.setMax(mTriggerPolyhedron.pointList[i]); + } + } else { + mObjBox.min.set(-0.5, -0.5, -0.5); + mObjBox.max.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.min + mWorldBox.max) * 0.5; + VectorF bv = mWorldBox.max - sphere.center; + sphere.radius = bv.len(); + + enter->buildPolyList(&mClippedList, mWorldBox, sphere); + return mClippedList.isEmpty() == false; +} + + +void Trigger::potentialEnterObject(GameBase* enter) +{ + AssertFatal(isServerObject(), "Error, should never be called on the client!"); + + for (U32 i = 0; i < mObjects.size(); i++) { + if (mObjects[i] == enter) + return; + } + + if (testObject(enter) == true) { + mObjects.push_back(enter); + deleteNotify(enter); + + Con::executef(mDataBlock, 3, "onEnterTrigger", scriptThis(), Con::getIntArg(enter->getId())); + } +} + + +void Trigger::processTick(const Move* move) +{ + Parent::processTick(move); + + if (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); + Con::executef(mDataBlock, 3, "onLeaveTrigger", scriptThis(), remove->scriptThis()); + } + } + + if (mObjects.size() != 0) + Con::executef(mDataBlock, 2, "onTickTrigger", scriptThis()); + } else { + mCurrTick += TickMs; + } +} + + +//-------------------------------------------------------------------------- +bool Trigger::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)) { +// SceneRenderImage* image = new SceneRenderImage; +// image->obj = this; +// image->isTranslucent = true; +// image->sortKey = 0.0f; +// state->insertRenderImage(image); +// } + + return false; +} + + +void Trigger::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + mTriggerPolyhedron.render(); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +U32 Trigger::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 i; + 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); + mathWrite(*stream, mObjScale); + + // Write the polyhedron + 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]); + } + + return retMask; +} + +void Trigger::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + U32 i, size; + MatrixF temp; + Point3F tempScale; + Polyhedron tempPH; + + // Transform + stream->readAffineTransform(&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]); + } + + setTriggerPolyhedron(tempPH); + setScale(tempScale); + setTransform(temp); +} + + diff --git a/game/trigger.h b/game/trigger.h new file mode 100644 index 0000000..79c1319 --- /dev/null +++ b/game/trigger.h @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_TRIGGER +#define _H_TRIGGER + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif +#ifndef _MBOX_H_ +#include "Math/mBox.h" +#endif +#ifndef _EARLYOUTPOLYLIST_H_ +#include "Collision/earlyOutPolyList.h" +#endif + +class Convex; + +struct TriggerData: public GameBaseData { + typedef GameBaseData Parent; + + public: + S32 tickPeriodMS; + + TriggerData(); + DECLARE_CONOBJECT(TriggerData); + bool onAdd(); + static void consoleInit(); + 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; + + protected: + bool onAdd(); + void onRemove(); + void onDeleteNotify(SimObject*); + bool onNewDataBlock(GameBaseData* dptr); + + bool testObject(GameBase* enter); + void processTick(const Move*); + + Convex* mConvexList; + void buildConvex(const Box3F& box, Convex* convex); + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + public: + void setTransform(const MatrixF&); + + public: + Trigger(); + ~Trigger(); + + void setTriggerPolyhedron(const Polyhedron&); + + void potentialEnterObject(GameBase*); + U32 getNumTriggeringObjects() const; + GameBase* getObject(const U32); + + DECLARE_CONOBJECT(Trigger); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +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/game/tsStatic.cc b/game/tsStatic.cc new file mode 100644 index 0000000..0d14814 --- /dev/null +++ b/game/tsStatic.cc @@ -0,0 +1,556 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/tsStatic.h" +#include "core/bitStream.h" +#include "dgl/dgl.h" +#include "scenegraph/sceneState.h" +#include "scenegraph/sceneGraph.h" +#include "math/mathIO.h" +#include "ts/tsShapeInstance.h" +#include "console/consoleTypes.h" +#include "game/shapeBase.h" +#include "game/shadow.h" +#include "scenegraph/detailManager.h" + +IMPLEMENT_CO_NETOBJECT_V1(TSStatic); + + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +TSStatic::TSStatic() +{ + mNetFlags.set(Ghostable | ScopeAlways); + + mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType; + + mShapeName = ""; + mShapeInstance = NULL; + mShadow = NULL; + + for (U32 i = 0; i < MaxCollisionShapes; i++) { + mCollisionDetails[i] = -1; + mLOSDetails[i] = -1; + } + + mConvexList = new Convex; +} + +TSStatic::~TSStatic() +{ + delete mConvexList; + mConvexList = NULL; + delete mShadow; +} + +//-------------------------------------------------------------------------- +void TSStatic::initPersistFields() +{ + Parent::initPersistFields(); + + addField("shapeName", TypeString, Offset(mShapeName, TSStatic)); +} + + +void TSStatic::consoleInit() +{ + // +} + +//-------------------------------------------------------------------------- +bool TSStatic::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (!mShapeName || mShapeName[0] == '\0') { + Con::errorf("TSStatic::onAdd: no shape name!"); + return false; + } + mShapeHash = _StringTable::hashString(mShapeName); + + char fullName[256]; + dSprintf(fullName,sizeof(fullName),"shapes/%s",mShapeName); + mShape = ResourceManager->load(fullName); + + if (bool(mShape) == false) { + AssertFatal(false, avar("TSStatic::onAdd: unable to load shape: %s", mShapeName)); + Con::errorf("TSStatic::onAdd: unable to load shape: %s", mShapeName); + return false; + } + + mObjBox = mShape->bounds; + resetWorldBox(); + setRenderTransform(mObjToWorld); + + mShapeInstance = new TSShapeInstance(mShape, isClientObject()); + + // Scan out the collision hulls... + U32 i; + for (i = 0; i < MaxCollisionShapes; i++) { + char buff[128]; + dSprintf(buff, sizeof(buff), "Collision-%d", i + 1); + mCollisionDetails[i] = mShape->findDetail(buff); + mLOSDetails[i] = mCollisionDetails[i]; + } + + // Compute the hull accelerators (actually, just force the shape to compute them) + for (i = 0; i < MaxCollisionShapes; i++) { + if (mCollisionDetails[i] != -1) { + mShapeInstance->getShape()->getAccelerator(mCollisionDetails[i]); + } + } + + addToScene(); + + return true; +} + + +void TSStatic::onRemove() +{ + mConvexList->nukeList(); + + removeFromScene(); + + delete mShapeInstance; + mShapeInstance = NULL; + delete mShadow; + mShadow = NULL; + + Parent::onRemove(); +} + + + +//-------------------------------------------------------------------------- +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)) { + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 dist = cameraOffset.len(); + if (dist < 0.01) + dist = 0.01; + F32 fogAmount = state->getHazeAndFog(dist,cameraOffset.z); + if (fogAmount>0.99f) + return false; + + F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); + DetailManager::selectPotentialDetails(mShapeInstance,dist,invScale); + if (mShapeInstance->getCurrentDetail()<0) + return false; + + if (mShapeInstance->hasSolid()) + { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = false; + image->textureSortKey = mShapeHash; + state->insertRenderImage(image); + } + + if (mShapeInstance->hasTranslucency()) + { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::Point; + image->textureSortKey = mShapeHash; + state->setImageRefPoint(this, image); + + state->insertRenderImage(image); + } + } + + return false; +} + + +void TSStatic::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); +} + + +void TSStatic::renderObject(SceneState* state, SceneRenderImage* image) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + if (!DetailManager::selectCurrentDetail(mShapeInstance)) + // we were detailed out + return; + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + installLights(); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + // This is something of a hack, but since the 3space objects don't have a + // clear conception of texels/meter like the interiors do, we're sorta + // stuck. I can't even claim this is anything more scientific than eyeball + // work. DMM + F32 axis = (getObjBox().len_x() + getObjBox().len_y() + getObjBox().len_z()) / 3.0; + F32 dist = (getRenderWorldBox().getClosestPoint(state->getCameraPosition()) - state->getCameraPosition()).len(); + if (dist != 0) + { + F32 projected = dglProjectRadius(dist, axis) / 350; + if (projected < (1.0 / 16.0)) + { + TextureManager::setSmallTexturesActive(true); + } + } + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + // RENDER CODE HERE + mShapeInstance->setAlphaAlways(1.0); + + Point3F cameraOffset; + mObjToWorld.getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + dist = cameraOffset.len(); + F32 fogAmount = state->getHazeAndFog(dist,cameraOffset.z); + + if (image->isTranslucent == true) + { + TSShapeInstance::smNoRenderNonTranslucent = true; + TSShapeInstance::smNoRenderTranslucent = false; + } + else + { + TSShapeInstance::smNoRenderNonTranslucent = false; + TSShapeInstance::smNoRenderTranslucent = true; + } + + mShapeInstance->setupFog(fogAmount,state->getFogColor()); + mShapeInstance->animate(); + mShapeInstance->render(); + + renderShadow(dist,fogAmount); + + TSShapeInstance::smNoRenderNonTranslucent = false; + TSShapeInstance::smNoRenderTranslucent = false; + TextureManager::setSmallTexturesActive(false); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + uninstallLights(); + dglSetCanonicalState(); + + if (GameBase::gShowBoundingBox) { + glDisable(GL_DEPTH_TEST); + Point3F box; + glPushMatrix(); + dglMultMatrix(&getTransform()); + box = (mObjBox.min + mObjBox.max) * 0.5; + glTranslatef(box.x,box.y,box.z); + box = (mObjBox.max - mObjBox.min) * 0.5; + glScalef(box.x,box.y,box.z); + glColor3f(1, 0, 1); + ShapeBase::wireCube(Point3F(1,1,1),Point3F(0,0,0)); + glPopMatrix(); + + glPushMatrix(); + box = (mWorldBox.min + mWorldBox.max) * 0.5; + glTranslatef(box.x,box.y,box.z); + box = (mWorldBox.max - mWorldBox.min) * 0.5; + glScalef(box.x,box.y,box.z); + glColor3f(0, 1, 1); + ShapeBase::wireCube(Point3F(1,1,1),Point3F(0,0,0)); + glPopMatrix(); + glEnable(GL_DEPTH_TEST); + } + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +void TSStatic::renderShadow(F32 dist, F32 fogAmount) +{ + if (Shadow::getGlobalShadowDetailLevel()getShape()->subShapeFirstTranslucentObject.empty() || mShapeInstance->getShape()->subShapeFirstTranslucentObject[0]==0) + return; + if (!mShadow) + mShadow = new Shadow(); + mShadow->setGeneric(Shadow::getGlobalShadowDetailLevel() < StaticShape_GenericShadowLevel); + mShadow->setMoving(false); + mShadow->setAnimating(false); + + Point3F lightDir(0.57f,0.57f,-0.57f); + 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 + if (Shadow::getGlobalShadowDetailLevel() < StaticShape_GenericShadowLevel) + pos *= 0.5f; + pos.convolve(mObjScale); + mObjToWorld.mulP(pos); + + // pos is where shadow will be centered (in world space) + mShadow->setRadius(mShapeInstance,mObjScale); + if (!mShadow->prepare(pos,lightDir,shadowLen,mObjScale,dist,fogAmount,mShapeInstance)) + return; + + F32 maxScale = getMax(mObjScale.x,getMax(mObjScale.y,mObjScale.z)); + + if (mShadow->needBitmap()) + { + mShadow->beginRenderToBitmap(); + mShadow->selectShapeDetail(mShapeInstance,dist,maxScale); + mShadow->renderToBitmap(mShapeInstance,mObjToWorld,pos,mObjScale); + mShadow->endRenderToBitmap(); + } + + mShadow->render(); +} + +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); + + 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(); +} + + +//-------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +bool TSStatic::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +{ + if (mShapeInstance) { + RayInfo shortest; + shortest.t = 1e8; + + info->object = NULL; + for (U32 i = 0; i < MaxCollisionShapes; i++) { + if (mLOSDetails[i] != -1) { + mShapeInstance->animate(mLOSDetails[i]); + if (mShapeInstance->castRay(start, end, info, mLOSDetails[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 TSStatic::buildPolyList(AbstractPolyList* polyList, const Box3F &, const SphereF &) +{ + if (mShapeInstance) { + bool ret = false; + + polyList->setTransform(&mObjToWorld, mObjScale); + polyList->setObject(this); + for (U32 i = 0; i < MaxCollisionShapes; i++) { + if (mCollisionDetails[i] != -1) { + mShapeInstance->buildPolyList(polyList, mCollisionDetails[i]); + ret = true; + } + } + + return ret; + } + + return false; +} + + +void TSStatic::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.min.convolveInverse(mObjScale); + realBox.max.convolveInverse(mObjScale); + + if (realBox.isOverlapped(getObjBox()) == false) + return; + + for (U32 i = 0; i < MaxCollisionShapes; i++) { + if (mCollisionDetails[i] != -1) { + // 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() == TSStaticConvexType && + (static_cast(itr->mConvex)->pStatic == this && + static_cast(itr->mConvex)->hullId == i)) { + cc = itr->mConvex; + break; + } + } + if (cc) + continue; + + // Create a new convex. + TSStaticConvex* cp = new TSStaticConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->mObject = this; + cp->pStatic = this; + cp->hullId = i; + cp->box = mObjBox; + } + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +Box3F TSStaticConvex::getBoundingBox() const +{ + return getBoundingBox(mObject->getTransform(), mObject->getScale()); +} + +Box3F TSStaticConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const +{ + Box3F newBox = box; + newBox.min.convolve(scale); + newBox.max.convolve(scale); + mat.mul(newBox); + return newBox; +} + +Point3F TSStaticConvex::support(const VectorF& v) const +{ + TSShape::ConvexHullAccelerator* pAccel = + pStatic->mShapeInstance->getShape()->getAccelerator(pStatic->mCollisionDetails[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 TSStaticConvex::getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf) +{ + cf->material = 0; + cf->object = mObject; + + TSShape::ConvexHullAccelerator* pAccel = + pStatic->mShapeInstance->getShape()->getAccelerator(pStatic->mCollisionDetails[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 TSStaticConvex::getPolyList(AbstractPolyList* list) +{ + list->setTransform(&pStatic->getTransform(), pStatic->getScale()); + list->setObject(pStatic); + + pStatic->mShapeInstance->animate(pStatic->mCollisionDetails[hullId]); + pStatic->mShapeInstance->buildPolyList(list, pStatic->mCollisionDetails[hullId]); +} + + diff --git a/game/tsStatic.h b/game/tsStatic.h new file mode 100644 index 0000000..2a494b7 --- /dev/null +++ b/game/tsStatic.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSSTATIC_H_ +#define _TSSTATIC_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif +#ifndef _RESMANAGER_H_ +#include "core/resManager.h" +#endif + +class TSShape; +class TSShapeInstance; +class TSStatic; +class Shadow; + +//-------------------------------------------------------------------------- +class TSStaticConvex : public Convex +{ + typedef Convex Parent; + friend class TSStatic; + + protected: + TSStatic* pStatic; + + public: + U32 hullId; + Box3F box; + + public: + TSStaticConvex() { mType = TSStaticConvexType; } + TSStaticConvex(const TSStaticConvex& cv) { + mType = TSStaticConvexType; + mObject = cv.mObject; + pStatic = cv.pStatic; + 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 TSStatic : public SceneObject +{ + typedef SceneObject Parent; + friend class TSStaticConvex; + + static U32 smUniqueIdentifier; + + enum Constants { + MaxCollisionShapes = 8 + }; + + protected: + bool onAdd(); + void onRemove(); + + // Collision + 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); + protected: + Convex* mConvexList; + + StringTableEntry mShapeName; + U32 mShapeHash; + Resource mShape; + TSShapeInstance* mShapeInstance; + Shadow * mShadow; + + S32 mCollisionDetails[MaxCollisionShapes]; + S32 mLOSDetails[MaxCollisionShapes]; + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + void renderShadow(F32 dist, F32 fogAmount); + void setTransform(const MatrixF&); + + public: + TSStatic(); + ~TSStatic(); + + DECLARE_CONOBJECT(TSStatic); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); +}; + +#endif // _H_TSSTATIC + diff --git a/game/turret.cc b/game/turret.cc new file mode 100644 index 0000000..39b794e --- /dev/null +++ b/game/turret.cc @@ -0,0 +1,1580 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/turret.h" +#include "core/bitStream.h" +#include "game/gameConnection.h" +#include "ts/tsShapeInstance.h" +#include "console/consoleTypes.h" +#include "game/projectile.h" +#include "ai/graphMath.h" +#include "game/shockwave.h" + +IMPLEMENT_CO_DATABLOCK_V1(TurretData); +IMPLEMENT_CO_DATABLOCK_V1(TurretImageData); +IMPLEMENT_CO_NETOBJECT_V1(Turret); + +const U32 Turret::csmActiveScanMask = (TerrainObjectType | + InteriorObjectType | + VehicleObjectType); + +const F32 Turret::csmFullyDeactivated = 0.0; +const F32 Turret::csmFullyActivated = 1.0; +const F32 Turret::csmPhiNull = 0.0; +const F32 Turret::csmThetaNull = 90.0; + +const U32 Turret::csmDefaultDeactivateDelay = 1000; +const U32 Turret::csmDefaultThinkTime = 200; +const F32 Turret::csmDefaultActivationSpeed = 1.0f; +const F32 Turret::csmDefaultPhiSpeed = 180.0f; +const F32 Turret::csmDefaultThetaSpeed = 45.0f; +const F32 Turret::csmDefaultAttackRadius = 40.0f; + +const F32 sgBigClunkyMultiplicationFactor = 40.0f; + +namespace { + +void cTurretSetSkill(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-turret get here?"); + Turret* pTurret = static_cast(obj); + + F32 skillLevel = dAtof(argv[2]); + pTurret->setSkill(skillLevel); +} + +bool cTurretSetTarget(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-turret get here?"); + Turret* pTurret = static_cast(obj); + + S32 targetId = dAtoi(argv[2]); + ShapeBase* pTarget = NULL; + Sim::findObject(targetId, pTarget); + + return pTurret->setTarget(pTarget); +} + +void cTurretClearTarget(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-turret get here?"); + Turret* pTurret = static_cast(obj); + + pTurret->setTarget(NULL); +} + + +S32 cTurretGetTarget(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-turret get here?"); + Turret* pTurret = static_cast(obj); + + return pTurret->getTargetId(); +} + +bool cTurretIsValidTarget(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-turret get here?"); + Turret* pTurret = static_cast(obj); + + S32 targetId = dAtoi(argv[2]); + ShapeBase* pTarget = NULL; + Sim::findObject(targetId, pTarget); + + if (pTarget) + return pTurret->isValidTarget(pTarget); + else + return false; +} + + +bool cTurretBarrelReplace(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-turret get here?"); + Turret* pTurret = static_cast(obj); + + S32 targetId = dAtoi(argv[2]); + ShapeBase* pTarget = NULL; + Sim::findObject(targetId, pTarget); + + if (pTarget) + return pTurret->initiateReplace(pTarget); + else + return false; +} + +static void cTurretSetAutoFire(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did a non-turret get here?"); + Turret* pTurret = static_cast(obj); + + bool status = (!dStricmp(argv[2], "true")) || (dAtoi(argv[2]) > 0); + pTurret->setAutoFire(status); +} + +} // namespace {} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +TurretData::TurretData() +{ + thetaMin = 45; + thetaMax = 135; + thetaNull = -1; + + neverUpdateControl = false; + + primaryAxis = ZAxis; +} + +TurretData::~TurretData() +{ + +} + + +//-------------------------------------------------------------------------- +static EnumTable::Enums sgPrimaryAxisEnums[] = +{ + { TurretData::YAxis, "yaxis" }, + { TurretData::RevYAxis, "revyaxis" }, + { TurretData::ZAxis, "zaxis" }, + { TurretData::RevZAxis, "revzaxis" } +}; +static EnumTable sgPrimaryAxisTable(4, &sgPrimaryAxisEnums[0]); + +void TurretData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("thetaMin", TypeF32, Offset(thetaMin, TurretData)); + addField("thetaMax", TypeF32, Offset(thetaMax, TurretData)); + addField("thetaNull", TypeF32, Offset(thetaNull, TurretData)); + addField("neverUpdateControl", TypeBool, Offset(neverUpdateControl, TurretData)); + addField("primaryAxis", TypeEnum, Offset(primaryAxis, TurretData), 1, &sgPrimaryAxisTable); +} + +bool TurretData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (bool(shape)) { + activateSeq = shape->findSequence("activate"); + elevateSeq = shape->findSequence("elevate"); + turnSeq = shape->findSequence("turn"); + + if (activateSeq == -1 || elevateSeq == -1 || turnSeq == -1) { + Con::errorf(ConsoleLogEntry::General, + "TurretData(%s)::onAdd: shape %s does not have all sequences necessary (turn, elevate, activate)", + getName(), shapeName); + return false; + } else { + if (thetaMin < 0.0f || thetaMin > 90.0f) { + Con::warnf(ConsoleLogEntry::General, + "TurretData(%s)::onAdd: thetaMin must be in the range [0, 90]", getName()); + thetaMin = thetaMin < 0.0 ? 0.0 : 90.0f; + } + if (thetaMax < 90.0f || thetaMax > 180.0f) { + Con::warnf(ConsoleLogEntry::General, + "TurretData(%s)::onAdd: thetaMax must be in the range [90, 180]", getName()); + thetaMax = thetaMax < 90.0 ? 90.0 : 180.0f; + } + + return true; + } + } + + Con::errorf(ConsoleLogEntry::General, "TurretData(%s)::onAdd: must have a shape: %s not found", getName(), shapeName); + return false; +} + + +//-------------------------------------------------------------------------- +void TurretData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(thetaMin); + stream->write(thetaMax); + stream->write(thetaNull); + stream->writeFlag(neverUpdateControl); + stream->writeRangedU32(primaryAxis, __FirstValidAxis, __LastValidAxis); +} + +void TurretData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&thetaMin); + stream->read(&thetaMax); + stream->read(&thetaNull); + neverUpdateControl = stream->readFlag(); + primaryAxis = (PrimaryAxis)stream->readRangedU32(__FirstValidAxis, __LastValidAxis); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +TurretImageData::TurretImageData() +{ + activationMS = 1000; + deactivateDelayMS = 1000; + thinkTimeMS = 100; + attackRadius = 40.0; + degPerSecTheta = 45.0; + degPerSecPhi = 180.0; + dontFireInsideDamageRadius = false; + damageRadius = 0.0f; + muzzleFlash = NULL; + muzzleFlashID = 0; +} + +TurretImageData::~TurretImageData() +{ + +} + + +//-------------------------------------------------------------------------- +void TurretImageData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("activationMS", TypeS32, Offset(activationMS, TurretImageData)); + addField("deactivateDelayMS", TypeS32, Offset(deactivateDelayMS, TurretImageData)); + addField("thinkTimeMS", TypeS32, Offset(thinkTimeMS, TurretImageData)); + addField("degPerSecTheta", TypeF32, Offset(degPerSecTheta, TurretImageData)); + addField("degPerSecPhi", TypeF32, Offset(degPerSecPhi, TurretImageData)); + addField("attackRadius", TypeF32, Offset(attackRadius, TurretImageData)); + addField("damageRadius", TypeF32, Offset(damageRadius, TurretImageData)); + addField("dontFireInsideDamageRadius", TypeBool, Offset(damageRadius, TurretImageData)); + addField("muzzleFlash", TypeShockwaveDataPtr, Offset(muzzleFlash, TurretImageData)); +} + + +//-------------------------------------------------------------------------- +bool TurretImageData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (activationMS < TickMs || activationMS > 5000) { + Con::warnf(ConsoleLogEntry::General, "TurretImageData(%s)::onAdd: activationMS must be in the range [%d, 5000]", getName(), TickMs); + activationMS = activationMS < TickMs ? TickMs : 5000; + } + activationMS = (activationMS >> 5) << 5; + + if (deactivateDelayMS < TickMs || deactivateDelayMS > 5000) { + Con::warnf(ConsoleLogEntry::General, "TurretImageData(%s)::onAdd: deactivateDelayMS must be in the range [%d, 5000]", getName(), TickMs); + deactivateDelayMS = deactivateDelayMS < TickMs ? TickMs : 5000; + } + deactivateDelayMS = (deactivateDelayMS >> 5) << 5; + + if (thinkTimeMS < TickMs || thinkTimeMS > 5000) { + Con::warnf(ConsoleLogEntry::General, "TurretImageData(%s)::onAdd: thinkTimeMS must be in the range [%d, 5000]", getName(), TickMs); + thinkTimeMS = thinkTimeMS < TickMs ? TickMs : 5000; + } + if (degPerSecTheta < 1.0f || degPerSecTheta > 1080.0f) { + Con::warnf(ConsoleLogEntry::General, "TurretImageData(%s)::onAdd: degPerSecTheta must be in the range [1, 1080]", getName()); + degPerSecTheta = degPerSecTheta < 1.0 ? 1.0 : 1080.0f; + } + degPerSecTheta = U32(degPerSecTheta); + + if (degPerSecPhi < 1.0f || degPerSecPhi > 1080.0f) { + Con::warnf(ConsoleLogEntry::General, "TurretImageData(%s)::onAdd: degPerSecPhi must be in the range [1, 1080]", getName()); + degPerSecPhi = degPerSecPhi < 1.0 ? 1.0 : 1080.0f; + } + degPerSecPhi = U32(degPerSecPhi); + if (attackRadius < 10.0f || attackRadius > 1000) { + Con::warnf(ConsoleLogEntry::General, "TurretImageData(%s)::onAdd: attackRadius must be in the range [10, 1000]", getName()); + attackRadius = attackRadius < 10.0 ? 10.0 : 1000.0f; + } + + // Make activation time a multiple of a tick + activationMS = (activationMS + TickMs - 1) & ~(TickMs - 1); + + // Make ThinkTime a multiple of a tick + thinkTimeMS = (thinkTimeMS + TickMs - 1) & ~(TickMs - 1); + +if( muzzleFlash ) +{ + int test = 1; +} + + if (!muzzleFlash && muzzleFlashID) { + if (!Sim::findObject( muzzleFlashID, muzzleFlash )) { + Con::errorf( ConsoleLogEntry::General, "TurretImageData::onAdd: Invalid packet, bad datablockId(shockwave): 0x%x", muzzleFlashID ); + } + } + + return true; +} + +//-------------------------------------------------------------------------- +void TurretImageData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeInt(activationMS >> 5, 8); + stream->writeInt(deactivateDelayMS >> 5, 8); + stream->writeRangedU32(degPerSecTheta,0, 1080); + stream->writeRangedU32(degPerSecPhi,0, 1080); + stream->writeFlag(dontFireInsideDamageRadius); + stream->write(damageRadius); + + if( stream->writeFlag( muzzleFlash ) ) + { + stream->writeRangedU32( muzzleFlash->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } +} + +void TurretImageData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + activationMS = stream->readInt(8) << 5; + deactivateDelayMS = stream->readInt(8) << 5; + degPerSecTheta = stream->readRangedU32(0, 1080); + degPerSecPhi = stream->readRangedU32(0, 1080); + dontFireInsideDamageRadius = stream->readFlag(); + stream->read(&damageRadius); + + if( stream->readFlag() ) + { + muzzleFlashID = (S32) stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +Turret::Turret() +{ + mNetFlags.set(Ghostable); + mTypeMask |= TurretObjectType; + + mCurrState = Dormant; + mActivationLevel = csmFullyDeactivated; + mCurrPhi = csmPhiNull; + mCurrTheta = csmThetaNull; + + mActiveBase = mActivationLevel; + mPhiBase = mCurrPhi; + mThetaBase = mCurrTheta; + mActiveDelta = + mThetaDelta = + mPhiDelta = 0.0; + + mActivateThread = NULL; + mElevateThread = NULL; + mTurnThread = NULL; + + mCurrBarrel = ""; + mSkillLevel = 1.0f; + mAutoFire = true; + + mBarrelDamageThread = NULL; + mDeployThread = NULL; +} + +Turret::~Turret() +{ + // +} + +//-------------------------------------------------------------------------- +void Turret::initPersistFields() +{ + Parent::initPersistFields(); + + addField("initialBarrel", TypeString, Offset(mCurrBarrel, Turret)); +} + + +void Turret::consoleInit() +{ + Con::addCommand("Turret", "setSkill", cTurretSetSkill, "[Turret].setSkill(skill< 0 - 1 >)", 3, 3); + Con::addCommand("Turret", "setTargetObject", cTurretSetTarget, "[Turret].setTargetObject(target id)", 3, 3); + Con::addCommand("Turret", "clearTarget", cTurretClearTarget, "[Turret].clearTarget()", 2, 2); + Con::addCommand("Turret", "getTargetObject", cTurretGetTarget, "[Turret].getTargetObject()", 2, 2); + Con::addCommand("Turret", "isValidTarget", cTurretIsValidTarget, "[Turret].isValidTarget(target id)", 3, 3); + Con::addCommand("Turret", "initiateBarrelSwap", cTurretBarrelReplace, "[Turret].initiateBarrelSwap(engineer id)", 3, 3); + Con::addCommand("Turret", "setAutoFire", cTurretSetAutoFire, "[Turret].setAutoFire(bool)", 3, 3); +} + +//-------------------------------------------------------------------------- +bool Turret::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isServerObject()) { + // If there's a barrel, load it + if (mCurrBarrel && mCurrBarrel[0] != '\0') { + ShapeBaseImageData* pBarrel; + if (Sim::findObject(mCurrBarrel, pBarrel) == true) + mountImage(pBarrel, 0, false, 0); + } + } + + AssertFatal(mShapeInstance != NULL, "Turret::onAdd: must have a shape"); + AssertFatal((mDataBlock->activateSeq != -1 && + mDataBlock->elevateSeq != -1 && + mDataBlock->turnSeq != -1), "Turret::onAdd: must have a valid sequence list"); + + mLastThink = 0; + + return true; +} + + +void Turret::onRemove() +{ + if (mActivateThread) { + mShapeInstance->destroyThread(mActivateThread); + mActivateThread = NULL; + } + if (mElevateThread) { + mShapeInstance->destroyThread(mElevateThread); + mElevateThread = NULL; + } + if (mTurnThread) { + mShapeInstance->destroyThread(mTurnThread); + mTurnThread = NULL; + } + + Parent::onRemove(); +} + + +bool Turret::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +bool Turret::mountImage(ShapeBaseImageData* image,U32 imageSlot,bool loaded,S32 team) +{ + bool ret = Parent::mountImage(image, imageSlot, loaded, team); + + if (ret == true) + mCurrBarrel = image->getName(); + + return true; +} + +bool Turret::unmountImage(U32 imageSlot) +{ + bool ret = Parent::unmountImage(imageSlot); + + if (ret == true) + mCurrBarrel = ""; + return true; +} + +void Turret::setImage(U32 imageSlot, + ShapeBaseImageData* imageData, + U32 skinTag, + bool loaded, + bool ammo, + bool triggerDown, + bool target) +{ + if (isClientObject() && getMountedImage(imageSlot) != NULL && mBarrelDamageThread != NULL) + { + mMountedImageList[imageSlot].shapeInstance->destroyThread(mBarrelDamageThread); + mBarrelDamageThread = NULL; + } + + if (isClientObject() && getMountedImage(imageSlot) != NULL && mDeployThread != NULL) + { + mMountedImageList[imageSlot].shapeInstance->destroyThread(mDeployThread); + mDeployThread = NULL; + } + + Parent::setImage(imageSlot, imageData, skinTag, loaded, ammo, triggerDown, target); + + if (isClientObject() && mMountedImageList[imageSlot].shapeInstance != NULL) + { + S32 seq = mMountedImageList[imageSlot].shapeInstance->getShape()->findSequence("damage"); + if (seq != -1) + { + mBarrelDamageThread = mMountedImageList[imageSlot].shapeInstance->addThread(); + mMountedImageList[imageSlot].shapeInstance->setSequence(mBarrelDamageThread, seq, 0); + updateDamageLevel(); + } + seq = mMountedImageList[imageSlot].shapeInstance->getShape()->findSequence("deploy"); + if (seq != -1) + { + mDeployThread = mMountedImageList[imageSlot].shapeInstance->addThread(); + mMountedImageList[imageSlot].shapeInstance->setSequence(mDeployThread, seq, 0); + } + } +} + +//-------------------------------------------------------------------------- +void Turret::updateDamageLevel() +{ + Parent::updateDamageLevel(); + if (mBarrelDamageThread) { + // mDamage is already 0-1 on the client + F32 level = mDamage / mDataBlock->destroyedLevel; + if (level >= 1.0) + level = 1.0; + mMountedImageList[0].shapeInstance->setPos(mBarrelDamageThread,level); + } +} + +//-------------------------------------------------------------------------- +void Turret::updateState(bool playerControlled) +{ + if (playerControlled) { + mCurrTarget = NULL; + + if (mCurrState == Dormant) { + mCurrState = Activating; + } else if (mCurrState == Activating) { + // Do nothing + } else if (mCurrState == Deactivating) { + if (mActivationLevel == csmFullyActivated) { + if (!mElevateThread || !mTurnThread) { + mElevateThread = mShapeInstance->addThread(); + mTurnThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mElevateThread, mDataBlock->elevateSeq, 0); + mShapeInstance->setSequence(mTurnThread, mDataBlock->turnSeq, 0); + } + + mCurrState = Active; + } else { + mCurrState = Activating; + } + } else if (mCurrState == DeactivateForReplace) { + // Do nothing + } + } else { + if (getMountedImage(0) != NULL) { + // Go to active state if we have a target, otherwise, wait for deactive + // delay to expire, and deactivate... + // + if (mCurrState == Active) { + if (bool(mCurrTarget) == false && mTargetlessTime > getDeactivateDelay()) { + // Deactivate + mCurrState = Deactivating; + } + } + else if (mCurrState == Activating) { + // Do nothing, just wait + } + else if (mCurrState == Deactivating) { + if (bool(mCurrTarget)) { + if (mActivationLevel == csmFullyActivated) { + if (!mElevateThread || !mTurnThread) { + mElevateThread = mShapeInstance->addThread(); + mTurnThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mElevateThread, mDataBlock->elevateSeq, 0); + mShapeInstance->setSequence(mTurnThread, mDataBlock->turnSeq, 0); + } + + mCurrState = Active; + } else { + mCurrState = Activating; + } + } + } + else if (mCurrState == Dormant) { + if (mElevateThread) { + mShapeInstance->destroyThread(mElevateThread); + mElevateThread = NULL; + } + if (mTurnThread) { + mShapeInstance->destroyThread(mTurnThread); + mTurnThread = NULL; + } + if (bool(mCurrTarget)) + mCurrState = Activating; + } else { + // Do nothing + } + } else { + // Go to inactive state + mCurrTarget = NULL; + + if (mCurrState == Dormant || mCurrState == Deactivating) { + // Do nothing, just wait + if (mElevateThread) { + mShapeInstance->destroyThread(mElevateThread); + mElevateThread = NULL; + } + if (mTurnThread) { + mShapeInstance->destroyThread(mTurnThread); + mTurnThread = NULL; + } + } else if (mCurrState == Activating) { + mCurrState = Deactivating; + } else { + mCurrState = Deactivating; + } + } + } +} + +void Turret::processTick(const Move* move) +{ + Parent::processTick(move); + + if (isHidden() || mDataBlock->neverUpdateControl) + return; + + bool playerControlled = (move != NULL); + + Move nullMove = { 0, 0, 0, 0, 0, 0 }; + if (move == NULL) + move = &nullMove; + + if (isServerObject() || playerControlled) { + if(mMount.object) + setMaskBits(MountedUpdateMask); + // Controlled path: Either a serverObject or a playerControled client object + // if we're here. + // + if (isFrozen() == false) { + updateState(playerControlled); + + // We might have to give the ai a chance to think before we allow it + // to update... + if (!playerControlled && mAutoFire && getMountedImage(0) != NULL) { + mLastThink += TickMs; + if (mLastThink >= getThinkTime()) + aiThink(); + } + + if (mCurrState == Dormant) { + // Just make sure... + mActivationLevel = mActiveBase = 0.0; + mCurrPhi = mPhiBase = csmPhiNull; + mCurrTheta = mThetaBase = getThetaNull(); + mPhiDelta = mThetaDelta = mActiveDelta = 0.0; + } + else if (mCurrState == Activating) { + performActivateRamp(); + setMaskBits(BogoMask); + } + else if (mCurrState == Deactivating) { + performDeactivateRamp(); + setMaskBits(BogoMask); + } + else if (mCurrState == Active) { + // If we're not player controlled, the ai needs to think about + // this move... + // + F32 phiMove, thetaMove; + if (playerControlled == false) { + if (isServerObject()) + aiUpdateActive(&nullMove); + + phiMove = mFabs(move->yaw); + thetaMove = mFabs(move->pitch); + + if (bool(mCurrTarget) == false) { + mTargetlessTime += TickMs; + } + } else { + phiMove = mFabs(move->yaw * sgBigClunkyMultiplicationFactor); + thetaMove = mFabs(move->pitch * sgBigClunkyMultiplicationFactor); + } + + if (phiMove > getPhiSpeedPerTick()) + phiMove = getPhiSpeedPerTick(); + if (move->yaw < 0.0) + phiMove *= -1.0f; + if (thetaMove > getThetaSpeedPerTick()) + thetaMove = getThetaSpeedPerTick(); + if (move->pitch < 0.0) + thetaMove *= -1.0f; + + // Setup the deltas... + mCurrPhi += phiMove; + mCurrTheta += thetaMove; + + mPhiBase = mCurrPhi; + mPhiDelta = -phiMove; + mThetaBase = mCurrTheta; + mThetaDelta = -thetaMove; + mActiveBase = 1.0; + mActiveDelta = 0.0; + + + // Clamp phi to [0, 360) + while (mCurrPhi >= 360.0f) mCurrPhi -= 360.0f; + while (mCurrPhi < 0.0f) mCurrPhi += 360.0f; + + // Clamp theta + if (mCurrTheta > mDataBlock->thetaMax) + mCurrTheta = mDataBlock->thetaMax; + else if (mCurrTheta < mDataBlock->thetaMin) + mCurrTheta = mDataBlock->thetaMin; + + setMaskBits(BogoMask); + } + else if (mCurrState == DeactivateForReplace) { + if (bool(mCurrEngineer)) { + performDeactivateRamp(); + if (mCurrState == Dormant) { + // We just went dormant. Let's see if we can replace the + // barrel here... + checkReplace(); + } + } else { + mCurrState = Deactivating; + } + } + + if (mCurrState == Active) { + setImageTriggerState(0, move->trigger[0]); + setImageTriggerState(1, move->trigger[1]); + } else { + setImageTriggerState(0, false); + setImageTriggerState(1, false); + } + } else { + // If we're frozen, we do nothing but clear our current target (if any) + mCurrTarget = NULL; + mCurrEngineer = NULL; + } + + updateContainer(); + + if( mWaterCoverage > 0 ) + setHeat(0); + else + setHeat(mDataBlock->heat); + + } else { + // Non-player controlled client object if we're here... + // + mActiveBase = mActivationLevel; + mPhiBase = mCurrPhi; + mThetaBase = mCurrTheta; + mPhiDelta = mThetaDelta = mActiveDelta = 0.0; + + if (mActivationLevel == 1.0) { + if (!mElevateThread || !mTurnThread) { + mElevateThread = mShapeInstance->addThread(); + mTurnThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mElevateThread, mDataBlock->elevateSeq, 0); + mShapeInstance->setSequence(mTurnThread, mDataBlock->turnSeq, 0); + } + } else { + if (mElevateThread || mTurnThread) { + mShapeInstance->destroyThread(mElevateThread); + mShapeInstance->destroyThread(mTurnThread); + mElevateThread = NULL; + mTurnThread = NULL; + } + } + } + + // Set current positional state... + if (mActivationLevel != 0.0) + { + if (mActivateThread == NULL) + { + mActivateThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mActivateThread, mDataBlock->activateSeq, 0); + } + mShapeInstance->setPos(mActivateThread, mActivationLevel); + } + else + { + if (mActivateThread != NULL) + { + if (mActivateThread != NULL) { + mShapeInstance->destroyThread(mActivateThread); + mActivateThread = NULL; + } + } + } + + setOrientationThreads(mCurrTheta, mCurrPhi); + + if (isServerObject() || playerControlled != NULL) + mShapeInstance->animate(); +} + +//-------------------------------------------------------------------------- +void Turret::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + if(mDeployThread) + mMountedImageList[0].shapeInstance->advanceTime((dt / 2.0f), mDeployThread); +} + +//-------------------------------------------------------------------------- +void Turret::interpolateTick(F32 delta) +{ + Parent::interpolateTick(delta); + + if (isHidden()) + return; + + F32 interpActive = mActiveBase + delta * mActiveDelta; + F32 interpPhi = mPhiBase + delta * mPhiDelta; + F32 interpTheta = mThetaBase + delta * mThetaDelta; + + while (interpPhi < 0.0) + interpPhi += 360.0f; + while (interpPhi >= 360.0f) + interpPhi -= 360.0f; + + if (interpTheta < mDataBlock->thetaMin || interpTheta > mDataBlock->thetaMax) + interpTheta = interpTheta < mDataBlock->thetaMin ? mDataBlock->thetaMin : mDataBlock->thetaMax; + + if (mActivateThread != NULL) + mShapeInstance->setPos(mActivateThread, interpActive); + setOrientationThreads(interpTheta, interpPhi); + mShapeInstance->animate(); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +bool Turret::writePacketData(GameConnection *connection, BitStream* stream) +{ + bool ret = Parent::writePacketData(connection, stream); + + // Dump entire state + stream->writeRangedU32(mCurrState, TurretFirstState, TurretLastState); + stream->write(mActivationLevel); + stream->write(mCurrPhi); + stream->write(mCurrTheta); + return ret; +} + + +void Turret::readPacketData(GameConnection *connection, BitStream* stream) +{ + Parent::readPacketData(connection, stream); + + mCurrState = (States)stream->readRangedU32(TurretFirstState, TurretLastState); + stream->read(&mActivationLevel); + stream->read(&mCurrPhi); + stream->read(&mCurrTheta); + mActiveBase = mActivationLevel; + mPhiBase = mCurrPhi; + mThetaBase = mCurrTheta; + mActiveDelta = + mThetaDelta = + mPhiDelta = 0.0; +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +U32 Turret::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; + + if(stream->writeFlag(mask & BogoMask)) + { + // Otherwise, write our phi, theta, and activation levels + F32 compressedPhi = mCurrPhi / 360.0f; + F32 compressedTheta = (mCurrTheta - mDataBlock->thetaMin) / + (mDataBlock->thetaMax - mDataBlock->thetaMin); + + stream->writeFloat(compressedPhi, PhiBits); + stream->writeFloat(compressedTheta, ThetaBits); + stream->writeFloat(mActivationLevel, ActivationBits); + } + return retMask; +} + +void Turret::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + // If we're not the controlling object, then we just return... + if(stream->readFlag()) + return; + + if(stream->readFlag()) + { + F32 newPhi = stream->readFloat(PhiBits); + F32 newTheta = stream->readFloat(ThetaBits); + F32 newActivation = stream->readFloat(ActivationBits); + newPhi *= 360.0f; + newTheta *= (mDataBlock->thetaMax - mDataBlock->thetaMin); + newTheta += mDataBlock->thetaMin; + + updateWarp(newPhi, newTheta, newActivation); + } +} + + +//-------------------------------------------------------------------------- +void Turret::setOrientationThreads(F32 theta, F32 phi) +{ + AssertFatal(theta >= mDataBlock->thetaMin && theta <= mDataBlock->thetaMax, "Turret::setOrientation theta out of range"); + + if (theta < mDataBlock->thetaMin || theta > mDataBlock->thetaMax) + theta = theta < mDataBlock->thetaMin ? mDataBlock->thetaMin : mDataBlock->thetaMax; + + while (phi >= 360.0f) + phi -= 360.0f; + while (phi < 0.0f) + phi += 360.0f; + + F32 thetaPos = theta / 180.0f; + F32 phiPos = phi / 360.0f; + + if (mTurnThread) + mShapeInstance->setPos(mTurnThread, phiPos); + if (mElevateThread) + mShapeInstance->setPos(mElevateThread, thetaPos); +} + +F32 Turret::getPhiSpeed() +{ + ShapeBaseImageData* pSBID = getMountedImage(0); + if (pSBID == NULL) + return csmDefaultPhiSpeed; + AssertFatal(dynamic_cast(pSBID) != NULL, "Error, non-turretimage mounted on a turret base!"); + + return static_cast(pSBID)->degPerSecPhi; +} + +F32 Turret::getThetaSpeed() +{ + ShapeBaseImageData* pSBID = getMountedImage(0); + if (pSBID == NULL) + return csmDefaultThetaSpeed; + AssertFatal(dynamic_cast(pSBID) != NULL, "Error, non-turretimage mounted on a turret base!"); + + return static_cast(pSBID)->degPerSecTheta; +} + +F32 Turret::getActivationSpeed() +{ + ShapeBaseImageData* pSBID = getMountedImage(0); + if (pSBID == NULL) + return csmDefaultActivationSpeed; + AssertFatal(dynamic_cast(pSBID) != NULL, "Error, non-turretimage mounted on a turret base!"); + + return 1000.0f / (static_cast(pSBID)->activationMS); +} + +U32 Turret::getDeactivateDelay() +{ + ShapeBaseImageData* pSBID = getMountedImage(0); + if (pSBID == NULL) + return csmDefaultDeactivateDelay; + AssertFatal(dynamic_cast(pSBID) != NULL, "Error, non-turretimage mounted on a turret base!"); + + return static_cast(pSBID)->deactivateDelayMS; +} + +U32 Turret::getThinkTime() +{ + ShapeBaseImageData* pSBID = getMountedImage(0); + if (pSBID == NULL) + return csmDefaultThinkTime; + AssertFatal(dynamic_cast(pSBID) != NULL, "Error, non-turretimage mounted on a turret base!"); + + return static_cast(pSBID)->thinkTimeMS; +} + +F32 Turret::getAttackRadius() +{ + ShapeBaseImageData* pSBID = getMountedImage(0); + if (pSBID == NULL) + return csmDefaultAttackRadius; + AssertFatal(dynamic_cast(pSBID) != NULL, "Error, non-turretimage mounted on a turret base!"); + + return static_cast(pSBID)->attackRadius; +} + + +F32 Turret::getPhiSpeedPerTick() +{ + return getPhiSpeed() * (TickMs / 1000.0f); +} + +F32 Turret::getThetaSpeedPerTick() +{ + return getThetaSpeed() * (TickMs / 1000.0f); +} + +F32 Turret::getActivationSpeedPerTick() +{ + return getActivationSpeed() * (TickMs / 1000.0f); +} + +bool Turret::isFrozen() const +{ + return mDamageState != Enabled; +} + +F32 Turret::getThetaNull() const +{ + + if (mDataBlock && mDataBlock->thetaNull != -1) + return mDataBlock->thetaNull; + return + csmThetaNull; +} + +//-------------------------------------------------------------------------- +void Turret::setSkill(F32 skill) +{ + (skill <= 1 && skill > 0) ? (mSkillLevel = skill) : (mSkillLevel = 1.0f); +} + +bool Turret::setTarget(ShapeBase* newTarget) +{ + AssertFatal(isServerObject(), "Error, clients may not have targets right now"); + + mCurrTarget = newTarget; + mTargetlessTime = 0; + return bool(mCurrTarget); +} + +S32 Turret::getTargetId() const +{ + if (bool(mCurrTarget) == false) + return 0; + return mCurrTarget->getId(); +} + +bool Turret::currTargetValid() +{ + if (bool(mCurrTarget) == false) + return false; + + return isValidTarget(mCurrTarget); +} + + +bool Turret::isValidTarget(ShapeBase* target) +{ + if (target == NULL) + return false; + + // First, let's see if this target is dead... + if (target->getDamageValue() >= 1.0) + return false; + + // this and target need to have an associated 'target' + if(!target->getTargetInfo() || !getTargetInfo()) + return false; + + // If we are loaded with seeking projectiles, then we need + // to check to see if the target is hot enough to kill + if (getMountedImage(0) && getMountedImage(0)->isSeeker == true) + { + if (target->getHeat() < getMountedImage(0)->minSeekHeat) + return false; + } + + // information we'll need for a bunch of checks + Point3F centerBox; + F32 distToTarget; + target->getWorldBox().getCenter(¢erBox); + Point3F myPos; + getTransform().getColumn(3, &myPos); + distToTarget = (myPos - centerBox).len(); + + // make sure we have a fire condition before continuing + if( getMountedImage(0) ) + { + TurretImageData *tData = static_cast(getMountedImage(0)); + + if( tData ) + { + // see if we have to be at least the damage radius from the target + if( tData->dontFireInsideDamageRadius ) + if( distToTarget <= tData->damageRadius ) + return false; + + // see if we need to be a certain distance from the target + ShapeBaseImageData *sData = static_cast(tData); + if( sData->targetingDist ) + { + if( distToTarget <= sData->targetingDist) + return false; + } + } + } + + // Query sensor network + // - target needs to be visible to turret + // - turret cannot be friendly to targets sensor group + if(!gTargetManager->isTargetVisible(target->getTarget(), getSensorGroup()) || + gTargetManager->isTargetFriendly(getTarget(), target->getSensorGroup())) + return false; + + // Let's see if the target is outside of our attack range... + MatrixF mountTransform; + getMuzzleTransform(0, &mountTransform); + Point3F mountPoint; + mountTransform.getColumn(3, &mountPoint); + Point3F targetPoint; + target->getWorldBox().getCenter(&targetPoint); + + if ((targetPoint - mountPoint).len() > getAttackRadius()) + return false; + + target->disableCollision(); + disableCollision(); + RayInfo rinfo; + + if (gServerContainer.castRay(mountPoint, targetPoint, csmActiveScanMask, &rinfo) == true) { + // Can't see the center of our target. we're going to deactivate... + target->enableCollision(); + enableCollision(); + return false; + } + + target->enableCollision(); + enableCollision(); + return true; +} + + +void Turret::selectTarget() +{ + // Call out to script to set the target, if possible... + // + Con::executef(mDataBlock, 2, "selectTarget", scriptThis()); +} + +void Turret::setAutoFire(bool status) +{ + mAutoFire = status; + if (! mAutoFire) + setTarget(NULL); +} + + +void Turret::checkReplace() +{ + AssertFatal(bool(mCurrEngineer), "Shouldn't be here if our engineer is dead..."); + + Con::executef(mDataBlock, 3, "replaceCallback", scriptThis(), mCurrEngineer->scriptThis()); + mCurrEngineer = NULL; +} + +bool Turret::initiateReplace(ShapeBase* engineer) +{ + if (engineer == NULL) + return false; + + mCurrEngineer = engineer; + if (getMountedImage(0) == false || mCurrState == Dormant) { + checkReplace(); + Con::errorf(ConsoleLogEntry::General, "instant replace"); + } else { + mCurrState = DeactivateForReplace; + Con::errorf(ConsoleLogEntry::General, "active replace"); + } + + return true; +} + +void Turret::aiThink() +{ + mLastThink = 0; + + // First, if we have a target, let's see if the target is still valid + if (bool(mCurrTarget)) { + if (currTargetValid() == false) { + // Call out to select a new target, if possible... + mCurrTarget = NULL; + selectTarget(); + } else { + // Ok, we can still kill this guy, no need to worry + } + } else { + // No target. Select one... + selectTarget(); + } + + // Let's see if we still have a target... + // + if (bool(mCurrTarget)) { + // Ok, kill! Let's activate ourselves if necessary. + if (mCurrState == Dormant) { + mCurrState = Activating; + } + else if (mCurrState == Activating) { + // Just be patient. + } + else if (mCurrState == Active) { + // Right where we want to be. + } + else if (mCurrState == Deactivating) { + if (mActivationLevel == csmFullyActivated) { + mCurrState = Active; + } else { + mCurrState = Activating; + } + } + else if (mCurrState == DeactivateForReplace) { + // Do nothing. We're swapping out... + } + } else { + // No target. Since we had a chance to select one above, we're all done here. + // + } +} + +Point3F Turret::dopeAim(const Point3F &startLocation, const Point3F &aimLocation) +{ + static MRandomLCG rand(Sim::getCurrentTime()); + + //find the "horizontal" orthogonal vector + Point3F horzOrth; + if (! findCrossVector(startLocation - aimLocation, Point3F(0, 0, 1), &horzOrth)) + return aimLocation; + + //now find the "vertical" orthogonal vector + Point3F vertOrth; + if (! findCrossVector(startLocation - aimLocation, horzOrth, &vertOrth)) + return aimLocation; + + //now we have the horizonal and vertical components of the plane which is perpendicular to + //the direction we are firing... + + //determine the radius factor + F32 radiusFactor; + if (mSkillLevel == 1.0f) + radiusFactor = 0.0f; + else + radiusFactor = 0.04 + (1.0f - mSkillLevel) * 0.2; + + //calculate the radius error + F32 radiusError = (startLocation - aimLocation).len() * radiusFactor * 0.1; + F32 horzError = rand.randF() * radiusError * (rand.randF() < 0.5f ? -1.0f : 1.0f); + F32 vertError = rand.randF() * radiusError * (rand.randF() < 0.5f ? -1.0f : 1.0f); + + Point3F dopedAimLocation = aimLocation + (horzOrth * horzError) + (vertOrth * vertError); + return dopedAimLocation; +} + + +Point3F gbogo; +void Turret::aiUpdateActive(Move* move) +{ + if (bool(mCurrTarget)) { + // Let's find the vector we need to follow to hit this target... + // + MatrixF mountTransform; + getMuzzleTransform(0, &mountTransform); + Point3F mountPoint; + mountTransform.getColumn(3, &mountPoint); + Point3F targetPoint; + mCurrTarget->getWorldBox().getCenter(&targetPoint); + gbogo = targetPoint; + + // Can we see the target? + disableCollision(); + mCurrTarget->disableCollision(); + RayInfo rinfo; + + // only used by bots - when a player mounts, skill should be set to 1.0 (from script) + if (mSkillLevel < 1.0f) + targetPoint = dopeAim(mountPoint, targetPoint); + + if (gServerContainer.castRay(mountPoint, targetPoint, csmActiveScanMask, &rinfo) == true) { + // Can't see the center of our target. we're going to deactivate... + mCurrTarget->enableCollision(); + enableCollision(); + + mTargetlessTime = 0; + mCurrTarget = NULL; + return; + } + mCurrTarget->enableCollision(); + enableCollision(); + + ShapeBaseImageData* pSBID = getMountedImage(0); + AssertFatal(pSBID && dynamic_cast(pSBID), "This shouldn't happen to an AI controlled turret..."); + TurretImageData* pTID = static_cast(pSBID); + AssertFatal(pTID->projectile != NULL, "Error, must have a projectile!"); + + Point3F dirMin, dirMax; + F32 timeMin, timeMax; + bool allowFire; + if (pTID->projectile->calculateAim(targetPoint, + mCurrTarget->getVelocity(), + mountPoint, Point3F(0, 0, 0), &dirMin, &timeMin, &dirMax, &timeMax)) { + // Yay. + allowFire = true; + } else { + // Can't hit it. Point somewhere near it... + dirMin = targetPoint - mountPoint; + allowFire = false; + } + + // Let's translate this into our own coordinate system... +// mWorldToObj.mulV(dirMin); + MatrixF mat = getTransform(); + mat.setColumn(3, mountPoint); + mat.affineInverse(); + mat.mulV(dirMin); + dirMin.normalize(); + + // Ok, now we need to derive a phi/theta angle to point this way. + F32 thetaLen; + F32 newPhi; + F32 newTheta; + if (mDataBlock->primaryAxis == TurretData::ZAxis) + { + thetaLen = mSqrt(dirMin.x*dirMin.x + dirMin.y*dirMin.y); + newPhi = mAtan(dirMin.x, dirMin.y) * (360 / (2 * M_PI)); + newTheta = 180 - (mAtan(dirMin.z, thetaLen) * (90/(M_PI/2.0)) + 90.0); + } + else if (mDataBlock->primaryAxis == TurretData::YAxis) + { + thetaLen = mSqrt(dirMin.x*dirMin.x + dirMin.z*dirMin.z); + newPhi = mAtan(-dirMin.x, dirMin.z) * (360 / (2 * M_PI)); + newTheta = 180 - (mAtan(dirMin.y, thetaLen) * (90/(M_PI/2.0)) + 90.0); + } + else if (mDataBlock->primaryAxis == TurretData::RevZAxis) + { + thetaLen = mSqrt(dirMin.x*dirMin.x + dirMin.y*dirMin.y); + newPhi = 360 - (mAtan(dirMin.x, dirMin.y) * (360 / (2 * M_PI))); + newTheta = mAtan(dirMin.z, thetaLen) * (90/(M_PI/2.0)) + 90.0; + } + else if (mDataBlock->primaryAxis == TurretData::RevYAxis) + { + thetaLen = mSqrt(dirMin.x*dirMin.x + dirMin.z*dirMin.z); + newPhi = mAtan(-dirMin.x, dirMin.z) * (360 / (2 * M_PI)); + newTheta = 180 - (mAtan(dirMin.y, thetaLen) * (90/(M_PI/2.0)) + 90.0); + } + else + { + AssertFatal(false, "Invalid turret primary axis!"); + } + + // Lets twiddle newPhi + if (newPhi < 0.0f) + newPhi += 360.0f; + if (mFabs((newPhi - 360) - mCurrPhi) < mFabs(newPhi - mCurrPhi)) { + newPhi -= 360.0f; + } else if (mFabs((newPhi + 360) - mCurrPhi) < mFabs(newPhi - mCurrPhi)) { + newPhi += 360.0f; + } + + move->yaw = newPhi - mCurrPhi; + move->pitch = newTheta - mCurrTheta; + + // Determine whether or not we fire... + bool fire = false; + if (allowFire && mFabs(move->yaw) < 0.5 && mFabs(move->pitch) < 0.5) { + fire = true; + } + move->trigger[0] = fire; + move->trigger[1] = fire; + } else { + // Do nothing for right now. Possibly think out to script later + // Wait for deactivate + move->trigger[0] = false; + move->trigger[1] = false; + } +} + +void Turret::getMuzzleVector(U32 imageSlot,VectorF* vec) +{ + + if (GameConnection * gc = getControllingClient()) + { + if (gc->isAIControlled() == false) + { + if (gc->isFirstPerson() == true) + { + MatrixF mat; + getMuzzleTransform(imageSlot,&mat); + if (getCorrectedAim(mat, vec)) + { + return; + } + } + } + } + + Point3F vector; + if (mDataBlock->primaryAxis == TurretData::ZAxis) + { + F32 len = mSin(mDegToRad(mCurrTheta)); + + vector.x = mSin(mDegToRad(mCurrPhi)) * len; + vector.y = mCos(mDegToRad(mCurrPhi)) * len; + vector.z = mCos(mDegToRad(mCurrTheta)); + + *vec = vector; + } + else if (mDataBlock->primaryAxis == TurretData::YAxis) + { + F32 len = mSin(mDegToRad(mCurrTheta)); + + vector.x = -mSin(mDegToRad(mCurrPhi)) * len; + vector.y = mCos(mDegToRad(mCurrTheta)); + vector.z = mCos(mDegToRad(mCurrPhi)) * len; + *vec = vector; + } + else if (mDataBlock->primaryAxis == TurretData::RevZAxis) + { + F32 len = mSin(mDegToRad(mCurrTheta)); + + vector.x = mSin(mDegToRad(360 - mCurrPhi)) * len; + vector.y = mCos(mDegToRad(360 - mCurrPhi)) * len; + vector.z = mCos(mDegToRad(180 - mCurrTheta)); + *vec = vector; + } + else if (mDataBlock->primaryAxis == TurretData::RevYAxis) + { + F32 len = mSin(mDegToRad(mCurrTheta)); + + vector.x = mSin(mDegToRad(mCurrPhi)) * len; + vector.y = -mCos(mDegToRad(mCurrTheta)); + vector.z = -mCos(mDegToRad(mCurrPhi)) * len; + *vec = vector; + } + + getTransform().mulV(*vec); +} + + +//-------------------------------------------------------------------------- +void Turret::performActivateRamp() +{ + setImageLoadedState(0, true); + mActivationLevel += getActivationSpeedPerTick(); + if (mActivationLevel >= csmFullyActivated) { + if (!mElevateThread || !mTurnThread) { + mElevateThread = mShapeInstance->addThread(); + mTurnThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mElevateThread, mDataBlock->elevateSeq, 0); + mShapeInstance->setSequence(mTurnThread, mDataBlock->turnSeq, 0); + } + + mActivationLevel = csmFullyActivated; + mCurrState = Active; + } + + // Setup the deltas + mPhiBase = csmPhiNull; + mPhiDelta = 0.0; + mThetaBase = getThetaNull(); + mThetaDelta = 0.0; + mActiveBase = mActivationLevel; + mActiveDelta = 0.0; +} + +void Turret::performDeactivateRamp() +{ + bool rampDownActive = true; + + // Setup the deltas... + // Check the phi angle. This rotates, which is annoying, since we want the most + // efficient spin down. + AssertFatal(csmPhiNull == 0.0f, "Error, must rewrite this if phiNull != 0"); + if (mCurrPhi != 0.0f) { + rampDownActive = false; + F32 speed = getPhiSpeed() * (TickMs / 1000.0f); + + if (mCurrPhi > 180.0f) { + mCurrPhi += speed; + mPhiDelta = -speed; + } + else { + mCurrPhi -= speed; + mPhiDelta = speed; + } + mPhiBase = mCurrPhi; + + if (mCurrPhi >= 360.0f || + mCurrPhi <= 0.0f) { + mCurrPhi = 0.0f; + } + } else { + mPhiBase = mCurrPhi; + mPhiDelta = 0.0; + } + + // Check the theta angle. This is much easier + if (mCurrTheta != csmThetaNull) { + rampDownActive = false; + F32 speed = getThetaSpeed() * (TickMs / 1000.0f); + + F32 dir = (csmThetaNull - mCurrTheta) / mFabs(csmThetaNull - mCurrTheta); + + mCurrTheta += dir * speed; + mThetaDelta = -dir*speed; + mThetaBase = mCurrTheta; + + if ((dir < 0.0 && mCurrTheta <= csmThetaNull) || + (dir > 0.0 && mCurrTheta >= csmThetaNull)) + mCurrTheta = csmThetaNull; + } else { + mThetaDelta = 0.0; + } + + if (rampDownActive == true) { + if (mElevateThread || mTurnThread) { + mShapeInstance->destroyThread(mElevateThread); + mShapeInstance->destroyThread(mTurnThread); + mElevateThread = NULL; + mTurnThread = NULL; + } + + // TODO: Activate time from datablock + setImageLoadedState(0, false); + + mActivationLevel -= getActivationSpeedPerTick(); + + mActiveDelta = getActivationSpeedPerTick(); + mActiveBase = mActivationLevel; + + if (mActivationLevel <= csmFullyDeactivated) { + mActivationLevel = csmFullyDeactivated; + mCurrState = Dormant; + } + } else { + mActiveBase = mActivationLevel; + mActiveDelta = 0.0; + } +} + +//-------------------------------------------------------------------------- +void Turret::updateWarp(const F32 phi, const F32 theta, const F32 active) +{ + // First, we have to derive the exact current position + F32 currPhi = mPhiBase + mLastDelta * mPhiDelta; + F32 currTheta = mThetaBase + mLastDelta * mThetaDelta; + F32 currActive = mActiveBase + mLastDelta * mActiveDelta; + + mCurrPhi = phi; + mCurrTheta = theta; + mActivationLevel = active; +} + diff --git a/game/turret.h b/game/turret.h new file mode 100644 index 0000000..b0df47e --- /dev/null +++ b/game/turret.h @@ -0,0 +1,266 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_TURRET +#define _H_TURRET + +#ifndef _STATICSHAPE_H_ +#include "game/staticShape.h" +#endif + +class ShockwaveData; + +// ------------------------------------------------------------------------- +class TurretData : public StaticShapeData +{ + typedef StaticShapeData Parent; + + protected: + bool preload(bool server, char errorBuffer[256]); + + //-------------------------------------- Console set variables + public: + enum PrimaryAxis { + YAxis, + RevYAxis, + ZAxis, + RevZAxis, + + __FirstValidAxis = YAxis, + __LastValidAxis = RevZAxis + }; + + F32 thetaMin; + F32 thetaMax; + F32 thetaNull; + + PrimaryAxis primaryAxis; + + bool neverUpdateControl; + + //-------------------------------------- load set variables + public: + S32 activateSeq; + S32 elevateSeq; + S32 turnSeq; + + public: + TurretData(); + ~TurretData(); + + void packData(BitStream*); + void unpackData(BitStream*); + + DECLARE_CONOBJECT(TurretData); + static void initPersistFields(); +}; + +class TurretImageData : public ShapeBaseImageData +{ + typedef ShapeBaseImageData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + S32 activationMS; + S32 deactivateDelayMS; + S32 thinkTimeMS; + + F32 degPerSecTheta; + F32 degPerSecPhi; + + F32 attackRadius; + + bool dontFireInsideDamageRadius; + F32 damageRadius; + + ShockwaveData * muzzleFlash; + S32 muzzleFlashID; + + //-------------------------------------- load set variables + public: + + public: + TurretImageData(); + ~TurretImageData(); + + void packData(BitStream*); + void unpackData(BitStream*); + + DECLARE_CONOBJECT(TurretImageData); + static void initPersistFields(); +}; + + + +// ------------------------------------------------------------------------- +class TSThread; +class Turret : public StaticShape +{ + typedef StaticShape Parent; + + private: + TurretData* mDataBlock; + + protected: + enum States { + Dormant = 0, + Activating = 1, + Deactivating = 2, + Active = 3, + DeactivateForReplace = 4, + + TurretFirstState = Dormant, + TurretLastState = DeactivateForReplace + }; + enum Constants { + TurretWarpTicks = 10, + + PhiBits = 10, + ThetaBits = 10, + ActivationBits = 8 + }; + enum TurretNetMasks { + MountedUpdateMask = Parent::NextFreeMask, + BogoMask = MountedUpdateMask << 1, + NextFreeMask = BogoMask << 2 + }; + + static const U32 csmActiveScanMask; + + static const U32 csmDefaultDeactivateDelay; + static const U32 csmDefaultThinkTime; + static const F32 csmDefaultAttackRadius; + static const F32 csmFullyDeactivated; + static const F32 csmFullyActivated; + static const F32 csmPhiNull; + static const F32 csmThetaNull; + + // Used to deactivate if there is no image mounted + static const F32 csmDefaultActivationSpeed; + static const F32 csmDefaultPhiSpeed; + static const F32 csmDefaultThetaSpeed; + + // Server data, replicated to client through writePacketData + protected: + States mCurrState; + F32 mActivationLevel; // 0->completelyDeactivated, 1->completelyActivated + F32 mCurrPhi; // Both in degrees. Phi NULL == 0 + F32 mCurrTheta; // Theta NULL == 90 + F32 mSkillLevel; + + StringTableEntry mCurrBarrel; + + // Server data/functions used by ai to track target + protected: + SimObjectPtr mCurrTarget; // NULL == no target + U32 mTargetlessTime; + U32 mLastThink; + bool mAutoFire; + + public: + void setSkill(F32 skill); + bool setTarget(ShapeBase*); + S32 getTargetId() const; + bool currTargetValid(); + bool isValidTarget(ShapeBase*); + bool initiateReplace(ShapeBase*); + void setAutoFire(bool status); + void advanceTime(F32 dt); + + // Replacing object... + protected: + SimObjectPtr mCurrEngineer; + + // Animation thread data + protected: + TSThread* mActivateThread; + TSThread* mElevateThread; + TSThread* mTurnThread; + + TSThread* mBarrelDamageThread; + TSThread* mDeployThread; + + // Client side interpolation data + protected: + F32 mPhiBase; + F32 mThetaBase; + F32 mActiveBase; + F32 mPhiDelta; + F32 mThetaDelta; + F32 mActiveDelta; + + // Convenience functions... + protected: + void setOrientationThreads(F32 theta, F32 phi); + bool isFrozen() const; + + F32 getPhiSpeed(); + F32 getThetaSpeed(); + F32 getActivationSpeed(); + U32 getDeactivateDelay(); + U32 getThinkTime(); + F32 getAttackRadius(); + + F32 getThetaNull() const; + + F32 getPhiSpeedPerTick(); + F32 getThetaSpeedPerTick(); + F32 getActivationSpeedPerTick(); + + void performActivateRamp(); + void performDeactivateRamp(); + + void selectTarget(); + void checkReplace(); + + bool mountImage(ShapeBaseImageData* image,U32 imageSlot,bool loaded,S32 team); + bool unmountImage(U32 imageSlot); + virtual void setImage(U32 imageSlot, ShapeBaseImageData* imageData, U32 teamTag, + bool loaded, bool ammo, bool triggerDown, + bool target); + + // Control functions + protected: + void updateState(bool isPlayerControlled); + void updateWarp(const F32, const F32, const F32); + + // AI functions + protected: + void aiUpdateActive(Move* move); + void aiThink(); + Point3F dopeAim(const Point3F &startLocation, const Point3F &aimLocation); + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData*); + virtual void updateDamageLevel(); + + public: + Turret(); + ~Turret(); + + DECLARE_CONOBJECT(Turret); + static void initPersistFields(); + static void consoleInit(); + + void processTick(const Move*); + void interpolateTick(F32 delta); + + void getMuzzleVector(U32 imageSlot,VectorF* vec); + + bool writePacketData(GameConnection *, BitStream*); + void readPacketData(GameConnection *, BitStream*); + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_TURRET + diff --git a/game/underLava.cc b/game/underLava.cc new file mode 100644 index 0000000..5f4cea3 --- /dev/null +++ b/game/underLava.cc @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/underLava.h" +#include "dgl/dgl.h" +#include "Math/mRect.h" +#include "dgl/gTexManager.h" +#include "terrain/waterBlock.h" +#include "Math/mConstants.h" + +UnderLavaFX gLavaFX; + + +//************************************************************************** +// Under lava FX - "Lava - With pumice!" +//************************************************************************** +UnderLavaFX::UnderLavaFX() +{ + +} + +//-------------------------------------------------------------------------- +// Init +//-------------------------------------------------------------------------- +void UnderLavaFX::init() +{ + RectI viewport; + dglGetViewport( &viewport ); + + mViewSize = viewport.extent; + + mTexFrequency.x = F32(viewport.extent.x / viewport.extent.y); + mTexFrequency.y = 1.0; + + mNumPoints.x = 50 * F32(viewport.extent.x / viewport.extent.y); + mNumPoints.y = 50; + + mWave[0].amplitude = 0.02; + mWave[0].frequency = 2.0; + mWave[0].velocity = Sim::getCurrentTime() / 1000.0 * 2.0; + + mMoveSpeed = Sim::getCurrentTime() / 1000.0 * 0.025; +} + +//-------------------------------------------------------------------------- +// Render +//-------------------------------------------------------------------------- +void UnderLavaFX::render() +{ + init(); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glEnable(GL_TEXTURE_2D); +// TextureHandle lavaTex = WaterBlock::getSubmergeTexture(0); + glBindTexture(GL_TEXTURE_2D, WaterBlock::getSubmergeTexture(0).getGLName()); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_FALSE); + glColor4f(1.0, 1.0, 1.0, 0.5); + + if( WaterBlock::getSubmergeTexture(0) ) + { + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // render layer 1 + for( U32 i=0; i(object) != NULL, "Error, must be a vehicle here!"); + Vehicle* pVeh = static_cast(object); + pVeh->setFrozenState(dAtob(argv[2])); +} + + +//---------------------------------------------------------------------------- + +VehicleData::VehicleData() +{ + body.friction = 0; + body.restitution = 1; + + minImpactSpeed = 25; + softImpactSpeed = 25; + hardImpactSpeed = 50; + minRollSpeed = 0; + maxSteeringAngle = 0.785; // 45 deg. + + cameraOffset = 0; + cameraLag = 0; + + minDrag = 0; + maxDrag = 0; + + jetForce = 500; + jetEnergyDrain = 0.8; + minJetEnergy = 1; + + massCenter.set(0,0,0); + drag = 0.7; + 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( 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; + + genericShadowLevel = Vehicle_GenericShadowLevel; + noShadowLevel = Vehicle_NoShadowLevel; + + dMemset(waterSound, 0, sizeof(waterSound)); + + collDamageThresholdVel = 20; + collDamageMultiplier = 0.05; + + stuckTimerTicks = 1; + stuckTimerAngle = 180; +} + + +//---------------------------------------------------------------------------- + +bool VehicleData::preload(bool server, char errorBuffer[256]) +{ + if (!Parent::preload(server, errorBuffer)) + 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 (server) + { + if (stuckTimerTicks <= 0) + { + Con::warnf("VehicleData::preload: stuck timer ticks must be >= 1"); + stuckTimerTicks = 1; + } + if (stuckTimerAngle < 0.0 || stuckTimerAngle > 180.0) + { + Con::warnf("VehicleData::preload: stuck timer angle must be in range [0, 180]"); + stuckTimerAngle = stuckTimerAngle < 0.0 ? 0.0 : 180.0; + } + stuckTimerZ = mCos(stuckTimerAngle); + } + + +// // Center of mass in object space +// S32 mass = shape->findNode("mass"); +// if (mass != -1) { +// massCenter = shape->nodeStates[mass].transform.getTranslate(); +// massCenter.x = 0; +// } + massCenter.set(0, 0, 0); + + 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(jetForce); + stream->write(jetEnergyDrain); + stream->write(minJetEnergy); + + stream->write(cameraOffset); + stream->write(cameraLag); + + 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] = (AudioProfile*)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(&jetForce); + stream->read(&jetEnergyDrain); + stream->read(&minJetEnergy); + + stream->read(&cameraOffset); + stream->read(&cameraLag); + + 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() +{ + Parent::initPersistFields(); + + addField("jetForce", TypeF32, Offset(jetForce, VehicleData)); + addField("jetEnergyDrain", TypeF32, Offset(jetEnergyDrain, VehicleData)); + addField("minJetEnergy", TypeF32, Offset(minJetEnergy, VehicleData)); + + addField("bodyRestitution", TypeF32, Offset(body.restitution, VehicleData)); + addField("bodyFriction", TypeF32, Offset(body.friction, VehicleData)); + addField("softImpactSound", TypeAudioProfilePtr, Offset(body.sound[Body::SoftImpactSound], VehicleData)); + addField("hardImpactSound", TypeAudioProfilePtr, 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("maxSteerinAngle", TypeF32, Offset(maxSteeringAngle, VehicleData)); + + addField("maxDrag", TypeF32, Offset(maxDrag, VehicleData)); + addField("minDrag", TypeF32, Offset(minDrag, VehicleData)); + + addField("cameraOffset", TypeF32, Offset(cameraOffset, VehicleData)); + addField("cameraLag", TypeF32, Offset(cameraLag, 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", TypeAudioProfilePtr, Offset(waterSound[ExitWater], VehicleData)); + addField("impactWaterEasy", TypeAudioProfilePtr, Offset(waterSound[ImpactSoft], VehicleData)); + addField("impactWaterMedium", TypeAudioProfilePtr, Offset(waterSound[ImpactMedium], VehicleData)); + addField("impactWaterHard", TypeAudioProfilePtr, Offset(waterSound[ImpactHard], VehicleData)); + addField("waterWakeSound", TypeAudioProfilePtr, Offset(waterSound[Wake], VehicleData)); + + addField("collDamageThresholdVel", TypeF32, Offset(collDamageThresholdVel, VehicleData)); + addField("collDamageMultiplier", TypeF32, Offset(collDamageMultiplier, VehicleData)); + + addField("stuckTimerTicks", TypeS32, Offset(stuckTimerTicks, VehicleData)); + addField("stuckTimerAngle", TypeF32, Offset(stuckTimerAngle, VehicleData)); +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(Vehicle); + +Vehicle::Vehicle() +{ + 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.state.linPosition.set(0, 0, 0); + mRigid.state.linVelocity.set(0, 0, 0); + mRigid.state.angPosition.identity(); + mRigid.state.angVelocity.set(0, 0, 0); + mRigid.state.linMomentum.set(0, 0, 0); + mRigid.state.angMomentum.set(0, 0, 0); + + mMinRoll = false; + + mSteering.set(0,0); + mThrottle = 0; + mJetting = false; + + dMemset( mDustEmitterList, 0, sizeof( mDustEmitterList ) ); + dMemset( mDamageEmitterList, 0, sizeof( mDamageEmitterList ) ); + dMemset( mSplashEmitterList, 0, sizeof( mSplashEmitterList ) ); + + mDisableMove = false; + + inLiquid = false; + mFrozen = false; + waterWakeHandle = 0; + + mStuckTimer = 0; +} + + +void Vehicle::consoleInit() +{ + +} + +void Vehicle::updateWarp() +{ + AssertFatal(false, "Pure virtual (sorta) function called!"); +} + +U32 Vehicle::getCollisionMask() +{ + AssertFatal(false, "Pure virtual (sorta) function called!"); + return 0; +} + +Point3F Vehicle::getVelocity() const +{ + return mRigid.state.linVelocity; +} + +//---------------------------------------------------------------------------- + +bool Vehicle::onAdd() +{ + if (!sVehicleCount++) + sPolyList = new ClippedPolyList; + + if (!Parent::onAdd()) + return false; + + mRigid.state.setTransform(mObjToWorld); + mRigid.mass = 1; + mRigid.oneOverMass = 1 / mRigid.mass; + mRigid.setObjectInertia((mObjBox.max - mObjBox.min) * 0.5); + + mDelta.rot[1] = mDelta.rot[0] = mRigid.state.angPosition; + mDelta.pos = mRigid.state.linPosition; + mDelta.posVec = Point3F(0,0,0); + + if( !isServerObject() ) + { + 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.min.convolve(mObjScale); + mConvex.box.max.convolve(mObjScale); + + return true; +} + +void Vehicle::onRemove() +{ + if (!--sVehicleCount) { + delete sPolyList; + sPolyList = 0; + } + + 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) +{ + 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]); + updateWarp(); + + // 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; + } + + if (mFrozen == false) + { + updateWorkingCollisionSet(getCollisionMask()); + updateMove(move); + + mDelta.posVec = mRigid.state.linPosition; + mDelta.rot[0] = mRigid.state.angPosition; + + for (U32 i = 0; i < 1; i++) { + mRigid.clearForces(); + updateForces(TickSec); + updatePos(TickSec); + } + + mDelta.pos = mRigid.state.linPosition; + mDelta.posVec -= mRigid.state.linPosition; + mDelta.rot[1] = mRigid.state.angPosition; + + setPosition(mRigid.state.linPosition, mRigid.state.angPosition); + setMaskBits(PositionMask); + updateContainer(); + } + else + { + mDelta.posVec = mRigid.state.linPosition; + mDelta.rot[0] = mRigid.state.angPosition; + mDelta.pos = mRigid.state.linPosition; + mDelta.posVec -= mRigid.state.linPosition; + mDelta.rot[1] = mRigid.state.angPosition; + setPosition(mRigid.state.linPosition, mRigid.state.angPosition); + } + } +} + +void Vehicle::interpolateTick(F32 dt) +{ + Parent::interpolateTick(dt); + + if (mFrozen == false) + { + 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; + } + else + { + mDelta.dt = 0; + } +} + +void Vehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + updateLiftoffDust( dt ); + updateDamageSmoke( dt ); + + updateFroth(dt); +} + +//---------------------------------------------------------------------------- + +bool Vehicle::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + 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::getVelocity(const Point3F& r, Point3F* v) +{ + mRigid.state.getVelocity(r, v); +} + +void Vehicle::applyImpulse(const Point3F &pos, const Point3F &impulse) +{ + Point3F r = pos, massCenter; + mObjToWorld.mulP(mDataBlock->massCenter,&massCenter); + r -= massCenter; + localImpulse(r, impulse); +} + +void Vehicle::localImpulse(const Point3F &r,const Point3F &impulse) +{ + Point3F actualImpulse = impulse * mOneOverMass; + mRigid.applyImpulse(mRigid.state, r, actualImpulse); +} + +F32 Vehicle::getImpulse(const Point3F& r,const Point3F& normal) +{ + // Returns impulse value need to stop velocity along the + // given normal. + Point3F v; + getVelocity(r,&v); + F32 n = -mDot(v,normal); + + Point3F a,b; + mCross(r,normal,&a); + mCross(a,r,&b); + F32 d = mRigid.oneOverMass + (mDot(b,normal) * mRigid.oneOverMass); + + return n/d; +} + + +//---------------------------------------------------------------------------- +void Vehicle::updateWorkingCollisionSet(const U32 mask) +{ + // 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 + Point3F scaledVelocity = mRigid.state.linVelocity * TickSec; + F32 len = scaledVelocity.len(); + F32 newLen = len + (50 * 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.1) + 0.1; // fudge factor + convexBox.min -= Point3F(l, l, l); + convexBox.max += Point3F(l, l, l); + + disableCollision(); + mConvex.updateWorkingList(convexBox, mask); + enableCollision(); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::disableCollision() +{ + Parent::disableCollision(); + for (ShapeBase* ptr = getMountList(); ptr; ptr = ptr->getMountLink()) + ptr->disableCollision(); +} + +void Vehicle::enableCollision() +{ + Parent::enableCollision(); + for (ShapeBase* ptr = getMountList(); ptr; ptr = ptr->getMountLink()) + ptr->enableCollision(); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::updateMove(const Move* move) +{ + 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 + 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; + } + + if (!isGhost()) { + if(!inLiquid && mWaterCoverage != 0.0f) { + Con::executef(mDataBlock,4,"onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), Con::getIntArg(mLiquidType)); + inLiquid = true; + mHeat = 0.0; + } + else if(inLiquid && mWaterCoverage == 0.0f) { + Con::executef(mDataBlock,3,"onLeaveLiquid",scriptThis(), Con::getIntArg(mLiquidType)); + inLiquid = false; + mHeat = 1.0; + } + } + else { + F32 vSpeed = getVelocity().len(); + if(!inLiquid && mWaterCoverage >= 0.8f) { + if(vSpeed >= mDataBlock->hardSplashSoundVel) + alxPlay(mDataBlock->waterSound[VehicleData::ImpactHard], &getTransform()); + else if( vSpeed >= mDataBlock->medSplashSoundVel) + alxPlay(mDataBlock->waterSound[VehicleData::ImpactMedium], &getTransform()); + else if( vSpeed >= mDataBlock->softSplashSoundVel) + alxPlay(mDataBlock->waterSound[VehicleData::ImpactSoft], &getTransform()); + inLiquid = true; + } + else if(inLiquid && mWaterCoverage < 0.8f) { + if(vSpeed >= mDataBlock->exitSplashSoundVel) + alxPlay(mDataBlock->waterSound[VehicleData::ExitWater], &getTransform()); + inLiquid = false; + } + } + mMinRoll = mJetting; +} + + +void Vehicle::updateForces(F32 /*dt*/) +{ + // Nothing here. +} + + +//---------------------------------------------------------------------------- +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.state.setTransform(newMat); + Parent::setTransform(newMat); +} + + +void Vehicle::updatePos(F32 dt) +{ + advanceToCollision(dt); + +// // Check for rest condition +// F32 k = mRigid.getKineticEnergy(mWorldToObj); +// F32 G = -mRigid.state.force.z * mRigid.oneOverMass * TickSec; +// F32 Kg = 0.5 * mRigid.mass * G * G; +// if (k < Kg * sRestTol) +// mRigid.setAtRest(); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- + +bool Vehicle::advanceToCollision(F32 time) +{ + F32 ct = 0,dt = time; + Rigid::State ns = mRigid.state; + + MatrixF mat; + mRigid.state.getTransform(&mat); + CollisionState *state = mConvex.findClosestStateBounded(mat, getScale(), sCollisionTol); + + CollisionList info; + F32 mt = time / 2.0; + dt = mt; + + bool collided = false; + bool displaced = false; + Point3F origVelocity = mRigid.state.linVelocity; + + bool success = true; + do { + F32 prevDist = state != NULL ? state->dist : 1e7; + info.count = 0; + if (state && state->dist < sCollisionTol) { + // Try to displace the object out of the way... + SceneObject* obj = NULL; + if (state->a->getObject() == this) + obj = state->b->getObject(); + else + obj = state->a->getObject(); + AssertFatal(obj != NULL, "Well, that's odd."); + 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.1; + 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.1 * mt) == true) + { + // Determine the speed at which we will damage this object + objOldMom /= obj->getMass(); + objNewMom /= obj->getMass(); + F32 len = (objOldMom - objNewMom).len(); + + queueCollision(static_cast(obj), len); + state = 0; + displaced = true; + continue; + } + } + + mConvex.getCollisionInfo(mat, getScale(), &info, sCollisionTol * 1.25); + collided |= resolveCollision(ns, info); + resolveContacts(ns, info, dt); + if (collided) + { + ns.force.set(0, 0, 0); + ns.torque.set(0, 0, 0); + } + } + + mRigid.integrate(ns,dt); + ns.getTransform(&mat); + + state = mConvex.findClosestStateBounded(mat, getScale(), sCollisionTol); + if (state && state->dist <= sIntersectionTol && state->dist <= prevDist) { + if ((dt *= 0.25) < 0.0001) { + // Make sure we check the collision damage... + collided = true; + success = false; + mRigid.state.linVelocity.set(0,0,0); + mRigid.state.linMomentum.set(0,0,0); + mRigid.state.angVelocity.set(0,0,0); + mRigid.state.angMomentum.set(0,0,0); + goto exitRoutine; + } + + state = 0; + ns = mRigid.state; + continue; + } + + mRigid.state = ns; + ct += dt; + if (dt < mt) + dt *= 1.2; + if (dt > (time - ct)) + dt = time - ct; + } while (ct < time); + +exitRoutine: + if (collided || displaced) + { + F32 collVel = (origVelocity - mRigid.state.linVelocity).len(); + if (origVelocity.isZero() == false) + origVelocity.normalize(); + else + origVelocity = Point3F(0, 0, 1); + + if (isClientObject()) + { + S32 impactSound = -1; + if (collVel >= mDataBlock->hardImpactSpeed) + impactSound = VehicleData::Body::HardImpactSound; + else if (collVel >= mDataBlock->softImpactSpeed) + impactSound = VehicleData::Body::SoftImpactSound; + + if (impactSound != -1 && mDataBlock->body.sound[impactSound] != NULL) + alxPlay(mDataBlock->body.sound[impactSound], &getTransform()); + } + + if (isServerObject()) + { + if (collVel > mDataBlock->minImpactSpeed) + onImpact(origVelocity * collVel); + + damageQueuedObjects(collVel); + + MatrixF mat; + mRigid.state.getTransform(&mat); + Point3F up; + mat.getColumn(2, &up); + bool blowup = false; + if (up.z < -0.25f) + blowup = true; + else + { + if (up.z <= mDataBlock->stuckTimerZ) + mStuckTimer++; + else + mStuckTimer = 0; + + if (mStuckTimer >= mDataBlock->stuckTimerTicks) + blowup = true; + } + + if (blowup) + { + char buffer1[256]; + char buffer2[256]; + dSprintf(buffer1, 255, "%f %f %f", + mRigid.state.linPosition.x, + mRigid.state.linPosition.y, + mRigid.state.linPosition.z); + dSprintf(buffer2, 255, "%d", Con::getIntVariable("$DamageType::Ground")); + Con::executef(mDataBlock, 6, "damageObject", scriptThis(), "0", buffer1, "1000", buffer2); + } + } + } + else + { + if (isServerObject()) + mStuckTimer = 0; + } + + return success; +} + + +void Vehicle::damageQueuedObjects(const F32 collisionVel) +{ + AssertFatal(isServerObject(), "Error, does not happen on the client"); + + F32 damageVal = (collisionVel - mDataBlock->collDamageThresholdVel) * mDataBlock->collDamageMultiplier; + if (damageVal < 0.0f) + damageVal = 0.0f; + + // Notify all the objects that were just stamped during the queueing + // process. + SimTime expireTime = Sim::getCurrentTime() + CollisionTimeoutValue; + + char buffer2[256]; + dSprintf(buffer2, 255, "%d", Con::getIntVariable("$DamageType::Impact")); + char damageBuffer[64]; + dSprintf(damageBuffer, 63, "%f", damageVal); + + for (CollisionTimeout* ptr = mTimeoutList; ptr; ptr = ptr->next) + { + SimObjectPtr safePtr(ptr->object); + SimObjectPtr safeThis(this); + onCollision(ptr->object); + ptr->object = 0; + + if(!bool(safeThis)) + return; + + if(bool(safePtr)) + { + if (ptr->useData == false && damageVal != 0.0f) + { + char buffer1[256]; + dSprintf(buffer1, 255, "%f %f %f", + mRigid.state.linPosition.x, + mRigid.state.linPosition.y, + mRigid.state.linPosition.z); + Con::executef(safePtr->getDataBlock(), 6, "damageObject", + safePtr->scriptThis(), scriptThis(), buffer1, damageBuffer, buffer2); + } + else if (ptr->data > mDataBlock->collDamageThresholdVel) + { + F32 damageValLocal = (ptr->data - mDataBlock->collDamageThresholdVel) * mDataBlock->collDamageMultiplier; + + char buffer1[256]; + char buffer3[64]; + dSprintf(buffer1, 255, "%f %f %f", + mRigid.state.linPosition.x, + mRigid.state.linPosition.y, + mRigid.state.linPosition.z); + dSprintf(buffer3, 63, "%f", damageValLocal); + Con::executef(safePtr->getDataBlock(), 6, "damageObject", + safePtr->scriptThis(), scriptThis(), buffer1, buffer3, buffer2); + } + + if (bool(safePtr) && bool(safeThis)) + safePtr->onCollision(safeThis); + } + + if(!bool(safeThis)) + return; + } + + CollisionTimeout* walk = mTimeoutList; + mTimeoutList = NULL; + while (walk) + { + extern CollisionTimeout* sFreeTimeoutList; + CollisionTimeout* next = walk->next; + walk->next = sFreeTimeoutList; + sFreeTimeoutList = walk; + walk = next; + } +} + +//---------------------------------------------------------------------------- + +bool Vehicle::resolveCollision(Rigid::State& ns, + CollisionList& cList) +{ + // Apply impulses to resolve collision + bool collided = false; + bool colliding; + do { + colliding = false; + for (S32 i = 0; i < cList.count; i++) { + Collision& c = cList.collision[i]; + if (c.distance < sCollisionTol) { + Point3F v,r = c.point - ns.linPosition; + ns.getVelocity(r,&v); + F32 vn = mDot(v,c.normal); + + U32 objectMask = c.object->getTypeMask(); + + if(objectMask & sDirtySetMask) + { + setControlDirty(); + if(objectMask & ShapeBaseObjectType) + static_cast(c.object)->setControlDirty(); + } + + if (vn < -sContactTol) { + mRigid.resolveCollision(ns, + cList.collision[i].point, + cList.collision[i].normal); + colliding = true; + collided = true; + + // Track collisions + if (!isGhost() && c.object->getTypeMask() & ShapeBaseObjectType) + queueCollision(static_cast(c.object)); + } + } + } + } while (colliding); + + return collided; +} + + +//---------------------------------------------------------------------------- + +F32 Vehicle::resolveContacts(Rigid::State& ns,CollisionList& cList,F32 dt) +{ + // Apply impulse to resolve contacts + Point3F t,p(0,0,0),l(0,0,0); + for (S32 i = 0; i < cList.count; i++) { + Collision& c = cList.collision[i]; + if (c.distance < sCollisionTol) { + Point3F v,r = c.point - ns.linPosition; + ns.getVelocity(r,&v); + F32 vn = mDot(v,c.normal); + if (vn > -sContactTol) { + // Penetration force + F32 zi = mRigid.getZeroImpulse(mRigid.state,r,c.normal); + F32 d = (sCollisionTol - c.distance) / sCollisionTol; + F32 s = (d * d) * zi * sF - vn * sD; + Point3F f = c.normal * (s * dt); + + // Frictional force + Point3F uv = v - (c.normal * vn); + F32 ul = uv.len(); + if (s > 0 && ul) { + F32 u = s * mRigid.friction; + f -= uv * (u * dt / ul); + } + + // + p += f; + mCross(r,f,&t); + l += t; + } + } + } + + ns.linMomentum += p; + ns.angMomentum += l; + mRigid.updateVelocity(ns); + return 0; +} + + +//---------------------------------------------------------------------------- + +void Vehicle::updateLiftoffDust( F32 dt ) +{ + if( !mDustEmitterList[0] ) return; + + Point3F startPos = getPosition(); + Point3F endPos = startPos + Point3F( 0.0, 0.0, -mDataBlock->triggerDustHeight ); + + + RayInfo rayInfo; + if( !getContainer()->castRay( startPos, endPos, TerrainObjectType, &rayInfo ) ) + { + return; + } + + TerrainBlock* tBlock = static_cast(rayInfo.object); + S32 mapIndex = tBlock->mMPMIndex[0]; + + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntryFromIndex(mapIndex); + + if(pEntry) + { + S32 x; + ColorF colorList[ParticleEngine::PC_COLOR_KEYS]; + + for(x = 0; x < 2; ++x) + colorList[x].set( pEntry->puffColor[x].red, pEntry->puffColor[x].green, pEntry->puffColor[x].blue, pEntry->puffColor[x].alpha ); + for(x = 2; x < ParticleEngine::PC_COLOR_KEYS; ++x) + colorList[x].set( 1.0, 1.0, 1.0, 0.0 ); + + mDustEmitterList[0]->setColors( colorList ); + } + Point3F contactPoint = rayInfo.point + Point3F( 0.0, 0.0, mDataBlock->dustHeight ); + mDustEmitterList[0]->emitParticles( contactPoint, contactPoint, rayInfo.normal, getVelocity(), 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(), dt * 1000 ); + } + } + else + { + if( mDamageEmitterList[j] ) + { + mDamageEmitterList[j]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), dt * 1000 ); + } + } + } + break; + } + } + +} + + +//---------------------------------------------------------------------------- + +bool Vehicle::writePacketData(GameConnection *connection, BitStream *stream) +{ + bool ret = Parent::writePacketData(connection, stream); + mathWrite(*stream, mSteering); + + mathWrite(*stream, mRigid.state.linPosition); + mathWrite(*stream, mRigid.state.angPosition); + mathWrite(*stream, mRigid.state.linMomentum); + mathWrite(*stream, mRigid.state.angMomentum); + + stream->writeFlag(mDisableMove); + stream->writeFlag(mFrozen); + connection->setCompressionPoint(mRigid.state.linPosition); + return ret; +} + +void Vehicle::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + mathRead(*stream, &mSteering); + + mathRead(*stream, &mRigid.state.linPosition); + mathRead(*stream, &mRigid.state.angPosition); + mathRead(*stream, &mRigid.state.linMomentum); + mathRead(*stream, &mRigid.state.angMomentum); + + mRigid.updateVelocity(mRigid.state); + mDisableMove = stream->readFlag(); + mFrozen = stream->readFlag(); + connection->setCompressionPoint(mRigid.state.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); + + stream->writeFlag(mFrozen); + + if (stream->writeFlag(mask & PositionMask)) + { + con->writeCompressed(stream, mRigid.state.linPosition); + mathWrite(*stream, mRigid.state.angPosition); + mathWrite(*stream, mRigid.state.linMomentum); + mathWrite(*stream, mRigid.state.angMomentum); + } + + // send energy only to clients which need it + bool found = false; + if(mask & EnergyMask) + { + for (ShapeBase* ptr = getMountList(); ptr; ptr = ptr->getMountLink()) + { + if(!dynamic_cast(ptr)) + continue; + + GameConnection * controllingClient = ptr->getControllingClient(); + if(controllingClient == con) + { + if(controllingClient->getControlObject() != this) + found = true; + break; + } + } + } + + // write it... + if(stream->writeFlag(found)) + 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); + + mFrozen = stream->readFlag(); + if (stream->readFlag()) { + F32 speed = mRigid.state.linVelocity.len(); + mDelta.warpRot[0] = mRigid.state.angPosition; + con->readCompressed(stream, &mRigid.state.linPosition); + mathRead(*stream, &mRigid.state.angPosition); + mathRead(*stream, &mRigid.state.linMomentum); + mathRead(*stream, &mRigid.state.angMomentum); + mRigid.updateVelocity(mRigid.state); + + mPredictionCount = sMaxPredictionTicks; + + if (isProperlyAdded() && mFrozen == false) { + // Determin number of ticks to warp based on the average + // of the client and server velocities. + Point3F cp; + mObjToWorld.getColumn(3,&cp); + mDelta.warpOffset = mRigid.state.linPosition - cp; + F32 dt,as = (speed + mRigid.state.linVelocity.len()) * 0.5 * TickSec; + 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.state.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.state.linPosition - mDelta.pos; + mDelta.warpOffset /= mDelta.warpTicks; + mDelta.warpRot[0] = mDelta.rot[1]; + mDelta.warpRot[1] = mRigid.state.angPosition; + } + } + else { + // Set the vehicle to the server position + mDelta.dt = 0; + mDelta.pos = mRigid.state.linPosition; + mDelta.posVec.set(0,0,0); + mDelta.rot[1] = mDelta.rot[0] = mRigid.state.angPosition; + mDelta.warpCount = mDelta.warpTicks = 0; + setPosition(mRigid.state.linPosition, mRigid.state.angPosition); + } + } + + // energy? + if(stream->readFlag()) + setEnergyLevel(stream->readFloat(8) * mDataBlock->maxEnergy); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::initPersistFields() +{ + Parent::initPersistFields(); + + addField("disableMove", TypeBool, Offset(mDisableMove, Vehicle)); +} + + +void Vehicle::mountObject(ShapeBase* 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::updateFroth( F32 dt ) +{ + // update bubbles + Point3F moveDir = getVelocity(); + + Point3F contactPoint; + if( !collidingWithWater( contactPoint ) ) + { + if(waterWakeHandle) + { + alxStop(waterWakeHandle); + waterWakeHandle = 0; + } + return; + } + + F32 speed = moveDir.len(); + if( speed < mDataBlock->splashVelEpsilon ) speed = 0.0; + + U32 emitRate = speed * mDataBlock->splashFreqMod * dt; + + U32 i; + if(!waterWakeHandle) + waterWakeHandle = alxPlay(mDataBlock->waterSound[VehicleData::Wake], &getTransform()); + alxSourceMatrixF(waterWakeHandle, &getTransform()); + + 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.max.z - mObjBox.min.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); +} + +// F32 Vehicle::getHeat() const +// { +// return 1.0; +// } + + +void Vehicle::setFrozenState(const bool _frozen) +{ + mFrozen = _frozen; + setControlDirty(); +} + + +void Vehicle::renderObject(SceneState* state, SceneRenderImage* image) +{ + Parent::renderObject(state, image); + + if (gShowBoundingBox) { + RectI viewport; + dglGetViewport(&viewport); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&getRenderTransform()); + + glDisable(GL_DEPTH_TEST); + //-------------------------------------- + glColor3f(1, 0, 1); + wireCube(Point3F(0.25,0.25,0.25),Point3F(0,0,0)); + + glColor3f(1, 1, 1); + wireCube(Point3F(0.25,0.25,0.25),mDataBlock->massCenter); + + //-------------------------------------- + glEnable(GL_DEPTH_TEST); + + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + + // Show some collision points... + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + state->setupObjectProjection(this); + + ConvexFeature fa; + MatrixF mat; + mRigid.state.getTransform(&mat); + mConvex.getFeatures(mat, Point3F(0, -1, 0), &fa); + + glDisable(GL_DEPTH_TEST); + glColor3f(1, 0, 1); + for (U32 i = 0; i < fa.mVertexList.size(); i++) + { + wireCube(Point3F(0.25, 0.25, 0.25), fa.mVertexList[i]); + } + + glColor3f(1, 1, 0); + for (U32 i = 0; i < fa.mEdgeList.size(); i++) + { + glBegin(GL_LINES); + glVertex3fv(fa.mVertexList[fa.mEdgeList[i].vertex[0]]); + glVertex3fv(fa.mVertexList[fa.mEdgeList[i].vertex[1]]); + glEnd(); + } + + ClippedPolyList polyList; + // Planes bounding the square. + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(getWorldBox().min - Point3F(100, 100, 100),VectorF(-1,0,0)); + polyList.mPlaneList[1].set(getWorldBox().max + Point3F(100, 100, 100), VectorF(0,1,0)); + polyList.mPlaneList[2].set(getWorldBox().max - Point3F(100, 100, 100), VectorF(1,0,0)); + polyList.mPlaneList[3].set(getWorldBox().min + Point3F(100, 100, 100),VectorF(0,-1,0)); + polyList.mPlaneList[4].set(getWorldBox().min - Point3F(100, 100, 100),VectorF(0,0,-1)); + polyList.mPlaneList[5].set(getWorldBox().max + Point3F(100, 100, 100),VectorF(0,0,1)); + Box3F dummyBox; + SphereF dummySphere; + buildPolyList(&polyList, dummyBox, dummySphere); + + glColor3f(0, 1, 1); + for (U32 i = 0; i < polyList.mVertexList.size(); i++) + { + wireCube(Point3F(0.25, 0.25, 0.25), polyList.mVertexList[i].point); + } + + glEnable(GL_DEPTH_TEST); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + } +} diff --git a/game/vehicle.h b/game/vehicle.h new file mode 100644 index 0000000..9b18123 --- /dev/null +++ b/game/vehicle.h @@ -0,0 +1,259 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _VEHICLE_H_ +#define _VEHICLE_H_ + +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif +#ifndef _RIGID_H_ +#include "game/rigid.h" +#endif +#ifndef _BOXCONVEX_H_ +#include "collision/boxConvex.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; +class ClippedPolyList; + + +//---------------------------------------------------------------------------- + +struct VehicleData: public ShapeBaseData +{ + typedef ShapeBaseData Parent; + + struct Body { + enum Sounds { + SoftImpactSound, + HardImpactSound, + MaxSounds, + }; + AudioProfile* 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 + }; + AudioProfile* waterSound[MaxSounds]; + F32 exitSplashSoundVel; + F32 softSplashSoundVel; + F32 medSplashSoundVel; + F32 hardSplashSoundVel; + + F32 minImpactSpeed; + F32 softImpactSpeed; + F32 hardImpactSpeed; + F32 minRollSpeed; + F32 maxSteeringAngle; + + F32 collDamageThresholdVel; + F32 collDamageMultiplier; + + F32 cameraLag; + F32 cameraOffset; // Vertical offset + + F32 minDrag; + F32 maxDrag; + + F32 jetForce; + F32 jetEnergyDrain; // Energy drain/tick + F32 minJetEnergy; + + S32 stuckTimerTicks; + F32 stuckTimerAngle; + F32 stuckTimerZ; // calculated in preload... + + 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 ]; + S32 damageEmitterIDList[ VC_NUM_DAMAGE_EMITTERS ]; + Point3F damageEmitterOffset[ VC_NUM_DAMAGE_EMITTER_AREAS ]; + F32 damageLevelTolerance[ VC_NUM_DAMAGE_LEVELS ]; + F32 numDmgEmitterAreas; + + ParticleEmitterData* splashEmitterList[VC_NUM_SPLASH_EMITTERS]; + S32 splashEmitterIDList[VC_NUM_SPLASH_EMITTERS]; + F32 splashFreqMod; + F32 splashVelEpsilon; + + + // Initialized in load() + Point3F massCenter; + + // + VehicleData(); + bool preload(bool server, char errorBuffer[256]); + 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, + FrozenMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2, + EnergyMask = Parent::NextFreeMask << 3 + }; + + 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; + AUDIOHANDLE waterWakeHandle; + + bool mFrozen; + + // Control + Point2F mSteering; + F32 mThrottle; + bool mJetting; + + // Rigid Body + bool mMinRoll; + bool mDisableMove; + + // Stuck-ness timer + S32 mStuckTimer; + + ShapeBaseConvex mConvex; + + Rigid mRigid; + + 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 advanceToCollision(F32 time); + bool resolveCollision(Rigid::State& ns,CollisionList& cList); + F32 resolveContacts(Rigid::State& ns,CollisionList& cList,F32 dt); + void localImpulse(const Point3F &r,const Point3F &impulse); + + void damageQueuedObjects(const F32 speed); + + void setPosition(const Point3F& pos,const QuatF& rot); + void setRenderPosition(const Point3F& pos,const QuatF& rot); + void setTransform(const MatrixF& newMat); + +// virtual bool collideBody(const MatrixF& mat,Collision* info) = 0; + virtual void updateMove(const Move*); + virtual void updateForces(F32 dt); + virtual void updateWarp(); + + bool writePacketData(GameConnection *, BitStream *stream); + void readPacketData(GameConnection *, BitStream *stream); + U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *con, 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 ); + + void renderObject(SceneState*, SceneRenderImage*); + + public: + // Test code... + static ClippedPolyList* sPolyList; + static S32 sVehicleCount; + + // + Vehicle(); + static void initPersistFields(); + void processTick(const Move*); + bool onAdd(); + void onRemove(); + void interpolateTick(F32 dt); + void advanceTime(F32 dt); + + void disableCollision(); + void enableCollision(); + + Point3F getVelocity() const; + + void setEnergyLevel(F32 energy); + //F32 getHeat() const; + + void setFrozenState(const bool _frozen); + + // Rigid body methods + void getVelocity(const Point3F& r, Point3F* vel); + void applyImpulse(const Point3F &r,const Point3F &impulse); + F32 getImpulse(const Point3F& r,const Point3F& normal); + + void getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot); + void mountObject(ShapeBase* obj, U32 node); + + DECLARE_CONOBJECT(Vehicle); + static void consoleInit(); +}; + + +#endif diff --git a/game/vehicleBlocker.cc b/game/vehicleBlocker.cc new file mode 100644 index 0000000..4cd9996 --- /dev/null +++ b/game/vehicleBlocker.cc @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/vehicleBlocker.h" +#include "core/bitStream.h" +#include "dgl/dgl.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() +{ + Parent::initPersistFields(); + addField("dimensions", TypePoint3F, Offset(mDimensions, VehicleBlocker)); +} + + +void VehicleBlocker::consoleInit() +{ + // +} + +//-------------------------------------------------------------------------- +bool VehicleBlocker::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.min.set(-mDimensions.x, -mDimensions.y, 0); + mObjBox.max.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.min.set(-mDimensions.x, -mDimensions.y, 0); + mObjBox.max.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/game/vehicleBlocker.h b/game/vehicleBlocker.h new file mode 100644 index 0000000..8acf860 --- /dev/null +++ b/game/vehicleBlocker.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _VEHICLEBLOCKER_H_ +#define _VEHICLEBLOCKER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sim/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(); + static void consoleInit(); + + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); +}; + +#endif // _H_VEHICLEBLOCKER diff --git a/game/vehicles/flyingVehicle.cc b/game/vehicles/flyingVehicle.cc new file mode 100644 index 0000000..97ea182 --- /dev/null +++ b/game/vehicles/flyingVehicle.cc @@ -0,0 +1,748 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/vehicles/flyingVehicle.h" + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "game/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 "game/moveManager.h" +#include "core/bitStream.h" +#include "core/dnet.h" +#include "game/gameConnection.h" +#include "ts/tsShapeInstance.h" +#include "game/particleEngine.h" +#include "audio/audio.h" +#include "game/missionArea.h" + +//---------------------------------------------------------------------------- + +const static U32 sCollisionMoveMask = (TerrainObjectType | InteriorObjectType | + WaterObjectType | PlayerObjectType | + StaticShapeObjectType | VehicleObjectType | + VehicleBlockerObjectType | ForceFieldObjectType | + StaticTSObjectType); +static U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType +static U32 sClientCollisionMask = sCollisionMoveMask; + +static F32 sFlyingVehicleGravity = -20; + +// Sound +static F32 sIdleEngineVolume = 0.2; + +// +const char* FlyingVehicle::sJetSequence[FlyingVehicle::JetAnimCount] = +{ + "activateBack", + "maintainBack", + "activateBot", + "maintainBot", +}; + +const char* FlyingVehicleData::sJetNode[FlyingVehicleData::MaxJetNodes] = +{ + "JetNozzle0", // Thrust Forward + "JetNozzle1", + "JetNozzleX", // Thrust Backward + "JetNozzleX", + "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; + 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, char errorBuffer[256]) +{ + if (!Parent::preload(server, errorBuffer)) + return false; + + TSShapeInstance* si = new TSShapeInstance(shape,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] = shape->findNode(sJetNode[j]); + + // + maxSpeed = maneuveringForce / minDrag; + + delete si; + return true; +} + +void FlyingVehicleData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("jetSound", TypeAudioProfilePtr, Offset(sound[JetSound], FlyingVehicleData)); + addField("engineSound", TypeAudioProfilePtr, 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)); +} + +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] = (AudioProfile*)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() +{ + mGenerateShadow = true; + + 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() +{ + if (mJetSound) + alxStop(mJetSound); + if (mEngineSound) + alxStop(mEngineSound); +} + + +//---------------------------------------------------------------------------- + +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 (mJetSound) { + alxStop(mJetSound); + mJetSound = 0; + } + if (mEngineSound) { + alxStop(mEngineSound); + mEngineSound = 0; + } + if (isGhost()) { + if (mDataBlock->sound[FlyingVehicleData::EngineSound]) + mEngineSound = alxPlay(mDataBlock->sound[FlyingVehicleData::EngineSound], &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() +{ + scriptOnRemove(); + removeFromScene(); + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- +void FlyingVehicle::updateWarp() +{ +} + +void FlyingVehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + updateEngineSound(1); + updateJet(dt); +} + + +//---------------------------------------------------------------------------- + +void FlyingVehicle::updateMove(const Move* move) +{ + Parent::updateMove(move); + + if (move == &NullMove) + mSteering.set(0,0); + + F32 speed = mRigid.state.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("MissionArea")); + mCeilingFactor = 1.0f; + if (obj != NULL) + { + F32 flightCeiling = obj->getFlightCeiling(); + F32 ceilingRange = obj->getFlightCeilingRange(); + + if (mRigid.state.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.state.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; +} + + +//---------------------------------------------------------------------------- + +Point3F JetOffset[4] = +{ + Point3F(-1,-1,0), + Point3F(+1,-1,0), + Point3F(-1,+1,0), + Point3F(+1,+1,0) +}; + +void FlyingVehicle::updateForces(F32 /*dt*/) +{ + MatrixF currPosMat; + mRigid.state.getTransform(&currPosMat); + + 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.state.linVelocity.len(); + + Point3F force = Point3F(0, 0, sFlyingVehicleGravity * mMass * mGravityMod); + Point3F torque = Point3F(0, 0, 0); + + // Drag at any speed + force -= mRigid.state.linVelocity * mDataBlock->minDrag; + torque -= mRigid.state.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.state.linVelocity); + force -= xv * sf * mDot(xv, mRigid.state.linVelocity); + } + + // Hovering Jet +// if (!mBuoyancy) { + F32 vf = -sFlyingVehicleGravity * mMass * 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 * speed * mDot(xv,mRigid.state.linVelocity) * mDataBlock->horizontalSurfaceForce; + force -= zv * speed * mDot(zv,mRigid.state.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.state.linVelocity); + torque += yv * ar; + + force += mAppliedForce; + force -= Point3F(0, 0, 1) * (mBuoyancy * sFlyingVehicleGravity * mGravityMod); + force -= mRigid.state.linVelocity * mDrag; + +// // Add in force from physical zones... +// force += mAppliedForce; +// +//// // Container buoyancy & drag +//// mRigid.state.linVelocity.z -= mBuoyancy * sFlyingVehicleGravity * mGravityMod * TickSec; +//// mRigid.state.linVelocity -= mRigid.state.linVelocity * mDrag * TickSec; +//// mRigid.state.angVelocity -= mRigid.state.angVelocity * mDrag * TickSec; + + mRigid.state.force = force * mOneOverMass; + mRigid.state.torque = torque * mOneOverMass; +} + + +//---------------------------------------------------------------------------- + +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,-1,&collision)) + 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) { + alxSourceMatrixF(mEngineSound, &getTransform()); + alxSourcef(mEngineSound, AL_GAIN_LINEAR, 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: -1); + 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: -1); + 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.state.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 (!mDataBlock->sound[FlyingVehicleData::JetSound]) + return; + if (!mJetting) { + if (mJetSound) { + alxStop(mJetSound); + mJetSound = 0; + } + } + else { + if (!mJetSound) + mJetSound = alxPlay(mDataBlock->sound[FlyingVehicleData::JetSound], &getTransform()); + + alxSourceMatrixF(mJetSound, &getTransform()); + } +} + +//---------------------------------------------------------------------------- + +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(),dt * 1000); + } + } + else { + for (S32 j = idx; j < idx + count; j++) + if (bool(mJetEmitter[j])) { + mJetEmitter[j]->deleteWhenEmpty(); + mJetEmitter[j] = 0; + } + } +} + + +//---------------------------------------------------------------------------- + +bool FlyingVehicle::writePacketData(GameConnection *connection, BitStream *stream) +{ + return Parent::writePacketData(connection, stream); +} + +void FlyingVehicle::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + + setPosition(mRigid.state.linPosition,mRigid.state.angPosition); + mDelta.pos = mRigid.state.linPosition; + mDelta.rot[1] = mRigid.state.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(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(getControllingClient() == con) + return; + + createHeightOn = stream->readFlag(); + + mThrustDirection = ThrustDirection(stream->readInt(NumThrustBits)); +} + +void FlyingVehicle::initPersistFields() +{ + Parent::initPersistFields(); +} + +void cUseCreateHeight(SimObject *obj, S32, const char **argv) +{ + FlyingVehicle *ctrl = static_cast(obj); + ctrl->useCreateHeight(dAtob(argv[2])); +} + +void FlyingVehicle::consoleInit() +{ + Con::addCommand("FlyingVehicle", "useCreateHeight", cUseCreateHeight, "FlyingVehicle.setCreateHeight(bool)", 3, 3); +} + +void FlyingVehicle::useCreateHeight(bool val) +{ + createHeightOn = val; + setMaskBits(HoverHeight); +} diff --git a/game/vehicles/flyingVehicle.h b/game/vehicles/flyingVehicle.h new file mode 100644 index 0000000..174dcd6 --- /dev/null +++ b/game/vehicles/flyingVehicle.h @@ -0,0 +1,187 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FLYINGVEHICLE_H_ +#define _FLYINGVEHICLE_H_ + +#ifndef _VEHICLE_H_ +#include "game/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, + }; + AudioProfile* 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, char errorBuffer[256]); + void packData(BitStream* stream); + void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class FlyingVehicle: public Vehicle +{ + typedef Vehicle Parent; + + FlyingVehicleData* mDataBlock; + + AUDIOHANDLE mJetSound; + AUDIOHANDLE 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 updateWarp(); + 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); + + bool writePacketData(GameConnection *, BitStream *stream); + void readPacketData(GameConnection *, BitStream *stream); + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); + void useCreateHeight(bool val); + static void consoleInit(); +}; + + +#endif diff --git a/game/vehicles/hoverVehicle.cc b/game/vehicles/hoverVehicle.cc new file mode 100644 index 0000000..0e2fd8f --- /dev/null +++ b/game/vehicles/hoverVehicle.cc @@ -0,0 +1,974 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/vehicles/hoverVehicle.h" + +#include "core/bitStream.h" +#include "dgl/dgl.h" +#include "sceneGraph/sceneState.h" +#include "collision/clippedPolyList.h" +#include "collision/planeExtractor.h" +#include "game/moveManager.h" +#include "ts/tsShapeInstance.h" +#include "console/consoleTypes.h" +#include "terrain/terrData.h" +#include "sceneGraph/sceneGraph.h" +#include "audio/audio.h" +#include "game/particleEngine.h" +#include "math/mathIO.h" +#include "dgl/materialPropertyMap.h" +#include "terrain/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 | ForceFieldObjectType); + +const U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType +const U32 sClientCollisionMask = sCollisionMoveMask; + +void nonFilter(SceneObject* object,S32 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.25; + floatingThrustFactor = 0.15; + + mainThrustForce = 0; + reverseThrustForce = 0; + strafeThrustForce = 0; + turboFactor = 1.0; + + stabLenMin = 0.5; + stabLenMax = 2.0; + stabSpringConstant = 30; + stabDampingConstant = 10; + + gyroDrag = 10; + normalForce = 30; + restorativeForce = 10; + steeringForce = 25; + rollForce = 2.5; + pitchForce = 2.5; + + dustTrailEmitter = NULL; + dustTrailID = 0; + dustTrailOffset.set( 0.0, 0.0, 0.0 ); + dustTrailFreqMod = 15.0; + triggerTrailHeight = 2.5; + + 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() +{ + Parent::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", TypeAudioProfilePtr, Offset(sound[JetSound], HoverVehicleData)); + addField("engineSound", TypeAudioProfilePtr, Offset(sound[EngineSound], HoverVehicleData)); + addField("floatSound", TypeAudioProfilePtr, 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)); +} + + +//-------------------------------------------------------------------------- +bool HoverVehicleData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + return true; +} + + +bool HoverVehicleData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == 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] = shape->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()? + (AudioProfile*) 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_AUDIOHANDLE; + mEngineSound = NULL_AUDIOHANDLE; + mFloatSound = NULL_AUDIOHANDLE; + + mDustTrailEmitter = NULL; + + mBackMaintainOn = false; + for (S32 i = 0; i < JetAnimCount; i++) + mJetThread[i] = 0; +} + +HoverVehicle::~HoverVehicle() +{ + // +} + +//-------------------------------------------------------------------------- +void HoverVehicle::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void HoverVehicle::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +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() +{ + scriptOnRemove(); + removeFromScene(); + + if (mJetSound != NULL_AUDIOHANDLE) + alxStop(mJetSound); + if (mEngineSound != NULL_AUDIOHANDLE) + alxStop(mEngineSound); + if (mFloatSound != NULL_AUDIOHANDLE) + alxStop(mFloatSound); + + Parent::onRemove(); +} + + +bool HoverVehicle::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; +} + + + +//-------------------------------------------------------------------------- +void HoverVehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + // Update jetsound... + if (mJetting) { + if (mJetSound == NULL_AUDIOHANDLE && mDataBlock->sound[HoverVehicleData::JetSound] != NULL) + mJetSound = alxPlay(mDataBlock->sound[HoverVehicleData::JetSound], &getTransform()); + + if (mJetSound != NULL_AUDIOHANDLE) + alxSourceMatrixF(mJetSound, &getTransform()); + } else { + if (mJetSound != NULL_AUDIOHANDLE) { + alxStop(mJetSound); + mJetSound = NULL_AUDIOHANDLE; + } + } + + // Update engine sound... + if (mEngineSound == NULL_AUDIOHANDLE && mDataBlock->sound[HoverVehicleData::EngineSound] != NULL) + mEngineSound = alxPlay(mDataBlock->sound[HoverVehicleData::EngineSound], &getTransform()); + if (mEngineSound != NULL_AUDIOHANDLE) { + alxSourceMatrixF(mEngineSound, &getTransform()); + + F32 denom = mDataBlock->mainThrustForce + mDataBlock->strafeThrustForce; + F32 factor = getMin(mThrustLevel, denom) / denom; + F32 vol = 0.25 + factor * 0.75; + alxSourcef(mEngineSound, AL_GAIN_LINEAR, vol); + } + + // Are we floating? If so, start the floating sound... + if (mFloating) { + if (mFloatSound == NULL_AUDIOHANDLE && mDataBlock->sound[HoverVehicleData::FloatSound] != NULL) + mFloatSound = alxPlay(mDataBlock->sound[HoverVehicleData::FloatSound], &getTransform()); + + if (mFloatSound != NULL_AUDIOHANDLE) + alxSourceMatrixF(mFloatSound, &getTransform()); + } else { + if (mFloatSound != NULL_AUDIOHANDLE) { + alxStop(mFloatSound); + mFloatSound = NULL_AUDIOHANDLE; + } + } + 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.state.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*/) +{ + Point3F gravForce(0, 0, sHoverVehicleGravity * mGravityMod); + + MatrixF currTransform; + mRigid.state.getTransform(&currTransform); + + 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.state.linVelocity; + F32 baseStabLen = getBaseStabilizerLength(); + Point3F stabExtend(0, 0, -baseStabLen); + currTransform.mulV(stabExtend); + + StabPoint stabPoints[2]; + stabPoints[0].osPoint = Point3F((mObjBox.min.x + mObjBox.min.x) * 0.5, + mObjBox.max.y, + (mObjBox.min.z + mObjBox.max.z) * 0.5); + stabPoints[1].osPoint = Point3F((mObjBox.min.x + mObjBox.min.x) * 0.5, + mObjBox.min.y, + (mObjBox.min.z + mObjBox.max.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.state.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; + } + + // Check the waterblock directly + SimpleQueryList sql; + mSceneManager->getWaterObjectList(sql); + for (U32 i = 0; i < sql.mList.size(); i++) + { + WaterBlock* pBlock = static_cast(sql.mList[i]); + if (pBlock->isPointSubmerged(stabPoints[j].wsPoint)) + { + compression[j] = baseStabLen; + break; + } + } + } + + 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.state.linVelocity.len(); + if (mThrottle == 0.0f && + mLeftThrust == 0.0f && + mRightThrust == 0.0f && + vellen != 0.0f && + vellen < mDataBlock->brakingActivationSpeed) + { + Point3F dir = mRigid.state.linVelocity; + dir.normalize(); + dir.neg(); + force += dir * mDataBlock->brakingForce; + } + + // Gyro Drag + torque = -mRigid.state.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.state.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 * mGravityMod); + force -= mRigid.state.linVelocity * mDrag; + torque -= mRigid.state.angMomentum * mDrag; + + mRigid.state.force = force; + mRigid.state.torque = torque; +} + + +void HoverVehicle::updateWarp() +{ + // +} + + + +//-------------------------------------------------------------------------- +U32 HoverVehicle::getCollisionMask() +{ + if (isServerObject()) + return sServerCollisionMask; + else + return sClientCollisionMask; +} + + +//bool HoverVehicle::collideBody(const MatrixF& mat,Collision* info) +//{ +// // Database bounding box +// Box3F wBox = mObjBox; +// mat.mul(wBox); +// +// // Test the body against the database +// Box3F box; +// SphereF sphere; +// MatrixF imat(1); +// PlaneExtractorPolyList extractor; +// sPolyList->mPlaneList.clear(); +// extractor.mPlaneList = &sPolyList->mPlaneList; +// extractor.setTransform(&mat, VectorF(1,1,1)); +// mShapeInstance->buildPolyList(&extractor,mDataBlock->collisionDetails[0]); +// +// sPolyList->clear(); +// // 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) { +// pConvex->getPolyList(sPolyList); +// } +// pList = pList->wLink.mNext; +// } +// +// info->face = sPolyList->mPolyList.size() ? BodyCollision : 0; +// +// // Pick best collision point +// F32 bestDist = 1.0E30; +// ClippedPolyList::Poly* bestPoly = 0; +// ClippedPolyList::Vertex* bestVertex; +// +// if (sPolyList->mPolyList.size()) { +// Point3F massCenter; +// mat.mulP(mDataBlock->massCenter,&massCenter); +// +// // Pick surface with best vertex velocity +// F32 bestVd = -1; +// ClippedPolyList::Poly* poly = sPolyList->mPolyList.begin(); +// ClippedPolyList::Poly* end = sPolyList->mPolyList.end(); +// for (; poly != end; poly++) { +// U32* vi = &sPolyList->mIndexList[poly->vertexStart]; +// U32* ve = vi + poly->vertexCount; +// for (; vi != ve; vi++) { +// ClippedPolyList::Vertex* ev = &sPolyList->mVertexList[*vi]; +// +// VectorF v,r; +// r = ev->point - massCenter; +// getVelocity(r,&v); +// +// F32 dist = mDot(poly->plane,v); +// if (dist < 0 && dist < bestDist) { +// bestDist = dist; +// bestVertex = ev; +// bestPoly = poly; +// } +// } +// } +// } +// +// if (bestPoly) { +// info->point = bestVertex->point; +// info->object = bestPoly->object; +// info->normal = bestPoly->plane; +// info->material = bestPoly->material; +// return true; +// } +// +// return false; +//} + +void HoverVehicle::updateDustTrail( F32 dt ) +{ + if( !mDustTrailEmitter ) return; + + // check if close to ground + Point3F startPos = getPosition(); + Point3F endPos = startPos + Point3F( 0.0, 0.0, -mDataBlock->triggerTrailHeight ); + + RayInfo rayInfo; + if( !getContainer()->castRay( startPos, endPos, TerrainObjectType, &rayInfo ) ) + { + return; + } + + VectorF vel = getVelocity(); + + TerrainBlock* tBlock = static_cast(rayInfo.object); + S32 mapIndex = tBlock->mMPMIndex[0]; + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntryFromIndex(mapIndex); + + // emit dust if moving + if( vel.len() > 2.0 && pEntry) + { + VectorF axis = vel; + axis.normalize(); + + S32 x; + ColorF colorList[ParticleEngine::PC_COLOR_KEYS]; + + for(x = 0; x < 2; ++x) + colorList[x].set( pEntry->puffColor[x].red, pEntry->puffColor[x].green, pEntry->puffColor[x].blue, pEntry->puffColor[x].alpha ); + for(x = 2; x < ParticleEngine::PC_COLOR_KEYS; ++x) + colorList[x].set( 1.0, 1.0, 1.0, 0.0 ); + + mDustTrailEmitter->setColors( colorList ); + + Point3F contactPoint = rayInfo.point + mDataBlock->dustTrailOffset; + mDustTrailEmitter->emitParticles( contactPoint , true, axis, vel, dt * 1000 * (vel.len() / mDataBlock->dustTrailFreqMod) ); + } + +} + +void HoverVehicle::updateJet(F32 dt) +{ + if (mJetThread[BackActivate] == NULL) + return; + + F32 pos = mShapeInstance->getPos(mJetThread[BackActivate]); + F32 scale = mShapeInstance->getTimeScale(mJetThread[BackActivate]); + + // 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: -1); + 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]); + + // 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(),dt * 1000); + } + } + else { + for (S32 j = idx; j < idx + count; j++) + if (bool(mJetEmitter[j])) { + mJetEmitter[j]->deleteWhenEmpty(); + mJetEmitter[j] = 0; + } + } +} diff --git a/game/vehicles/hoverVehicle.h b/game/vehicles/hoverVehicle.h new file mode 100644 index 0000000..03f2b91 --- /dev/null +++ b/game/vehicles/hoverVehicle.h @@ -0,0 +1,200 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HOVERVEHICLE_H_ +#define _HOVERVEHICLE_H_ + +#ifndef _VEHICLE_H_ +#include "game/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 + }; + AudioProfile* 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, char errorBuffer[256]); + + 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*); + void updateDustTrail( F32 dt ); + + // Vehicle overrides + protected: + void updateMove(const Move *move); + void updateWarp(); + + // Physics + protected: + void updateForces(F32); + F32 getBaseStabilizerLength() const; + + bool mFloating; + F32 mThrustLevel; + + F32 mForwardThrust; + F32 mReverseThrust; + F32 mLeftThrust; + F32 mRightThrust; + + AUDIOHANDLE mJetSound; + AUDIOHANDLE mEngineSound; + AUDIOHANDLE 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); + + DECLARE_CONOBJECT(HoverVehicle); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +#endif // _H_HOVERVEHICLE + diff --git a/game/vehicles/vehicle.cc b/game/vehicles/vehicle.cc new file mode 100644 index 0000000..257264e --- /dev/null +++ b/game/vehicles/vehicle.cc @@ -0,0 +1,1728 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/vehicles/vehicle.h" + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "game/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 "game/moveManager.h" +#include "core/bitStream.h" +#include "core/dnet.h" +#include "game/gameConnection.h" +#include "ts/tsShapeInstance.h" +#include "game/particleEngine.h" +#include "audio/audio.h" +#include "math/mathIO.h" +#include "sceneGraph/sceneState.h" +#include "terrain/terrData.h" +#include "dgl/materialPropertyMap.h" +#include "game/player.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; + +const F32 sCollisionTol = 0.07; // Distance to maintain +const F32 sIntersectionTol = 0.01; // Min collision distance +const F32 sContactTol = 0.5; // Collision contact velocity +const F32 sF = 400; // Spring Force +const F32 sD = 2; // Spring Damping + +} // namespace {} + +S32 Vehicle::sVehicleCount = 0; +ClippedPolyList* Vehicle::sPolyList; +static U32 sDirtySetMask = (ForceFieldObjectType | + PlayerObjectType | VehicleObjectType); + +IMPLEMENT_CONOBJECT(VehicleData); + +ConsoleMethod(Vehicle, setFrozenState, void, 3, 3, "[obj].setFrozenState(t|f)") +{ + argc; + AssertFatal(dynamic_cast(object) != NULL, "Error, must be a vehicle here!"); + Vehicle* pVeh = static_cast(object); + pVeh->setFrozenState(dAtob(argv[2])); +} + + +//---------------------------------------------------------------------------- + +VehicleData::VehicleData() +{ + body.friction = 0; + body.restitution = 1; + + minImpactSpeed = 25; + softImpactSpeed = 25; + hardImpactSpeed = 50; + minRollSpeed = 0; + maxSteeringAngle = 0.785; // 45 deg. + + cameraOffset = 0; + cameraLag = 0; + + minDrag = 0; + maxDrag = 0; + + jetForce = 500; + jetEnergyDrain = 0.8; + minJetEnergy = 1; + + massCenter.set(0,0,0); + drag = 0.7; + 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( 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; + + genericShadowLevel = Vehicle_GenericShadowLevel; + noShadowLevel = Vehicle_NoShadowLevel; + + dMemset(waterSound, 0, sizeof(waterSound)); + + collDamageThresholdVel = 20; + collDamageMultiplier = 0.05; + + stuckTimerTicks = 1; + stuckTimerAngle = 180; +} + + +//---------------------------------------------------------------------------- + +bool VehicleData::preload(bool server, char errorBuffer[256]) +{ + if (!Parent::preload(server, errorBuffer)) + 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 (server) + { + if (stuckTimerTicks <= 0) + { + Con::warnf("VehicleData::preload: stuck timer ticks must be >= 1"); + stuckTimerTicks = 1; + } + if (stuckTimerAngle < 0.0 || stuckTimerAngle > 180.0) + { + Con::warnf("VehicleData::preload: stuck timer angle must be in range [0, 180]"); + stuckTimerAngle = stuckTimerAngle < 0.0 ? 0.0 : 180.0; + } + stuckTimerZ = mCos(stuckTimerAngle); + } + + +// // Center of mass in object space +// S32 mass = shape->findNode("mass"); +// if (mass != -1) { +// massCenter = shape->nodeStates[mass].transform.getTranslate(); +// massCenter.x = 0; +// } + massCenter.set(0, 0, 0); + + 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(jetForce); + stream->write(jetEnergyDrain); + stream->write(minJetEnergy); + + stream->write(cameraOffset); + stream->write(cameraLag); + + 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] = (AudioProfile*)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(&jetForce); + stream->read(&jetEnergyDrain); + stream->read(&minJetEnergy); + + stream->read(&cameraOffset); + stream->read(&cameraLag); + + 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() +{ + Parent::initPersistFields(); + + addField("jetForce", TypeF32, Offset(jetForce, VehicleData)); + addField("jetEnergyDrain", TypeF32, Offset(jetEnergyDrain, VehicleData)); + addField("minJetEnergy", TypeF32, Offset(minJetEnergy, VehicleData)); + + addField("bodyRestitution", TypeF32, Offset(body.restitution, VehicleData)); + addField("bodyFriction", TypeF32, Offset(body.friction, VehicleData)); + addField("softImpactSound", TypeAudioProfilePtr, Offset(body.sound[Body::SoftImpactSound], VehicleData)); + addField("hardImpactSound", TypeAudioProfilePtr, 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("maxSteerinAngle", TypeF32, Offset(maxSteeringAngle, VehicleData)); + + addField("maxDrag", TypeF32, Offset(maxDrag, VehicleData)); + addField("minDrag", TypeF32, Offset(minDrag, VehicleData)); + + addField("cameraOffset", TypeF32, Offset(cameraOffset, VehicleData)); + addField("cameraLag", TypeF32, Offset(cameraLag, 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", TypeAudioProfilePtr, Offset(waterSound[ExitWater], VehicleData)); + addField("impactWaterEasy", TypeAudioProfilePtr, Offset(waterSound[ImpactSoft], VehicleData)); + addField("impactWaterMedium", TypeAudioProfilePtr, Offset(waterSound[ImpactMedium], VehicleData)); + addField("impactWaterHard", TypeAudioProfilePtr, Offset(waterSound[ImpactHard], VehicleData)); + addField("waterWakeSound", TypeAudioProfilePtr, Offset(waterSound[Wake], VehicleData)); + + addField("collDamageThresholdVel", TypeF32, Offset(collDamageThresholdVel, VehicleData)); + addField("collDamageMultiplier", TypeF32, Offset(collDamageMultiplier, VehicleData)); + + addField("stuckTimerTicks", TypeS32, Offset(stuckTimerTicks, VehicleData)); + addField("stuckTimerAngle", TypeF32, Offset(stuckTimerAngle, VehicleData)); +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(Vehicle); + +Vehicle::Vehicle() +{ + 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.state.linPosition.set(0, 0, 0); + mRigid.state.linVelocity.set(0, 0, 0); + mRigid.state.angPosition.identity(); + mRigid.state.angVelocity.set(0, 0, 0); + mRigid.state.linMomentum.set(0, 0, 0); + mRigid.state.angMomentum.set(0, 0, 0); + + mMinRoll = false; + + mSteering.set(0,0); + mThrottle = 0; + mJetting = false; + + dMemset( mDustEmitterList, 0, sizeof( mDustEmitterList ) ); + dMemset( mDamageEmitterList, 0, sizeof( mDamageEmitterList ) ); + dMemset( mSplashEmitterList, 0, sizeof( mSplashEmitterList ) ); + + mDisableMove = false; + + inLiquid = false; + mFrozen = false; + waterWakeHandle = 0; + + mStuckTimer = 0; +} + + +void Vehicle::consoleInit() +{ + +} + +void Vehicle::updateWarp() +{ + AssertFatal(false, "Pure virtual (sorta) function called!"); +} + +U32 Vehicle::getCollisionMask() +{ + AssertFatal(false, "Pure virtual (sorta) function called!"); + return 0; +} + +Point3F Vehicle::getVelocity() const +{ + return mRigid.state.linVelocity; +} + +//---------------------------------------------------------------------------- + +bool Vehicle::onAdd() +{ + if (!sVehicleCount++) + sPolyList = new ClippedPolyList; + + if (!Parent::onAdd()) + return false; + + mRigid.state.setTransform(mObjToWorld); + mRigid.mass = 1; + mRigid.oneOverMass = 1 / mRigid.mass; + mRigid.setObjectInertia((mObjBox.max - mObjBox.min) * 0.5); + + mDelta.rot[1] = mDelta.rot[0] = mRigid.state.angPosition; + mDelta.pos = mRigid.state.linPosition; + mDelta.posVec = Point3F(0,0,0); + + if( !isServerObject() ) + { + 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.min.convolve(mObjScale); + mConvex.box.max.convolve(mObjScale); + + return true; +} + +void Vehicle::onRemove() +{ + if (!--sVehicleCount) { + delete sPolyList; + sPolyList = 0; + } + + 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) +{ + 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]); + updateWarp(); + + // 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; + } + + if (mFrozen == false) + { + updateWorkingCollisionSet(getCollisionMask()); + updateMove(move); + + mDelta.posVec = mRigid.state.linPosition; + mDelta.rot[0] = mRigid.state.angPosition; + + for (U32 i = 0; i < 1; i++) { + mRigid.clearForces(); + updateForces(TickSec); + updatePos(TickSec); + } + + mDelta.pos = mRigid.state.linPosition; + mDelta.posVec -= mRigid.state.linPosition; + mDelta.rot[1] = mRigid.state.angPosition; + + setPosition(mRigid.state.linPosition, mRigid.state.angPosition); + setMaskBits(PositionMask); + updateContainer(); + } + else + { + mDelta.posVec = mRigid.state.linPosition; + mDelta.rot[0] = mRigid.state.angPosition; + mDelta.pos = mRigid.state.linPosition; + mDelta.posVec -= mRigid.state.linPosition; + mDelta.rot[1] = mRigid.state.angPosition; + setPosition(mRigid.state.linPosition, mRigid.state.angPosition); + } + } +} + +void Vehicle::interpolateTick(F32 dt) +{ + Parent::interpolateTick(dt); + + if (mFrozen == false) + { + 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; + } + else + { + mDelta.dt = 0; + } +} + +void Vehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + updateLiftoffDust( dt ); + updateDamageSmoke( dt ); + + updateFroth(dt); +} + +//---------------------------------------------------------------------------- + +bool Vehicle::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + 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::getVelocity(const Point3F& r, Point3F* v) +{ + mRigid.state.getVelocity(r, v); +} + +void Vehicle::applyImpulse(const Point3F &pos, const Point3F &impulse) +{ + Point3F r = pos, massCenter; + mObjToWorld.mulP(mDataBlock->massCenter,&massCenter); + r -= massCenter; + localImpulse(r, impulse); +} + +void Vehicle::localImpulse(const Point3F &r,const Point3F &impulse) +{ + Point3F actualImpulse = impulse * mOneOverMass; + mRigid.applyImpulse(mRigid.state, r, actualImpulse); +} + +F32 Vehicle::getImpulse(const Point3F& r,const Point3F& normal) +{ + // Returns impulse value need to stop velocity along the + // given normal. + Point3F v; + getVelocity(r,&v); + F32 n = -mDot(v,normal); + + Point3F a,b; + mCross(r,normal,&a); + mCross(a,r,&b); + F32 d = mRigid.oneOverMass + (mDot(b,normal) * mRigid.oneOverMass); + + return n/d; +} + + +//---------------------------------------------------------------------------- +void Vehicle::updateWorkingCollisionSet(const U32 mask) +{ + // 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 + Point3F scaledVelocity = mRigid.state.linVelocity * TickSec; + F32 len = scaledVelocity.len(); + F32 newLen = len + (50 * 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.1) + 0.1; // fudge factor + convexBox.min -= Point3F(l, l, l); + convexBox.max += Point3F(l, l, l); + + disableCollision(); + mConvex.updateWorkingList(convexBox, mask); + enableCollision(); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::disableCollision() +{ + Parent::disableCollision(); + for (ShapeBase* ptr = getMountList(); ptr; ptr = ptr->getMountLink()) + ptr->disableCollision(); +} + +void Vehicle::enableCollision() +{ + Parent::enableCollision(); + for (ShapeBase* ptr = getMountList(); ptr; ptr = ptr->getMountLink()) + ptr->enableCollision(); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::updateMove(const Move* move) +{ + 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 + 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; + } + + if (!isGhost()) { + if(!inLiquid && mWaterCoverage != 0.0f) { + Con::executef(mDataBlock,4,"onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), Con::getIntArg(mLiquidType)); + inLiquid = true; + mHeat = 0.0; + } + else if(inLiquid && mWaterCoverage == 0.0f) { + Con::executef(mDataBlock,3,"onLeaveLiquid",scriptThis(), Con::getIntArg(mLiquidType)); + inLiquid = false; + mHeat = 1.0; + } + } + else { + F32 vSpeed = getVelocity().len(); + if(!inLiquid && mWaterCoverage >= 0.8f) { + if(vSpeed >= mDataBlock->hardSplashSoundVel) + alxPlay(mDataBlock->waterSound[VehicleData::ImpactHard], &getTransform()); + else if( vSpeed >= mDataBlock->medSplashSoundVel) + alxPlay(mDataBlock->waterSound[VehicleData::ImpactMedium], &getTransform()); + else if( vSpeed >= mDataBlock->softSplashSoundVel) + alxPlay(mDataBlock->waterSound[VehicleData::ImpactSoft], &getTransform()); + inLiquid = true; + } + else if(inLiquid && mWaterCoverage < 0.8f) { + if(vSpeed >= mDataBlock->exitSplashSoundVel) + alxPlay(mDataBlock->waterSound[VehicleData::ExitWater], &getTransform()); + inLiquid = false; + } + } + mMinRoll = mJetting; +} + + +void Vehicle::updateForces(F32 /*dt*/) +{ + // Nothing here. +} + + +//---------------------------------------------------------------------------- +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.state.setTransform(newMat); + Parent::setTransform(newMat); +} + + +void Vehicle::updatePos(F32 dt) +{ + advanceToCollision(dt); + +// // Check for rest condition +// F32 k = mRigid.getKineticEnergy(mWorldToObj); +// F32 G = -mRigid.state.force.z * mRigid.oneOverMass * TickSec; +// F32 Kg = 0.5 * mRigid.mass * G * G; +// if (k < Kg * sRestTol) +// mRigid.setAtRest(); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- + +bool Vehicle::advanceToCollision(F32 time) +{ + F32 ct = 0,dt = time; + Rigid::State ns = mRigid.state; + + MatrixF mat; + mRigid.state.getTransform(&mat); + CollisionState *state = mConvex.findClosestStateBounded(mat, getScale(), sCollisionTol); + + CollisionList info; + F32 mt = time / 2.0; + dt = mt; + + bool collided = false; + bool displaced = false; + Point3F origVelocity = mRigid.state.linVelocity; + + bool success = true; + do { + F32 prevDist = state != NULL ? state->dist : 1e7; + info.count = 0; + if (state && state->dist < sCollisionTol) { + // Try to displace the object out of the way... + SceneObject* obj = NULL; + if (state->a->getObject() == this) + obj = state->b->getObject(); + else + obj = state->a->getObject(); + AssertFatal(obj != NULL, "Well, that's odd."); + 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.1; + 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.1 * mt) == true) + { + // Determine the speed at which we will damage this object + objOldMom /= obj->getMass(); + objNewMom /= obj->getMass(); + F32 len = (objOldMom - objNewMom).len(); + + queueCollision(static_cast(obj), len); + state = 0; + displaced = true; + continue; + } + } + + mConvex.getCollisionInfo(mat, getScale(), &info, sCollisionTol * 1.25); + collided |= resolveCollision(ns, info); + resolveContacts(ns, info, dt); + if (collided) + { + ns.force.set(0, 0, 0); + ns.torque.set(0, 0, 0); + } + } + + mRigid.integrate(ns,dt); + ns.getTransform(&mat); + + state = mConvex.findClosestStateBounded(mat, getScale(), sCollisionTol); + if (state && state->dist <= sIntersectionTol && state->dist <= prevDist) { + if ((dt *= 0.25) < 0.0001) { + // Make sure we check the collision damage... + collided = true; + success = false; + mRigid.state.linVelocity.set(0,0,0); + mRigid.state.linMomentum.set(0,0,0); + mRigid.state.angVelocity.set(0,0,0); + mRigid.state.angMomentum.set(0,0,0); + goto exitRoutine; + } + + state = 0; + ns = mRigid.state; + continue; + } + + mRigid.state = ns; + ct += dt; + if (dt < mt) + dt *= 1.2; + if (dt > (time - ct)) + dt = time - ct; + } while (ct < time); + +exitRoutine: + if (collided || displaced) + { + F32 collVel = (origVelocity - mRigid.state.linVelocity).len(); + if (origVelocity.isZero() == false) + origVelocity.normalize(); + else + origVelocity = Point3F(0, 0, 1); + + if (isClientObject()) + { + S32 impactSound = -1; + if (collVel >= mDataBlock->hardImpactSpeed) + impactSound = VehicleData::Body::HardImpactSound; + else if (collVel >= mDataBlock->softImpactSpeed) + impactSound = VehicleData::Body::SoftImpactSound; + + if (impactSound != -1 && mDataBlock->body.sound[impactSound] != NULL) + alxPlay(mDataBlock->body.sound[impactSound], &getTransform()); + } + + if (isServerObject()) + { + if (collVel > mDataBlock->minImpactSpeed) + onImpact(origVelocity * collVel); + + damageQueuedObjects(collVel); + + MatrixF mat; + mRigid.state.getTransform(&mat); + Point3F up; + mat.getColumn(2, &up); + bool blowup = false; + if (up.z < -0.25f) + blowup = true; + else + { + if (up.z <= mDataBlock->stuckTimerZ) + mStuckTimer++; + else + mStuckTimer = 0; + + if (mStuckTimer >= mDataBlock->stuckTimerTicks) + blowup = true; + } + + if (blowup) + { + char buffer1[256]; + char buffer2[256]; + dSprintf(buffer1, 255, "%f %f %f", + mRigid.state.linPosition.x, + mRigid.state.linPosition.y, + mRigid.state.linPosition.z); + dSprintf(buffer2, 255, "%d", Con::getIntVariable("$DamageType::Ground")); + Con::executef(mDataBlock, 6, "damageObject", scriptThis(), "0", buffer1, "1000", buffer2); + } + } + } + else + { + if (isServerObject()) + mStuckTimer = 0; + } + + return success; +} + + +void Vehicle::damageQueuedObjects(const F32 collisionVel) +{ + AssertFatal(isServerObject(), "Error, does not happen on the client"); + + F32 damageVal = (collisionVel - mDataBlock->collDamageThresholdVel) * mDataBlock->collDamageMultiplier; + if (damageVal < 0.0f) + damageVal = 0.0f; + + // Notify all the objects that were just stamped during the queueing + // process. + SimTime expireTime = Sim::getCurrentTime() + CollisionTimeoutValue; + + char buffer2[256]; + dSprintf(buffer2, 255, "%d", Con::getIntVariable("$DamageType::Impact")); + char damageBuffer[64]; + dSprintf(damageBuffer, 63, "%f", damageVal); + + for (CollisionTimeout* ptr = mTimeoutList; ptr; ptr = ptr->next) + { + SimObjectPtr safePtr(ptr->object); + SimObjectPtr safeThis(this); + onCollision(ptr->object); + ptr->object = 0; + + if(!bool(safeThis)) + return; + + if(bool(safePtr)) + { + if (ptr->useData == false && damageVal != 0.0f) + { + char buffer1[256]; + dSprintf(buffer1, 255, "%f %f %f", + mRigid.state.linPosition.x, + mRigid.state.linPosition.y, + mRigid.state.linPosition.z); + Con::executef(safePtr->getDataBlock(), 6, "damageObject", + safePtr->scriptThis(), scriptThis(), buffer1, damageBuffer, buffer2); + } + else if (ptr->data > mDataBlock->collDamageThresholdVel) + { + F32 damageValLocal = (ptr->data - mDataBlock->collDamageThresholdVel) * mDataBlock->collDamageMultiplier; + + char buffer1[256]; + char buffer3[64]; + dSprintf(buffer1, 255, "%f %f %f", + mRigid.state.linPosition.x, + mRigid.state.linPosition.y, + mRigid.state.linPosition.z); + dSprintf(buffer3, 63, "%f", damageValLocal); + Con::executef(safePtr->getDataBlock(), 6, "damageObject", + safePtr->scriptThis(), scriptThis(), buffer1, buffer3, buffer2); + } + + if (bool(safePtr) && bool(safeThis)) + safePtr->onCollision(safeThis); + } + + if(!bool(safeThis)) + return; + } + + CollisionTimeout* walk = mTimeoutList; + mTimeoutList = NULL; + while (walk) + { + CollisionTimeout* sFreeTimeoutList; + CollisionTimeout* next = walk->next; + walk->next = sFreeTimeoutList; + sFreeTimeoutList = walk; + walk = next; + } +} + +//---------------------------------------------------------------------------- + +bool Vehicle::resolveCollision(Rigid::State& ns,CollisionList& cList) +{ + // Apply impulses to resolve collision + bool collided = false; + bool colliding; + do { + colliding = false; + for (S32 i = 0; i < cList.count; i++) { + Collision& c = cList.collision[i]; + if (c.distance < sCollisionTol) { + Point3F v,r = c.point - ns.linPosition; + ns.getVelocity(r,&v); + F32 vn = mDot(v,c.normal); + + U32 objectMask = c.object->getTypeMask(); + + if(objectMask & sDirtySetMask) + { + setControlDirty(); + if(objectMask & ShapeBaseObjectType) + static_cast(c.object)->setControlDirty(); + } + + if (vn < -sContactTol) { + mRigid.resolveCollision(ns, + cList.collision[i].point, + cList.collision[i].normal); + colliding = true; + collided = true; + + // Track collisions + if (!isGhost() && c.object->getTypeMask() & ShapeBaseObjectType) + queueCollision(static_cast(c.object)); + } + } + } + } while (colliding); + + return collided; +} + + +//---------------------------------------------------------------------------- + +F32 Vehicle::resolveContacts(Rigid::State& ns,CollisionList& cList,F32 dt) +{ + // Apply impulse to resolve contacts + Point3F t,p(0,0,0),l(0,0,0); + for (S32 i = 0; i < cList.count; i++) { + Collision& c = cList.collision[i]; + if (c.distance < sCollisionTol) { + Point3F v,r = c.point - ns.linPosition; + ns.getVelocity(r,&v); + F32 vn = mDot(v,c.normal); + if (vn > -sContactTol) { + // Penetration force + F32 zi = mRigid.getZeroImpulse(mRigid.state,r,c.normal); + F32 d = (sCollisionTol - c.distance) / sCollisionTol; + F32 s = (d * d) * zi * sF - vn * sD; + Point3F f = c.normal * (s * dt); + + // Frictional force + Point3F uv = v - (c.normal * vn); + F32 ul = uv.len(); + if (s > 0 && ul) { + F32 u = s * mRigid.friction; + f -= uv * (u * dt / ul); + } + + // + p += f; + mCross(r,f,&t); + l += t; + } + } + } + + ns.linMomentum += p; + ns.angMomentum += l; + mRigid.updateVelocity(ns); + return 0; +} + + +//---------------------------------------------------------------------------- + +void Vehicle::updateLiftoffDust( F32 dt ) +{ + if( !mDustEmitterList[0] ) return; + + Point3F startPos = getPosition(); + Point3F endPos = startPos + Point3F( 0.0, 0.0, -mDataBlock->triggerDustHeight ); + + + RayInfo rayInfo; + if( !getContainer()->castRay( startPos, endPos, TerrainObjectType, &rayInfo ) ) + { + return; + } + + TerrainBlock* tBlock = static_cast(rayInfo.object); + S32 mapIndex = tBlock->mMPMIndex[0]; + + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntryFromIndex(mapIndex); + + if(pEntry) + { + S32 x; + ColorF colorList[ParticleEngine::PC_COLOR_KEYS]; + + for(x = 0; x < 2; ++x) + colorList[x].set( pEntry->puffColor[x].red, pEntry->puffColor[x].green, pEntry->puffColor[x].blue, pEntry->puffColor[x].alpha ); + for(x = 2; x < ParticleEngine::PC_COLOR_KEYS; ++x) + colorList[x].set( 1.0, 1.0, 1.0, 0.0 ); + + mDustEmitterList[0]->setColors( colorList ); + } + Point3F contactPoint = rayInfo.point + Point3F( 0.0, 0.0, mDataBlock->dustHeight ); + mDustEmitterList[0]->emitParticles( contactPoint, contactPoint, rayInfo.normal, getVelocity(), 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(), dt * 1000 ); + } + } + else + { + if( mDamageEmitterList[j] ) + { + mDamageEmitterList[j]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), dt * 1000 ); + } + } + } + break; + } + } + +} + + +//---------------------------------------------------------------------------- + +bool Vehicle::writePacketData(GameConnection *connection, BitStream *stream) +{ + bool ret = Parent::writePacketData(connection, stream); + mathWrite(*stream, mSteering); + + mathWrite(*stream, mRigid.state.linPosition); + mathWrite(*stream, mRigid.state.angPosition); + mathWrite(*stream, mRigid.state.linMomentum); + mathWrite(*stream, mRigid.state.angMomentum); + + stream->writeFlag(mDisableMove); + stream->writeFlag(mFrozen); + connection->setCompressionPoint(mRigid.state.linPosition); + return ret; +} + +void Vehicle::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + mathRead(*stream, &mSteering); + + mathRead(*stream, &mRigid.state.linPosition); + mathRead(*stream, &mRigid.state.angPosition); + mathRead(*stream, &mRigid.state.linMomentum); + mathRead(*stream, &mRigid.state.angMomentum); + + mRigid.updateVelocity(mRigid.state); + mDisableMove = stream->readFlag(); + mFrozen = stream->readFlag(); + connection->setCompressionPoint(mRigid.state.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); + + stream->writeFlag(mFrozen); + + if (stream->writeFlag(mask & PositionMask)) + { + con->writeCompressed(stream, mRigid.state.linPosition); + mathWrite(*stream, mRigid.state.angPosition); + mathWrite(*stream, mRigid.state.linMomentum); + mathWrite(*stream, mRigid.state.angMomentum); + } + + // send energy only to clients which need it + bool found = false; + if(mask & EnergyMask) + { + for (ShapeBase* ptr = getMountList(); ptr; ptr = ptr->getMountLink()) + { + if(!dynamic_cast(ptr)) + continue; + + GameConnection * controllingClient = ptr->getControllingClient(); + if(controllingClient == con) + { + if(controllingClient->getControlObject() != this) + found = true; + break; + } + } + } + + // write it... + if(stream->writeFlag(found)) + 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); + + mFrozen = stream->readFlag(); + if (stream->readFlag()) { + F32 speed = mRigid.state.linVelocity.len(); + mDelta.warpRot[0] = mRigid.state.angPosition; + con->readCompressed(stream, &mRigid.state.linPosition); + mathRead(*stream, &mRigid.state.angPosition); + mathRead(*stream, &mRigid.state.linMomentum); + mathRead(*stream, &mRigid.state.angMomentum); + mRigid.updateVelocity(mRigid.state); + + mPredictionCount = sMaxPredictionTicks; + + if (isProperlyAdded() && mFrozen == false) { + // Determin number of ticks to warp based on the average + // of the client and server velocities. + Point3F cp; + mObjToWorld.getColumn(3,&cp); + mDelta.warpOffset = mRigid.state.linPosition - cp; + F32 dt,as = (speed + mRigid.state.linVelocity.len()) * 0.5 * TickSec; + 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.state.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.state.linPosition - mDelta.pos; + mDelta.warpOffset /= mDelta.warpTicks; + mDelta.warpRot[0] = mDelta.rot[1]; + mDelta.warpRot[1] = mRigid.state.angPosition; + } + } + else { + // Set the vehicle to the server position + mDelta.dt = 0; + mDelta.pos = mRigid.state.linPosition; + mDelta.posVec.set(0,0,0); + mDelta.rot[1] = mDelta.rot[0] = mRigid.state.angPosition; + mDelta.warpCount = mDelta.warpTicks = 0; + setPosition(mRigid.state.linPosition, mRigid.state.angPosition); + } + } + + // energy? + if(stream->readFlag()) + setEnergyLevel(stream->readFloat(8) * mDataBlock->maxEnergy); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::initPersistFields() +{ + Parent::initPersistFields(); + + addField("disableMove", TypeBool, Offset(mDisableMove, Vehicle)); +} + + +void Vehicle::mountObject(ShapeBase* 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::updateFroth( F32 dt ) +{ + // update bubbles + Point3F moveDir = getVelocity(); + + Point3F contactPoint; + if( !collidingWithWater( contactPoint ) ) + { + if(waterWakeHandle) + { + alxStop(waterWakeHandle); + waterWakeHandle = 0; + } + return; + } + + F32 speed = moveDir.len(); + if( speed < mDataBlock->splashVelEpsilon ) speed = 0.0; + + U32 emitRate = speed * mDataBlock->splashFreqMod * dt; + + U32 i; + if(!waterWakeHandle) + waterWakeHandle = alxPlay(mDataBlock->waterSound[VehicleData::Wake], &getTransform()); + alxSourceMatrixF(waterWakeHandle, &getTransform()); + + 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.max.z - mObjBox.min.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); +} + +// F32 Vehicle::getHeat() const +// { +// return 1.0; +// } + + +void Vehicle::setFrozenState(const bool _frozen) +{ + mFrozen = _frozen; + setControlDirty(); +} + + +void Vehicle::renderObject(SceneState* state, SceneRenderImage* image) +{ + Parent::renderObject(state, image); + + if (gShowBoundingBox) { + RectI viewport; + dglGetViewport(&viewport); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&getRenderTransform()); + + glDisable(GL_DEPTH_TEST); + //-------------------------------------- + glColor3f(1, 0, 1); + wireCube(Point3F(0.25,0.25,0.25),Point3F(0,0,0)); + + glColor3f(1, 1, 1); + wireCube(Point3F(0.25,0.25,0.25),mDataBlock->massCenter); + + //-------------------------------------- + glEnable(GL_DEPTH_TEST); + + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + + // Show some collision points... + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + state->setupObjectProjection(this); + + ConvexFeature fa; + MatrixF mat; + mRigid.state.getTransform(&mat); + mConvex.getFeatures(mat, Point3F(0, -1, 0), &fa); + + glDisable(GL_DEPTH_TEST); + glColor3f(1, 0, 1); + for (U32 i = 0; i < fa.mVertexList.size(); i++) + { + wireCube(Point3F(0.25, 0.25, 0.25), fa.mVertexList[i]); + } + + glColor3f(1, 1, 0); + for (U32 i = 0; i < fa.mEdgeList.size(); i++) + { + glBegin(GL_LINES); + glVertex3fv(fa.mVertexList[fa.mEdgeList[i].vertex[0]]); + glVertex3fv(fa.mVertexList[fa.mEdgeList[i].vertex[1]]); + glEnd(); + } + + ClippedPolyList polyList; + // Planes bounding the square. + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(getWorldBox().min - Point3F(100, 100, 100),VectorF(-1,0,0)); + polyList.mPlaneList[1].set(getWorldBox().max + Point3F(100, 100, 100), VectorF(0,1,0)); + polyList.mPlaneList[2].set(getWorldBox().max - Point3F(100, 100, 100), VectorF(1,0,0)); + polyList.mPlaneList[3].set(getWorldBox().min + Point3F(100, 100, 100),VectorF(0,-1,0)); + polyList.mPlaneList[4].set(getWorldBox().min - Point3F(100, 100, 100),VectorF(0,0,-1)); + polyList.mPlaneList[5].set(getWorldBox().max + Point3F(100, 100, 100),VectorF(0,0,1)); + Box3F dummyBox; + SphereF dummySphere; + buildPolyList(&polyList, dummyBox, dummySphere); + + glColor3f(0, 1, 1); + for (U32 i = 0; i < polyList.mVertexList.size(); i++) + { + wireCube(Point3F(0.25, 0.25, 0.25), polyList.mVertexList[i].point); + } + + glEnable(GL_DEPTH_TEST); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + } +} diff --git a/game/vehicles/vehicle.h b/game/vehicles/vehicle.h new file mode 100644 index 0000000..e3339bd --- /dev/null +++ b/game/vehicles/vehicle.h @@ -0,0 +1,259 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _VEHICLE_H_ +#define _VEHICLE_H_ + +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif +#ifndef _RIGID_H_ +#include "game/rigid.h" +#endif +#ifndef _BOXCONVEX_H_ +#include "collision/boxConvex.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; +class ClippedPolyList; + + +//---------------------------------------------------------------------------- + +struct VehicleData: public ShapeBaseData +{ + typedef ShapeBaseData Parent; + + struct Body { + enum Sounds { + SoftImpactSound, + HardImpactSound, + MaxSounds, + }; + AudioProfile* 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 + }; + AudioProfile* waterSound[MaxSounds]; + F32 exitSplashSoundVel; + F32 softSplashSoundVel; + F32 medSplashSoundVel; + F32 hardSplashSoundVel; + + F32 minImpactSpeed; + F32 softImpactSpeed; + F32 hardImpactSpeed; + F32 minRollSpeed; + F32 maxSteeringAngle; + + F32 collDamageThresholdVel; + F32 collDamageMultiplier; + + F32 cameraLag; + F32 cameraOffset; // Vertical offset + + F32 minDrag; + F32 maxDrag; + + F32 jetForce; + F32 jetEnergyDrain; // Energy drain/tick + F32 minJetEnergy; + + S32 stuckTimerTicks; + F32 stuckTimerAngle; + F32 stuckTimerZ; // calculated in preload... + + 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 ]; + S32 damageEmitterIDList[ VC_NUM_DAMAGE_EMITTERS ]; + Point3F damageEmitterOffset[ VC_NUM_DAMAGE_EMITTER_AREAS ]; + F32 damageLevelTolerance[ VC_NUM_DAMAGE_LEVELS ]; + F32 numDmgEmitterAreas; + + ParticleEmitterData* splashEmitterList[VC_NUM_SPLASH_EMITTERS]; + S32 splashEmitterIDList[VC_NUM_SPLASH_EMITTERS]; + F32 splashFreqMod; + F32 splashVelEpsilon; + + + // Initialized in load() + Point3F massCenter; + + // + VehicleData(); + bool preload(bool server, char errorBuffer[256]); + 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, + FrozenMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2, + EnergyMask = Parent::NextFreeMask << 3 + }; + + 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; + AUDIOHANDLE waterWakeHandle; + + bool mFrozen; + + // Control + Point2F mSteering; + F32 mThrottle; + bool mJetting; + + // Rigid Body + bool mMinRoll; + bool mDisableMove; + + // Stuck-ness timer + S32 mStuckTimer; + + ShapeBaseConvex mConvex; + + Rigid mRigid; + + 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 advanceToCollision(F32 time); + bool resolveCollision(Rigid::State& ns,CollisionList& cList); + F32 resolveContacts(Rigid::State& ns,CollisionList& cList,F32 dt); + void localImpulse(const Point3F &r,const Point3F &impulse); + + void damageQueuedObjects(const F32 speed); + + void setPosition(const Point3F& pos,const QuatF& rot); + void setRenderPosition(const Point3F& pos,const QuatF& rot); + void setTransform(const MatrixF& newMat); + +// virtual bool collideBody(const MatrixF& mat,Collision* info) = 0; + virtual void updateMove(const Move*); + virtual void updateForces(F32 dt); + virtual void updateWarp(); + + bool writePacketData(GameConnection *, BitStream *stream); + void readPacketData(GameConnection *, BitStream *stream); + U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *con, 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 ); + + void renderObject(SceneState*, SceneRenderImage*); + + public: + // Test code... + static ClippedPolyList* sPolyList; + static S32 sVehicleCount; + + // + Vehicle(); + static void initPersistFields(); + void processTick(const Move*); + bool onAdd(); + void onRemove(); + void interpolateTick(F32 dt); + void advanceTime(F32 dt); + + void disableCollision(); + void enableCollision(); + + Point3F getVelocity() const; + + void setEnergyLevel(F32 energy); + //F32 getHeat() const; + + void setFrozenState(const bool _frozen); + + // Rigid body methods + void getVelocity(const Point3F& r, Point3F* vel); + void applyImpulse(const Point3F &r,const Point3F &impulse); + F32 getImpulse(const Point3F& r,const Point3F& normal); + + void getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot); + void mountObject(ShapeBase* obj, U32 node); + + DECLARE_CONOBJECT(Vehicle); + static void consoleInit(); +}; + + +#endif diff --git a/game/vehicles/vehicleBlocker.cc b/game/vehicles/vehicleBlocker.cc new file mode 100644 index 0000000..a92df19 --- /dev/null +++ b/game/vehicles/vehicleBlocker.cc @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/vehicles/vehicleBlocker.h" +#include "core/bitStream.h" +#include "dgl/dgl.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() +{ + Parent::initPersistFields(); + addField("dimensions", TypePoint3F, Offset(mDimensions, VehicleBlocker)); +} + + +void VehicleBlocker::consoleInit() +{ + // +} + +//-------------------------------------------------------------------------- +bool VehicleBlocker::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.min.set(-mDimensions.x, -mDimensions.y, 0); + mObjBox.max.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.min.set(-mDimensions.x, -mDimensions.y, 0); + mObjBox.max.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/game/vehicles/vehicleBlocker.h b/game/vehicles/vehicleBlocker.h new file mode 100644 index 0000000..9a42950 --- /dev/null +++ b/game/vehicles/vehicleBlocker.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _VEHICLEBLOCKER_H_ +#define _VEHICLEBLOCKER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sim/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(); + static void consoleInit(); + + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); +}; + +#endif // _H_VEHICLEBLOCKER diff --git a/game/vehicles/wheeledVehicle.cc b/game/vehicles/wheeledVehicle.cc new file mode 100644 index 0000000..96fdc02 --- /dev/null +++ b/game/vehicles/wheeledVehicle.cc @@ -0,0 +1,1287 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "game/vehicles/wheeledVehicle.h" + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "game/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 "game/moveManager.h" +#include "core/bitStream.h" +#include "core/dnet.h" +#include "game/gameConnection.h" +#include "ts/tsShapeInstance.h" +#include "game/particleEngine.h" +#include "audio/audio.h" +#include "sceneGraph/sceneGraph.h" +#include "sim/decalManager.h" +#include "dgl/materialPropertyMap.h" +#include "terrain/terrData.h" + +//---------------------------------------------------------------------------- + +static U32 sCollisionMoveMask = (TerrainObjectType | InteriorObjectType | + PlayerObjectType | StaticShapeObjectType | + VehicleObjectType | VehicleBlockerObjectType | + ForceFieldObjectType | StaticTSObjectType); +static U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType +static U32 sClientCollisionMask = sCollisionMoveMask; + +static F32 sWheeledVehicleGravity = -20; +static F32 sBreakZeroEpsilon = 0.02; // m/s +static F32 sTireEmitterVerticalScale = 0.7; +static F32 sTireCollisionExpansion = 0.02; // Expanded size in buildPolyList + +// Sound +static F32 sMinSqueelVolume = 0.05; +static F32 sIdleEngineVolume = 0.2; + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleData); + +WheeledVehicleData::WheeledVehicleData() +{ + tire.friction = 0.3; + tire.restitution = 1; + tire.radius = 0.6; + tire.lateralForce = 1000; + tire.lateralDamping = 100; + tire.lateralRelaxation = 1; + tire.longitudinalForce = 1000; + tire.longitudinalDamping = 100; + tire.longitudinalRelaxation = 1; + tire.emitter = 0; + maxWheelSpeed = 40; + engineTorque = 1; + breakTorque = 1; + staticLoadScale = 1; + antiSwayForce = 1; + antiRockForce = 0; + springForce = 0.6; + tailLightSequence = -1; + + stabilizerForce = 0; + gyroForce = 0; + gyroDamping = 0; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = 0; +} + +bool WheeledVehicleData::preload(bool server, char errorBuffer[256]) +{ + if (!Parent::preload(server, errorBuffer)) + return false; + + TSShapeInstance* si = new TSShapeInstance(shape, 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 (tire.emitter) + Sim::findObject(SimObjectId(tire.emitter),tire.emitter); + } + + // Extract wheel information from the shape + TSThread* thread = si->addThread(); + Wheel* wp = wheel; + for (S32 i = 0; i < MaxWheels; i++) { + char buff[10]; + + // Spring and ground information have to exist for + // the wheel to operate at all. + dSprintf(buff,sizeof(buff),"ground%d",i); + wp->springNode = shape->findNode(buff); + dSprintf(buff,sizeof(buff),"spring%d",i); + wp->springSequence = shape->findSequence(buff); + if (wp->springSequence != -1 && wp->springNode != -1) { + + // Extract spring pos & movement + si->setSequence(thread,wp->springSequence,0); + si->animate(); + si->mNodeTransforms[wp->springNode].getColumn(3,&wp->spring); + si->setPos(thread,1); + si->animate(); + si->mNodeTransforms[wp->springNode].getColumn(3, &wp->pos); + wp->safePos.set(wp->pos.x, wp->pos.y, wp->pos.z + tire.radius); + if (!mirrorWheel(wp)) { + wp->spring.x = wp->spring.y = 0; + wp->spring.z -= wp->pos.z; + } + + // More animation sequences + dSprintf(buff,sizeof(buff),"turn%d",i); + wp->steeringSequence = shape->findSequence(buff); + dSprintf(buff,sizeof(buff),"wheel%d",i); + wp->rotationSequence = shape->findSequence(buff); + + // + wp->springRest = 0.5; + wp->springForce = springForce; + wp->springDamping = springDamping; + wp->steering = (wp->steeringSequence != -1)? Wheel::Forward: Wheel::None; + wp++; + } + } + wheelCount = wp - wheel; + + // + tailLightSequence = shape->findSequence("taillight"); + + // Extract collision planes from shape collision detail level + if (collisionDetails[0] != -1) { + MatrixF imat(1); + SphereF sphere; + sphere.center = shape->center; + sphere.radius = shape->radius; + PlaneExtractorPolyList polyList; + polyList.mPlaneList = &rigidBody.mPlaneList; + polyList.setTransform(&imat, Point3F(1,1,1)); + si->buildPolyList(&polyList,collisionDetails[0]); + } + + delete si; + return true; +} + +bool WheeledVehicleData::mirrorWheel(Wheel* we) +{ + we->opposite = -1; + // Find matching wheel mirrored along Y axis + 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->spring = wp->spring; + we->opposite = wp - wheel; + wp->opposite = we - wheel; + return true; + } + return false; +} + + +void WheeledVehicleData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("jetSound", TypeAudioProfilePtr, Offset(sound[JetSound], WheeledVehicleData)); + addField("engineSound", TypeAudioProfilePtr, Offset(sound[EngineSound], WheeledVehicleData)); + addField("squeelSound", TypeAudioProfilePtr, Offset(sound[SqueelSound], WheeledVehicleData)); + addField("WheelImpactSound", TypeAudioProfilePtr, Offset(sound[WheelImpactSound], WheeledVehicleData)); + + addField("tireRadius", TypeF32, Offset(tire.radius, WheeledVehicleData)); + addField("tireFriction", TypeF32, Offset(tire.friction, WheeledVehicleData)); + addField("tireRestitution", TypeF32, Offset(tire.restitution, WheeledVehicleData)); + addField("tireLateralForce", TypeF32, Offset(tire.lateralForce, WheeledVehicleData)); + addField("tireLateralDamping", TypeF32, Offset(tire.lateralDamping, WheeledVehicleData)); + addField("tireLateralRelaxation", TypeF32, Offset(tire.lateralRelaxation, WheeledVehicleData)); + addField("tireLongitudinalForce", TypeF32, Offset(tire.longitudinalForce, WheeledVehicleData)); + addField("tireLongitudinalDamping", TypeF32, Offset(tire.longitudinalDamping, WheeledVehicleData)); + addField("tireLogitudinalRelaxation", TypeF32, Offset(tire.longitudinalRelaxation, WheeledVehicleData)); + addField("tireEmitter",TypeParticleEmitterDataPtr, Offset(tire.emitter, WheeledVehicleData)); + + addField("maxWheelSpeed", TypeF32, Offset(maxWheelSpeed, WheeledVehicleData)); + addField("engineTorque", TypeF32, Offset(engineTorque, WheeledVehicleData)); + addField("breakTorque", TypeF32, Offset(breakTorque, WheeledVehicleData)); + addField("staticLoadScale", TypeF32, Offset(staticLoadScale, WheeledVehicleData)); + addField("springForce", TypeF32, Offset(springForce, WheeledVehicleData)); + addField("springDamping", TypeF32, Offset(springDamping, WheeledVehicleData)); + addField("antiSwayForce", TypeF32, Offset(antiSwayForce, WheeledVehicleData)); + addField("antiRockForce", TypeF32, Offset(antiRockForce, WheeledVehicleData)); + + addField("stabilizerForce", TypeF32, Offset(stabilizerForce, WheeledVehicleData)); + addField("gyroForce", TypeF32, Offset(gyroForce, WheeledVehicleData)); + addField("gyroDamping", TypeF32, Offset(gyroDamping, WheeledVehicleData)); +} + +void WheeledVehicleData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(tire.friction); + stream->write(tire.restitution); + stream->write(tire.radius); + stream->write(tire.lateralForce); + stream->write(tire.lateralDamping); + stream->write(tire.lateralRelaxation); + stream->write(tire.longitudinalForce); + stream->write(tire.longitudinalDamping); + stream->write(tire.longitudinalRelaxation); + + if (stream->writeFlag(tire.emitter)) + stream->writeRangedU32(packed? SimObjectId(tire.emitter): + tire.emitter->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(springForce); + stream->write(springDamping); + stream->write(antiSwayForce); + stream->write(antiRockForce); + stream->write(maxWheelSpeed); + stream->write(engineTorque); + stream->write(breakTorque); + stream->write(staticLoadScale); + + stream->write(stabilizerForce); + stream->write(gyroForce); + stream->write(gyroDamping); +} + +void WheeledVehicleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&tire.friction); + stream->read(&tire.restitution); + stream->read(&tire.radius); + stream->read(&tire.lateralForce); + stream->read(&tire.lateralDamping); + stream->read(&tire.lateralRelaxation); + stream->read(&tire.longitudinalForce); + stream->read(&tire.longitudinalDamping); + stream->read(&tire.longitudinalRelaxation); + + tire.emitter = stream->readFlag()? + (ParticleEmitterData*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast): 0; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = stream->readFlag()? + (AudioProfile*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast): 0; + + stream->read(&springForce); + stream->read(&springDamping); + stream->read(&antiSwayForce); + stream->read(&antiRockForce); + stream->read(&maxWheelSpeed); + stream->read(&engineTorque); + stream->read(&breakTorque); + stream->read(&staticLoadScale); + + stream->read(&stabilizerForce); + stream->read(&gyroForce); + stream->read(&gyroDamping); +} + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(WheeledVehicle); + +WheeledVehicle::WheeledVehicle() +{ + mGenerateShadow = true; + + mBraking = false; + mWheelContact = false; + mTailLightThread = 0; + + for (S32 i = 0; i < WheeledVehicleData::MaxWheels; i++) { + mWheel[i].springThread = 0; + mWheel[i].steeringThread = 0; + mWheel[i].rotationThread = 0; + mWheel[i].Dy = mWheel[i].Dx = 0; + } + + mJetSound = 0; + mEngineSound = 0; + mSqueelSound = 0; + + mDataBlock = NULL; +} + +WheeledVehicle::~WheeledVehicle() +{ + if (mJetSound) + alxStop(mJetSound); + if (mEngineSound) + alxStop(mEngineSound); + if (mSqueelSound) + alxStop(mSqueelSound); +} + + +//---------------------------------------------------------------------------- + +bool WheeledVehicle::onAdd() +{ + if(!Parent::onAdd()) + return false; + + addToScene(); + if (isServerObject()) + scriptOnAdd(); + return true; +} + +bool WheeledVehicle::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + F32 frontStatic = 0; + F32 backStatic = 0; + F32 fCount = 0; + F32 bCount = 0; + + // Wheel threads + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + WheeledVehicleData::Wheel* wd = &mDataBlock->wheel[i]; + Wheel* wp = &mWheel[i]; + + wp->surface.contact = false; + wp->surface.object = NULL; + wp->evel = 0; + wp->avel = 0; + wp->apos = 0; + wp->steeringThread = 0; + wp->springThread = 0; + wp->rotationThread = 0; + + if (wd->steeringSequence != -1) { + wp->steeringThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(wp->steeringThread,wd->steeringSequence,0); + } + if (wd->springSequence != -1) { + wp->springThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(wp->springThread,wd->springSequence,0); + } + if (wd->rotationSequence != -1) { + wp->rotationThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(wp->rotationThread,wd->rotationSequence,0); + } + + // Set springs to currenty gravity + wp->k = wd->springForce; + wp->s = wd->springDamping; +// F32 staticForce = (mMass * -sWheeledVehicleGravity) / mDataBlock->wheelCount; +// F32 sprungForce = staticForce * 0.8; +// wp->center = wd->springRest + (sprungForce / wp->k); +// if (wp->center > 1) + wp->center = 1; + wp->extension = wp->center; + + // + wp->particleSlip = 0; + if (mDataBlock->tire.emitter) { + wp->emitter = new ParticleEmitter; + wp->emitter->onNewDataBlock(mDataBlock->tire.emitter); + wp->emitter->registerObject(); + } + else + wp->emitter = NULL; + } + + // + if (mDataBlock->tailLightSequence != -1) { + mTailLightThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mTailLightThread,mDataBlock->tailLightSequence,0); + } + else + mTailLightThread = 0; + + // Sounds + if (mJetSound) { + alxStop(mJetSound); + mJetSound = 0; + } + if (mEngineSound) { + alxStop(mEngineSound); + mEngineSound = 0; + } + if (mSqueelSound) { + alxStop(mSqueelSound); + mSqueelSound = 0; + } + if (isGhost()) { + if (mDataBlock->sound[WheeledVehicleData::EngineSound]) + mEngineSound = alxPlay(mDataBlock->sound[WheeledVehicleData::EngineSound], &getTransform()); + } + + scriptOnNewDataBlock(); + return true; +} + +void WheeledVehicle::onRemove() +{ + if (mDataBlock != NULL) + { + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + if (bool(wp->emitter)) { + wp->emitter->deleteWhenEmpty(); + wp->emitter = 0; + } + } + + } + scriptOnRemove(); + removeFromScene(); + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateWarp() +{ + updateWheels(); +} + +void WheeledVehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + Point3F bz; + mObjToWorld.getColumn(2,&bz); + + if (mTailLightThread) + mShapeInstance->advanceTime(dt,mTailLightThread); + + // Update wheels + if (mFrozen == false) + { + F32 slipTotal = 0; + F32 torqueTotal = 0; + for (S32 i = 0; i < mDataBlock->wheelCount; i++) + { + Wheel* wp = &mWheel[i]; + + // Update angular position + wp->apos += (wp->avel * dt) / M_2PI; + wp->apos -= mFloor(wp->apos); + if (wp->apos < 0) + wp->apos = 1 - wp->apos; + + // Keep track of largest slip + slipTotal += wp->particleSlip; + torqueTotal += wp->torqueScale; + + Point3F wv = Parent::getVelocity(); + + // emit dust if moving + if( wv.len() > 1.0 && wp->surface.object && wp->surface.object->getTypeMask() & TerrainObjectType ) + { + TerrainBlock* tBlock = static_cast(wp->surface.object); + S32 mapIndex = tBlock->mMPMIndex[0]; + + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntryFromIndex(mapIndex); + + if(pEntry) + { + S32 x; + ColorF colorList[ParticleEngine::PC_COLOR_KEYS]; + + for(x = 0; x < 2; ++x) + colorList[x].set(pEntry->puffColor[x].red, + pEntry->puffColor[x].green, + pEntry->puffColor[x].blue, + pEntry->puffColor[x].alpha); + + for(x = 2; x < ParticleEngine::PC_COLOR_KEYS; ++x) + colorList[x].set( 1.0, 1.0, 1.0, 0.0 ); + + wp->emitter->setColors( colorList ); + } + Point3F axis = wv; + axis.normalize(); + + wp->emitter->emitParticles(wp->surface.pos + Point3F( 0.0, 0.0, 0.5 ),true, + axis, wv, dt * 1000 * (wv.len() / 15.0) ); + } + } + + // + updateJet(dt); + updateSqueelSound(slipTotal / mDataBlock->wheelCount); + updateEngineSound(sIdleEngineVolume + (1 - sIdleEngineVolume) * + (1 - (torqueTotal / mDataBlock->wheelCount))); + } +} + + +//---------------------------------------------------------------------------- + +bool WheeledVehicle::buildPolyList(AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) +{ + // Parent will take care of body collision. + Parent::buildPolyList(polyList,box,sphere); + + // Add wheels. + Box3F wbox; + wbox.min.x = -(wbox.max.x = (mDataBlock->tire.radius / 2) + sTireCollisionExpansion); + wbox.min.y = -(wbox.max.y = mDataBlock->tire.radius + sTireCollisionExpansion); + wbox.max.z = 2 * mDataBlock->tire.radius; + wbox.min.z = 0; + MatrixF mat = mObjToWorld; + + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + WheeledVehicleData::Wheel* wd = &mDataBlock->wheel[i]; + Wheel* wp = &mWheel[i]; + + Point3F sp,vec; + mObjToWorld.mulP(wd->pos,&sp); + mObjToWorld.mulV(wd->spring,&vec); + Point3F ep = sp + (vec * wp->extension); + mat.setColumn(3,ep); + polyList->setTransform(&mat,Point3F(1,1,1)); + polyList->addBox(wbox); + } + return !polyList->isEmpty(); +} + + +//---------------------------------------------------------------------------- +void WheeledVehicle::processTick(const Move* move) +{ + Parent::processTick(move); + + if (isServerObject() && mWaterCoverage > 0.5) + { + char buffer1[256], buffer2[256]; + dSprintf(buffer1, 255, "%f %f %f", + mRigid.state.linPosition.x, + mRigid.state.linPosition.y, + mRigid.state.linPosition.z); + dSprintf(buffer2, 255, "%d", Con::getIntVariable("$DamageType::Water")); + Con::executef(mDataBlock, 6, "damageObject", scriptThis(), "0", buffer1, "1000", buffer2); + + } +} + +void WheeledVehicle::updateMove(const Move* move) +{ + Parent::updateMove(move); + + // Breaking + F32 wvel = 0; + for (S32 i = 0; i < mDataBlock->wheelCount; i++) + wvel += mWheel[i].avel; + if (mFabs(wvel * mDataBlock->tire.radius) < sBreakZeroEpsilon) + wvel = 0; + if (mBraking) { + // If the throttle is not set, or is set in the same direction + // as are wheel velocity, then we are no longer breaking. + if (!wvel || !mThrottle || (mThrottle > 0 && wvel > 0) || (mThrottle < 0 && wvel < 0)) + mBraking = false; + } + else + // If the throttle is opposite to our wheel velocity, then break. + if ((mThrottle > 0 && wvel < 0) || (mThrottle < 0 && wvel > 0)) + mBraking = true; + + if (mTailLightThread) + mShapeInstance->setTimeScale(mTailLightThread,mBraking? 1: -1); + + // + updateWheels(); + + if (mWheelContact) + mSteering.y = 0; +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateForces(F32 dt) +{ + S32 j; + + MatrixF currMatrix; + mRigid.state.getTransform(&currMatrix); + MatrixF currMatrixInv = currMatrix; + currMatrixInv.inverse(); + + // + F32 oneOverSprungMass = 1 / (mMass * 0.8); + F32 maxAvel = mDataBlock->maxWheelSpeed / mDataBlock->tire.radius; + F32 aMomentum = mMass / mDataBlock->wheelCount; + + mRigid.state.force.set(0, 0, 0); + mRigid.state.torque.set(0, 0, 0); + + // Drag + { + mRigid.state.force -= mRigid.state.linVelocity * mDataBlock->minDrag * mOneOverMass; + mRigid.state.torque -= mRigid.state.angMomentum * mDataBlock->antiRockForce * mOneOverMass; + } + + // Body & Steering Vectors + Point3F bx,by,bz; + { + currMatrix.getColumn(0,&bx); + currMatrix.getColumn(1,&by); + currMatrix.getColumn(2,&bz); + } + + Point3F worldZ(0, 0, 1); + Point3F worldY(0, 1, 0); + currMatrix.getColumn(1, &worldY); + currMatrix.getColumn(2, &worldZ); + + F32 quadraticSteering = -(mSteering.x * mFabs(mSteering.x)); + F32 cosSteering; + F32 sinSteering; + mSinCos(quadraticSteering, sinSteering, cosSteering); + + // Center of mass in world space + Point3F massCenter; + currMatrix.mulP(mDataBlock->massCenter, &massCenter); + currMatrix.mulP(Point3F(0, 0, 0), &massCenter); + + Point3F force = Point3F(0,0,0); + Point3F torque = Point3F(0,0,0); + + AssertFatal(mDataBlock->wheelCount < 8, "Error, only <= 8 wheels supported"); + Point3F wheelForce[8]; + + // Calculate vertical load for friction. This was marked by TimG as "a hack" + U32 contactCount = 0; + F32 verticalLoad = 0; + { + for (j = 0; j < mDataBlock->wheelCount; j++) { + if (mWheel[j].surface.contact) + contactCount++; + } + + if (contactCount != 0) { + verticalLoad = (mDataBlock->staticLoadScale * + (mMass * -sWheeledVehicleGravity) / contactCount); + } + } + + // Engine and break torque + F32 engineTorque,breakTorque,maxBreakVel; + if (mBraking) { + breakTorque = mDataBlock->breakTorque * mFabs(mThrottle); + maxBreakVel = (breakTorque / aMomentum) * dt; + engineTorque = 0; + } + else { + if (mThrottle) { + engineTorque = mDataBlock->engineTorque * mThrottle; + maxBreakVel = breakTorque = 0; + if (mThrottle > 0 && mJetting) + // Double the engineTorque to help out the jets + engineTorque += mDataBlock->engineTorque; + } + else { + // Engine break. + breakTorque = mDataBlock->engineTorque; + maxBreakVel = (breakTorque / aMomentum) * dt; + engineTorque = 0; + } + } + + force = Point3F(0, 0, sWheeledVehicleGravity); + + // Jet Force + if (mJetting) + { + force += worldY * (mDataBlock->jetForce * mOneOverMass); + } + + // Calculate wheel forces + // + for (j = 0; j < mDataBlock->wheelCount; j++) + { + Wheel* wheel = &mWheel[j]; + WheeledVehicleData::Wheel* wheelData = &mDataBlock->wheel[j]; + + // Zero the force on this wheel + Point3F& forceVector = wheelForce[j]; + forceVector.set(0, 0, 0); + + if (wheel->surface.contact) { + // Wheel in contact with the ground. + // Forces acting on the body due to this wheel: + // - Spring forces + + // Torques acting on the body due to this wheel: + // - None + + // First, let's compute the wheels position, and worldspace velocity + Point3F pos, r, localVel; + currMatrix.mulP(wheelData->pos, &pos); + r = pos - massCenter; + getVelocity(r, &localVel); + + // Spring forces on this wheel act in the z direction of the body, at the point + // of contact. + { + // Spring force + F32 spring = wheel->k * (wheel->center - wheel->extension); + + // Damping in the spring + F32 damping = wheel->s * -mDot(bz, localVel); + if (damping < 0) + damping = 0; + + // Anti-sway force based on difference in suspension extension + F32 antiSway = 0; + if (wheelData->opposite != -1) + { + Wheel* oppositeWheel = &mWheel[wheelData->opposite]; + if (oppositeWheel->surface.contact) { + antiSway = ((oppositeWheel->extension - wheel->extension) * + mDataBlock->antiSwayForce); + } + if (antiSway < 0) + antiSway = 0; + } + + forceVector += bz * ((spring + damping + antiSway) * oneOverSprungMass); + } + + // Tire direction vectors perpendicular to surface normal + Point3F wheelXVec; + if (wheelData->steering == WheeledVehicleData::Wheel::Forward) { + wheelXVec = bx * cosSteering; + wheelXVec += by * sinSteering; + } + else if (wheelData->steering == WheeledVehicleData::Wheel::Backward) { + wheelXVec = bx * cosSteering; + wheelXVec -= by * sinSteering; + } + else { + wheelXVec = bx; + } + + Point3F tireX, tireY; + mCross(wheel->surface.normal, wheelXVec, &tireY); + tireY.normalize(); + mCross(tireY, wheel->surface.normal, &tireX); + tireX.normalize(); + + // Velocity of wheel at surface contact + Point3F wheelContact = wheel->surface.pos - massCenter; + Point3F wheelVelocity; + getVelocity(wheelContact, &wheelVelocity); + F32 xVelocity = mDot(tireX, wheelVelocity); + F32 yVelocity = mDot(tireY, wheelVelocity); + + // Longitudinal deformation force + F32 ddy = ((wheel->avel * mDataBlock->tire.radius) - + yVelocity - + mDataBlock->tire.longitudinalRelaxation * mFabs(wheel->avel) * wheel->Dy); + + wheel->Dy += ddy * dt; + + F32 Fy = (mDataBlock->tire.longitudinalForce * wheel->Dy + + mDataBlock->tire.longitudinalDamping * ddy); + + // Lateral deformation force + F32 ddx = (xVelocity - + (mDataBlock->tire.lateralRelaxation * mFabs(wheel->avel) * wheel->Dx)); + + wheel->Dx += ddx * dt; + + F32 Fx = -(mDataBlock->tire.lateralForce * wheel->Dx + + mDataBlock->tire.lateralDamping * ddx); + + // Vertical load on tire. + F32 Fz = verticalLoad; + + // Reduce forces based on friction limit + F32 sN = mDot(wheel->surface.normal,Point3F(0,0,1)); + if (sN > 0) { + F32 muS = Fz * mDataBlock->tire.friction * sN; + F32 muS2 = muS * muS; + F32 Fn = (Fz * Fz) * muS2; + F32 Ff = (Fx * Fx + Fy * Fy) * muS2; + if (Ff > Fn) { + F32 K = mSqrt(Fn / Ff); + Fy *= K; + Fx *= K; + wheel->Dy *= K; + wheel->Dx *= K; + } + } + else { + Fy = Fx = 0; + } + + // Apply forces to wheel ground contact point + forceVector += (tireX * (Fx * mOneOverMass)) + (tireY * (Fy * mOneOverMass)); + + // Wheel angular acceleration from engine torque and tire + // deformation force. + wheel->torqueScale = (mFabs(wheel->avel) > maxAvel) ? 0 : + 1 - (mFabs(wheel->avel) / maxAvel); + + wheel->avel += (((wheel->torqueScale * engineTorque) - + Fy * + mDataBlock->tire.radius) / aMomentum) * dt; + + // Wheel angular acceleration from break torque + if (maxBreakVel > mFabs(wheel->avel)) + { + wheel->avel = 0; + } + else + { + if (wheel->avel > 0) + wheel->avel -= maxBreakVel; + else + wheel->avel += maxBreakVel; + } + } + else { + // Wheel not in contact with the ground + // Forces acting on the body due to this wheel: + // - None + // Torques acting on the body due to this wheel: + // - None + + wheel->torqueScale = 0; + wheel->particleSlip = 0; + wheel->Dy += (-mDataBlock->tire.longitudinalRelaxation * + mFabs(wheel->avel) * wheel->Dy) * dt; + wheel->Dx += (-mDataBlock->tire.lateralRelaxation * + mFabs(wheel->avel) * wheel->Dx) * dt; + } + } + + // Sum up the forces + { + // Now sum up the torques and forces + + for (j = 0; j < mDataBlock->wheelCount; j++) { + WheeledVehicleData::Wheel* wheelData = &mDataBlock->wheel[j]; + Point3F pos, r, t; + currMatrix.mulP(wheelData->pos, &pos); + r = pos - massCenter; + mCross(r, wheelForce[j], &t); + torque += t; + force += wheelForce[j]; + } + } + + // Container buoyancy & drag + force += Point3F(0, 0, -mBuoyancy * sWheeledVehicleGravity); + force -= mRigid.state.linVelocity * mDrag; + torque -= mRigid.state.angMomentum * mDrag; + + // Apply forces to body + mRigid.state.force += force; + mRigid.state.torque += torque; +} + + +//---------------------------------------------------------------------------- + +U32 WheeledVehicle::getCollisionMask() +{ + if (isServerObject()) + return sServerCollisionMask; + else + return sClientCollisionMask; +} + + +//bool WheeledVehicle::collideBody(const MatrixF& mat,Collision* info) +//{ +// // Database bounding box +// Box3F wBox = mObjBox; +// mat.mul(wBox); +// +// // Test the body against the database +// Box3F box; +// SphereF sphere; +// MatrixF imat(1); +// PlaneExtractorPolyList extractor; +// sPolyList->mPlaneList.clear(); +// extractor.mPlaneList = &sPolyList->mPlaneList; +// extractor.setTransform(&mat, Point3F(1,1,1)); +// mShapeInstance->buildPolyList(&extractor,mDataBlock->collisionDetails[0]); +// +// sPolyList->clear(); +// +// // 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) { +// pConvex->getPolyList(sPolyList); +// } +// pList = pList->wLink.mNext; +// } +// +// S32 count = sPolyList->mPolyList.size(); +// info->face = count? BodyCollision: 0; +// +// // Test the wheels against the database +// Point3F xvec,yvec,zvec; +// mat.getColumn(0,&xvec); +// xvec *= mObjBox.len_x(); +// mat.getColumn(1,&yvec); +// yvec *= mObjBox.len_y(); +// mat.getColumn(2,&zvec); +// zvec *= mObjBox.len_z(); +// +// for (S32 i = 0; i != mDataBlock->wheelCount; i++) { +// WheeledVehicleData::Wheel* wd = &mDataBlock->wheel[i]; +// Wheel* wp = &mWheel[i]; +// +// Box3F box; +// F32 wr = mDataBlock->tire.radius, ww = wr / 2; +// box.min.set(wd->pos.x - ww,wd->pos.y - wr,wd->pos.z); +// box.max.set(wd->pos.x + ww,wd->pos.y + wr,wd->pos.z + 2*wr); +// +// Point3F min,max; +// mat.mulP(box.min,&min); +// mat.mulP(box.max,&max); +// +// sPolyList->mPlaneList.clear(); +// sPolyList->mNormal.set(0,0,0); +// sPolyList->mPlaneList.setSize(6); +// sPolyList->mPlaneList[0].set(min,xvec); +// sPolyList->mPlaneList[0].invert(); +// sPolyList->mPlaneList[1].set(max,yvec); +// sPolyList->mPlaneList[2].set(max,xvec); +// sPolyList->mPlaneList[3].set(min,yvec); +// sPolyList->mPlaneList[3].invert(); +// sPolyList->mPlaneList[4].set(min,zvec); +// sPolyList->mPlaneList[4].invert(); +// sPolyList->mPlaneList[5].set(max,zvec); +// +// // 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) { +// pConvex->getPolyList(sPolyList); +// } +// pList = pList->wLink.mNext; +// } +// } +// +// if (sPolyList->mPolyList.size() != count) +// info->face |= WheelCollision; +// +// // Pick best collision point +// F32 bestDist = 1.0E30; +// ClippedPolyList::Poly* bestPoly = 0; +// ClippedPolyList::Vertex* bestVertex; +// +// if (sPolyList->mPolyList.size()) { +// Point3F massCenter; +// mat.mulP(mDataBlock->massCenter,&massCenter); +// +// // Pick surface with best vertex velocity +// F32 bestVd = -1; +// ClippedPolyList::Poly* poly = sPolyList->mPolyList.begin(); +// ClippedPolyList::Poly* end = sPolyList->mPolyList.end(); +// for (; poly != end; poly++) { +// U32* vi = &sPolyList->mIndexList[poly->vertexStart]; +// U32* ve = vi + poly->vertexCount; +// for (; vi != ve; vi++) { +// ClippedPolyList::Vertex* ev = &sPolyList->mVertexList[*vi]; +// +// Point3F v,r; +// r = ev->point - massCenter; +// getVelocity(r,&v); +// +// F32 dist = mDot(poly->plane,v); +// if (dist < 0 && dist < bestDist) { +// bestDist = dist; +// bestVertex = ev; +// bestPoly = poly; +// } +// } +// } +// } +// +// // +// if (bestPoly) { +// info->point = bestVertex->point; +// info->object = bestPoly->object; +// info->normal = bestPoly->plane; +// info->material = bestPoly->material; +// return true; +// } +// +// return false; +//} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateWheels() +{ + disableCollision(); + static Polyhedron polyh; +// static ExtrudedPolyList sExtrudedList; + mWheelContact = false; + + MatrixF currMatrix; + mRigid.state.getTransform(&currMatrix); + + for (S32 i = 0; i != mDataBlock->wheelCount; i++) { + WheeledVehicleData::Wheel* wheelData = &mDataBlock->wheel[i]; + Wheel* currWheel = &mWheel[i]; + + currWheel->extension = 1; + + Point3F sp; + Point3F vec; + currMatrix.mulP(wheelData->pos,&sp); + currMatrix.mulV(wheelData->spring,&vec); + Point3F hp = sp - vec * currWheel->extension; + Point3F ep = sp + vec * currWheel->extension; + + MatrixF wmat = currMatrix; + wmat.setColumn(3,hp); + F32 wr = mDataBlock->tire.radius, ww = wr / 2; + Box3F box; + box.min.set(-ww,-wr,0); + box.max.set(+ww,+wr,2*wr); + polyh.buildBox(wmat,box); + + // DMMTODO: Replace with search through working set... + // + CollisionList collisionList; + if (mContainer->buildCollisionList(polyh, + hp, ep, vec * 2.0f, + sClientCollisionMask & ~PlayerObjectType, + &collisionList)) { + currWheel->evel = 0; + + if (collisionList.t > 0.5) + currWheel->extension *= (collisionList.t - 0.5) * 2.0f; + else + currWheel->extension = 0; + + currWheel->surface.contact = true; + currWheel->surface.pos = sp + vec * currWheel->extension; + currWheel->surface.object = collisionList.collision[0].object; + mWheelContact = true; + + // Pick flatest surface normal + F32 dot = 1E30f; + Collision *collision, *cp = collisionList.collision; + Collision *ep = cp + collisionList.count; + for (; cp != ep; cp++) { + F32 d = mDot(cp->normal,vec); + if (d < dot) { + collision = cp; + dot = d; + } + } + + currWheel->surface.normal = collision->normal; + currWheel->surface.material = collision->material; + } + else { + // Make sure that we haven't sunk into the ground... + Point3F safety; + currMatrix.mulP(wheelData->safePos,&safety); + + RayInfo rInfo; + if (mContainer->castRay(safety, sp, sClientCollisionMask & ~PlayerObjectType, &rInfo)) { + // Actually stuck at this point + currWheel->evel = 0; + currWheel->extension = 0; + currWheel->surface.normal = rInfo.normal; + currWheel->surface.pos = rInfo.point; + currWheel->surface.material = rInfo.material; + currWheel->surface.contact = true; + currWheel->surface.object = rInfo.object; + mWheelContact = true; + } else { + // Ok, no collision. + currWheel->surface.contact = false; + currWheel->surface.object = NULL; + } + } + } + enableCollision(); +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateWheelThreads() +{ + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + if (wp->springThread) { + F32 p = wp->extension; + if (p > wp->center) + p = wp->center; + mShapeInstance->setPos(wp->springThread,1 - p); + } + if (wp->steeringThread) { + F32 t = (mSteering.x * mFabs(mSteering.x)) / mDataBlock->maxSteeringAngle; + mShapeInstance->setPos(wp->steeringThread,0.5 - t * 0.5); + } + if (wp->rotationThread) + mShapeInstance->setPos(wp->rotationThread,wp->apos); + } +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateEngineSound(F32 level) +{ + if (mEngineSound) { + alxSourceMatrixF(mEngineSound, &getTransform()); + alxSourcef(mEngineSound, AL_GAIN_LINEAR, level); + } +} + +void WheeledVehicle::updateSqueelSound(F32 level) +{ + if (!mDataBlock->sound[WheeledVehicleData::SqueelSound]) + return; + // Allocate/Deallocate voice on demand. + if (level < sMinSqueelVolume) { + if (mSqueelSound) { + alxStop(mSqueelSound); + mSqueelSound = 0; + } + } + else { + if (!mSqueelSound) + mSqueelSound = alxPlay(mDataBlock->sound[WheeledVehicleData::SqueelSound], &getTransform()); + + alxSourceMatrixF(mSqueelSound, &getTransform()); + alxSourcef(mSqueelSound, AL_GAIN_LINEAR, level); + } +} + +void WheeledVehicle::updateJet(F32 ) +{ + if (!mDataBlock->sound[WheeledVehicleData::JetSound]) + return; + // Allocate/Deallocate voice on demand. + if (!mJetting) { + if (mJetSound) { + alxStop(mJetSound); + mJetSound = 0; + } + } + else { + if (!mJetSound) + mJetSound = alxPlay(mDataBlock->sound[WheeledVehicleData::JetSound], &getTransform()); + + alxSourceMatrixF(mJetSound, &getTransform()); + } +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::renderImage(SceneState* state, SceneRenderImage* image) +{ + updateWheelThreads(); + Parent::renderImage(state, image); +} + + +//---------------------------------------------------------------------------- + +bool WheeledVehicle::writePacketData(GameConnection *connection, BitStream *stream) +{ + bool ret = Parent::writePacketData(connection, stream); + stream->writeFlag(mBraking); + + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + stream->write(wp->avel); + stream->write(wp->Dy); + stream->write(wp->Dx); + } + return ret; +} + +void WheeledVehicle::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + mBraking = stream->readFlag(); + + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + stream->read(&wp->avel); + stream->read(&wp->Dy); + stream->read(&wp->Dx); + } + + setPosition(mRigid.state.linPosition,mRigid.state.angPosition); + mDelta.pos = mRigid.state.linPosition; + mDelta.rot[1] = mRigid.state.angPosition; +} + +U32 WheeledVehicle::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( ((GameConnection *) con)->getControlObject() == this && !(mask & InitialUpdateMask)) + return retMask; + + stream->writeFlag(mBraking); + + if (stream->writeFlag(mask & PositionMask)) { + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + stream->write(wp->avel); + stream->write(wp->Dy); + stream->write(wp->Dx); + } + } + return retMask; +} + +void WheeledVehicle::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con,stream); + + if( ((GameConnection *) con)->getControlObject() == this) + return; + + mBraking = stream->readFlag(); + + if (stream->readFlag()) { + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + stream->read(&wp->avel); + stream->read(&wp->Dy); + stream->read(&wp->Dx); + } + } +} + +void WheeledVehicle::initPersistFields() +{ + Parent::initPersistFields(); +} + diff --git a/game/vehicles/wheeledVehicle.h b/game/vehicles/wheeledVehicle.h new file mode 100644 index 0000000..f16eea7 --- /dev/null +++ b/game/vehicles/wheeledVehicle.h @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WHEELEDVEHICLE_H_ +#define _WHEELEDVEHICLE_H_ + +#ifndef _VEHICLE_H_ +#include "game/vehicles/vehicle.h" +#endif + +#ifndef _CLIPPEDPOLYLIST_H_ +#include "collision/clippedPolyList.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; + + +//---------------------------------------------------------------------------- + +struct WheeledVehicleData: public VehicleData { + typedef VehicleData Parent; + + enum Constants { + MaxWheels = 8, + MaxWheelBits = 3 + }; + + enum Sounds { + JetSound, + EngineSound, + SqueelSound, + WheelImpactSound, + MaxSounds, + }; + AudioProfile* sound[MaxSounds]; + + struct Tire { + F32 friction; + F32 restitution; + F32 radius; + F32 lateralForce; + F32 lateralDamping; + F32 lateralRelaxation; + F32 longitudinalForce; + F32 longitudinalDamping; + F32 longitudinalRelaxation; + ParticleEmitterData* emitter; + } tire; + + F32 staticLoadScale; + F32 springForce; + F32 springDamping; + F32 antiSwayForce; + F32 antiRockForce; + F32 maxWheelSpeed; + F32 engineTorque; + F32 breakTorque; + + F32 stabilizerForce; + F32 gyroForce; + F32 gyroDamping; + + // Initialized onAdd + struct Wheel { + enum Steering { + None, + Forward, + Backward, + } steering; + S32 opposite; + F32 springForce; // Spring Constant in 1G + F32 springDamping; // Shock absorber + F32 springRest; // Resting point at 1G + Point3F safePos; + Point3F pos; + Point3F spring; + S32 springNode; + S32 springSequence; + S32 rotationSequence; + S32 steeringSequence; + } wheel[MaxWheels]; + U32 wheelCount; + ClippedPolyList rigidBody; // Planes extracted from shape + S32 tailLightSequence; + + // + WheeledVehicleData(); + DECLARE_CONOBJECT(WheeledVehicleData); + static void initPersistFields(); + bool preload(bool, char errorBuffer[256]); + bool mirrorWheel(Wheel* we); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class WheeledVehicle: public Vehicle +{ + typedef Vehicle Parent; + +// struct StateDelta: Vehicle::StateDelta { +// struct Wheel { +// F32 pos; +// F32 posVec; +// } wheel[WheeledVehicleData::MaxWheels]; +// }; +// StateDelta delta; + + WheeledVehicleData* mDataBlock; + + bool mBraking; + bool mWheelContact; + TSThread* mTailLightThread; + + AUDIOHANDLE mJetSound; + AUDIOHANDLE mEngineSound; + AUDIOHANDLE mSqueelSound; + + struct Wheel { + F32 k; // Spring coefficient + F32 s; // Shock absorber coefficient + F32 center; // Neutral point of spring + F32 extension; // Spring extension (0-1) + F32 evel; + F32 avel; + F32 apos; + F32 Dy,Dx; + struct Surface { + bool contact; + Point3F normal; + U32 material; + Point3F pos; + SceneObject* object; + } surface; + F32 traction; + TSThread* springThread; + TSThread* rotationThread; + TSThread* steeringThread; + + F32 torqueScale; // 0-1 + F32 particleSlip; // 0-1 slip + Point3F particleAxis; // Last tire axis + SimObjectPtr emitter; + }; + Wheel mWheel[WheeledVehicleData::MaxWheels]; + + // + bool onNewDataBlock(GameBaseData* dptr); + void processTick(const Move* move); + void updateMove(const Move *move); + void updateWarp(); + void updateWheels(); + void updateForces(F32 dt); + void updateWheelThreads(); + void renderImage(SceneState* state, SceneRenderImage*); + + // Client sounds & particles + void updateJet(F32 dt); + void updateEngineSound(F32 level); + void updateSqueelSound(F32 level); + + 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&, const SphereF&); + + bool writePacketData(GameConnection *, BitStream *stream); + void readPacketData(GameConnection *, BitStream *stream); + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); +}; + + +#endif diff --git a/game/version.cc b/game/version.cc new file mode 100644 index 0000000..919d3d1 --- /dev/null +++ b/game/version.cc @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "game/version.h" + +static const U32 csgVersionNumber = 1000; + +U32 getVersionNumber() +{ + return csgVersionNumber; +} + + diff --git a/game/version.h b/game/version.h new file mode 100644 index 0000000..f17f484 --- /dev/null +++ b/game/version.h @@ -0,0 +1,13 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _VERSION_H_ +#define _VERSION_H_ + +U32 getVersionNumber(); + +#endif diff --git a/game/weaponBeam.cc b/game/weaponBeam.cc new file mode 100644 index 0000000..3a74f19 --- /dev/null +++ b/game/weaponBeam.cc @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Math/mMath.h" + +#include "game/weaponBeam.h" +#include "PlatformWin32/platformGL.h" + +//************************************************************************** +// Weapon Beam +//************************************************************************** +WeaponBeam::WeaponBeam() +{ + +}; + + +//-------------------------------------------------------------------------- +void WeaponBeam::render( F32 width, F32 UVOffset, F32 numRepeat ) +{ + width *= 0.5; + + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f( UVOffset, 0.0 ); + glVertex3fv( mData.startPos + mData.axis * width ); + + glTexCoord2f( UVOffset, 1.0 ); + glVertex3fv( mData.startPos - mData.axis * width ); + + glTexCoord2f( numRepeat+UVOffset, 1.0 ); + glVertex3fv( mData.endRenderPos - mData.axis * width ); + + glTexCoord2f( numRepeat+UVOffset, 0.0 ); + glVertex3fv( mData.endRenderPos + mData.axis * width ); + glEnd(); + + +} + +//-------------------------------------------------------------------------- +void WeaponBeam::adjustEdge( BeamData &beamData ) +{ + + beamData.onEdge = false; + beamData.endRenderPos = beamData.endPos; + + if( beamData.angToCamPos < BEAM_EDGE_TOLERANCE ) + { + + Point3F diff = beamData.direction - (-beamData.dirFromCam); + F32 curOffset = diff.len(); + if( curOffset <= BEAM_EDGE_EPSILON ) + { + // beam is perfectly on edge + curOffset = 0.0; + diff.set(0.0, 0.0, 0.0 ); + beamData.onEdge = true; + } + else + { + diff.normalize(); + } + diff *= BEAM_EDGE_ADJUST - curOffset; + + Point3F newDir = beamData.direction + diff; + newDir.normalizeSafe(); + + beamData.endRenderPos = beamData.startPos + newDir * beamData.length; + + } + +} diff --git a/game/weaponBeam.h b/game/weaponBeam.h new file mode 100644 index 0000000..b9523f2 --- /dev/null +++ b/game/weaponBeam.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WEAPONBEAM_H_ +#define _WEAPONBEAM_H_ + +#define BEAM_EDGE_TOLERANCE -0.99980 // 1.1459 degrees +#define BEAM_EDGE_ADJUST 0.022 +#define BEAM_EDGE_EPSILON 0.0001 + +class Point3F; + +//************************************************************************** +// Beam Data +//************************************************************************** + +struct BeamData +{ + Point3F direction; + Point3F startPos; + Point3F endPos; // actual end position + Point3F endRenderPos; // modified if beam is on-edge + Point3F axis; // axis for front facing beam + F32 angToCamPos; // angle from line btwn cam pos and inital pos and the beam dir + F32 angToCamDir; // angle btwn cam look dir and beam dir + bool onEdge; // true if beam perfectly lines up with camera + F32 length; + Point3F dirFromCam; // dir from camPos to beam initialPos + + + BeamData() + { + dMemset( this, 0, sizeof( BeamData ) ); + } +}; + + +//************************************************************************** +// Weapon Beam +//************************************************************************** +class WeaponBeam +{ +private: + +public: + + + BeamData mData; + + +public: + + WeaponBeam(); + + void adjustEdge( BeamData &beamData ); + void render( F32 width, F32 UVOffset = 0.0, F32 numRepeat = 1.0 ); + + + + +}; + + +#endif diff --git a/game/wheeledVehicle.cc b/game/wheeledVehicle.cc new file mode 100644 index 0000000..ed3bdf5 --- /dev/null +++ b/game/wheeledVehicle.cc @@ -0,0 +1,1287 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "dgl/dgl.h" +#include "game/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 "game/moveManager.h" +#include "core/bitStream.h" +#include "core/dnet.h" +#include "game/gameConnection.h" +#include "ts/tsShapeInstance.h" +#include "game/wheeledVehicle.h" +#include "game/particleEngine.h" +#include "audio/audio.h" +#include "game/forceFieldBare.h" +#include "scenegraph/sceneGraph.h" +#include "sim/decalManager.h" +#include "dgl/materialPropertyMap.h" +#include "terrain/terrData.h" + +//---------------------------------------------------------------------------- + +static U32 sCollisionMoveMask = (TerrainObjectType | InteriorObjectType | + PlayerObjectType | StaticShapeObjectType | + VehicleObjectType | VehicleBlockerObjectType | + ForceFieldObjectType | StaticTSObjectType); +static U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType +static U32 sClientCollisionMask = sCollisionMoveMask; + +static F32 sWheeledVehicleGravity = -20; +static F32 sBreakZeroEpsilon = 0.02; // m/s +static F32 sTireEmitterVerticalScale = 0.7; +static F32 sTireCollisionExpansion = 0.02; // Expanded size in buildPolyList + +// Sound +static F32 sMinSqueelVolume = 0.05; +static F32 sIdleEngineVolume = 0.2; + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleData); + +WheeledVehicleData::WheeledVehicleData() +{ + tire.friction = 0.3; + tire.restitution = 1; + tire.radius = 0.6; + tire.lateralForce = 1000; + tire.lateralDamping = 100; + tire.lateralRelaxation = 1; + tire.longitudinalForce = 1000; + tire.longitudinalDamping = 100; + tire.longitudinalRelaxation = 1; + tire.emitter = 0; + maxWheelSpeed = 40; + engineTorque = 1; + breakTorque = 1; + staticLoadScale = 1; + antiSwayForce = 1; + antiRockForce = 0; + springForce = 0.6; + tailLightSequence = -1; + + stabilizerForce = 0; + gyroForce = 0; + gyroDamping = 0; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = 0; +} + +bool WheeledVehicleData::preload(bool server, char errorBuffer[256]) +{ + if (!Parent::preload(server, errorBuffer)) + return false; + + TSShapeInstance* si = new TSShapeInstance(shape, 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 (tire.emitter) + Sim::findObject(SimObjectId(tire.emitter),tire.emitter); + } + + // Extract wheel information from the shape + TSThread* thread = si->addThread(); + Wheel* wp = wheel; + for (S32 i = 0; i < MaxWheels; i++) { + char buff[10]; + + // Spring and ground information have to exist for + // the wheel to operate at all. + dSprintf(buff,sizeof(buff),"ground%d",i); + wp->springNode = shape->findNode(buff); + dSprintf(buff,sizeof(buff),"spring%d",i); + wp->springSequence = shape->findSequence(buff); + if (wp->springSequence != -1 && wp->springNode != -1) { + + // Extract spring pos & movement + si->setSequence(thread,wp->springSequence,0); + si->animate(); + si->mNodeTransforms[wp->springNode].getColumn(3,&wp->spring); + si->setPos(thread,1); + si->animate(); + si->mNodeTransforms[wp->springNode].getColumn(3, &wp->pos); + wp->safePos.set(wp->pos.x, wp->pos.y, wp->pos.z + tire.radius); + if (!mirrorWheel(wp)) { + wp->spring.x = wp->spring.y = 0; + wp->spring.z -= wp->pos.z; + } + + // More animation sequences + dSprintf(buff,sizeof(buff),"turn%d",i); + wp->steeringSequence = shape->findSequence(buff); + dSprintf(buff,sizeof(buff),"wheel%d",i); + wp->rotationSequence = shape->findSequence(buff); + + // + wp->springRest = 0.5; + wp->springForce = springForce; + wp->springDamping = springDamping; + wp->steering = (wp->steeringSequence != -1)? Wheel::Forward: Wheel::None; + wp++; + } + } + wheelCount = wp - wheel; + + // + tailLightSequence = shape->findSequence("taillight"); + + // Extract collision planes from shape collision detail level + if (collisionDetails[0] != -1) { + MatrixF imat(1); + SphereF sphere; + sphere.center = shape->center; + sphere.radius = shape->radius; + PlaneExtractorPolyList polyList; + polyList.mPlaneList = &rigidBody.mPlaneList; + polyList.setTransform(&imat, Point3F(1,1,1)); + si->buildPolyList(&polyList,collisionDetails[0]); + } + + delete si; + return true; +} + +bool WheeledVehicleData::mirrorWheel(Wheel* we) +{ + we->opposite = -1; + // Find matching wheel mirrored along Y axis + 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->spring = wp->spring; + we->opposite = wp - wheel; + wp->opposite = we - wheel; + return true; + } + return false; +} + + +void WheeledVehicleData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("jetSound", TypeAudioProfilePtr, Offset(sound[JetSound], WheeledVehicleData)); + addField("engineSound", TypeAudioProfilePtr, Offset(sound[EngineSound], WheeledVehicleData)); + addField("squeelSound", TypeAudioProfilePtr, Offset(sound[SqueelSound], WheeledVehicleData)); + addField("WheelImpactSound", TypeAudioProfilePtr, Offset(sound[WheelImpactSound], WheeledVehicleData)); + + addField("tireRadius", TypeF32, Offset(tire.radius, WheeledVehicleData)); + addField("tireFriction", TypeF32, Offset(tire.friction, WheeledVehicleData)); + addField("tireRestitution", TypeF32, Offset(tire.restitution, WheeledVehicleData)); + addField("tireLateralForce", TypeF32, Offset(tire.lateralForce, WheeledVehicleData)); + addField("tireLateralDamping", TypeF32, Offset(tire.lateralDamping, WheeledVehicleData)); + addField("tireLateralRelaxation", TypeF32, Offset(tire.lateralRelaxation, WheeledVehicleData)); + addField("tireLongitudinalForce", TypeF32, Offset(tire.longitudinalForce, WheeledVehicleData)); + addField("tireLongitudinalDamping", TypeF32, Offset(tire.longitudinalDamping, WheeledVehicleData)); + addField("tireLogitudinalRelaxation", TypeF32, Offset(tire.longitudinalRelaxation, WheeledVehicleData)); + addField("tireEmitter",TypeParticleEmitterDataPtr, Offset(tire.emitter, WheeledVehicleData)); + + addField("maxWheelSpeed", TypeF32, Offset(maxWheelSpeed, WheeledVehicleData)); + addField("engineTorque", TypeF32, Offset(engineTorque, WheeledVehicleData)); + addField("breakTorque", TypeF32, Offset(breakTorque, WheeledVehicleData)); + addField("staticLoadScale", TypeF32, Offset(staticLoadScale, WheeledVehicleData)); + addField("springForce", TypeF32, Offset(springForce, WheeledVehicleData)); + addField("springDamping", TypeF32, Offset(springDamping, WheeledVehicleData)); + addField("antiSwayForce", TypeF32, Offset(antiSwayForce, WheeledVehicleData)); + addField("antiRockForce", TypeF32, Offset(antiRockForce, WheeledVehicleData)); + + addField("stabilizerForce", TypeF32, Offset(stabilizerForce, WheeledVehicleData)); + addField("gyroForce", TypeF32, Offset(gyroForce, WheeledVehicleData)); + addField("gyroDamping", TypeF32, Offset(gyroDamping, WheeledVehicleData)); +} + +void WheeledVehicleData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(tire.friction); + stream->write(tire.restitution); + stream->write(tire.radius); + stream->write(tire.lateralForce); + stream->write(tire.lateralDamping); + stream->write(tire.lateralRelaxation); + stream->write(tire.longitudinalForce); + stream->write(tire.longitudinalDamping); + stream->write(tire.longitudinalRelaxation); + + if (stream->writeFlag(tire.emitter)) + stream->writeRangedU32(packed? SimObjectId(tire.emitter): + tire.emitter->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(springForce); + stream->write(springDamping); + stream->write(antiSwayForce); + stream->write(antiRockForce); + stream->write(maxWheelSpeed); + stream->write(engineTorque); + stream->write(breakTorque); + stream->write(staticLoadScale); + + stream->write(stabilizerForce); + stream->write(gyroForce); + stream->write(gyroDamping); +} + +void WheeledVehicleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&tire.friction); + stream->read(&tire.restitution); + stream->read(&tire.radius); + stream->read(&tire.lateralForce); + stream->read(&tire.lateralDamping); + stream->read(&tire.lateralRelaxation); + stream->read(&tire.longitudinalForce); + stream->read(&tire.longitudinalDamping); + stream->read(&tire.longitudinalRelaxation); + + tire.emitter = stream->readFlag()? + (ParticleEmitterData*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast): 0; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = stream->readFlag()? + (AudioProfile*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast): 0; + + stream->read(&springForce); + stream->read(&springDamping); + stream->read(&antiSwayForce); + stream->read(&antiRockForce); + stream->read(&maxWheelSpeed); + stream->read(&engineTorque); + stream->read(&breakTorque); + stream->read(&staticLoadScale); + + stream->read(&stabilizerForce); + stream->read(&gyroForce); + stream->read(&gyroDamping); +} + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(WheeledVehicle); + +WheeledVehicle::WheeledVehicle() +{ + mGenerateShadow = true; + + mBraking = false; + mWheelContact = false; + mTailLightThread = 0; + + for (S32 i = 0; i < WheeledVehicleData::MaxWheels; i++) { + mWheel[i].springThread = 0; + mWheel[i].steeringThread = 0; + mWheel[i].rotationThread = 0; + mWheel[i].Dy = mWheel[i].Dx = 0; + } + + mJetSound = 0; + mEngineSound = 0; + mSqueelSound = 0; + + mDataBlock = NULL; +} + +WheeledVehicle::~WheeledVehicle() +{ + if (mJetSound) + alxStop(mJetSound); + if (mEngineSound) + alxStop(mEngineSound); + if (mSqueelSound) + alxStop(mSqueelSound); +} + + +//---------------------------------------------------------------------------- + +bool WheeledVehicle::onAdd() +{ + if(!Parent::onAdd()) + return false; + + addToScene(); + if (isServerObject()) + scriptOnAdd(); + return true; +} + +bool WheeledVehicle::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + F32 frontStatic = 0; + F32 backStatic = 0; + F32 fCount = 0; + F32 bCount = 0; + + // Wheel threads + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + WheeledVehicleData::Wheel* wd = &mDataBlock->wheel[i]; + Wheel* wp = &mWheel[i]; + + wp->surface.contact = false; + wp->surface.object = NULL; + wp->evel = 0; + wp->avel = 0; + wp->apos = 0; + wp->steeringThread = 0; + wp->springThread = 0; + wp->rotationThread = 0; + + if (wd->steeringSequence != -1) { + wp->steeringThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(wp->steeringThread,wd->steeringSequence,0); + } + if (wd->springSequence != -1) { + wp->springThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(wp->springThread,wd->springSequence,0); + } + if (wd->rotationSequence != -1) { + wp->rotationThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(wp->rotationThread,wd->rotationSequence,0); + } + + // Set springs to currenty gravity + wp->k = wd->springForce; + wp->s = wd->springDamping; +// F32 staticForce = (mMass * -sWheeledVehicleGravity) / mDataBlock->wheelCount; +// F32 sprungForce = staticForce * 0.8; +// wp->center = wd->springRest + (sprungForce / wp->k); +// if (wp->center > 1) + wp->center = 1; + wp->extension = wp->center; + + // + wp->particleSlip = 0; + if (mDataBlock->tire.emitter) { + wp->emitter = new ParticleEmitter; + wp->emitter->onNewDataBlock(mDataBlock->tire.emitter); + wp->emitter->registerObject(); + } + else + wp->emitter = NULL; + } + + // + if (mDataBlock->tailLightSequence != -1) { + mTailLightThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mTailLightThread,mDataBlock->tailLightSequence,0); + } + else + mTailLightThread = 0; + + // Sounds + if (mJetSound) { + alxStop(mJetSound); + mJetSound = 0; + } + if (mEngineSound) { + alxStop(mEngineSound); + mEngineSound = 0; + } + if (mSqueelSound) { + alxStop(mSqueelSound); + mSqueelSound = 0; + } + if (isGhost()) { + if (mDataBlock->sound[WheeledVehicleData::EngineSound]) + mEngineSound = alxPlay(mDataBlock->sound[WheeledVehicleData::EngineSound], &getTransform()); + } + + scriptOnNewDataBlock(); + return true; +} + +void WheeledVehicle::onRemove() +{ + if (mDataBlock != NULL) + { + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + if (bool(wp->emitter)) { + wp->emitter->deleteWhenEmpty(); + wp->emitter = 0; + } + } + + } + scriptOnRemove(); + removeFromScene(); + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateWarp() +{ + updateWheels(); +} + +void WheeledVehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + Point3F bz; + mObjToWorld.getColumn(2,&bz); + + if (mTailLightThread) + mShapeInstance->advanceTime(dt,mTailLightThread); + + // Update wheels + if (mFrozen == false) + { + F32 slipTotal = 0; + F32 torqueTotal = 0; + for (S32 i = 0; i < mDataBlock->wheelCount; i++) + { + Wheel* wp = &mWheel[i]; + + // Update angular position + wp->apos += (wp->avel * dt) / M_2PI; + wp->apos -= mFloor(wp->apos); + if (wp->apos < 0) + wp->apos = 1 - wp->apos; + + // Keep track of largest slip + slipTotal += wp->particleSlip; + torqueTotal += wp->torqueScale; + + Point3F wv = Parent::getVelocity(); + + // emit dust if moving + if( wv.len() > 1.0 && wp->surface.object && wp->surface.object->getTypeMask() & TerrainObjectType ) + { + TerrainBlock* tBlock = static_cast(wp->surface.object); + S32 mapIndex = tBlock->mMPMIndex[0]; + + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntryFromIndex(mapIndex); + + if(pEntry) + { + S32 x; + ColorF colorList[ParticleEngine::PC_COLOR_KEYS]; + + for(x = 0; x < 2; ++x) + colorList[x].set(pEntry->puffColor[x].red, + pEntry->puffColor[x].green, + pEntry->puffColor[x].blue, + pEntry->puffColor[x].alpha); + + for(x = 2; x < ParticleEngine::PC_COLOR_KEYS; ++x) + colorList[x].set( 1.0, 1.0, 1.0, 0.0 ); + + wp->emitter->setColors( colorList ); + } + Point3F axis = wv; + axis.normalize(); + + wp->emitter->emitParticles(wp->surface.pos + Point3F( 0.0, 0.0, 0.5 ),true, + axis, wv, dt * 1000 * (wv.len() / 15.0) ); + } + } + + // + updateJet(dt); + updateSqueelSound(slipTotal / mDataBlock->wheelCount); + updateEngineSound(sIdleEngineVolume + (1 - sIdleEngineVolume) * + (1 - (torqueTotal / mDataBlock->wheelCount))); + } +} + + +//---------------------------------------------------------------------------- + +bool WheeledVehicle::buildPolyList(AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) +{ + // Parent will take care of body collision. + Parent::buildPolyList(polyList,box,sphere); + + // Add wheels. + Box3F wbox; + wbox.min.x = -(wbox.max.x = (mDataBlock->tire.radius / 2) + sTireCollisionExpansion); + wbox.min.y = -(wbox.max.y = mDataBlock->tire.radius + sTireCollisionExpansion); + wbox.max.z = 2 * mDataBlock->tire.radius; + wbox.min.z = 0; + MatrixF mat = mObjToWorld; + + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + WheeledVehicleData::Wheel* wd = &mDataBlock->wheel[i]; + Wheel* wp = &mWheel[i]; + + Point3F sp,vec; + mObjToWorld.mulP(wd->pos,&sp); + mObjToWorld.mulV(wd->spring,&vec); + Point3F ep = sp + (vec * wp->extension); + mat.setColumn(3,ep); + polyList->setTransform(&mat,Point3F(1,1,1)); + polyList->addBox(wbox); + } + return !polyList->isEmpty(); +} + + +//---------------------------------------------------------------------------- +void WheeledVehicle::processTick(const Move* move) +{ + Parent::processTick(move); + + if (isServerObject() && mWaterCoverage > 0.5) + { + char buffer1[256], buffer2[256]; + dSprintf(buffer1, 255, "%f %f %f", + mRigid.state.linPosition.x, + mRigid.state.linPosition.y, + mRigid.state.linPosition.z); + dSprintf(buffer2, 255, "%d", Con::getIntVariable("$DamageType::Water")); + Con::executef(mDataBlock, 6, "damageObject", scriptThis(), "0", buffer1, "1000", buffer2); + + } +} + +void WheeledVehicle::updateMove(const Move* move) +{ + Parent::updateMove(move); + + // Breaking + F32 wvel = 0; + for (S32 i = 0; i < mDataBlock->wheelCount; i++) + wvel += mWheel[i].avel; + if (mFabs(wvel * mDataBlock->tire.radius) < sBreakZeroEpsilon) + wvel = 0; + if (mBraking) { + // If the throttle is not set, or is set in the same direction + // as are wheel velocity, then we are no longer breaking. + if (!wvel || !mThrottle || (mThrottle > 0 && wvel > 0) || (mThrottle < 0 && wvel < 0)) + mBraking = false; + } + else + // If the throttle is opposite to our wheel velocity, then break. + if ((mThrottle > 0 && wvel < 0) || (mThrottle < 0 && wvel > 0)) + mBraking = true; + + if (mTailLightThread) + mShapeInstance->setTimeScale(mTailLightThread,mBraking? 1: -1); + + // + updateWheels(); + + if (mWheelContact) + mSteering.y = 0; +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateForces(F32 dt) +{ + S32 j; + + MatrixF currMatrix; + mRigid.state.getTransform(&currMatrix); + MatrixF currMatrixInv = currMatrix; + currMatrixInv.inverse(); + + // + F32 oneOverSprungMass = 1 / (mMass * 0.8); + F32 maxAvel = mDataBlock->maxWheelSpeed / mDataBlock->tire.radius; + F32 aMomentum = mMass / mDataBlock->wheelCount; + + mRigid.state.force.set(0, 0, 0); + mRigid.state.torque.set(0, 0, 0); + + // Drag + { + mRigid.state.force -= mRigid.state.linVelocity * mDataBlock->minDrag * mOneOverMass; + mRigid.state.torque -= mRigid.state.angMomentum * mDataBlock->antiRockForce * mOneOverMass; + } + + // Body & Steering Vectors + Point3F bx,by,bz; + { + currMatrix.getColumn(0,&bx); + currMatrix.getColumn(1,&by); + currMatrix.getColumn(2,&bz); + } + + Point3F worldZ(0, 0, 1); + Point3F worldY(0, 1, 0); + currMatrix.getColumn(1, &worldY); + currMatrix.getColumn(2, &worldZ); + + F32 quadraticSteering = -(mSteering.x * mFabs(mSteering.x)); + F32 cosSteering; + F32 sinSteering; + mSinCos(quadraticSteering, sinSteering, cosSteering); + + // Center of mass in world space + Point3F massCenter; + currMatrix.mulP(mDataBlock->massCenter, &massCenter); + currMatrix.mulP(Point3F(0, 0, 0), &massCenter); + + Point3F force = Point3F(0,0,0); + Point3F torque = Point3F(0,0,0); + + AssertFatal(mDataBlock->wheelCount < 8, "Error, only <= 8 wheels supported"); + Point3F wheelForce[8]; + + // Calculate vertical load for friction. This was marked by TimG as "a hack" + U32 contactCount = 0; + F32 verticalLoad = 0; + { + for (j = 0; j < mDataBlock->wheelCount; j++) { + if (mWheel[j].surface.contact) + contactCount++; + } + + if (contactCount != 0) { + verticalLoad = (mDataBlock->staticLoadScale * + (mMass * -sWheeledVehicleGravity) / contactCount); + } + } + + // Engine and break torque + F32 engineTorque,breakTorque,maxBreakVel; + if (mBraking) { + breakTorque = mDataBlock->breakTorque * mFabs(mThrottle); + maxBreakVel = (breakTorque / aMomentum) * dt; + engineTorque = 0; + } + else { + if (mThrottle) { + engineTorque = mDataBlock->engineTorque * mThrottle; + maxBreakVel = breakTorque = 0; + if (mThrottle > 0 && mJetting) + // Double the engineTorque to help out the jets + engineTorque += mDataBlock->engineTorque; + } + else { + // Engine break. + breakTorque = mDataBlock->engineTorque; + maxBreakVel = (breakTorque / aMomentum) * dt; + engineTorque = 0; + } + } + + force = Point3F(0, 0, sWheeledVehicleGravity); + + // Jet Force + if (mJetting) + { + force += worldY * (mDataBlock->jetForce * mOneOverMass); + } + + // Calculate wheel forces + // + for (j = 0; j < mDataBlock->wheelCount; j++) + { + Wheel* wheel = &mWheel[j]; + WheeledVehicleData::Wheel* wheelData = &mDataBlock->wheel[j]; + + // Zero the force on this wheel + Point3F& forceVector = wheelForce[j]; + forceVector.set(0, 0, 0); + + if (wheel->surface.contact) { + // Wheel in contact with the ground. + // Forces acting on the body due to this wheel: + // - Spring forces + + // Torques acting on the body due to this wheel: + // - None + + // First, let's compute the wheels position, and worldspace velocity + Point3F pos, r, localVel; + currMatrix.mulP(wheelData->pos, &pos); + r = pos - massCenter; + getVelocity(r, &localVel); + + // Spring forces on this wheel act in the z direction of the body, at the point + // of contact. + { + // Spring force + F32 spring = wheel->k * (wheel->center - wheel->extension); + + // Damping in the spring + F32 damping = wheel->s * -mDot(bz, localVel); + if (damping < 0) + damping = 0; + + // Anti-sway force based on difference in suspension extension + F32 antiSway = 0; + if (wheelData->opposite != -1) + { + Wheel* oppositeWheel = &mWheel[wheelData->opposite]; + if (oppositeWheel->surface.contact) { + antiSway = ((oppositeWheel->extension - wheel->extension) * + mDataBlock->antiSwayForce); + } + if (antiSway < 0) + antiSway = 0; + } + + forceVector += bz * ((spring + damping + antiSway) * oneOverSprungMass); + } + + // Tire direction vectors perpendicular to surface normal + Point3F wheelXVec; + if (wheelData->steering == WheeledVehicleData::Wheel::Forward) { + wheelXVec = bx * cosSteering; + wheelXVec += by * sinSteering; + } + else if (wheelData->steering == WheeledVehicleData::Wheel::Backward) { + wheelXVec = bx * cosSteering; + wheelXVec -= by * sinSteering; + } + else { + wheelXVec = bx; + } + + Point3F tireX, tireY; + mCross(wheel->surface.normal, wheelXVec, &tireY); + tireY.normalize(); + mCross(tireY, wheel->surface.normal, &tireX); + tireX.normalize(); + + // Velocity of wheel at surface contact + Point3F wheelContact = wheel->surface.pos - massCenter; + Point3F wheelVelocity; + getVelocity(wheelContact, &wheelVelocity); + F32 xVelocity = mDot(tireX, wheelVelocity); + F32 yVelocity = mDot(tireY, wheelVelocity); + + // Longitudinal deformation force + F32 ddy = ((wheel->avel * mDataBlock->tire.radius) - + yVelocity - + mDataBlock->tire.longitudinalRelaxation * mFabs(wheel->avel) * wheel->Dy); + + wheel->Dy += ddy * dt; + + F32 Fy = (mDataBlock->tire.longitudinalForce * wheel->Dy + + mDataBlock->tire.longitudinalDamping * ddy); + + // Lateral deformation force + F32 ddx = (xVelocity - + (mDataBlock->tire.lateralRelaxation * mFabs(wheel->avel) * wheel->Dx)); + + wheel->Dx += ddx * dt; + + F32 Fx = -(mDataBlock->tire.lateralForce * wheel->Dx + + mDataBlock->tire.lateralDamping * ddx); + + // Vertical load on tire. + F32 Fz = verticalLoad; + + // Reduce forces based on friction limit + F32 sN = mDot(wheel->surface.normal,Point3F(0,0,1)); + if (sN > 0) { + F32 muS = Fz * mDataBlock->tire.friction * sN; + F32 muS2 = muS * muS; + F32 Fn = (Fz * Fz) * muS2; + F32 Ff = (Fx * Fx + Fy * Fy) * muS2; + if (Ff > Fn) { + F32 K = mSqrt(Fn / Ff); + Fy *= K; + Fx *= K; + wheel->Dy *= K; + wheel->Dx *= K; + } + } + else { + Fy = Fx = 0; + } + + // Apply forces to wheel ground contact point + forceVector += (tireX * (Fx * mOneOverMass)) + (tireY * (Fy * mOneOverMass)); + + // Wheel angular acceleration from engine torque and tire + // deformation force. + wheel->torqueScale = (mFabs(wheel->avel) > maxAvel) ? 0 : + 1 - (mFabs(wheel->avel) / maxAvel); + + wheel->avel += (((wheel->torqueScale * engineTorque) - + Fy * + mDataBlock->tire.radius) / aMomentum) * dt; + + // Wheel angular acceleration from break torque + if (maxBreakVel > mFabs(wheel->avel)) + { + wheel->avel = 0; + } + else + { + if (wheel->avel > 0) + wheel->avel -= maxBreakVel; + else + wheel->avel += maxBreakVel; + } + } + else { + // Wheel not in contact with the ground + // Forces acting on the body due to this wheel: + // - None + // Torques acting on the body due to this wheel: + // - None + + wheel->torqueScale = 0; + wheel->particleSlip = 0; + wheel->Dy += (-mDataBlock->tire.longitudinalRelaxation * + mFabs(wheel->avel) * wheel->Dy) * dt; + wheel->Dx += (-mDataBlock->tire.lateralRelaxation * + mFabs(wheel->avel) * wheel->Dx) * dt; + } + } + + // Sum up the forces + { + // Now sum up the torques and forces + + for (j = 0; j < mDataBlock->wheelCount; j++) { + WheeledVehicleData::Wheel* wheelData = &mDataBlock->wheel[j]; + Point3F pos, r, t; + currMatrix.mulP(wheelData->pos, &pos); + r = pos - massCenter; + mCross(r, wheelForce[j], &t); + torque += t; + force += wheelForce[j]; + } + } + + // Container buoyancy & drag + force += Point3F(0, 0, -mBuoyancy * sWheeledVehicleGravity); + force -= mRigid.state.linVelocity * mDrag; + torque -= mRigid.state.angMomentum * mDrag; + + // Apply forces to body + mRigid.state.force += force; + mRigid.state.torque += torque; +} + + +//---------------------------------------------------------------------------- + +U32 WheeledVehicle::getCollisionMask() +{ + if (isServerObject()) + return sServerCollisionMask; + else + return sClientCollisionMask; +} + + +//bool WheeledVehicle::collideBody(const MatrixF& mat,Collision* info) +//{ +// // Database bounding box +// Box3F wBox = mObjBox; +// mat.mul(wBox); +// +// // Test the body against the database +// Box3F box; +// SphereF sphere; +// MatrixF imat(1); +// PlaneExtractorPolyList extractor; +// sPolyList->mPlaneList.clear(); +// extractor.mPlaneList = &sPolyList->mPlaneList; +// extractor.setTransform(&mat, Point3F(1,1,1)); +// mShapeInstance->buildPolyList(&extractor,mDataBlock->collisionDetails[0]); +// +// sPolyList->clear(); +// +// // 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) { +// pConvex->getPolyList(sPolyList); +// } +// pList = pList->wLink.mNext; +// } +// +// S32 count = sPolyList->mPolyList.size(); +// info->face = count? BodyCollision: 0; +// +// // Test the wheels against the database +// Point3F xvec,yvec,zvec; +// mat.getColumn(0,&xvec); +// xvec *= mObjBox.len_x(); +// mat.getColumn(1,&yvec); +// yvec *= mObjBox.len_y(); +// mat.getColumn(2,&zvec); +// zvec *= mObjBox.len_z(); +// +// for (S32 i = 0; i != mDataBlock->wheelCount; i++) { +// WheeledVehicleData::Wheel* wd = &mDataBlock->wheel[i]; +// Wheel* wp = &mWheel[i]; +// +// Box3F box; +// F32 wr = mDataBlock->tire.radius, ww = wr / 2; +// box.min.set(wd->pos.x - ww,wd->pos.y - wr,wd->pos.z); +// box.max.set(wd->pos.x + ww,wd->pos.y + wr,wd->pos.z + 2*wr); +// +// Point3F min,max; +// mat.mulP(box.min,&min); +// mat.mulP(box.max,&max); +// +// sPolyList->mPlaneList.clear(); +// sPolyList->mNormal.set(0,0,0); +// sPolyList->mPlaneList.setSize(6); +// sPolyList->mPlaneList[0].set(min,xvec); +// sPolyList->mPlaneList[0].invert(); +// sPolyList->mPlaneList[1].set(max,yvec); +// sPolyList->mPlaneList[2].set(max,xvec); +// sPolyList->mPlaneList[3].set(min,yvec); +// sPolyList->mPlaneList[3].invert(); +// sPolyList->mPlaneList[4].set(min,zvec); +// sPolyList->mPlaneList[4].invert(); +// sPolyList->mPlaneList[5].set(max,zvec); +// +// // 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) { +// pConvex->getPolyList(sPolyList); +// } +// pList = pList->wLink.mNext; +// } +// } +// +// if (sPolyList->mPolyList.size() != count) +// info->face |= WheelCollision; +// +// // Pick best collision point +// F32 bestDist = 1.0E30; +// ClippedPolyList::Poly* bestPoly = 0; +// ClippedPolyList::Vertex* bestVertex; +// +// if (sPolyList->mPolyList.size()) { +// Point3F massCenter; +// mat.mulP(mDataBlock->massCenter,&massCenter); +// +// // Pick surface with best vertex velocity +// F32 bestVd = -1; +// ClippedPolyList::Poly* poly = sPolyList->mPolyList.begin(); +// ClippedPolyList::Poly* end = sPolyList->mPolyList.end(); +// for (; poly != end; poly++) { +// U32* vi = &sPolyList->mIndexList[poly->vertexStart]; +// U32* ve = vi + poly->vertexCount; +// for (; vi != ve; vi++) { +// ClippedPolyList::Vertex* ev = &sPolyList->mVertexList[*vi]; +// +// Point3F v,r; +// r = ev->point - massCenter; +// getVelocity(r,&v); +// +// F32 dist = mDot(poly->plane,v); +// if (dist < 0 && dist < bestDist) { +// bestDist = dist; +// bestVertex = ev; +// bestPoly = poly; +// } +// } +// } +// } +// +// // +// if (bestPoly) { +// info->point = bestVertex->point; +// info->object = bestPoly->object; +// info->normal = bestPoly->plane; +// info->material = bestPoly->material; +// return true; +// } +// +// return false; +//} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateWheels() +{ + disableCollision(); + static Polyhedron polyh; +// static ExtrudedPolyList sExtrudedList; + mWheelContact = false; + + MatrixF currMatrix; + mRigid.state.getTransform(&currMatrix); + + for (S32 i = 0; i != mDataBlock->wheelCount; i++) { + WheeledVehicleData::Wheel* wheelData = &mDataBlock->wheel[i]; + Wheel* currWheel = &mWheel[i]; + + currWheel->extension = 1; + + Point3F sp; + Point3F vec; + currMatrix.mulP(wheelData->pos,&sp); + currMatrix.mulV(wheelData->spring,&vec); + Point3F hp = sp - vec * currWheel->extension; + Point3F ep = sp + vec * currWheel->extension; + + MatrixF wmat = currMatrix; + wmat.setColumn(3,hp); + F32 wr = mDataBlock->tire.radius, ww = wr / 2; + Box3F box; + box.min.set(-ww,-wr,0); + box.max.set(+ww,+wr,2*wr); + polyh.buildBox(wmat,box); + + // DMMTODO: Replace with search through working set... + // + CollisionList collisionList; + if (mContainer->buildCollisionList(polyh, + hp, ep, vec * 2.0f, + sClientCollisionMask & ~PlayerObjectType, + &collisionList)) { + currWheel->evel = 0; + + if (collisionList.t > 0.5) + currWheel->extension *= (collisionList.t - 0.5) * 2.0f; + else + currWheel->extension = 0; + + currWheel->surface.contact = true; + currWheel->surface.pos = sp + vec * currWheel->extension; + currWheel->surface.object = collisionList.collision[0].object; + mWheelContact = true; + + // Pick flatest surface normal + F32 dot = 1E30f; + Collision *collision, *cp = collisionList.collision; + Collision *ep = cp + collisionList.count; + for (; cp != ep; cp++) { + F32 d = mDot(cp->normal,vec); + if (d < dot) { + collision = cp; + dot = d; + } + } + + currWheel->surface.normal = collision->normal; + currWheel->surface.material = collision->material; + } + else { + // Make sure that we haven't sunk into the ground... + Point3F safety; + currMatrix.mulP(wheelData->safePos,&safety); + + RayInfo rInfo; + if (mContainer->castRay(safety, sp, sClientCollisionMask & ~PlayerObjectType, &rInfo)) { + // Actually stuck at this point + currWheel->evel = 0; + currWheel->extension = 0; + currWheel->surface.normal = rInfo.normal; + currWheel->surface.pos = rInfo.point; + currWheel->surface.material = rInfo.material; + currWheel->surface.contact = true; + currWheel->surface.object = rInfo.object; + mWheelContact = true; + } else { + // Ok, no collision. + currWheel->surface.contact = false; + currWheel->surface.object = NULL; + } + } + } + enableCollision(); +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateWheelThreads() +{ + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + if (wp->springThread) { + F32 p = wp->extension; + if (p > wp->center) + p = wp->center; + mShapeInstance->setPos(wp->springThread,1 - p); + } + if (wp->steeringThread) { + F32 t = (mSteering.x * mFabs(mSteering.x)) / mDataBlock->maxSteeringAngle; + mShapeInstance->setPos(wp->steeringThread,0.5 - t * 0.5); + } + if (wp->rotationThread) + mShapeInstance->setPos(wp->rotationThread,wp->apos); + } +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::updateEngineSound(F32 level) +{ + if (mEngineSound) { + alxSourceMatrixF(mEngineSound, &getTransform()); + alxSourcef(mEngineSound, AL_GAIN_LINEAR, level); + } +} + +void WheeledVehicle::updateSqueelSound(F32 level) +{ + if (!mDataBlock->sound[WheeledVehicleData::SqueelSound]) + return; + // Allocate/Deallocate voice on demand. + if (level < sMinSqueelVolume) { + if (mSqueelSound) { + alxStop(mSqueelSound); + mSqueelSound = 0; + } + } + else { + if (!mSqueelSound) + mSqueelSound = alxPlay(mDataBlock->sound[WheeledVehicleData::SqueelSound], &getTransform()); + + alxSourceMatrixF(mSqueelSound, &getTransform()); + alxSourcef(mSqueelSound, AL_GAIN_LINEAR, level); + } +} + +void WheeledVehicle::updateJet(F32 ) +{ + if (!mDataBlock->sound[WheeledVehicleData::JetSound]) + return; + // Allocate/Deallocate voice on demand. + if (!mJetting) { + if (mJetSound) { + alxStop(mJetSound); + mJetSound = 0; + } + } + else { + if (!mJetSound) + mJetSound = alxPlay(mDataBlock->sound[WheeledVehicleData::JetSound], &getTransform()); + + alxSourceMatrixF(mJetSound, &getTransform()); + } +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::renderImage(SceneState* state, SceneRenderImage* image) +{ + updateWheelThreads(); + Parent::renderImage(state, image); +} + + +//---------------------------------------------------------------------------- + +bool WheeledVehicle::writePacketData(GameConnection *connection, BitStream *stream) +{ + bool ret = Parent::writePacketData(connection, stream); + stream->writeFlag(mBraking); + + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + stream->write(wp->avel); + stream->write(wp->Dy); + stream->write(wp->Dx); + } + return ret; +} + +void WheeledVehicle::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + mBraking = stream->readFlag(); + + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + stream->read(&wp->avel); + stream->read(&wp->Dy); + stream->read(&wp->Dx); + } + + setPosition(mRigid.state.linPosition,mRigid.state.angPosition); + mDelta.pos = mRigid.state.linPosition; + mDelta.rot[1] = mRigid.state.angPosition; +} + +U32 WheeledVehicle::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( ((GameConnection *) con)->getControlObject() == this && !(mask & InitialUpdateMask)) + return retMask; + + stream->writeFlag(mBraking); + + if (stream->writeFlag(mask & PositionMask)) { + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + stream->write(wp->avel); + stream->write(wp->Dy); + stream->write(wp->Dx); + } + } + return retMask; +} + +void WheeledVehicle::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con,stream); + + if( ((GameConnection *) con)->getControlObject() == this) + return; + + mBraking = stream->readFlag(); + + if (stream->readFlag()) { + for (S32 i = 0; i < mDataBlock->wheelCount; i++) { + Wheel* wp = &mWheel[i]; + stream->read(&wp->avel); + stream->read(&wp->Dy); + stream->read(&wp->Dx); + } + } +} + +void WheeledVehicle::initPersistFields() +{ + Parent::initPersistFields(); +} + diff --git a/game/wheeledVehicle.h b/game/wheeledVehicle.h new file mode 100644 index 0000000..256e16d --- /dev/null +++ b/game/wheeledVehicle.h @@ -0,0 +1,185 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WHEELEDVEHICLE_H_ +#define _WHEELEDVEHICLE_H_ + +#ifndef _VEHICLE_H_ +#include "game/vehicle.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; + + +//---------------------------------------------------------------------------- + +struct WheeledVehicleData: public VehicleData { + typedef VehicleData Parent; + + enum Constants { + MaxWheels = 8, + MaxWheelBits = 3 + }; + + enum Sounds { + JetSound, + EngineSound, + SqueelSound, + WheelImpactSound, + MaxSounds, + }; + AudioProfile* sound[MaxSounds]; + + struct Tire { + F32 friction; + F32 restitution; + F32 radius; + F32 lateralForce; + F32 lateralDamping; + F32 lateralRelaxation; + F32 longitudinalForce; + F32 longitudinalDamping; + F32 longitudinalRelaxation; + ParticleEmitterData* emitter; + } tire; + + F32 staticLoadScale; + F32 springForce; + F32 springDamping; + F32 antiSwayForce; + F32 antiRockForce; + F32 maxWheelSpeed; + F32 engineTorque; + F32 breakTorque; + + F32 stabilizerForce; + F32 gyroForce; + F32 gyroDamping; + + // Initialized onAdd + struct Wheel { + enum Steering { + None, + Forward, + Backward, + } steering; + S32 opposite; + F32 springForce; // Spring Constant in 1G + F32 springDamping; // Shock absorber + F32 springRest; // Resting point at 1G + Point3F safePos; + Point3F pos; + Point3F spring; + S32 springNode; + S32 springSequence; + S32 rotationSequence; + S32 steeringSequence; + } wheel[MaxWheels]; + U32 wheelCount; + ClippedPolyList rigidBody; // Planes extracted from shape + S32 tailLightSequence; + + // + WheeledVehicleData(); + DECLARE_CONOBJECT(WheeledVehicleData); + static void initPersistFields(); + bool preload(bool, char errorBuffer[256]); + bool mirrorWheel(Wheel* we); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class WheeledVehicle: public Vehicle +{ + typedef Vehicle Parent; + +// struct StateDelta: Vehicle::StateDelta { +// struct Wheel { +// F32 pos; +// F32 posVec; +// } wheel[WheeledVehicleData::MaxWheels]; +// }; +// StateDelta delta; + + WheeledVehicleData* mDataBlock; + + bool mBraking; + bool mWheelContact; + TSThread* mTailLightThread; + + AUDIOHANDLE mJetSound; + AUDIOHANDLE mEngineSound; + AUDIOHANDLE mSqueelSound; + + struct Wheel { + F32 k; // Spring coefficient + F32 s; // Shock absorber coefficient + F32 center; // Neutral point of spring + F32 extension; // Spring extension (0-1) + F32 evel; + F32 avel; + F32 apos; + F32 Dy,Dx; + struct Surface { + bool contact; + Point3F normal; + U32 material; + Point3F pos; + SceneObject* object; + } surface; + F32 traction; + TSThread* springThread; + TSThread* rotationThread; + TSThread* steeringThread; + + F32 torqueScale; // 0-1 + F32 particleSlip; // 0-1 slip + Point3F particleAxis; // Last tire axis + SimObjectPtr emitter; + }; + Wheel mWheel[WheeledVehicleData::MaxWheels]; + + // + bool onNewDataBlock(GameBaseData* dptr); + void processTick(const Move* move); + void updateMove(const Move *move); + void updateWarp(); + void updateWheels(); + void updateForces(F32 dt); + void updateWheelThreads(); + void renderImage(SceneState* state, SceneRenderImage*); + + // Client sounds & particles + void updateJet(F32 dt); + void updateEngineSound(F32 level); + void updateSqueelSound(F32 level); + + 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&, const SphereF&); + + bool writePacketData(GameConnection *, BitStream *stream); + void readPacketData(GameConnection *, BitStream *stream); + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); +}; + + +#endif diff --git a/gui/channelVector.cc b/gui/channelVector.cc new file mode 100644 index 0000000..abe9cd3 --- /dev/null +++ b/gui/channelVector.cc @@ -0,0 +1,283 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/channelVector.h" + +struct TempLineBreak +{ + S32 start; + S32 end; + char *content; +}; + + +IMPLEMENT_CONOBJECT(ChannelVector); + +//-------------------------------------- Console methods +ConsoleMethod(ChannelVector,AddMember,bool,4,5,"obj.addMember(id,nick,flags)") +{ + ChannelVector* pCV = static_cast(object); + + if (argc == 4) + return pCV->addMember(dAtoi(argv[2]),argv[3]); + else + return pCV->addMember(dAtoi(argv[2]),argv[3],dAtoi(argv[4])); +} + +ConsoleMethod(ChannelVector,RemoveMember,bool,3,3,"obj.removeMember(id)") +{ + argc; + ChannelVector* pCV = static_cast(object); + + return pCV->removeMember(dAtoi(argv[2])); +} + +ConsoleMethod(ChannelVector,Sort,void,2,2,"obj.sort()") +{ + argc; argv; + ChannelVector* pCV = static_cast(object); + + pCV->sort(); +} + +ConsoleMethod(ChannelVector,FindMember,S32,3,3,"obj.findMember(id)") +{ + argc; + ChannelVector* pCV = static_cast(object); + + return pCV->findMember(dAtoi(argv[2])); +} + +ConsoleMethod(ChannelVector,NumMembers,S32,2,2,"obj.numMembers()") +{ + argc; argv; + ChannelVector* pCV = static_cast(object); + + return pCV->numMembers(); +} + +ConsoleMethod(ChannelVector,GetMemberId,S32,3,3,"obj.getMemberId(i)") +{ + argc; + ChannelVector* pCV = static_cast(object); + + return pCV->getMemberId(dAtoi(argv[2])); +} + +ConsoleMethod(ChannelVector,GetMemberNick,const char *,3,3,"obj.getMemberNick(i)") +{ + argc; + ChannelVector* pCV = static_cast(object); + + return pCV->getMemberNick(dAtoi(argv[2])); +} + +ConsoleMethod(ChannelVector,SetMemberNick,bool,4,4,"obj.setMemberNick(i,nick)") +{ + argc; + ChannelVector* pCV = static_cast(object); + + return pCV->setMemberNick(dAtoi(argv[2]),argv[3]); +} + +ConsoleMethod(ChannelVector,SetFlags,void,5,5,"obj.setFlags(i,flags,set)") +{ + argc; + ChannelVector* pCV = static_cast(object); + + pCV->setFlags(dAtoi(argv[2]),dAtoi(argv[3]),dAtob(argv[4])); +} + +ConsoleMethod(ChannelVector,GetFlags,S32,3,3,"obj.getFlags(i)") +{ + argc; + ChannelVector* pCV = static_cast(object); + + return pCV->getFlags(dAtoi(argv[2])); +} + + +//-------------------------------------------------------------------------- +ChannelVector::ChannelVector() +{ + VECTOR_SET_ASSOCIATION(mLineTags); + VECTOR_SET_ASSOCIATION(mMembers); +} + + +//-------------------------------------------------------------------------- +ChannelVector::~ChannelVector() +{ + for (U32 i = 0; i < mLineTags.size(); ++i) + delete [] mLineTags[i].specials; + mLineTags.clear(); +} + + +//------------------------------------------------------------------------------ +S32 QSORT_CALLBACK ChannelVector::compareMembers(const void *a, const void *b) +{ + Member *memberA = (Member*) a; + Member *memberB = (Member*) b; + + S32 ao = memberA->flags & (PERSON_SPEAKER|PERSON_OPERATOR); + S32 bo = memberB->flags & (PERSON_SPEAKER|PERSON_OPERATOR); + + if (ao != bo) + return (bo-ao); + + char atag[64]; + const char *anick; + char btag[64]; + const char *bnick; + + dStrcpy(atag,memberA->nick); + atag[7] = '\0'; + if (dStrcmp(atag,"nick[9]; + else + anick = memberA->nick; + dStrcpy(btag,memberB->nick); + btag[7] = '\0'; + if (dStrcmp(btag,"nick[9]; + else + bnick = memberB->nick; + + + return (dStricmp(anick, bnick)); +} + + +//------------------------------------------------------------------------------ +void ChannelVector::sort() +{ + dQsort((void*) &mMembers[0], mMembers.size(), sizeof(Member), compareMembers); +} + + +//-------------------------------------------------------------------------- +void ChannelVector::initPersistFields() +{ + Parent::initPersistFields(); +} + + +//-------------------------------------------------------------------------- +void ChannelVector::consoleInit() +{ +} + + +//-------------------------------------------------------------------------- +bool ChannelVector::onAdd() +{ + return Parent::onAdd(); +} + + +//-------------------------------------------------------------------------- +void ChannelVector::onRemove() +{ + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +void ChannelVector::insertLine(const U32 position, const char *newMessage, const S32 newMessageTag) +{ + Vector tempSpecials(__FILE__, __LINE__); + Vector tempTypes(__FILE__, __LINE__); + + char *copy = new char[dStrlen(newMessage) + 1]; + const char* cur = copy; + + dStrcpy(copy,newMessage); + while(cur[0] != '\0') + { + const char *open; + + if ((open = dStrstr(cur,""); + tag.end = tag.start + (close-open) - 1; + + dMemcpy(©[tag.start],open,tag.end-tag.start+1); + dStrcpy(©[tag.end+1],close+8); + + cur = ©[tempSpecials.last().end+1]; + } + else + if ((open = dStrstr(cur,""); + tag.content = new char[open-content+1]; + dMemcpy(tag.content,content,open-content); + tag.content[open-content] = '\0'; + ++open; + close = dStrstr(open,""); + tag.end = tag.start + (close-open) - 1; + + dMemcpy(©[tag.start],open,tag.end-tag.start+1); + dStrcpy(©[tag.end+1],close+11); + + cur = ©[tag.end+1]; + } + else + break; + } + + SpecialMarkers tags; + + if ((tags.numSpecials = tempSpecials.size()) != 0) { + tags.specials = new SpecialMarkers::Special[tempSpecials.size()]; + for (U32 i = 0; i < tempSpecials.size(); i++) { + tags.specials[i].specialType = tempTypes[i]; + tags.specials[i].start = tempSpecials[i].start; + tags.specials[i].end = tempSpecials[i].end; + tags.specials[i].content = tempSpecials[i].content; + } + } else + tags.specials = NULL; + mLineTags.insert(position); + mLineTags[position] = tags; + + Parent::insertLine(position,copy,newMessageTag); + + delete [] copy; +} + + +//-------------------------------------------------------------------------- +void ChannelVector::deleteLine(const U32 position) +{ + delete [] mLineTags[position].specials; + mLineTags.erase(position); + + Parent::deleteLine(position); +} diff --git a/gui/channelVector.h b/gui/channelVector.h new file mode 100644 index 0000000..6f41687 --- /dev/null +++ b/gui/channelVector.h @@ -0,0 +1,202 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CHANNELVECTOR_H_ +#define _CHANNELVECTOR_H_ + +#ifndef _GUIMESSAGEVECTORCTRL_H_ +#include "GUI/guiMessageVectorCtrl.h" +#endif + +class ChannelVector : public MessageVector +{ + private: + typedef MessageVector Parent; + + enum Constants { + // Person flags + PERSON_SPEAKER = BIT(0), + PERSON_OPERATOR = BIT(1), + PERSON_IGNORE = BIT(2), + PERSON_AWAY = BIT(3), + }; + + public: + enum { + NickTag = -3, + TribeTag = -2, + ServerTag = -1, + }; + + struct SpecialMarkers { + struct Special { + S32 specialType; + S32 start; + S32 end; + char *content; + + Special() { content = NULL; }; + ~Special() { delete [] content; }; + }; + + U32 numSpecials; + Special* specials; + }; + + ChannelVector(); + ~ChannelVector(); + + DECLARE_CONOBJECT(ChannelVector); + + virtual void insertLine(const U32 position, const char*, const S32); + virtual void deleteLine(const U32); + + const SpecialMarkers & getLineTags(U32 position) + { + return mLineTags[position]; + } + + bool addMember(S32 id, const char *nick, S32 flags = 0); + bool removeMember(S32 id); + static S32 QSORT_CALLBACK compareMembers(const void *a, const void *b); + void sort(); + S32 findMember(S32 id); + S32 numMembers(); + S32 getMemberId(S32 i); + const char *getMemberNick(S32 i); + bool setMemberNick(S32 i, const char *nick); + void setFlags(S32 i, S32 flags, bool set); + S32 getFlags(S32 i); + + static void initPersistFields(); + static void consoleInit(); + + protected: + struct Member + { + S32 id; + StringTableEntry nick; + S32 flags; + }; + + bool onAdd(); + void onRemove(); + + Vector mLineTags; + Vector mMembers; +}; + + +//------------------------------------------------------------------------------ +inline bool ChannelVector::addMember(S32 id, const char *nick, S32 flags) +{ + Vector::iterator itr = mMembers.begin(); + + for (; itr != mMembers.end(); itr++) + if (id == itr->id) + break; + + if (itr == mMembers.end()) + { + mMembers.increment(); + + Member *pm = mMembers.end()-1; + + pm->id = id; + pm->nick = StringTable->insert(nick); + pm->flags = flags; + sort(); + + return true; + } + + return false; +} + +//------------------------------------------------------------------------------ +inline bool ChannelVector::removeMember(S32 id) +{ + Vector::iterator itr = mMembers.begin(); + + for (; itr != mMembers.end(); itr++) + if (id == itr->id) + { + mMembers.erase(itr); + + return true; + } + + return false; +} + +//------------------------------------------------------------------------------ +inline S32 ChannelVector::findMember(S32 id) +{ + for (S32 i = 0; i < mMembers.size(); ++i) + if (id == mMembers[i].id) + return i; + + return -1; +} + +//------------------------------------------------------------------------------ +inline S32 ChannelVector::numMembers() +{ + return mMembers.size(); +} + +//------------------------------------------------------------------------------ +inline S32 ChannelVector::getMemberId(S32 i) +{ + if (i >= 0 && i < mMembers.size()) + return mMembers[i].id; + else + return 0; +} + +//------------------------------------------------------------------------------ +inline const char * ChannelVector::getMemberNick(S32 i) +{ + if (i >= 0 && i < mMembers.size()) + return mMembers[i].nick; + else + return NULL; +} + +//------------------------------------------------------------------------------ +inline bool ChannelVector::setMemberNick(S32 i, const char *nick) +{ + if (i >= 0 && i < mMembers.size()) + { + mMembers[i].nick = StringTable->insert(nick); + + return true; + } + else + return false; +} + +//------------------------------------------------------------------------------ +inline void ChannelVector::setFlags(S32 i, S32 flags, bool set) +{ + if (i >= 0 && i < mMembers.size()) + if (set) + mMembers[i].flags |= flags; + else + mMembers[i].flags &= ~flags; +} + +//------------------------------------------------------------------------------ +inline S32 ChannelVector::getFlags(S32 i) +{ + if (i >= 0 && i < mMembers.size()) + return mMembers[i].flags; + else + return 0; +} + +#endif // _H_CHANNELVECTOR_ diff --git a/gui/guiArrayCtrl.cc b/gui/guiArrayCtrl.cc new file mode 100644 index 0000000..e59189e --- /dev/null +++ b/gui/guiArrayCtrl.cc @@ -0,0 +1,488 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "Platform/event.h" +#include "GUI/guiScrollCtrl.h" +#include "GUI/guiArrayCtrl.h" + + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +GuiArrayCtrl::GuiArrayCtrl() +{ + mActive = true; + + mCellSize.set(80, 30); + mSize = Point2I(5, 30); + mSelectedCell.set(-1, -1); + mMouseOverCell.set(-1, -1); + mHeaderDim.set(0, 0); +} + +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); + + resize(mBounds.point, 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); + return true; +} + +void GuiArrayCtrl::onCellSelected(Point2I cell) +{ + Con::executef(this, 3, "onSelect", Con::getFloatArg(cell.x), Con::getFloatArg(cell.y)); + + //call the console function + if (mConsoleCommand[0]) + Con::evaluate(mConsoleCommand, false); +} + +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 + GuiControl *parent = getParent(); + if (! parent) return; + + //make sure we have a valid cell selected + if ((cell.x < 0) || (cell.y < 0)) return; + + //get the parent extent, and the offset of the selected cell + Point2I parentExtent = parent->getExtent(); + Point2I cellPos(mBounds.point.x + cell.x * mCellSize.x, + mBounds.point.y + cell.y * mCellSize.y); + + Point2I delta(0,0); + + //find out how far the selected cell is outside the parent extent horizontally + if (cellPos.x <= 0) + { + delta.x = -cellPos.x; + } + else if (cellPos.x + mCellSize.x > parentExtent.x) + { + delta.x = parentExtent.x - (cellPos.x + mCellSize.x); + } + + //find out how far the selected cell is outside the parent extent vertically + if (cellPos.y <= 0) + { + delta.y = -cellPos.y; + } + else if (cellPos.y + mCellSize.y > parentExtent.y) + { + delta.y = parentExtent.y - (cellPos.y + mCellSize.y); + } + + //if we need to scroll, set the new position + if (delta.x || delta.y) + { + Point2I newPosition = mBounds.point; + newPosition.x += delta.x; + newPosition.y += delta.y; + resize(newPosition, mBounds.extent); + } +} + +void GuiArrayCtrl::scrollSelectionTop() +{ + scrollCellTop( mSelectedCell ); +} + +void GuiArrayCtrl::scrollSelectionBottom() +{ + scrollCellBottom( mSelectedCell ); +} + +void GuiArrayCtrl::scrollCellTop( Point2I cell ) +{ + // Make sure we have a parent: + GuiControl* parent = getParent(); + if ( !parent ) + return; + + // Make sure the cell is valid: + if ( ( cell.x < 0 ) || ( cell.y < 0 ) ) + return; + + // This is the >desired< new position--let the scroll control decide + // whether it is a reasonable position or not... + Point2I newPos( -( cell.x * mCellSize.x ), -( cell.y * mCellSize.y ) ); + resize( newPos, mBounds.extent ); +} + +void GuiArrayCtrl::scrollCellBottom( Point2I cell ) +{ + // Make sure we have a parent: + GuiControl* parent = getParent(); + if ( !parent ) + return; + + // Make sure the cell is valid: + if ( ( cell.x < 0 ) || ( cell.y < 0 ) ) + return; + + // This is the >desired< new position--let the scroll control decide + // whether it is a reasonable position or not... + Point2I newPos( parent->mBounds.extent.x - ( ( cell.x + 1 ) * mCellSize.x ), + parent->mBounds.extent.y - ( ( cell.y + 1 ) * mCellSize.y ) ); + resize( newPos, mBounds.extent ); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiArrayCtrl::onRenderColumnHeaders(Point2I offset, Point2I parentOffset, Point2I headerDim) +{ + if (mProfile->mBorder) + { + RectI cellR(offset.x + headerDim.x, parentOffset.y, mBounds.extent.x - headerDim.x, headerDim.y); + dglDrawRectFill(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); + dglDrawRectFill(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); + dglDrawRectFill(cellR, color); +} + +void GuiArrayCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + firstResponder; + + //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; + headerClip.extent.y = mHeaderDim.y; + + if (headerClip.intersect(clipRect)) + { + dglSetClipRect(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)) + { + dglSetClipRect(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 + dglSetClipRect(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 ); + } +} + +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); +} + +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); + } +} + +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->mBounds.extent.y / 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, 4, "onRightMouseDown", Con::getIntArg(cell.x), Con::getIntArg(cell.y), buf); + } +} diff --git a/gui/guiArrayCtrl.h b/gui/guiArrayCtrl.h new file mode 100644 index 0000000..da679ea --- /dev/null +++ b/gui/guiArrayCtrl.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIARRAYCTRL_H_ +#define _GUIARRAYCTRL_H_ + +#ifndef _GUITYPES_H_ +#include "GUI/guiTypes.h" +#endif +#ifndef _GUITEXTCTRL_H_ +#include "GUI/guiTextCtrl.h" +#endif + +class GuiArrayCtrl : public GuiControl +{ + typedef GuiControl Parent; + +protected: + + Point2I mHeaderDim; + Point2I mSize; + Point2I mCellSize; + Point2I mSelectedCell; + Point2I mMouseOverCell; + + Resource mFont; + + bool cellSelected(Point2I cell); + virtual void onCellSelected(Point2I cell); +public: + + GuiArrayCtrl(); + DECLARE_CONOBJECT(GuiArrayCtrl); + + bool onWake(); + void onSleep(); + + //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); + + //selected cell methods + void setSelectedCell(Point2I cell); + void deselectCells() { mSelectedCell.set(-1,-1); } + Point2I getSelectedCell(); + void scrollSelectionVisible(); + void scrollCellVisible(Point2I cell); + void scrollSelectionTop(); + void scrollSelectionBottom(); + void scrollCellTop(Point2I cell); + void scrollCellBottom(Point2I cell); + + //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 onPreRender(); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + + //mouse input methods + void onMouseDown(const GuiEvent &event); + void onMouseMove(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/guiAviBitmapCtrl.cc b/gui/guiAviBitmapCtrl.cc new file mode 100644 index 0000000..6c362a0 --- /dev/null +++ b/gui/guiAviBitmapCtrl.cc @@ -0,0 +1,1387 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "gui/guiControl.h" + +#ifdef __linux +#if DEDICATED +#define ENABLE_AVI_GUI 0 +#define ENABLE_MPG_GUI 0 +#else +#define ENABLE_AVI_GUI 0 +#define ENABLE_MPG_GUI 1 +#endif +#else +/* Windows */ +#define ENABLE_AVI_GUI 1 +#define ENABLE_MPG_GUI 0 +#endif + +#if ENABLE_AVI_GUI || ENABLE_MPG_GUI +#include "audio/audio.h" +#if ENABLE_AVI_GUI +#include +#include +#endif +#if ENABLE_MPG_GUI +#include "smpeg.h" +#endif +#endif /* Either AVI or MPG GUI */ + + +#include "gui/guiAviBitmapCtrl.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.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() +{ + Parent::initPersistFields(); + addField("done", TypeBool, Offset(mDone, GuiAviBitmapCtrl)); +} +#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 + + +//---------------------------------------------------------------------------- +static void cSetFilename(SimObject *obj, S32, const char **argv) +{ + GuiAviBitmapCtrl* movie = static_cast(obj); + + movie->setFilename(argv[2]); +} + +//---------------------------------------------------------------------------- +static void cPlay(SimObject *obj, S32, const char **) +{ + GuiAviBitmapCtrl* movie = static_cast(obj); + + movie->movieStart(); +} + +//---------------------------------------------------------------------------- +static void cStop(SimObject *obj, S32, const char **) +{ + GuiAviBitmapCtrl* movie = static_cast(obj); + + movie->movieStop(); +} + +////////////////////////////////////////////////////////////////////////////// +// Audio Operations + +bool GuiAviBitmapCtrl::sndOpen() +{ + char fileBuffer[256]; + + mAudioLatency = 0; + + // 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); + + // 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; + char fileBuffer[256]; + + if (!dStrcmp(mAviFilename,"")) + return MOVERR_NOVIDEOSTREAM; + + dSprintf(fileBuffer, sizeof(fileBuffer), "base/textures/%s", mAviFilename); + rval = AVIFileOpen(&mPFile, fileBuffer, 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[3*((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*3); + else + for (U32 k = 0; k < width; ++k) + { + *dest++ = src[2]; + *dest++ = src[1]; + *dest++ = src[0]; + src += 3; + } + } + + 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::consoleInit() +{ + Con::addCommand("GuiAviBitmapCtrl", "setFilename", cSetFilename, "obj.setFilename(filename);", 3, 3); + Con::addCommand("GuiAviBitmapCtrl", "play", cPlay, "obj.play();", 2, 2); + Con::addCommand("GuiAviBitmapCtrl", "stop", cStop, "obj.stop();", 2, 2); +} + +void GuiAviBitmapCtrl::initPersistFields() +{ + Parent::initPersistFields(); + + addField("aviFilename", TypeString, Offset(mAviFilename, GuiAviBitmapCtrl)); + addField("wavFilename", TypeString, Offset(mWavFilename, GuiAviBitmapCtrl)); + addField("swapRB", TypeBool, Offset(mSwapRB, GuiAviBitmapCtrl)); + addField("done", TypeBool, Offset(mDone, GuiAviBitmapCtrl)); + addField("letterBox", TypeBool, Offset(mLetterBox, GuiAviBitmapCtrl)); + addField("speed", TypeF32, Offset(mSpeed, GuiAviBitmapCtrl)); +} + +//---------------------------------------------------------------------------- +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 TextureHandle[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] = TextureHandle(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] = TextureHandle(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, GuiControl *firstResponder) +{ + vidsVideoDraw(); + + if (mTextureHandles) + { + RectI displayRect(mBounds); + 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 = (mBounds.extent.y >> 3); + displayRect.extent.y = (displayRect.extent.y * 3 >> 2); + displayRect.point.y += verticalDisplace; + RectI upperRect(offset.x, offset.y, displayRect.extent.x, verticalDisplace + 1); + dglDrawRectFill(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; + + dglSetBitmapModulation(ColorF(1,1,1)); + for (U32 i = 0; i < mWidthCount; i++) + { + for (U32 j = 0; j < mHeightCount; j++) + { + TextureHandle 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; + dglDrawBitmapStretch(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, mBounds.extent.y - verticalDisplace - 1, + mBounds.extent.x + 2, verticalDisplace + 2); + dglDrawRectFill(lowerRect, mProfile->mFillColorHL); + } + + renderChildControls(offset, updateRect, firstResponder); + + if (mBPlaying) + setUpdate(); + } + else + Parent::onRender(offset, updateRect, firstResponder); +} + +#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; + + dSprintf(fileBuffer, sizeof(fileBuffer), "base/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::consoleInit() +{ + Con::addCommand("GuiAviBitmapCtrl", "setFilename", cSetFilename, "obj.setFilename(filename);", 3, 3); + Con::addCommand("GuiAviBitmapCtrl", "play", cPlay, "obj.play();", 2, 2); + Con::addCommand("GuiAviBitmapCtrl", "stop", cStop, "obj.stop();", 2, 2); +} + +void GuiAviBitmapCtrl::initPersistFields() +{ + Parent::initPersistFields(); + + addField("aviFilename", TypeString, Offset(mAviFilename, GuiAviBitmapCtrl)); + addField("wavFilename", TypeString, Offset(mWavFilename, GuiAviBitmapCtrl)); + addField("done", TypeBool, Offset(mDone, GuiAviBitmapCtrl)); + addField("letterBox", TypeBool, Offset(mLetterBox, GuiAviBitmapCtrl)); +} + +//---------------------------------------------------------------------------- +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 TextureHandle[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] = TextureHandle(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] = TextureHandle(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"); + mSurface = SDL_CreateRGBSurfaceFrom(mPBuf, + mBitmapWidth, mBitmapHeight, 24, + mBitmapWidth*3, + 0x000000FF, 0x0000FF00, 0x00FF0000, 0); + AssertFatal(mSurface, "Out of memory"); + + // Target the decoding to our surface + mDecodeLock = SDL_CreateMutex(); + AssertFatal(mDecodeLock, "Out of memory"); + 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, GuiControl *firstResponder) +{ + if (mTextureHandles) + { + RectI displayRect(mBounds); + 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[3*((y+lp)*mBitmapAlignedWidth + x)]; + U8 *dest = bmp->getAddress(0, lp); + + dMemcpy(dest, src, width*3); + } + 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 = (mBounds.extent.y >> 3); + displayRect.extent.y = (displayRect.extent.y * 3 >> 2); + displayRect.point.y += verticalDisplace; + RectI upperRect(offset.x, offset.y, displayRect.extent.x, verticalDisplace + 1); + dglDrawRectFill(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; + + dglSetBitmapModulation(ColorF(1,1,1)); + for (U32 i = 0; i < mWidthCount; i++) + { + for (U32 j = 0; j < mHeightCount; j++) + { + TextureHandle 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; + dglDrawBitmapStretch(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, mBounds.extent.y - verticalDisplace - 1, + mBounds.extent.x + 2, verticalDisplace + 2); + dglDrawRectFill(lowerRect, mProfile->mFillColorHL); + } + + renderChildControls(offset, updateRect, firstResponder); + + mBPlaying = (SMPEG_status(mMPEG) == SMPEG_PLAYING); + if (mBPlaying) + setUpdate(); + else + movieStop(); + } + else + Parent::onRender(offset, updateRect, firstResponder); +} + +#endif /* ENABLE_MPG_GUI */ + +#endif /* Enabled Movie GUI */ diff --git a/gui/guiAviBitmapCtrl.h b/gui/guiAviBitmapCtrl.h new file mode 100644 index 0000000..6e032eb --- /dev/null +++ b/gui/guiAviBitmapCtrl.h @@ -0,0 +1,190 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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; + TextureHandle *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 consoleInit(); + 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, GuiControl *firstResponder); +}; + +#endif /* ENABLE_AVI_GUI */ + +#if ENABLE_MPG_GUI + +class GuiAviBitmapCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + + protected: + StringTableEntry mAviFilename; + StringTableEntry mWavFilename; + U32 mNumTextures; + TextureHandle *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 consoleInit(); + 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, GuiControl *firstResponder); +}; + +#endif /* ENABLE_MPG_GUI */ + +#endif /* _GUIAVIBITMAPCTRL_H_ */ diff --git a/gui/guiBackgroundCtrl.cc b/gui/guiBackgroundCtrl.cc new file mode 100644 index 0000000..12f9b31 --- /dev/null +++ b/gui/guiBackgroundCtrl.cc @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "gui/guiBackgroundCtrl.h" + +//-------------------------------------------------------------------------- +GuiBackgroundCtrl::GuiBackgroundCtrl() : GuiControl() +{ + mDraw = false; +} + +//-------------------------------------------------------------------------- +void GuiBackgroundCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + if ( mDraw ) + Parent::onRender( offset, updateRect, firstResponder ); + + renderChildControls(offset, updateRect, firstResponder); +} + + diff --git a/gui/guiBackgroundCtrl.h b/gui/guiBackgroundCtrl.h new file mode 100644 index 0000000..b76a0d9 --- /dev/null +++ b/gui/guiBackgroundCtrl.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBACKGROUNDCTRL_H_ +#define _GUIBACKGROUNDCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/guiControl.h" +#endif + +class GuiBackgroundCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +public: + bool mDraw; + + //creation methods + DECLARE_CONOBJECT(GuiBackgroundCtrl); + GuiBackgroundCtrl(); + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); +}; + +#endif diff --git a/gui/guiBitmapBorderCtrl.cc b/gui/guiBitmapBorderCtrl.cc new file mode 100644 index 0000000..f29f12e --- /dev/null +++ b/gui/guiBitmapBorderCtrl.cc @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "gui/guiControl.h" +#include "dgl/dgl.h" + +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] + TextureHandle mTextureHandle; +public: + bool onWake(); + void onSleep(); + void onRender(Point2I offset, const RectI &updateRect); + DECLARE_CONOBJECT(GuiBitmapBorderCtrl); +}; + +IMPLEMENT_CONOBJECT(GuiBitmapBorderCtrl); + +bool GuiBitmapBorderCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + //get the texture for the close, minimize, and maximize buttons + mTextureHandle = mProfile->mTextureHandle; + bool result = mProfile->constructBitmapArray() >= NumBitmaps; + AssertFatal(result, "Failed to create the bitmap array"); + if(!result) + return false; + + mBitmapBounds = mProfile->mBitmapArrayRects.address(); + return true; +} + +void GuiBitmapBorderCtrl::onSleep() +{ + mTextureHandle = NULL; + Parent::onSleep(); +} + +void GuiBitmapBorderCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + renderChildControls( offset, updateRect ); + dglSetClipRect(updateRect); + + //draw the outline + RectI winRect; + winRect.point = offset; + winRect.extent = mBounds.extent; + + 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) + dglDrawRectFill(winRect, mProfile->mFillColor); + + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, offset, mBitmapBounds[BorderTopLeft]); + dglDrawBitmapSR(mTextureHandle, Point2I(offset.x + mBounds.extent.x - 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 = mBounds.extent.x - mBitmapBounds[BorderTopLeft].extent.x - mBitmapBounds[BorderTopRight].extent.x; + destRect.extent.y = mBitmapBounds[BorderTop].extent.y; + RectI stretchRect = mBitmapBounds[BorderTop]; + stretchRect.inset(1,0); + dglDrawBitmapStretchSR(mTextureHandle, 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 = mBounds.extent.y - mBitmapBounds[BorderTopLeft].extent.y - mBitmapBounds[BorderBottomLeft].extent.y; + stretchRect = mBitmapBounds[BorderLeft]; + stretchRect.inset(0,1); + dglDrawBitmapStretchSR(mTextureHandle, destRect, stretchRect); + + destRect.point.x = offset.x + mBounds.extent.x - mBitmapBounds[BorderRight].extent.x; + destRect.extent.x = mBitmapBounds[BorderRight].extent.x; + destRect.point.y = offset.y + mBitmapBounds[BorderTopRight].extent.y; + destRect.extent.y = mBounds.extent.y - mBitmapBounds[BorderTopRight].extent.y - mBitmapBounds[BorderBottomRight].extent.y; + + stretchRect = mBitmapBounds[BorderRight]; + stretchRect.inset(0,1); + dglDrawBitmapStretchSR(mTextureHandle, destRect, stretchRect); + + dglDrawBitmapSR(mTextureHandle, offset + Point2I(0, mBounds.extent.y - mBitmapBounds[BorderBottomLeft].extent.y), mBitmapBounds[BorderBottomLeft]); + dglDrawBitmapSR(mTextureHandle, offset + mBounds.extent - mBitmapBounds[BorderBottomRight].extent, mBitmapBounds[BorderBottomRight]); + + destRect.point.x = offset.x + mBitmapBounds[BorderBottomLeft].extent.x; + destRect.extent.x = mBounds.extent.x - mBitmapBounds[BorderBottomLeft].extent.x - mBitmapBounds[BorderBottomRight].extent.x; + + destRect.point.y = offset.y + mBounds.extent.y - mBitmapBounds[BorderBottom].extent.y; + destRect.extent.y = mBitmapBounds[BorderBottom].extent.y; + stretchRect = mBitmapBounds[BorderBottom]; + stretchRect.inset(1,0); + + dglDrawBitmapStretchSR(mTextureHandle, destRect, stretchRect); +} diff --git a/gui/guiBitmapCtrl.cc b/gui/guiBitmapCtrl.cc new file mode 100644 index 0000000..60de13d --- /dev/null +++ b/gui/guiBitmapCtrl.cc @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" + +#include "GUI/guiBitmapCtrl.h" + + + +GuiBitmapCtrl::GuiBitmapCtrl(void) +{ + mBitmapName = StringTable->insert(""); + startPoint.set(0, 0); + mWrap = false; +} + + +void GuiBitmapCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("bitmap", TypeString, Offset(mBitmapName, GuiBitmapCtrl)); + addField("wrap", TypeBool, Offset(mWrap, GuiBitmapCtrl)); +} + +static void cBitmapSetValue(SimObject *obj, S32, const char **argv) +{ + GuiBitmapCtrl *ctrl = static_cast(obj); + ctrl->setValue(dAtoi(argv[2]), dAtoi(argv[3])); +} + +static void cBitmapSetBitmap(SimObject *obj, S32, const char **argv) +{ + GuiBitmapCtrl *ctrl = static_cast(obj); + ctrl->setBitmap(argv[2]); +} + +void GuiBitmapCtrl::consoleInit() +{ + Con::addCommand("GuiBitmapCtrl", "setBitmap", cBitmapSetBitmap, "guiBitmapCtrl.setBitmap(blah)", 3, 3); + Con::addCommand("GuiBitmapCtrl", "setValue", cBitmapSetValue, "guiBitmapCtrl.setValue(xAxis, yAxis)", 4, 4); +} + + +bool GuiBitmapCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + setActive(true); + setBitmap(mBitmapName); + return true; +} + +void GuiBitmapCtrl::onSleep() +{ + mTextureHandle = NULL; + Parent::onSleep(); +} + +void GuiBitmapCtrl::setBitmap(const char *name) +{ + mBitmapName = StringTable->insert(name); + if (*mBitmapName) + mTextureHandle = TextureHandle(mBitmapName, BitmapTexture, true); + else + mTextureHandle = NULL; + setUpdate(); +} + + +void GuiBitmapCtrl::setBitmap(const TextureHandle &handle) +{ + mTextureHandle = handle; +} + + +void GuiBitmapCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + if (mTextureHandle) + { + dglClearBitmapModulation(); + if(mWrap) + { + TextureObject* texture = (TextureObject *) mTextureHandle; + RectI srcRegion; + RectI dstRegion; + float xdone = ((float)mBounds.extent.x/(float)texture->bitmapWidth)+1; + float ydone = ((float)mBounds.extent.y/(float)texture->bitmapHeight)+1; + + int xshift = startPoint.x%texture->bitmapWidth; + int yshift = startPoint.y%texture->bitmapHeight; + for(int y = 0; y < ydone; ++y) + for(int x = 0; x < xdone; ++x) + { + srcRegion.set(0,0,texture->bitmapWidth,texture->bitmapHeight); + dstRegion.set( ((texture->bitmapWidth*x)+offset.x)-xshift, + ((texture->bitmapHeight*y)+offset.y)-yshift, + texture->bitmapWidth, + texture->bitmapHeight); + dglDrawBitmapStretchSR(texture,dstRegion, srcRegion, false); + } + } + else + { + RectI rect(offset, mBounds.extent); +// RectI rect = mBounds; +// rect.point += offset; + dglDrawBitmapStretch(mTextureHandle, rect); + } + } + else + { + RectI rect = mBounds; + rect.point += offset; + + glColor4f(0, 0, 0, 1); + glBegin(GL_LINE_LOOP); + glVertex2i(rect.point.x, rect.point.y); + glVertex2i(rect.point.x + rect.extent.x - 1, rect.point.y); + glVertex2i(rect.point.x + rect.extent.x - 1, rect.point.y + rect.extent.y - 1); + glVertex2i(rect.point.x, rect.point.y + rect.extent.y - 1); + glEnd(); + } + + renderChildControls(offset, updateRect, firstResponder); +} + +void GuiBitmapCtrl::setValue(S32 x, S32 y) +{ + if (mTextureHandle) + { + TextureObject* texture = (TextureObject *) mTextureHandle; + x+=texture->bitmapWidth/2; + y+=texture->bitmapHeight/2; + } + while (x < 0) + x += 256; + startPoint.x = x % 256; + + while (y < 0) + y += 256; + startPoint.y = y % 256; +} diff --git a/gui/guiBitmapCtrl.h b/gui/guiBitmapCtrl.h new file mode 100644 index 0000000..c5e4d5c --- /dev/null +++ b/gui/guiBitmapCtrl.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBITMAPCTRL_H_ +#define _GUIBITMAPCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif + +class GuiBitmapCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +protected: + StringTableEntry mBitmapName; + TextureHandle mTextureHandle; + Point2I startPoint; + bool mWrap; + +public: + //creation methods + DECLARE_CONOBJECT(GuiBitmapCtrl); + GuiBitmapCtrl(); + static void initPersistFields(); + static void consoleInit(); + + //Parental methods + bool onWake(); + void onSleep(); + + void setBitmap(const char *name); + void setBitmap(const TextureHandle &handle); + + S32 getWidth() const { return(mTextureHandle.getWidth()); } + S32 getHeight() const { return(mTextureHandle.getHeight()); } + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void setValue(S32 x, S32 y); +}; + +#endif diff --git a/gui/guiBorderButton.cc b/gui/guiBorderButton.cc new file mode 100644 index 0000000..d367baf --- /dev/null +++ b/gui/guiBorderButton.cc @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "gui/guiCanvas.h" +#include "gui/guiButtonBaseCtrl.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) +{ + RectI bounds(offset, mBounds.extent); + if(mActive && mMouseOver) + { + bounds.inset(2,2); + dglDrawRect(bounds, mProfile->mFontColorHL); + bounds.inset(-2,-2); + } + if(mActive && (mStateOn || mDepressed)) + { + dglDrawRect(bounds, mProfile->mFontColorHL); + bounds.inset(1,1); + dglDrawRect(bounds, mProfile->mFontColorHL); + } + renderChildControls(offset, updateRect); +} + diff --git a/gui/guiBubbleTextCtrl.cc b/gui/guiBubbleTextCtrl.cc new file mode 100644 index 0000000..a8e71f1 --- /dev/null +++ b/gui/guiBubbleTextCtrl.cc @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiBubbleTextCtrl.h" +#include "GUI/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->setField("profile","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->setField("profile","GuiBubblePopupProfile"); + + mMLText = new GuiMLTextCtrl(); + AssertFatal(mMLText, "Failed to create the GuiMLTextCtrl for the BubbleTextCtrl"); + mMLText->setField("profile","GuiBubbleTextProfile"); + mMLText->setField("position", "2 2"); + mMLText->setField("extent", "296 51"); + + mMLText->setText(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/guiBubbleTextCtrl.h b/gui/guiBubbleTextCtrl.h new file mode 100644 index 0000000..42c39c3 --- /dev/null +++ b/gui/guiBubbleTextCtrl.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBUBBLETEXTCTRL_H_ +#define _GUIBUBBLETEXTCTRL_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "GUI/guiTextCtrl.h" +#endif +#ifndef _GUIMLTEXTCTRL_H_ +#include "GUI/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/guiButtonBaseCtrl.cc b/gui/guiButtonBaseCtrl.cc new file mode 100644 index 0000000..315ca73 --- /dev/null +++ b/gui/guiButtonBaseCtrl.cc @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// Torque Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "console/consoleTypes.h" +#include "platform/platformAudio.h" +#include "gui/guiCanvas.h" +#include "gui/guiButtonBaseCtrl.h" + +GuiButtonBaseCtrl::GuiButtonBaseCtrl() +{ + mDepressed = false; + mMouseOver = false; + mActive = true; + mButtonText = StringTable->insert(""); +} + +ConsoleMethod( GuiButtonBaseCtrl, setText, void, 3, 3, "(string text) - sets the text of the button to the string." ) +{ + argc; + GuiButtonBaseCtrl* ctrl = static_cast( object ); + ctrl->setText( argv[2] ); +} + +ConsoleMethod( GuiButtonBaseCtrl, getText, const char *, 2, 2, "() - returns the text of the button." ) +{ + argc; argv; + GuiButtonBaseCtrl* ctrl = static_cast( object ); + return ctrl->getText( ); +} + +void GuiButtonBaseCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("text", TypeCaseString, Offset(mButtonText, GuiButtonBaseCtrl)); +} + +void GuiButtonBaseCtrl::setText(const char *text) +{ + mButtonText = StringTable->insert(text); +} + +const char *GuiButtonBaseCtrl::getText() +{ + return mButtonText; +} + +//--------------------------------------------------------------------------- +void GuiButtonBaseCtrl::AcceleratorKeyPress(void) +{ + if (! mActive) + return; + + //set the bool + mDepressed = true; + + if (mProfile->mTabable) + setFirstResponder(); +} + +//--------------------------------------------------------------------------- +void GuiButtonBaseCtrl::AcceleratorKeyRelease(void) +{ + if (! mActive) + return; + + //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) + { + F32 pan = (F32(event.mousePoint.x)/F32(Canvas->mBounds.extent.x)*2.0f-1.0f)*0.8f; + AUDIOHANDLE handle = alxCreateSource(mProfile->mSoundButtonDown); + alxPlay(handle); + } + + //lock the mouse + mouseLock(); + mDepressed = true; + + //update + setUpdate(); +} + +void GuiButtonBaseCtrl::onMouseEnter(const GuiEvent &event) +{ + setUpdate(); + if(isMouseLocked()) + { + mDepressed = true; + mMouseOver = true; + } + else + { + if ( mActive && mProfile->mSoundButtonOver ) + { + F32 pan = (F32(event.mousePoint.x)/F32(Canvas->mBounds.extent.x)*2.0f-1.0f)*0.8f; + AUDIOHANDLE handle = alxCreateSource(mProfile->mSoundButtonOver); + alxPlay(handle); + } + mMouseOver = true; + } +} + +void GuiButtonBaseCtrl::onMouseLeave(const GuiEvent &) +{ + setUpdate(); + if(isMouseLocked()) + mDepressed = false; + mMouseOver = false; +} + +void GuiButtonBaseCtrl::onMouseUp(const GuiEvent &) +{ + if (! mActive) + return; + + mouseUnlock(); + + setUpdate(); + + //if we released the mouse within this control, perform the action + if (mDepressed) + onAction(); + + mDepressed = false; +} + + +//-------------------------------------------------------------------------- +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 ) + { + F32 pan = ( F32( event.mousePoint.x ) / F32( Canvas->mBounds.extent.x ) * 2.0f - 1.0f ) * 0.8f; + AUDIOHANDLE handle = alxCreateSource( mProfile->mSoundButtonDown ); + alxPlay( handle ); + } + 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 ((event.keyCode == KEY_RETURN || event.keyCode == KEY_SPACE) + && event.modifier == 0) + { + onAction(); + return true; + } + //otherwise, pass the event to it's parent + return Parent::onKeyDown(event); +} + + diff --git a/gui/guiButtonBaseCtrl.h b/gui/guiButtonBaseCtrl.h new file mode 100644 index 0000000..16de9cb --- /dev/null +++ b/gui/guiButtonBaseCtrl.h @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Torque Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _GUIBUTTONBASECTRL_H_ +#define _GUIBUTTONBASECTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/guiControl.h" +#endif + +class GuiButtonBaseCtrl : public GuiControl +{ + typedef GuiControl Parent; + +protected: + StringTableEntry mButtonText; + bool mDepressed; + bool mMouseOver; +public: + GuiButtonBaseCtrl(); + static void initPersistFields(); + + void setText(const char *text); + const char *getText(); + + void AcceleratorKeyPress(); + void AcceleratorKeyRelease(); + + void onMouseDown(const GuiEvent &); + void onMouseUp(const GuiEvent &); + + void onMouseEnter(const GuiEvent &); + void onMouseLeave(const GuiEvent &); + + bool onKeyDown(const GuiEvent &event); + bool onKeyUp(const GuiEvent &event); +}; + +#endif \ No newline at end of file diff --git a/gui/guiButtonCtrl.cc b/gui/guiButtonCtrl.cc new file mode 100644 index 0000000..d0f83ab --- /dev/null +++ b/gui/guiButtonCtrl.cc @@ -0,0 +1,262 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "console/consoleTypes.h" +#include "Platform/platformAudio.h" +#include "audio/audioDataBlock.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiButtonCtrl.h" + +GuiButtonCtrl::GuiButtonCtrl() +{ + mActive = true; + mBounds.extent.set(140, 30); + mButtonText = 0; + mButtonState = Normal; + mMouseInControl = false; +} + +void GuiButtonCtrl::consoleInit() +{ +} + +void GuiButtonCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("text", TypeCaseString, Offset(mButtonText, GuiButtonCtrl)); +} + +const char * GuiButtonCtrl::getScriptValue() +{ + return(mButtonText); +} + +void GuiButtonCtrl::setScriptValue(const char * value) +{ + mButtonText = StringTable->insert(value); +} + +//-------------------------------------------------------------------------- +void GuiButtonCtrl::AcceleratorKeyPress(void) +{ + if ((! mVisible) || (! mActive) || (! mAwake)) + return; + + onAction(); + setFirstResponder(); +} + + +//-------------------------------------------------------------------------- +bool GuiButtonCtrl::onKeyDown(const GuiEvent &event) +{ + //if the control is a dead end, kill the event + if ((! mVisible) || (! mActive) || (! mAwake)) + 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 ) + { + F32 pan = ( F32( event.mousePoint.x ) / F32( Canvas->mBounds.extent.x ) * 2.0f - 1.0f ) * 0.8f; + AUDIOHANDLE handle = alxCreateSource( mProfile->mSoundButtonDown ); + alxSourcef( handle, AL_PAN, pan ); + alxPlay( handle ); + } + + onAction(); + return true; + } + + //otherwise, pass the event to it's parent + return Parent::onKeyDown(event); +} + + +//-------------------------------------------------------------------------- +void GuiButtonCtrl::onMouseDown(const GuiEvent &event) +{ + // Are we answering the phone? + if (!mVisible || !mActive || !mAwake) + return; + + // Ok, this is a bona-fide real click. Check our preconditions + AssertFatal(mButtonState == Normal, "Error, we should never reach this state!"); + + setFirstResponder(); + mButtonState = MouseDown; + getRoot()->mouseLock(this); + + if (mProfile->mSoundButtonDown) + { + F32 pan = (F32(event.mousePoint.x)/F32(Canvas->mBounds.extent.x)*2.0f-1.0f)*0.8f; + AUDIOHANDLE handle = alxCreateSource(mProfile->mSoundButtonDown); + alxSourcef(handle, AL_PAN, pan); + alxPlay(handle); + //alxPlay(mProfile->mSoundButtonDown, NULL, NULL); + //Audio::play2D(mProfile->mSoundButtonDown->getName(), mProfile->mSoundButtonDown->getDescription(), pan); + } + setUpdate(); +} + + +//-------------------------------------------------------------------------- +void GuiButtonCtrl::onMouseUp(const GuiEvent&) +{ + GuiCanvas* root = getRoot(); + if (mButtonState == Normal) { + AssertFatal(root->getMouseLockedControl() != this, "Error, we should not have the mouse locked here!"); + return; + } + + mButtonState = Normal; + setUpdate(); + + root->mouseUnlock(this); + if (cursorInControl()) + onAction(); +} + + +//-------------------------------------------------------------------------- +void GuiButtonCtrl::onMouseEnter(const GuiEvent& event) +{ + mMouseInControl = true; + + if ( mActive && mProfile->mSoundButtonOver ) + { + F32 pan = (F32(event.mousePoint.x)/F32(Canvas->mBounds.extent.x)*2.0f-1.0f)*0.8f; + AUDIOHANDLE handle = alxCreateSource(mProfile->mSoundButtonOver); + alxSourcef(handle, AL_PAN, pan); + alxPlay(handle); + + //alxPlay(mProfile->mSoundButtonOver, NULL, NULL); + //Audio::play2D(mProfile->mSoundButtonOver->getName(), mProfile->mSoundButtonOver->getDescription(), pan); + } + + Parent::onMouseEnter( event ); + setUpdate(); +} + + +//-------------------------------------------------------------------------- +void GuiButtonCtrl::onMouseLeave(const GuiEvent& event) +{ + mMouseInControl = false; + Parent::onMouseLeave(event); + setUpdate(); +} + + +//-------------------------------------------------------------------------- +void GuiButtonCtrl::onSleep() +{ + mMouseInControl = false; + mButtonState = Normal; + Parent::onSleep(); +} + +//-------------------------------------------------------------------------- +void GuiButtonCtrl::onRender(Point2I offset, + const RectI& updateRect, + GuiControl* firstResponder) +{ + bool highlight = false; + bool depressed = false; + + if ( mActive ) + { + if ( mButtonState == MouseDown ) + { + highlight = true; + depressed = cursorInControl(); + } + else + highlight = mMouseInControl; + } + + ColorI fontColor = mActive ? (highlight ? mProfile->mFontColorHL : mProfile->mFontColor) : mProfile->mFontColorNA; + ColorI backColor = mActive ? mProfile->mFillColor : mProfile->mFillColorNA; + ColorI borderColor = mActive ? mProfile->mBorderColor : mProfile->mBorderColorNA; + +#if 1 + // first draw the background + Point2I extent( offset.x+mBounds.extent.x-1, offset.y+mBounds.extent.y-1); + if ( mProfile->mOpaque ) + { + if (depressed) + renderLoweredBox(offset, extent, backColor); + else + renderRaisedBox(offset, extent, backColor); + } + else + if ( mProfile->mBorder ) + dglDrawRect( offset, Point2I(extent.x+1, extent.y+1), backColor ); + +#else + // first draw the background + RectI r( offset, mBounds.extent ); + if ( mProfile->mOpaque ) + dglDrawRectFill( r, backColor ); + + if ( mProfile->mBorder ) + { + dglDrawRect( r, borderColor ); + + if ( mActive && firstResponder == this ) + { + r.point += Point2I(1, 1); + r.extent -= Point2I(2, 2); + dglDrawRect( r, mProfile->mBorderColorHL ); + } + } +#endif + + // finally draw the text + if ( mButtonText && mButtonText[0] != NULL ) + { + S32 txt_w = mProfile->mFont->getStrWidth( mButtonText ); + Point2I localStart; + + // align the horizontal + switch (mProfile->mAlignment) + { + case GuiControlProfile::RightJustify: + localStart.x = mBounds.extent.x - txt_w; + break; + case GuiControlProfile::CenterJustify: + localStart.x = ( mBounds.extent.x - txt_w ) / 2; + break; + default: + // GuiControlProfile::LeftJustify + localStart.x = 0; + break; + } + + // center the vertical + localStart.y = ( mBounds.extent.y - ( mProfile->mFont->getHeight() - 2 ) ) / 2; + + localStart.y -= 2; + if ( depressed ) + { + localStart.x += 1; + localStart.y += 1; + } + Point2I globalStart = localToGlobalCoord( localStart ); + + dglSetBitmapModulation( fontColor ); + dglDrawText(mProfile->mFont, globalStart, mButtonText, mProfile->mFontColors); + + //render the children + renderChildControls( offset, updateRect, firstResponder ); + } +} + diff --git a/gui/guiButtonCtrl.h b/gui/guiButtonCtrl.h new file mode 100644 index 0000000..73e1e0b --- /dev/null +++ b/gui/guiButtonCtrl.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBUTTONCTRL_H_ +#define _GUIBUTTONCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +class GuiButtonCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + + protected: + enum State { + Normal, + MouseDown + }; + + bool mMouseInControl; + State mButtonState; + StringTableEntry mButtonText; + + public: + DECLARE_CONOBJECT(GuiButtonCtrl); + GuiButtonCtrl(); + static void consoleInit(); + static void initPersistFields(); + + void AcceleratorKeyPress(void); + + bool onKeyDown(const GuiEvent&); + + void onMouseUp(const GuiEvent&); + void onMouseDown(const GuiEvent&); + void onMouseEnter(const GuiEvent&); + void onMouseLeave(const GuiEvent&); + + const char * getScriptValue(); + void setScriptValue(const char *); + + void onSleep(); + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void drawBorder(const RectI &r, const ColorI &color); +}; + +#endif //_GUI_BUTTON_CTRL_H diff --git a/gui/guiCanvas.cc b/gui/guiCanvas.cc new file mode 100644 index 0000000..b88d76a --- /dev/null +++ b/gui/guiCanvas.cc @@ -0,0 +1,1260 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "platform/event.h" +#include "platform/platform.h" +#include "platform/platformVideo.h" +#include "gui/guiTypes.h" +#include "gui/guiTextCtrl.h" +#include "gui/guiTextEditCtrl.h" +#include "gui/guiTextListCtrl.h" +#include "gui/guiBitmapCtrl.h" +#include "gui/guiButtonCtrl.h" +#include "gui/guiRadioCtrl.h" +#include "gui/guiCheckBoxCtrl.h" +#include "gui/guiArrayCtrl.h" +#include "gui/guiScrollCtrl.h" +#include "gui/guiSliderCtrl.h" +#include "gui/guiWindowCtrl.h" +#include "gui/guiConsole.h" +#include "gui/guiCanvas.h" +#include "gui/guiInspector.h" +#include "gui/guiTreeViewCtrl.h" +#include "gui/guiEditCtrl.h" +#include "gui/guiFilterCtrl.h" +#include "gui/guiConsoleTextCtrl.h" +#include "gui/guiPopUpCtrl.h" +#include "gui/guiDebugger.h" +#include "editor/guiTerrPreviewCtrl.h" +#include "gui/guiBackgroundCtrl.h" +#include "gui/guiTextEditSliderCtrl.h" +#include "gui/guiProgressCtrl.h" +#include "gui/guiVoteCtrl.h" +#include "platform/profiler.h" + +extern bool gDGLRender; + + +//all the ConObject implements go here +IMPLEMENT_CONOBJECT(GuiControlProfile); +IMPLEMENT_CONOBJECT(GuiCursor); +IMPLEMENT_CONOBJECT(GuiControl); +IMPLEMENT_CONOBJECT(GuiCanvas); +IMPLEMENT_CONOBJECT(GuiTextCtrl); +IMPLEMENT_CONOBJECT(GuiTextEditCtrl); +IMPLEMENT_CONOBJECT(GuiTextListCtrl); +IMPLEMENT_CONOBJECT(GuiBitmapCtrl); +IMPLEMENT_CONOBJECT(GuiButtonCtrl); +IMPLEMENT_CONOBJECT(GuiRadioCtrl); +IMPLEMENT_CONOBJECT(GuiCheckBoxCtrl); +IMPLEMENT_CONOBJECT(GuiArrayCtrl); +IMPLEMENT_CONOBJECT(GuiScrollContentCtrl); +IMPLEMENT_CONOBJECT(GuiScrollCtrl); +IMPLEMENT_CONOBJECT(GuiSliderCtrl); +IMPLEMENT_CONOBJECT(GuiWindowCtrl); +IMPLEMENT_CONOBJECT(GuiPopUpMenuCtrl); +IMPLEMENT_CONOBJECT(GuiFilterCtrl); +IMPLEMENT_CONOBJECT(GuiBackgroundCtrl); +IMPLEMENT_CONOBJECT(GuiTextEditSliderCtrl); +IMPLEMENT_CONOBJECT(GuiProgressCtrl); +IMPLEMENT_CONOBJECT(GuiVoteCtrl); + +IMPLEMENT_CONOBJECT(GuiConsole); +IMPLEMENT_CONOBJECT(GuiInspector); +IMPLEMENT_CONOBJECT(GuiTreeView); +IMPLEMENT_CONOBJECT(GuiEditCtrl); + +// editor/debugging controls +IMPLEMENT_CONOBJECT(GuiConsoleTextCtrl); +IMPLEMENT_CONOBJECT(DbgFileView); + +GuiCanvas *Canvas = NULL; + +static S32 cGuiCanvasGetContent(SimObject *obj, S32, const char **) +{ + GuiControl *ctrl = ((GuiCanvas *) obj)->getContentControl(); + if(ctrl) + return ctrl->getId(); + return -1; +} + +static void cGuiCanvasSetContent(SimObject *obj, S32 argc, const char **argv) +{ + obj; + argc; + + GuiControl *gui = NULL; + if(argv[2][0]) + { + SimObject* ctrl = Sim::findObject(argv[2]); + if (ctrl) + gui = dynamic_cast(ctrl); + if (! gui) + { + Con::printf("%s(): Invalid control: %s", argv[0], argv[2]); + return; + } + } + //set the new content control + Canvas->setContentControl(gui); +} + +static void cGuiCanvasPushDialog(SimObject *obj, S32 argc, const char **argv) +{ + obj; + + GuiControl *gui = NULL; + SimObject* ctrl = Sim::findObject(argv[2]); + if (ctrl) + gui = dynamic_cast(ctrl); + if (! 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 + Canvas->pushDialogControl(gui, layer); +} + +static void cGuiCanvasPopDialog(SimObject *obj, S32 argc, const char **argv) +{ + obj; + + GuiControl *gui = NULL; + if (argc == 3) + { + SimObject* ctrl = Sim::findObject(argv[2]); + if (ctrl) + gui = dynamic_cast(ctrl); + if (! gui) + { + Con::printf("%s(): Invalid control: %s", argv[0], argv[2]); + return; + } + } + + if (gui) + Canvas->popDialogControl(gui); + else + Canvas->popDialogControl(); +} + +static void cGuiCanvasPopLayer(SimObject *obj, S32 argc, const char **argv) +{ + obj; + + S32 layer = 0; + if (argc == 3) + layer = dAtoi(argv[2]); + Canvas->popDialogControl(layer); +} + +static void cGuiCanvasCursorOn(SimObject *obj, S32 argc, const char **argv) +{ + obj; + argc; + argv; + Canvas->setCursorON(true); +} + +static void cGuiCanvasCursorOff(SimObject *obj, S32 argc, const char **argv) +{ + obj; + argc; + argv; + Canvas->setCursorON(false); +} + +static void cGuiCanvasSetCursor(SimObject *obj, S32 argc, const char **argv) +{ + obj; + 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; + } + } + Canvas->setCursor(curs); +} + +static void cGuiCanvasRenderFront(SimObject*, S32, const char **argv) +{ + Canvas->setRenderFront(dAtob(argv[2])); +} + +static void cGuiCanvasShowCursor(SimObject *, S32, const char **) +{ + Canvas->showCursor(true); +} + +static void cGuiCanvasHideCursor(SimObject *, S32, const char **) +{ + Canvas->showCursor(false); +} + +static bool cGuiCanvasIsCursorOn(SimObject *, S32, const char **) +{ + return Canvas->isCursorON(); +} + +static void cGuiCanvasRepaint(SimObject *, S32, const char **) +{ + Canvas->paint(); +} + +static void cGuiCanvasReset(SimObject *, S32, const char **) +{ + Canvas->resetUpdateRegions(); +} + +static const char * cGuiCanvasGetCursorPos(SimObject *, S32, const char **) +{ + Point2I pos = Canvas->getCursorPos(); + char * ret = Con::getReturnBuffer(32); + dSprintf(ret, 32, "%d %d", pos.x, pos.y); + return(ret); +} + +static void cGuiCanvasSetCursorPos(SimObject *, S32 argc, const char ** argv) +{ + Point2I pos(0,0); + if(argc == 4) + pos.set(dAtoi(argv[2]), dAtoi(argv[3])); + else + dSscanf(argv[3], "%d %d", &pos.x, &pos.y); + Canvas->setCursorPos(pos); +} + +void GuiCanvas::consoleInit(void) +{ + Con::addCommand("GuiCanvas", "renderFront", cGuiCanvasRenderFront, "canvas.renderFront(bool)", 3, 3); + Con::addCommand("GuiCanvas", "setContent", cGuiCanvasSetContent, "canvas.setContent(ctrl)", 3, 3); + Con::addCommand("GuiCanvas", "getContent", cGuiCanvasGetContent, "canvas.getContent()", 2, 2); + Con::addCommand("GuiCanvas", "pushDialog", cGuiCanvasPushDialog, "canvas.pushDialog(ctrl)", 3, 4); + Con::addCommand("GuiCanvas", "popDialog", cGuiCanvasPopDialog, "canvas.popDialog()", 2, 3); + Con::addCommand("GuiCanvas", "popLayer", cGuiCanvasPopLayer, "canvas.popLayer()", 2, 3); + Con::addCommand("GuiCanvas", "cursorOn", cGuiCanvasCursorOn, "canvas.cursorOn()", 2, 2); + Con::addCommand("GuiCanvas", "cursorOff", cGuiCanvasCursorOff, "canvas.cursorOff()", 2, 2); + Con::addCommand("GuiCanvas", "setCursor", cGuiCanvasSetCursor, "canvas.setCursor(cursor)", 3, 3); + Con::addCommand("GuiCanvas", "hideCursor", cGuiCanvasHideCursor, "canvas.hideCursor()", 2, 2); + Con::addCommand("GuiCanvas", "showCursor", cGuiCanvasShowCursor, "canvas.showCursor()", 2, 2); + Con::addCommand("GuiCanvas", "repaint", cGuiCanvasRepaint, "canvas.repaint()", 2, 2); + Con::addCommand("GuiCanvas", "reset", cGuiCanvasReset, "canvas.reset()", 2, 2); + Con::addCommand("GuiCanvas", "isCursorOn", cGuiCanvasIsCursorOn, "canvas.isCursorOn()", 2, 2); + Con::addCommand("GuiCanvas", "getCursorPos", cGuiCanvasGetCursorPos, "canvas.getCursorPos()", 2, 2); + Con::addCommand("GuiCanvas", "setCursorPos", cGuiCanvasSetCursorPos, "canvas.setCursorPos(pos)", 3, 4); +} + +GuiCanvas::GuiCanvas() +{ + mBounds.set(0, 0, 640, 480); + mAwake = true; + mPixelsPerMickey = 1.0f; + lastCursorON = false; + cursorON = true; + mShowCursor = true; + rLastFrameTime = 0.0f; + + mMouseCapturedControl = NULL; + mMouseControl = NULL; + mMouseControlClicked = false; + mMouseButtonDown = false; + mMouseRightButtonDown = false; + + lastCursor = NULL; + lastCursorPt.set(0,0); + cursorPt.set(0,0); + + mLastMouseClickCount = 0; + mLastMouseDownTime = 0; + mPrevMouseTime = 0; + defaultCursor = NULL; + + mRenderFront = false; +} + +GuiCanvas::~GuiCanvas() +{ + if(Canvas == this) + Canvas = 0; +} + +//------------------------------------------------------------------------------ +void GuiCanvas::setCursorON(bool onOff) +{ + cursorON = onOff; + if(!cursorON) + mMouseControl = NULL; +} + +void GuiCanvas::AddAcceleratorKey(GuiControl *ctrl, U32 keyCode, U32 modifier) +{ + if (keyCode > 0 && ctrl) + { + AccKeyMap newMap; + newMap.ctrl = ctrl; + newMap.keyCode = keyCode; + newMap.modifier = modifier; + mAcceleratorMap.push_back(newMap); + } +} + +void 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(); + + if ( oldResponder ) + oldResponder->onLoseFirstResponder(); + } + } +} + +void 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(); + + if ( oldResponder ) + oldResponder->onLoseFirstResponder(); + } + } +} + +void GuiCanvas::processMouseMoveEvent(const MouseMoveEvent *event) +{ + InputEvent iEvent; + iEvent.deviceType = MouseDeviceType; + iEvent.objType = SI_XAXIS; + iEvent.fValue = event->xPos - cursorPt.x; + iEvent.modifier = event->modifier; + processInputEvent(&iEvent); + iEvent.objType = SI_YAXIS; + iEvent.fValue = event->yPos - cursorPt.y; + processInputEvent(&iEvent); +} + +bool GuiCanvas::processInputEvent(const InputEvent *event) +{ + GuiEvent evt; + + // First call the general input handler (on the extremely off-chance that it will be handled): + if ( mFirstResponder ) + { + if ( mFirstResponder->onInputEvent( *event ) ) + return( true ); + } + + if(event->deviceType == KeyboardDeviceType) + { + evt.ascii = event->ascii; + evt.modifier = event->modifier; + evt.keyCode = event->objInst; + + if (event->action == SI_MAKE) + { + //see if we should tab next/prev + + //see if we should now pass the event to the first responder + if (mFirstResponder) + { + if(mFirstResponder->onKeyDown(evt)) + return true; + } + + if ( isCursorON() && ( event->objInst == KEY_TAB ) ) + { + if (size() > 0) + { + if (event->modifier & SI_SHIFT) + { + tabPrev(); + return true; + } + else if (event->modifier == 0) + { + tabNext(); + return true; + } + } + } + + //if not handled, search for an accelerator + for (U32 i = 0; i < mAcceleratorMap.size(); i++) + { + if ((U32)mAcceleratorMap[i].keyCode == (U32)event->objInst && (U32)mAcceleratorMap[i].modifier == (U32)event->modifier) + { + mAcceleratorMap[i].ctrl->AcceleratorKeyPress(); + return true; + } + } + } + else if(event->action == SI_BREAK) + { + if(mFirstResponder) + if(mFirstResponder->onKeyUp(evt)) + return true; + + //see if there's an accelerator + for (U32 i = 0; i < mAcceleratorMap.size(); i++) + { + if ((U32)mAcceleratorMap[i].keyCode == (U32)event->objInst && (U32)mAcceleratorMap[i].modifier == (U32)event->modifier) + { + mAcceleratorMap[i].ctrl->AcceleratorKeyRelease(); + return true; + } + } + } + else if(event->action == SI_REPEAT) + { + if(mFirstResponder) + mFirstResponder->onKeyRepeat(evt); + return true; + } + } + else if(event->deviceType == MouseDeviceType && cursorON) + { + //copy the modifier into the new event + evt.modifier = event->modifier; + + if(event->objType == SI_XAXIS || event->objType == SI_YAXIS) + { + bool moved = false; + Point2I oldpt(cursorPt.x, cursorPt.y); + Point2F pt(cursorPt.x, cursorPt.y); + + if (event->objType == SI_XAXIS) + { + pt.x += (event->fValue * mPixelsPerMickey); + cursorPt.x = getMax(0, getMin((S32)pt.x, mBounds.extent.x - 1)); + if (oldpt.x != S32(cursorPt.x)) + moved = true; + } + else + { + pt.y += (event->fValue * mPixelsPerMickey); + cursorPt.y = getMax(0, getMin((S32)pt.y, mBounds.extent.y - 1)); + if (oldpt.y != S32(cursorPt.y)) + moved = true; + } + if (moved) + { + evt.mousePoint.x = S32(cursorPt.x); + evt.mousePoint.y = S32(cursorPt.y); + + if (mMouseButtonDown) + rootMouseDragged(evt); + else if (mMouseRightButtonDown) + rootRightMouseDragged(evt); + else + rootMouseMove(evt); + } + return true; + } + else if ( event->objType == SI_ZAXIS ) + { + evt.mousePoint.x = S32( cursorPt.x ); + evt.mousePoint.y = S32( cursorPt.y ); + + if ( event->fValue < 0.0f ) + rootMouseWheelDown( evt ); + else + rootMouseWheelUp( evt ); + } + else if(event->objType == SI_BUTTON) + { + //copy the cursor point into the event + evt.mousePoint.x = S32(cursorPt.x); + evt.mousePoint.y = S32(cursorPt.y); + + if(event->objInst == KEY_BUTTON0) // left button + { + //see if button was pressed + if (event->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 <= 500) + mLastMouseClickCount++; + else + mLastMouseClickCount = 1; + } + else + { + mLeftMouseLast = true; + mLastMouseClickCount = 1; + } + + mLastMouseDownTime = curTime; + evt.mouseClickCount = mLastMouseClickCount; + + mLastMouseEvent = evt; + rootMouseDown(evt); + } + //else button was released + else + { + mNextMouseTime = 0xFFFFFFFF; + rootMouseUp(evt); + } + return true; + } + else if(event->objInst == KEY_BUTTON1) // right button + { + if(event->action == SI_MAKE) + { + U32 curTime = Platform::getVirtualMilliseconds(); + + //if the last button pressed was the right... + if (! mLeftMouseLast) + { + //if it was within the double click time count the clicks + if (curTime - mLastMouseDownTime <= 50) + mLastMouseClickCount++; + else + mLastMouseClickCount = 1; + } + else + { + mLeftMouseLast = false; + mLastMouseClickCount = 1; + } + + mLastMouseDownTime = curTime; + evt.mouseClickCount = mLastMouseClickCount; + + rootRightMouseDown(evt); + } + else // it was a mouse up + rootRightMouseUp(evt); + return true; + } + } + } + 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 pass it to whoever is underneath the cursor + else + { + 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->mProfile->mModal)) + continue; + else + { + GuiControl * firstResponder = getFirstResponder(); + if(firstResponder && firstResponder != controlHit) + { + firstResponder->clearFirstResponder(); + firstResponder->onLoseFirstResponder(); + } + controlHit->onMouseDown(event); + break; + } + } + } + if (bool(mMouseControl)) + mMouseControlClicked = true; +} + +void GuiCanvas::findMouseControl(const GuiEvent &event) +{ + if(size() == 0) + { + mMouseControl = NULL; + return; + } + GuiControl *controlHit = NULL; + for(S32 i = size() - 1; i >= 0; i--) + { + GuiControl *ctrl = static_cast((*this)[i]); + Point2I pt = ctrl->globalToLocalCoord(Point2I(cursorPt.x, cursorPt.y)); + controlHit = ctrl->findHitControl(pt); + if(controlHit->mProfile->mModal) + break; + } + 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(cursorPt.x); + evt.mousePoint.y = S32(cursorPt.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::rootMouseDragged(const GuiEvent &event) +{ + //pass the event to the mouse locked control + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onMouseDragged(event); + else + { + findMouseControl(event); + if(bool(mMouseControl)) + mMouseControl->onMouseDragged(event); + } +} + +void GuiCanvas::rootMouseMove(const GuiEvent &event) +{ + if (bool(mMouseCapturedControl)) + 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)) + { + if(mMouseControl->mActive && mMouseControl->mProfile->mModal) + { + GuiControl * firstResponder = getFirstResponder(); + if(firstResponder && firstResponder != static_cast(mMouseControl)) + { + firstResponder->clearFirstResponder(); + firstResponder->onLoseFirstResponder(); + } + } + 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)) + mMouseCapturedControl->onRightMouseDragged(event); + else + { + findMouseControl(event); + + if(bool(mMouseControl)) + mMouseControl->onRightMouseDragged(event); + } +} + +void GuiCanvas::rootMouseWheelUp(const GuiEvent &event) +{ + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onMouseWheelUp(event); + else + { + findMouseControl(event); + + if (bool(mMouseControl)) + mMouseControl->onMouseWheelUp(event); + } +} + +void GuiCanvas::rootMouseWheelDown(const GuiEvent &event) +{ + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onMouseWheelDown(event); + else + { + findMouseControl(event); + + if (bool(mMouseControl)) + mMouseControl->onMouseWheelDown(event); + } +} + +void GuiCanvas::setContentControl(GuiControl *gui) +{ + //remove all dialogs on layer 0 + + // awaken the new control: + U32 index = 0; + bool wakedGui = false; + + if(gui) + { + //add the gui to the front + if(!size() || gui != (*this)[0]) + { + // automatically wakes objects in GuiControl::onAdd + wakedGui = !gui->isAwake(); + addObject(gui); + if (size() >= 2) + reOrder(gui, *begin()); + } + index = 1; + } + while (size() > index) + { + GuiControl *ctrl = static_cast((*this)[index]); + if (ctrl->mLayer != 0) + break; + + bool didSleep = ctrl->isAwake(); + + // GuiControl::removeObject removes awoke controls + removeObject(ctrl); + if(didSleep) + Con::executef(ctrl, 1, "onSleep"); + + Sim::getGuiGroup()->addObject(ctrl); + } + + if(gui) + { + //wake the object up in script + if(wakedGui) + Con::executef(gui, 1, "onWake"); + + GuiControl *oldResponder = mFirstResponder; + mFirstResponder = gui->findFirstTabable(); + if(oldResponder && oldResponder != mFirstResponder) + oldResponder->onLoseFirstResponder(); + } + //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->mProfile->mModal) + break; + } + refreshMouseControl(); +} + +GuiControl *GuiCanvas::getContentControl() +{ + if(size() > 0) + return (GuiControl *) first(); + return NULL; +} + +void GuiCanvas::pushDialogControl(GuiControl *gui, S32 layer) +{ + //add the gui + gui->mLayer = layer; + + // GuiControl::addObject wakes the object + bool wakedGui = !gui->isAwake(); + 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 'onWake' method? + if(wakedGui) + Con::executef(gui, 1, "onWake"); + + //call the dialog push method + gui->onDialogPush(); + + //find the top most dialog + GuiControl *topCtrl = static_cast(last()); + + //save the old + GuiControl *oldResponder = mFirstResponder; + + //find the first responder + mFirstResponder = gui->findFirstTabable(); + + if (oldResponder && oldResponder != mFirstResponder) + oldResponder->onLoseFirstResponder(); + + //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(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(); + + // sleep the object + bool didSleep = ctrl->isAwake(); + + //now pop the last child (will sleep if awake) + removeObject(ctrl); + + // Save the old responder: + GuiControl *oldResponder = mFirstResponder; + + if(didSleep) + Con::executef(ctrl, 1, "onSleep"); + + Sim::getGuiGroup()->addObject(ctrl); + + if (size() > 0) + { + GuiControl *ctrl = static_cast(last()); + mFirstResponder = ctrl->mFirstResponder; + } + else + { + mFirstResponder = NULL; + } + + if (oldResponder && oldResponder != mFirstResponder) + oldResponder->onLoseFirstResponder(); + + //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; +} + +void GuiCanvas::mouseUnlock(GuiControl *lockingControl) +{ + if (static_cast(mMouseCapturedControl) != lockingControl) + return; + + GuiEvent evt; + evt.mousePoint.x = S32(cursorPt.x); + evt.mousePoint.y = S32(cursorPt.y); + + GuiControl * controlHit = NULL; + for(S32 i = size() - 1; i >= 0; i--) + { + GuiControl *ctrl = static_cast((*this)[i]); + Point2I pt = ctrl->globalToLocalCoord(Point2I(cursorPt.x, cursorPt.y)); + controlHit = ctrl->findHitControl(pt); + if(controlHit->mProfile->mModal) + break; + } + + if (controlHit != static_cast(mMouseCapturedControl)) + { + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onMouseLeave(evt); + + 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 (gDGLRender) + renderFrame(false); +} + +void GuiCanvas::renderFrame(bool preRenderOnly) +{ + PROFILE_START(CanvasPreRender); + if(mRenderFront) + glDrawBuffer(GL_FRONT); + else + glDrawBuffer(GL_BACK); + + Point2I size = Platform::getWindowSize(); + + if(size.x == 0 || size.y == 0) + return; + + RectI screenRect(0, 0, size.x, size.y); + mBounds = 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::renderFrame: 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(); + } + } + + //preRender (recursive) all controls + preRender(); + PROFILE_END(); + if(preRenderOnly) + return; + + //resetUpdateRegions(); + + // finish the gl render so we don't get too far ahead of ourselves + + PROFILE_START(glFinish); + glFinish(); + PROFILE_END(); + + //draw the mouse, but not using tags... + PROFILE_START(CanvasRenderControls); + + GuiCursor *mouseCursor = NULL; + if(bool(mMouseCapturedControl)) + mouseCursor = mMouseCapturedControl->getCursor(); + else if(bool(mMouseControl)) + mouseCursor = mMouseControl->getCursor(); + + //determine if a help ctrl should be drawn + //get the time + //U32 time = Platform::getVirtualMilliseconds(); + + //static SimManager* simMngr = SimGame::get()->getManager(); + //AssertFatal(simMngr, "Unable to get the sim manager"); + //GuiHelpCtrl *helpCtrl = static_cast(simMngr->findObject(guiHelpObjectID)); + + //S32 helpTag = 0; + //const char *helpText = NULL; + //if (mMouseControl && cursorON && (! mMouseCapturedControl) && (! mMouseButtonDown) && (! mMouseRightButtonDown)) + //{ + // helpTag = mMouseControl->getHelpTag(time - mPrevMouseTime); + // helpText = mMouseControl->getHelpText(time - mPrevMouseTime); + //} + //if (helpCtrl) + //{ + // helpCtrl->setHelpTag(this, helpTag, time - mPrevMouseTime, mMouseControlClicked); + // helpCtrl->setHelpText(this, helpText, time - mPrevMouseTime, mMouseControlClicked); + //} + + Point2I cursorPos(cursorPt.x, cursorPt.y); + if(!mouseCursor) + mouseCursor = defaultCursor; + + if(lastCursorON && lastCursor) + { + Point2I spot = lastCursor->getHotSpot(); + Point2I cext = lastCursor->getExtent(); + Point2I pos = lastCursorPt - spot; + addUpdateRegion(pos - Point2I(2, 2), Point2I(cext.x + 4, cext.y + 4)); + } + if(cursorON && 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)); + } + + lastCursorON = cursorON; + lastCursor = mouseCursor; + lastCursorPt = cursorPos; + + RectI updateUnion; + buildUpdateUnion(&updateUnion); + if (updateUnion.intersect(screenRect)) + { + //fill in with black first + //glClearColor(0, 0, 0, 0); + //glClear(GL_COLOR_BUFFER_BIT); + + //render the dialogs + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *contentCtrl = static_cast(*i); + dglSetClipRect(updateUnion); + glDisable( GL_CULL_FACE ); + contentCtrl->onRender(contentCtrl->getPosition(), updateUnion, mFirstResponder); + } + + dglSetClipRect(updateUnion); + + //temp draw the mouse + if (cursorON && mShowCursor && !mouseCursor) + { + glColor4ub(255, 0, 0, 255); + glRecti((S32)cursorPt.x, (S32)cursorPt.y, (S32)(cursorPt.x + 2), (S32)(cursorPt.y + 2)); + } + + //DEBUG + //draw the help ctrl + //if (helpCtrl) + //{ + // helpCtrl->render(srf); + //} + + if (cursorON && mouseCursor && mShowCursor) + { + Point2I pos(cursorPt.x, cursorPt.y); + Point2I spot = mouseCursor->getHotSpot(); + + pos -= spot; + dglClearBitmapModulation(); + dglDrawBitmap(mouseCursor->getTextureHandle(), pos); + } + } + PROFILE_END(); + PROFILE_START(SwapBuffers); + //flip the surface + if(!mRenderFront) + Video::swapBuffers(); + 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].set(mBounds.point, mBounds.extent); + mOldUpdateRects[1] = mOldUpdateRects[0]; + mCurUpdateRect = mOldUpdateRects[0]; +} + +void GuiCanvas::setFirstResponder( GuiControl* newResponder ) +{ + GuiControl* oldResponder = mFirstResponder; + Parent::setFirstResponder( newResponder ); + + if ( oldResponder && ( oldResponder != mFirstResponder ) ) + oldResponder->onLoseFirstResponder(); +} diff --git a/gui/guiCanvas.h b/gui/guiCanvas.h new file mode 100644 index 0000000..5387999 --- /dev/null +++ b/gui/guiCanvas.h @@ -0,0 +1,147 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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/guiControl.h" +#endif + +class GuiCanvas : public GuiControl +{ + +private: + typedef GuiControl Parent; + typedef SimObject Grandparent; + + //Rendering members + RectI mOldUpdateRects[2]; + RectI mCurUpdateRect; + F32 rLastFrameTime; + +public: + +private: + //Mouse members + F32 mPixelsPerMickey; + bool cursorON; + bool mShowCursor; + bool mRenderFront; + Point2F cursorPt; + Point2I lastCursorPt; + GuiCursor *defaultCursor; + GuiCursor *lastCursor; + bool lastCursorON; + + //Input Members - mouse + SimObjectPtr mMouseCapturedControl; // All mouse events will go to this ctrl only + SimObjectPtr mMouseControl; // the control the mouse was last seen in unless some other on 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; // bool to determine if the button is depressed + bool mMouseRightButtonDown; // bool to determine if the right button is depressed + GuiEvent mLastMouseEvent; + + //Input Members - keyboard + GuiControl *keyboardControl; // All keyboard events will go to this ctrl first + U32 nextKeyTime; + //GuiEvent lastKeyEvent; + + U8 mLastMouseClickCount; + S32 mLastMouseDownTime; + bool mLeftMouseLast; + + //accelerator key map + struct AccKeyMap + { + GuiControl *ctrl; + U32 keyCode; + U32 modifier; + }; + Vector mAcceleratorMap; + void findMouseControl(const GuiEvent &event); + void refreshMouseControl(); + +public: + DECLARE_CONOBJECT(GuiCanvas); + GuiCanvas(); + ~GuiCanvas(); + static void consoleInit(); + + //Rendering methods + void renderFrame(bool preRenderOnly); //called ever cycle to repaint the updateRects + void paint(); //causes the entire canvas to be repainted + void addUpdateRegion(Point2I pos, Point2I ext); + void resetUpdateRegions(); + void buildUpdateUnion(RectI *updateUnion); + + //Control content methods + void setContentControl(GuiControl *gui); + GuiControl *getContentControl(); + + void pushDialogControl(GuiControl *gui, S32 layer = 0); + void popDialogControl(S32 layer = 0); + void popDialogControl(GuiControl *gui); + + // general cursor commands + void setCursor(GuiCursor *curs) { defaultCursor = curs; } + void setRenderFront(bool front) { mRenderFront = front; } + bool isCursorON() {return cursorON; } + void setCursorON(bool onOff); + void setCursorPos(const Point2I &pt) { cursorPt.x = F32(pt.x); cursorPt.y = F32(pt.y); } + Point2I getCursorPos() { return Point2I(S32(cursorPt.x), S32(cursorPt.y)); } + void showCursor(bool state) { mShowCursor = state; } + bool isCursorShown() { return(mShowCursor); } + + //all input events come through the canvas + bool processInputEvent(const InputEvent *event); + void processMouseMoveEvent(const MouseMoveEvent *event); + + //mouse methods + void mouseLock(GuiControl *lockingControl); + void mouseUnlock(GuiControl *lockingControl); + GuiControl* getMouseControl() { return mMouseControl; } + GuiControl* getMouseLockedControl() { return mMouseCapturedControl; } + bool mouseButtonDown(void) { return mMouseButtonDown; } + bool mouseRightButtonDown(void) { return mMouseRightButtonDown; } + + //Mouse input methods + void rootMouseUp(const GuiEvent &event); + void rootMouseDown(const GuiEvent &event); + void rootMouseMove(const GuiEvent &event); + void rootMouseDragged(const GuiEvent &event); + + void rootRightMouseDown(const GuiEvent &event); + void rootRightMouseUp(const GuiEvent &event); + void rootRightMouseDragged(const GuiEvent &event); + + void rootMouseWheelUp(const GuiEvent &event); + void rootMouseWheelDown(const GuiEvent &event); + + //Keyboard input methods + void tabNext(void); + void tabPrev(void); + void AddAcceleratorKey(GuiControl *ctrl, U32 keyCode, U32 modifier); + + void setFirstResponder(GuiControl *firstResponder); + + //deleted? possibly + //DWORD onMessage(SimObject *sender, DWORD msg); // root handles IDGUI and IDCMD messages +}; + +extern GuiCanvas *Canvas; + +#endif diff --git a/gui/guiChannelVectorCtrl.cc b/gui/guiChannelVectorCtrl.cc new file mode 100644 index 0000000..f441c11 --- /dev/null +++ b/gui/guiChannelVectorCtrl.cc @@ -0,0 +1,300 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiChannelVectorCtrl.h" +#include "GUI/channelVector.h" +#include "GUI/guiScrollCtrl.h" +#include "dgl/dgl.h" + +IMPLEMENT_CONOBJECT(GuiChannelVectorCtrl); + + +//-------------------------------------------------------------------------- +void GuiChannelVectorCtrl::lineInserted(const U32 arg) +{ + AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!"); + + GuiScrollCtrl* pScroll = NULL; + GuiControl* pParent = getParent(); + if (pParent) { + GuiControl* pGrandParent = pParent->getParent(); + if (pGrandParent) + pScroll = dynamic_cast(pGrandParent); + } + + bool scrollToBottom = false; + if (pScroll != NULL) + scrollToBottom = pScroll->getCurrVPos() == 1.0; + + mSpecialMarkers.insert(arg); + createSpecialMarkers(mSpecialMarkers[arg], mMessageVector->getLine(arg).message); + + const ChannelVector::SpecialMarkers &tags = ((ChannelVector *) mMessageVector)->getLineTags(arg); + + if (tags.numSpecials) + { + U32 n = mSpecialMarkers[arg].numSpecials+tags.numSpecials; + SpecialMarkers::Special *s = new SpecialMarkers::Special[n]; + + for (U32 i = 0, j = 0, k = 0; k < n; ++k) + { + if (i < mSpecialMarkers[arg].numSpecials && + (j == tags.numSpecials || + mSpecialMarkers[arg].specials[i].start <= tags.specials[j].start)) + s[k] = mSpecialMarkers[arg].specials[i++]; + else + { + s[k].specialType = tags.specials[j].specialType; + s[k].start = tags.specials[j].start; + s[k].end = tags.specials[j].end; + ++j; + } + } + mSpecialMarkers[arg].numSpecials = n; + delete [] mSpecialMarkers[arg].specials; + mSpecialMarkers[arg].specials = s; + } + + 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; + } + + U32 newHeight = (mProfile->mFont->getHeight() + mLineSpacingPixels) * getMax(numLines, U32(1)); + resize(mBounds.point, Point2I(mBounds.extent.x, newHeight)); + + if (arg == mSpecialMarkers.size() - 1 && scrollToBottom == true) + pScroll->scrollTo(0, 1); +} + + +//-------------------------------------------------------------------------- +void GuiChannelVectorCtrl::onRender(Point2I offset, + const RectI& updateRect, + GuiControl*fr) +{ + GuiControl::onRender(offset, updateRect,fr); + 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; + + dglSetBitmapModulation(lastColor); + 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) { + dglSetBitmapModulation(lastColor); + dglSetTextAnchorColor(mProfile->mFontColor); + strWidth = dglDrawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start], + walkAcross->end - walkAcross->start + 1, mProfile->mFontColors); + dglGetBitmapModulation(&lastColor); + } else { + SpecialMarkers::Special &s = mSpecialMarkers[i].specials[walkAcross->specialReference]; + + dglGetBitmapModulation(&lastColor); + if (s.specialType >= 0) + dglSetBitmapModulation(mSpecialColor); + else + { + U32 colorIndex; + + switch(s.specialType) + { + case ChannelVector::NickTag: + colorIndex = NickColor; + + break; + case ChannelVector::TribeTag: + colorIndex = TribeColor; + + break; + case ChannelVector::ServerTag: + colorIndex = ServerColor; + + break; + } + + dglSetBitmapModulation(mProfile->mFontColors[colorIndex]); + } + dglSetTextAnchorColor(mProfile->mFontColor); + strWidth = dglDrawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start], + walkAcross->end - walkAcross->start + 1); + + // in case we have 2 in a row... + dglSetBitmapModulation(lastColor); + } + + if (walkAcross->specialReference != -1) { + Point2I lineStart = localStart; + Point2I lineEnd = localStart; + SpecialMarkers::Special &s = mSpecialMarkers[i].specials[walkAcross->specialReference]; + + lineStart.y += mProfile->mFont->getBaseline() + 2; + lineEnd.x += strWidth; + lineEnd.y += mProfile->mFont->getBaseline() + 2; + + if (s.specialType >= 0) + dglDrawLine(localToGlobalCoord(lineStart), + localToGlobalCoord(lineEnd), + mSpecialColor); + else + { + U32 colorIndex; + + switch(s.specialType) + { + case ChannelVector::NickTag: + colorIndex = NickColor; + + break; + case ChannelVector::TribeTag: + colorIndex = TribeColor; + + break; + case ChannelVector::ServerTag: + colorIndex = ServerColor; + + break; + } + + dglDrawLine(localToGlobalCoord(lineStart), + localToGlobalCoord(lineEnd), + mProfile->mFontColors[colorIndex]); + } + } + + localStart.x += strWidth; + walkAcross = walkAcross->nextInLine; + } + + currLine++; + pElement = pElement->nextPhysicalLine; + } + } + dglClearBitmapModulation(); + } +} + + +//-------------------------------------------------------------------------- +void GuiChannelVectorCtrl::onMouseUp(const GuiEvent& event) +{ + GuiControl::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 + char type[16]; + const char *content; + + SpecialMarkers& rSpecial = mSpecialMarkers[currSpecialLine]; + S32 specialStart = rSpecial.specials[currSpecialRef].start; + S32 specialEnd = rSpecial.specials[currSpecialRef].end; + + switch (rSpecial.specials[currSpecialRef].specialType) + { + case ChannelVector::TribeTag: + if (currSpecialRef && + rSpecial.specials[currSpecialRef-1].specialType == ChannelVector::NickTag) + { + --currSpecialRef; + specialStart = rSpecial.specials[currSpecialRef].start; + specialEnd = rSpecial.specials[currSpecialRef].end; + } + else + { + ++currSpecialRef; + specialStart = rSpecial.specials[currSpecialRef].start; + specialEnd = rSpecial.specials[currSpecialRef].end; + } + case ChannelVector::NickTag: + dStrcpy(type,"warrior"); + content = ""; + + break; + case ChannelVector::ServerTag: + { + const ChannelVector::SpecialMarkers &tags = ((ChannelVector *) mMessageVector)->getLineTags(currSpecialLine); + + dStrcpy(type,"server"); + for (U32 i = 0; i < tags.numSpecials; ++i) + if (tags.specials[i].start == specialStart) + { + content = tags.specials[i].content; + break; + } + + break; + } + default: + { + dStrcpy(type,"http"); + content = ""; + } + + break; + } + + 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, 4, "urlClickCallback", type, copyURL, content); + + delete [] copyURL; + } + + mMouseDown = false; + mMouseSpecialLine = -1; + mMouseSpecialRef = -1; +} diff --git a/gui/guiChannelVectorCtrl.h b/gui/guiChannelVectorCtrl.h new file mode 100644 index 0000000..6dbca1f --- /dev/null +++ b/gui/guiChannelVectorCtrl.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICHANNELVECTORCTRL_H_ +#define _GUICHANNELVECTORCTRL_H_ + +#ifndef _CHANNELVECTOR_H_ +#include "GUI/channelVector.h" +#endif + +class GuiChannelVectorCtrl : public GuiMessageVectorCtrl +{ + typedef GuiMessageVectorCtrl Parent; + + protected: + enum { + NickColor = 1, + TribeColor = 2, + ServerColor = 3, + }; + + virtual void onMouseUp(const GuiEvent &event); + + virtual void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + virtual void lineInserted(const U32); + + public: + DECLARE_CONOBJECT(GuiChannelVectorCtrl); +}; + +#endif // _H_GUICHANNELVECTORCTRL_ diff --git a/gui/guiChatMenuTreeCtrl.cc b/gui/guiChatMenuTreeCtrl.cc new file mode 100644 index 0000000..4cb3f42 --- /dev/null +++ b/gui/guiChatMenuTreeCtrl.cc @@ -0,0 +1,262 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "GUI/guiChatMenuTreeCtrl.h" +#include "console/consoleTypes.h" +#include "Platform/event.h" + +IMPLEMENT_CONOBJECT(GuiChatMenuTreeCtrl); + +//------------------------------------------------------------------------------ +GuiChatMenuTreeCtrl::GuiChatMenuTreeCtrl() : GuiTreeViewCtrl() +{ + mTexRollover = NULL; + mTexSelected = NULL; + + // Default colors: + mAltFontColor.set( 6, 215, 245 ); + mAltFontColorHL.set( 6, 215, 245 ); + mAltFontColorSE.set( 25, 56, 68 ); +} + + +//------------------------------------------------------------------------------ +void GuiChatMenuTreeCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField( "altFontColor", TypeColorI, Offset( mAltFontColor, GuiChatMenuTreeCtrl ) ); + addField( "altFontColorHL", TypeColorI, Offset( mAltFontColorHL, GuiChatMenuTreeCtrl ) ); + addField( "altFontColorSE", TypeColorI, Offset( mAltFontColorSE, GuiChatMenuTreeCtrl ) ); +} + + +//------------------------------------------------------------------------------ +void GuiChatMenuTreeCtrl::consoleInit() +{ +} + + +//------------------------------------------------------------------------------ +bool GuiChatMenuTreeCtrl::onWake() +{ + if ( !Parent::onWake() ) + return( false ); + + // Set the item height to the height of the bitmaps so we know they + // will connect up right: + mItemHeight = mImageBounds[BmpDunno].extent.y; + + char buf[512]; + dSprintf( buf, sizeof( buf ), "%s_rol.png", mProfile->mBitmapBase ); + mTexRollover = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_act.png", mProfile->mBitmapBase ); + mTexSelected = TextureHandle( buf, BitmapTexture ); + + return( true ); +} + + +//------------------------------------------------------------------------------ +void GuiChatMenuTreeCtrl::onSleep() +{ + mTexRollover = NULL; + mTexSelected = NULL; + Parent::onSleep(); +} + + +//------------------------------------------------------------------------------ +bool GuiChatMenuTreeCtrl::onKeyDown( const GuiEvent& event ) +{ + if ( !mVisible || !mActive || !mAwake ) + return true; + + if ( !mSelectedItem ) + return true; + + Item* item = getItem( mSelectedItem ); + if ( !item ) + return true; + + if ( event.modifier == 0 ) + { + if ( event.keyCode == KEY_RETURN ) + { + if ( mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand ); + return true; + } + + if ( event.keyCode == KEY_DELETE ) + { + // Don't delete the root! + if ( item->mParent ) + { + removeItem( item->mId ); + return true; + } + } + } + + // The Alt key lets you move items around: + if ( event.modifier & SI_ALT ) + { + switch ( event.keyCode ) + { + case KEY_UP: + if ( item->mPrevious ) + { + moveItemUp( item->mId ); + buildVisibleTree(); + } + return true; + + case KEY_DOWN: + if ( item->mNext ) + { + moveItemUp( item->mNext->mId ); + buildVisibleTree(); + } + return true; + + case KEY_LEFT: + if ( item->mParent ) + { + if ( item->mParent->mParent ) + { + // We're movin'... + if ( item->mPrevious ) + item->mPrevious->mNext = item->mNext; + else + item->mParent->mChild = item->mNext; + + if ( item->mNext ) + item->mNext->mPrevious = item->mPrevious; + + // Make the item be its former parent's next sibling: + item->mPrevious = item->mParent; + item->mNext = item->mParent->mNext; + + if ( item->mNext ) + item->mNext->mPrevious = item; + item->mParent->mNext = item; + + item->mParent = item->mParent->mParent; + buildVisibleTree(); + } + } + return true; + + case KEY_RIGHT: + if ( item->mPrevious ) + { + // Make the item the last child of its previous sibling: + if ( dStrcmp( item->mPrevious->mValue, "0" ) == 0 ) + { + item->mPrevious->mNext = item->mNext; + + if ( item->mNext ) + item->mNext->mPrevious = item->mPrevious; + + item->mParent = item->mPrevious; + item->mNext = NULL; + + 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 ) ) + expandItem( item->mParent->mId, true ); + buildVisibleTree(); + } + } + return true; + } + } + + // Not handled, so pass to parent: + return Parent::onKeyDown( event ); +} + + +//------------------------------------------------------------------------------ +void GuiChatMenuTreeCtrl::onRenderCell( Point2I offset, Point2I cell, bool, bool ) +{ + AssertFatal( cell.y < mVisibleItems.size(), "GuiChatMenuTreeCtrl::onRenderCell: invalid cell" ); + Item* item = mVisibleItems[cell.y]; + RectI drawRect( offset, mCellSize ); + U32 bitmap; + dglClearBitmapModulation(); + + // Draw inheritance lines: + 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 ) + dglDrawBitmapSR( mImagesHandle, drawRect.point, mImageBounds[BmpLine] ); + + parent = parent->mParent; + } + + // Draw the bitmap: + drawRect.point.x = offset.x + mTabSize * item->mTabLevel; + + if ( !item->mChild ) + bitmap = item->mNext ? BmpChild : BmpLastChild; + else + { + bitmap = item->mState.test( Item::Expanded ) ? BmpExp : BmpCon; + + if ( item->mParent || item->mPrevious ) + bitmap += ( item->mNext ? 3 : 2 ); + else + bitmap += ( item->mNext ? 1 : 0 ); + } + + if ( ( bitmap >= 0 ) && ( bitmap < mNumImages ) ) + { + dglDrawBitmapSR( mImagesHandle, drawRect.point, mImageBounds[bitmap] ); + + // Draw the rollover glow if applicable: + if ( bitmap > BmpChild && item->mState.test( Item::MouseOverBmp ) ) + dglDrawBitmapSR( mImagesHandle, drawRect.point, mImageBounds[BmpGlow] ); + + drawRect.point.x += ( mImageBounds[bitmap].extent.x + 2 ); + } + + // Draw the rollover/selected bar if applicable: + drawRect.extent.x = mProfile->mFont->getStrWidth( item->mText ) + ( 2 * mTextOffset ); + if ( item->mState.test( Item::Selected ) && mTexSelected ) + dglDrawBitmapStretch( mTexSelected, drawRect ); + else if ( item->mState.test( Item::MouseOverText ) && mTexRollover ) + dglDrawBitmapStretch( mTexRollover, drawRect ); + + drawRect.point.x += mTextOffset; + ColorI fontColor; + if ( dStrcmp( item->mValue, "0" ) ) + fontColor = item->mState.test( Item::Selected ) ? mProfile->mFontColorSEL : ( item->mState.test( Item::MouseOverText ) ? mProfile->mFontColorHL : mProfile->mFontColor ); + else + fontColor = item->mState.test( Item::Selected ) ? mAltFontColorSE : ( item->mState.test( Item::MouseOverText ) ? mAltFontColorHL : mAltFontColor ); + dglSetBitmapModulation( fontColor ); + dglDrawText( mProfile->mFont, drawRect.point, item->mText, mProfile->mFontColors ); +} diff --git a/gui/guiChatMenuTreeCtrl.h b/gui/guiChatMenuTreeCtrl.h new file mode 100644 index 0000000..28b4de9 --- /dev/null +++ b/gui/guiChatMenuTreeCtrl.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICHATMENUTREECTRL_H_ +#define _GUICHATMENUTREECTRL_H_ + +#ifndef _GUITREEVIEWCTRL_H_ +#include "GUI/guiTreeViewCtrl.h" +#endif + +class GuiChatMenuTreeCtrl : public GuiTreeViewCtrl +{ + private: + typedef GuiTreeViewCtrl Parent; + + protected: + enum BmpIndices + { + BmpDunno, + BmpLastChild, + BmpChild, + BmpExp, + BmpExpN, + BmpExpP, + BmpExpPN, + BmpCon, + BmpConN, + BmpConP, + BmpConPN, + BmpLine, + BmpGlow, + }; + + TextureHandle mTexRollover; + TextureHandle mTexSelected; + + ColorI mAltFontColor; + ColorI mAltFontColorHL; + ColorI mAltFontColorSE; + + public: + DECLARE_CONOBJECT(GuiChatMenuTreeCtrl); + GuiChatMenuTreeCtrl(); + + static void consoleInit(); + static void initPersistFields(); + + bool onWake(); + void onSleep(); + + bool onKeyDown( const GuiEvent& event ); + + void onRenderCell( Point2I offset, Point2I cell, bool, bool ); +}; + +#endif // _GUI_CHATMENUTREECTRL_H diff --git a/gui/guiCheckBoxCtrl.cc b/gui/guiCheckBoxCtrl.cc new file mode 100644 index 0000000..703b621 --- /dev/null +++ b/gui/guiCheckBoxCtrl.cc @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiCheckBoxCtrl.h" +#include "console/consoleTypes.h" + +//--------------------------------------------------------------------------- +GuiCheckBoxCtrl::GuiCheckBoxCtrl() +{ + mActive = true; + mBounds.extent.set(140, 30); + mKeyPressed = false; + mStateOn = false; +} + +//--------------------------------------------------------------------------- +void GuiCheckBoxCtrl::consoleInit() +{ +} + +//--------------------------------------------------------------------------- +void GuiCheckBoxCtrl::setScriptValue(const char *value) +{ + mStateOn = dAtob(value); + + // Update the console variable: + if ( mConsoleVariable[0] ) + Con::setBoolVariable( mConsoleVariable, mStateOn ); + + setUpdate(); +} + +//--------------------------------------------------------------------------- +const char *GuiCheckBoxCtrl::getScriptValue() +{ + return mStateOn ? "1" : "0"; +} + +//--------------------------------------------------------------------------- +void GuiCheckBoxCtrl::AcceleratorKeyPress(void) +{ + if (! mActive) return; + + //set the bool + mKeyPressed = true; + + if (mProfile->mTabable) + { + setFirstResponder(); + } + +} + +//--------------------------------------------------------------------------- +void GuiCheckBoxCtrl::AcceleratorKeyRelease(void) +{ + if (! mActive) return; + + //set the bool + mKeyPressed = false; + //perform the action + onAction(); + + //update + setUpdate(); +} + +//--------------------------------------------------------------------------- +bool GuiCheckBoxCtrl::onKeyDown(const GuiEvent &event) +{ + //if the control is a dead end, kill the event + if ((! mVisible) || (! mActive) || (! mAwake)) + return true; + + //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 it's parent + return Parent::onKeyDown(event); +} + +//--------------------------------------------------------------------------- +void GuiCheckBoxCtrl::drawBorder(const RectI &r, const ColorI &color) +{ + Point2I p1, p2; + p1 = r.point; + p2 = r.point; + p2.x += r.extent.x - 1; + p2.y += r.extent.y - 1; + + glColor4ub(color.red, color.green, color.blue, color.alpha); + glBegin(GL_LINE_LOOP); + glVertex2i(p1.x + 2, p1.y); + glVertex2i(p2.x - 2, p1.y); + glVertex2i(p2.x, p1.y + 2); + glVertex2i(p2.x, p2.y - 2); + glVertex2i(p2.x - 2, p2.y); + glVertex2i(p1.x + 2, p2.y); + glVertex2i(p1.x, p2.y - 2); + glVertex2i(p1.x, p1.y + 2); + glEnd(); +} + +//--------------------------------------------------------------------------- +void GuiCheckBoxCtrl::onAction() +{ + mStateOn = mStateOn ? false : true; + + // Update the console variable: + if ( mConsoleVariable[0] ) + Con::setBoolVariable( mConsoleVariable, mStateOn ); + + Parent::onAction(); +} + +//--------------------------------------------------------------------------- +void GuiCheckBoxCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + ColorI backColor = mActive ? mProfile->mFillColor : mProfile->mFillColorNA; + ColorI fontColor = (cursorInControl()) ? mProfile->mFontColorHL : mProfile->mFontColor; + ColorI insideBorderColor = (firstResponder == this) ? mProfile->mBorderColorHL : mProfile->mBorderColor; + + if(mText[0] != NULL) + { + ColorI outsideBorderColor = mProfile->mBorderColor; + + S32 txt_w = mFont->getStrWidth(mText); + + Point2I localStart; + + // align the horizontal + switch (mProfile->mAlignment) + { + case GuiControlProfile::RightJustify: + localStart.x = mBounds.extent.x - txt_w; + break; + case GuiControlProfile::CenterJustify: + localStart.x = (mBounds.extent.x - txt_w) / 2; + break; + default: + // GuiControlProfile::LeftJustify + localStart.x = 0; + break; + } + + // center the vertical + localStart.y = (mBounds.extent.y - (mFont->getHeight() - 2)) / 2; + + // first draw the background + RectI r(offset, mBounds.extent); + dglDrawRectFill(r, backColor); + r.point.x +=2; + r.point.y +=2; + r.extent.x -=4; + r.extent.y -=4; + dglDrawRectFill(r, backColor); + + S32 incVal = mBounds.extent.y - 8; + //draw the check box + glColor3i(insideBorderColor.red, insideBorderColor.green, insideBorderColor.blue); + glBegin(GL_LINE_LOOP); + glVertex2i(r.point.x+3, r.point.y+3); + glVertex2i(r.point.x+incVal, r.point.y+3); + glVertex2i(r.point.x+incVal, r.point.y+incVal); + glVertex2i(r.point.x+3, r.point.y+incVal); + glEnd(); + + if (mStateOn) + { + glColor3i(fontColor.red, fontColor.green, fontColor.blue); + glBegin(GL_LINES); + glVertex2i(r.point.x+3, r.point.y+3); + glVertex2i(r.point.x+incVal, r.point.y+incVal); + glVertex2i(r.point.x+incVal, r.point.y+3); + glVertex2i(r.point.x+3, r.point.y+incVal); + glEnd(); + } + // draw the oustside border + r.point.x -=1; + r.point.y -=1; + r.extent.x +=2; + r.extent.y +=2; + drawBorder(r, outsideBorderColor); + + // finally draw the text + localStart.y -= 2; + localStart.x += 4; + if (mStateOn) + { + localStart.x += 1; + localStart.y += 1; + } + Point2I globalStart = localToGlobalCoord(localStart); + + dglSetBitmapModulation(fontColor); + dglDrawText(mFont, globalStart, mText, mProfile->mFontColors); + } + else + { + RectI r(Point2I(offset.x+3, offset.y+3), Point2I(mBounds.extent.x-3, mBounds.extent.y-3)); + dglDrawRectFill(r, backColor); + + dglDrawRect(r,insideBorderColor); + + if (mStateOn) + { + glColor3i(fontColor.red, fontColor.green, fontColor.blue); + glBegin(GL_LINES); + glVertex2i(r.point.x-1, r.point.y); + glVertex2i(r.point.x-1 + r.extent.x, r.point.y + r.extent.y); + glVertex2i(r.point.x-1 + r.extent.x, r.point.y); + glVertex2i(r.point.x-1, r.point.y + r.extent.y); + glEnd(); + } + } + //render the children + renderChildControls(offset, updateRect, firstResponder); +} +//--------------------------------------------------------------------------- +// EOF // + + diff --git a/gui/guiCheckBoxCtrl.h b/gui/guiCheckBoxCtrl.h new file mode 100644 index 0000000..978d9b5 --- /dev/null +++ b/gui/guiCheckBoxCtrl.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICHECKBOXCTRL_H_ +#define _GUICHECKBOXCTRL_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "GUI/guiTextCtrl.h" +#endif + +class GuiCheckBoxCtrl : public GuiTextCtrl +{ +private: + typedef GuiTextCtrl Parent; + +protected: + bool mStateOn; + bool mKeyPressed; + +public: + DECLARE_CONOBJECT(GuiCheckBoxCtrl); + GuiCheckBoxCtrl(); + static void consoleInit(); + + void AcceleratorKeyPress(void); + void AcceleratorKeyRelease(void); + + bool onKeyDown(const GuiEvent &event); + + // GuiTextCtrl updates mText with the variable value, which is not desired + void onPreRender() {} + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void drawBorder(const RectI &r, const ColorI &color); + void onAction(); + void setScriptValue(const char *value); + const char *getScriptValue(); +}; + +#endif //_GUI_CHECKBOX_CTRL_H diff --git a/gui/guiChunkedBitmapCtrl.cc b/gui/guiChunkedBitmapCtrl.cc new file mode 100644 index 0000000..93de242 --- /dev/null +++ b/gui/guiChunkedBitmapCtrl.cc @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "dgl/gBitmap.h" +#include "GUI/guiControl.h" +#include "dgl/gTexManager.h" +#include "dgl/gChunkedTexManager.h" + +class GuiChunkedBitmapCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +protected: + StringTableEntry mBitmapName; + ChunkedTextureHandle mTexHandle; + bool mUseVariable; + +public: + //creation methods + DECLARE_CONOBJECT(GuiChunkedBitmapCtrl); + GuiChunkedBitmapCtrl(); + static void initPersistFields(); + static void consoleInit(); + + //Parental methods + bool onWake(); + void onSleep(); + + void setBitmap(const char *name); + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); +}; + +IMPLEMENT_CONOBJECT(GuiChunkedBitmapCtrl); + +void GuiChunkedBitmapCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField( "bitmap", TypeString, Offset( mBitmapName, GuiChunkedBitmapCtrl ) ); + addField( "useVariable", TypeBool, Offset( mUseVariable, GuiChunkedBitmapCtrl ) ); +} + +static void cChunkBmpSetBitmap( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cChunkBmpSetBitmap is not a GuiChunkedBitmapCtrl!" ); + GuiChunkedBitmapCtrl* ctrl = static_cast( obj ); + ctrl->setBitmap( argv[2] ); +} + +void GuiChunkedBitmapCtrl::consoleInit() +{ + Con::addCommand( "GuiChunkedBitmapCtrl", "setBitmap", cChunkBmpSetBitmap, "ctrl.setBitmap( name );", 3, 3 ); +} + +GuiChunkedBitmapCtrl::GuiChunkedBitmapCtrl() +{ + mBitmapName = StringTable->insert(""); + mUseVariable = 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 ( mUseVariable ) + mTexHandle = ChunkedTextureHandle( Con::getVariable( mConsoleVariable ) ); + else + mTexHandle = ChunkedTextureHandle( mBitmapName ); + return true; +} + +void GuiChunkedBitmapCtrl::onSleep() +{ + mTexHandle = NULL; + Parent::onSleep(); +} + +void GuiChunkedBitmapCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + if(mTexHandle) + { + U32 widthCount = mTexHandle.getTextureCountWidth(); + U32 heightCount = mTexHandle.getTextureCountHeight(); + if(!widthCount || !heightCount) + return; + + F32 widthScale = F32(mBounds.extent.x) / F32(mTexHandle.getWidth()); + F32 heightScale = F32(mBounds.extent.y) / F32(mTexHandle.getHeight()); + dglSetBitmapModulation(ColorF(1,1,1)); + for(U32 i = 0; i < widthCount; i++) + { + for(U32 j = 0; j < heightCount; j++) + { + TextureHandle 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); + stretchRegion.extent.x = (S32)((i * 256 + t.getWidth() ) * widthScale + offset.x - stretchRegion.point.x); + stretchRegion.extent.y = (S32)((j * 256 + t.getHeight()) * heightScale + offset.y - stretchRegion.point.y); + dglDrawBitmapStretch(t, stretchRegion); + } + } + renderChildControls(offset, updateRect, firstResponder); + } + else + Parent::onRender(offset, updateRect, firstResponder); +} diff --git a/gui/guiConsole.cc b/gui/guiConsole.cc new file mode 100644 index 0000000..5b85147 --- /dev/null +++ b/gui/guiConsole.cc @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "GUI/guiTypes.h" +#include "GUI/guiControl.h" +#include "GUI/guiConsole.h" + +GuiConsole::GuiConsole() +{ + mBounds.extent.set(1, 1); + 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::getLog(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(log[i].mString))); + + return(result + 6); +} + +void GuiConsole::onPreRender() +{ + //see if the size has changed + U32 prevSize = mBounds.extent.y / mCellSize.y; + U32 size; + ConsoleLogEntry *log; + + Con::getLog(log, size); + + if(size != prevSize) + { + //first, find out if the console was scrolled up + bool scrolled = false; + GuiControl *parent = getParent(); + if (mBounds.point.y > parent->mBounds.extent.y - mBounds.extent.y) + scrolled = true; + + //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 + resize(mBounds.point, Point2I(mCellSize.x, mCellSize.y * size)); + + //if the console was not scrolled, make the last entry visible + if (! scrolled) + { + mSelectedCell.set(0, mSize.y - 1); + scrollSelectionVisible(); + } + } +} + +void GuiConsole::onRenderCell(Point2I offset, Point2I cell, bool /*selected*/, bool /*mouseOver*/) +{ + U32 size; + ConsoleLogEntry *log; + + Con::getLog(log, size); + + ConsoleLogEntry &entry = log[cell.y]; + switch (entry.mLevel) + { + case ConsoleLogEntry::Normal: dglSetBitmapModulation(mProfile->mFontColor); break; + case ConsoleLogEntry::Warning: dglSetBitmapModulation(mProfile->mFontColorHL); break; + case ConsoleLogEntry::Error: dglSetBitmapModulation(mProfile->mFontColorNA); break; + } + dglDrawText(mFont, Point2I(offset.x + 3, offset.y), entry.mString, mProfile->mFontColors); +} diff --git a/gui/guiConsole.h b/gui/guiConsole.h new file mode 100644 index 0000000..774daec --- /dev/null +++ b/gui/guiConsole.h @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICONSOLE_H_ +#define _GUICONSOLE_H_ + +#ifndef _GUIARRAYCTRL_H_ +#include "GUI/guiArrayCtrl.h" +#endif + +class GuiConsole : public GuiArrayCtrl +{ + private: + typedef GuiArrayCtrl Parent; + + Resource mFont; + + S32 getMaxWidth(S32 startIndex, S32 endIndex); + + public: + GuiConsole(); + DECLARE_CONOBJECT(GuiConsole); + + bool onWake(); + + void onPreRender(); + void onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver); +}; + +#endif diff --git a/gui/guiConsoleEditCtrl.cc b/gui/guiConsoleEditCtrl.cc new file mode 100644 index 0000000..4b203b5 --- /dev/null +++ b/gui/guiConsoleEditCtrl.cc @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/dgl.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiConsoleEditCtrl.h" + +IMPLEMENT_CONOBJECT(GuiConsoleEditCtrl); + +GuiConsoleEditCtrl::GuiConsoleEditCtrl() +{ + tabBuffer[0] = 0; + baseStart = 0; + baseLen = 0; +} + +void GuiConsoleEditCtrl::handleTab(bool forwardTab) +{ + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + if(dStrcmp(tabBuffer, mText)) + { + dStrcpy(tabBuffer, mText); + // scan back from mCursorPos + S32 p = mCursorPos; + while(p > 0 && mText[p-1] != ' ' && mText[p-1] != '.' && mText[p-1] != '(') + p--; + baseStart = p; + baseLen = mCursorPos-p; + if(mText[p-1] == '.') + { + if(p <= 1) + return; + S32 objLast = --p; + while(p > 0 && mText[p-1] != ' ') + p--; + if(objLast == p) + return; + dStrncpy(buf, mText + p, objLast - p); + buf[objLast - p] = 0; + tabObject = Sim::findObject(buf); + if(!bool(tabObject)) + return; + } + else + tabObject = 0; + } + const char *newText; + mText[mCursorPos] = 0; + + if(bool(tabObject)) + newText = tabObject->tabComplete(mText + baseStart, baseLen, forwardTab); + else + newText = Con::tabComplete(mText + baseStart, baseLen, forwardTab); + if(newText) + { + S32 len = dStrlen(newText); + if(len + baseStart > GuiTextCtrl::MAX_STRING_LENGTH) + len = GuiTextCtrl::MAX_STRING_LENGTH - baseStart; + dStrncpy(mText + baseStart, newText, len); + mText[baseStart + len] = 0; + mCursorPos = baseStart + len; + } + else + { + mText[baseStart + baseLen] = 0; + mCursorPos = baseStart + baseLen; + } + dStrcpy(tabBuffer, mText); +} + +bool GuiConsoleEditCtrl::onKeyDown(const GuiEvent &event) +{ + S32 stringLen = dStrlen(mText); + setUpdate(); + + if (event.keyCode == KEY_TAB) + { + if (event.modifier & SI_SHIFT) + { + handleTab(false); + return true; + } + else + { + handleTab(true); + return true; + } + } + return Parent::onKeyDown(event); +} + diff --git a/gui/guiConsoleEditCtrl.h b/gui/guiConsoleEditCtrl.h new file mode 100644 index 0000000..81789ee --- /dev/null +++ b/gui/guiConsoleEditCtrl.h @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICONSOLEEDITCTRL_H_ +#define _GUICONSOLEEDITCTRL_H_ + +#ifndef _GUITYPES_H_ +#include "GUI/guiTypes.h" +#endif +#ifndef _GUITEXTEDITCTRL_H_ +#include "GUI/guiTextEditCtrl.h" +#endif + +class GuiConsoleEditCtrl : public GuiTextEditCtrl +{ +private: + typedef GuiTextEditCtrl Parent; + + // max string len, must be less then or equal to 255 + SimObjectPtr tabObject; + S32 baseStart; + S32 baseLen; + char tabBuffer[GuiTextCtrl::MAX_STRING_LENGTH + 1]; +public: + GuiConsoleEditCtrl(); + DECLARE_CONOBJECT(GuiConsoleEditCtrl); + + bool onKeyDown(const GuiEvent &event); + void handleTab(bool forwardTab); +}; + +#endif //_GUI_TEXTEDIT_CTRL_H diff --git a/gui/guiConsoleTextCtrl.cc b/gui/guiConsoleTextCtrl.cc new file mode 100644 index 0000000..6706370 --- /dev/null +++ b/gui/guiConsoleTextCtrl.cc @@ -0,0 +1,160 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "Core/color.h" +#include "GUI/guiConsoleTextCtrl.h" +#include "dgl/dgl.h" + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +GuiConsoleTextCtrl::GuiConsoleTextCtrl() +{ + //default fonts + mConsoleExpression = NULL; + mResult = NULL; +} + +void GuiConsoleTextCtrl::consoleInit() +{ +} + +void GuiConsoleTextCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("expression", TypeCaseString, Offset(mConsoleExpression, GuiConsoleTextCtrl)); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +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 = StringTable->insert(txt); + else + mConsoleExpression = NULL; + + //Make sure we have a font + mProfile->incRefCount(); + mFont = mProfile->mFont; + + setUpdate(); + + //decrement the profile referrence + mProfile->decRefCount(); +} + +void GuiConsoleTextCtrl::calcResize() +{ + if (!mResult) + return; + + //resize + if (mProfile->mAutoSizeWidth) + { + if (mProfile->mAutoSizeHeight) + resize(mBounds.point, Point2I(mFont->getStrWidth(mResult) + 4, mFont->getHeight() + 4)); + else + resize(mBounds.point, Point2I(mFont->getStrWidth(mResult) + 4, mBounds.extent.y)); + } + else if (mProfile->mAutoSizeHeight) + { + resize(mBounds.point, Point2I(mBounds.extent.x, mFont->getHeight() + 4)); + } +} + + +void GuiConsoleTextCtrl::onPreRender() +{ + if (mConsoleExpression) + mResult = Con::evaluatef("$temp = %s;", mConsoleExpression); + calcResize(); +} + + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiConsoleTextCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + // draw the background rectangle + RectI r(offset, mBounds.extent); + dglDrawRectFill(r, ColorI(255,255,255)); + + // draw the border + r.extent += r.point; + glColor4ub(0, 0, 0, 0); + glBegin(GL_LINE_LOOP); + glVertex2i(r.point.x, r.point.y); + glVertex2i(r.extent.x-1, r.point.y); + glVertex2i(r.extent.x-1, r.extent.y-1); + glVertex2i(r.point.x, r.extent.y-1); + glEnd(); + + if (mResult) + { + S32 txt_w = mFont->getStrWidth(mResult); + Point2I localStart; + switch (mProfile->mAlignment) + { + case GuiControlProfile::RightJustify: + localStart.set(mBounds.extent.x - txt_w-2, 0); + break; + case GuiControlProfile::CenterJustify: + localStart.set((mBounds.extent.x - txt_w) / 2, 0); + break; + default: + // GuiControlProfile::LeftJustify + localStart.set(2,0); + break; + } + + Point2I globalStart = localToGlobalCoord(localStart); + + //draw the text + dglSetBitmapModulation(mProfile->mFontColor); + dglDrawText(mFont, globalStart, mResult, mProfile->mFontColors); + } + + //render the child controls + renderChildControls(offset, updateRect, firstResponder); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +const char *GuiConsoleTextCtrl::getScriptValue() +{ + return getText(); +} + +void GuiConsoleTextCtrl::setScriptValue(const char *val) +{ + setText(val); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// EOF // diff --git a/gui/guiConsoleTextCtrl.h b/gui/guiConsoleTextCtrl.h new file mode 100644 index 0000000..4fbdaa3 --- /dev/null +++ b/gui/guiConsoleTextCtrl.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICONSOLETEXTCTRL_H_ +#define _GUICONSOLETEXTCTRL_H_ + +#ifndef _GFONT_H_ +#include "dgl/gFont.h" +#endif +#ifndef _GUITYPES_H_ +#include "GUI/guiTypes.h" +#endif +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +class GuiConsoleTextCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +public: + enum Constants { MAX_STRING_LENGTH = 255 }; + + +protected: + const char *mConsoleExpression; + const char *mResult; + Resource mFont; + +public: + + //creation methods + DECLARE_CONOBJECT(GuiConsoleTextCtrl); + GuiConsoleTextCtrl(); + static void consoleInit(); + static void initPersistFields(); + + //Parental methods + bool onWake(); + void onSleep(); + + //text methods + virtual void setText(const char *txt = NULL); + const char *getText() { return mConsoleExpression; } + + //rendering methods + void calcResize(); + void onPreRender(); // do special pre render processing + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + + //Console methods + const char *getScriptValue(); + void setScriptValue(const char *value); +}; + +#endif //_GUI_TEXT_CONTROL_H_ diff --git a/gui/guiControl.cc b/gui/guiControl.cc new file mode 100644 index 0000000..f790fc1 --- /dev/null +++ b/gui/guiControl.cc @@ -0,0 +1,1276 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "Platform/event.h" +#include "dgl/gBitmap.h" +#include "dgl/dgl.h" +#include "Sim/actionMap.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiControl.h" +#include "console/consoleInternal.h" +#include "console/objectTypes.h" + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +//used to locate the next/prev responder when tab is pressed +GuiControl *GuiControl::gPrevResponder = NULL; +GuiControl *GuiControl::gCurResponder = NULL; + +GuiControl::GuiControl() +{ + mLayer = 0; + mBounds.set(0, 0, 64, 64); + mMinExtent.set(8, 8); + mHelpTag = 0; + mHelpText = StringTable->insert(""); + + mProfile = NULL; + + mConsoleVariable = StringTable->insert(""); + mConsoleCommand = StringTable->insert(""); + mAltConsoleCommand = StringTable->insert(""); + mAcceleratorKey = StringTable->insert(""); + + mFirstResponder = NULL; + + mVisible = true; + mActive = false; + mAwake = false; + + mHorizSizing = horizResizeRight; + mVertSizing = vertResizeBottom; + + mTypeMask |= GuiControlObjectType; +} + +GuiControl::~GuiControl() +{ +} + +bool GuiControl::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::getGuiGroup()->addObject(this); + Con::executef(this, 1, "onAdd"); + return true; +} + + +static EnumTable::Enums horzEnums[] = +{ + { GuiControl::horizResizeRight, "right" }, + { GuiControl::horizResizeWidth, "width" }, + { GuiControl::horizResizeLeft, "left" }, + { GuiControl::horizResizeCenter, "center" }, + { GuiControl::horizResizeRelative, "relative" } +}; +static EnumTable gHorizSizingTable(5, &horzEnums[0]); + +static EnumTable::Enums vertEnums[] = +{ + { GuiControl::vertResizeBottom, "bottom" }, + { GuiControl::vertResizeHeight, "height" }, + { GuiControl::vertResizeTop, "top" }, + { GuiControl::vertResizeCenter, "center" }, + { GuiControl::vertResizeRelative, "relative" } +}; +static EnumTable gVertSizingTable(5, &vertEnums[0]); + +void GuiControl::initPersistFields() +{ + Parent::initPersistFields(); + addField("profile", TypeGuiProfile, Offset(mProfile, GuiControl)); + 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("visible", TypeBool, Offset(mVisible, GuiControl)); + addDepricatedField("modal"); + addDepricatedField("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)); + + addField("helpTag", TypeS32, Offset(mHelpTag, GuiControl)); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiControl::setHelp(S32 helpTag) +{ + mHelpText = StringTable->insert(""); + mHelpTag = helpTag; +} + +void GuiControl::setHelp(const char *text) +{ + if (! text) return; + + mHelpText = StringTable->insert(text); + mHelpTag = 0; +} + +void GuiControl::addObject(SimObject *object) +{ + GuiControl *ctrl = dynamic_cast(object); + if(!ctrl) + { + AssertWarn(0, "GuiControl::addObject: attempted to add NON GuiControl to set"); + return; + } + + if(object->getGroup() == this) + return; + + Parent::addObject(object); + + AssertFatal(!ctrl->isAwake(), "GuiControl::addObject: object is already awake before add"); + if(mAwake) + ctrl->awaken(); +} + +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(); + Parent::removeObject(object); +} + +GuiControl *GuiControl::getParent() +{ + GuiControl *parent = NULL; + SimObject *obj = getGroup(); + if (obj && (obj->getType() & GuiControlObjectType)) + parent = static_cast(obj); + return parent; +} + +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() +{ + if(mAwake) + { + onSleep(); // release all our resources. + mAwake = true; + } +} + +void GuiControl::inspectPostApply() +{ + if(mAwake) + { + mAwake = false; + onWake(); + } +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +Point2I GuiControl::localToGlobalCoord(const Point2I &src) +{ + Point2I ret = src; + ret += mBounds.point; + GuiControl *walk = getParent(); + while(walk) + { + ret += walk->getPosition(); + walk = walk->getParent(); + } + return ret; +} + +Point2I GuiControl::globalToLocalCoord(const Point2I &src) +{ + Point2I ret = src; + ret -= mBounds.point; + GuiControl *walk = getParent(); + while(walk) + { + ret -= walk->getPosition(); + walk = walk->getParent(); + } + return ret; +} + +//---------------------------------------------------------------- + +void GuiControl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + //call set update both before and after + setUpdate(); + Point2I actualNewExtent = Point2I(getMax(mMinExtent.x, newExtent.x), + getMax(mMinExtent.y, newExtent.y)); + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + ctrl->parentResized(mBounds.extent, actualNewExtent); + } + mBounds.set(newPosition, actualNewExtent); + + GuiControl *parent = getParent(); + if (parent) + parent->childResized(this); + setUpdate(); +} + +void GuiControl::childResized(GuiControl *child) +{ + child; + // default to do nothing... +} + +void GuiControl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent) +{ + Point2I newPosition = getPosition(); + Point2I newExtent = getExtent(); + + S32 deltaX = newParentExtent.x - oldParentExtent.x; + S32 deltaY = newParentExtent.y - oldParentExtent.y; + + if (mHorizSizing == horizResizeCenter) + newPosition.x = (newParentExtent.x - mBounds.extent.x) >> 1; + else if (mHorizSizing == horizResizeWidth) + newExtent.x += deltaX; + else if (mHorizSizing == horizResizeLeft) + newPosition.x += deltaX; + else if (mHorizSizing == horizResizeRelative && oldParentExtent.x != 0) + { + S32 newLeft = (newPosition.x * newParentExtent.x) / oldParentExtent.x; + S32 newRight = ((newPosition.x + newExtent.x) * newParentExtent.x) / oldParentExtent.x; + + newPosition.x = newLeft; + newExtent.x = newRight - newLeft; + } + + if (mVertSizing == vertResizeCenter) + newPosition.y = (newParentExtent.y - mBounds.extent.y) >> 1; + else if (mVertSizing == vertResizeHeight) + newExtent.y += deltaY; + else if (mVertSizing == vertResizeTop) + newPosition.y += deltaY; + else if(mVertSizing == vertResizeRelative && oldParentExtent.y != 0) + { + + S32 newTop = (newPosition.y * newParentExtent.y) / oldParentExtent.y; + S32 newBottom = ((newPosition.y + newExtent.y) * newParentExtent.y) / oldParentExtent.y; + + newPosition.y = newTop; + newExtent.y = newBottom - newTop; + } + resize(newPosition, newExtent); +} + +//---------------------------------------------------------------- + +void GuiControl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + RectI ctrlRect(offset, mBounds.extent); + + //if opaque, fill the update rect with the fill color + if (mProfile->mOpaque) + dglDrawRectFill(ctrlRect, mProfile->mFillColor); + + //if there's a boarder, draw the boarder + if (mProfile->mBorder) + dglDrawRect(ctrlRect, mProfile->mBorderColor); + + renderChildControls(offset, updateRect, firstResponder); +} + +void GuiControl::renderChildControls(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + // 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()); + + if (childClip.intersect(clipRect)) + { + dglSetClipRect(childClip); + glDisable(GL_CULL_FACE); + ctrl->onRender(childPosition, childClip, firstResponder); + } + } + } +} + +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), mBounds.extent); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +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(); +} + +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(); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +bool GuiControl::onWake() +{ + AssertFatal(!mAwake, "GuiControl::onWake: control is already awake"); + if(mAwake) + return false; + + //make sure we have a profile + if (! mProfile) + { + SimObject *obj = Sim::findObject("GuiDefaultProfile"); + if (obj) + mProfile = dynamic_cast(obj); + AssertFatal(mProfile, avar("GuiControl: %s created with no profile.", getName())); + } + + //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(); + + //see if we should force this control to be the first responder + if (mProfile->mTabable && mProfile->mCanKeyFocus) + setFirstResponder(); + + //increment the profile + mProfile->incRefCount(); + return true; +} + +void GuiControl::onSleep() +{ + AssertFatal(mAwake, "GuiControl::onSleep: control is not awake"); + if(!mAwake) + return; + + //decrement the profile referrence + mProfile->decRefCount(); + clearFirstResponder(); + mouseUnlock(); + + //set the flag + mAwake = false; +} + +void GuiControl::setControlProfile(GuiControlProfile *prof) +{ + AssertFatal(prof, "GuiControl::setControlProfile: invalid profile"); + if(prof == mProfile) + return; + if(mAwake) + mProfile->decRefCount(); + mProfile = prof; + if(mAwake) + mProfile->incRefCount(); + +} + +void GuiControl::onPreRender() +{ + // do nothing. +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +static void cGuiControlSetValue(SimObject *obj, S32, const char **argv) +{ + GuiControl *ctrl = static_cast(obj); + ctrl->setScriptValue(argv[2]); +} + +static const char* cGuiControlGetValue(SimObject *obj, S32, const char **) +{ + GuiControl *ctrl = static_cast(obj); + return ctrl->getScriptValue(); +} + +static void cGuiControlSetActive(SimObject *obj, S32, const char **argv) +{ + GuiControl *ctrl = static_cast(obj); + ctrl->setActive(dAtob(argv[2])); +} + +static bool cGuiControlIsActive(SimObject *obj, S32, const char **) +{ + GuiControl *ctrl = static_cast(obj); + return ctrl->isActive(); +} + +static void cGuiControlSetVisible(SimObject *obj, S32, const char **argv) +{ + GuiControl *ctrl = static_cast(obj); + ctrl->setVisible(dAtob(argv[2])); +} + +static void cGuiControlMakeFirstResponder(SimObject *obj, S32, const char **argv) +{ + GuiControl *ctrl = static_cast(obj); + ctrl->makeFirstResponder(dAtob(argv[2])); +} + +static bool cGuiControlIsVisible(SimObject *obj, S32, const char **) +{ + GuiControl *ctrl = static_cast(obj); + return ctrl->isVisible(); +} + +static bool cGuiControlIsAwake(SimObject *obj, S32, const char **) +{ + GuiControl *ctrl = static_cast(obj); + return ctrl->isAwake(); +} + +static void cGuiControlSetProfile(SimObject *obj, S32, const char ** argv) +{ + GuiControl * ctrl = static_cast(obj); + GuiControlProfile * profile = dynamic_cast(Sim::findObject(argv[2])); + if(profile) + ctrl->setControlProfile(profile); +} + +static void cGuiControlResize(SimObject *obj, S32, const char **argv) +{ + GuiControl *ctrl = static_cast(obj); + Point2I newPos(dAtoi(argv[2]), dAtoi(argv[3])); + Point2I newExt(dAtoi(argv[4]), dAtoi(argv[5])); + ctrl->resize(newPos, newExt); +} + +static const char *cGuiControlGetPosition(SimObject *obj, S32, const char **) +{ + GuiControl *ctrl = static_cast(obj); + char *retBuffer = Con::getReturnBuffer(64); + const Point2I &pos = ctrl->getPosition(); + dSprintf(retBuffer, 64, "%d %d", pos.x, pos.y); + return retBuffer; +} + +static const char *cGuiControlGetExtent(SimObject *obj, S32, const char **) +{ + GuiControl *ctrl = static_cast(obj); + char *retBuffer = Con::getReturnBuffer(64); + const Point2I &ext = ctrl->getExtent(); + dSprintf(retBuffer, 64, "%d %d", ext.x, ext.y); + return retBuffer; +} + +static const char *cGuiControlGetMinExtent(SimObject *obj, S32, const char **) +{ + GuiControl *ctrl = static_cast(obj); + char *retBuffer = Con::getReturnBuffer(64); + const Point2I &minExt = ctrl->getMinExtent(); + dSprintf(retBuffer, 64, "%d %d", minExt.x, minExt.y); + return retBuffer; +} + +void GuiControl::consoleInit() +{ + Con::addCommand("GuiControl", "getPosition", cGuiControlGetPosition, "ctrl.getPosition()", 2, 2); + Con::addCommand("GuiControl", "getExtent", cGuiControlGetExtent, "ctrl.getExtent()", 2, 2); + Con::addCommand("GuiControl", "getMinExtent", cGuiControlGetMinExtent, "ctrl.getMinExtent()", 2, 2); + Con::addCommand("GuiControl", "resize", cGuiControlResize, "ctrl.resize(x,y,w,h)", 6, 6); + Con::addCommand("GuiControl", "setValue", cGuiControlSetValue, "ctrl.setValue(value)", 3, 3); + Con::addCommand("GuiControl", "getValue", cGuiControlGetValue, "ctrl.getValue()", 2, 2); + Con::addCommand("GuiControl", "setActive", cGuiControlSetActive, "ctrl.setActive(value)", 3, 3); + Con::addCommand("GuiControl", "isActive", cGuiControlIsActive, "ctrl.isActive()", 2, 2); + Con::addCommand("GuiControl", "setVisible", cGuiControlSetVisible, "ctrl.setVisible(value)", 3, 3); + Con::addCommand("GuiControl", "isVisible", cGuiControlIsVisible, "ctrl.isVisible()", 2, 2); + Con::addCommand("GuiControl", "isAwake", cGuiControlIsAwake, "ctrl.isAwake()", 2, 2); + + Con::addCommand("GuiControl", "setProfile", cGuiControlSetProfile, "ctrl.setProfile(profileI)", 3, 3); + Con::addCommand("GuiControl", "makeFirstResponder", cGuiControlMakeFirstResponder, "ctrl.makeFirstResponder(value)", 3, 3); +} + +void GuiControl::onRemove() +{ + Con::executef(this, 1, "onRemove"); + clearFirstResponder(); + Parent::onRemove(); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +const char *GuiControl::getScriptValue() +{ + return NULL; +} + +void GuiControl::setScriptValue(const char *value) +{ + value; +} + +void GuiControl::setConsoleVariable(const char *variable) +{ + if (variable) + { + mConsoleVariable = StringTable->insert(variable); + } + else + { + mConsoleVariable = StringTable->insert(""); + } +} + +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 offset = localToGlobalCoord(Point2I(0, 0)); + if (pt.x >= offset.x && pt.y >= offset.y && + pt.x < offset.x + mBounds.extent.x && pt.y < offset.y + mBounds.extent.y) + { + return true; + } + else + { + return false; + } +} + +bool GuiControl::pointInControl(const Point2I& parentCoordPoint) +{ + S32 xt = parentCoordPoint.x - mBounds.point.x; + S32 yt = parentCoordPoint.y - mBounds.point.y; + return xt >= 0 && yt >= 0 && xt < mBounds.extent.x && yt < mBounds.extent.y; +} + +GuiControl* GuiControl::findHitControl(const Point2I &pt, S32 initialLayer) +{ + Point2I ptemp = pt; + 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->pointInControl(ptemp)) + { + ptemp.x -= ctrl->mBounds.point.x; + ptemp.y -= ctrl->mBounds.point.y; + return ctrl->findHitControl(ptemp); + } + } + return this; +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +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 InputEvent &) +{ + // Do nothing by default... + return( false ); +} + +void GuiControl::onMouseUp(const GuiEvent &) +{ + if (! mActive) return; + + mouseUnlock(); + + setUpdate(); + + //if we released the mouse within this control, perform the action + if (cursorInControl()) + { + onAction(); + } +} + +void GuiControl::onMouseDown(const GuiEvent &) +{ + if ((! mActive) || (! mVisible) || (! mAwake)) return; + + if (mProfile->mCanKeyFocus) + setFirstResponder(); + + //lock the mouse + mouseLock(); + + //update + setUpdate(); +} + +void GuiControl::onMouseMove(const GuiEvent &) +{ +} + +void GuiControl::onMouseDragged(const GuiEvent &) +{ +} + +void GuiControl::onMouseEnter(const GuiEvent &) +{ +} + +void GuiControl::onMouseLeave(const GuiEvent &) +{ +} + +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 &) +{ +} + +GuiControl* GuiControl::findFirstTabable() +{ + 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->mTabable && mAwake && mVisible ) ? this : NULL; +} + +GuiControl* GuiControl::findLastTabable(bool firstCall) +{ + //if this is the first call, clear the global + if (firstCall) + gPrevResponder = NULL; + + //if this control is tabable, set the global + if (mProfile->mTabable) + gPrevResponder = 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 = gPrevResponder; + return gPrevResponder; +} + +GuiControl *GuiControl::findNextTabable(GuiControl *curResponder, bool firstCall) +{ + //if this is the first call, clear the global + if (firstCall) + gCurResponder = NULL; + + //first find the current responder + if (curResponder == this) + gCurResponder = this; + + //if the first responder has been found, return the very next *tabable* control + else if ( gCurResponder && 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) +{ + if (firstCall) + gPrevResponder = NULL; + + //if this is the current reponder, return the previous one + if (curResponder == this) + return gPrevResponder; + + //else if this is a responder, store it in case the next found is the current responder + else if ( mProfile->mTabable && mAwake && mVisible && mActive ) + gPrevResponder = 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(); +} + +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; +} + +void GuiControl::setFirstResponder( GuiControl* firstResponder ) +{ + if ( firstResponder && firstResponder->mProfile->mCanKeyFocus ) + mFirstResponder = firstResponder; + + GuiControl *parent = getParent(); + if ( parent ) + parent->setFirstResponder( firstResponder ); +} + +void GuiControl::setFirstResponder() +{ + if ( mAwake && mVisible ) + { + GuiControl *parent = getParent(); + if (mProfile->mCanKeyFocus && parent) + parent->setFirstResponder(this); + // Since many controls have visual cues when they are the firstResponder... + setUpdate(); + } +} + +void GuiControl::clearFirstResponder() +{ + GuiControl *parent = this; + while((parent = parent->getParent()) != NULL) + { + if(parent->mFirstResponder == this) + parent->mFirstResponder = NULL; + else + break; + } +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +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, accelEvent.eventCode, accelEvent.flags); +} + +void GuiControl::AcceleratorKeyPress() +{ + onAction(); +} + +void GuiControl::AcceleratorKeyRelease() +{ + //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[0]) + { + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", getId()); + Con::setVariable("$ThisControl", buf); + Con::evaluate(mConsoleCommand, false); + } + else + Con::executef(this, 1, "onAction"); +} + +void GuiControl::onMessage(GuiControl *sender, S32 msg) +{ + sender; + 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() +{ +} + +void GuiControl::onDialogPop() +{ +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +bool GuiControl::createBitmapArray(GBitmap *bmp, RectI *boundsArray, S32 numStates, S32 numBitmaps) +{ + //get the separator color + ColorI sepColor; + if ( !bmp->getColor( 0, 0, sepColor ) ) + { + AssertFatal( false, "createBitmapArray - failed to get sepearator color!" ); + return false; + } + + //now loop through all the scroll pieces, and find the bounding rectangle for each piece in each state + U32 bmpYOffset = 0; + for ( S32 i = 0; i < numBitmaps; i++ ) + { + ColorI cmpColor; + U32 bmpWidth; + U32 bmpHeight; + for ( bmpWidth = 0; bmpWidth < bmp->getWidth(); bmpWidth++ ) + { + if ( !bmp->getColor( bmpWidth, bmpYOffset, cmpColor ) ) + { + AssertFatal( false, avar( "createBitmapArray - getColor failed in width loop on (%d, %d)!\nBitmap index = %d", bmpWidth, bmpYOffset, i ) ); + return false; + } + if ( cmpColor != sepColor ) + break; + } + bmpYOffset += 1; + + for ( bmpHeight = 0; bmpHeight + bmpYOffset < bmp->getHeight(); bmpHeight++ ) + { + if ( !bmp->getColor( 0, bmpHeight + bmpYOffset, cmpColor ) ) + { + AssertFatal( false, avar( "createBitmapArray - getColor failed in height loop on (0, %d)!\nBitmap index = %d", ( bmpHeight + bmpYOffset ), i ) ); + return false; + } + if (cmpColor == sepColor) + break; + } + //set the bounding rectangle for each piece + for ( S32 j = 0; j < numStates; j++ ) + { + boundsArray[numStates * i + j].set( j * bmpWidth, bmpYOffset, bmpWidth, bmpHeight ); + boundsArray[numStates * i + j].point += Point2I( 1, 1 ); + boundsArray[numStates * i + j].extent -= Point2I( 2, 2 ); + + //make sure we don't go past the end + if ( j * bmpWidth + bmpWidth > bmp->getWidth() + 1 ) + { + AssertFatal( false, avar( "createBitmapArray - column widths don't match up in row %d!", i ) ); + return false; + } + } + + //update the offset + bmpYOffset += bmpHeight; + } + + //make sure we didn't go past the end + if ( bmpYOffset > bmp->getHeight() + 1 ) + { + AssertFatal( false, "createBitmapArray - y offset exceeded bitmap height!" ); + return false; + } + + return true; +} + +//------------------------------------------------------------------------------ +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 renderRaisedBox(const Point2I &offset, const Point2I &extent, const ColorI &color) +{ + ColorI light, dark; + light.interpolate(ColorI(255,255,255), color, 0.70); + dark.interpolate( ColorI(255,255,255), color, 1.3); + + // fill + dglDrawRectFill( offset, Point2I(extent.x+1, extent.y+1), color ); + + // hilight + dglDrawLine(offset.x, offset.y, extent.x, offset.y, light); + dglDrawLine(offset.x, offset.y, offset.x, extent.y, light); + + // shadow + dglDrawLine(offset.x+1, extent.y-1, extent.x, extent.y-1, dark); + dglDrawLine(extent.x-1, offset.y+1, extent.x-1, extent.y-1, dark); + + // edge + dglDrawLine(offset.x, extent.y, extent.x+1, extent.y, ColorI(0,0,0)); + dglDrawLine(extent.x, offset.y, extent.x, extent.y, ColorI(0,0,0)); +} + + +void renderLoweredBox(const Point2I &offset, const Point2I &extent, const ColorI &color) +{ + ColorI light, dark; + light.interpolate(ColorI(255,255,255), color, 0.70); + dark.interpolate( ColorI(255,255,255), color, 1.3); + + // fill + dglDrawRectFill( offset, Point2I(extent.x+1, extent.y+1), color ); + + // edge + dglDrawLine(offset.x, offset.y, extent.x, offset.y, ColorI(0,0,0)); + dglDrawLine(offset.x, offset.y, offset.x, extent.y, ColorI(0,0,0)); + + // shadow + dglDrawLine(offset.x+1, offset.y+1, extent.x-1, offset.y+1, dark); + dglDrawLine(offset.x+1, offset.y+1, offset.x+1, extent.y-1, dark); + + // hilight + dglDrawLine(offset.x, extent.y, extent.x+1, extent.y, light); + dglDrawLine(extent.x, offset.y, extent.x, extent.y, light); +} diff --git a/gui/guiControl.h b/gui/guiControl.h new file mode 100644 index 0000000..521476c --- /dev/null +++ b/gui/guiControl.h @@ -0,0 +1,243 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICONTROL_H_ +#define _GUICONTROL_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.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/guiTypes.h" +#endif +#ifndef _EVENT_H_ +#include "Platform/event.h" +#endif + +class GuiCanvas; + +class GuiControl : public SimGroup +{ + +private: + typedef SimGroup Parent; + + +public: + GuiControlProfile *mProfile; + + bool mVisible; + bool mActive; + bool mAwake; + bool mSetFirstResponder; + + S32 mLayer; + RectI mBounds; + Point2I mMinExtent; + + //keyboard input + GuiControl *mFirstResponder; + static GuiControl *gPrevResponder; + static GuiControl *gCurResponder; + + 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 + }; + 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 + }; + +protected: + + S32 mHorizSizing; + S32 mVertSizing; + + StringTableEntry mConsoleVariable; + StringTableEntry mConsoleCommand; + StringTableEntry mAltConsoleCommand; + + StringTableEntry mAcceleratorKey; + + S32 mHelpTag; + StringTableEntry mHelpText; + + // console variable and command methods + void setVariable(const char *value); + void setIntVariable(S32 value); + void setFloatVariable(F32 value); + + const char* getVariable(); + S32 getIntVariable(); + F32 getFloatVariable(); + +public: + void setConsoleVariable(const char *variable); + void setConsoleCommand(const char *newCmd); + const char * getConsoleCommand(); + void setSizing(S32 horz, S32 vert); + void inspectPreApply(); + void inspectPostApply(); + +public: + static GuiControl* find(const char *name); + + DECLARE_CONOBJECT(GuiControl); + GuiControl(); + virtual ~GuiControl(); + bool onAdd(); + static void initPersistFields(); + static void consoleInit(); + + const Point2I& getPosition() { return mBounds.point; } + const Point2I& getExtent() { return mBounds.extent; } + const Point2I& getMinExtent() { return mMinExtent; } + + virtual void setVisible(bool value); + inline bool isVisible() { return mVisible; } + virtual void makeFirstResponder(bool value); + + void setActive(bool value); + bool isActive() { return mActive; } + + bool isAwake() + { + return( mAwake ); + } + + //tag, mouseTag and helpTag related calls + virtual void setHelp(S32 helpTag); + virtual void setHelp(const char *); + virtual S32 getHelpTag(F32) { return mHelpTag; } + virtual const char *getHelpText(F32) { return mHelpText; } + virtual GuiCursor *getCursor() {return NULL; } + + //methods for organizing the children + void addObject(SimObject*); + void removeObject(SimObject*); + GuiControl *getParent(); + GuiCanvas *getRoot(); + + //parents//children coordinate and sizing methods + Point2I localToGlobalCoord(const Point2I &src); + Point2I globalToLocalCoord(const Point2I &src); + + virtual void resize(const Point2I &newPosition, const Point2I &newExtent); + virtual void childResized(GuiControl *child); + virtual void parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent); + + //rendering related methods + virtual void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void renderChildControls(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void setUpdateRegion(Point2I pos, Point2I ext); + void setUpdate(); + + //child hierarchy calls + void awaken(); // called when this control and it's children have been wired up. + void sleep(); // called when this control are no longer + void preRender(); //prerender it and all it's children + + //parent hierarchy calls - should call Parent::..(); + virtual bool onWake(); + virtual void onSleep(); //mostly resource stuff + virtual void onPreRender(); // do special pre render processing + virtual void onRemove(); + + //console related methods + virtual const char *getScriptValue(); + virtual void setScriptValue(const char *value); + + //mouse/keyboard input related methods + virtual bool pointInControl(const Point2I& parentCoordPoint); + bool cursorInControl(); + virtual GuiControl* findHitControl(const Point2I &pt, S32 initialLayer = -1); + + void mouseLock(GuiControl *lockingControl); + void mouseLock(); + void mouseUnlock(); + + // General input handler -- for in-GUI control mapping: + virtual bool onInputEvent(const InputEvent &event); + + 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 GuiControl* findFirstTabable(); + virtual GuiControl* findLastTabable(bool firstCall = true); + virtual GuiControl* findPrevTabable(GuiControl *curResponder, bool firstCall = true); + virtual GuiControl* findNextTabable(GuiControl *curResponder, bool firstCall = true); + virtual bool ControlIsChild(GuiControl *child); + virtual void setFirstResponder(GuiControl *firstResponder); + void setFirstResponder(); + void clearFirstResponder(); + GuiControl *getFirstResponder() { return mFirstResponder; } + virtual void onLoseFirstResponder(); + + void AddAcceleratorKey(); + void BuildAcceleratorMap(); + virtual void AcceleratorKeyPress(); + virtual void AcceleratorKeyRelease(); + virtual bool onKeyDown(const GuiEvent &event); + virtual bool onKeyUp(const GuiEvent &event); + virtual bool onKeyRepeat(const GuiEvent &event); + + //misc + void setControlProfile(GuiControlProfile *prof); + + //the action + virtual void onAction(); + + //peer messaging + void messageSiblings(S32 message); //send a message to all siblings + virtual void onMessage(GuiControl *sender, S32 msg); //receive a message from another control + + //functions called by the canvas + virtual void onDialogPush(); + virtual void onDialogPop(); + + //this will move into the graphics library at some poing + bool createBitmapArray(GBitmap *bmp, RectI *boundsArray, S32 numStates, S32 numBitmaps); +}; + + +// default renderer helper functions +void renderRaisedBox(const Point2I &offset, const Point2I &extent, const ColorI &color); +void renderLoweredBox(const Point2I &offset, const Point2I &extent, const ColorI &color); + +#endif diff --git a/gui/guiControlListPopup.cc b/gui/guiControlListPopup.cc new file mode 100644 index 0000000..453d049 --- /dev/null +++ b/gui/guiControlListPopup.cc @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiPopUpCtrl.h" + +class GuiControlListPopUp : public GuiPopUpMenuCtrl +{ + typedef GuiPopUpMenuCtrl Parent; +public: + bool onAdd(); + static void consoleInit(); + + DECLARE_CONOBJECT(GuiControlListPopUp); +}; + +IMPLEMENT_CONOBJECT(GuiControlListPopUp); + +void GuiControlListPopUp::consoleInit() +{ +} + +bool GuiControlListPopUp::onAdd() +{ + if(!Parent::onAdd()) + return false; + clear(); + + for(AbstractClassRep *rep = AbstractClassRep::getClassList(); rep; rep = rep->getNextClass()) + { + ConsoleObject *obj = rep->create(); + if(obj && dynamic_cast(obj)) + addEntry(rep->getClassName(), 0); + delete obj; + } + return true; +} diff --git a/gui/guiDebugger.cc b/gui/guiDebugger.cc new file mode 100644 index 0000000..dad87f1 --- /dev/null +++ b/gui/guiDebugger.cc @@ -0,0 +1,1462 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiDebugger.h" +#include "Core/stream.h" + +static const char* itoa(S32 i) +{ + static char buf[32]; + dSprintf(buf, sizeof(buf), "%d", i); + return buf; +} + +static const char* itoa2(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'; + S32 mFindLineNumber = -1; + + mSize.set(1, 0); +} + +static void cDbgFileViewSetCurrentLine(SimObject *obj, S32, const char **argv) +{ + DbgFileView *dbgCtrl = static_cast(obj); + dbgCtrl->setCurrentLine(dAtoi(argv[2]), dAtob(argv[3])); +} + +static const char* cDbgFileViewGetCurrentLine(SimObject *obj, S32, const char **) +{ + DbgFileView *dbgCtrl = static_cast(obj); + S32 lineNum; + const char *file = dbgCtrl->getCurrentLine(lineNum); + char* ret = Con::getReturnBuffer(256); + dSprintf(ret, sizeof(ret), "%s\t%d", file, lineNum); + return ret; +} + +static bool cDbgFileViewOpen(SimObject *obj, S32, const char **argv) +{ + DbgFileView *dbgCtrl = static_cast(obj); + return dbgCtrl->openFile(argv[2]); +} + +static void cDbgFileClearBreakPositions(SimObject *obj, S32, const char **) +{ + DbgFileView *dbgCtrl = static_cast(obj); + dbgCtrl->clearBreakPositions(); +} + +static void cDbgFileSetBreakPosition(SimObject *obj, S32, const char **argv) +{ + DbgFileView *dbgCtrl = static_cast(obj); + dbgCtrl->setBreakPosition(dAtoi(argv[2])); +} + +static void cDbgFileSetBreak(SimObject *obj, S32, const char **argv) +{ + DbgFileView *dbgCtrl = static_cast(obj); + dbgCtrl->setBreakPointStatus(dAtoi(argv[2]), true); +} + +static void cDbgFileRemoveBreak(SimObject *obj, S32, const char **argv) +{ + DbgFileView *dbgCtrl = static_cast(obj); + dbgCtrl->setBreakPointStatus(dAtoi(argv[2]), false); +} + +static bool cDbgFindString(SimObject *obj, S32, const char **argv) +{ + DbgFileView *dbgCtrl = static_cast(obj); + return dbgCtrl->findString(argv[2]); +} + +void DbgFileView::consoleInit() +{ + Con::addCommand("DbgFileView", "open", cDbgFileViewOpen, "fileView.open(file)", 3, 3); + Con::addCommand("DbgFileView", "setCurrentLine", cDbgFileViewSetCurrentLine,"fileView.setCurrentLine(line, displayLine)", 4, 4); + Con::addCommand("DbgFileView", "getCurrentLine", cDbgFileViewGetCurrentLine,"fileView.getCurrentLine()", 2, 2); + Con::addCommand("DbgFileView", "clearBreakPositions",cDbgFileClearBreakPositions,"fileView.clearBreakPositions()", 2, 2); + Con::addCommand("DbgFileView", "setBreakPosition",cDbgFileSetBreakPosition, "fileView.setBreakPosition(line)", 3, 3); + Con::addCommand("DbgFileView", "setBreak", cDbgFileSetBreak, "fileView.setBreak(line)", 3, 3); + Con::addCommand("DbgFileView", "removeBreak", cDbgFileRemoveBreak, "fileView.removeBreak(line)", 3, 3); + Con::addCommand("DbgFileView", "findString", cDbgFindString, "fileView.findString(Text)", 3, 3); +} + +//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(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; + + U32 fileSize = ResourceManager->getSize(fileName); + char *fileBuf; + if (fileSize) + { + fileBuf = new char [fileSize+1]; + Stream *s = ResourceManager->openStream(fileName); + if (s) + { + s->read(fileSize, fileBuf); + ResourceManager->closeStream(s); + fileBuf[fileSize] = '\0'; + } + else + { + delete [] fileBuf; + fileBuf = NULL; + } + } + if (!fileSize || !fileBuf) + { + 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 = fileBuf; + 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 [] fileBuf; + + //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 + mBounds.point.y >= 0 && yOffset + mBounds.point.y < parent->mBounds.extent.y - mCellSize.y)) + { + //reposition the control + S32 newYOffset = getMin(0, getMax(parent->mBounds.extent.y - mBounds.extent.y, (mCellSize.y * 4) - yOffset)); + resize(Point2I(mBounds.point.x, newYOffset), mBounds.extent); + } + + //hilite the line + cellSelected(Point2I(0, lineNumber - 1)); +} + + +S32 DbgFileView::findMouseOverChar(const char *text, S32 stringPosition) +{ + //find which character we're over + char tempBuf[256], *bufPtr = &tempBuf[1]; + dStrcpy(tempBuf, text); + bool found = false; + bool finished = false; + do + { + char c = *bufPtr; + *bufPtr = '\0'; + if ((S32)mFont->getStrWidth(tempBuf) > stringPosition) + { + *bufPtr = c; + bufPtr--; + found = true; + finished = true; + } + else + { + *bufPtr = c; + bufPtr++; + if (*bufPtr == '\0') finished = true; + } + + } while (! finished); + + //see if we found a char + if (found) + return bufPtr - tempBuf; + 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(2, "DbgSetCursorWatch", mMouseOverVariable); + } + else + Con::executef(2, "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, 2, "onRemoveBreakPoint", itoa(cell.y + 1)); + else + Con::executef(this, 2, "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, 2, "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) + { + dglSetBitmapModulation(mProfile->mFontColorHL); + dglDrawText(mFont, cellOffset, "#"); + } + else if (mFileView[cell.y].breakPosition) + { + dglSetBitmapModulation(mProfile->mFontColor); + dglDrawText(mFont, cellOffset, "-"); + } + cellOffset.x += 8; + + //draw in the "current line" indicator + if (mFileName == mPCFileName && (cell.y + 1 == mPCCurrentLine)) + { + dglSetBitmapModulation(mProfile->mFontColorHL); + dglDrawText(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) + { + dglDrawRectFill(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(tempBuf); + + //get the start coord + tempBuf[mBlockStart] = '\0'; + startPos = mFont->getStrWidth(tempBuf); + + //draw the hilite + dglDrawRectFill(RectI(cellOffset.x + startPos, cellOffset.y - 3, endPos - startPos + 2, mCellSize.y + 6), mProfile->mFillColorHL); + } + } + + //draw the line of text + dglSetBitmapModulation(mFileView[cell.y].breakOnLine ? mProfile->mFontColorHL : mProfile->mFontColor); + dglDrawText(mFont, cellOffset, mFileView[cell.y].text); +} + +// // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// +// static void cDbgBreakPointSet(SimObject *obj, S32 argc, const char **argv) +// { +// DbgBreakPointView *dbgBreakCtrl = static_cast(obj); +// +// if (argc == 4) +// { +// //this call prevent the expression, passct, and clear flag from being overwritten +// dbgBreakCtrl->setBreakPointStatus(argv[2], dAtoi(argv[3]), true); +// } +// else +// { +// //optional params +// bool clear = false; +// S32 passct = 0; +// const char *expr = NULL; +// if (argc >= 5) +// clear = !dStricmp(argv[4], "true"); +// if (argc >= 6) +// passct = dAtoi(argv[5]); +// if (argc == 7) +// expr = argv[6]; +// +// dbgBreakCtrl->setBreakPointStatus(argv[2], dAtoi(argv[3]), true, clear, passct, expr); +// } +// } +// +// static void cDbgBreakPointRemove(SimObject *obj, S32, const char **argv) +// { +// DbgBreakPointView *dbgBreakCtrl = static_cast(obj); +// dbgBreakCtrl->setBreakPointStatus(argv[2], dAtoi(argv[3]), false); +// } +// +// static void cDbgBreakPointSetCondition(SimObject *obj, S32, const char **argv) +// { +// DbgBreakPointView *dbgBreakCtrl = static_cast(obj); +// dbgBreakCtrl->setBreakPointCondition(!dStricmp(argv[4], "true"), dAtoi(argv[3]), argv[2]); +// } +// +// static const char* cDbgBreakPointIsSet(SimObject *obj, S32, const char **argv) +// { +// DbgBreakPointView *dbgBreakCtrl = static_cast(obj); +// +// if (dbgBreakCtrl->isBreakPoint(argv[2], dAtoi(argv[3]))) +// return "true"; +// else +// return "false"; +// } +// +// static void cDbgBreakPointClearAll(SimObject *obj, S32, const char **) +// { +// DbgBreakPointView *dbgBreakCtrl = static_cast(obj); +// dbgBreakCtrl->clearBreakPoints(); +// } +// +// DbgBreakPointView::DbgBreakPointView() +// { +// mSize.set(1, 0); +// } +// +// void DbgBreakPointView::consoleInit() +// { +// Con::addCommand("DbgBreakPointView", "set", cDbgBreakPointSet, "breakPoints.set(file, line, , , )", 4, 7); +// Con::addCommand("DbgBreakPointView", "remove", cDbgBreakPointRemove, "breakPoints.remove(file, line)", 4, 4); +// Con::addCommand("DbgBreakPointView", "condition", cDbgBreakPointSetCondition, "breakPoints.condition(clear, passct, condition)", 5, 5); +// Con::addCommand("DbgBreakPointView", "isSet", cDbgBreakPointIsSet, "breakPoints.isSet(file, line)", 4, 4); +// Con::addCommand("DbgBreakPointView", "clear", cDbgBreakPointClearAll, "breakPoints.clear()", 2, 2); +// Con::linkNamespaces("GuiArrayCtrl", "DbgBreakPointView"); +// } +// +// bool DbgBreakPointView::onWake() +// { +// if (! Parent::onWake()) +// return false; +// +// //adjust the cellwidth to the maximum line length +// AdjustCellSize(); +// mSize.set(1, mBreakPointList.size()); +// +// return true; +// } +// +// void DbgBreakPointView::AdjustCellSize() +// { +// if (! bool(mFont)) +// return; +// S32 maxWidth = 1; +// for (U32 i = 0; i < mBreakPointList.size(); i++) +// { +// S32 cellWidth = 158 + mFont->getStrWidth(mBreakPointList[i].condition) + 4; +// maxWidth = getMax(maxWidth, cellWidth); +// } +// +// mCellSize.set(maxWidth, mFont->getHeight() + 2); +// setSize(mSize); +// } +// +// void DbgBreakPointView::setBreakPointStatus(const char *fileName, S32 lineNumber, bool value) +// { +// //sanity check +// if ((! fileName) || (! fileName[0])) +// return; +// if (lineNumber < 0) +// return; +// +// //see if it's already in the list +// U32 index; +// for (index = 0; index < mBreakPointList.size(); index++) +// { +// if ((! dStricmp(mBreakPointList[index].fileName, fileName)) && (mBreakPointList[index].lineNumber == lineNumber)) +// break; +// } +// +// if (index < mBreakPointList.size()) +// { +// char buf[256]; +// dStrcpy(buf, mBreakPointList[index].condition); +// setBreakPointStatus(fileName, lineNumber, value, +// mBreakPointList[index].clear, mBreakPointList[index].passct, buf); +// } +// //else, add it in with default params +// else +// { +// setBreakPointStatus(fileName, lineNumber, value, false, 0, NULL); +// } +// } +// +// void DbgBreakPointView::setBreakPointStatus(const char *fileName, S32 lineNumber, bool value, +// bool clear, S32 passct, const char *expr) +// { +// //sanity check +// if ((! fileName) || (! fileName[0])) +// return; +// if (lineNumber < 0) +// return; +// +// //see if it's already in the list +// S32 found = -1; +// for (U32 i = 0; i < mBreakPointList.size(); i++) +// { +// if ((! dStricmp(mBreakPointList[i].fileName, fileName)) && (mBreakPointList[i].lineNumber == lineNumber)) +// { +// mBreakPointList[i].active = value; +// found = i; +// break; +// } +// } +// +// //if not found, add it to the list +// if (found < 0) +// { +// BreakPointRep newBreakPoint; +// mBreakPointList.push_back(newBreakPoint); +// found = mBreakPointList.size() - 1; +// } +// +// //set the values +// dStrcpy(mBreakPointList[found].fileName, fileName); +// mBreakPointList[found].lineNumber = lineNumber; +// if (expr && expr[0]) +// dStrncpy(mBreakPointList[found].condition, expr, 255); +// else +// dStrcpy(mBreakPointList[found].condition, "true"); +// mBreakPointList[found].condition[255] = '\0'; +// mBreakPointList[found].clear = clear; +// mBreakPointList[found].passct = passct; +// mBreakPointList[found].active = value; +// +// //update the size() +// mSelectedCell.set(1, found); +// AdjustCellSize(); +// setSize(Point2I(1, mBreakPointList.size())); +// +// //send the message to the server +// if (value) +// { +// char buf[2048]; +// dSprintf(buf, sizeof(buf), "BRKSET:%s %d %d %d %s", mBreakPointList[found].fileName, +// mBreakPointList[found].lineNumber, +// mBreakPointList[found].clear, +// mBreakPointList[found].passct, +// mBreakPointList[found].condition); +// Con::executef(2, "DbgRemoteSend", buf); +// } +// else +// { +// char buf[2048]; +// dSprintf(buf, sizeof(buf), "BRKCLR:%s %d", fileName, lineNumber); +// Con::executef(2, "DbgRemoteSend", buf); +// } +// } +// +// void DbgBreakPointView::setBreakPointCondition(bool clear, S32 passct, const char *expr) +// { +// if (mSelectedCell.y < 0 || mSelectedCell.y >= (S32)mBreakPointList.size()) +// return; +// +// setBreakPointStatus(mBreakPointList[mSelectedCell.y].fileName, +// mBreakPointList[mSelectedCell.y].lineNumber, +// mBreakPointList[mSelectedCell.y].active, +// clear, passct, expr); +// } +// +// bool DbgBreakPointView::isBreakPoint(const char *fileName, S32 lineNumber) +// { +// if ((! fileName) || (! fileName[0])) +// return false; +// if (lineNumber < 0) +// return false; +// +// //see if it's in the list +// for (U32 i = 0; i < mBreakPointList.size(); i++) +// { +// if ((! dStricmp(mBreakPointList[i].fileName, fileName)) && (mBreakPointList[i].lineNumber == lineNumber)) +// return mBreakPointList[i].active; +// } +// +// //not found +// return false; +// } +// +// void DbgBreakPointView::clearBreakPoints() +// { +// for (U32 i = 0; i < mBreakPointList.size(); i++) +// { +// if (mBreakPointList[i].active) +// { +// Con::executef(3, "DbgRemoveBreakPoint", mBreakPointList[i].fileName, +// itoa(mBreakPointList[i].lineNumber)); +// } +// } +// +// //empty the list +// mBreakPointList.clear(); +// AdjustCellSize(); +// setSize(Point2I(1, 0)); +// mSelectedCell.set(-1, -1); +// } +// +// void DbgBreakPointView::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 (! mBreakPointList[cell.y].active) +// Con::executef(6, "DbgSetBreakPoint", mBreakPointList[cell.y].fileName, +// itoa(mBreakPointList[cell.y].lineNumber), +// mBreakPointList[cell.y].clear ? "true" : "false", +// itoa2(mBreakPointList[cell.y].passct), +// mBreakPointList[cell.y].condition); +// else +// Con::executef(3, "DbgRemoveBreakPoint", mBreakPointList[cell.y].fileName, +// itoa(mBreakPointList[cell.y].lineNumber)); +// } +// else +// { +// Point2I prevSelected = mSelectedCell; +// Parent::onMouseDown(event); +// +// //open the file view +// if (mSelectedCell.y == prevSelected.y && doubleClick) +// { +// Con::executef(4, "DbgOpenFile", mBreakPointList[cell.y].fileName, +// itoa(mBreakPointList[cell.y].lineNumber), "false"); +// } +// } +// } +// else +// { +// Parent::onMouseDown(event); +// } +// } +// +// void DbgBreakPointView::onRenderCell(Point2I offset, Point2I cell, bool selected, bool) +// { +// Point2I cellOffset = offset; +// cellOffset.x += 4; +// +// //draw the break point marks +// if (mBreakPointList[cell.y].active) +// { +// dglSetBitmapModulation(mProfile->mFontColorHL); +// dglDrawText(mFont, cellOffset, "#"); +// } +// else +// { +// dglSetBitmapModulation(mProfile->mFontColor); +// dglDrawText(mFont, cellOffset, "-"); +// } +// cellOffset.x += 16; +// +// //hilite the line if selected +// if (selected) +// { +// dglDrawRectFill(RectI(cellOffset.x - 2, cellOffset.y - 3, mCellSize.x + 4, mCellSize.y + 6), +// mProfile->mFillColorHL); +// } +// +// //draw the line number +// char bufLine[32]; +// dSprintf(bufLine, sizeof(bufLine), "%d", mBreakPointList[cell.y].lineNumber); +// dglSetBitmapModulation(mBreakPointList[cell.y].active ? mProfile->mFontColorHL : mProfile->mFontColor); +// dglDrawText(mFont, cellOffset, bufLine); +// +// cellOffset.x += 40; +// +// //draw the file name clipped to 120 pix +// char clippedBuf[256], *clipPtr; +// dStrcpy(clippedBuf, mBreakPointList[cell.y].fileName); +// clipPtr = &clippedBuf[dStrlen(clippedBuf)]; +// bool finished = false; +// while ((clipPtr > &clippedBuf[0]) && (! finished)) +// { +// if (mFont->getStrWidth(clippedBuf) <= 90) +// { +// finished = true; +// } +// else +// { +// *--clipPtr = '\0'; +// } +// } +// dglSetBitmapModulation(mProfile->mFontColor); +// dglDrawText(mFont, cellOffset, clippedBuf); +// cellOffset.x += 98; +// +// //draw the condition +// dglSetBitmapModulation(mProfile->mFontColorHL); +// dglDrawText(mFont, cellOffset, mBreakPointList[cell.y].condition); +// } +// +// // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// +// static void cDbgWatchViewSet(SimObject *obj, S32, const char **argv) +// { +// DbgWatchView *dbgCtrl = static_cast(obj); +// dbgCtrl->addWatch(argv[2]); +// } +// +// static void cDbgWatchViewUpdate(SimObject *obj, S32, const char **argv) +// { +// DbgWatchView *dbgCtrl = static_cast(obj); +// dbgCtrl->updateWatch(dAtoi(argv[2]), argv[3]); +// } +// +// static void cDbgWatchViewEdit(SimObject *obj, S32, const char **argv) +// { +// DbgWatchView *dbgCtrl = static_cast(obj); +// dbgCtrl->editCurrentWatch(argv[2]); +// } +// +// static void cDbgWatchViewRemove(SimObject *obj, S32, const char **) +// { +// DbgWatchView *dbgCtrl = static_cast(obj); +// dbgCtrl->deleteCurrentWatch(); +// } +// +// static void cDbgWatchViewQueryAll(SimObject *obj, S32, const char **) +// { +// DbgWatchView *dbgCtrl = static_cast(obj); +// dbgCtrl->queryAll(); +// } +// +// static void cDbgWatchViewClear(SimObject *obj, S32, const char **) +// { +// DbgWatchView *dbgCtrl = static_cast(obj); +// dbgCtrl->clearWatches(); +// } +// +// DbgWatchView::DbgWatchView() +// { +// mSize.set(1, 0); +// mNextQueryID = 1; +// } +// +// void DbgWatchView::consoleInit() +// { +// Con::addCommand("DbgWatchView", "set", cDbgWatchViewSet, "watchView.set(expression)", 3, 3); +// Con::addCommand("DbgWatchView", "update", cDbgWatchViewUpdate, "watchView.update(queryId, value)", 4, 4); +// Con::addCommand("DbgWatchView", "edit", cDbgWatchViewEdit, "watchView.edit(newValue)", 3, 3); +// Con::addCommand("DbgWatchView", "remove", cDbgWatchViewRemove, "watchView.remove()", 2, 2); +// Con::addCommand("DbgWatchView", "queryAll", cDbgWatchViewQueryAll, "watchView.queryAll()", 2, 2); +// Con::addCommand("DbgWatchView", "clear", cDbgWatchViewClear, "watchView.clear()", 2, 2); +// Con::linkNamespaces("GuiArrayCtrl", "DbgWatchView"); +// } +// +// bool DbgWatchView::onWake() +// { +// if (! Parent::onWake()) +// return false; +// +// //adjust the cellwidth to the maximum line length +// AdjustCellSize(); +// mSize.set(1, mWatchList.size()); +// +// return true; +// } +// +// void DbgWatchView::AdjustCellSize() +// { +// if (! bool(mFont)) +// return; +// S32 maxWidth = 1; +// for (U32 i = 0; i < mWatchList.size(); i++) +// { +// S32 cellWidth = 132 + mFont->getStrWidth(mWatchList[i].value) + 8; +// maxWidth = getMax(maxWidth, cellWidth); +// } +// +// mCellSize.set(maxWidth, mFont->getHeight() + 2); +// setSize(mSize); +// } +// +// void DbgWatchView::addWatch(const char *varName) +// { +// if ((! varName) || (! varName[0])) return; +// +// //see if it's already in the list +// for (U32 i = 0; i < mWatchList.size(); i++) +// { +// if (! dStricmp(mWatchList[i].variable, varName)) +// { +// cellSelected(Point2I(0, i)); +// return; +// } +// } +// +// //it wasn't found, add it to the list +// WatchRep newWatch; +// dStrcpy(newWatch.variable, varName); +// newWatch.id = mNextQueryID++; +// newWatch.value[0] = '\0'; +// mWatchList.push_back(newWatch); +// +// //update the size() +// AdjustCellSize(); +// setSize(Point2I(1, mWatchList.size())); +// cellSelected(Point2I(0, mWatchList.size() - 1)); +// +// //send the query +// char buf[300]; +// const char *frame = Con::executef(1, "DbgStackGetFrame"); +// dSprintf(buf, sizeof(buf), "EVAL:%d %d %s", newWatch.id, dAtoi(frame), newWatch.variable); +// Con::executef(2, "DbgRemoteSend", buf); +// } +// +// void DbgWatchView::deleteWatch(const char *varName) +// { +// if ((! varName) || (! varName[0])) return; +// +// //find it in the list +// U32 cell; +// for (cell = 0; cell < mWatchList.size(); cell++) +// { +// if (! dStricmp(mWatchList[cell].variable, varName)) +// break; +// } +// +// //see if we found it +// if (cell < mWatchList.size()) +// { +// //send the message to the server +// char buf[256]; +// dSprintf(buf, sizeof(buf), "dbgRemoveWatch(%s);", mWatchList[cell].variable); +// Con::executef(2, "remoteDebugSend", buf); +// +// //delete the watch +// mWatchList.erase(cell); +// +// //update the size +// AdjustCellSize(); +// setSize(Point2I(1, mWatchList.size())); +// mSelectedCell.set(-1, -1); +// } +// } +// +// void DbgWatchView::deleteCurrentWatch() +// { +// if (mSelectedCell.y < 0 || mSelectedCell.y >= (S32)mWatchList.size()) +// return; +// deleteWatch(mWatchList[mSelectedCell.y].variable); +// } +// +// void DbgWatchView::updateWatch(S32 id, const char *value) +// { +// if (! value) +// return; +// +// //find it in the list +// for (U32 i = 0; i < mWatchList.size(); i++) +// { +// if (mWatchList[i].id == id) +// { +// if (! dStrcmp(value, "\"\"")) +// dStrcpy(mWatchList[i].value, value); +// else +// dSprintf(mWatchList[i].value, sizeof(mWatchList[i].value), "\"%s\"", value); +// break; +// } +// } +// +// //adjust the cellwidth to the maximum line length +// AdjustCellSize(); +// mSize.set(1, mWatchList.size()); +// } +// +// void DbgWatchView::editCurrentWatch(const char *newValue) +// { +// if (! newValue) +// return; +// if (mSelectedCell.y < 0 || mSelectedCell.y >= (S32)mWatchList.size()) +// return; +// +// //send the edit to the server - at the same time, it will generate a query +// char valBuf[256]; +// char buf[512]; +// +// //make a copy, because executing DbgStackGetFrame will change the contents of argv... +// dStrcpy(valBuf, newValue); +// +// const char *frame = Con::executef(1, "DbgStackGetFrame"); +// dSprintf(buf, sizeof(buf), "EVAL:%d %d %s=%s", mWatchList[mSelectedCell.y].id, dAtoi(frame), +// mWatchList[mSelectedCell.y].variable, +// valBuf[0] ? valBuf : "\"\""); +// Con::executef(2, "DbgRemoteSend", buf); +// } +// +// void DbgWatchView::queryAll() +// { +// //loop through the list +// for (U32 i = 0; i < mWatchList.size(); i++) +// { +// char buf[300]; +// const char *frame = Con::executef(1, "DbgStackGetFrame"); +// dSprintf(buf, sizeof(buf), "EVAL:%d %d %s", mWatchList[i].id, dAtoi(frame), mWatchList[i].variable); +// Con::executef(2, "DbgRemoteSend", buf); +// } +// } +// +// void DbgWatchView::clearWatches() +// { +// //loop through the list +// for (U32 i = 0; i < mWatchList.size(); i++) +// { +// //send the message to the server +// char buf[256]; +// dSprintf(buf, sizeof(buf), "dbgRemoveWatch(%s);", mWatchList[i].variable); +// Con::executef(2, "remoteDebugSend", buf); +// } +// +// //clear the list +// mWatchList.clear(); +// +// //update the size +// AdjustCellSize(); +// setSize(Point2I(1, mWatchList.size())); +// mSelectedCell.set(-1, -1); +// } +// +// void DbgWatchView::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) +// { +// Point2I prevSelected = mSelectedCell; +// Parent::onMouseDown(event); +// +// //open the file view +// //if (mSelectedCell.y == prevSelected.y && doubleClick) +// //{ +// // Con::executef(1, "DbgEditWatchDialog::open"); +// //} +// } +// else +// { +// Parent::onMouseDown(event); +// } +// } +// +// void DbgWatchView::onRenderCell(Point2I offset, Point2I cell, bool selected, bool) +// { +// Point2I cellOffset = offset; +// cellOffset.x += 4; +// +// //hilite the line if selected +// if (selected) +// dglDrawRectFill(RectI(cellOffset.x - 2, cellOffset.y - 3, mCellSize.x + 4, mCellSize.y + 6), mProfile->mFillColorHL); +// +// //draw the variable name clipped to 120 pix +// char clippedBuf[256], *clipPtr; +// dStrcpy(clippedBuf, mWatchList[cell.y].variable); +// clipPtr = &clippedBuf[dStrlen(clippedBuf)]; +// bool finished = false; +// while ((clipPtr > &clippedBuf[0]) && (! finished)) +// { +// if (mFont->getStrWidth(clippedBuf) <= 120) +// { +// finished = true; +// } +// else +// { +// *--clipPtr = '\0'; +// } +// } +// dglSetBitmapModulation(mProfile->mFontColor); +// dglDrawText(mFont, cellOffset, clippedBuf); +// cellOffset.x += 128; +// +// //draw the value +// dglSetBitmapModulation(mProfile->mFontColorHL); +// dglDrawText(mFont, cellOffset, mWatchList[cell.y].value); +// } +// +// // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// +// static void cDbgCallStackViewFunctionCall(SimObject *obj, S32, const char **argv) +// { +// DbgCallStackView *dbgCtrl = static_cast(obj); +// dbgCtrl->addFunctionCall(argv[2], dAtoi(argv[3]), argv[4]); +// } +// +// static const char* cDbgCallStackViewGetFrame(SimObject *obj, S32, const char **) +// { +// DbgCallStackView *dbgCtrl = static_cast(obj); +// return itoa(dbgCtrl->getFrameNumber()); +// } +// +// static void cDbgCallStackViewClear(SimObject *obj, S32, const char **) +// { +// DbgCallStackView *dbgCtrl = static_cast(obj); +// dbgCtrl->clear(); +// } +// +// DbgCallStackView::DbgCallStackView() +// { +// mSize.set(1, 0); +// } +// +// void DbgCallStackView::consoleInit() +// { +// Con::addCommand("DbgCallStackView", "add", cDbgCallStackViewFunctionCall, "callStack.add(file, line, function)", 5, 5); +// Con::addCommand("DbgCallStackView", "getFrame", cDbgCallStackViewGetFrame, "callStack.getFrame()", 2, 2); +// Con::addCommand("DbgCallStackView", "clear", cDbgCallStackViewClear, "callStack.clear()", 2, 2); +// Con::linkNamespaces("GuiArrayCtrl", "DbgCallStackView"); +// } +// +// bool DbgCallStackView::onWake() +// { +// if (! Parent::onWake()) +// return false; +// +// //adjust the cellwidth to the maximum line length +// AdjustCellSize(); +// mSize.set(1, mCallStack.size()); +// +// return true; +// } +// +// void DbgCallStackView::AdjustCellSize() +// { +// if (! bool(mFont)) +// return; +// S32 maxWidth = 1; +// for (U32 i = 0; i < mCallStack.size(); i++) +// { +// S32 cellWidth = mFont->getStrWidth(mCallStack[i].functionName) + 8; +// maxWidth = getMax(maxWidth, cellWidth); +// } +// +// mCellSize.set(maxWidth, mFont->getHeight() + 2); +// setSize(mSize); +// } +// +// void DbgCallStackView::addFunctionCall(const char *fileName, S32 lineNumber, const char *functionName) +// { +// if ((! functionName) || (! functionName[0])) +// return; +// if ((! fileName) || (! fileName[0])) +// return; +// if (lineNumber <= 0) +// return; +// +// CallStackRep newCall; +// dSprintf(newCall.functionName, sizeof(newCall.functionName), "%s();", functionName); +// dStrcpy(newCall.fileName, fileName); +// newCall.lineNumber = lineNumber; +// mCallStack.push_back(newCall); +// +// //update the size() +// AdjustCellSize(); +// setSize(Point2I(1, mCallStack.size())); +// mSelectedCell.set(1, 0); +// } +// +// S32 DbgCallStackView::getFrameNumber() +// { +// if (mSelectedCell.y < 0 || mSelectedCell.y >= (S32)mCallStack.size()) +// return 0; +// return mCallStack.size() - 1 - mSelectedCell.y; +// } +// +// void DbgCallStackView::clear() +// { +// mCallStack.clear(); +// mSelectedCell.set(-1, -1); +// +// //update the size() +// AdjustCellSize(); +// setSize(Point2I(1, mCallStack.size())); +// } +// +// void DbgCallStackView::onMouseDown(const GuiEvent &event) +// { +// if (! mActive) +// { +// Parent::onMouseDown(event); +// return; +// } +// +// bool doubleClick = (event.mouseClickCount > 1); +// +// Point2I prevSelected = mSelectedCell; +// Parent::onMouseDown(event); +// +// //open the file view +// if (mSelectedCell.y == prevSelected.y && doubleClick) +// { +// Con::executef(this, 4, "onCellSelect", mCallStack[mSelectedCell.y].fileName, +// itoa(mCallStack[mSelectedCell.y].lineNumber), +// mSelectedCell.y == 0 ? "true" : "false"); +// } +// } +// +// void DbgCallStackView::onRenderCell(Point2I offset, Point2I cell, bool selected, bool) +// { +// Point2I cellOffset = offset; +// cellOffset.x += 4; +// +// //hilite the line if selected +// //if (selected) +// // dglDrawRectFill(RectI(cellOffset.x - 2, cellOffset.y - 3, +// // mCellSize.x, mCellSize.y), mFillColorHL); +// +// //draw the function name +// dglSetBitmapModulation(selected ? mProfile->mFontColorHL : mProfile->mFontColor); +// dglDrawText(mFont, cellOffset, mCallStack[cell.y].functionName); +// } +// diff --git a/gui/guiDebugger.h b/gui/guiDebugger.h new file mode 100644 index 0000000..88c5ac9 --- /dev/null +++ b/gui/guiDebugger.h @@ -0,0 +1,85 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIDEBUGGER_H_ +#define _GUIDEBUGGER_H_ + +#ifndef _GUIARRAYCTRL_H_ +#include "GUI/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); + static void consoleInit(); + + 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/guiDefaultControlRender.cc b/gui/guiDefaultControlRender.cc new file mode 100644 index 0000000..62d32d2 --- /dev/null +++ b/gui/guiDefaultControlRender.cc @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "gui/guiDefaultControlRender.h" +#include "core/color.h" +#include "math/mRect.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(RectI &bounds) +{ + 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; + + dglDrawRectFill( bounds, colorLightGray); + dglDrawLine(l, t, l, b - 1, colorWhite); + dglDrawLine(l, t, r - 1, t, colorWhite); + + dglDrawLine(l, b, r, b, colorBlack); + dglDrawLine(r, b - 1, r, t, colorBlack); + + dglDrawLine(l + 1, b - 1, r - 1, b - 1, colorGray); + dglDrawLine(r - 1, b - 2, r - 1, t + 1, colorGray); +} + + +void renderLoweredBox(RectI &bounds) +{ + 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; + + dglDrawRectFill( bounds, colorLightGray); + + dglDrawLine(l, b, r, b, colorWhite); + dglDrawLine(r, b - 1, r, t, colorWhite); + + dglDrawLine(l, t, r - 1, t, colorBlack); + dglDrawLine(l, t + 1, l, b - 1, colorBlack); + + dglDrawLine(l + 1, t + 1, r - 2, t + 1, colorGray); + dglDrawLine(l + 1, t + 2, l + 1, b - 2, colorGray); +} + diff --git a/gui/guiDefaultControlRender.h b/gui/guiDefaultControlRender.h new file mode 100644 index 0000000..c23db9f --- /dev/null +++ b/gui/guiDefaultControlRender.h @@ -0,0 +1,13 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _H_GUIDEFAULTCONTROLRENDER_ +#define _H_GUIDEFAULTCONTROLRENDER_ + +void renderRaisedBox(RectI &bounds); +void renderLoweredBox(RectI &bounds); + +#endif \ No newline at end of file diff --git a/gui/guiEditCtrl.cc b/gui/guiEditCtrl.cc new file mode 100644 index 0000000..e56ebe0 --- /dev/null +++ b/gui/guiEditCtrl.cc @@ -0,0 +1,775 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "console/simBase.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiEditCtrl.h" +#include "Platform/event.h" +#include "Core/fileStream.h" + +GuiEditCtrl::GuiEditCtrl() +{ + VECTOR_SET_ASSOCIATION(mSelectedControls); + + mActive = true; + mCurrentAddSet = NULL; + mGridSnap.set(0, 0); +} + + +static void cGuiEditCtrlSetRoot(SimObject *obj, S32, const char **argv) +{ + GuiControl *ctrl; + if(!Sim::findObject(argv[2], ctrl)) + return; + ((GuiEditCtrl *) obj)->setRoot(ctrl); +} + +static void cGuiEditCtrlAdd(SimObject *obj, S32, const char **argv) +{ + GuiControl *ctrl; + if(!Sim::findObject(argv[2], ctrl)) + return; + ((GuiEditCtrl *) obj)->addNewControl(ctrl); +} + +static void cGuiEditCtrlSelect(SimObject *obj, S32, const char **argv) +{ + GuiControl *ctrl; + if(!Sim::findObject(argv[2], ctrl)) + return; + ((GuiEditCtrl *) obj)->select(ctrl); +} + +static void cGuiEditCtrlSetAddSet(SimObject *obj, S32, const char **argv) +{ + GuiEditCtrl *editor = static_cast(obj); + GuiControl *addSet = NULL; + SimObject *target = Sim::findObject(argv[2]); + if (target) + addSet = dynamic_cast(target); + if (! addSet) + { + Con::printf("%s(): Invalid control: %s", argv[0], argv[2]); + return; + } + editor->setCurrentAddSet(addSet); +} + +static void cGuiEditCtrlToggle(SimObject *obj, S32, const char **) +{ + GuiEditCtrl *editor = static_cast(obj); + editor->setEditMode(! editor->mActive); +} + +static void cGuiEditCtrlJustify(SimObject *obj, S32, const char **argv) +{ + GuiEditCtrl *editor = static_cast(obj); + editor->justifySelection((GuiEditCtrl::Justification)dAtoi(argv[2])); +} + +static void cGuiEditCtrlBringToFront(SimObject *obj, S32, const char **) +{ + GuiEditCtrl *editor = static_cast(obj); + editor->bringToFront(); +} + +static void cGuiEditCtrlPushToBack(SimObject *obj, S32, const char **) +{ + GuiEditCtrl *editor = static_cast(obj); + editor->pushToBack(); +} + +void GuiEditCtrl::consoleInit() +{ + Con::addCommand("GuiEditCtrl", "addNewCtrl", cGuiEditCtrlAdd, "editCtrl.addNewCtrl(ctrl)", 3, 3); + Con::addCommand("GuiEditCtrl", "select", cGuiEditCtrlSelect, "editCtrl.select(ctrl)", 3, 3); + Con::addCommand("GuiEditCtrl", "setRoot", cGuiEditCtrlSetRoot, "editCtrl.setRoot(root)", 3, 3); + Con::addCommand("GuiEditCtrl", "setCurrentAddSet", cGuiEditCtrlSetAddSet, "editCtrl.setCurrentAddSet(ctrl)", 3, 3); + Con::addCommand("GuiEditCtrl", "toggle", cGuiEditCtrlToggle, "editCtrl.toggle()", 2, 2); + Con::addCommand("GuiEditCtrl", "justify", cGuiEditCtrlJustify, "editCtrl.justify(mode)", 3, 3); + Con::addCommand("GuiEditCtrl", "bringToFront", cGuiEditCtrlBringToFront, "editCtrl.bringToFront()", 2, 2); + Con::addCommand("GuiEditCtrl", "pushToBack", cGuiEditCtrlPushToBack, "editCtrl.pushToBack()", 2, 2); +} + +bool GuiEditCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + setEditMode(true); + return true; +} + +void GuiEditCtrl::setRoot(GuiControl *root) +{ + mContentControl = root; +} + +enum { GUI_BLACK = 0, GUI_WHITE = 255 }; +enum { NUT_SIZE = 3 }; + +void GuiEditCtrl::setEditMode(bool value) +{ + mActive = value; + mSelectedControls.clear(); + if (mActive && mAwake) + mCurrentAddSet = NULL; +} + +void GuiEditCtrl::setCurrentAddSet(GuiControl *ctrl) +{ + if (ctrl != mCurrentAddSet) + { + mSelectedControls.clear(); + mCurrentAddSet = ctrl; + } +} + +void GuiEditCtrl::setSelection(GuiControl *ctrl, bool inclusive) +{ + //sanity check + if (! ctrl) + return; + + // otherwise, we hit a new control... + GuiControl *newAddSet = ctrl->getParent(); + + //see if we should clear the old selection set + if (newAddSet != mCurrentAddSet || (! inclusive)) + mSelectedControls.clear(); + + //set the selection + mCurrentAddSet = newAddSet; + mSelectedControls.push_back(ctrl); +} + +void GuiEditCtrl::addNewControl(GuiControl *ctrl) +{ + if (! mCurrentAddSet) + mCurrentAddSet = mContentControl; + + mCurrentAddSet->addObject(ctrl); + mSelectedControls.clear(); + mSelectedControls.push_back(ctrl); +} + +void GuiEditCtrl::drawNut(const Point2I &nut, bool multisel) +{ + RectI r(nut.x - NUT_SIZE, nut.y - NUT_SIZE, 2 * NUT_SIZE + 1, 2 * NUT_SIZE + 1); + dglDrawRect(r, multisel ? ColorI(0, 0, 0) : ColorI(255, 255, 255)); + r.point += Point2I(1, 1); + r.extent -= Point2I(1, 1); + dglDrawRectFill(r, multisel ? ColorI(255, 255, 255) : ColorI(0, 0, 0)); +} + +static inline 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; +} + +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; + + 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::drawNuts(RectI &box, bool multisel) +{ + 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), multisel); + drawNut(Point2I(lx, cy), multisel); + drawNut(Point2I(lx, by), multisel); + drawNut(Point2I(rx, ty), multisel); + drawNut(Point2I(rx, cy), multisel); + drawNut(Point2I(rx, by), multisel); + drawNut(Point2I(cx, ty), multisel); + drawNut(Point2I(cx, by), multisel); +} + +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::onPreRender() +{ + setUpdate(); +} + +void GuiEditCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + Point2I ctOffset; + Point2I cext; + + 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 + 1,ctOffset.y + 1, cext.x - 2, cext.y - 2); + dglDrawRect(box, ColorI(255, 255, 255)); + box.point -= Point2I(1, 1); + box.extent += Point2I(1, 1); + dglDrawRect(box, ColorI(0, 0, 0)); + } + 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); + drawNuts(box, multisel); + } + if (mMouseDownMode == DragSelecting) + { + RectI b; + getDragRect(b); + dglDrawRect(b, ColorI(255, 255, 255)); + } + } + + renderChildControls(offset, updateRect, firstResponder); +} + +bool GuiEditCtrl::selectionContains(GuiControl *ctrl) +{ + Vector::iterator i; + for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + if (ctrl == *i) return true; + return false; +} + +void GuiEditCtrl::onRightMouseDown(const GuiEvent &event) +{ + if (! mActive) + { + Parent::onRightMouseDown(event); + return; + } + setFirstResponder(); + + //search for the control hit in any layer below the edit layer + GuiControl *hitCtrl = mContentControl->findHitControl(event.mousePoint, mLayer - 1); + if (hitCtrl != mCurrentAddSet) + { + mSelectedControls.clear(); + mCurrentAddSet = hitCtrl; + } +} +void GuiEditCtrl::select(GuiControl *ctrl) +{ + mSelectedControls.clear(); + if(ctrl != mContentControl) + mSelectedControls.push_back(ctrl); + else + mCurrentAddSet = mContentControl; +} + +void GuiEditCtrl::onMouseDown(const GuiEvent &event) +{ + if (! mActive) + { + Parent::onMouseDown(event); + return; + } + if(!mContentControl) + return; + + setFirstResponder(); + //lock the mouse + mouseLock(); + + Point2I ctOffset; + Point2I cext; + GuiControl *ctrl; + + mLastMousePos = event.mousePoint; + + // first see if we hit a sizing knob on the currently selected control... + if (mSelectedControls.size() == 1) + { + ctrl = mSelectedControls.first(); + cext = ctrl->getExtent(); + ctOffset = ctrl->localToGlobalCoord(Point2I(0,0)); + RectI box(ctOffset.x,ctOffset.y,cext.x, cext.y); + + if ((mSizingMode = (GuiEditCtrl::sizingModes)getSizingHitKnobs(event.mousePoint, box)) != 0) + { + mMouseDownMode = SizingSelection; + return; + } + } + + if(!mCurrentAddSet) + mCurrentAddSet = mContentControl; + + //find the control we clicked + ctrl = mContentControl->findHitControl(event.mousePoint, mCurrentAddSet->mLayer); + + if (selectionContains(ctrl)) + { + //if we're holding shift, de-select the clicked ctrl + if (event.modifier & SI_SHIFT) + { + Vector::iterator i; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + { + if (*i == ctrl) + { + mSelectedControls.erase(i); + break; + } + } + + //set the mode + mMouseDownMode = Selecting; + } + + //else we hit a ctrl we've already selected, so set the mode to moving + else + mMouseDownMode = MovingSelection; + } + + //else we clicked on an unselected control + else + { + //if we clicked in the current add set + if (ctrl == mCurrentAddSet) + { + // start dragging a rectangle + // if the shift is not down, nuke prior selection + if (!(event.modifier & SI_SHIFT)) + mSelectedControls.clear(); + mSelectionAnchor = event.mousePoint; + mMouseDownMode = DragSelecting; + } + else + { + //find the new add set + GuiControl *newAddSet = ctrl->getParent(); + + //if we're holding shift and the ctrl is in the same add set + if (event.modifier & SI_SHIFT && newAddSet == mCurrentAddSet) + { + mSelectedControls.push_back(ctrl); + mMouseDownMode = Selecting; + } + else if (ctrl != mContentControl) + { + //find and set the new add set + mCurrentAddSet = ctrl->getParent(); + + //clear and set the selected controls + mSelectedControls.clear(); + mSelectedControls.push_back(ctrl); + mMouseDownMode = Selecting; + } + else + mMouseDownMode = Selecting; + } + } +} + +void GuiEditCtrl::onMouseUp(const GuiEvent &event) +{ + if (! mActive) + { + Parent::onMouseUp(event); + return; + } + + //unlock the mouse + mouseUnlock(); + + mLastMousePos = event.mousePoint; + if (mMouseDownMode == DragSelecting) + { + RectI b; + getDragRect(b); + GuiControl::iterator i; + for(i = mCurrentAddSet->begin(); i != mCurrentAddSet->end(); i++) + { + GuiControl *ctrl = dynamic_cast(*i); + Point2I upperL = ctrl->localToGlobalCoord(Point2I(0,0)); + Point2I lowerR = upperL + ctrl->mBounds.extent - Point2I(1, 1); + + if (b.pointInRect(upperL) && b.pointInRect(lowerR) && !selectionContains(ctrl)) + mSelectedControls.push_back(ctrl); + } + } + if (mSelectedControls.size() == 1) + Con::executef(this, 2, "onSelect", avar("%d", mSelectedControls[0]->getId())); + + setFirstResponder(); + //reset the mouse mode + mMouseDownMode = Selecting; +} + +void GuiEditCtrl::onMouseDragged(const GuiEvent &event) +{ + if (! mActive) + { + Parent::onMouseDragged(event); + return; + } + + if(!mCurrentAddSet) + mCurrentAddSet = mContentControl; + + Point2I mousePoint = event.mousePoint; + + if (mMouseDownMode == SizingSelection) + { + if (mGridSnap.x) + mousePoint.x -= mousePoint.x % mGridSnap.x; + if (mGridSnap.y) + mousePoint.y -= mousePoint.y % mGridSnap.y; + + GuiControl *ctrl = mSelectedControls.first(); + Point2I ctrlPoint = mCurrentAddSet->globalToLocalCoord(mousePoint); + Point2I newPosition = ctrl->getPosition(); + Point2I newExtent = ctrl->getExtent(); + Point2I minExtent = ctrl->getMinExtent(); + + if (mSizingMode & sizingLeft) + { + newPosition.x = ctrlPoint.x; + newExtent.x = ctrl->mBounds.extent.x + ctrl->mBounds.point.x - ctrlPoint.x; + if(newExtent.x < minExtent.x) + { + newPosition.x -= minExtent.x - newExtent.x; + newExtent.x = minExtent.x; + } + } + else if (mSizingMode & sizingRight) + { + newExtent.x = ctrlPoint.x - ctrl->mBounds.point.x; + if(newExtent.x < minExtent.x) + newExtent.x = minExtent.x; + } + + if (mSizingMode & sizingTop) + { + newPosition.y = ctrlPoint.y; + newExtent.y = ctrl->mBounds.extent.y + ctrl->mBounds.point.y - ctrlPoint.y; + if(newExtent.y < minExtent.y) + { + newPosition.y -= minExtent.y - newExtent.y; + newExtent.y = minExtent.y; + } + } + else if (mSizingMode & sizingBottom) + { + newExtent.y = ctrlPoint.y - ctrl->mBounds.point.y; + if(newExtent.y < minExtent.y) + newExtent.y = minExtent.y; + } + + ctrl->resize(newPosition, newExtent); + mCurrentAddSet->childResized(ctrl); + } + else if (mMouseDownMode == MovingSelection && mSelectedControls.size()) + { + Vector::iterator i = mSelectedControls.begin(); + Point2I minPos = (*i)->mBounds.point; + for(; i != mSelectedControls.end(); i++) + { + if ((*i)->mBounds.point.x < minPos.x) + minPos.x = (*i)->mBounds.point.x; + if ((*i)->mBounds.point.y < minPos.y) + minPos.y = (*i)->mBounds.point.y; + } + Point2I delta = mousePoint - mLastMousePos; + delta += minPos; // find new minPos; + + if (mGridSnap.x) + delta.x -= delta.x % mGridSnap.x; + if (mGridSnap.y) + delta.y -= delta.y % mGridSnap.y; + + delta -= minPos; + moveSelection(delta); + mLastMousePos += delta; + } + else + mLastMousePos = mousePoint; +} + +void GuiEditCtrl::moveSelection(const Point2I &delta) +{ + Vector::iterator i; + + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + (*i)->resize((*i)->mBounds.point + delta, (*i)->mBounds.extent); +} + +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)->mBounds.point.x; + maxX = minX + (*i)->mBounds.extent.x; + minY = (*i)->mBounds.point.y; + maxY = minY + (*i)->mBounds.extent.y; + extentX = (*i)->mBounds.extent.x; + extentY = (*i)->mBounds.extent.y; + i++; + for(;i != mSelectedControls.end(); i++) + { + minX = getMin(minX, (*i)->mBounds.point.x); + maxX = getMax(maxX, (*i)->mBounds.point.x + (*i)->mBounds.extent.x); + minY = getMin(minY, (*i)->mBounds.point.y); + maxY = getMax(maxY, (*i)->mBounds.point.y + (*i)->mBounds.extent.y); + extentX += (*i)->mBounds.extent.x; + extentY += (*i)->mBounds.extent.y; + } + S32 deltaX = maxX - minX; + S32 deltaY = maxY - minY; + switch(j) + { + case JUSTIFY_LEFT: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + (*i)->resize(Point2I(minX, (*i)->mBounds.point.y), (*i)->mBounds.extent); + break; + case JUSTIFY_TOP: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + (*i)->resize(Point2I((*i)->mBounds.point.x, minY), (*i)->mBounds.extent); + break; + case JUSTIFY_RIGHT: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + (*i)->resize(Point2I(maxX - (*i)->mBounds.extent.x + 1, (*i)->mBounds.point.y), (*i)->mBounds.extent); + break; + case JUSTIFY_BOTTOM: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + (*i)->resize(Point2I((*i)->mBounds.point.x, maxY - (*i)->mBounds.extent.y + 1), (*i)->mBounds.extent); + break; + case JUSTIFY_CENTER: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + (*i)->resize(Point2I(minX + ((deltaX - (*i)->mBounds.extent.x) >> 1), (*i)->mBounds.point.y), + (*i)->mBounds.extent); + 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)->mBounds.point.y < (*k)->mBounds.point.y) + break; + } + sortedList.insert(k, *i); + } + S32 space = (deltaY - extentY) / (mSelectedControls.size() - 1); + S32 curY = minY; + for(k = sortedList.begin(); k != sortedList.end(); k++) + { + (*k)->resize(Point2I((*k)->mBounds.point.x, curY), (*k)->mBounds.extent); + curY += (*k)->mBounds.extent.y + 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)->mBounds.point.x < (*k)->mBounds.point.x) + break; + } + sortedList.insert(k, *i); + } + S32 space = (deltaX - extentX) / (mSelectedControls.size() - 1); + S32 curX = minX; + for(k = sortedList.begin(); k != sortedList.end(); k++) + { + (*k)->resize(Point2I(curX, (*k)->mBounds.point.y), (*k)->mBounds.extent); + curX += (*k)->mBounds.extent.x + space; + } + } + break; + } +} + +void GuiEditCtrl::deleteSelection(void) +{ + + Vector::iterator i; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + (*i)->deleteObject(); + mSelectedControls.clear(); +} + +void GuiEditCtrl::loadSelection(const char* filename) +{ + if (! mCurrentAddSet) + mCurrentAddSet = mContentControl; + + Con::executef(2, "exec", filename); + SimSet *set; + if(!Sim::findObject("guiClipboard", set)) + return; + + if(set->size()) + { + mSelectedControls.clear(); + for(U32 i = 0; i < set->size(); i++) + { + GuiControl *ctrl = dynamic_cast((*set)[i]); + if(ctrl) + { + mCurrentAddSet->addObject(ctrl); + mSelectedControls.push_back(ctrl); + } + } + } + set->deleteObject(); +} + +void GuiEditCtrl::saveSelection(const char* filename) +{ + FileStream stream; + if(!ResourceManager->openFileForWrite(stream, NULL, filename)) + return; + SimSet *clipboardSet = new SimSet; + clipboardSet->registerObject(); + Sim::getRootGroup()->addObject(clipboardSet, "guiClipboard"); + + Vector::iterator i; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + clipboardSet->addObject(*i); + + clipboardSet->write(stream, 0); + clipboardSet->deleteObject(); +} + +void GuiEditCtrl::selectAll() +{ + GuiControl::iterator i; + if (!mCurrentAddSet) + return; + mSelectedControls.clear(); + for(i = mCurrentAddSet->begin(); i != mCurrentAddSet->end(); i++) + { + GuiControl *ctrl = dynamic_cast(*i); + mSelectedControls.push_back(ctrl); + } +} + +void GuiEditCtrl::bringToFront() +{ + if (mSelectedControls.size() != 1) + return; + + GuiControl *ctrl = *(mSelectedControls.begin()); + mCurrentAddSet->bringObjectToFront(ctrl); +} + +void GuiEditCtrl::pushToBack() +{ + if (mSelectedControls.size() != 1) + return; + + GuiControl *ctrl = *(mSelectedControls.begin()); + mCurrentAddSet->pushObjectToBack(ctrl); +} + +bool GuiEditCtrl::onKeyDown(const GuiEvent &event) +{ + if (! mActive) + return Parent::onKeyDown(event); + + if (event.modifier & SI_CTRL) + { + switch(event.keyCode) + { + case KEY_A: + selectAll(); + break; + case KEY_C: + saveSelection("gui/clipboard.gui"); + break; + case KEY_X: + saveSelection("gui/clipboard.gui"); + deleteSelection(); + break; + case KEY_V: + loadSelection("gui/clipboard.gui"); + break; + } + } + else + { + S32 delta = (event.modifier & SI_SHIFT) ? 10 : 1; + + switch(event.keyCode) + { + case KEY_RIGHT: + moveSelection(Point2I(delta, 0)); + break; + case KEY_LEFT: + moveSelection(Point2I(-delta, 0)); + break; + case KEY_UP: + moveSelection(Point2I(0, -delta)); + break; + case KEY_DOWN: + moveSelection(Point2I(0, delta)); + break; + case KEY_BACKSPACE: + case KEY_DELETE: + deleteSelection(); + break; + } + } + + return false; +} diff --git a/gui/guiEditCtrl.h b/gui/guiEditCtrl.h new file mode 100644 index 0000000..739d1a7 --- /dev/null +++ b/gui/guiEditCtrl.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIEDITCTRL_H_ +#define _GUIEDITCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +class GuiEditCtrl : public GuiControl +{ + typedef GuiControl Parent; + + Vector mSelectedControls; + GuiControl* mCurrentAddSet; + GuiControl* mContentControl; + Point2I mLastMousePos; + Point2I mSelectionAnchor; + Point2I mGridSnap; + + enum mouseModes { Selecting, MovingSelection, SizingSelection, DragSelecting }; + enum sizingModes { sizingNone = 0, sizingLeft = 1, sizingRight = 2, sizingTop = 4, sizingBottom = 8 }; + + mouseModes mMouseDownMode; + sizingModes mSizingMode; + + public: + GuiEditCtrl(); + DECLARE_CONOBJECT(GuiEditCtrl); + static void consoleInit(); + + bool onWake(); + + void select(GuiControl *ctrl); + void setRoot(GuiControl *ctrl); + void setEditMode(bool value); + S32 getSizingHitKnobs(const Point2I &pt, const RectI &box); + void getDragRect(RectI &b); + void drawNut(const Point2I &nut, bool); + void drawNuts(RectI &box, bool); + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void addNewControl(GuiControl *ctrl); + bool selectionContains(GuiControl *ctrl); + void setCurrentAddSet(GuiControl *ctrl); + void setSelection(GuiControl *ctrl, bool inclusive = false); + + 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); + + enum Justification { + JUSTIFY_LEFT, + JUSTIFY_CENTER, + JUSTIFY_RIGHT, + JUSTIFY_TOP, + JUSTIFY_BOTTOM, + SPACING_VERTICAL, + SPACING_HORIZONTAL + }; + + void justifySelection( Justification j); + void moveSelection(const Point2I &delta); + void saveSelection(const char *filename); + void loadSelection(const char *filename); + void deleteSelection(); + void selectAll(); + void bringToFront(); + void pushToBack(); +}; + +#endif //_GUI_EDIT_CTRL_H diff --git a/gui/guiFilterCtrl.cc b/gui/guiFilterCtrl.cc new file mode 100644 index 0000000..d981b9c --- /dev/null +++ b/gui/guiFilterCtrl.cc @@ -0,0 +1,251 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "dgl/gTexManager.h" +#include "GUI/guiFilterCtrl.h" +#include "Platform/event.h" +#include "Math/mMath.h" + + +GuiFilterCtrl::GuiFilterCtrl() +{ + mControlPointRequest = 7; + mFilter.setSize(7); + identity(); +} + + +void GuiFilterCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("controlPoints", TypeS32, Offset(mControlPointRequest, GuiFilterCtrl)); + addField("filter", TypeF32Vector, Offset(mFilter, GuiFilterCtrl)); +} + +static const char* cGuiFilter_GetValue(SimObject *obj, S32, const char **argv) +{ + argv; + static char buffer[512]; + const Filter *filter = static_cast(obj)->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; +} + +static void cGuiFilter_SetValue(SimObject *obj, S32 argc, const char **argv) +{ + GuiFilterCtrl *ctrl = static_cast(obj); + Filter filter; + + argc -= 2; + argv += 2; + + filter.set(argc, argv); + ctrl->set(filter); +} + +static void cGuiFilter_Identity(SimObject *obj, S32, const char **) +{ + GuiFilterCtrl *ctrl = static_cast(obj); + ctrl->identity(); +} + +void GuiFilterCtrl::consoleInit() +{ + Con::addCommand("GuiFilterCtrl", "getValue", cGuiFilter_GetValue, "guiFilterCtrl.getValue()", 2, 2); + Con::addCommand("GuiFilterCtrl", "setValue", cGuiFilter_SetValue, "guiFilterCtrl.setValue(f1, f2, ...)", 3, 20); + Con::addCommand("GuiFilterCtrl", "identity", cGuiFilter_Identity, "guiFilterCtrl.identity()", 2, 2); +} + + +bool GuiFilterCtrl::onWake() +{ + if (!Parent::onWake()) + return false; + + if (U32(mControlPointRequest) != mFilter.size()) + { + mFilter.setSize(mControlPointRequest); + identity(); + } + + return true; +} + + +void GuiFilterCtrl::identity() +{ + S32 size = mFilter.size()-1; + for (U32 i=0; S32(i) <= size; i++) + mFilter[i] = (F32)i/(F32)size; +} + + +void GuiFilterCtrl::onMouseDown(const GuiEvent &event) +{ + mouseLock(); + setFirstResponder(); + + Point2I p = globalToLocalCoord(event.mousePoint); + + // determine which knot (offset same as in onRender) + F32 w = F32(mBounds.extent.x-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), mBounds.extent.y)/(F32)mBounds.extent.y); + setUpdate(); +} + + +void GuiFilterCtrl::onMouseDragged(const GuiEvent &event) +{ + mouseLock(); + setFirstResponder(); + + Point2I p = globalToLocalCoord(event.mousePoint); + mFilter[mCurKnot] = 1.0f - F32(getMin(getMax(0, p.y), mBounds.extent.y)/(F32)mBounds.extent.y); + setUpdate(); +} + +void GuiFilterCtrl::onMouseUp(const GuiEvent &) +{ + mouseUnlock(); + if (mConsoleCommand[0]) + Con::evaluate(mConsoleCommand, false); +} + +void GuiFilterCtrl::onPreRender() +{ + if(U32(mControlPointRequest) != mFilter.size()) + { + mFilter.setSize(mControlPointRequest); + identity(); + setUpdate(); + } +} + +void GuiFilterCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + Point2I pos = offset; + Point2I ext = mBounds.extent; + + RectI r(pos, ext); + dglDrawRectFill(r, ColorI(255,255,255)); + dglDrawRect(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 + glColor3f(0.9, 0.9, 0.9); + glBegin(GL_LINES); + glVertex2i(pos.x, pos.y+ext.y); + glVertex2i(pos.x+ext.x, pos.y); + glEnd(); + + // draw the curv + glColor3f(0.4, 0.4, 0.4); + glBegin(GL_LINE_STRIP); + + F32 scale = 1.0f/F32(ext.x); + for (U32 i=0; S32(i) < ext.x; i++) + { + F32 index = F32(i)*scale; + S32 y = (S32)(ext.y*(1.0f-mFilter.getValue(index))); + glVertex2i(pos.x+i, pos.y+y ); + } + glEnd(); + + // 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); + + dglDrawRectFill(r, ColorI(255,0,0)); + } + + renderChildControls(offset, updateRect, firstResponder); +} + + + +//-------------------------------------- +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/guiFilterCtrl.h b/gui/guiFilterCtrl.h new file mode 100644 index 0000000..218e630 --- /dev/null +++ b/gui/guiFilterCtrl.h @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIFILTERCTRL_H_ +#define _GUIFILTERCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + + + +//-------------------------------------- +// helper class +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 +{ + private: + typedef GuiControl Parent; + + S32 mControlPointRequest; + S32 mCurKnot; + Filter mFilter; + + public: + //creation methods + DECLARE_CONOBJECT(GuiFilterCtrl); + GuiFilterCtrl(); + static void initPersistFields(); + static void consoleInit(); + + //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, GuiControl *firstResponder); +}; + + +inline F32 GuiFilterCtrl::getValue(S32 n) +{ + S32 index = getMin(getMax(n,0), (S32)mFilter.size()-1); + return mFilter[n]; +} + + +inline void GuiFilterCtrl::set(const Filter &f) +{ + mControlPointRequest = f.size(); + mFilter = f; +} + +#endif diff --git a/gui/guiFrameCtrl.cc b/gui/guiFrameCtrl.cc new file mode 100644 index 0000000..dc767e4 --- /dev/null +++ b/gui/guiFrameCtrl.cc @@ -0,0 +1,1048 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/****************************************************************************** + * FILENAME: D:\Tribes\darkstar\GUI\guiFrameCtrl.cc + * + * DESCRIPTION: + * + * CREATED: 10/11/99 10:01:16 + * + * BY: PeteW + ******************************************************************************/ + +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiFrameCtrl.h" + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiFrameSetCtrl); + +//----------------------------------------------------------------------------- + +static EnumTable::Enums borderStateEnums[] = +{ + { GuiFrameSetCtrl::FRAME_STATE_ON, "alwaysOn" }, + { GuiFrameSetCtrl::FRAME_STATE_OFF, "alwaysOff" }, + { GuiFrameSetCtrl::FRAME_STATE_AUTO, "dynamic" } +}; +static EnumTable gBorderStateTable(3, &borderStateEnums[0]); + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::initPersistFields() +{ + Parent::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)); +} + +//----------------------------------------------------------------------------- +static void cGFSCFrameBorderEnable(SimObject *obj, S32 argc, const char **argv) +{ + GuiFrameSetCtrl *gfsc = dynamic_cast(obj); + if (gfsc != NULL) + { + S32 index = dAtoi(argv[2]); + if (argc == 3) + gfsc->frameBorderEnable(index); + else + gfsc->frameBorderEnable(index, argv[3]); + } +} + +static void cGFSCFrameBorderMovable(SimObject *obj, S32 argc, const char **argv) +{ + GuiFrameSetCtrl *gfsc = dynamic_cast(obj); + if (gfsc != NULL) + { + S32 index = dAtoi(argv[2]); + if (argc == 3) + gfsc->frameBorderMovable(index); + else + gfsc->frameBorderMovable(index, argv[3]); + } +} + +static void cGFSCFrameMinExtent(SimObject *obj, S32, const char **argv) +{ + GuiFrameSetCtrl *gfsc = dynamic_cast(obj); + if (gfsc != NULL) + { + Point2I extent(getMax(0, dAtoi(argv[3])), getMax(0, dAtoi(argv[4]))); + gfsc->frameMinExtent(dAtoi(argv[2]), extent); + } +} + +static void cGFSCAddColumn(SimObject * obj, S32, const char **) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + Vector * columns = ctrl->columnOffsets(); + columns->push_back(0); + ctrl->balanceFrames(); +} + +static void cGFSCAddRow(SimObject * obj, S32, const char **) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + Vector * rows = ctrl->rowOffsets(); + rows->push_back(0); + ctrl->balanceFrames(); +} + +static void cGFSCRemoveColumn(SimObject * obj, S32, const char **) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + Vector * columns = ctrl->columnOffsets(); + + if(columns->size() > 0) + { + columns->setSize(columns->size() - 1); + ctrl->balanceFrames(); + } + else + Con::errorf(ConsoleLogEntry::General, "No columns exist to remove"); +} + +static void cGFSCRemoveRow(SimObject * obj, S32, const char **) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + Vector * rows = ctrl->rowOffsets(); + + if(rows->size() > 0) + { + rows->setSize(rows->size() - 1); + ctrl->balanceFrames(); + } + else + Con::errorf(ConsoleLogEntry::General, "No rows exist to remove"); +} + +static S32 cGFSCGetColumnCount(SimObject * obj, S32, const char **) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + return(ctrl->columnOffsets()->size()); +} + +static S32 cGFSCGetRowCount(SimObject * obj, S32, const char **) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + return(ctrl->rowOffsets()->size()); +} + +static S32 cGFSCGetColumnOffset(SimObject * obj, S32, const char ** argv) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + S32 index = dAtoi(argv[2]); + if(index < 0 || index > ctrl->columnOffsets()->size()) + { + Con::errorf(ConsoleLogEntry::General, "Column index out of range"); + return(0); + } + return((*ctrl->columnOffsets())[index]); +} + +static S32 cGFSCGetRowOffset(SimObject * obj, S32, const char ** argv) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + S32 index = dAtoi(argv[2]); + if(index < 0 || index > ctrl->rowOffsets()->size()) + { + Con::errorf(ConsoleLogEntry::General, "Row index out of range"); + return(0); + } + return((*ctrl->rowOffsets())[index]); +} + +static void cGFSCSetColumnOffset(SimObject * obj, S32, const char ** argv) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + + Vector & columns = *(ctrl->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; + ctrl->updateSizes(); +} + +static void cGFSCSetRowOffset(SimObject * obj, S32, const char ** argv) +{ + GuiFrameSetCtrl * ctrl = dynamic_cast(obj); + + Vector & rows = *(ctrl->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; + ctrl->updateSizes(); +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::consoleInit() +{ + Con::addCommand("GuiFrameSetCtrl", "frameBorder", cGFSCFrameBorderEnable, "gfsc.frameBorder(index, enable)", 3, 4); + Con::addCommand("GuiFrameSetCtrl", "frameMovable", cGFSCFrameBorderMovable, "gfsc.frameMovable(index, enable)", 3, 4); + Con::addCommand("GuiFrameSetCtrl", "frameMinExtent", cGFSCFrameMinExtent, "gfsc.frameMinExtent(index, w, h)", 5, 5); + Con::addCommand("GuiFrameSetCtrl", "addColumn", cGFSCAddColumn, "gfsc.addColumn();", 2, 2); + Con::addCommand("GuiFrameSetCtrl", "addRow", cGFSCAddRow, "gfsc.addRow();", 2, 2); + Con::addCommand("GuiFrameSetCtrl", "removeColumn", cGFSCRemoveColumn, "gfsc.removeColumn();", 2, 2); + Con::addCommand("GuiFrameSetCtrl", "removeRow", cGFSCRemoveRow, "gfsc.removeRow();", 2, 2); + Con::addCommand("GuiFrameSetCtrl", "getColumnCount", cGFSCGetColumnCount, "gfsc.getColumnCount();", 2, 2); + Con::addCommand("GuiFrameSetCtrl", "getRowCount", cGFSCGetRowCount, "gfsc.getRowCount();", 2, 2); + Con::addCommand("GuiFrameSetCtrl", "getColumnOffset",cGFSCGetColumnOffset, "gfsc.getColumnOffset(index);", 3, 3); + Con::addCommand("GuiFrameSetCtrl", "getRowOffset", cGFSCGetRowOffset, "gfsc.getRowOffset(index);", 3, 3); + Con::addCommand("GuiFrameSetCtrl", "setColumnOffset",cGFSCSetColumnOffset, "gfsc.setColumnOffset(index, offset);", 4, 4); + Con::addCommand("GuiFrameSetCtrl", "setRowOffset", cGFSCSetRowOffset, "gfsc.setRowOffset(index, offset);", 4, 4); +} + +//----------------------------------------------------------------------------- +GuiFrameSetCtrl::GuiFrameSetCtrl() +{ + VECTOR_SET_ASSOCIATION(mColumnOffsets); + VECTOR_SET_ASSOCIATION(mRowOffsets); + VECTOR_SET_ASSOCIATION(mFrameDetails); + + 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->mMinExtent; + 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) + 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); +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::resize(const Point2I &newPos, const Point2I &newExtent) +{ + // rebalance before losing the old extent (if required) + //if (mAutoBalance == true) + //rebalance(newExtent); + + Parent::resize(newPos, newExtent); + // compute new sizing info for the frames - takes care of resizing the children + computeSizes( mAutoBalance ); +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::onMouseMove(const GuiEvent &event) +{ + if (mFramesetDetails.mBorderEnable != FRAME_STATE_OFF && mFramesetDetails.mBorderMovable != FRAME_STATE_OFF && initCursors() == true) + { + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + Region curRegion = pointInAnyRegion(curMousePos); + switch (curRegion) + { + case VERTICAL_DIVIDER: + // change to left-right cursor + Canvas->setCursor(mLeftRightCursor); + break; + case HORIZONTAL_DIVIDER: + // change to up-down cursor + Canvas->setCursor(mUpDownCursor); + break; + case DIVIDER_INTERSECTION: + // change to move cursor + Canvas->setCursor(mMoveCursor); + break; + case NONE: + default: + // change to default cursor + Canvas->setCursor(mDefaultCursor); + 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) +{ + 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(); + } + } +} + +void GuiFrameSetCtrl::onMouseEnter(const GuiEvent &event) +{ + // change cursor to appropriate one... + onMouseMove(event); +} + +void GuiFrameSetCtrl::onMouseLeave(const GuiEvent &event) +{ + // restore cursor to default... + onMouseMove(event); +} + +//----------------------------------------------------------------------------- +bool GuiFrameSetCtrl::onAdd() +{ + if (Parent::onAdd() == false) + return(false); + + computeSizes(); + return(true); +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + RectI r(offset.x, offset.y, mBounds.extent.x, mBounds.extent.y); + + // draw the border of the frameset if specified + if (mProfile->mOpaque) + dglDrawRectFill(r, mProfile->mFillColor); + + drawDividers(offset); + + if (mProfile->mBorder) + dglDrawRect(r, mProfile->mBorderColor); + + // draw the frame contents + renderChildControls(offset, updateRect, firstResponder); +} + +//----------------------------------------------------------------------------- +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); + } + } + } + } + mMoveCursor = NULL; + mUpDownCursor = NULL; + mLeftRightCursor = NULL; + mDefaultCursor = NULL; + initCursors(); + 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); +} + +//----------------------------------------------------------------------------- +bool GuiFrameSetCtrl::initCursors() +{ + if (mMoveCursor == NULL || mUpDownCursor == NULL || mLeftRightCursor == NULL || mDefaultCursor == NULL) + { + SimObject *obj; + obj = Sim::findObject("MoveCursor"); + mMoveCursor = dynamic_cast(obj); + obj = Sim::findObject("UpDownCursor"); + mUpDownCursor = dynamic_cast(obj); + obj = Sim::findObject("LeftRightCursor"); + mLeftRightCursor = dynamic_cast(obj); + obj = Sim::findObject("DefaultCursor"); + mDefaultCursor = dynamic_cast(obj); + return(mMoveCursor != NULL && mUpDownCursor != NULL && mLeftRightCursor != NULL && mDefaultCursor != NULL); + } + else + 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 - ( mBounds.extent.x - 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 - ( mBounds.extent.y - 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(mBounds.extent.x - vDividers * mFramesetDetails.mBorderWidth); + F32 totHeight = F32(mBounds.extent.y - 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; + GuiControl *gc; + S32 column = 0; + S32 row = 0; + Point2I newPos; + Point2I newExtent; + // step through all the children + for (soitr = begin(); soitr != end(); soitr++) + { + // 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 = mBounds.extent; + 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 = mBounds.extent.x - 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 = mBounds.extent.y - mRowOffsets[row]; // last row + else + newExtent.y = mRowOffsets[row + 1] - mRowOffsets[row] - mFramesetDetails.mBorderWidth; // any other row + // 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) +{ + newExtent; +/* + // look at old_width and old_height - current extent + F32 widthScale = (F32)newExtent.x/(F32)mBounds.extent.x; + F32 heightScale = (F32)newExtent.y/(F32)mBounds.extent.y; + 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]; + 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)) ? mBounds.extent.x : 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)) ? mBounds.extent.y : 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)) ? mBounds.extent.x : 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)) ? mBounds.extent.y : 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, mBounds.extent.y - ( 2 * mFudgeFactor ) ); + dglDrawRectFill(r, mFramesetDetails.mBorderColor); + } + for (itr = mRowOffsets.begin() + 1; itr < mRowOffsets.end(); itr++) + { + r.point = Point2I(mFudgeFactor, *itr - mFramesetDetails.mBorderWidth) + offset; + r.extent.set(mBounds.extent.x - ( 2 * mFudgeFactor ), mFramesetDetails.mBorderWidth); + dglDrawRectFill(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; +} diff --git a/gui/guiFrameCtrl.h b/gui/guiFrameCtrl.h new file mode 100644 index 0000000..ac0c950 --- /dev/null +++ b/gui/guiFrameCtrl.h @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/****************************************************************************** + * FILENAME: D:\Tribes\darkstar\GUI\guiFrameCtrl.h + * + * DESCRIPTION: 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. + * + * CREATED: 10/08/99 14:40:24 + * + * BY: PeteW + ******************************************************************************/ + +#ifndef _GUIFRAMECTRL_H_ +#define _GUIFRAMECTRL_H_ + +#define DISABLE_COPY_CTOR(className) \ + className(const className &) + +#define DISABLE_ASSIGNMENT(className) \ + className& operator=(const className &) + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +// for debugging porpoises... +#define GUI_FRAME_DEBUG +// ...save the porpoises + +class GuiFrameSetCtrl : public GuiControl +{ +private: + typedef GuiControl 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 = 20 + }; + enum Region + { + VERTICAL_DIVIDER, + HORIZONTAL_DIVIDER, + DIVIDER_INTERSECTION, + NONE + }; + struct FrameDetail + { + U32 mBorderWidth; + ColorI mBorderColor; + S32 mBorderEnable; + S32 mBorderMovable; + Point2I mMinExtent; + FrameDetail() { mBorderWidth = DEFAULT_BORDER_WIDTH; mBorderEnable = FRAME_STATE_AUTO; mBorderMovable = FRAME_STATE_AUTO; mMinExtent.set(DEFAULT_MIN_FRAME_EXTENT, DEFAULT_MIN_FRAME_EXTENT); } + }; + DECLARE_CONOBJECT(GuiFrameSetCtrl); + static void consoleInit(); + static void initPersistFields(); + + GuiFrameSetCtrl(); + GuiFrameSetCtrl(U32 columns, U32 rows, const U32 columnOffsets[] = NULL, const U32 rowOffsets[] = NULL); + virtual ~GuiFrameSetCtrl(); + + void addObject(SimObject *object); + void removeObject(SimObject *object); + + virtual void resize(const Point2I &newPos, const Point2I &newExtent); + + virtual void onMouseMove(const GuiEvent &event); + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + virtual void onMouseEnter(const GuiEvent &event); + virtual void onMouseLeave(const GuiEvent &event); + + bool onAdd(); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); +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[]); + bool initCursors(); + + 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); + + 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 balanceFrames() { computeSizes(true); } + void updateSizes() { computeSizes(); } + +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(mBounds.extent.y))); +} + +//----------------------------------------------------------------------------- +// 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(mBounds.extent.x)) && (point.y >= S32(y - mFramesetDetails.mBorderWidth)) && (point.y < y)); +} + +#endif // _GUI_FRAME_CTRL_H diff --git a/gui/guiHelpCtrl.cc b/gui/guiHelpCtrl.cc new file mode 100644 index 0000000..f9dc20f --- /dev/null +++ b/gui/guiHelpCtrl.cc @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "g_surfac.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiHelpCtrl.h" + +GuiHelpCtrl::GuiHelpCtrl(void) +{ + //set the id + id = guiHelpObjectID; + + mHelpTag = 0; + mHelpText = NULL; +} + +GuiHelpCtrl::~GuiHelpCtrl() +{ + if (mHelpText) + { + delete [] (char*)mHelpText; + mHelpText = NULL; + } +} + +bool GuiHelpCtrl::onAdd(void) +{ + if (! Parent::onAdd()) + return FALSE; + + setGFXFont(mFont, mFontString); + setGFXFont(mFontHL, mFontStringHL); + setGFXFont(mFontNA, mFontStringNA); + + return TRUE; +} + +void GuiHelpCtrl::render(GFXSurface *sfc) +{ + if ((! mHelpText) || (! mHelpText[0])) + return; + + //if opaque, fill the update rect with the fill color + if (mOpaque) + { + sfc->drawRect2d_f(&RectI(mPosition, Point2I(mPosition.x + mExtent.x - 1, mPosition.y + mExtent.y - 1)), mFillColor); + } + + //if there's a boarder, draw the boarder + if (mBorder) + { + sfc->drawRect2d(&RectI(mPosition, Point2I(mPosition.x + mExtent.x - 1, mPosition.y + mExtent.y - 1)), mBorderColor); + } + + //draw the help text + sfc->drawText_p(mFont, &Point2I(mPosition.x + 2, mPosition.y), mHelpText); +} + +void GuiHelpCtrl::setHelpText(guiCanvas *root, const char *text, F32 /* rElapsedTime */, bool /*mouseClicked*/) +{ + Point2I newPos, newExt; + if (mHelpText) + { + delete [] (char*)mHelpText; + mHelpText = NULL; + newPos.set(0, 0); + newExt.set(1, 1); + } + + if (text && text[0]) + { + mHelpText = strnew(text); + mHelpTag = 0; + + if (bool(mFont) && root) + { + newExt.set(mFont->getStrWidth(text) + 4, mFont->getHeight() + 4); + newPos = root->getCursorPos(); + newPos.x = MAX(0, MIN(newPos.x, root->mExtent.x - newExt.x)); + newPos.y = MAX(0, MIN(newPos.y, root->mExtent.y - newExt.y)); + } + } + resize(newPos, newExt); +} + +void GuiHelpCtrl::setHelpTag(guiCanvas *root, S32 helpTag, F32 /* rElapsedTime */, bool /*mouseClicked*/) +{ + if (mHelpText) + { + delete [] (char*)mHelpText; + mHelpText = NULL; + } + + mHelpTag = helpTag; +} diff --git a/gui/guiHelpCtrl.h b/gui/guiHelpCtrl.h new file mode 100644 index 0000000..2343a26 --- /dev/null +++ b/gui/guiHelpCtrl.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIHELPCTRL_H_ +#define _GUIHELPCTRL_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "GUI/guiTextCtrl.h" +#endif + +class guiCanvas; + +class GuiHelpCtrl : public guiTextCtrl +{ + +private: + typedef guiTextCtrl Parent; + +protected: + S32 mHelpTag; + const char *mHelpText; + +public: + GuiHelpCtrl(); + ~GuiHelpCtrl(); + + bool onAdd(void); + + virtual void setHelpText(guiCanvas *root, const char *text, F32 timeElapsed, bool mouseClicked = FALSE); + virtual void setHelpTag(guiCanvas *root, S32 helpTag, F32 timeElapsed, bool mouseClicked = FALSE); + virtual void render(GFXSurface *sfc); + + DECLARE_PERSISTENT(GuiHelpCtrl); +}; + +#endif //_GUI_HELP_CTRL_H diff --git a/gui/guiInputCtrl.cc b/gui/guiInputCtrl.cc new file mode 100644 index 0000000..24f1e6c --- /dev/null +++ b/gui/guiInputCtrl.cc @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "gui/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 ); + + 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 InputEvent &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, 3, "onInputEvent", deviceString, actionString ); + 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, 3, "onInputEvent", "keyboard", keyString ); + return( true ); + } + } + + return( false ); +} diff --git a/gui/guiInputCtrl.h b/gui/guiInputCtrl.h new file mode 100644 index 0000000..026f2dc --- /dev/null +++ b/gui/guiInputCtrl.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIINPUTCTRL_H_ +#define _GUIINPUTCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif +#ifndef _EVENT_H_ +#include "Platform/event.h" +#endif + +class GuiInputCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + + public: + DECLARE_CONOBJECT(GuiInputCtrl); + + bool onWake(); + void onSleep(); + + bool onInputEvent( const InputEvent &event ); +}; + +#endif // _GUI_INPUTCTRL_H diff --git a/gui/guiInspector.cc b/gui/guiInspector.cc new file mode 100644 index 0000000..23f0516 --- /dev/null +++ b/gui/guiInspector.cc @@ -0,0 +1,491 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/dgl.h" +#include "gui/guiTypes.h" +#include "gui/guiTextCtrl.h" +#include "gui/guiTextEditCtrl.h" +#include "gui/guiInspector.h" +#include "gui/guiCheckBoxCtrl.h" +#include "gui/guiPopUpCtrl.h" +#include "platform/event.h" + +// datablocks +#include "game/gameBase.h" +#include "game/explosion.h" +#include "game/particleEngine.h" +#include "game/projectile.h" +#include "sim/cannedChatDataBlock.h" +#include "game/Debris.h" +#include "game/commanderMapIcon.h" +#include "game/shockwave.h" +#include "game/splash.h" +#include "game/shieldImpact.h" +#include "game/projEnergy.h" +#include "game/projBomb.h" + +#define NULL_STRING "" + +GuiInspector::GuiInspector() +{ + mEditControlOffset = 5; + mEntryHeight = 16; + mTextExtent = 80; + mEntrySpacing = 2; + mMaxMenuExtent = 80; +} + +void GuiInspector::onRemove() +{ + mTarget = 0; + while(size()) + first()->deleteObject(); + Parent::onRemove(); +} + +//------------------------------------------------------------------------------ +static S32 QSORT_CALLBACK stringCompare(const void *a,const void *b) +{ + StringTableEntry sa = *(StringTableEntry*)a; + StringTableEntry sb = *(StringTableEntry*)b; + return(dStricmp(sa, sb)); +} + +void GuiInspector::inspect(SimObject * obj) +{ + mTarget = obj; + + while(size()) + first()->deleteObject(); + + S32 curYOffset = mEntrySpacing; + + if(!bool(mTarget)) + { + resize(mBounds.point, Point2I(mBounds.extent.x, curYOffset)); + return; + } + + // add in the static fields + AbstractClassRep::FieldList fieldList = mTarget->getFieldList(); + AbstractClassRep::FieldList::iterator itr; + for(itr = fieldList.begin(); itr != fieldList.end(); itr++) + { + if(itr->type == AbstractClassRep::DepricatedFieldType) + continue; + + char fdata[1024]; + const char * dstr = Con::getData(itr->type, (void *)(S32(obj) + itr->offset), 0, itr->table, itr->flag); + if(!dstr) + dstr = ""; + expandEscape(fdata, dstr); + + GuiTextCtrl * textCtrl = new GuiTextCtrl(); + textCtrl->setField("profile", "GuiTextProfile"); + textCtrl->setField("text", itr->pFieldname); + textCtrl->registerObject(); + addObject(textCtrl); + + S32 textWidth = textCtrl->mProfile->mFont->getStrWidth(itr->pFieldname); + S32 xStartPoint = (textWidth < (mTextExtent + mEntrySpacing + mEditControlOffset)) ? + (mEntrySpacing + mTextExtent) : textWidth + mEditControlOffset; + + textCtrl->mBounds.point = Point2I(mEntrySpacing, curYOffset); + textCtrl->mBounds.extent = Point2I(textWidth, mEntryHeight); + + S32 maxWidth = mBounds.extent.x - xStartPoint - mEntrySpacing; + + //now add the field + GuiControl * editControl = NULL; + + switch(itr->type) + { + // text control + default: + { + GuiTextEditCtrl * edit = new GuiTextEditCtrl(); + edit->setField("profile", "GuiInspectorTextEditProfile"); + edit->setField("text", fdata); + editControl = edit; + + edit->mBounds.point = Point2I(xStartPoint, curYOffset); + edit->mBounds.extent = Point2I(maxWidth, mEntryHeight); + edit->setSizing(GuiControl::horizResizeWidth, GuiControl::vertResizeBottom); + break; + } + + // checkbox + case TypeBool: + case TypeFlag: + { + GuiCheckBoxCtrl * checkBox = new GuiCheckBoxCtrl(); + checkBox->setField("profile", "GuiCheckBoxProfile"); + checkBox->mBounds.point = Point2I(xStartPoint, curYOffset); + checkBox->mBounds.extent = Point2I(mEntryHeight, mEntryHeight); + checkBox->setScriptValue(fdata); + + editControl = checkBox; + break; + } + + // dropdown list + case TypeEnum: + { + AssertFatal(itr->table, "TypeEnum declared with NULL table"); + GuiPopUpMenuCtrl * menu = new GuiPopUpMenuCtrl(); + menu->setField("profile", "GuiPopUpMenuProfile"); + menu->setField("text", fdata); + + menu->mBounds.point = Point2I(xStartPoint, curYOffset); + menu->mBounds.extent = Point2I(getMin(maxWidth, mMaxMenuExtent), mEntryHeight); + + //now add the entries + for(S32 i = 0; i < itr->table->size; i++) + menu->addEntry(itr->table->table[i].label, itr->table->table[i].index); + + editControl = menu; + break; + } + + // guiprofiles + case TypeGuiProfile: + { + GuiPopUpMenuCtrl * menu = new GuiPopUpMenuCtrl(); + menu->setField("profile", "GuiPopUpMenuProfile"); + menu->setField("text", *fdata ? fdata : NULL_STRING); + menu->mBounds.point = Point2I(xStartPoint, curYOffset); + menu->mBounds.extent = Point2I(getMin(maxWidth, mMaxMenuExtent), mEntryHeight); + + // add 'NULL' + menu->addEntry(NULL_STRING, -1); + + // add entries to list so they can be sorted prior to adding to menu (want null on top) + Vector entries; + + SimGroup * grp = Sim::getGuiDataGroup(); + for(SimGroup::iterator i = grp->begin(); i != grp->end(); i++) + { + GuiControlProfile * profile = dynamic_cast(*i); + if(profile) + entries.push_back(profile->getName()); + } + + // sort the entries + dQsort(entries.address(), entries.size(), sizeof(StringTableEntry), stringCompare); + for(U32 j = 0; j < entries.size(); j++) + menu->addEntry(entries[j], 0); + + editControl = menu; + break; + } + + // datablock types + case TypeGameBaseDataPtr: + case TypeExplosionDataPtr: + case TypeShockwaveDataPtr: + case TypeSplashDataPtr: + case TypeEnergyProjectileDataPtr: + case TypeBombProjectileDataPtr: + case TypeParticleEmitterDataPtr: + case TypeAudioDescriptionPtr: + case TypeAudioProfilePtr: + case TypeProjectileDataPtr: + case TypeCannedChatItemPtr: + case TypeDebrisDataPtr: + case TypeCommanderIconDataPtr: + { + GuiPopUpMenuCtrl * menu = new GuiPopUpMenuCtrl(); + menu->setField("profile", "GuiPopUpMenuProfile"); + menu->setField("text", *fdata ? fdata : NULL_STRING); + menu->mBounds.point = Point2I(xStartPoint, curYOffset); + menu->mBounds.extent = Point2I(getMin(maxWidth, mMaxMenuExtent), mEntryHeight); + + // add the 'NULL' entry on top + menu->addEntry(NULL_STRING, -1); + + // add to a list so they can be sorted + Vector entries; + + SimGroup * grp = Sim::getDataBlockGroup(); + for(SimGroup::iterator i = grp->begin(); i != grp->end(); i++) + { + SimObject * obj = 0; + switch(itr->type) + { + case TypeGameBaseDataPtr: + obj = dynamic_cast(*i); + break; + case TypeExplosionDataPtr: + obj = dynamic_cast(*i); + break; + case TypeShockwaveDataPtr: + obj = dynamic_cast(*i); + break; + case TypeSplashDataPtr: + obj = dynamic_cast(*i); + break; + case TypeEnergyProjectileDataPtr: + obj = dynamic_cast(*i); + break; + case TypeBombProjectileDataPtr: + obj = dynamic_cast(*i); + break; + case TypeParticleEmitterDataPtr: + obj = dynamic_cast(*i); + break; + case TypeAudioDescriptionPtr: + obj = dynamic_cast(*i); + break; + case TypeAudioProfilePtr: + obj = dynamic_cast(*i); + break; + case TypeProjectileDataPtr: + obj = dynamic_cast(*i); + break; + case TypeCannedChatItemPtr: + obj = dynamic_cast(*i); + break; + case TypeDebrisDataPtr: + obj = dynamic_cast(*i); + break; + case TypeCommanderIconDataPtr: + obj = dynamic_cast(*i); + break; + } + + if(obj) + entries.push_back(obj->getName()); + } + + // sort the entries + dQsort(entries.address(), entries.size(), sizeof(StringTableEntry), stringCompare); + for(U32 j = 0; j < entries.size(); j++) + menu->addEntry(entries[j], 0); + + editControl = menu; + break; + } + } + + if(editControl) + { + char buf[256]; + dSprintf(buf, sizeof(buf), "InspectStatic%s", itr->pFieldname); + editControl->registerObject(buf); + addObject(editControl); + } + + curYOffset += (mEntryHeight + mEntrySpacing); + } + + // dynamic field seperator: text + GuiTextCtrl * textCtrl = new GuiTextCtrl(); + textCtrl->setField("profile", "GuiTextProfile"); + textCtrl->setField("text", " Dynamic Fields"); + textCtrl->registerObject(); + textCtrl->mBounds.point = Point2I(mEntrySpacing, curYOffset); + textCtrl->mBounds.extent = Point2I(mTextExtent, mEntryHeight); + addObject(textCtrl); + + // button + GuiButtonCtrl * button = new GuiButtonCtrl(); + button->setField("profile", "GuiButtonProfile"); + button->setField("text", "Add"); + + Con::setIntVariable("InspectingObject", mTarget->getId()); + Con::setIntVariable("Inspector", getId()); + + S32 textWidth = textCtrl->mProfile->mFont->getStrWidth(textCtrl->getScriptValue()); + S32 xStartPoint = (textWidth < (mTextExtent + mEntrySpacing + mEditControlOffset)) ? + (mEntrySpacing + mTextExtent) : textWidth + mEditControlOffset; + S32 maxWidth = mBounds.extent.x - xStartPoint - mEntrySpacing; + + button->mBounds.point = Point2I(xStartPoint, curYOffset); + button->mBounds.extent = Point2I(getMin(maxWidth, mMaxMenuExtent), mEntryHeight); + button->registerObject(); + + char buf[1024]; + dSprintf(buf, sizeof(buf), "%d.addDynamicField(%d);", getId(), mTarget->getId()); + button->setField("command", buf); + addObject(button); + + // offset + curYOffset += (mEntryHeight + mEntrySpacing); + + // add the dynamic fields + SimFieldDictionary * fieldDictionary = mTarget->getFieldDictionary(); + for(SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr) + { + SimFieldDictionary::Entry * entry = (*ditr); + + // create the name ctrl + GuiTextCtrl * nameCtrl = new GuiTextCtrl(); + + nameCtrl->setField("profile", "GuiTextProfile"); + nameCtrl->setField("text", entry->slotName); + nameCtrl->registerObject(); + addObject(nameCtrl); + + nameCtrl->mBounds.point = Point2I(mEntrySpacing, curYOffset); + nameCtrl->mBounds.extent = Point2I(mTextExtent, mEntryHeight); + + // add a 'remove' button + GuiButtonCtrl * button = new GuiButtonCtrl(); + + button->setField("profile", "GuiButtonProfile"); + button->setField("text", "x"); + button->registerObject(); + addObject(button); + + char buf[1024]; + dSprintf(buf, sizeof(buf), "%d.%s = \"\";%d.inspect(%d);", mTarget->getId(), entry->slotName, getId(), mTarget->getId()); + button->setField("command", buf); + + S32 textWidth = mProfile->mFont->getStrWidth(entry->slotName); + + // 10x10 with 2x2 frame + button->mBounds.point.set(textWidth + 4, curYOffset + 2); + button->mBounds.extent.set(10, 10); + + textWidth += 14; + S32 xStartPoint = (textWidth < (mTextExtent + mEntrySpacing + mEditControlOffset)) ? + (mEntrySpacing + mTextExtent) : textWidth + mEditControlOffset; + S32 maxWidth = mBounds.extent.x - xStartPoint - mEntrySpacing; + + expandEscape(buf, entry->value ? entry->value : ""); + + // create the edit ctrl + GuiTextEditCtrl * editCtrl = new GuiTextEditCtrl(); + editCtrl->setField("profile", "GuiInspectorTextEditProfile"); + editCtrl->setField("text", buf); + editCtrl->setSizing(GuiControl::horizResizeWidth, GuiControl::vertResizeBottom); + + dSprintf(buf, sizeof(buf), "InspectDynamic%s", entry->slotName); + editCtrl->registerObject(buf); + addObject(editCtrl); + + editCtrl->mBounds.point = Point2I(xStartPoint, curYOffset); + editCtrl->mBounds.extent = Point2I(maxWidth, mEntryHeight); + + curYOffset += (mEntryHeight + mEntrySpacing); + } + + resize(mBounds.point, Point2I(mBounds.extent.x, curYOffset)); +} + +void GuiInspector::apply(const char * newName) +{ + if(!bool(mTarget)) + { + while(size()) + first()->deleteObject(); + return; + } + + mTarget->assignName(newName); + mTarget->inspectPreApply(); + + SimObject * obj = static_cast(mTarget); + + //now add in the fields + AbstractClassRep::FieldList fieldList = mTarget->getFieldList(); + AbstractClassRep::FieldList::iterator itr; + for(itr = fieldList.begin(); itr != fieldList.end(); itr++) + { + if(itr->type == AbstractClassRep::DepricatedFieldType) + continue; + + char fdata[1024]; + dSprintf(fdata, sizeof(fdata), "InspectStatic%s", itr->pFieldname); + GuiControl * editCtrl = NULL; + SimObject * inspectObj = Sim::findObject(fdata); + if(inspectObj) + editCtrl = dynamic_cast(inspectObj); + if(!editCtrl) + continue; + + const char * newValue = 0; + + // check for null on profiles (-1 popup id) + GuiPopUpMenuCtrl * menu = dynamic_cast(editCtrl); + if(!(menu && (menu->getSelected() == -1))) + newValue = editCtrl->getScriptValue(); + + if(!newValue) + newValue = ""; + + dStrcpy(fdata, newValue); + collapseEscape(fdata); + + //now set the field + const char *argv[1]; + argv[0] = &fdata[0]; + Con::setData(itr->type, (void *)(S32(obj) + itr->offset), 0, 1, argv, itr->table, itr->flag); + } + + // get the dynamic field data + SimFieldDictionary * fieldDictionary = mTarget->getFieldDictionary(); + for(SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr) + { + SimFieldDictionary::Entry * entry = (*ditr); + + char buf[1024]; + dSprintf(buf, sizeof(buf), "InspectDynamic%s", entry->slotName); + + GuiControl * editCtrl = static_cast(Sim::findObject(buf)); + if(!editCtrl) + continue; + + const char * newValue = editCtrl->getScriptValue(); + dStrcpy(buf, newValue ? newValue : ""); + collapseEscape(buf); + + fieldDictionary->setFieldValue(entry->slotName, buf); + } + + mTarget->inspectPostApply(); + + //now re-inspect the object + inspect(mTarget); +} + +//------------------------------------------------------------------------------ +static void cInspect(SimObject *obj, S32, const char **argv) +{ + GuiInspector * inspector = static_cast(obj); + SimObject * target = Sim::findObject(argv[2]); + if(!target) + { + Con::printf("%s(): invalid object: %s", argv[0], argv[2]); + return; + } + inspector->inspect(target); +} + +static void cApply(SimObject *obj, S32, const char **argv) +{ + GuiInspector *inspector = static_cast(obj); + inspector->apply(argv[2]); +} + +void GuiInspector::consoleInit() +{ + Con::addCommand("GuiInspector", "inspect", cInspect, "inspector.inspect(obj)", 3, 3); + Con::addCommand("GuiInspector", "apply", cApply, "inspector.apply(newName)", 3, 3); +} + +void GuiInspector::initPersistFields() +{ + Parent::initPersistFields(); + addField("editControlOffset", TypeS32, Offset(mEditControlOffset, GuiInspector)); + addField("entryHeight", TypeS32, Offset(mEntryHeight, GuiInspector)); + addField("textExtent", TypeS32, Offset(mTextExtent, GuiInspector)); + addField("entrySpacing", TypeS32, Offset(mEntrySpacing, GuiInspector)); + addField("maxMenuExtent", TypeS32, Offset(mMaxMenuExtent, GuiInspector)); +} diff --git a/gui/guiInspector.h b/gui/guiInspector.h new file mode 100644 index 0000000..a697246 --- /dev/null +++ b/gui/guiInspector.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIINSPECTOR_H_ +#define _GUIINSPECTOR_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif +#ifndef _GUIARRAYCTRL_H_ +#include "GUI/guiArrayCtrl.h" +#endif + +class GuiInspector : public GuiControl +{ + private: + typedef GuiControl Parent; + SimObjectPtr mTarget; + + public: + DECLARE_CONOBJECT(GuiInspector); + + GuiInspector(); + + // field data + S32 mEditControlOffset; + S32 mEntryHeight; + S32 mTextExtent; + S32 mEntrySpacing; + S32 mMaxMenuExtent; + + static void consoleInit(); + static void initPersistFields(); + void onRemove(); + + void inspect(SimObject *); + void apply(const char *); +}; + +#endif diff --git a/gui/guiMLTextCtrl.cc b/gui/guiMLTextCtrl.cc new file mode 100644 index 0000000..17b8a18 --- /dev/null +++ b/gui/guiMLTextCtrl.cc @@ -0,0 +1,1990 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "gui/guiMLTextCtrl.h" +#include "gui/guiScrollCtrl.h" +#include "dgl/dgl.h" +#include "console/consoleTypes.h" +#include "gui/guiCanvas.h" + +IMPLEMENT_CONOBJECT(GuiMLTextCtrl); + +const U32 GuiMLTextCtrl::csmTextBufferGrowthSize = 1024; + +namespace { + +static void cMLTextSetText(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-mltextctrl get here?"); + + GuiMLTextCtrl* textCtrl = static_cast(obj); + textCtrl->setText(argv[2], dStrlen(argv[2])); +} + +static const char* cMLTextGetText(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-mltextctrl get here?"); + + GuiMLTextCtrl* textCtrl = static_cast(obj); + return( textCtrl->getTextContent() ); +} + +static void cMLTextAddText(SimObject* obj, S32, const char **argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-mltextctrl get here?"); + + GuiMLTextCtrl* textCtrl = static_cast(obj); + textCtrl->addText(argv[2], dStrlen(argv[2]), dAtob(argv[3])); +} + +static bool cMLTextSetCursorPosition(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-mltextctrl get here?"); + + GuiMLTextCtrl* textCtrl = static_cast(obj); + return textCtrl->setCursorPosition(dAtoi(argv[2])); +} + +static void cMLTextScrollToTag( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cMLTextScrollToTag is not a GuiMLTextCtrl!" ); + GuiMLTextCtrl* textCtrl = static_cast( obj ); + textCtrl->scrollToTag( dAtoi( argv[2] ) ); +} + +static void cMLTextScrollToTop( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cMLTextScrollToTop is not a GuiMLTextCtrl!" ); + GuiMLTextCtrl* textCtrl = static_cast( obj ); + textCtrl->scrollToTop(); +} + +static const char* cStripMLControlChars(SimObject*, S32, const char** argv) +{ + return GuiMLTextCtrl::stripControlChars(argv[1]); +} + + +} // namespace {} + + + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::GuiMLTextCtrl() +{ + mIsEditCtrl = false; + + mCursorPosition = 0; + + mTextBuffer = new char[csmTextBufferGrowthSize]; + mCurrTextSize = 0; + mCurrBufferSize = csmTextBufferGrowthSize; + mTextBuffer[mCurrTextSize] = 0; + mMaxBufferSize = -1; + + mSelectionActive = false; + mSelectionStart = 0; + mSelectionEnd = 0; + + mLineSpacingPixels = 2; + mAllowColorChars = false; + mBitmapList = 0; + mBitmapRefList = 0; + mFontList = 0; + mDirty = true; + mLineList = 0; + mTagList = 0; + mHitURL = 0; + mActive = true; + + mDeniedSound = dynamic_cast( Sim::findObject( "InputDeniedSound" ) ); +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::~GuiMLTextCtrl() +{ + delete [] mTextBuffer; + mTextBuffer = NULL; + mCurrTextSize = 0; + mCurrBufferSize = 0; + + mCursorPosition = 0; + + mSelectionActive = false; + mSelectionStart = 0; + mSelectionEnd = 0; + freeResources(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("lineSpacing", TypeS32, Offset(mLineSpacingPixels, GuiMLTextCtrl)); + addField("allowColorChars", TypeBool, Offset(mAllowColorChars, GuiMLTextCtrl)); + addField("maxChars", TypeS32, Offset(mMaxBufferSize, GuiMLTextCtrl)); + addField("deniedSound", TypeAudioProfilePtr, Offset(mDeniedSound, GuiMLTextCtrl)); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::consoleInit() +{ + Con::addCommand("GuiMLTextCtrl", "setText", cMLTextSetText, "[MLTextCtrl].setText(\"text\");", 3, 3); + Con::addCommand("GuiMLTextCtrl", "getText", cMLTextGetText, "[MLTextCtrl].getText();", 2, 2); + Con::addCommand("GuiMLTextCtrl", "addText", cMLTextAddText, "[MLTextCtrl].addText(\"text\", reformatBool);", 4, 4); + Con::addCommand("GuiMLTextCtrl", "setCursorPosition", cMLTextSetCursorPosition, "[MLTextCtrl].setCursorPosition([new position]);", 3, 3); + Con::addCommand("GuiMLTextCtrl", "scrollToTag", cMLTextScrollToTag, "[MLTextCtrl].scrollToTag([tag id]);", 3, 3); + Con::addCommand("GuiMLTextCtrl", "scrollToTop", cMLTextScrollToTop, "[MLTextCtrl].scrollToTop();", 2, 2); + + Con::addCommand("StripMLControlChars", cStripMLControlChars, "StripMLControlChars(string);", 2, 2); +} + +//-------------------------------------------------------------------------- +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) + xOff += font->getStrNWidth(mTextBuffer + atom->textStart, start - atom->textStart); + + Point2I drawPoint(offset.x + atom->xStart + xOff, offset.y + atom->yStart); + + ColorI color = atom->style->color; + if(atom->url && atom->url->mouseDown) + { + color.red = 255 - color.red; + color.green = 255 - color.green; + color.blue = 255 - color.blue; + color.alpha = 255; + } + + if(!sel) + { + dglSetBitmapModulation(color); + dglDrawTextN(font, drawPoint, mTextBuffer + start, 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(mTextBuffer + start, end - start); + dglDrawTextN(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(mTextBuffer + start, end - start) + 1; + rect.extent.y = line->height + 1; + + dglDrawRectFill(rect, mProfile->mFillColorHL); + dglSetBitmapModulation( mProfile->mFontColorHL ); // over-ride atom color: + dglDrawTextN(font, drawPoint, mTextBuffer + start, 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(mTextBuffer + start, end - start); + dglDrawTextN(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(mTextBuffer + start, end - start); + dglDrawLine(drawPoint, p2, color); + } +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::onRender(Point2I offset, const RectI& updateRect, GuiControl* firstResponder) +{ + Parent::onRender(offset, updateRect, firstResponder); + // draw all the bitmaps + for(BitmapRef *walk = mBitmapRefList; walk; walk = walk->next) + { + RectI screenBounds = *walk; + screenBounds.point += offset; + if(!screenBounds.overlaps(updateRect)) + continue; + dglClearBitmapModulation(); + dglDrawBitmap(walk->bitmap->bitmapHandle, screenBounds.point); + //dglDrawRectFill(screenBounds, mProfile->mFillColor); + } + + // draw all the text and dividerStyles + for(Line *lwalk = mLineList; lwalk; lwalk = lwalk->next) + { + RectI lineRect(offset.x, offset.y + lwalk->y, mBounds.extent.x, lwalk->height); + if(!lineRect.overlaps(updateRect)) + continue; + if(lwalk->divStyle) + dglDrawRectFill(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); + } + } + } + dglClearBitmapModulation(); +} + +//-------------------------------------------------------------------------- +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) + { + delete[] walk->faceName; + walk->fontRes = 0; + } + for(Bitmap* bwalk = mBitmapList; bwalk; bwalk = bwalk->next) + bwalk->bitmapHandle = 0; + mFontList = NULL; + mBitmapList = NULL; + mResourceChunker.freeBlocks(); + mDirty = true; + freeLineBuffers(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::onSleep() +{ + freeResources(); + Parent::onSleep(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::inspectPostApply() +{ + Parent::inspectPostApply(); + + if (mLineSpacingPixels < 0) + mLineSpacingPixels = 0; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::resize( const Point2I& newPosition, const Point2I& newExtent ) +{ + Parent::resize( newPosition, newExtent ); + //Con::executef( this, 3, "onResize", Con::getIntArg( newExtent.x ), Con::getIntArg( newExtent.y ) ); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::parentResized(const Point2I& oldParentExtent, + const Point2I& newParentExtent) +{ + Parent::parentResized(oldParentExtent, newParentExtent); + mDirty = true; +} + +//-------------------------------------------------------------------------- +U32 GuiMLTextCtrl::getNumChars() const +{ + return mCurrTextSize; +} + +//-------------------------------------------------------------------------- +U32 GuiMLTextCtrl::getText(char* pBuffer, const U32 bufferSize) const +{ + U32 ret = mCurrTextSize; + if (ret > bufferSize) + ret = bufferSize; + + dMemcpy(pBuffer, mTextBuffer, ret); + return ret; +} + +//-------------------------------------------------------------------------- +const char* GuiMLTextCtrl::getTextContent() +{ + if ( mCurrTextSize > 0 && mTextBuffer ) + { + char* returnString = Con::getReturnBuffer( mCurrTextSize + 1 ); + dStrcpy( returnString, mTextBuffer ); + return( returnString ); + } + + return( "" ); +} + +//-------------------------------------------------------------------------- +const char *GuiMLTextCtrl::getScriptValue() +{ + mTextBuffer[mCurrTextSize] = 0; + return mTextBuffer; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::setScriptValue(const char *newText) +{ + setText(newText, dStrlen(newText)); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::setText(const char* textBuffer, const U32 numChars) +{ + delete [] mTextBuffer; + mTextBuffer = NULL; + mCurrTextSize = 0; + mCurrBufferSize = 0; + + S32 numCharsToUse = numChars; + if (mMaxBufferSize > 0 && numCharsToUse > mMaxBufferSize - 1) + numCharsToUse = mMaxBufferSize - 1; + + mCurrBufferSize = (( (numCharsToUse + 1) / csmTextBufferGrowthSize) + 1) * csmTextBufferGrowthSize; + mTextBuffer = new char[mCurrBufferSize]; + AssertFatal(mCurrBufferSize > numCharsToUse, "GuiMLTexCtrl::setTextError: bad calculation of curr buffer size"); + + dMemcpy(mTextBuffer, textBuffer, numCharsToUse); + mCurrTextSize = numCharsToUse; + mTextBuffer[mCurrTextSize] = 0; + + //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 we don't have any text at all yet... + if (!mTextBuffer) + { + setText(textBuffer, numChars); + return; + } + + //make sure we've got room + S32 numCharsToUse = numChars; + if (mMaxBufferSize > 0 && mCurrTextSize + numCharsToUse > mMaxBufferSize - 1) + numCharsToUse = mMaxBufferSize - mCurrTextSize - 1; + + //create a new buffer + mCurrBufferSize = (( (mCurrBufferSize + numCharsToUse + 1) / csmTextBufferGrowthSize) + 1) * csmTextBufferGrowthSize; + char *tempBuffer = new char[mCurrBufferSize]; + + //copy the original text in... + dMemcpy(tempBuffer, mTextBuffer, mCurrTextSize); + + //copy the next text in + dMemcpy(&tempBuffer[mCurrTextSize], textBuffer, numCharsToUse); + tempBuffer[mCurrTextSize + numCharsToUse] = '\0'; + + //replace the old buffer + delete [] mTextBuffer; + mTextBuffer = tempBuffer; + mCurrTextSize += numCharsToUse; + + //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 > mCurrTextSize) { + mCursorPosition = mCurrTextSize; + 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(); + if (!pParent || pParent->size() != 1 || dynamic_cast(pParent) == NULL) + return; + + GuiScrollContentCtrl* pSCCtrl = static_cast(pParent); + + // Are we smaller than the actual content control? + if (mBounds.extent.y <= pSCCtrl->getExtent().y) + return; + + GuiControl* pGrandParent = pSCCtrl->getParent(); + if (!pGrandParent || dynamic_cast(pGrandParent) == NULL) + return; + GuiScrollCtrl* pSCtrl = static_cast(pGrandParent); + + // 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); + + S32 cursorTop = cursorTopP.y; + S32 cursorBottom = cursorBottomP.y; + + F32 sposY = pSCtrl->getCurrVPos(); + // Since we know that we're the only control in the content window at this point, + // that pos represents a percentage of _our_ extent. + S32 realExtent = (mBounds.extent.y - pSCCtrl->getExtent().y); + S32 realPosY = (S32)(realExtent * sposY); + + S32 realTop = realPosY; + S32 realBottom = realPosY + pSCCtrl->getExtent().y; + + if ( cursorTop < realTop ) + { + S32 newRealTop = cursorTop; + F32 newPos = F32(newRealTop) / F32(realExtent); + pSCtrl->scrollTo(0, newPos); + } + else if ( cursorBottom > realBottom ) + { + // Not yet... + S32 newRealTop = cursorBottom - pSCCtrl->getExtent().y; + F32 newPos = F32(newRealTop) / F32(realExtent); + pSCtrl->scrollTo(0, newPos); + } +} + +//-------------------------------------- +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; + x += font->getStrNWidth(mTextBuffer + awalk->textStart, mCursorPosition - awalk->textStart); + 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_CTRL) + { + switch(event.keyCode) + { + //copy + case KEY_C: + { + //make sure we actually have something selected + if (mSelectionActive) + { + copyToClipboard(mSelectionStart, mSelectionEnd); + setUpdate(); + } + return true; + } + } + } + + // 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 > mCurrTextSize) + mSelectionAnchor = mCurrTextSize; + + mVertMoveAnchorValid = false; + + setUpdate(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::onMouseDragged(const GuiEvent& event) +{ + if (!mActive || (Canvas->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; + + S32 newSelection = 0; + if (!mHitURL || !mHitURL->mouseDown) + { + newSelection = getTextPosition(globalToLocalCoord(event.mousePoint)); + if (newSelection < 0) + newSelection = 0; + else if (newSelection > mCurrTextSize) + newSelection = mCurrTextSize; + + 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); + setUpdate(); + mDirty = true; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::onMouseUp(const GuiEvent& event) +{ + if (!mActive || (Canvas->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; + char *url = Con::getArgBuffer(mHitURL->len + 1); + dStrncpy(url, mTextBuffer + mHitURL->textStart, mHitURL->len); + url[mHitURL->len] = 0; + + Con::executef(this, 2, "onURL", url); + mHitURL = 0; + + 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::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 <= mCurrTextSize, "GuiMLTextCtrl::insertChars: can't insert outside of current text!"); + + //make sure the text will fit... + S32 numCharsToInsert = numInputChars; + if (mMaxBufferSize > 0 && mCurrTextSize + numInputChars + 1 > mMaxBufferSize) + numCharsToInsert = mMaxBufferSize - mCurrTextSize - 1; + if (numCharsToInsert <= 0) + { + // Play the "Denied" sound: + if ( numInputChars > 0 && mDeniedSound ) + { + AUDIOHANDLE handle = alxCreateSource( mDeniedSound ); + alxPlay( handle ); + } + return; + } + + if ((mCurrTextSize + numCharsToInsert + 1) > mCurrBufferSize) + { + // Need to resize the buffer... + mCurrBufferSize = (((numCharsToInsert+mCurrTextSize+1) / csmTextBufferGrowthSize) + 1) * csmTextBufferGrowthSize; + char* newBuffer = new char[mCurrBufferSize]; + + dMemcpy(&newBuffer[0], mTextBuffer, position); + dMemcpy(&newBuffer[position], inputChars, numCharsToInsert); + dMemcpy(&newBuffer[position+numCharsToInsert], &mTextBuffer[position], mCurrTextSize - position); + + delete [] mTextBuffer; + mTextBuffer = newBuffer; + } + else + { + // We can just insert the characters into the current buffer... + dMemmove(&mTextBuffer[position + numCharsToInsert], &mTextBuffer[position], mCurrTextSize - position); + dMemcpy(&mTextBuffer[position], inputChars, numCharsToInsert); + } + mCurrTextSize += numCharsToInsert; + + if (mCursorPosition < position) { + // Cursor was before the inserted text, no change + } else { + // Cursor was at or after the inserted text, move forward... + mCursorPosition += numCharsToInsert; + } + mTextBuffer[mCurrTextSize] = 0; + AssertFatal(mCursorPosition <= mCurrTextSize, "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 < mCurrTextSize && rangeEnd < mCurrTextSize, + avar("GuiMLTextCtrl::deleteChars: can't delete outside of current text (%d, %d, %d)", + rangeStart, rangeEnd, mCurrTextSize)); + AssertFatal(rangeStart <= rangeEnd, "GuiMLTextCtrl::deleteChars: invalid delete range"); + + // Currently deleting text doesn't resize the text buffer, perhaps this should + // change? + dMemmove(&mTextBuffer[rangeStart], &mTextBuffer[rangeEnd+1], (mCurrTextSize - rangeEnd - 1)); + mCurrTextSize -= (rangeEnd - rangeStart) + 1; + + 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; + } + mTextBuffer[mCurrTextSize] = 0; + AssertFatal(mCursorPosition <= mCurrTextSize, "GuiMLTextCtrl::deleteChars: bad cursor position"); + mDirty = true; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::copyToClipboard(const U32 rangeStart, const U32 rangeEnd) +{ + AssertFatal(rangeStart < mCurrTextSize && rangeEnd < mCurrTextSize, + avar("GuiMLTextCtrl::copyToClipboard: can't copy outside of current text (%d, %d, %d)", + rangeStart, rangeEnd, mCurrTextSize)); + AssertFatal(rangeStart <= rangeEnd, "GuiMLTextCtrl::copyToClipboard: invalid copy range"); + + //copy the selection to the clipboard + char temp = mTextBuffer[rangeEnd+1]; + mTextBuffer[rangeEnd+1] = '\0'; + Platform::setClipboard(&mTextBuffer[rangeStart]); + mTextBuffer[rangeEnd+1] = temp; +} + +//-------------------------------------------------------------------------- +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: + GuiControl *pappy = getParent(); + if ( !pappy || !dynamic_cast( 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; + } + + Point2I newPos( 0, -tag->y ); + resize( newPos, mBounds.extent ); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::scrollToTop() +{ + // If the parent control is not a GuiScrollContentCtrl, then this call is invalid: + GuiControl *pappy = getParent(); + if ( !pappy || !dynamic_cast( pappy ) ) + return; + + resize( Point2I( 0, 0 ), mBounds.extent ); +} + +//-------------------------------------------------------------------------- +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(); + U32 last = 0; + + 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... + U32 x = awalk->xStart; + GFont *font = awalk->style->font->fontRes; + U32 bp = font->getBreakPos(mTextBuffer + awalk->textStart, awalk->len, localCoords.x - awalk->xStart, false); + return awalk->textStart + bp; + } + return walk->textStart + walk->len; + } + } + return mCurrTextSize - 1; +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::Font *GuiMLTextCtrl::allocFont(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; + 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); + if(bool(ret->fontRes)) + { + ret->next = mFontList; + mFontList = ret; + return ret; + } + return NULL; +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::Bitmap *GuiMLTextCtrl::allocBitmap(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))); + ret->bitmapName = bitmapName; + ret->bitmapNameLen = bitmapNameLen; + char nameBuffer[256]; + char c = bitmapName[bitmapNameLen]; + bitmapName[bitmapNameLen] = 0; + dSprintf(nameBuffer, sizeof(nameBuffer), "texticons/%s", bitmapName); + bitmapName[bitmapNameLen] = c; + ret->bitmapHandle = TextureHandle(nameBuffer); + if(bool(ret->bitmapHandle)) + { + 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) +{ + if(mCurRMargin <= mCurLMargin) + return; + if(mCurRMargin - mCurLMargin < bmp->bitmapHandle.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->bitmapHandle.getWidth(); + ref->extent.y = bmp->bitmapHandle.getHeight(); + // find the first space in the blocker list that will fit this thats > curLMargin + + 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; + + while(atomList) + { + // split the tokenlist by space + // first find the first space that the text can go into: + BitmapRef *br = mBlockList; + bool bailout = false; + 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; + + while(list) + { + GFont *font = list->style->font->fontRes; + U32 breakPos; + + //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(mTextBuffer + list->textStart, 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(mTextBuffer + list->textStart, 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(mTextBuffer + list->textStart, breakPos); + + //set the pointer to the last atom that fit within the clip region + clipAtom = list; + } + } + else + { + breakPos = font->getBreakPos(mTextBuffer + list->textStart, list->len, width - totalWidth, true); + if(breakPos == 0) + break; + + //set the atom width == to the string length + list->width = font->getStrNWidthPrecise(mTextBuffer + list->textStart, 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); + + //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); + breakPos = font->getBreakPos(mTextBuffer + clipAtom->textStart, 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(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 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->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; + + U32 width = mBounds.extent.x; + + 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(;;) + { + char *str = mTextBuffer + mScanPos; + if(!*str) + break; + if(*str == '\n') + { + textStart = mScanPos; + len = 1; + mScanPos++; + processEmitAtoms(); + emitNewLine(textStart); + mCurDiv = 0; + continue; + } + if(*str == '\t') + { + textStart = mScanPos; + len = 1; + mScanPos++; + processEmitAtoms(); + if(mTabStopCount) + { + if(mCurTabStop < mTabStopCount) + { + if(mCurX < mTabStops[mCurTabStop]) + mCurX = mTabStops[mCurTabStop]; + } + mCurTabStop++; + } + continue; + } + + if(*str == '<') + { + // it's probably some kind of tag: + 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(mTextBuffer + textStart, 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) + 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]); + color.alpha = 255; + mScanPos += 14; + + if(mCurStyle->used) + mCurStyle = allocStyle(mCurStyle); + mCurStyle->color = color; + + continue; + } + + if(!dStrnicmp(str +1, "bitmap:", 7)) + { + idx = 8; + if(!scanforchar(str, idx, '>')) + goto textemit; + textStart = mScanPos + 8; + len = idx - 8; + + mScanPos += idx + 1; + + processEmitAtoms(); + Bitmap *bmp; + bmp = allocBitmap(mTextBuffer + textStart, len); + if(bmp) + emitBitmapToken(bmp, textStart); + 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(!scanforchar(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 = (mBounds.extent.x * 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 = (mBounds.extent.x * 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(str[idx] != '\t' && str[idx] != '<' && str[idx] != '\n' && str[idx]) + idx++; + len = idx; + mScanPos += idx; + emitTextToken(textStart, len); + } + processEmitAtoms(); + emitNewLine(mScanPos); + resize(mBounds.point, Point2I(mBounds.extent.x, mMaxY)); + Con::executef( this, 3, "onResize", Con::getIntArg( mBounds.extent.x ), 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') + { + bufPtr++; + continue; + } + if(*bufPtr == '\t') + { + bufPtr++; + continue; + } + if(*bufPtr < 0x20 && *bufPtr >= 0) + { + bufPtr++; + 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/guiMLTextCtrl.h b/gui/guiMLTextCtrl.h new file mode 100644 index 0000000..3f33532 --- /dev/null +++ b/gui/guiMLTextCtrl.h @@ -0,0 +1,257 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMLTEXTCTRL_H_ +#define _GUIMLTEXTCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/guiControl.h" +#endif + +class GFont; + +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 { + const char *bitmapName; + U32 bitmapNameLen; + TextureHandle bitmapHandle; + Bitmap *next; + }; + + struct URL + { + bool mouseDown; + U32 textStart; + U32 len; + bool noUnderline; + }; + + struct Style + { + ColorI color; + 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(); + + // Text substitution functions + void setText(const char* textBuffer, const U32 numChars); + void addText(const char* textBuffer, const U32 numChars, bool reformat); + + bool setCursorPosition(const S32); + void ensureCursorOnScreen(); + + // Scroll functions + void scrollToTag( U32 id ); + void scrollToTop(); + + DECLARE_CONOBJECT(GuiMLTextCtrl); + static void initPersistFields(); + static void consoleInit(); + + 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; + + 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(); + virtual void reflow(); + Bitmap *allocBitmap(char *bitmapName, U32 bitmapNameLen); + Font *allocFont(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); + 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 + char* mTextBuffer; + U32 mCurrTextSize; + U32 mCurrBufferSize; + U32 mLineStart; + S32 mMaxBufferSize; + + // Selection information + bool mSelectionActive; + U32 mSelectionStart; + U32 mSelectionEnd; + + U32 mVertMoveAnchor; + bool mVertMoveAnchorValid; + + S32 mSelectionAnchor; + Point2I mSelectionAnchorDropped; + + // Font resource + Resource mFont; + U32 mMinSensibleWidth; + + // Console settable parameters + U32 mLineSpacingPixels; + bool mAllowColorChars; + + // Too many chars sound: + AudioProfile* mDeniedSound; + + //-------------------------------------- Protected interface + protected: + // Inserting and deleting character blocks... + void insertChars(const char* inputChars, + const U32 numInputChars, + const U32 position); + void deleteChars(const U32 rangeStart, + const U32 rangeEnd); + void copyToClipboard(const U32 rangeStart, + const U32 rangeEnd); + + // Selection maintainance + bool isSelectionActive() const; + void clearSelection(); + + // Pixel -> textposition mappings + S32 getTextPosition(const Point2I& localPosition); + + // Gui control overrides + bool onWake(); + void onSleep(); + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void getCursorPositionAndColor(Point2I &cursorTop, Point2I &cursorBottom, ColorI &color); + void inspectPostApply(); + void resize(const Point2I &newPosition, const Point2I &newExtent); + void parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent); + bool onKeyDown(const GuiEvent& event); + void onMouseDown(const GuiEvent&); + void onMouseDragged(const GuiEvent&); + void onMouseUp(const GuiEvent&); +}; + +#endif // _H_GUIMLTEXTCTRL_ diff --git a/gui/guiMLTextEditCtrl.cc b/gui/guiMLTextEditCtrl.cc new file mode 100644 index 0000000..179237c --- /dev/null +++ b/gui/guiMLTextEditCtrl.cc @@ -0,0 +1,365 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiMLTextEditCtrl.h" +#include "GUI/guiScrollCtrl.h" +#include "dgl/dgl.h" +#include "console/consoleTypes.h" +#include "Platform/event.h" + +IMPLEMENT_CONOBJECT(GuiMLTextEditCtrl); + + +//-------------------------------------------------------------------------- +GuiMLTextEditCtrl::GuiMLTextEditCtrl() +{ + mEscapeCommand = StringTable->insert( "" ); + + mIsEditCtrl = true; + + mActive = true; + + mVertMoveAnchorValid = false; +} + + +//-------------------------------------------------------------------------- +GuiMLTextEditCtrl::~GuiMLTextEditCtrl() +{ + +} + + +//-------------------------------------------------------------------------- +void 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->mBounds.extent.y, newExt.y ); + + Parent::resize( newPosition, newExt ); +} + + +//-------------------------------------------------------------------------- +void GuiMLTextEditCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField( "escapeCommand", TypeString, Offset( mEscapeCommand, GuiMLTextEditCtrl ) ); +} + + +//-------------------------------------------------------------------------- +void GuiMLTextEditCtrl::consoleInit() +{ + +} + +//-------------------------------------------------------------------------- +// Key events... +bool GuiMLTextEditCtrl::onKeyDown(const GuiEvent& event) +{ + setUpdate(); + //handle modifiers first... + if (event.modifier & SI_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; + } + } + } + else if ( event.modifier & SI_SHIFT ) + { + switch ( event.keyCode ) + { + case KEY_TAB: + return( Parent::onKeyDown( event ) ); + } + } + 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; + } + } + + if (event.ascii != 0) + { + // Normal ascii keypress. Go ahead and add the chars... + if (mSelectionActive == true) + { + mSelectionActive = false; + deleteChars(mSelectionStart, mSelectionEnd); + mCursorPosition = mSelectionStart; + } + + char ascii = char(event.ascii); + insertChars(&ascii, 1, 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); + mCursorPosition = mSelectionStart; + } + else + { + switch ( event.keyCode ) + { + case KEY_BACKSPACE: + if (mCursorPosition != 0) + { + // delete one character left + deleteChars(mCursorPosition-1, mCursorPosition-1); + setUpdate(); + } + break; + + case KEY_DELETE: + if (mCursorPosition != mCurrTextSize) + { + // delete one character right + deleteChars(mCursorPosition, mCursorPosition); + setUpdate(); + } + break; + + default: + AssertFatal(false, "Should not be here!"); + } + } +} + + +//-------------------------------------- +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 != mCurrTextSize ) + { + 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, "Should not be here!"); + } + + ensureCursorOnScreen(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextEditCtrl::onRender(Point2I offset, const RectI& updateRect, GuiControl* firstResponder) +{ + Parent::onRender(offset, updateRect, firstResponder); + + // We are the first responder, draw our cursor in the appropriate position... + if (firstResponder == this) { + Point2I top, bottom; + ColorI color; + getCursorPositionAndColor(top, bottom, color); + dglDrawLine(top + offset, bottom + offset, mProfile->mCursorColor); + } +} + diff --git a/gui/guiMLTextEditCtrl.h b/gui/guiMLTextEditCtrl.h new file mode 100644 index 0000000..e974421 --- /dev/null +++ b/gui/guiMLTextEditCtrl.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMLTEXTEDITCTRL_H_ +#define _GUIMLTEXTEDITCTRL_H_ + +#ifndef _GUIMLTEXTCTRL_H_ +#include "GUI/guiMLTextCtrl.h" +#endif + +class GuiMLTextEditCtrl : public GuiMLTextCtrl +{ + typedef GuiMLTextCtrl Parent; + + //-------------------------------------- Overrides + protected: + StringTableEntry mEscapeCommand; + + // Events + bool onKeyDown(const GuiEvent&); + + // Event forwards + void handleMoveKeys(const GuiEvent&); + void handleDeleteKeys(const GuiEvent&); + + // rendering + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + + public: + GuiMLTextEditCtrl(); + ~GuiMLTextEditCtrl(); + + void resize(const Point2I &newPosition, const Point2I &newExtent); + + DECLARE_CONOBJECT(GuiMLTextEditCtrl); + static void initPersistFields(); + static void consoleInit(); +}; + +#endif // _H_GUIMLTEXTEDITCTRL_ diff --git a/gui/guiMenuBar.cc b/gui/guiMenuBar.cc new file mode 100644 index 0000000..e02bc5c --- /dev/null +++ b/gui/guiMenuBar.cc @@ -0,0 +1,693 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/dgl.h" +#include "gui/guiCanvas.h" +#include "gui/guiDefaultControlRender.h" +#include "gui/guiTextListCtrl.h" +#include "sim/actionMap.h" +#include "gui/guiMenuBar.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) 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 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) + +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// console methods +//------------------------------------------------------------------------------ + +ConsoleMethod(GuiMenuBar, clearMenus, void, 2, 2, "() - clears all the menus from the menu bar.") +{ + ((GuiMenuBar *)object)->clearMenus(); +} + +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; + } + GuiMenuBar *menuBar = (GuiMenuBar *) object; + menuBar->addMenu(argv[2], dAtoi(argv[3])); +} + +ConsoleMethod(GuiMenuBar, addMenuItem, void, 5, 6, "(string menu, string menuItemText, int menuItemId, string accelerator = NULL) - adds a menu item to the specified menu. The menu argument can be either the text of a menu or its id.") +{ + GuiMenuBar *menuBar = (GuiMenuBar *) object; + 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 = menuBar->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for addMenuItem.", argv[2]); + return; + } + menuBar->addMenuItem(menu, argv[3], dAtoi(argv[4]), argc == 5 ? NULL : argv[5]); +} + +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 *menuBar = (GuiMenuBar *) object; + GuiMenuBar::Menu *menu = menuBar->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuItemEnable.", argv[2]); + return; + } + GuiMenuBar::MenuItem *menuItem = menuBar->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, setMenuText, void, 4, 4, "(string menu, string newMenuText) - sets the text of the specified menu to the new string.") +{ + GuiMenuBar *menuBar = (GuiMenuBar *) object; + 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 = menuBar->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuText.", argv[2]); + return; + } + dFree(menu->text); + menu->text = dStrdup(argv[3]); + menuBar->menuBarDirty = true; +} + +ConsoleMethod(GuiMenuBar, setMenuItemText, void, 5, 5, "(string menu, string menuItem, string newMenuItemText) - sets the text of the specified menu item to the new string.") +{ + GuiMenuBar *menuBar = (GuiMenuBar *) object; + 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 = menuBar->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuItemText.", argv[2]); + return; + } + GuiMenuBar::MenuItem *menuItem = menuBar->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, removeMenuItem, void, 4, 4, "(string menu, string menuItem) - removes the specified menu item from the menu.") +{ + GuiMenuBar *menuBar = (GuiMenuBar *) object; + + GuiMenuBar::Menu *menu = menuBar->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for removeMenuItem.", argv[2]); + return; + } + GuiMenuBar::MenuItem *menuItem = menuBar->findMenuItem(menu, argv[3]); + if(!menuItem) + { + Con::errorf("Cannot find menu item %s for removeMenuItem.", argv[3]); + return; + } + menuBar->removeMenuItem(menu, menuItem); +} + +ConsoleMethod(GuiMenuBar, clearMenuItems, void, 3, 3, "(string menu) - removes all the menu items from the specified menu.") +{ + GuiMenuBar *menuBar = (GuiMenuBar *) object; + + GuiMenuBar::Menu *menu = menuBar->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for clearMenuItems.", argv[2]); + return; + } + menuBar->clearMenuItems(menu); +} + +ConsoleMethod(GuiMenuBar, removeMenu, void, 3, 3, "(string menu) - removes the specified menu from the menu bar.") +{ + GuiMenuBar *menuBar = (GuiMenuBar *) object; + + GuiMenuBar::Menu *menu = menuBar->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for removeMenu.", argv[2]); + return; + } + menuBar->clearMenuItems(menu); + menuBar->menuBarDirty = true; +} + +//------------------------------------------------------------------------------ +// 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; + + // 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; + } + } + dFree(menuItem->text); + dFree(menuItem->accelerator); + delete menuItem; +} + +void GuiMenuBar::addMenuItem(Menu *menu, const char *text, U32 id, const char *accelerator) +{ + // allocate the new menu item + MenuItem *newMenuItem = new MenuItem; + newMenuItem->text = dStrdup(text); + if(accelerator) + newMenuItem->accelerator = dStrdup(accelerator); + else + newMenuItem->accelerator = NULL; + newMenuItem->id = id; + newMenuItem->nextMenuItem = NULL; + newMenuItem->acceleratorIndex = 0; + newMenuItem->enabled = true; + + // 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); +} + +//------------------------------------------------------------------------------ +// initialization, input and render methods +//------------------------------------------------------------------------------ + +GuiMenuBar::GuiMenuBar() +{ + menuList = NULL; + menuBarDirty = true; + mouseDownMenu = NULL; + mouseOverMenu = NULL; + mCurAcceleratorIndex = 0; +} + +GuiMenuBar::Menu *GuiMenuBar::findHitMenu(Point2I mousePoint) +{ + Point2I pos = globalToLocalCoord(mousePoint); + + for(Menu *walk = menuList; walk; walk = walk->nextMenu) + if(walk->bounds.pointInRect(pos)) + return walk; + return NULL; +} + +void GuiMenuBar::onPreRender() +{ + Parent::onPreRender(); + if(menuBarDirty) + { + menuBarDirty = false; + U32 curX = 0; + for(Menu *walk = menuList; walk; walk = walk->nextMenu) + { + walk->bounds.set(curX, 1, mProfile->mFont->getStrWidth(walk->text) + 12, mBounds.extent.y - 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) + { + mouseOverMenu = hit; + setUpdate(); + } +} + +void GuiMenuBar::onMouseLeave(const GuiEvent &event) +{ + if(mouseOverMenu) + setUpdate(); + mouseOverMenu = NULL; +} + +void GuiMenuBar::onMouseDragged(const GuiEvent &event) +{ + Menu *hit = findHitMenu(event.mousePoint); + + if(hit != mouseOverMenu) + { + 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) +{ + //if opaque, fill the update rect with the fill color + if (mProfile->mOpaque) + dglDrawRectFill(RectI(offset, mBounds.extent), mProfile->mFillColor); + + for(Menu *walk = menuList; walk; walk = walk->nextMenu) + { + ColorI fontColor = mProfile->mFontColor; + RectI bounds = walk->bounds; + bounds.point += offset; + + Point2I start; + + start.x = walk->bounds.point.x + 6; + start.y = walk->bounds.point.y + ( walk->bounds.extent.y - ( mProfile->mFont->getHeight() - 2 ) ) / 2; + + if(walk == mouseDownMenu) + renderSlightlyLoweredBox(bounds); + else if(walk == mouseOverMenu && mouseDownMenu == NULL) + renderSlightlyRaisedBox(bounds); + + dglSetBitmapModulation( fontColor ); + + dglDrawText( mProfile->mFont, start + offset, walk->text, mProfile->mFontColors ); + } +} + +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) + { + for(MenuItem *item = menu->firstMenuItem; item; item = item->nextMenuItem) + { + if(item->acceleratorIndex == index) + { + 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; +} + +void GuiMenuTextListCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver) +{ + if(dStrcmp(mList[cell.y].text, "-\t")) + Parent::onRenderCell(offset, cell, selected, mouseOver); + else + { + S32 yp = offset.y + mCellSize.y / 2; + dglDrawLine(offset.x, yp, offset.x + mCellSize.x, yp, ColorI(128,128,128)); + dglDrawLine(offset.x, yp+1, offset.x + mCellSize.x, yp+1, ColorI(255,255,255)); + } +} + +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 GuiMenuBar::menuItemSelected(GuiMenuBar::Menu *menu, GuiMenuBar::MenuItem *item) +{ + if(item->enabled) + Con::executef( this, 6, "onMenuItemSelect", Con::getIntArg(menu->id), + menu->text, Con::getIntArg(item->id), item->text); +} + +void GuiMenuBar::closeMenu() +{ + // Get the selection from the text list: + S32 selectionIndex = mTextList->getSelectedCell().y; + + // Pop the background: + getRoot()->popDialogControl(mBackground); + + // Kill the popup: + mBackground->deleteObject(); + + // 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; +} + +//------------------------------------------------------------------------------ +void GuiMenuBar::onAction() +{ + if(!mouseDownMenu) + return; + + // first, call the script callback for menu selection: + Con::executef( this, 4, "onMenuSelect", Con::getIntArg(mouseDownMenu->id), + mouseDownMenu->text); + + if(!mouseDownMenu->firstMenuItem) + { + mouseDownMenu = NULL; + return; + } + + mTextList = new GuiMenuTextListCtrl(this); + mTextList->mProfile = mProfile; + + mBackground = new GuiMenuBackgroundCtrl(this, mTextList); + + GuiCanvas *root = getRoot(); + Point2I windowExt = root->mBounds.extent; + + mBackground->mBounds.point.set(0,0); + mBackground->mBounds.extent = root->mBounds.extent; + + S32 textWidth = 0, width = 0; + S32 acceleratorWidth = 0; + + GFont *font = mProfile->mFont; + + for(MenuItem *walk = mouseDownMenu->firstMenuItem; walk; walk = walk->nextMenuItem) + { + 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 + 8; + + mTextList->setCellSize(Point2I(width, font->getHeight()+3)); + mTextList->addColumnOffset(textWidth + 4); + + U32 entryCount = 0; + + for(MenuItem *walk = mouseDownMenu->firstMenuItem; walk; walk = walk->nextMenuItem) + { + char buf[512]; + dSprintf(buf, sizeof(buf), "%s\t%s", 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 + 2; + + GuiControl *ctrl = new GuiControl; + ctrl->mBounds.point = menuPoint; + ctrl->mBounds.extent = mTextList->mBounds.extent + Point2I(6, 6); + ctrl->mProfile = mProfile; + mTextList->mBounds.point += Point2I(3,3); + + //mTextList->mBounds.point = Point2I(3,3); + + mTextList->registerObject(); + mBackground->registerObject(); + ctrl->registerObject(); + + mBackground->addObject( ctrl ); + ctrl->addObject( mTextList ); + + root->pushDialogControl(mBackground, mLayer + 1); + mTextList->setFirstResponder(); +} + + diff --git a/gui/guiMenuBar.h b/gui/guiMenuBar.h new file mode 100644 index 0000000..6642848 --- /dev/null +++ b/gui/guiMenuBar.h @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMENUBAR_H_ +#define _GUIMENUBAR_H_ + +#ifndef _GUITEXTLISTCTRL_H_ +#include "gui/guiTextListCtrl.h" +#endif + +class GuiMenuBar; +class GuiMenuTextListCtrl; + +class GuiMenuBackgroundCtrl : public GuiControl +{ +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 GuiMenuTextListCtrl : public GuiTextListCtrl +{ + private: + typedef GuiTextListCtrl Parent; + + protected: + GuiMenuBar *mMenuBarCtrl; + + public: + 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); +}; + +//------------------------------------------------------------------------------ + +class GuiMenuBar : public GuiControl +{ + typedef GuiControl Parent; +public: + + 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 + MenuItem *nextMenuItem; // next menu item in the linked list + }; + struct Menu + { + char *text; + U32 id; + RectI bounds; + + Menu *nextMenu; + MenuItem *firstMenuItem; + }; + + GuiMenuBackgroundCtrl *mBackground; + GuiMenuTextListCtrl *mTextList; + + Menu *menuList; + Menu *mouseDownMenu; + Menu *mouseOverMenu; + + bool menuBarDirty; + U32 mCurAcceleratorIndex; + + GuiMenuBar(); + + // 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); + void clearMenuItems(Menu *menu); + void clearMenus(); + + // display/mouse functions + + Menu *findHitMenu(Point2I mousePoint); + + 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); + + DECLARE_CONOBJECT(GuiMenuBar); +}; + +#endif \ No newline at end of file diff --git a/gui/guiMessageVectorCtrl.cc b/gui/guiMessageVectorCtrl.cc new file mode 100644 index 0000000..5edd5f9 --- /dev/null +++ b/gui/guiMessageVectorCtrl.cc @@ -0,0 +1,840 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiMessageVectorCtrl.h" +#include "GUI/messageVector.h" +#include "dgl/dgl.h" +#include "console/consoleTypes.h" +#include "GUI/guiScrollCtrl.h" + +IMPLEMENT_CONOBJECT(GuiMessageVectorCtrl); + + +//-------------------------------------- Console functions +namespace { + +bool cGMVCAttach(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did this get here?"); + GuiMessageVectorCtrl* pGMVC = static_cast(obj); + + 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 pGMVC->attach(pMV); +} + +void cGMVCDetach(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "How did this get here?"); + GuiMessageVectorCtrl* pGMVC = static_cast(obj); + + if (pGMVC->isAttached() == false) { + Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl: double detach"); + return; + } + + pGMVC->detach(); +} + + +struct TempLineBreak +{ + S32 start; + S32 end; +}; + + +} // namespace {} + +//-------------------------------------------------------------------------- +// Callback for messageVector +void sMVCtrlCallback(const U32 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() +{ + Parent::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)); +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::consoleInit() +{ + Con::addCommand("GuiMessageVectorCtrl", "attach", cGMVCAttach, "[GuiMessageVectorCtrl].attach(MessageVectorId)", 3, 3); + Con::addCommand("GuiMessageVectorCtrl", "detach", cGMVCDetach, "[GuiMessageVectorCtrl].detach()", 2, 2); +} + + +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) + 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, U32(this)); + + return true; +} + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::detach() +{ + if (isAttached() == false) { + Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl::detach: not attached!"); + return; + } + + mMessageVector->unregisterSpectator(U32(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 = NULL; + GuiControl* pParent = getParent(); + if (pParent) { + GuiControl* pGrandParent = pParent->getParent(); + if (pGrandParent) + pScroll = dynamic_cast(pGrandParent); + } + + bool scrollToBottom = false; + if (pScroll != NULL) + scrollToBottom = pScroll->getCurrVPos() == 1.0; + + 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; + } + + U32 newHeight = (mProfile->mFont->getHeight() + mLineSpacingPixels) * getMax(numLines, U32(1)); + resize(mBounds.point, Point2I(mBounds.extent.x, newHeight)); + + if (arg == mSpecialMarkers.size() - 1 && scrollToBottom == true) + pScroll->scrollTo(0, 1); +} + + +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(mBounds.point, Point2I(mBounds.extent.x, 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(mBounds.point, Point2I(mBounds.extent.x, 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... + char* pLCCopy = new char[dStrlen(string) + 1]; + for (U32 i = 0; string[i] != '\0'; i++) + pLCCopy[i] = dTolower(string[i]); + pLCCopy[dStrlen(string)] = '\0'; + + 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; + } + + bool used = false; + U32 splitWidth = mBounds.extent.x; + 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(&string[rLine.start], rLine.end-rLine.start+1); + if (baseLength > splitWidth) { + // DMMNOTE: Replace with bin search eventually + U32 currPos; + U32 breakPos; + for (currPos = 0; currPos < rLine.end-rLine.start+1; currPos++) { + U32 currLength = mProfile->mFont->getStrNWidthPrecise(&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, + GuiControl*fr) +{ + Parent::onRender(offset, updateRect,fr); + 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) { + dglSetBitmapModulation(lastColor); + dglSetTextAnchorColor(mProfile->mFontColor); + strWidth = dglDrawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start], + walkAcross->end - walkAcross->start + 1, mProfile->mFontColors, mMaxColorIndex); + dglGetBitmapModulation(&lastColor); + } else { + dglGetBitmapModulation(&lastColor); + dglSetBitmapModulation(mSpecialColor); + dglSetTextAnchorColor(mProfile->mFontColor); + strWidth = dglDrawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start], + walkAcross->end - walkAcross->start + 1); + + // in case we have 2 in a row... + dglSetBitmapModulation(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; + + dglDrawLine(localToGlobalCoord(lineStart), + localToGlobalCoord(lineEnd), + mSpecialColor); + } + + localStart.x += strWidth; + walkAcross = walkAcross->nextInLine; + } + + currLine++; + pElement = pElement->nextPhysicalLine; + } + } + dglClearBitmapModulation(); + } +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::inspectPostApply() +{ + Parent::inspectPostApply(); +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::parentResized(const Point2I& oldSize, + const Point2I& newSize) +{ + Parent::parentResized(oldSize, newSize); + + 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 >= mBounds.extent.x) || + (point.y < 0 || point.y >= mBounds.extent.y)) { + *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++) { + 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; + } + + U32 currElem = 0; + while (line) { + U32 newX = currX + mProfile->mFont->getStrNWidth(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, 2, "urlClickCallback", copyURL); + delete [] copyURL; + } + + mMouseDown = false; + mMouseSpecialLine = -1; + mMouseSpecialRef = -1; +} + diff --git a/gui/guiMessageVectorCtrl.h b/gui/guiMessageVectorCtrl.h new file mode 100644 index 0000000..6dd797d --- /dev/null +++ b/gui/guiMessageVectorCtrl.h @@ -0,0 +1,130 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMESSAGEVECTORCTRL_H_ +#define _GUIMESSAGEVECTORCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif +#ifndef _MESSAGEVECTOR_H_ +#include "GUI/messageVector.h" +#endif + +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, GuiControl *firstResponder); + void inspectPostApply(); + void parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent); + + 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); + static void initPersistFields(); + static void consoleInit(); +}; + +#endif // _H_GUIMESSAGEVECTORCTRL_ diff --git a/gui/guiMouseEventCtrl.cc b/gui/guiMouseEventCtrl.cc new file mode 100644 index 0000000..5ee663f --- /dev/null +++ b/gui/guiMouseEventCtrl.cc @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/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, 4, name, buf[0], buf[1], buf[2]); +} + +//------------------------------------------------------------------------------ +void GuiMouseEventCtrl::consoleInit() +{ + 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::initPersistFields() +{ + Parent::initPersistFields(); + addField("lockMouse", TypeBool, Offset(mLockMouse, GuiMouseEventCtrl)); +} + +//------------------------------------------------------------------------------ +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/guiMouseEventCtrl.h b/gui/guiMouseEventCtrl.h new file mode 100644 index 0000000..fe408e2 --- /dev/null +++ b/gui/guiMouseEventCtrl.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMOUSEEVENTCTRL_H_ +#define _GUIMOUSEEVENTCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/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 consoleInit(); + static void initPersistFields(); + + DECLARE_CONOBJECT(GuiMouseEventCtrl); +}; + +#endif diff --git a/gui/guiPopUpCtrl.cc b/gui/guiPopUpCtrl.cc new file mode 100644 index 0000000..29d0c95 --- /dev/null +++ b/gui/guiPopUpCtrl.cc @@ -0,0 +1,929 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiPopUpCtrl.h" +#include "console/consoleTypes.h" + + +//------------------------------------------------------------------------------ +GuiPopUpTextListCtrl::GuiPopUpTextListCtrl() +{ + mPopUpCtrl = NULL; + mMouseDown = AlreadyDown; +} + + +//------------------------------------------------------------------------------ +GuiPopUpTextListCtrl::GuiPopUpTextListCtrl(GuiPopUpMenuCtrl *ctrl) +{ + mPopUpCtrl = ctrl; + mMouseDown = AlreadyDown; +} + +//------------------------------------------------------------------------------ +GuiPopUpTextListCtrl::~GuiPopUpTextListCtrl() +{ +} + +//------------------------------------------------------------------------------ +void GuiPopUpTextListCtrl::onSleep() +{ + mMouseDown = None; + Parent::onSleep(); +} + +//------------------------------------------------------------------------------ +void GuiPopUpTextListCtrl::onCellSelected( Point2I /*cell*/ ) +{ + // Do nothing, the parent control will take care of everything... +} + +//------------------------------------------------------------------------------ +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) +{ + mMouseDown = None; + + // See if it's within the scroll content ctrl: + GuiControl *parent = getParent(); + if ( !parent ) + return; + + if ( parent->cursorInControl() ) + { + //let the parent handle the event + Parent::onMouseDown(event); + mMouseDown = TextListArea; + //we're done + return; + } + + //see if it's within the scroll control + GuiControl *grandParent = parent->getParent(); + if ( !grandParent ) + return; + + if ( grandParent->cursorInControl() ) + { + //let the scroll control deal with it + grandParent->onMouseDown(event); + mMouseDown = ScrollArea; + //and we're done + return; + } +} + +//------------------------------------------------------------------------------ +void GuiPopUpTextListCtrl::onMouseDragged(const GuiEvent &event) +{ + //the only way to get here, is if the scroll control is dragging + //else see if it's within the scroll control + GuiControl *parent = getParent(); + if ( !parent ) + return; + + GuiControl *grandParent = parent->getParent(); + if ( !grandParent ) + return; + + //let the scroll control deal with it + grandParent->onMouseDragged( event ); + //will highlight the text while dragging + onMouseMove( event ); + + if ( mMouseDown == AlreadyDown ) + { + if ( parent->cursorInControl() ) + mMouseDown = TextListArea; + return; + } + + if ( mMouseDown != ScrollArea ) + mPopUpCtrl->setupAutoScroll( event ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpTextListCtrl::onMouseUp(const GuiEvent &event) +{ + mPopUpCtrl->mScrollDir = GuiScrollCtrl::None; + + //see if it's within the scroll content ctrl + GuiControl *parent = getParent(); + if ( !parent ) + return; + + // Initial case--this control locks the mouse upon creation. + // If mouse is released outside of the menu, just move to next stage + // else if dragged over menu and released, select the appropriate entry + if ( mMouseDown == AlreadyDown ) + { + if ( !parent->cursorInControl() ) + { + mMouseDown = None; + return; + } + } + + if ( parent->cursorInControl() ) + { + // Select the item the mouse is over: + Point2I localPt = globalToLocalCoord( event.mousePoint ); + Point2I cell( 0, ( localPt.y < 0 ? -1 : localPt.y / mCellSize.y ) ); + if ( cell.y >= 0 && cell.y < mSize.y ) + cellSelected( Point2I( 0, cell.y ) ); + + //now let the pop up know a cell has been selected... + mPopUpCtrl->closePopUp(); + //we're done + return; + } + + //see if it's within the scroll control + GuiControl *grandParent = parent->getParent(); + if ( !grandParent ) + return; + + if ( grandParent->cursorInControl() ) + { + //let the scroll control deal with it + grandParent->onMouseUp( event ); + //and we're done + return; + } + + // otherwise, we clicked outside the popup, and we're done + // let the pop up know a cell has been selected... + if ( mMouseDown == None ) + mPopUpCtrl->closePopUp(); +} + + +//------------------------------------------------------------------------------ +void GuiPopUpTextListCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver) +{ + ColorI fontColor; + mPopUpCtrl->getFontColor( fontColor, mList[cell.y].id, selected, mouseOver ); + + dglSetBitmapModulation( fontColor ); + dglDrawText( mFont, Point2I( offset.x + 4, offset.y ), mList[cell.y].text ); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +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; +} + +//------------------------------------------------------------------------------ +GuiPopUpMenuCtrl::~GuiPopUpMenuCtrl() +{ + +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::initPersistFields(void) +{ + Parent::initPersistFields(); + + addField("maxPopupHeight", TypeS32, Offset(mMaxPopupHeight, GuiPopUpMenuCtrl)); +} + +//------------------------------------------------------------------------------ +static void cGuiPopUpMenuAdd(SimObject *obj, S32 argc, const char **argv) +{ + GuiPopUpMenuCtrl *ctrl = static_cast(obj); + if ( argc > 4 ) + ctrl->addEntry(argv[2],dAtoi(argv[3]),dAtoi(argv[4])); + else + ctrl->addEntry(argv[2],dAtoi(argv[3]),0); +} + +static void cGuiPopUpMenuAddScheme(SimObject *obj, S32, const char **argv) +{ + GuiPopUpMenuCtrl *ctrl = static_cast(obj); + 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 ); + + ctrl->addScheme( dAtoi( argv[2] ), fontColor, fontColorHL, fontColorSEL ); +} + +static void cGuiPopUpMenuSetText(SimObject *obj, S32, const char **argv) +{ + GuiPopUpMenuCtrl *ctrl = static_cast(obj); + ctrl->setText(argv[2]); +} + +static const char * cGuiPopUpMenuGetText(SimObject *obj, S32, const char **) +{ + GuiPopUpMenuCtrl *ctrl = static_cast(obj); + return ctrl->getText(); +} + +static void cGuiPopUpMenuClear(SimObject *obj, S32, const char **) +{ + GuiPopUpMenuCtrl *ctrl = static_cast(obj); + ctrl->clear(); +} + +static void cGuiPopUpMenuSort(SimObject *obj, S32, const char **) +{ + GuiPopUpMenuCtrl *ctrl = static_cast(obj); + ctrl->sort(); +} + +static void cGuiPopUpMenuOnAction(SimObject *obj, S32, const char **) +{ + GuiPopUpMenuCtrl *ctrl = static_cast(obj); + ctrl->onAction(); +} + +static void cGuiPopUpMenuClose(SimObject *obj, S32, const char **) +{ + GuiPopUpMenuCtrl *ctrl = static_cast(obj); + ctrl->closePopUp(); +} + +static S32 cGuiPopUpMenuGetSelected(SimObject * obj, S32, const char **) +{ + GuiPopUpMenuCtrl * ctrl = static_cast(obj); + return ctrl->getSelected(); +} + +static void cGuiPopUpMenuSetSelected(SimObject * obj, S32, const char ** argv) +{ + GuiPopUpMenuCtrl * ctrl = static_cast(obj); + ctrl->setSelected(dAtoi(argv[2])); +} + +static const char * cGuiPopUpMenuGetTextById(SimObject * obj, S32, const char ** argv) +{ + GuiPopUpMenuCtrl * ctrl = static_cast(obj); + return(ctrl->getTextById(dAtoi(argv[2]))); +} + +//------------------------------------------------------------------------------ +// this fills the popup with a classrep's field enumeration type info - 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.. +// +// args: +// argv[2] - class +// argv[3] - field +static void cGuiPopUpMenuSetEnumContent(SimObject * obj, S32, const char ** argv) +{ + GuiPopUpMenuCtrl * menu = static_cast(obj); + 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++) + menu->addEntry(field.table->table[i].label, field.table->table[i].index); +} + +//------------------------------------------------------------------------------ +static S32 cGuiPopUpMenuFindText( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cGuiPopUpMenuFindText is not a GuiPopUpMenuCtrl!" ); + GuiPopUpMenuCtrl* ctrl = static_cast( obj ); + return( ctrl->findText( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static S32 cGuiPopUpSize( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cGuiPopUpSize is not a GuiPopUpMenuCtrl!" ); + GuiPopUpMenuCtrl* ctrl = static_cast( obj ); + return( ctrl->getNumEntries() ); +} + +//------------------------------------------------------------------------------ +void cGuiReplaceText( SimObject* obj, S32, const char** argv) +{ + GuiPopUpMenuCtrl* ctrl = static_cast( obj ); + ctrl->replaceText(dAtoi(argv[2])); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::consoleInit() +{ + Con::addCommand("GuiPopUpMenuCtrl", "sort", cGuiPopUpMenuSort, "menu.sort()", 2, 2); + Con::addCommand("GuiPopUpMenuCtrl", "add", cGuiPopUpMenuAdd, "menu.add(name,idNum{,scheme})", 4, 5); + Con::addCommand("GuiPopUpMenuCtrl", "addScheme", cGuiPopUpMenuAddScheme, "menu.addScheme(id, fontColor, fontColorHL, fontColorSEL)", 6, 6); + Con::addCommand("GuiPopUpMenuCtrl", "getText", cGuiPopUpMenuGetText, "menu.getText()", 2, 2); + Con::addCommand("GuiPopUpMenuCtrl", "setText", cGuiPopUpMenuSetText, "menu.setText(text)", 3, 3); + Con::addCommand("GuiPopUpMenuCtrl", "getValue", cGuiPopUpMenuGetText, "menu.getValue()", 2, 2); + Con::addCommand("GuiPopUpMenuCtrl", "setValue", cGuiPopUpMenuSetText, "menu.setValue(text)", 3, 3); + Con::addCommand("GuiPopUpMenuCtrl", "clear", cGuiPopUpMenuClear, "menu.clear()", 2, 2); + Con::addCommand("GuiPopUpMenuCtrl", "forceOnAction", cGuiPopUpMenuOnAction, "menu.forceOnAction()", 2, 2); + Con::addCommand("GuiPopUpMenuCtrl", "forceClose", cGuiPopUpMenuClose, "menu.forceClose()", 2, 2); + Con::addCommand("GuiPopUpMenuCtrl", "getSelected", cGuiPopUpMenuGetSelected, "menu.getSelected()", 2, 2); + Con::addCommand("GuiPopUpMenuCtrl", "setSelected", cGuiPopUpMenuSetSelected, "menu.setSelected(id)", 3, 3); + Con::addCommand("GuiPopUpMenuCtrl", "getTextById", cGuiPopUpMenuGetTextById, "menu.getTextById(id)", 3, 3); + Con::addCommand("GuiPopUpMenuCtrl", "setEnumContent", cGuiPopUpMenuSetEnumContent, "menu.setEnumContent(class, enum)", 4, 4); + Con::addCommand("GuiPopUpMenuCtrl", "findText", cGuiPopUpMenuFindText, "menu.findText(text)", 3, 3); + Con::addCommand("GuiPopUpMenuCtrl", "size", cGuiPopUpSize, "menu.size()", 2, 2); + Con::addCommand("GuiPopUpMenuCtrl", "replaceText", cGuiReplaceText, "menu.replaceText(bool)", 3, 3); +} + +//------------------------------------------------------------------------------ +bool GuiPopUpMenuCtrl::onAdd() +{ + if (!Parent::onAdd()) + return false; + mSelIndex = -1; + mReplaceText = true; + return true; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::onSleep() +{ + Parent::onSleep(); + closePopUp(); // Tests in function. +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::clear() +{ + mEntries.setSize(0); + setText(""); + mSelIndex = -1; + mRevNum = 0; +} + +//------------------------------------------------------------------------------ +static S32 QSORT_CALLBACK textCompare(const void *a,const void *b) +{ + GuiPopUpMenuCtrl::Entry *ea = (GuiPopUpMenuCtrl::Entry *) (a); + GuiPopUpMenuCtrl::Entry *eb = (GuiPopUpMenuCtrl::Entry *) (b); + return (dStricmp(eb->buf, ea->buf)); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::sort() +{ + dQsort((void *)&(mEntries[0]), mEntries.size(), sizeof(Entry), textCompare); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::addEntry(const char *buf, S32 id, U32 scheme) +{ + 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; + + 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) +{ + S32 i; + for (i = 0; U32(i) < mEntries.size(); i++) + if (id == mEntries[i].id) + { + i = (mRevNum > i) ? mRevNum - i : i; + mSelIndex = i; + setText(mEntries[i].buf); + return; + } + + setText(""); + mSelIndex = -1; +} + +//------------------------------------------------------------------------------ +const char *GuiPopUpMenuCtrl::getScriptValue() +{ + return getText(); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + updateRect; + firstResponder; + Point2I localStart; + + if(mScrollDir != GuiScrollCtrl::None) + autoScroll(); + RectI r(offset, mBounds.extent); + dglDrawRectFill(r, mProfile->mBorderColor); + r.point.x +=2; + r.point.y +=2; + r.extent.x -=4; + r.extent.y -=4; + dglDrawRectFill(r, mProfile->mFillColor); + + S32 txt_w = mFont->getStrWidth(mText); + localStart.x = 0; + localStart.y = (mBounds.extent.y - (mFont->getHeight())) / 2; + + // align the horizontal + switch (mProfile->mAlignment) + { + case GuiControlProfile::RightJustify: + localStart.x = mBounds.extent.x - txt_w; + break; + case GuiControlProfile::CenterJustify: + localStart.x = (mBounds.extent.x - txt_w) / 2; + break; + default: + // GuiControlProfile::LeftJustify + localStart.x = 0; + break; + } + Point2I globalStart = localToGlobalCoord(localStart); + dglSetBitmapModulation(mProfile->mFontColor); + dglDrawText(mFont, globalStart, mText, mProfile->mFontColors); +} + +//------------------------------------------------------------------------------ +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(); + + // Pop the background: + getRoot()->popDialogControl(mBackground); + + // Kill the popup: + mBackground->removeObject( mSc ); + mTl->deleteObject(); + mSc->deleteObject(); + mBackground->deleteObject(); + + // Set this as the first responder: + setFirstResponder(); + + // Now perform the popup action: + if ( mSelIndex != -1 ) + { + char idval[24]; + dSprintf( idval, sizeof(idval), "%d", mEntries[mSelIndex].id ); + Con::executef( this, 3, "onSelect", idval, mEntries[mSelIndex].buf ); + } + else + Con::executef( this, 1, "onCancel" ); + + // Execute the popup console command: + if ( mConsoleCommand[0] ) + Con::evaluate( mConsoleCommand, false ); +} + +//------------------------------------------------------------------------------ +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->mBounds.extent; + + mBackground->mBounds.point.set(0,0); + mBackground->mBounds.extent = root->mBounds.extent; + + S32 textWidth = 0, width = mBounds.extent.x; + const S32 menuSpace = 5; + const S32 textSpace = 2; + bool setScroll = false; + + for(U32 i=0; igetStrWidth(mEntries[i].buf)) > textWidth) + textWidth = mFont->getStrWidth(mEntries[i].buf); + + if(textWidth > mBounds.extent.x) + { + textWidth +=10; + width = textWidth; + } + + mTl->setCellSize(Point2I(width, mFont->getHeight()+3)); + + for(U32 j=0; jaddEntry(mEntries[j].id, mEntries[j].buf); + + Point2I pointInGC = canCtrl->localToGlobalCoord(mBounds.point); + Point2I scrollPoint(pointInGC.x, pointInGC.y + mBounds.extent.y); + + //Calc max Y distance, so Scroll Ctrl will fit on window + S32 maxYdis = windowExt.y - pointInGC.y - mBounds.extent.y - menuSpace; + + //If scroll bars need to be added + if(maxYdis < mTl->mBounds.extent.y + textSpace) + { + //Should we pop menu list above the button + if(maxYdis < pointInGC.y - menuSpace) + { + reverseTextList(); + maxYdis = pointInGC.y - menuSpace; + //Does the menu need a scroll bar + if(maxYdis < mTl->mBounds.extent.y + textSpace) + { + //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); + setScroll = true; + } + //No scroll bar needed + else + { + maxYdis = mTl->mBounds.extent.y + textSpace; + scrollPoint.set(pointInGC.x, pointInGC.y - maxYdis -1); + } + } + //Scroll bar needed but Don't pop above button + else + { + mRevNum = 0; + //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 + maxYdis = mTl->mBounds.extent.y + textSpace; + + //offset it from the background so it lines up properly + mSc->mBounds.point = mBackground->globalToLocalCoord(scrollPoint); + + if(mSc->mBounds.point.x + width > mBackground->mBounds.extent.x) + if(width - mBounds.extent.x > 0) + mSc->mBounds.point.x -= width - mBounds.extent.x; + + mSc->mBounds.extent.set(width-1, maxYdis); + + mSc->registerObject(); + mTl->registerObject(); + mBackground->registerObject(); + + mSc->addObject( mTl ); + mBackground->addObject( mSc ); + + root->pushDialogControl(mBackground,canCtrl->mLayer); + + if ( setScroll ) + { + if ( mSelIndex ) + mTl->scrollSelectionTop(); + else + mTl->scrollCellVisible( Point2I( 0, 0 ) ); + } + + mTl->setFirstResponder(); + mTl->mouseLock(); + + mInAction = true; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::addChildren() +{ + mTl = new GuiPopUpTextListCtrl(this); + AssertFatal(mTl, "Failed to create the GuiPopUpTextListCtrl for the PopUpMenu"); + mTl->mProfile = mProfile; + mTl->setField("noDuplicates", "false"); + + mSc = new GuiScrollCtrl; + AssertFatal(mSc, "Failed to create the GuiScrollCtrl for the PopUpMenu"); + mSc->mProfile = mProfile; + mSc->setField("hScrollBar","AlwaysOff"); + mSc->setField("vScrollBar","dynamic"); + + mBackground = new GuiBackgroundCtrl; + 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->mFontColor; + + return( true ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::onMouseDown(const GuiEvent &event) +{ + event; + onAction(); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::onMouseUp(const GuiEvent &event) +{ + event; +} + +//------------------------------------------------------------------------------ +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->mBounds.extent.y || mousePt.y < 0) + { + S32 topOrBottom = (mousePt.y > mSc->mBounds.extent.y) ? 1 : 0; + mSc->scrollTo(0, topOrBottom); + return; + } + + F32 percent = (F32)mousePt.y / (F32)mSc->mBounds.extent.y; + 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; +} +// EOF // diff --git a/gui/guiPopUpCtrl.h b/gui/guiPopUpCtrl.h new file mode 100644 index 0000000..21a5fab --- /dev/null +++ b/gui/guiPopUpCtrl.h @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIPOPUPCTRL_H_ +#define _GUIPOPUPCTRL_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "GUI/guiTextCtrl.h" +#endif +#ifndef _GUITEXTLISTCTRL_H_ +#include "GUI/guiTextListCtrl.h" +#endif +#ifndef _GUIBUTTONCTRL_H_ +#include "GUI/guiButtonCtrl.h" +#endif +#ifndef _GUIBACKGROUNDCTRL_H_ +#include "GUI/guiBackgroundCtrl.h" +#endif +#ifndef _GUISCROLLCTRL_H_ +#include "GUI/guiScrollCtrl.h" +#endif +class GuiPopUpMenuCtrl; + +class GuiPopUpTextListCtrl : public GuiTextListCtrl +{ + private: + typedef GuiTextListCtrl Parent; + + public: + enum ButtonDown + { + None = 0, + AlreadyDown = 1, + ScrollArea = 2, + TextListArea = 3 + }; + + protected: + GuiPopUpMenuCtrl *mPopUpCtrl; + ButtonDown mMouseDown; + + public: + GuiPopUpTextListCtrl(); // for inheritance + GuiPopUpTextListCtrl(GuiPopUpMenuCtrl *ctrl); + ~GuiPopUpTextListCtrl(); + + void onSleep(); + + // GuiArrayCtrl overload: + void onCellSelected(Point2I cell); + + // GuiControl overloads: + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + bool onKeyDown(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; + }; + + struct Scheme + { + U32 id; + ColorI fontColor; + ColorI fontColorHL; + ColorI fontColorSEL; + }; + + protected: + GuiPopUpTextListCtrl *mTl; + GuiScrollContentCtrl *mSContentc; + GuiScrollCtrl *mSc; + GuiBackgroundCtrl *mBackground; + Vector mEntries; + Vector mSchemes; + S32 mSelIndex; + S32 mMaxPopupHeight; + F32 mIncValue; + F32 mScrollCount; + S32 mLastYvalue; + GuiEvent mEventSave; + S32 mRevNum; + bool mInAction; + bool mReplaceText; + + virtual void addChildren(); + virtual void repositionPopup(); + + public: + GuiPopUpMenuCtrl(void); + ~GuiPopUpMenuCtrl(); + GuiScrollCtrl::Region mScrollDir; + bool onAdd(); + void onSleep(); + static void consoleInit(); + void sort(); + void addEntry(const char *buf, S32 id, U32 scheme = 0); + void addScheme(U32 id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void onAction(); + virtual void closePopUp(); + void clear(); + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void setupAutoScroll(const GuiEvent &event); + void autoScroll(); + bool onKeyDown(const GuiEvent &event); + void reverseTextList(); + bool getFontColor(ColorI &fontColor, S32 id, bool selected, bool mouseOver); + + S32 getSelected(); + void setSelected(S32 id); + 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/guiProgressCtrl.cc b/gui/guiProgressCtrl.cc new file mode 100644 index 0000000..6522e6e --- /dev/null +++ b/gui/guiProgressCtrl.cc @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" + +#include "GUI/guiProgressCtrl.h" + +GuiProgressCtrl::GuiProgressCtrl() +{ + mProgress = 0.0f; +} + +const char* GuiProgressCtrl::getScriptValue() +{ + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%f", 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, GuiControl *firstResponder) +{ + RectI ctrlRect(offset, mBounds.extent); + + //draw the progress + S32 width = (S32)((F32)mBounds.extent.x * mProgress); + if (width > 0) + { + RectI progressRect = ctrlRect; + progressRect.extent.x = width; + dglDrawRectFill(progressRect, mProfile->mFillColor); + } + + //now draw the border + if (mProfile->mBorder) + { + dglDrawRect(ctrlRect, mProfile->mBorderColor); + + RectI copyRect = ctrlRect; + copyRect.point.x += 1; + copyRect.point.y += 1; + copyRect.extent.x -= 2; + copyRect.extent.y -= 2; + ColorI color = mProfile->mBorderColor*0.75; + color.alpha = 255; + dglDrawRect(copyRect, color); + } + + //render the children + renderChildControls(offset, updateRect, firstResponder); +} + diff --git a/gui/guiProgressCtrl.h b/gui/guiProgressCtrl.h new file mode 100644 index 0000000..19cc5e3 --- /dev/null +++ b/gui/guiProgressCtrl.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIPROGRESSCTRL_H_ +#define _GUIPROGRESSCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +class GuiProgressCtrl : public GuiControl +{ +private: + typedef GuiControl 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, GuiControl *firstResponder); +}; + +#endif diff --git a/gui/guiRadioCtrl.cc b/gui/guiRadioCtrl.cc new file mode 100644 index 0000000..73af509 --- /dev/null +++ b/gui/guiRadioCtrl.cc @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "dgl/dgl.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiRadioCtrl.h" +#include "console/consoleTypes.h" + +//--------------------------------------------------------------------------- +GuiRadioCtrl::GuiRadioCtrl() +{ + mActive = true; + mBounds.extent.set(140, 30); + mKeyPressed = false; + mStateOn = false; + mGroupNum = -1; +} + +//--------------------------------------------------------------------------- +void GuiRadioCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("groupNum", TypeS32, Offset(mGroupNum, GuiRadioCtrl)); +} + +//--------------------------------------------------------------------------- +void GuiRadioCtrl::consoleInit() +{ +} + +//--------------------------------------------------------------------------- +void GuiRadioCtrl::AcceleratorKeyPress(void) +{ + if (! mActive) return; + + //set the bool + mKeyPressed = true; + + if (mProfile->mTabable) + { + setFirstResponder(); + } + +} + +//--------------------------------------------------------------------------- +void GuiRadioCtrl::AcceleratorKeyRelease(void) +{ + if (! mActive) return; + + //set the bool + mKeyPressed = false; + //perform the action + onAction(); + + //update + setUpdate(); +} + +//--------------------------------------------------------------------------- +bool GuiRadioCtrl::onKeyDown(const GuiEvent &event) +{ + //if the control is a dead end, kill the event + if ((! mVisible) || (! mActive) || (! mAwake)) + return true; + + //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 it's parent + return Parent::onKeyDown(event); +} + +//--------------------------------------------------------------------------- +void GuiRadioCtrl::drawBorder(const RectI &r, const ColorI &color) +{ + Point2I p1, p2; + p1 = r.point; + p2 = r.point; + p2.x += r.extent.x - 1; + p2.y += r.extent.y - 1; + + glColor4ub(color.red, color.green, color.blue, color.alpha); + glBegin(GL_LINE_LOOP); + glVertex2i(p1.x + 2, p1.y); + glVertex2i(p2.x - 2, p1.y); + glVertex2i(p2.x, p1.y + 2); + glVertex2i(p2.x, p2.y - 2); + glVertex2i(p2.x - 2, p2.y); + glVertex2i(p1.x + 2, p2.y); + glVertex2i(p1.x, p2.y - 2); + glVertex2i(p1.x, p1.y + 2); + glEnd(); +} + +//--------------------------------------------------------------------------- +void GuiRadioCtrl::onAction() +{ + mStateOn = true; + if (mGroupNum >= 0) + messageSiblings(mGroupNum); + + setUpdate(); + Parent::onAction(); +} + +//--------------------------------------------------------------------------- +void GuiRadioCtrl::onMessage(GuiControl *sender, S32 msg) +{ + Parent::onMessage(sender, msg); + if (mGroupNum == msg) + { + setUpdate(); + mStateOn = (sender == this); + } +} + +//--------------------------------------------------------------------------- +void GuiRadioCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + bool stateOver = cursorInControl(); + bool stateDepressed = mGroupNum >= 0 && mStateOn; + ColorI fontColor = (stateOver ? mProfile->mFontColorHL : mProfile->mFontColor); + ColorI backColor = mActive ? mProfile->mFillColor : mProfile->mFillColorNA; + ColorI insideBorderColor = (firstResponder == this) ? mProfile->mBorderColorHL : mProfile->mBorderColor; + ColorI outsideBorderColor = mProfile->mBorderColor; + + S32 txt_w = mFont->getStrWidth(mText); + + Point2I localStart; + + // align the horizontal + switch (mProfile->mAlignment) + { + case GuiControlProfile::RightJustify: + localStart.x = mBounds.extent.x - txt_w; + break; + case GuiControlProfile::CenterJustify: + localStart.x = (mBounds.extent.x - txt_w) / 2; + break; + default: + // GuiControlProfile::LeftJustify + localStart.x = 0; + break; + } + + // center the vertical + localStart.y = (mBounds.extent.y - (mFont->getHeight() - 2)) / 2; + + // first draw the background + RectI r(offset, mBounds.extent); + dglDrawRectFill(r, backColor); + r.point.x +=2; + r.point.y +=2; + r.extent.x -=4; + r.extent.y -=4; + dglDrawRectFill(r, backColor); + + //draw the radio box + glColor3f(0.4f,0.4f,0.4f); + glBegin(GL_LINE_LOOP); + glVertex2i(r.point.x+3, r.point.y+3); + glVertex2i(r.point.x+12, r.point.y+3); + glVertex2i(r.point.x+12, r.point.y+12); + glVertex2i(r.point.x+3, r.point.y+12); + glEnd(); + + if (stateDepressed) + { + glColor3f(0.0f,0.0f,0.0f); + glBegin(GL_QUADS); + glVertex2i(r.point.x+6, r.point.y+6); + glVertex2i(r.point.x+10, r.point.y+6); + glVertex2i(r.point.x+10, r.point.y+10); + glVertex2i(r.point.x+6, r.point.y+10); + glEnd(); + } + // draw the oustside border + r.point.x -=1; + r.point.y -=1; + r.extent.x +=2; + r.extent.y +=2; + drawBorder(r, outsideBorderColor); + + // finally draw the text + localStart.y -= 2; + localStart.x += 4; + if (stateDepressed) + { + localStart.x += 1; + localStart.y += 1; + } + Point2I globalStart = localToGlobalCoord(localStart); + + dglSetBitmapModulation(fontColor); + dglDrawText(mFont, globalStart, mText, mProfile->mFontColors); + + //render the children + renderChildControls(offset, updateRect, firstResponder); +} + +//--------------------------------------------------------------------------- +void GuiRadioCtrl::setScriptValue(const char *newState) +{ + if(dAtob(newState)) + onAction(); + else + mStateOn = false; + setUpdate(); +} + +//--------------------------------------------------------------------------- +const char *GuiRadioCtrl::getScriptValue() +{ + return mStateOn ? "1" : "0"; +} + +// EOF // + diff --git a/gui/guiRadioCtrl.h b/gui/guiRadioCtrl.h new file mode 100644 index 0000000..a395e24 --- /dev/null +++ b/gui/guiRadioCtrl.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIRADIOCTRL_H_ +#define _GUIRADIOCTRL_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "GUI/guiTextCtrl.h" +#endif + +class GuiRadioCtrl : public GuiTextCtrl +{ +private: + typedef GuiTextCtrl Parent; + +protected: + bool mKeyPressed; + bool mStateOn; + S32 mGroupNum; + +public: + DECLARE_CONOBJECT(GuiRadioCtrl); + GuiRadioCtrl(); + static void consoleInit(); + static void initPersistFields(); + + void AcceleratorKeyPress(void); + void AcceleratorKeyRelease(void); + + bool onKeyDown(const GuiEvent &event); + + // GuiTextCtrl updates mText with the variable value, which is not desired + void onPreRender() {} + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void drawBorder(const RectI &r, const ColorI &color); + void onAction(); + void onMessage(GuiControl *,S32 msg); + void setScriptValue(const char *); + const char *getScriptValue(); +}; + +#endif //_GUI_RADIO_CTRL_H diff --git a/gui/guiScrollCtrl.cc b/gui/guiScrollCtrl.cc new file mode 100644 index 0000000..025f8c0 --- /dev/null +++ b/gui/guiScrollCtrl.cc @@ -0,0 +1,956 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/gBitmap.h" +#include "dgl/gTexManager.h" +#include "Core/resManager.h" +#include "Platform/event.h" +#include "dgl/dgl.h" +#include "GUI/guiArrayCtrl.h" +#include "GUI/guiScrollCtrl.h" + +void GuiScrollContentCtrl::childResized(GuiControl *child) +{ + GuiControl *parent = getParent(); + if (parent) + { + ((GuiScrollCtrl *)parent)->computeSizes(); + } + + Parent::childResized(child); +} + +void GuiScrollContentCtrl::removeObject(SimObject *object) +{ + Parent::removeObject(object); + GuiScrollCtrl *sctrl = NULL; + GuiControl *parent = getParent(); + if (parent) + { + sctrl = dynamic_cast(parent); + } + if (sctrl) + { + sctrl->computeSizes(); + } +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +GuiScrollCtrl::GuiScrollCtrl() +{ + mBounds.extent.set(200,200); + mChildMargin.set(0,0); + mBorderThickness = 1; + mScrollBarThickness = 16; + mScrollBarArrowBtnLength = 16; + stateDepressed = false; + curHitRegion = None; + + mBitmapBounds = NULL; + + mLine_V = 0.1f; + mLine_H = 0.1f; + mPage_V = 0.2f; + mPage_H = 0.2f; + + mWillFirstRespond = true; + mThumbAnchorPos = 0.0f; + mUseConstantHeightThumb = false; + + mForceVScrollBar = ScrollBarAlwaysOn; + mForceHScrollBar = ScrollBarAlwaysOn; + + mVBarThumbPos = 0; + mHBarThumbPos = 0; + + mDefaultLineHeight = 15; + + mContentCtrl = NULL; +} + +GuiScrollCtrl::~GuiScrollCtrl() +{ + if ( mBitmapBounds != NULL ) + { + delete [] mBitmapBounds; + mBitmapBounds = NULL; + } +} + +static EnumTable::Enums scrollBarEnums[] = +{ + { GuiScrollCtrl::ScrollBarAlwaysOn, "alwaysOn" }, + { GuiScrollCtrl::ScrollBarAlwaysOff, "alwaysOff" }, + { GuiScrollCtrl::ScrollBarDynamic, "dynamic" }, +}; +static EnumTable gScrollBarTable(3, &scrollBarEnums[0]); + +static void cGuiScrollCtrlScrollToTop( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Control passed to cGuiScrollCtrlScrollToTop is not a GuiScrollCtrl!" ); + GuiScrollCtrl* control = static_cast( obj ); + control->scrollTo( 0, 0 ); +} + +static void cGuiScrollCtrlScrollToBottom( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Control passed to cGuiScrollCtrlScrollToBottom is not a GuiScrollCtrl!" ); + GuiScrollCtrl* control = static_cast( obj ); + control->scrollTo( 0, 1 ); +} + +void GuiScrollCtrl::consoleInit() +{ + Con::addCommand( "GuiScrollCtrl", "scrollToTop", cGuiScrollCtrlScrollToTop, "control.scrollToTop();", 2, 2 ); + Con::addCommand( "GuiScrollCtrl", "scrollToBottom", cGuiScrollCtrlScrollToBottom, "control.scrollToBottom();", 2, 2 ); +} + +void GuiScrollCtrl::initPersistFields() +{ + Parent::initPersistFields(); + + addField("willFirstRespond", TypeBool, Offset(mWillFirstRespond, GuiScrollCtrl)); + addField("hScrollBar", TypeEnum, Offset(mForceHScrollBar, GuiScrollCtrl), 1, &gScrollBarTable); + addField("vScrollBar", TypeEnum, Offset(mForceVScrollBar, GuiScrollCtrl), 1, &gScrollBarTable); + addField("constantThumbHeight", TypeBool, Offset(mUseConstantHeightThumb, GuiScrollCtrl)); + addField("defaultLineHeight", TypeS32, Offset(mDefaultLineHeight, GuiScrollCtrl)); + addField("childMargin", TypePoint2I, Offset(mChildMargin, GuiScrollCtrl)); + +} + +void GuiScrollCtrl::resize(const Point2I &newPos, const Point2I &newExt) +{ + Parent::resize(newPos, newExt); + computeSizes(); +} + +bool GuiScrollCtrl::onAdd() +{ + if(!Parent::onAdd()) + return false; + GuiScrollContentCtrl *content = new GuiScrollContentCtrl; + + if (!content->registerObject()) + Con::errorf(ConsoleLogEntry::General, "Unhandled execption, could not add content in GuiScrollCtrl::onAdd()"); + addObject(content); + + return true; +} + +bool GuiScrollCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + AssertFatal(size(), "Scroll control created with no content control."); + + mTextureHandle = mProfile->mTextureHandle; + + mBitmapBounds = new RectI[BmpStates * BmpCount]; + AssertFatal( mBitmapBounds, "Failed to allocate memory for bitmap array!" ); + + bool result = createBitmapArray(mTextureHandle.getBitmap(), mBitmapBounds, BmpStates, BmpCount); + AssertFatal(result, "Failed to create the bitmap array"); + + for(S32 i = 0; i < BmpStates; i++) + { + mBitmapBounds[BmpStates * BmpVThumb + i].inset(0, 1); + mBitmapBounds[BmpStates * BmpVPage + i].inset(0, 1); + mBitmapBounds[BmpStates * BmpHThumb + i].inset(1, 0); + mBitmapBounds[BmpStates * BmpHPage + i].inset(1, 0); + } + + //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(); + return true; +} + +void GuiScrollCtrl::onSleep() +{ + Parent::onSleep(); + + if ( mBitmapBounds ) + { + delete [] mBitmapBounds; + mBitmapBounds = NULL; + } + + mTextureHandle = NULL; +} + +bool GuiScrollCtrl::calcChildExtents(Point2I *pos, Point2I *ext) +{ + // loop through the children of this scroll view... + if (! mContentCtrl->size()) + return false; +#if 1 + GuiControl *ctrl = (GuiControl *) (mContentCtrl->front()); + *ext = ctrl->mBounds.extent; + *pos = ctrl->mBounds.point; + + return true; + +#else + bool anyVisible = false; + SimGroup::iterator i; + for(i = mContentCtrl->begin(); i != mContentCtrl->end();i++) + { + GuiControl *ctrl = (GuiControl *) (*i); + if (ctrl->isVisible()) + { + anyVisible = true; + pos->x = getMin(pos->x, ctrl->mBounds.point.x); + pos->y = getMin(pos->y, ctrl->mBounds.point.y); + if (ctrl->mBounds.point.x + ctrl->mBounds.extent.x > pos->x + ext->x) + ext->x = ctrl->mBounds.point.x + ctrl->mBounds.extent.x - pos->x; + if (ctrl->mBounds.point.y + ctrl->mBounds.extent.y > pos->y + ext->y) + ext->y = ctrl->mBounds.point.y + ctrl->mBounds.extent.y - pos->y; + } + } + return anyVisible; +#endif +} + +void GuiScrollCtrl::addObject(SimObject *object) +{ + GuiScrollContentCtrl *content = dynamic_cast(object); + if(content) + { + if(mContentCtrl) + mContentCtrl->deleteObject(); + Parent::addObject(object); + mContentCtrl = content; + computeSizes(); + return; + } + AssertFatal(mContentCtrl, "ERROR - no content control for a scroll control"); + + mContentCtrl->addObject(object); + computeSizes(); +} + +void GuiScrollCtrl::computeSizes() +{ + Point2I pos(mBorderThickness, mBorderThickness); + Point2I ext(mBounds.extent.x - (mBorderThickness << 1), mBounds.extent.y - (mBorderThickness << 1)); + Point2I cpos; + Point2I cext; + mHBarEnabled = false; + mVBarEnabled = false; + mHasVScrollBar = (mForceVScrollBar == ScrollBarAlwaysOn); + mHasHScrollBar = (mForceHScrollBar == ScrollBarAlwaysOn); + + setUpdate(); + + if (calcChildExtents(&cpos, &cext)) + { + if (mHasVScrollBar) + ext.x -= mScrollBarThickness; + if (mHasHScrollBar) + { + ext.y -= mScrollBarThickness; + } + if (cext.x > ext.x && (mForceHScrollBar == ScrollBarDynamic)) + { + mHasHScrollBar = true; + ext.y -= mScrollBarThickness; + } + if (cext.y > ext.y && (mForceVScrollBar == ScrollBarDynamic)) + { + mHasVScrollBar = true; + ext.x -= mScrollBarThickness; + // doh! ext.x changed, so check hscrollbar again + if (cext.x > ext.x && !mHasHScrollBar && (mForceHScrollBar == ScrollBarDynamic)) + { + mHasHScrollBar = true; + ext.y -= mScrollBarThickness; + } + } + + mContentCtrl->mBounds.point = pos + mChildMargin; + mContentCtrl->mBounds.extent = (ext - mChildMargin) - mChildMargin; + ext = mContentCtrl->mBounds.extent; + // see if the child controls need to be repositioned (null space in control) + Point2I delta(0,0); + + if (cpos.x > 0) + delta.x = -cpos.x; + else if (ext.x > cpos.x + cext.x) + { + S32 diff = ext.x - (cpos.x + cext.x); + delta.x = getMin(-cpos.x, diff); + } + + //reposition the children if the child extent > the scroll content extent + if (cpos.y > 0) + delta.y = -cpos.y; + else if (ext.y > cpos.y + cext.y) + { + S32 diff = ext.y - (cpos.y + cext.y); + delta.y = getMin(-cpos.y, diff); + } + + // apply the deltas to the children... + if (delta.x || delta.y) + { + SimGroup::iterator i; + for(i = mContentCtrl->begin(); i != mContentCtrl->end();i++) + { + GuiControl *ctrl = (GuiControl *) (*i); + ctrl->mBounds.point += delta; + } + cpos += delta; + } + if (cext.x < ext.x) + cext.x = ext.x; + if (cext.y < ext.y) + cext.y = ext.y; + + // enable needed scroll bars + if (cext.x > ext.x) + mHBarEnabled = true; + if (cext.y > ext.y) + mVBarEnabled = true; + + } + // build all the rectangles and such... + calcScrollRects(); + mLine_V = 0.1f; + mLine_H = 0.1f; + mPage_V = 0.2f; + mPage_H = 0.2f; + +/* + SimGroup::iterator i; + i = mContentCtrl->begin(); + if (i != mContentCtrl->end()) + { + GuiControl *ctrl = (GuiControl *) (*i); + RectI clientR(0,0,mContentCtrl->mBounds.extent.x, mContentCtrl->mBounds.extent.y); + if (ctrl->mBounds.extent.y) + { + mLine_V = ctrl->scroll_mLine_V(clientR) / (F32)ctrl->mBounds.extent.y; + + mPage_V = ctrl->scroll_mPage_V(clientR) / (F32)ctrl->mBounds.extent.y; + } + if (ctrl->mBounds.extent.x) + { + mLine_H = ctrl->scroll_mLine_H(clientR) / (F32)ctrl->mBounds.extent.x; + mPage_H = ctrl->scroll_mPage_H(clientR) / (F32)ctrl->mBounds.extent.x; + } + } +*/ + + calcThumbs(cpos, cext); +} + +void GuiScrollCtrl::calcScrollRects(void) +{ + if (mHasHScrollBar) + { + mLeftArrowRect.set(mBorderThickness, + mBounds.extent.y - mBorderThickness - mScrollBarThickness - 1, + mScrollBarArrowBtnLength, + mScrollBarThickness); + + mRightArrowRect.set(mBounds.extent.x - mBorderThickness - (mHasVScrollBar ? mScrollBarThickness : 0) - mScrollBarArrowBtnLength, + mBounds.extent.y - mBorderThickness - mScrollBarThickness - 1, + 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(mBounds.extent.x - mBorderThickness - mScrollBarThickness, + mBorderThickness, + mScrollBarThickness, + mScrollBarArrowBtnLength); + mDownArrowRect.set(mBounds.extent.x - mBorderThickness - mScrollBarThickness, + mBounds.extent.y - mBorderThickness - mScrollBarArrowBtnLength - (mHasHScrollBar ? ( mScrollBarThickness + 1 ) : 0), + 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(Point2I cpos, Point2I cext) +{ + Point2I ext = mContentCtrl->mBounds.extent; + if (mHBarEnabled) + { + mHBarThumbPos = -cpos.x / F32(cext.x - ext.x); + mHBarThumbWidth = ext.x / F32(cext.x); + + if (mUseConstantHeightThumb) + mHThumbSize = mBaseThumbSize; + else + mHThumbSize = getMax(mBaseThumbSize, S32(mHBarThumbWidth * mHTrackRect.len_x())); + mHThumbPos = (S32)(mHTrackRect.point.x + mHBarThumbPos * (mHTrackRect.len_x() - mHThumbSize)); + } + if (mVBarEnabled) + { + mVBarThumbPos = -cpos.y / F32(cext.y - ext.y); + mVBarThumbWidth = ext.y / F32(cext.y); + + if (mUseConstantHeightThumb) + mVThumbSize = mBaseThumbSize; + else + mVThumbSize = getMax(mBaseThumbSize, S32(mVBarThumbWidth * mVTrackRect.len_y())); + mVThumbPos = (S32)(mVTrackRect.point.y + mVBarThumbPos * (mVTrackRect.len_y() - mVThumbSize)); + } +} + +void GuiScrollCtrl::scrollTo(F32 x, F32 y) +{ + setUpdate(); + if (x < 0) + x = 0; + else if (x > 1) + x = 1; + + if (y < 0) + y = 0; + else if (y > 1) + y = 1; + + if (x == mHBarThumbPos && y == mVBarThumbPos) + return; + + Point2I cpos, cext; + + if (calcChildExtents(&cpos, &cext)) + { + Point2I ext = mContentCtrl->mBounds.extent; + Point2I npos; + if (ext.x < cext.x) + npos.x = (S32)(-x * (cext.x - ext.x)); + else + npos.x = 0; + if (ext.y < cext.y) + npos.y = (S32)(-y * (cext.y - ext.y)); + else + npos.y = 0; + + Point2I delta(npos.x - cpos.x, npos.y - cpos.y); + // get rid of bad (roundoff) deltas + if (x == mHBarThumbPos) + delta.x = 0; + if (y == mVBarThumbPos) + delta.y = 0; + SimGroup::iterator i; + + for(i = mContentCtrl->begin(); i != mContentCtrl->end();i++) + { + GuiControl *ctrl = (GuiControl *) (*i); + ctrl->mBounds.point += delta; + } + cpos += delta; + calcThumbs(cpos, cext); + } +} + +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; + } + } + return Parent::onKeyDown(event); +} + +void GuiScrollCtrl::onMouseDown(const GuiEvent &event) +{ + mouseLock(); + setFirstResponder(); + + setUpdate(); + + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + curHitRegion = findHitRegion(curMousePos); + stateDepressed = true; + + scrollByRegion(curHitRegion); + + if (curHitRegion == VertThumb) + { + mThumbAnchorPos = mVBarThumbPos; + mThumbDelta = curMousePos.y - mVThumbPos; + } + else if (curHitRegion == HorizThumb) + { + mThumbAnchorPos = mHBarThumbPos; + mThumbDelta = curMousePos.x - mHThumbPos; + } +} + +void GuiScrollCtrl::onMouseUp(const GuiEvent &) +{ + mouseUnlock(); + + setUpdate(); + + curHitRegion = None; + stateDepressed = false; +} + +void GuiScrollCtrl::onMouseDragged(const GuiEvent &event) +{ + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + setUpdate(); + + if ( (curHitRegion != VertThumb) && (curHitRegion != HorizThumb) ) + { + Region hit = findHitRegion(curMousePos); + if (hit != curHitRegion) + stateDepressed = false; + else + stateDepressed = true; + return; + } + + // ok... if the mouse is 'near' the scroll bar, scroll with it + // otherwise, snap back to the previous position. + + if (curHitRegion == VertThumb) + { + if (curMousePos.x >= mVTrackRect.point.x - mScrollBarThickness && + curMousePos.x <= mVTrackRect.point.x + mVTrackRect.extent.x - 1 + mScrollBarThickness && + curMousePos.y >= mVTrackRect.point.y - mScrollBarThickness && + curMousePos.y <= mVTrackRect.point.y + mVTrackRect.extent.y - 1 + mScrollBarThickness) + { + scrollTo(mHBarThumbPos, + (curMousePos.y - mThumbDelta - mVTrackRect.point.y) / + F32(mVTrackRect.len_y() - mVThumbSize)); + } + else + scrollTo(mHBarThumbPos, mThumbAnchorPos); + } + else if (curHitRegion == HorizThumb) + { + if (curMousePos.x >= mHTrackRect.point.x - mScrollBarThickness && + curMousePos.x <= mHTrackRect.point.x + mHTrackRect.extent.x - 1 + mScrollBarThickness && + curMousePos.y >= mHTrackRect.point.y - mScrollBarThickness && + curMousePos.y <= mHTrackRect.point.y + mHTrackRect.extent.y - 1 + mScrollBarThickness) + { + scrollTo((curMousePos.x - mThumbDelta - mHTrackRect.point.x) / + F32(mHTrackRect.len_x() - mHThumbSize), + mVBarThumbPos); + } + else + scrollTo(mThumbAnchorPos, mVBarThumbPos); + } +} + +bool GuiScrollCtrl::onMouseWheelUp(const GuiEvent &event) +{ + if ( !mAwake || !mVisible ) + return( false ); + + scrollByRegion((event.modifier & SI_CTRL) ? UpPage : UpArrow); + + // Tell the kids that the mouse moved (relatively): + iterator itr; + for ( itr = mContentCtrl->begin(); itr != mContentCtrl->end(); itr++ ) + { + GuiControl* grandKid = static_cast( *itr ); + grandKid->onMouseMove( event ); + } + + return( true ); +} + +bool GuiScrollCtrl::onMouseWheelDown(const GuiEvent &event) +{ + if ( !mAwake || !mVisible ) + return( false ); + + scrollByRegion((event.modifier & SI_CTRL) ? DownPage : DownArrow); + + // Tell the kids that the mouse moved (relatively): + iterator itr; + for ( itr = mContentCtrl->begin(); itr != mContentCtrl->end(); itr++ ) + { + GuiControl* grandKid = static_cast( *itr ); + grandKid->onMouseMove( event ); + } + + return( true ); +} + + +void GuiScrollCtrl::scrollByRegion(Region reg) +{ + setUpdate(); + if (mVBarEnabled) + { + bool alreadyScrolled = false; + GuiControl* grandKid = NULL; + GuiArrayCtrl *ac = NULL; + if (mContentCtrl->begin()) + { + grandKid = (GuiControl *)(mContentCtrl->front()); + ac = dynamic_cast(grandKid); + } + + if ( ac || ( grandKid && mDefaultLineHeight > 0 ) ) + { + S32 lineHeight, numCells; + if ( ac ) + ac->getScrollDimensions(lineHeight, numCells); + else + { + lineHeight = mDefaultLineHeight; + numCells = 47; // OK, we're just fakin'... + } + + if (lineHeight > 0 && numCells > 0) + { + S32 pageHeight = S32(mContentCtrl->mBounds.extent.y / lineHeight) * (lineHeight - 1) - 1; + switch(reg) + { + case UpPage: + grandKid->mBounds.point.y = getMin(0, getMax(mContentCtrl->mBounds.extent.y - grandKid->mBounds.extent.y, grandKid->mBounds.point.y + pageHeight)); + break; + case DownPage: + grandKid->mBounds.point.y = getMin(0, getMax(mContentCtrl->mBounds.extent.y - grandKid->mBounds.extent.y, grandKid->mBounds.point.y - pageHeight)); + break; + case UpArrow: + grandKid->mBounds.point.y = getMin(0, getMax(mContentCtrl->mBounds.extent.y - grandKid->mBounds.extent.y, grandKid->mBounds.point.y + lineHeight)); + break; + case DownArrow: + grandKid->mBounds.point.y = getMin(0, getMax(mContentCtrl->mBounds.extent.y - grandKid->mBounds.extent.y, grandKid->mBounds.point.y - lineHeight)); + break; + } + calcThumbs(grandKid->mBounds.point, grandKid->mBounds.extent); + alreadyScrolled = true; + } + } + + if (! alreadyScrolled) + { + // If all else fails, scroll by percentage: + mPage_V = 0.1f; + mLine_V = 0.1f; + + switch(reg) + { + case UpPage: + scrollTo(mHBarThumbPos, mVBarThumbPos - mPage_V); + break; + case DownPage: + scrollTo(mHBarThumbPos, mVBarThumbPos + mPage_V); + break; + case UpArrow: + scrollTo(mHBarThumbPos, mVBarThumbPos - mLine_V); + break; + case DownArrow: + scrollTo(mHBarThumbPos, mVBarThumbPos + mLine_V); + break; + } + } + } + + if (mHBarEnabled) + { + switch(reg) + { + case LeftPage: + scrollTo(mHBarThumbPos - mPage_H, mVBarThumbPos); + break; + case RightPage: + scrollTo(mHBarThumbPos + mPage_H, mVBarThumbPos); + break; + case LeftArrow: + scrollTo(mHBarThumbPos - mLine_H, mVBarThumbPos); + break; + case RightArrow: + scrollTo(mHBarThumbPos + mLine_H, mVBarThumbPos); + break; + } + } +} + + +void GuiScrollCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + // draw border + drawBorder( offset, ( firstResponder == this ) ); + + // draw scroll bars + if (mHasVScrollBar) + drawVScrollBar(offset); + + if (mHasHScrollBar) + drawHScrollBar(offset); + + //draw the scroll corner + if (mHasVScrollBar && mHasHScrollBar) + drawScrollCorner(offset); + + // draw content and its children + renderChildControls(offset, updateRect, firstResponder); +} + +void GuiScrollCtrl::drawBorder( const Point2I &offset, bool /*isFirstResponder*/ ) +{ + RectI r(offset.x, offset.y, mBounds.extent.x, mBounds.extent.y); + + if (mProfile->mOpaque) + dglDrawRectFill(r, mProfile->mFillColor); + + if (mProfile->mBorder) + dglDrawRect(r, mProfile->mBorderColor); +} + +void GuiScrollCtrl::drawVScrollBar(const Point2I &offset) +{ + Point2I pos = offset + mUpArrowRect.point; + S32 bitmap = (mVBarEnabled ? ((curHitRegion == UpArrow && stateDepressed) ? + BmpStates * BmpUp + BmpHilite : BmpStates * BmpUp) : BmpStates * BmpUp + BmpDisabled); + + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[bitmap]); + + pos.y += mScrollBarArrowBtnLength; + S32 end; + if (mVBarEnabled) + end = mVThumbPos + offset.y; + else + end = mDownArrowRect.point.y + offset.y; + + bitmap = (mVBarEnabled ? ((curHitRegion == DownPage && stateDepressed) ? + BmpStates * BmpVPage + BmpHilite : BmpStates * BmpVPage) : BmpStates * BmpVPage + BmpDisabled); + + if (end > pos.y) + { + dglClearBitmapModulation(); + dglDrawBitmapStretchSR(mTextureHandle, RectI(pos, Point2I(mBitmapBounds[bitmap].extent.x, end - pos.y)), mBitmapBounds[bitmap]); + } + + pos.y = end; + if (mVBarEnabled) + { + bool thumbSelected = (curHitRegion == VertThumb && stateDepressed); + S32 ttop = (thumbSelected ? BmpStates * BmpVThumbTopCap + BmpHilite : BmpStates * BmpVThumbTopCap); + S32 tmid = (thumbSelected ? BmpStates * BmpVThumb + BmpHilite : BmpStates * BmpVThumb); + S32 tbot = (thumbSelected ? BmpStates * BmpVThumbBottomCap + BmpHilite : BmpStates * BmpVThumbBottomCap); + + // draw the thumb + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[ttop]); + pos.y += mBitmapBounds[ttop].extent.y; + end = mVThumbPos + mVThumbSize - mBitmapBounds[tbot].extent.y + offset.y; + + if (end > pos.y) + { + dglClearBitmapModulation(); + dglDrawBitmapStretchSR(mTextureHandle, RectI(pos, Point2I(mBitmapBounds[tmid].extent.x, end - pos.y)), mBitmapBounds[tmid]); + } + + pos.y = end; + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[tbot]); + pos.y += mBitmapBounds[tbot].extent.y; + end = mVTrackRect.point.y + mVTrackRect.extent.y - 1 + offset.y; + + bitmap = (curHitRegion == DownPage && stateDepressed) ? BmpStates * BmpVPage + BmpHilite : BmpStates * BmpVPage; + if (end > pos.y) + { + dglClearBitmapModulation(); + dglDrawBitmapStretchSR(mTextureHandle, RectI(pos, Point2I(mBitmapBounds[bitmap].extent.x, end - pos.y)), mBitmapBounds[bitmap]); + } + + pos.y = end; + } + + bitmap = (mVBarEnabled ? ((curHitRegion == DownArrow && stateDepressed ) ? + BmpStates * BmpDown + BmpHilite : BmpStates * BmpDown) : BmpStates * BmpDown + BmpDisabled); + + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[bitmap]); +} + +void GuiScrollCtrl::drawHScrollBar(const Point2I &offset) +{ + S32 bitmap; + + //draw the left arrow + bitmap = (mHBarEnabled ? ((curHitRegion == LeftArrow && stateDepressed) ? + BmpStates * BmpLeft + BmpHilite : BmpStates * BmpLeft) : BmpStates * BmpLeft + BmpDisabled); + + Point2I pos = offset; + pos += mLeftArrowRect.point; + + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[bitmap]); + + pos.x += mLeftArrowRect.extent.x; + + //draw the left page + S32 end; + if (mHBarEnabled) + end = mHThumbPos + offset.x; + else + end = mRightArrowRect.point.x + offset.x; + + bitmap = (mHBarEnabled ? ((curHitRegion == LeftPage && stateDepressed) ? + BmpStates * BmpHPage + BmpHilite : BmpStates * BmpHPage) : BmpStates * BmpHPage + BmpDisabled); + + if (end > pos.x) + { + dglClearBitmapModulation(); + dglDrawBitmapStretchSR(mTextureHandle, RectI(pos, Point2I(end - pos.x, mBitmapBounds[bitmap].extent.y)), mBitmapBounds[bitmap]); + } + pos.x = end; + + //draw the thumb and the rightPage + if (mHBarEnabled) + { + bool thumbSelected = (curHitRegion == HorizThumb && stateDepressed); + S32 ttop = (thumbSelected ? BmpStates * BmpHThumbLeftCap + BmpHilite : BmpStates * BmpHThumbLeftCap ); + S32 tmid = (thumbSelected ? BmpStates * BmpHThumb + BmpHilite : BmpStates * BmpHThumb); + S32 tbot = (thumbSelected ? BmpStates * BmpHThumbRightCap + BmpHilite : BmpStates * BmpHThumbRightCap); + + // draw the thumb + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[ttop]); + pos.x += mBitmapBounds[ttop].extent.x; + end = mHThumbPos + mHThumbSize - mBitmapBounds[tbot].extent.x + offset.x; + if (end > pos.x) + { + dglClearBitmapModulation(); + dglDrawBitmapStretchSR(mTextureHandle, RectI(pos, Point2I(end - pos.x, mBitmapBounds[tmid].extent.y)), mBitmapBounds[tmid]); + } + + pos.x = end; + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[tbot]); + pos.x += mBitmapBounds[tbot].extent.x; + end = mHTrackRect.point.x + mHTrackRect.extent.x - 1 + offset.x; + + bitmap = ((curHitRegion == RightPage && stateDepressed) ? BmpStates * BmpHPage + BmpHilite : BmpStates * BmpHPage); + + if (end > pos.x) + { + dglClearBitmapModulation(); + dglDrawBitmapStretchSR(mTextureHandle, RectI(pos, Point2I(end - pos.x, mBitmapBounds[bitmap].extent.y)), mBitmapBounds[bitmap]); + } + + pos.x = end; + } + bitmap = (mHBarEnabled ? ((curHitRegion == RightArrow && stateDepressed) ? + BmpStates * BmpRight + BmpHilite : BmpStates * BmpRight) : BmpStates * BmpRight + BmpDisabled); + + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[bitmap]); +} + +void GuiScrollCtrl::drawScrollCorner(const Point2I &offset) +{ + Point2I pos = offset; + pos.x += mRightArrowRect.point.x + mRightArrowRect.extent.x; + pos.y += mRightArrowRect.point.y; + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[BmpStates * BmpResize]); +} + +void GuiScrollCtrl::autoScroll(Region reg) +{ + scrollByRegion(reg); +} diff --git a/gui/guiScrollCtrl.h b/gui/guiScrollCtrl.h new file mode 100644 index 0000000..b3ecadc --- /dev/null +++ b/gui/guiScrollCtrl.h @@ -0,0 +1,238 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUISCROLLCTRL_H_ +#define _GUISCROLLCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +// the function of the scroll content control class +// is to notify the parent class that children have resized. +// basically it just calls it's parent (enclosing) control's +// childResized method which turns around and computes new sizes +// for the scroll bars + +class GuiScrollContentCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +protected: + void childResized(GuiControl *child); + void removeObject(SimObject *object); + +public: + DECLARE_CONOBJECT(GuiScrollContentCtrl); +}; + +class GuiScrollCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +protected: + // the scroll control uses a bitmap array to draw all its + // stuff... 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] + TextureHandle mTextureHandle; + + GuiScrollContentCtrl *mContentCtrl; // always have a pointer to the content control + + S32 mBorderThickness; // this gets set per class in the constructor + Point2I mChildMargin; // the thickeness 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 + + bool mHBarEnabled; + bool mVBarEnabled; + bool mHasHScrollBar; + bool mHasVScrollBar; + + F32 mHBarThumbPos; + F32 mHBarThumbWidth; + F32 mVBarThumbPos; + F32 mVBarThumbWidth; + + S32 mHThumbSize; + S32 mHThumbPos; + + S32 mVThumbSize; + S32 mVThumbPos; + + S32 mBaseThumbSize; + + RectI mUpArrowRect; + RectI mDownArrowRect; + RectI mLeftArrowRect; + RectI mRightArrowRect; + RectI mHTrackRect; + RectI mVTrackRect; + + S32 mDefaultLineHeight; // line height for scroll controls that DO NOT have array controls in them + // ignored if left at zero + + F32 mLine_V; // percentage to scroll line Vertically + F32 mLine_H; + F32 mPage_V; // percentage to scroll page Vertically + F32 mPage_H; + + + //-------------------------------------- + // for determing hit area +public: //called by the ComboPopUp class + enum Region + { + UpArrow, + DownArrow, + LeftArrow, + RightArrow, + UpPage, + DownPage, + LeftPage, + RightPage, + VertThumb, + HorizThumb, + None + }; + Region findHitRegion(const Point2I &); + +protected: + bool stateDepressed; + Region curHitRegion; + + virtual bool calcChildExtents(Point2I *pos, Point2I *ext); + virtual void calcScrollRects(void); + void calcThumbs(Point2I cpos, Point2I cext); + void scrollByRegion(Region reg); + + //-------------------------------------- + + //-------------------------------------- + // for mouse dragging the thumb + F32 mThumbAnchorPos; + S32 mThumbDelta; + //-------------------------------------- + +public: + GuiScrollCtrl(); + ~GuiScrollCtrl(); + DECLARE_CONOBJECT(GuiScrollCtrl); + static void consoleInit(); + static void initPersistFields(); + void autoScroll(Region reg); + bool disabled; + + F32 getCurrVPos() const; + F32 getCurrHPos() const; + + void scrollTo(F32 x, F32 y); + void computeSizes(); + + enum { + ScrollBarAlwaysOn = 0, + ScrollBarAlwaysOff = 1, + ScrollBarDynamic = 2 + }; + // you can change the bitmap array dynamically. + void loadBitmapArray(); + + S32 mForceHScrollBar; + S32 mForceVScrollBar; + + bool mUseConstantHeightThumb; + bool mWillFirstRespond; // for automatically handling arrow keys + + GuiScrollContentCtrl *getScrollContentCtrl() { return mContentCtrl; } + + void addObject(SimObject *object); + void resize(const Point2I &newPos, const Point2I &newExt); + 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 wantsTabListMembership(); + bool becomeFirstResponder(); + bool loseFirstResponder(); + + Region getCurHitRegion(void) { return curHitRegion; } + + bool onKeyDown(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseRepeat(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + bool onMouseWheelUp(const GuiEvent &event); + bool onMouseWheelDown(const GuiEvent &event); + + bool onAdd(); + bool onWake(); + void onSleep(); + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + 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); +}; + + +//-------------------------------------------------------------------------- +inline F32 GuiScrollCtrl::getCurrVPos() const +{ + if (mHasVScrollBar) + return mVBarThumbPos; + else + return 1.0; +} + +inline F32 GuiScrollCtrl::getCurrHPos() const +{ + if (mHasVScrollBar) + return mVBarThumbPos; + else + return 1.0; +} + +#endif //_GUI_SCROLL_CTRL_H diff --git a/gui/guiSliderCtrl.cc b/gui/guiSliderCtrl.cc new file mode 100644 index 0000000..e42e9c2 --- /dev/null +++ b/gui/guiSliderCtrl.cc @@ -0,0 +1,256 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" +#include "dgl/gTexManager.h" +#include "GUI/guiSliderCtrl.h" +#include "Platform/event.h" + +//---------------------------------------------------------------------------- +GuiSliderCtrl::GuiSliderCtrl(void) +{ + mActive = true; + mRange.set( 0.0f, 1.0f ); + mTicks = 1000; + mValue = 0.5f; + mThumbSize.set(8,20); + mShiftPoint = 5; + mShiftExtent = 10; + mDisplayValue = false; +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("range", TypePoint2F, Offset(mRange, GuiSliderCtrl)); + addField("ticks", TypeS32, Offset(mTicks, GuiSliderCtrl)); + addField("value", TypeF32, Offset(mValue, GuiSliderCtrl)); +} + +//---------------------------------------------------------------------------- +static F32 cGuiSlider_GetValue(SimObject *obj, S32, const char **) +{ + GuiSliderCtrl *ctrl = static_cast(obj); + return ctrl->getValue(); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::setScriptValue(const char *val) +{ + mValue = dAtof(val); + updateThumb(mValue); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::consoleInit() +{ + Con::addCommand("GuiSliderCtrl", "getValue", cGuiSlider_GetValue, "guiSliderCtrl.getValue()", 2, 2); +} + +//---------------------------------------------------------------------------- +bool GuiSliderCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + if(mThumbSize.y + mProfile->mFont->getHeight()-4 <= mBounds.extent.y) + mDisplayValue = true; + else + mDisplayValue = false; + + updateThumb( mValue, true ); + + return true; +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::onMouseDown(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + mouseLock(); + setFirstResponder(); + + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + F32 value; + if (mBounds.extent.x >= mBounds.extent.y) + value = F32(curMousePos.x-mShiftPoint) / F32(mBounds.extent.x-mShiftExtent)*(mRange.y-mRange.x) + mRange.x; + else + value = F32(curMousePos.y) / F32(mBounds.extent.y)*(mRange.y-mRange.x) + mRange.x; + updateThumb(value); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::onMouseDragged(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + F32 value; + if (mBounds.extent.x >= mBounds.extent.y) + value = F32(curMousePos.x-mShiftPoint) / F32(mBounds.extent.x-mShiftExtent)*(mRange.y-mRange.x) + mRange.x; + else + value = F32(curMousePos.y) / F32(mBounds.extent.y)*(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 + Con::printf("snapping"); + + 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); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::onMouseUp(const GuiEvent &) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + mouseUnlock(); + if (mConsoleCommand[0]) + { + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", getId()); + Con::setVariable("$ThisControl", buf); + Con::evaluate(mConsoleCommand, false); + } +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::updateThumb( F32 value, bool onWake ) +{ + mValue = value; + // clamp the thumb to legal values + if (mValue < mRange.x) mValue = mRange.x; + if (mValue > mRange.y) mValue = mRange.y; + + Point2I ext = mBounds.extent; + ext.x -= ( mShiftExtent + mThumbSize.x ); + // update the bounding thumb rect + if (mBounds.extent.x >= mBounds.extent.y) + { // 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; + } + setConsoleVariable("value"); + setFloatVariable(mValue); + setUpdate(); + + // Use the alt console command if you want to continually update: + if ( !onWake && mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand, false ); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + Point2I pos(offset.x+mShiftPoint, offset.y); + Point2I ext(mBounds.extent.x - mShiftExtent, mBounds.extent.y); + + if (mBounds.extent.x >= mBounds.extent.y) + { + Point2I mid(ext.x, ext.y/2); + if(mDisplayValue) + mid.set(ext.x, mThumbSize.y/2); + + glColor4f(0, 0, 0, 1); + glBegin(GL_LINES); + // horz rule + glVertex2i(pos.x, pos.y+mid.y); + glVertex2i(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)); + glVertex2i(pos.x+x, pos.y+mid.y-mShiftPoint); + glVertex2i(pos.x+x, pos.y+mid.y+mShiftPoint); + } + glEnd(); + } + else + { + Point2I mid(ext.x/2, ext.y); + + glColor4f(0, 0, 0, 1); + glBegin(GL_LINES); + // horz rule + glVertex2i(pos.x+mid.x, pos.y); + glVertex2i(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)); + glVertex2i(pos.x+mid.x-mShiftPoint, pos.y+y); + glVertex2i(pos.x+mid.x+mShiftPoint, pos.y+y); + } + glEnd(); + mDisplayValue = false; + } + // draw the thumb + RectI thumb = mThumb; + thumb.point += pos; + dglDrawRectFill(thumb, ColorI(255,255,255)); + dglDrawRect(thumb, ColorI(0,0,0)); + + if(mDisplayValue) + { + char buf[20]; + dSprintf(buf,sizeof(buf),"%0.3f",mValue); + + Point2I textStart = thumb.point; + + S32 txt_w = mProfile->mFont->getStrWidth(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+mBounds.extent.x) + textStart.x -=((textStart.x + txt_w) - (offset.x+mBounds.extent.x)); + + dglSetBitmapModulation(mProfile->mFontColor); + dglDrawText(mProfile->mFont, textStart, buf, mProfile->mFontColors); + } + renderChildControls(offset, updateRect, firstResponder); +} + diff --git a/gui/guiSliderCtrl.h b/gui/guiSliderCtrl.h new file mode 100644 index 0000000..c28fd01 --- /dev/null +++ b/gui/guiSliderCtrl.h @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUISLIDERCTRL_H_ +#define _GUISLIDERCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/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 onWake = false ); + S32 mShiftPoint; + S32 mShiftExtent; + bool mDisplayValue; + +public: + //creation methods + DECLARE_CONOBJECT(GuiSliderCtrl); + GuiSliderCtrl(); + static void initPersistFields(); + static void consoleInit(); + + //Parental methods + bool onWake(); + + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseUp(const GuiEvent &); + + F32 getValue() { return mValue; } + void setScriptValue(const char *val); + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); +}; + +#endif diff --git a/gui/guiTSControl.cc b/gui/guiTSControl.cc new file mode 100644 index 0000000..de6a809 --- /dev/null +++ b/gui/guiTSControl.cc @@ -0,0 +1,130 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiTSControl.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneLighting.h" + +IMPLEMENT_CONOBJECT(GuiTSCtrl); + +U32 GuiTSCtrl::smFrameCount = 0; + +GuiTSCtrl::GuiTSCtrl() +{ + for(U32 i = 0; i < 16; i++) + { + mSaveModelview[i] = 0; + mSaveProjection[i] = 0; + } + mSaveModelview[0] = 1; + mSaveModelview[5] = 1; + mSaveModelview[10] = 1; + mSaveModelview[15] = 1; + mSaveProjection[0] = 1; + mSaveProjection[5] = 1; + mSaveProjection[10] = 1; + mSaveProjection[15] = 1; + + mSaveViewport[0] = 0; + mSaveViewport[1] = 0; + mSaveViewport[2] = 2; + mSaveViewport[3] = 2; +} + +void GuiTSCtrl::consoleInit() +{ + Con::addVariable("$TSControl::frameCount", TypeS32, &smFrameCount); +} + +void GuiTSCtrl::onPreRender() +{ + setUpdate(); +} + +bool GuiTSCtrl::processCameraQuery(CameraQuery *) +{ + return false; +} + +void GuiTSCtrl::renderWorld(const RectI& /*updateRect*/) +{ +} + +bool GuiTSCtrl::project(const Point3F &pt, Point3F *dest) +{ + GLdouble winx, winy, winz; + GLint result = gluProject(pt.x, pt.y, pt.z, + mSaveModelview, mSaveProjection, mSaveViewport, + &winx, &winy, &winz); + if(result == GL_FALSE || winz < 0 || winz > 1) + return false; + dest->set(winx, winy, winz); + return true; +} + +bool GuiTSCtrl::unproject(const Point3F &pt, Point3F *dest) +{ + GLdouble objx, objy, objz; + GLint result = gluUnProject(pt.x, pt.y, pt.z, + mSaveModelview, mSaveProjection, mSaveViewport, + &objx, &objy, &objz); + if(result == GL_FALSE) + return false; + dest->set(objx, objy, objz); + return true; +} + +void GuiTSCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl* /*firstResponder*/) +{ + if(SceneLighting::isLighting()) + return; + + if(!processCameraQuery(&mLastCameraQuery)) + return; + + // set up the camera and viewport stuff: + F32 wwidth = mLastCameraQuery.nearPlane * mTan(mLastCameraQuery.fov / 2); + F32 wheight = F32(mBounds.extent.y) / F32(mBounds.extent.x) * wwidth; + + F32 hscale = wwidth * 2 / F32(mBounds.extent.x); + F32 vscale = wheight * 2 / F32(mBounds.extent.y); + + 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); + + dglSetViewport(updateRect); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + dglSetFrustum(left, right, bottom, top, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + //Point3F refPt = query.position + query.viewVector; + + mLastCameraQuery.cameraMatrix.inverse(); + dglMultMatrix(&mLastCameraQuery.cameraMatrix); + + glGetDoublev(GL_PROJECTION_MATRIX, mSaveProjection); + glGetDoublev(GL_MODELVIEW_MATRIX, mSaveModelview); + //glGetIntegerv(GL_VIEWPORT, mSaveViewport); + + //U32 screenHeight = Platform::getWindowSize().y; + mSaveViewport[0] = updateRect.point.x; + mSaveViewport[1] = updateRect.point.y + updateRect.extent.y; + mSaveViewport[2] = updateRect.extent.x; + mSaveViewport[3] = -updateRect.extent.y; + + + //gluLookAt(query.position.x, query.position.y, query.position.z, + // refPt.x, refPt.y, refPt.z, + // query.upVector.x, query.upVector.y, query.upVector.z); + + renderWorld(updateRect); + smFrameCount++; +} diff --git a/gui/guiTSControl.h b/gui/guiTSControl.h new file mode 100644 index 0000000..bd1f655 --- /dev/null +++ b/gui/guiTSControl.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITSCONTROL_H_ +#define _GUITSCONTROL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _DGL_H_ +#include "dgl/dgl.h" +#endif + +struct CameraQuery +{ + SimObject* object; + F32 nearPlane; + F32 farPlane; + F32 fov; + MatrixF cameraMatrix; + + //Point3F position; + //Point3F viewVector; + //Point3F upVector; +}; + +class GuiTSCtrl : public GuiControl +{ + typedef GuiControl Parent; + + GLdouble mSaveModelview[16]; + GLdouble mSaveProjection[16]; + GLint mSaveViewport[4]; + + static U32 smFrameCount; + +public: + CameraQuery mLastCameraQuery; + GuiTSCtrl(); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + virtual bool processCameraQuery(CameraQuery *query); + virtual void renderWorld(const RectI &updateRect); + + static void consoleInit(); + + bool project(const Point3F &pt, Point3F *dest); // returns screen space X,Y and Z for world space point + bool unproject(const Point3F &pt, Point3F *dest); // returns world space point for X, Y and Z + + DECLARE_CONOBJECT(GuiTSCtrl); +}; + +#endif diff --git a/gui/guiTextCtrl.cc b/gui/guiTextCtrl.cc new file mode 100644 index 0000000..f96ebff --- /dev/null +++ b/gui/guiTextCtrl.cc @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "Core/color.h" +#include "GUI/guiTextCtrl.h" +#include "dgl/dgl.h" + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +GuiTextCtrl::GuiTextCtrl() +{ + //default fonts + mInitialText = StringTable->insert(""); + mText[0] = '\0'; + mMaxStrLen = GuiTextCtrl::MAX_STRING_LENGTH; +} + +ConsoleMethod( GuiTextCtrl, setText, void, 3, 3, "obj.setText( newText )" ) +{ + argc; + GuiTextCtrl* ctrl = static_cast( object ); + ctrl->setText( argv[2] ); +} + +void GuiTextCtrl::consoleInit() +{ +} + +void GuiTextCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField( "text", TypeCaseString, Offset( mInitialText, GuiTextCtrl ) ); + addField( "maxLength", TypeS32, Offset( mMaxStrLen, GuiTextCtrl ) ); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +bool GuiTextCtrl::onAdd() +{ + if(!Parent::onAdd()) + return false; + dStrncpy(mText, mInitialText, MAX_STRING_LENGTH); + mText[MAX_STRING_LENGTH] = '\0'; + return true; +} + +void GuiTextCtrl::inspectPostApply() +{ + Parent::inspectPostApply(); + setText(mInitialText); +} + +bool GuiTextCtrl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + mFont = mProfile->mFont; + AssertFatal( bool( mFont ), "GuiTextCtrl::onWake: invalid font in profile" ); + + 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 + if ( mProfile->mAutoSizeWidth ) + { + if ( mProfile->mAutoSizeHeight ) + resize( mBounds.point, Point2I( mFont->getStrWidth( mText ), mFont->getHeight() + 4 ) ); + else + resize( mBounds.point, Point2I( mFont->getStrWidth( mText ), mBounds.extent.y ) ); + } + else if ( mProfile->mAutoSizeHeight ) + resize( mBounds.point, Point2I( mBounds.extent.x, mFont->getHeight() + 4 ) ); + + return true; +} + +void GuiTextCtrl::onSleep() +{ + Parent::onSleep(); + mFont = NULL; +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiTextCtrl::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) + dStrncpy(mText, txt, MAX_STRING_LENGTH); + mText[MAX_STRING_LENGTH] = '\0'; + + //Make sure we have a font + mProfile->incRefCount(); + mFont = mProfile->mFont; + + //resize + if (mProfile->mAutoSizeWidth) + { + if (mProfile->mAutoSizeHeight) + resize(mBounds.point, Point2I(mFont->getStrWidth(mText), mFont->getHeight() + 4)); + else + resize(mBounds.point, Point2I(mFont->getStrWidth(mText), mBounds.extent.y)); + } + else if (mProfile->mAutoSizeHeight) + { + resize(mBounds.point, Point2I(mBounds.extent.x, mFont->getHeight() + 4)); + } + + setVariable(mText); + setUpdate(); + + //decrement the profile referrence + mProfile->decRefCount(); +} + +//------------------------------------------------------------------------------ +void GuiTextCtrl::onPreRender() +{ + const char * var = getVariable(); + if(var && var[0] && dStricmp(mText, var)) + setText(var); +} + +//------------------------------------------------------------------------------ +void GuiTextCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + S32 txt_w = mFont->getStrWidth(mText); + + Point2I localStart; + switch (mProfile->mAlignment) + { + case GuiControlProfile::RightJustify: + localStart.set(mBounds.extent.x - txt_w, 0); + break; + case GuiControlProfile::CenterJustify: + localStart.set((mBounds.extent.x - txt_w) / 2, 0); + break; + default: + // GuiControlProfile::LeftJustify + localStart.set(0,0); + break; + } + + Point2I globalStart = localToGlobalCoord(localStart); + + //draw the text + dglSetBitmapModulation(mProfile->mFontColor); + dglDrawText(mFont, globalStart, mText, mProfile->mFontColors); + + //render the child controls + renderChildControls(offset, updateRect, firstResponder); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +const char *GuiTextCtrl::getScriptValue() +{ + return getText(); +} + +void GuiTextCtrl::setScriptValue(const char *val) +{ + setText(val); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// EOF // diff --git a/gui/guiTextCtrl.h b/gui/guiTextCtrl.h new file mode 100644 index 0000000..21ac5fc --- /dev/null +++ b/gui/guiTextCtrl.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITEXTCTRL_H_ +#define _GUITEXTCTRL_H_ + +#ifndef _GFONT_H_ +#include "dgl/gFont.h" +#endif +#ifndef _GUITYPES_H_ +#include "GUI/guiTypes.h" +#endif +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +class GuiTextCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +public: + enum Constants { MAX_STRING_LENGTH = 255 }; + + +protected: + StringTableEntry mInitialText; + char 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); + GuiTextCtrl(); + static void consoleInit(); + static void initPersistFields(); + + //Parental methods + bool onAdd(); + virtual bool onWake(); + virtual void onSleep(); + + //text methods + virtual void setText(const char *txt = NULL); + const char *getText() { return mText; } + + void inspectPostApply(); + //rendering methods + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + + //Console methods + const char *getScriptValue(); + void setScriptValue(const char *value); +}; + +#endif //_GUI_TEXT_CONTROL_H_ diff --git a/gui/guiTextEditCtrl.cc b/gui/guiTextEditCtrl.cc new file mode 100644 index 0000000..c7c8a30 --- /dev/null +++ b/gui/guiTextEditCtrl.cc @@ -0,0 +1,1110 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/dgl.h" +#include "gui/guiCanvas.h" +#include "gui/guiMLTextCtrl.h" +#include "gui/guiTextEditCtrl.h" + +U32 GuiTextEditCtrl::mNumAwake = 0; + +GuiTextEditCtrl::GuiTextEditCtrl() +{ + mInsertOn = true; + mBlockStart = 0; + mBlockEnd = 0; + mCursorPos = 0; + + mDragHit = false; + mTabComplete = false; + mScrollDir = 0; + + mUndoText[0] = '\0'; + mUndoBlockStart = 0; + mUndoBlockEnd = 0; + mUndoCursorPos = 0; + mPasswordText = false; + + mActive = true; + + mTextOffset.x = 65535; + + mHistoryDirty = false; + mHistorySize = 0; + mHistoryLast = -1; + mHistoryIndex = 0; + mHistoryBuf = NULL; + + mValidateCommand = StringTable->insert(""); + mEscapeCommand = StringTable->insert( "" ); + 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() +{ + Parent::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", TypeAudioProfilePtr, Offset(mDeniedSound, GuiTextEditCtrl)); +} + +void GuiTextEditCtrl::consoleInit() +{ +} + +bool GuiTextEditCtrl::onAdd() +{ + if ( ! Parent::onAdd() ) + return false; + + //create the history buffer + if ( mHistorySize > 0 ) + { + mHistoryBuf = new char*[mHistorySize]; + for ( S32 i = 0; i < mHistorySize; i++ ) + { + mHistoryBuf[i] = new char[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + mHistoryBuf[i][0] = '\0'; + } + } + return true; +} + +bool GuiTextEditCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + // If this is the first awake text edit control, enable keyboard translation + if (mNumAwake == 0) + Platform::enableKeyboardTranslation(); + ++mNumAwake; + + return true; +} + +void GuiTextEditCtrl::onSleep() +{ + Parent::onSleep(); + + // If this is the last awake text edit control, disable keyboard translation + --mNumAwake; + if (mNumAwake == 0) + Platform::disableKeyboardTranslation(); +} + +void GuiTextEditCtrl::updateHistory( const char *txt, bool moveIndex ) +{ + if(!txt) + txt = ""; + 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: + { + char *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++; + + dStrncpy( mHistoryBuf[mHistoryLast], txt, GuiTextCtrl::MAX_STRING_LENGTH ); + mHistoryBuf[mHistoryLast][GuiTextCtrl::MAX_STRING_LENGTH] = '\0'; + } + if(moveIndex) + mHistoryIndex = mHistoryLast + 1; +} + +void GuiTextEditCtrl::getText( char *dest ) +{ + if ( dest ) + dStrcpy( dest, mText ); +} + +void GuiTextEditCtrl::setText( const char *txt ) +{ + Parent::setText( txt ); + mCursorPos = dStrlen( mText ); +} + +void GuiTextEditCtrl::reallySetCursorPos( const S32 newPos ) +{ + S32 charCount = dStrlen( mText ); + S32 realPos = newPos > charCount ? charCount : newPos < 0 ? 0 : newPos; + if ( realPos != mCursorPos ) + { + mCursorPos = realPos; + setUpdate(); + } +} + +S32 GuiTextEditCtrl::setCursorPos( const Point2I &offset ) +{ + Point2I ctrlOffset = localToGlobalCoord( Point2I( 0, 0 ) ); + S32 charCount = dStrlen( mText ); + S32 charLength = 0; + S32 curX; + + curX = offset.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 + mBounds.extent.x ) + return -2; + + curX = offset.x - mTextOffset.x; + S32 pos = 0; + while ( pos < charCount ) + { + charLength += mFont->getCharXIncrement( mText[pos] ); + if ( charLength > curX ) + break; + pos++; + } + + return pos; +} + +void GuiTextEditCtrl::onMouseDown( const GuiEvent &event ) +{ + mDragHit = false; + + //undo any block function + mBlockStart = 0; + mBlockEnd = 0; + + //find out where the cursor should be + S32 pos = setCursorPos( 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 = dStrlen(mText); + 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 + Parent::onMouseDown( event ); +} + +void GuiTextEditCtrl::onMouseDragged( const GuiEvent &event ) +{ + S32 pos = setCursorPos( 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) +{ + event; + mDragHit = false; + mScrollDir = 0; + mouseUnlock(); +} + +void GuiTextEditCtrl::saveUndoState() +{ + //save the current state + dStrcpy(mUndoText, mText); + mUndoBlockStart = mBlockStart; + mUndoBlockEnd = mBlockEnd; + mUndoCursorPos = mCursorPos; +} + +bool GuiTextEditCtrl::onKeyDown(const GuiEvent &event) +{ + S32 stringLen = dStrlen(mText); + setUpdate(); + + if (event.modifier & SI_SHIFT) + { + switch (event.keyCode) + { + case KEY_TAB: + if ( mTabComplete ) + { + Con::executef( this, 2, "onTabComplete", "1" ); + return( true ); + } + + 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; + } + } + else if (event.modifier & SI_CTRL) + { + switch(event.keyCode) + { + // Added UNIX emacs key bindings - just a little hack here... + + // 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 + + case KEY_C: + case KEY_X: + if(mPasswordText) + return true; + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + if (mBlockEnd > 0) + { + //save the current state + saveUndoState(); + + //copy the text to the clipboard + char temp = mText[mBlockEnd]; + mText[mBlockEnd] = '\0'; + Platform::setClipboard(&mText[mBlockStart]); + mText[mBlockEnd] = temp; + + //if we pressed ctrl-x, we need to cut the selected text from the control... + if (event.keyCode == KEY_X) + { + dStrcpy(buf, &mText[mBlockEnd]); + mCursorPos = mBlockStart; + dStrcpy(&mText[mBlockStart], buf); + } + + mBlockStart = 0; + mBlockEnd = 0; + } + + return true; + + case KEY_V: + { + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + + //first, make sure there's something in the clipboard to copy... + const char *temp = Platform::getClipboard(); + const char *clipBuf = GuiMLTextCtrl::stripControlChars(temp); + if (dStrlen(clipBuf) <= 0) + return true; + + //save the current state + saveUndoState(); + + //delete anything hilited + if (mBlockEnd > 0) + { + dStrcpy(buf, &mText[mBlockEnd]); + mCursorPos = mBlockStart; + dStrcpy(&mText[mBlockStart], buf); + mBlockStart = 0; + mBlockEnd = 0; + } + + S32 pasteLen = dStrlen(clipBuf); + if ((stringLen + pasteLen) > mMaxStrLen) + pasteLen = mMaxStrLen - stringLen; + + if (mCursorPos == stringLen) + { + dStrncpy(&mText[mCursorPos], clipBuf, pasteLen); + mText[mCursorPos + pasteLen] = '\0'; + } + else + { + dStrcpy(buf, &mText[mCursorPos]); + dStrncpy(&mText[mCursorPos], clipBuf, pasteLen); + dStrcpy(&mText[mCursorPos + pasteLen], buf); + } + mCursorPos += pasteLen; + + return true; + } + + case KEY_Z: + if (! mDragHit) + { + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + S32 tempBlockStart; + S32 tempBlockEnd; + S32 tempCursorPos; + + //save the current + dStrcpy(buf, mText); + tempBlockStart = mBlockStart; + tempBlockEnd = mBlockEnd; + tempCursorPos = mCursorPos; + + //restore the prev + dStrcpy(mText, mUndoText); + mBlockStart = mUndoBlockStart; + mBlockEnd = mUndoBlockEnd; + mCursorPos = mUndoCursorPos; + + //update the undo + dStrcpy(mUndoText, buf); + mUndoBlockStart = tempBlockStart; + mUndoBlockEnd = tempBlockEnd; + mUndoCursorPos = tempCursorPos; + + return true; + } + + case KEY_DELETE: + case KEY_BACKSPACE: + //save the current state + saveUndoState(); + + //delete everything in the field + mText[0] = '\0'; + mCursorPos = 0; + mBlockStart = 0; + mBlockEnd = 0; + + // 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, mText ); + + return true; + } //switch + } + else + { + switch(event.keyCode) + { + case KEY_ESCAPE: + if ( mEscapeCommand[0] ) + { + Con::evaluate( mEscapeCommand ); + return( true ); + } + return( Parent::onKeyDown( event ) ); + + case KEY_RETURN: + //first validate + onLoseFirstResponder(); + mHistoryDirty = false; + + //next exec the alt console command + if ( mAltConsoleCommand[0] ) + { + char buf[16]; + dSprintf( buf, sizeof( buf ), "%d", getId() ); + Con::setVariable( "$ThisControl", buf ); + Con::evaluate( mAltConsoleCommand, false ); + } + + if (mProfile->mReturnTab) + { + GuiCanvas *root = getRoot(); + if (root) + { + root->tabNext(); + return true; + } + } + return true; + + case KEY_UP: + if(mHistoryDirty) + { + updateHistory(mText, 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(mText, false); + mHistoryDirty = false; + } + mHistoryIndex++; + if(mHistoryIndex > mHistoryLast) + { + mHistoryIndex = mHistoryLast + 1; + setText(""); + } + else + setText(mHistoryBuf[mHistoryIndex]); + return true; + case KEY_LEFT: + mBlockStart = 0; + mBlockEnd = 0; + if (mCursorPos > 0) + { + mCursorPos--; + } + return true; + + case KEY_RIGHT: + mBlockStart = 0; + mBlockEnd = 0; + if (mCursorPos < stringLen) + { + mCursorPos++; + } + return true; + + case KEY_BACKSPACE: + //save the current state + saveUndoState(); + + if (mBlockEnd > 0) + { + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + dStrcpy(buf, &mText[mBlockEnd]); + mCursorPos = mBlockStart; + dStrcpy(&mText[mBlockStart], buf); + mBlockStart = 0; + mBlockEnd = 0; + mHistoryDirty = true; + + // 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, mText); + } + else if (mCursorPos > 0) + { + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + dStrcpy(buf, &mText[mCursorPos]); + mCursorPos--; + dStrcpy(&mText[mCursorPos], buf); + mHistoryDirty = true; + + // 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, mText); + } + return true; + + case KEY_DELETE: + //save the current state + saveUndoState(); + + if (mBlockEnd > 0) + { + mHistoryDirty = true; + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + dStrcpy(buf, &mText[mBlockEnd]); + mCursorPos = mBlockStart; + dStrcpy(&mText[mBlockStart], buf); + mBlockStart = 0; + mBlockEnd = 0; + + // 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, mText); + } + else if (mCursorPos < stringLen) + { + mHistoryDirty = true; + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + dStrcpy(buf, &mText[mCursorPos + 1]); + dStrcpy(&mText[mCursorPos], buf); + + // 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, mText); + } + 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; + + } + } + + switch ( event.keyCode ) + { + case KEY_TAB: + if ( mTabComplete ) + { + Con::executef( this, 2, "onTabComplete", "0" ); + return( true ); + } + case KEY_UP: + case KEY_DOWN: + case KEY_ESCAPE: + return Parent::onKeyDown( event ); + } + + if ( mFont->isValidChar( event.ascii ) ) + { + //see if it's a number field + if ( mProfile->mNumbersOnly ) + { + if (event.ascii == '-') + { + //a minus sign only exists at the beginning, and only a single minus sign + if ( mCursorPos != 0 ) + { + playDeniedSound(); + return true; + } + + if ( mInsertOn && ( mText[0] == '-' ) ) + { + playDeniedSound(); + return true; + } + } + else if ( event.ascii < '0' || event.ascii > '9' ) + { + playDeniedSound(); + return true; + } + } + + //save the current state + saveUndoState(); + + //delete anything hilited + if ( mBlockEnd > 0 ) + { + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + dStrcpy( buf, &mText[mBlockEnd] ); + mCursorPos = mBlockStart; + dStrcpy( &mText[mBlockStart], buf ); + mBlockStart = 0; + mBlockEnd = 0; + } + + if ( ( mInsertOn && ( stringLen < mMaxStrLen ) ) || + ( !mInsertOn && ( mCursorPos < mMaxStrLen ) ) ) + { + if ( mCursorPos == stringLen ) + { + mText[mCursorPos++] = event.ascii; + mText[mCursorPos] = '\0'; + } + else + { + if ( mInsertOn ) + { + char buf[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + dStrcpy( buf, &mText[mCursorPos] ); + mText[mCursorPos] = event.ascii; + mCursorPos++; + dStrcpy( &mText[mCursorPos], buf ); + } + else + { + mText[mCursorPos++] = event.ascii; + if ( mCursorPos > stringLen ) + mText[mCursorPos] = '\0'; + } + } + } + else + playDeniedSound(); + + //reset the history index + mHistoryDirty = true; + + //execute the console command if it exists + if ( mConsoleCommand[0] ) + { + char buf[16]; + dSprintf( buf, sizeof( buf ), "%d", getId() ); + Con::setVariable( "$ThisControl", buf ); + Con::evaluate( mConsoleCommand, false ); + } + + //now set the console var if exists + if ( mConsoleVariable[0] ) + Con::setVariable( mConsoleVariable, mText ); + + return true; + } + + //not handled - pass the event to it's parent + return Parent::onKeyDown( event ); +} + +void GuiTextEditCtrl::onLoseFirstResponder() +{ + //first, update the history + updateHistory( mText, true ); + + //execute the validate command + if ( mValidateCommand[0] ) + Con::evaluate( mValidateCommand, false ); + + // Redraw the control: + setUpdate(); +} + + + +void GuiTextEditCtrl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent) +{ + Parent::parentResized( oldParentExtent, newParentExtent ); + mTextOffset.x = 65535; +} + +void GuiTextEditCtrl::onRender(Point2I offset, const RectI & /*updateRect*/, GuiControl *firstResponder) +{ + RectI ctrlRect( offset, mBounds.extent ); + + //if opaque, fill the update rect with the fill color + if ( mProfile->mOpaque ) + dglDrawRectFill( ctrlRect, mProfile->mFillColor ); + + DrawText( ctrlRect, ( firstResponder == this ) ); + + //if there's a border, draw the border + if ( mProfile->mBorder ) + dglDrawRect( ctrlRect, mProfile->mBorderColor ); +} + +void GuiTextEditCtrl::onPreRender() +{ + bool focused = false; + GuiCanvas *canvas = getRoot(); + if ( canvas ) + focused = ( canvas->getFirstResponder() == this ); + + if ( focused ) + { + U32 timeElapsed = Platform::getVirtualMilliseconds() - mTimeLastCursorFlipped; + mNumFramesElapsed++; + if ( ( timeElapsed > 500 ) && ( mNumFramesElapsed > 3 ) ) + { + mCursorOn = 1 - 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 GuiTextEditCtrl::DrawText( const RectI &drawRect, bool isFocused ) +{ + Point2I drawPoint = drawRect.point; + + // Center vertically: + drawPoint.y += ( ( drawRect.extent.y - mFont->getHeight() ) / 2 ) + mProfile->mTextOffset.y; + char textBuffer[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + + dStrcpy(textBuffer, mText); + if(mPasswordText) + for(U32 i = 0; textBuffer[i]; i++) + textBuffer[i] = '*'; + + // Align horizontally: + S32 txt_w = mFont->getStrWidth( textBuffer ); + if ( drawRect.extent.x > txt_w ) + { + switch( mProfile->mAlignment ) + { + case GuiControlProfile::RightJustify: + //BH localStart.set( mBounds.extent.x - txt_w, 0 ); + drawPoint.x += ( drawRect.extent.x - txt_w ); + break; + case GuiControlProfile::CenterJustify: + //BH localStart.set( ( mBounds.extent.x - txt_w ) / 2, 0 ); + drawPoint.x += ( ( drawRect.extent.x - txt_w ) / 2 ); + break; + //BH default: + // GuiControlProfile::LeftJustify + //BH localStart.set( ( mProfile->mTextOffset.x != 0 ? mProfile->mTextOffset.x : 4 ), 0 ); + //BH break; + } + } + //BH else + //BH localStart.set( ( mProfile->mTextOffset.x != 0 ? mProfile->mTextOffset.x : 4 ), 0 ); + + //BH Point2I ctrlOffset = localToGlobalCoord( localStart ); + + ColorI fontColor = mActive ? mProfile->mFontColor : mProfile->mFontColorNA; + + // now draw the text + //BH S32 txt_h = mFont->getHeight(); + Point2I cursorStart, cursorEnd; + //BH mTextOffset.y = ctrlOffset.y + ( ( mBounds.extent.y - txt_h ) >> 1 ); + mTextOffset.y = drawPoint.y; + if ( ( mTextOffset.x == 65535 ) || ( txt_w < drawRect.extent.x ) ) + mTextOffset.x = drawRect.point.x + 3; + + // calculate the cursor + if ( isFocused ) + { + char temp = textBuffer[mCursorPos]; + textBuffer[mCursorPos] = '\0'; + S32 tempWidth = mFont->getStrWidth( textBuffer ); + + if( tempWidth >= drawRect.extent.x) + { + cursorStart.x = drawRect.extent.x + drawRect.point.x + 4; + } + else + { + cursorStart.x = mTextOffset.x + tempWidth; + } + + textBuffer[mCursorPos] = temp; + cursorEnd.x = cursorStart.x; + + S32 cursorHeight = mFont->getHeight(); + if ( cursorHeight < drawRect.extent.y ) + { + cursorStart.y = drawRect.point.y + ( ( drawRect.extent.y - cursorHeight ) / 2 ) - 2; + cursorEnd.y = cursorStart.y + cursorHeight; + } + else + { + cursorStart.y = drawRect.point.y; + cursorEnd.y = cursorStart.y + drawRect.extent.y - 1; + } + + if ( cursorStart.x < ( drawRect.point.x ) ) + { + //mTextOffset.x += (3 + mTextClipRect.point.x - cursorStart.x); + cursorStart.x = 3 + drawRect.point.x; + mTextOffset.x = cursorStart.x - tempWidth; + cursorEnd.x = cursorStart.x; + } +// else if ( cursorStart.x >= ( drawRect.point.x + drawRect.extent.x ) ) + else if ( cursorStart.x >= ( drawRect.point.x + drawRect.extent.x ) ) + { + cursorStart.x = drawRect.point.x + mBounds.extent.x - 4; +// mTextOffset.x = cursorStart.x - tempWidth; + mTextOffset.x = drawRect.point.x - (tempWidth-drawRect.extent.x) ; + cursorEnd.x = cursorStart.x; + } + } + else + { + if(mTextOffset.x < drawRect.point.x) + { + mTextOffset.x = drawRect.point.x + 3; + } + } + + //draw the text + if ( !isFocused ) + mBlockStart = mBlockEnd = 0; + + //also verify the block start/end + if ((mBlockStart > S32(dStrlen(textBuffer))) || (mBlockEnd > S32(dStrlen(textBuffer))) || (mBlockStart > mBlockEnd)) + mBlockStart = mBlockEnd = 0; + + char temp; + + Point2I tempOffset = mTextOffset; + tempOffset.y -= 2; + + //draw the portion before the highlite + if ( mBlockStart > 0 ) + { + temp = textBuffer[mBlockStart]; + textBuffer[mBlockStart] = '\0'; + + dglSetBitmapModulation( fontColor ); + dglDrawText( mFont, tempOffset, textBuffer, mProfile->mFontColors ); + tempOffset.x += mFont->getStrWidth( textBuffer ); + textBuffer[mBlockStart] = temp; + } + + //draw the hilited portion + if ( mBlockEnd > 0 ) + { + temp = textBuffer[mBlockEnd]; + textBuffer[mBlockEnd] = '\0'; + S32 highlightWidth = mFont->getStrWidth( &textBuffer[mBlockStart] ); + + dglDrawRectFill( Point2I( tempOffset.x, drawRect.point.y ), + Point2I( tempOffset.x + highlightWidth, drawRect.point.y + drawRect.extent.y - 1), + mProfile->mFillColorHL ); + + dglSetBitmapModulation( mProfile->mFontColorHL ); + dglDrawText( mFont, tempOffset, &textBuffer[mBlockStart], mProfile->mFontColors ); + tempOffset.x += highlightWidth; + textBuffer[mBlockEnd] = temp; + } + + //draw the portion after the highlite + dglSetBitmapModulation( fontColor ); + dglDrawText( mFont, tempOffset, &textBuffer[mBlockEnd], mProfile->mFontColors ); + + //draw the cursor + if ( isFocused && mCursorOn ) + dglDrawLine( cursorStart, cursorEnd, mProfile->mCursorColor ); +} + +bool GuiTextEditCtrl::hasText() +{ + return (mText[0]); +} + +void GuiTextEditCtrl::playDeniedSound() +{ + if ( mDeniedSound ) + { + AUDIOHANDLE handle = alxCreateSource( mDeniedSound ); + alxPlay( handle ); + } +} + +ConsoleMethod( GuiTextEditCtrl, getCursorPos, S32, 2, 2, "textEditCtrl.getCursorPos()" ) +{ + argc; argv; + return( static_cast( object )->getCursorPos() ); +} + +ConsoleMethod( GuiTextEditCtrl, setCursorPos, void, 3, 3, "textEditCtrl.setCursorPos( newPos )" ) +{ + argc; + static_cast( object )->reallySetCursorPos( dAtoi( argv[2] ) ); +} diff --git a/gui/guiTextEditCtrl.h b/gui/guiTextEditCtrl.h new file mode 100644 index 0000000..4a6c355 --- /dev/null +++ b/gui/guiTextEditCtrl.h @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITEXTEDITCTRL_H_ +#define _GUITEXTEDITCTRL_H_ + +#ifndef _GUITYPES_H_ +#include "gui/guiTypes.h" +#endif +#ifndef _GUITEXTCTRL_H_ +#include "gui/guiTextCtrl.h" +#endif + +class GuiTextEditCtrl : public GuiTextCtrl +{ +private: + typedef GuiTextCtrl Parent; + + static U32 mNumAwake; + +protected: + StringTableEntry mValidateCommand; + StringTableEntry mEscapeCommand; + AudioProfile* mDeniedSound; + + // for animating the cursor + S32 mNumFramesElapsed; + U32 mTimeLastCursorFlipped; + ColorI mCursorColor; + bool mCursorOn; + + bool mInsertOn; + S32 mMouseDragStart; + Point2I mTextOffset; + bool mDragHit; + bool mTabComplete; + S32 mScrollDir; + + //undo members + char mUndoText[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + S32 mUndoBlockStart; + S32 mUndoBlockEnd; + S32 mUndoCursorPos; + void saveUndoState(); + + S32 mBlockStart; + S32 mBlockEnd; + S32 mCursorPos; + S32 setCursorPos(const Point2I &offset); + + bool mHistoryDirty; + S32 mHistoryLast; + S32 mHistoryIndex; + S32 mHistorySize; + bool mPasswordText; + char **mHistoryBuf; + void updateHistory(const char *txt, bool moveIndex); + + void playDeniedSound(); + +public: + GuiTextEditCtrl(); + ~GuiTextEditCtrl(); + DECLARE_CONOBJECT(GuiTextEditCtrl); + static void initPersistFields(); + static void consoleInit(); + + bool onAdd(); + bool onWake(); + void onSleep(); + + void getText(char *dest); // dest must be of size + // StructDes::MAX_STRING_LEN + 1 + + void setText(S32 tag); + virtual void setText(const char *txt); + S32 getCursorPos() { return( mCursorPos ); } + void reallySetCursorPos( const S32 newPos ); + + bool onKeyDown(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + + void onLoseFirstResponder(); + + void parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent); + bool hasText(); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + virtual void DrawText( const RectI &drawRect, bool isFocused ); +}; + +#endif //_GUI_TEXTEDIT_CTRL_H diff --git a/gui/guiTextEditSliderCtrl.cc b/gui/guiTextEditSliderCtrl.cc new file mode 100644 index 0000000..e82a2f0 --- /dev/null +++ b/gui/guiTextEditSliderCtrl.cc @@ -0,0 +1,280 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/dgl.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiTextEditSliderCtrl.h" + +GuiTextEditSliderCtrl::GuiTextEditSliderCtrl() +{ + mRange.set(0, 1); + mIncAmount = 1.0f; + mValue = 0.0f; + mMulInc = 0; + mIncCounter = 0.0f; + mFormat = StringTable->insert("%3.2f"); + mTextAreaHit = None; +} + +GuiTextEditSliderCtrl::~GuiTextEditSliderCtrl() +{ +} + +void GuiTextEditSliderCtrl::initPersistFields() +{ + Parent::initPersistFields(); + + addField("format", TypeString, Offset(mFormat, GuiTextEditSliderCtrl)); + addField("range", TypePoint2I, Offset(mRange, GuiTextEditSliderCtrl)); + addField("increment", TypeF32, Offset(mIncAmount, GuiTextEditSliderCtrl)); +} + +void GuiTextEditSliderCtrl::consoleInit() +{ +} + +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]; + dSprintf(buf,sizeof(buf),mFormat, mValue); + Parent::setText(buf); +} + +void GuiTextEditSliderCtrl::onMouseDown(const GuiEvent &event) +{ + 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(mBounds.point); + + if(camPos.x > point.x + mBounds.extent.x - 14) + { + if(camPos.y > point.y + (mBounds.extent.y/2)) + { + mValue -=mIncAmount; + mTextAreaHit = ArrowDown; + mMulInc = -0.15f; + } + else + { + mValue +=mIncAmount; + mTextAreaHit = ArrowUp; + mMulInc = 0.15f; + } + + checkRange(); + setValue(); + mouseLock(); + return; + } + Parent::onMouseDown(event); +} + +void GuiTextEditSliderCtrl::onMouseDragged(const GuiEvent &event) +{ + if(mTextAreaHit == None || mTextAreaHit == Slider) + { + mTextAreaHit = Slider; + GuiControl *parent = getParent(); + if(!parent) + return; + Point2I camPos = event.mousePoint; + Point2I point = parent->localToGlobalCoord(mBounds.point); + F32 maxDis = 100; + F32 val; + if(camPos.y < point.y) + { + if(point.y < maxDis) + maxDis = 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 + mBounds.extent.y) + { + GuiCanvas *root = getRoot(); + val = root->mBounds.extent.y - (point.y + mBounds.extent.y); + if(val < maxDis) + maxDis = val; + if( val > 0) + mMulInc= -(float)(camPos.y - (point.y + mBounds.extent.y))/maxDis; + else + mMulInc = -1.0f; + checkIncValue(); + return; + } + mTextAreaHit = None; + Parent::onMouseDragged(event); + } +} + +void GuiTextEditSliderCtrl::onMouseUp(const GuiEvent &event) +{ + mMulInc = 0.0f; + mouseUnlock(); + //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()) + Con::evaluate(mConsoleCommand, false); + mTextAreaHit = None; +} +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, GuiControl *firstResponder) +{ + 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(); + } + } + } + Parent::onRender(offset, updateRect, firstResponder); + + Point2I start(offset.x + mBounds.extent.x - 14, offset.y); + Point2I midPoint(start.x + 7, start.y + (mBounds.extent.y/2)); + + dglDrawRectFill(Point2I(start.x+1,start.y+1), Point2I(start.x+13,start.y+mBounds.extent.y-1) , mProfile->mFillColor); + + dglDrawLine(start, Point2I(start.x, start.y+mBounds.extent.y),mProfile->mFontColor); + dglDrawLine(Point2I(start.x,midPoint.y), + Point2I(start.x+14,midPoint.y), + mProfile->mFontColor); + + glColor3i(0,0,0); + + glBegin(GL_TRIANGLES); + if(mTextAreaHit == ArrowUp) + { + glVertex2i(midPoint.x, start.y+1); + glVertex2i(start.x+11,midPoint.y-2); + glVertex2i(start.x+3,midPoint.y-2); + } + else + { + glVertex2i(midPoint.x, start.y+2); + glVertex2i(start.x+11,midPoint.y-1); + glVertex2i(start.x+3,midPoint.y-1); + } + if(mTextAreaHit == ArrowDown) + { + glVertex2i(midPoint.x, start.y+mBounds.extent.y-1); + glVertex2i(start.x+11,midPoint.y+3); + glVertex2i(start.x+3,midPoint.y+3); + } + else + { + glVertex2i(midPoint.x, start.y+mBounds.extent.y-2); + glVertex2i(start.x+11,midPoint.y+2); + glVertex2i(start.x+3,midPoint.y+2); + } + glEnd(); +} + +void GuiTextEditSliderCtrl::onPreRender() +{ + bool focused = false; + GuiCanvas *canvas = getRoot(); + if (canvas) + focused = (canvas->getFirstResponder() == this); + + if (focused) + { + U32 timeElapsed = Platform::getVirtualMilliseconds() - mTimeLastCursorFlipped; + mNumFramesElapsed++; + if ((timeElapsed > 500) && (mNumFramesElapsed > 3)) + { + mCursorOn = 1 - 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/guiTextEditSliderCtrl.h b/gui/guiTextEditSliderCtrl.h new file mode 100644 index 0000000..5043aa8 --- /dev/null +++ b/gui/guiTextEditSliderCtrl.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITEXTEDITSLIDERCTRL_H_ +#define _GUITEXTEDITSLIDERCTRL_H_ + +#ifndef _GUITYPES_H_ +#include "GUI/guiTypes.h" +#endif +#ifndef _GUITEXTEDITCTRL_H_ +#include "GUI/guiTextEditCtrl.h" +#endif + +class GuiTextEditSliderCtrl : public GuiTextEditCtrl +{ +private: + typedef GuiTextEditCtrl Parent; + Point2I mRange; + F32 mIncAmount; + F32 mValue; + F32 mIncCounter; + F32 mMulInc; + StringTableEntry mFormat; + U32 mMouseDownTime; + // max string len, must be less then or equal to 255 +public: + enum CtrlArea + { + None, + Slider, + ArrowUp, + ArrowDown + }; +private: + CtrlArea mTextAreaHit; +public: + GuiTextEditSliderCtrl(); + ~GuiTextEditSliderCtrl(); + DECLARE_CONOBJECT(GuiTextEditSliderCtrl); + static void initPersistFields(); + static void consoleInit(); + + void getText(char *dest); // dest must be of size + // StructDes::MAX_STRING_LEN + 1 + + void setText(S32 tag); + void setText(const char *txt); + + void setValue(); + void checkRange(); + void checkIncValue(); + void timeInc(U32 elapseTime); + + bool onKeyDown(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + + +}; + +#endif //_GUI_TEXTEDIT_CTRL_H diff --git a/gui/guiTextListCtrl.cc b/gui/guiTextListCtrl.cc new file mode 100644 index 0000000..3c9442e --- /dev/null +++ b/gui/guiTextListCtrl.cc @@ -0,0 +1,584 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/dgl.h" +#include "GUI/guiTextListCtrl.h" + +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 = dStricmp( 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 ); + char* aBuf = new char[dStrlen( aCol ) + 1]; + char* bBuf = new char[dStrlen( bCol ) + 1]; + dStrcpy( aBuf, aCol ); + dStrcpy( bBuf, bCol ); + char* ptr = dStrchr( aBuf, '\t' ); + if ( ptr ) + *ptr = '\0'; + ptr = dStrchr( bBuf, '\t' ); + if ( ptr ) + *ptr = '\0'; + S32 result = dAtoi( aBuf ) - dAtoi( bBuf ); + if ( result == 0 ) + return( dStricmp( ea->text, eb->text ) ); + delete [] aBuf; + delete [] bBuf; + return ( sIncreasing ? result : -result ); +} + +GuiTextListCtrl::GuiTextListCtrl() +{ + VECTOR_SET_ASSOCIATION(mList); + VECTOR_SET_ASSOCIATION(mColumnOffsets); + + mActive = true; + mEnumerate = false; + mResizeCell = true; + mSize.set(1, 0); + mColumnOffsets.push_back(0); + mFitParentWidth = true; + mClipColumnText = false; +} + +void GuiTextListCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("enumerate", TypeBool, Offset(mEnumerate, GuiTextListCtrl)); + addField("resizeCell", TypeBool, Offset(mResizeCell, GuiTextListCtrl)); + addField("columns", TypeS32Vector, Offset(mColumnOffsets, GuiTextListCtrl)); + addField("fitParentWidth", TypeBool, Offset(mFitParentWidth, GuiTextListCtrl)); + addField("clipColumnText", TypeBool, Offset(mClipColumnText, GuiTextListCtrl)); +} + +static S32 cTextListGetSelectedCellId(SimObject *obj, S32, const char **) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + return ctrl->getSelectedId(); +} + +static void cTextListSetSelectedCellId(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + U32 id = dAtoi(argv[2]); + S32 index = ctrl->findEntryById(id); + if(index < 0) + return ; + + ctrl->setSelectedCell(Point2I(0, index)); +} + +static void cTextListSetSelectedRow(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast( obj ); + ctrl->setSelectedCell( Point2I( 0, dAtoi( argv[2] ) ) ); +} + +static void cTextListClearSelection(SimObject *obj, S32, const char **) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + ctrl->setSelectedCell(Point2I(-1, -1)); +} + +static S32 cTextListAdd(SimObject *obj, S32 argc, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + S32 ret = ctrl->mList.size(); + if(argc < 5) + ctrl->addEntry(dAtoi(argv[2]), argv[3]); + else + ctrl->insertEntry(dAtoi(argv[2]), argv[3], dAtoi(argv[4])); + + return ret; +} + +static void cTextListSet(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + ctrl->setEntry(dAtoi(argv[2]), argv[3]); +} + +static void cTextListSort(SimObject *obj, S32 argc, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + if ( argc == 3 ) + ctrl->sort(dAtoi(argv[2])); + else + ctrl->sort( dAtoi( argv[2] ), dAtob( argv[3] ) ); +} + +static void cTextListSortNumerical( SimObject *obj, S32 argc, const char **argv ) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + if ( argc == 3 ) + ctrl->sortNumerical( dAtoi( argv[2] ) ); + else + ctrl->sortNumerical( dAtoi( argv[2] ), dAtob( argv[3] ) ); +} + +static void cTextListClear(SimObject *obj, S32, const char **) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + ctrl->clear(); +} + +static S32 cTextListCount(SimObject *obj, S32, const char **) +{ + return ((GuiTextListCtrl *) obj)->getNumEntries(); +} + +static S32 cTextListGetRowId(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + U32 index = dAtoi(argv[2]); + if(index >= ctrl->getNumEntries()) + return -1; + + return ctrl->mList[index].id; +} + +static const char *cTextListGetRowTextById(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + U32 id = dAtoi(argv[2]); + S32 index = ctrl->findEntryById(id); + if(index < 0) + return ""; + return ctrl->mList[index].text; +} + +static S32 cTextListGetRowNumById(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + U32 id = dAtoi(argv[2]); + + S32 index = ctrl->findEntryById(id); + if(index < 0) + return -1; + return index; +} + +static const char *cTextListGetRowText(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + S32 index = dAtoi(argv[2]); + if(index < 0 || index >= ctrl->mList.size()) + return ""; + return ctrl->mList[index].text; +} + +static void cTextListRemoveRowById(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + U32 id = dAtoi(argv[2]); + ctrl->removeEntry(id); +} + +static void cTextListRemoveRow(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + U32 index = dAtoi(argv[2]); + ctrl->removeEntryByIndex(index); +} + +static void cTextListScrollRowVisible(SimObject *obj, S32, const char **argv) +{ + GuiTextListCtrl *ctrl = static_cast(obj); + ctrl->scrollCellVisible(Point2I(0, dAtoi(argv[2]))); +} + +static S32 cTextListFindText( SimObject* obj, S32, const char** argv ) +{ + GuiTextListCtrl* ctrl = static_cast( obj ); + return( ctrl->findEntryByText( argv[2] ) ); +} + +static void cTextListSetRowActive( SimObject* obj, S32, const char** argv ) +{ + GuiTextListCtrl* ctrl = static_cast( obj ); + ctrl->setEntryActive( U32( dAtoi( argv[2] ) ), dAtob( argv[3] ) ); +} + +static bool cTextListIsRowActive( SimObject* obj, S32, const char** argv ) +{ + GuiTextListCtrl* ctrl = static_cast( obj ); + return( ctrl->isEntryActive( U32( dAtoi( argv[2] ) ) ) ); +} + +void GuiTextListCtrl::consoleInit() +{ + Con::addCommand("GuiTextListCtrl", "getSelectedId", cTextListGetSelectedCellId, "textList.getSelectedId()", 2, 2); + Con::addCommand("GuiTextListCtrl", "setSelectedById", cTextListSetSelectedCellId, "textList.setSelectedById(id)", 3, 3); + Con::addCommand("GuiTextListCtrl", "setSelectedRow", cTextListSetSelectedRow, "textList.setSelectedRow(index)", 3, 3); + Con::addCommand("GuiTextListCtrl", "clearSelection", cTextListClearSelection, "textList.clearSelection()", 2, 2); + Con::addCommand("GuiTextListCtrl", "clear", cTextListClear, "textList.clear()", 2, 2); + Con::addCommand("GuiTextListCtrl", "addRow", cTextListAdd, "textList.addRow(id,text,index)", 4, 5); + Con::addCommand("GuiTextListCtrl", "setRowById", cTextListSet, "textList.setRow(id,text)", 4, 4); + Con::addCommand("GuiTextListCtrl", "getRowId", cTextListGetRowId, "textList.getRowId(index)", 3, 3); + Con::addCommand("GuiTextListCtrl", "removeRowById", cTextListRemoveRowById, "textList.removeRowById(id)", 3, 3); + Con::addCommand("GuiTextListCtrl", "getRowTextById", cTextListGetRowTextById, "textList.getRowTextById(id)", 3, 3); + Con::addCommand("GuiTextListCtrl", "getRowNumById", cTextListGetRowNumById, "textList.getRowNumById(id)", 3, 3); + Con::addCommand("GuiTextListCtrl", "getRowText", cTextListGetRowText, "textList.getRowText(index)", 3, 3); + Con::addCommand("GuiTextListCtrl", "removeRow", cTextListRemoveRow, "textList.removeRow(index)", 3, 3); + Con::addCommand("GuiTextListCtrl", "rowCount", cTextListCount, "textList.rowCount()", 2, 2); + Con::addCommand("GuiTextListCtrl", "scrollVisible", cTextListScrollRowVisible, "textList.scrollVisible(index)", 3, 3); + Con::addCommand("GuiTextListCtrl", "sort", cTextListSort, "textList.sort(colId{, increasing})", 3, 4); + Con::addCommand("GuiTextListCtrl", "sortNumerical", cTextListSortNumerical, "textList.sortNumerical(colId{, increasing})", 3, 4); + Con::addCommand("GuiTextListCtrl", "findTextIndex", cTextListFindText, "textList.findText(text)", 3, 3 ); + Con::addCommand("GuiTextListCtrl", "setRowActive", cTextListSetRowActive, "textlist.setRowActive(id, )", 4, 4 ); + Con::addCommand("GuiTextListCtrl", "isRowActive", cTextListIsRowActive, "textlist.isRowActive(id)", 3, 3 ); +} + +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; +} + +void GuiTextListCtrl::onCellSelected(Point2I cell) +{ + Con::executef(this, 3, "onSelect", Con::getIntArg(mList[cell.y].id), mList[cell.y].text); + + if (mConsoleCommand[0]) + Con::evaluate(mConsoleCommand, false); +} + +void GuiTextListCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver) +{ + if ( mList[cell.y].active ) + { + if (selected) + { + dglDrawRectFill(RectI(offset.x, offset.y, mCellSize.x, mCellSize.y), mProfile->mFillColorHL); + dglSetBitmapModulation(mProfile->mFontColorHL); + } + else + dglSetBitmapModulation(mouseOver ? mProfile->mFontColorHL : mProfile->mFontColor); + } + else + dglSetBitmapModulation( 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) + { + U32 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 = dglGetClipRect(); + + RectI clipRect(pos, Point2I(mColumnOffsets[index+1] - mColumnOffsets[index] - 4, mCellSize.y)); + if(clipRect.intersect(saveClipRect)) + { + clipped = true; + dglSetClipRect(clipRect); + } + } + + dglDrawTextN(mFont, pos, text, slen, mProfile->mFontColors); + + if(clipped) + dglSetClipRect(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(text, nextCol - text); + else + textWidth = mFont->getStrWidth(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(&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 ) + { + GuiControl* parent = getParent(); + if ( parent ) + mCellSize.x = parent->mBounds.extent.x - mBounds.point.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 ); + resize( mBounds.point, 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; + + if ( event.keyCode == KEY_RETURN ) + { + if ( mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand, false ); + return( true ); + } + + if ( event.keyCode == KEY_DELETE && ( mSelectedCell.y >= 0 && mSelectedCell.y < mList.size() ) ) + { + Con::executef( this, 2, "onDeleteKey", Con::getIntArg( mList[mSelectedCell.y].id ) ); + return( true ); + } + + return( Parent::onKeyDown( event ) ); +} + diff --git a/gui/guiTextListCtrl.h b/gui/guiTextListCtrl.h new file mode 100644 index 0000000..16266ef --- /dev/null +++ b/gui/guiTextListCtrl.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITEXTLISTCTRL_H_ +#define _GUITEXTLISTCTRL_H_ + +#ifndef _GUIARRAYCTRL_H_ +#include "GUI/guiArrayCtrl.h" +#endif + +class GuiTextListCtrl : public GuiArrayCtrl +{ + private: + typedef GuiArrayCtrl Parent; + + public: + struct Entry + { + char *text; + U32 id; + bool active; + }; + + Vector mList; + + bool mEnumerate; + bool mResizeCell; + + protected: + enum ScrollConst + { + UP = 0, + DOWN = 1 + }; + enum { + InvalidId = 0xFFFFFFFF + }; + Vector mColumnOffsets; + + bool mFitParentWidth; + bool mClipColumnText; + + U32 getRowWidth(Entry *row); + void onCellSelected(Point2I cell); + + public: + GuiTextListCtrl(); + + DECLARE_CONOBJECT(GuiTextListCtrl); + static void initPersistFields(); + static void consoleInit(); + + 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(); + 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(); +}; + +#endif //_GUI_TEXTLIST_CTRL_H diff --git a/gui/guiTreeViewCtrl.cc b/gui/guiTreeViewCtrl.cc new file mode 100644 index 0000000..062de5b --- /dev/null +++ b/gui/guiTreeViewCtrl.cc @@ -0,0 +1,1473 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiTreeViewCtrl.h" +#include "GUI/guiScrollCtrl.h" +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/dgl.h" +#include "GUI/guiTypes.h" +#include "Platform/event.h" + +IMPLEMENT_CONOBJECT(GuiTreeViewCtrl); + +GuiTreeViewCtrl::Item::Item() +{ + mText = NULL; + mValue = NULL; +} + +GuiTreeViewCtrl::Item::~Item() +{ + if ( mText ) + { + delete [] mText; + mText = NULL; + } + + if ( mValue ) + { + delete [] mValue; + mValue = NULL; + } +} + + +//------------------------------------------------------------------------------ + +GuiTreeViewCtrl::GuiTreeViewCtrl() +{ + VECTOR_SET_ASSOCIATION(mItems); + VECTOR_SET_ASSOCIATION(mVisibleItems); + VECTOR_SET_ASSOCIATION(mImageBounds); + + mItemFreeList = 0; + mRoot = 0; + mItemCount = 0; + mSelectedItem = 0; + + // persist info.. + mTabSize = 16; + mImagesBitmap = StringTable->insert(""); + mNumImages = 0; + mTextOffset = 2; + mFullRowSelect = false; + mItemHeight = 20; + + // + setSize(Point2I(1, 0)); +} + +GuiTreeViewCtrl::~GuiTreeViewCtrl() +{ + destroyTree(); +} + +//------------------------------------------------------------------------------ + +GuiTreeViewCtrl::Item * GuiTreeViewCtrl::getItem(S32 itemId) +{ + if((itemId > 0) && (itemId <= mItems.size())) + return(mItems[itemId-1]); + return(0); +} + +//------------------------------------------------------------------------------ + +GuiTreeViewCtrl::Item * GuiTreeViewCtrl::createItem() +{ + Item * item; + + // grab from the free list? + if(mItemFreeList) + { + item = mItemFreeList; + mItemFreeList = item->mNext; + + // re-add to vector + mItems[item->mId-1] = item; + } + else + { + item = new Item; + mItems.push_back(item); + + // set the id + item->mId = mItems.size(); + } + + // reset + item->mNext = item->mPrevious = item->mParent = item->mChild = 0; + item->mText = 0; + item->mValue = 0; + item->mState = 0; + item->mTabLevel = 0; + + mItemCount++; + return(item); +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::destroyItem(Item * item) +{ + if(!item) + return; + + // free memory: + if ( item->mText ) + { + delete [] item->mText; + item->mText = NULL; + } + if ( item->mValue ) + { + delete [] item->mValue; + item->mValue = NULL; + } + + // 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; + + // destroy all the children + while(item->mChild) + destroyItem(item->mChild); + + // remove from vector + mItems[item->mId-1] = 0; + + // set as root free item + item->mNext = mItemFreeList; + mItemFreeList = item; + + // + if(item->mState.test(Item::Expanded)) + mFlags.set(RebuildVisible); + + mItemCount--; +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::destroyTree() +{ + // clear the item list + for(U32 i = 0; i < mItems.size(); i++) + delete mItems[i]; + mItems.clear(); + + // clear the free list + while(mItemFreeList) + { + Item * next = mItemFreeList->mNext; + delete mItemFreeList; + mItemFreeList = next; + } + + mVisibleItems.clear(); + + // + mItemFreeList = 0; + mRoot = 0; + mItemCount = 0; + mSelectedItem = 0; +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::buildItem( Item* item, U32 tabLevel ) +{ + if ( !item ) + return; + + item->mTabLevel = tabLevel; + mVisibleItems.push_back( item ); + + if ( bool( mProfile->mFont ) && item->mText ) + { + S32 width = ( tabLevel + 1 ) * mTabSize + mProfile->mFont->getStrWidth(item->mText); + if ( mNumImages > 0 ) + width += mImageBounds[0].extent.x; + + if ( width > mMaxWidth ) + mMaxWidth = width; + } + + // if expanded, then add all the children items as well + if ( item->mState.test( Item::Expanded ) ) + { + Item * child = item->mChild; + while ( child ) + { + buildItem( child, tabLevel + 1 ); + child = child->mNext; + } + } +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::buildVisibleTree() +{ + mMaxWidth = 0; + mVisibleItems.clear(); + + // build the root items + Item * traverse = mRoot; + while(traverse) + { + buildItem(traverse, 0); + traverse = traverse->mNext; + } + + // adjust the GuiArrayCtrl + mCellSize.set(mMaxWidth+1, mItemHeight); + setSize(Point2I(1, mVisibleItems.size())); + + // Now, if in a scroll control, make sure we are positioned correctly: + GuiControl *pappy = getParent(); + if ( !pappy || !dynamic_cast( pappy ) ) + return; + + Point2I diff( pappy->mBounds.extent.x - mBounds.extent.x, + pappy->mBounds.extent.y - mBounds.extent.y ); + Point2I newPos( ( ( diff.x >= 0 ) ? 0 : ( mBounds.point.x < diff.x ) ? diff.x : mBounds.point.x ), + ( ( diff.y >= 0 ) ? 0 : ( mBounds.point.y < diff.y ) ? diff.y : mBounds.point.y ) ); + + if ( newPos != mBounds.point ) + resize( newPos, mBounds.extent ); +} + +//------------------------------------------------------------------------------ + +S32 GuiTreeViewCtrl::insertItem(S32 parentId, const char * text, const char * value, 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); + } + + // create an item (assigns id) + Item * item = createItem(); + + // fill the data + item->mText = new char[dStrlen( text ) + 1]; + dStrcpy( item->mText, text ); + item->mValue = new char[dStrlen( value ) + 1]; + dStrcpy( item->mValue, value ); + item->mNormalImage = normalImage; + item->mExpandedImage = expandedImage; + + // root level? + if(parentId == 0) + { + // insert back + if(mRoot) + { + Item * traverse = mRoot; + while(traverse->mNext) + traverse = traverse->mNext; + + traverse->mNext = item; + item->mPrevious = traverse; + } + else + mRoot = item; + mFlags.set(RebuildVisible); + } + else + { + Item * parent = mItems[parentId-1]; + + // insert back + 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; + + if(parent->mState.test(Item::Expanded)) + mFlags.set(RebuildVisible); + } + + // + if(mFlags.test(RebuildVisible)) + buildVisibleTree(); + + return(item->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; + + if(itemId == mSelectedItem) + { + // Select the previous visible item: + if ( item->mPrevious ) + { + Item* temp = item->mPrevious; + while ( temp->mChild && temp->mState.test( Item::Expanded ) ) + { + temp = temp->mChild; + while ( temp->mNext ) + temp = temp->mNext; + } + selectItem( temp->mId, true ); + buildVisibleTree(); + } + // or select parent: + else if ( item->mParent ) + { + selectItem( item->mParent->mId, true ); + buildVisibleTree(); + } + } + + destroyItem(item); + + if(mFlags.test(RebuildVisible)) + buildVisibleTree(); + + return(true); +} + + +//------------------------------------------------------------------------------ + +S32 GuiTreeViewCtrl::getFirstRootItem() +{ + if(!mRoot) + return(0); + + return(mRoot->mId); +} + +//------------------------------------------------------------------------------ + +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 ) +{ + Item* item = getItem( itemId ); + if ( !item ) + { + Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemUp: invalid item id!"); + return; + } + + Item* prevItem = item->mPrevious; + if ( !prevItem ) + { + Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemUp: no previous sibling - how'd this get called?"); + return; + } + + if ( prevItem->mPrevious ) + prevItem->mPrevious->mNext = item; + else if ( item->mParent ) + item->mParent->mChild = item; + + if ( item->mNext ) + item->mNext->mPrevious = prevItem; + + item->mPrevious = prevItem->mPrevious; + prevItem->mNext = item->mNext; + item->mNext = prevItem; + prevItem->mPrevious = item; + + buildVisibleTree(); +} + +//------------------------------------------------------------------------------ + +bool GuiTreeViewCtrl::onWake() +{ + if(!Parent::onWake()) + return(false); + + mImagesHandle = TextureHandle(mImagesBitmap, BitmapKeepTexture); + if(!bool(mImagesHandle)) + return(false); + + mImageBounds.setSize(mNumImages); + + if(!mImagesHandle.getBitmap()) + return(false); + + // ugh + if(!createBitmapArray(mImagesHandle.getBitmap(), &mImageBounds[0], 1, mNumImages)) + return(false); + + return(true); +} + +void GuiTreeViewCtrl::onSleep() +{ + mImagesHandle = 0; + mImageBounds.clear(); + Parent::onSleep(); +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::onPreRender() +{ + Parent::onPreRender(); +} + +//------------------------------------------------------------------------------ + +bool GuiTreeViewCtrl::hitTest(const Point2I & pnt, Item* & item, BitSet32 & flags) +{ + 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 it + AssertFatal(cell.y < mVisibleItems.size(), "GuiTreeViewCtrl::onMouseDown: invalid cell"); + item = mVisibleItems[cell.y]; + + S32 min = mTabSize * item->mTabLevel; + + // left of icon/text? + if(pos.x < min) + return(true); + + // check image + S32 image = item->mState.test(Item::Expanded) ? item->mExpandedImage : item->mNormalImage; + if((image >= 0) && (image < mNumImages)) + min += mImageBounds[image].extent.x; + + // image? + if(pos.x < min) + { + flags.set(OnImage); + return(true); + } + + // offset + min += mTextOffset; + + // check text + min += mProfile->mFont->getStrWidth(item->mText); + if(pos.x < min) + flags.set(OnText); + + return(true); +} + +bool GuiTreeViewCtrl::selectItem(S32 itemId, bool select) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::selectItem: invalid item id!"); + return(false); + } + + if(mSelectedItem) + { + Item * selected = getItem(mSelectedItem); + selected->mState.set(Item::Selected, false); + + char buf[16]; + dSprintf(buf, 16, "%d", mSelectedItem); + Con::executef(this, 2, "onUnSelect", buf); + + mSelectedItem = 0; + } + + if(select) + { + item->mState.set(Item::Selected, true); + + char buf[16]; + dSprintf(buf, 16, "%d", item->mId); + Con::executef(this, 2, "onSelect", buf); + + mSelectedItem = item->mId; + } + + setUpdate(); + + return(true); +} + +bool GuiTreeViewCtrl::expandItem(S32 itemId, bool expand) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::expandItem: invalid item id!"); + return(false); + } + + if(item->mState.test(Item::Expanded) == expand) + return(true); + + // expand parents + if(expand) + { + while(item) + { + item->mState.set(Item::Expanded, true); + item = item->mParent; + } + } + else + item->mState.set(Item::Expanded, false); + + // rebuild + buildVisibleTree(); + 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->mText ? item->mText : ""); +} + +const char * GuiTreeViewCtrl::getItemValue(S32 itemId) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getItemValue: invalid item id!"); + return(""); + } + + return(item->mValue ? item->mValue : ""); +} + +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!"); + return false; + } + + delete [] item->mText; + item->mText = new char[dStrlen( newText ) + 1]; + dStrcpy( item->mText, newText ); + + delete [] item->mValue; + item->mValue = new char[dStrlen( newValue ) + 1]; + dStrcpy( item->mValue, newValue ); + + // Update the widths and such: + buildVisibleTree(); + + return true; +} + + +//------------------------------------------------------------------------------ +bool GuiTreeViewCtrl::onKeyDown( const GuiEvent& event ) +{ + if ( !mVisible || !mActive || !mAwake ) + return true; + + // All the keyboard functionality requires a selected item, so if none exists... + if ( !mSelectedItem ) + return true; + + Item* item = getItem( mSelectedItem ); + if ( !item ) + return true; + + // Explorer-esque Navigation... + switch( event.keyCode ) + { + case KEY_UP: + // Select previous visible item: + if ( item->mPrevious ) + { + item = item->mPrevious; + while ( item->mChild && item->mState.test( Item::Expanded ) ) + { + item = item->mChild; + while ( item->mNext ) + item = item->mNext; + } + selectItem( item->mId, true ); + buildVisibleTree(); + return true; + } + // or select parent: + if ( item->mParent ) + { + selectItem( item->mParent->mId, true ); + buildVisibleTree(); + return true; + } + break; + + case KEY_DOWN: + // Selected child if it is visible: + if ( item->mChild && item->mState.test( Item::Expanded ) ) + { + selectItem( item->mChild->mId, true ); + buildVisibleTree(); + return true; + } + // or select next sibling (recursively): + do + { + if ( item->mNext ) + { + selectItem( item->mNext->mId, true ); + buildVisibleTree(); + return true; + } + + item = item->mParent; + } while ( item ); + break; + + case KEY_LEFT: + // Contract current menu: + if ( item->mState.test( Item::Expanded ) ) + { + expandItem( item->mId, false ); + buildVisibleTree(); + return true; + } + // or select parent: + if ( item->mParent ) + { + selectItem( item->mParent->mId, true ); + buildVisibleTree(); + return true; + } + break; + + case KEY_RIGHT: + // Expand selected item: + if ( item->mChild ) + { + if ( !item->mState.test( Item::Expanded ) ) + { + expandItem( item->mId, true ); + buildVisibleTree(); + return true; + } + // or select child: + selectItem( item->mChild->mId, true ); + buildVisibleTree(); + return true; + } + break; + } + + // Not processed, so pass the event on: + return Parent::onKeyDown( event ); +} + + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::onMouseDown(const GuiEvent & event) +{ + if( !mActive || !mAwake || !mVisible ) + { + Parent::onMouseDown(event); + return; + } + + if ( mProfile->mCanKeyFocus ) + setFirstResponder(); + + Item * item = 0; + BitSet32 hitFlags; + + S32 prevSelected = mSelectedItem; + + // + if(!hitTest(event.mousePoint, item, hitFlags)) + return; + + // + if(event.modifier & SI_CTRL) + selectItem(item->mId, !item->mState.test(Item::Selected)); + else + selectItem(item->mId, true); + + if ( hitFlags.test( OnText ) && ( event.mouseClickCount > 1 ) && ( prevSelected == mSelectedItem ) && mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand ); + + if(!item->mChild) + return; + + // + if ( mFullRowSelect || hitFlags.test( OnImage ) ) + { + item->mState.toggle( Item::Expanded ); + buildVisibleTree(); + } +} + + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::onMouseMove( const GuiEvent &event ) +{ + if ( mMouseOverCell.y >= 0 ) + 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 ); + else + 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[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 = 0; + BitSet32 hitFlags; + + // + if(!hitTest(event.mousePoint, item, hitFlags)) + return; + + selectItem(item->mId, true); + + // + char bufs[2][32]; + dSprintf(bufs[0], 32, "%d", item->mId); + dSprintf(bufs[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y); + + Con::executef(this, 3, "onRightMouseDown", bufs[0], bufs[1]); +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::onRenderCell(Point2I offset, Point2I cell, bool, bool ) +{ + AssertFatal(cell.y < mVisibleItems.size(), "GuiTreeViewCtrl::onRenderCell: invalid cell"); + Item * item = mVisibleItems[cell.y]; + + // draw the bitmap + S32 image = item->mState.test(Item::Expanded) ? item->mExpandedImage : item->mNormalImage; + U32 textOffset = mTextOffset; + if((image >= 0) && (image < mNumImages)) + { + dglClearBitmapModulation(); + dglDrawBitmapSR(mImagesHandle, Point2I(offset.x + mTabSize * item->mTabLevel, offset.y), mImageBounds[image]); + textOffset += mImageBounds[image].extent.x; + } + + if ( item->mState.test( Item::Selected ) ) + { + dglDrawRectFill( RectI( offset.x + ( mTabSize * item->mTabLevel ), offset.y, mCellSize.x, mCellSize.y ), mProfile->mFillColorHL ); + dglSetBitmapModulation( mProfile->mFontColorHL ); + } + else + dglSetBitmapModulation( item->mState.test( Item::MouseOverText ) ? mProfile->mFontColorHL : mProfile->mFontColor ); + + dglDrawText( mProfile->mFont, Point2I( textOffset + offset.x + ( mTabSize * item->mTabLevel ), offset.y ), item->mText, mProfile->mFontColors ); +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("tabSize", TypeS32, Offset(mTabSize, GuiTreeViewCtrl)); + addField("imagesBitmap", TypeString, Offset(mImagesBitmap, GuiTreeViewCtrl)); + addField("numImages", TypeS32, Offset(mNumImages, GuiTreeViewCtrl)); + addField("textOffset", TypeS32, Offset(mTextOffset, GuiTreeViewCtrl)); + addField("fullRowSelect", TypeBool, Offset(mFullRowSelect, GuiTreeViewCtrl)); + addField("itemHeight", TypeS32, Offset(mItemHeight, GuiTreeViewCtrl)); +} + +//------------------------------------------------------------------------------ + +static S32 cInsertItem(SimObject * obj, S32 argc, const char ** argv) +{ + argc; + + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->insertItem(dAtoi(argv[2]), argv[3], argv[4], 0, 1)); +} + +static const char * cGetItemText(SimObject * obj, S32, const char ** argv) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->getItemText(dAtoi(argv[2]))); +} + +static const char * cGetItemValue(SimObject * obj, S32, const char ** argv) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->getItemValue(dAtoi(argv[2]))); +} + +static bool cEditItem(SimObject * obj, S32, const char ** argv) +{ + GuiTreeViewCtrl* tree = static_cast(obj); + return(tree->editItem(dAtoi(argv[2]), argv[3], argv[4])); +} + +static bool cRemoveItem(SimObject * obj, S32, const char ** argv) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->removeItem(dAtoi(argv[2]))); +} + +static void cClear(SimObject * obj, S32, const char **) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + tree->removeItem(0); +} + +static bool cSelectItem(SimObject * obj, S32 argc, const char ** argv) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + + S32 id = dAtoi(argv[2]); + bool select = true; + if(argc == 4) + select = dAtob(argv[3]); + + return(tree->selectItem(id, select)); +} + +static bool cExpandItem(SimObject * obj, S32 argc, const char ** argv) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + + S32 id = dAtoi(argv[2]); + bool expand = true; + if(argc == 4) + expand = dAtob(argv[3]); + + return(tree->expandItem(id, expand)); +} + +static S32 cGetFirstRootItem(SimObject * obj, S32, const char **) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->getFirstRootItem()); +} + +static S32 cGetChild(SimObject * obj, S32, const char ** argv) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->getChildItem(dAtoi(argv[2]))); +} + +static S32 cGetParent(SimObject * obj, S32, const char ** argv) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->getParentItem(dAtoi(argv[2]))); +} + +static S32 cGetNextSibling(SimObject * obj, S32, const char ** argv) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->getNextSiblingItem(dAtoi(argv[2]))); +} + +static S32 cGetPrevSibling(SimObject * obj, S32, const char ** argv) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->getPrevSiblingItem(dAtoi(argv[2]))); +} + +static S32 cGetItemCount(SimObject * obj, S32, const char **) +{ + GuiTreeViewCtrl * tree = static_cast(obj); + return(tree->getItemCount()); +} + +static S32 cGetSelectedItem(SimObject * obj, S32, const char **) +{ + GuiTreeViewCtrl* tree = static_cast(obj); + return ( tree->getSelectedItem() ); +} + +static void cMoveItemUp(SimObject* obj, S32, const char** argv) +{ + GuiTreeViewCtrl* tree = static_cast(obj); + tree->moveItemUp( dAtoi( argv[2] ) ); +} + + +void GuiTreeViewCtrl::consoleInit() +{ + Con::addCommand("GuiTreeViewCtrl", "insertItem", cInsertItem, "tree.insertItem(parent, name, value, normalImage, expandedImage);", 4, 7); + Con::addCommand("GuiTreeViewCtrl", "getItemText", cGetItemText, "tree.getItemText(item)", 3, 3); + Con::addCommand("GuiTreeViewCtrl", "getItemValue", cGetItemValue, "tree.getItemValue(item)", 3, 3); + Con::addCommand("GuiTreeViewCtrl", "editItem", cEditItem, "tree.editItem(item, \"text\", \"value\")", 5, 5); + Con::addCommand("GuiTreeViewCtrl", "removeItem", cRemoveItem, "tree.removeItem(item);", 3, 3); + Con::addCommand("GuiTreeViewCtrl", "clear", cClear, "tree.clear();", 2, 2); + Con::addCommand("GuiTreeViewCtrl", "selectItem", cSelectItem, "tree.selectItem(item, );", 3, 4); + Con::addCommand("GuiTreeViewCtrl", "expandItem", cExpandItem, "tree.expandItem(item, );", 3, 4); + Con::addCommand("GuiTreeViewCtrl", "getFirstRootItem", cGetFirstRootItem, "tree.getFirstRootItem();", 2, 2); + Con::addCommand("GuiTreeViewCtrl", "getChild", cGetChild, "tree.getChild(item);", 3, 3); + Con::addCommand("GuiTreeViewCtrl", "getParent", cGetParent, "tree.getParent(item);", 3, 3); + Con::addCommand("GuiTreeViewCtrl", "getNextSibling", cGetNextSibling, "tree.getNextSibling(item);", 3, 3); + Con::addCommand("GuiTreeViewCtrl", "getPrevSibling", cGetPrevSibling, "tree.getPrevSibling(item);", 3, 3); + Con::addCommand("GuiTreeViewCtrl", "getItemCount", cGetItemCount, "tree.getItemCount();", 2, 2); + Con::addCommand("GuiTreeViewCtrl", "getSelectedItem", cGetSelectedItem, "tree.getSelectedItem();", 2, 2); + Con::addCommand("GuiTreeViewCtrl", "moveItemUp", cMoveItemUp, "tree.moveItemUp(item);", 3, 3); +} +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +static void cGuiTreeViewOpen(SimObject *obj, S32, const char **argv) +{ + GuiTreeView *tree = static_cast(obj); + SimSet *treeRoot = NULL; + SimObject* target = Sim::findObject(argv[2]); + if (target) + treeRoot = dynamic_cast(target); + if (! treeRoot) + Sim::findObject(RootGroupId, treeRoot); + tree->setTreeRoot(treeRoot); +} + +void GuiTreeView::consoleInit() +{ + Con::addCommand("GuiTreeView", "open", cGuiTreeViewOpen, "treeView.open(obj)", 3, 3); +} + +void GuiTreeView::initPersistFields() +{ + Parent::initPersistFields(); + addField("allowMultipleSelections", TypeBool, Offset(mAllowMultipleSelections, GuiTreeView)); + addField("recurseSets", TypeBool, Offset(mRecurseSets, GuiTreeView)); +} + +GuiTreeView::GuiTreeView() +{ + VECTOR_SET_ASSOCIATION(mObjectList); + + + mAllowMultipleSelections = false; + mRecurseSets = false; + mSize = Point2I(1, 0); +} + +bool GuiTreeView::onWake() +{ + if (! Parent::onWake()) + return false; + + //get the bitmap texture handle + mTextureHandle = mProfile->mTextureHandle; + + //get the font + mFont = mProfile->mFont; + + bool result = createBitmapArray(mTextureHandle.getBitmap(), mBitmapBounds, 1, BmpCount); + AssertFatal(result, "Failed to create the bitmap array"); + + //init the size + mCellSize.set(640, mBitmapBounds[BmpParentContinue].extent.y); + return(true); +} + +void GuiTreeView::onSleep() +{ + Parent::onSleep(); + mTextureHandle = NULL; + mFont = NULL; +} + +void GuiTreeView::buildTree(SimSet *srcObj, S32 srcLevel, S32 srcParentIndex) +{ + //loop through the children of the src object + SimSet::iterator i; + for(i = srcObj->begin(); i != srcObj->end(); i++) + { + //push the child + mObjectList.push_back(ObjNode(srcLevel, *i, i+1 == srcObj->end(), srcParentIndex)); + + //if the child is expanded, build the tree under the child + SimSet *g = dynamic_cast(*i); + if(g && g->isExpanded()) + { + buildTree(g, srcLevel + 1, mObjectList.size() - 1); + } + } +} + +void GuiTreeView::setTreeRoot(SimSet *srcObj) +{ + if (srcObj) + { + mObjectList.clear(); + + //push the root + mObjectList.push_back(ObjNode(0, srcObj, true, -1)); + + if(srcObj->isExpanded()) + { + buildTree(srcObj, 1, 0); + } + + //set the size + setSize(Point2I(1, mObjectList.size())); + + mRootObject = srcObj; + } +} + +void GuiTreeView::onMouseDown(const GuiEvent &event) +{ + if(! mActive) + { + Parent::onMouseDown(event); + return; + } + + Point2I pt = globalToLocalCoord(event.mousePoint); + + //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) + { + //see where in the cell the hit happened + ObjNode *hit = &mObjectList[cell.y]; + S32 statusWidth = mBitmapBounds[BmpParentContinue].extent.x; + S32 statusOffset = statusWidth * hit->level; + + //if we clicked on the expanded icon + SimSet *grp = dynamic_cast(hit->object); + if (pt.x >= statusOffset && pt.x <= statusOffset + statusWidth && grp && grp->size()) + { + grp->setExpanded(! grp->isExpanded()); + setTreeRoot(mRootObject); + } + + //if we clicked on the object's name... + else if (pt.x >= 2 + ((hit->level + 1) * statusWidth)) + { + if(!hit->object) + return; + + if(mAllowMultipleSelections) + { + // calls 'onInspect' for the clicked object and 'onSelect/onUnselect' for + // every object (for set recursion) + if(event.modifier & SI_CTRL) + toggleSelected(hit->object); + else if(event.modifier & SI_SHIFT) + selectObject(hit->object); + else if(event.modifier & SI_ALT) + setInstantGroup(hit->object); + else + setSelected(hit->object); + + // + inspectObject(hit->object); + } + else + // will not call 'onInspect' + setSelected(hit->object); + } + } +} + +GuiTreeView::ObjNode * GuiTreeView::getHitNode(const GuiEvent & event) +{ + Point2I pt = globalToLocalCoord(event.mousePoint); + + //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) + { + //see where in the cell the hit happened + ObjNode *hit = &mObjectList[cell.y]; + if(!hit->object) + return(0); + + S32 statusWidth = mBitmapBounds[BmpParentContinue].extent.x; + + //if we clicked on the object's name... + if (pt.x >= 2 + ((hit->level + 1) * statusWidth)) + return(hit); + } + return(0); +} + +void GuiTreeView::onRightMouseDown(const GuiEvent & event) +{ + if(!mActive) + { + Parent::onMouseDown(event); + return; + } + + ObjNode * hit; + if(!(hit = getHitNode(event))) + return; + + if(!hit->object) + return; + + setSelected(hit->object); +} + +void GuiTreeView::onRightMouseUp(const GuiEvent & event) +{ + if(!mActive) + { + Parent::onMouseDown(event); + return; + } + + ObjNode * hit; + if(!(hit = getHitNode(event))) + return; + + if(!hit->object) + return; + + // + char buf1[32]; + dSprintf(buf1, sizeof(buf1), "%d %d", event.mousePoint.x, event.mousePoint.y); + + char buf2[16]; + dSprintf(buf2, sizeof(buf2), "%d", hit->object->getId()); + + Con::executef(this, 3, "onContextMenu", buf1, buf2); +} + +void GuiTreeView::setInstantGroup(SimObject * obj) +{ + // make sure a group + SimGroup * grp = dynamic_cast(obj); + if(!grp) + return; + + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", grp->getId()); + Con::setVariable("instantGroup", buf); +} + +void GuiTreeView::inspectObject(SimObject * obj) +{ + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", obj->getId()); + Con::executef(this, 2, "onInspect", buf); +} + +void GuiTreeView::selectObject(SimObject * obj) +{ + if(mRecurseSets) + { + SimSet * set = dynamic_cast(obj); + if(set) + for(SimSet::iterator itr = set->begin(); itr != set->end(); itr++) + selectObject(*itr); + } + + // + obj->setSelected(true); + + // + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", obj->getId()); + Con::executef(this, 2, "onSelect", buf); +} + +void GuiTreeView::unselectObject(SimObject * obj) +{ + if(mRecurseSets) + { + SimSet * set = dynamic_cast(obj); + if(set) + for(SimSet::iterator itr = set->begin(); itr != set->end(); itr++) + unselectObject(*itr); + } + + // + obj->setSelected(false); + + // + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", obj->getId()); + Con::executef(this, 2, "onUnselect", buf); +} + +void GuiTreeView::clearSelected() +{ + for(U32 i = 0; i < mObjectList.size(); i++) + if(mObjectList[i].object->isSelected()) + unselectObject(mObjectList[i].object); +} + +void GuiTreeView::toggleSelected(SimObject * obj) +{ + if(obj->isSelected()) + unselectObject(obj); + else + selectObject(obj); +} + +void GuiTreeView::setSelected(SimObject *selObj) +{ + clearSelected(); + selectObject(selObj); +} + +void GuiTreeView::onPreRender() +{ + setTreeRoot(mRootObject); +} + +void GuiTreeView::onRenderCell(Point2I offset, Point2I cell, bool, bool) +{ + Point2I cellOffset = offset; + + ObjNode *obj = &(mObjectList[cell.y]); + ObjNode *prev = cell.y ? &(mObjectList[cell.y - 1]) : NULL; + + /* + S32 bitmap = (mVBarEnabled ? ((curHitRegion == UpArrow && stateDepressed) ? + BmpStates * BmpUp + BmpHilite : BmpStates * BmpUp) : BmpStates * BmpUp + BmpDisabled); + + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[bitmap]); + */ + + S32 statusWidth = mBitmapBounds[BmpParentContinue].extent.x; + bool sel = obj->object->isSelected(); + + //draw the background behind the selected cell + if (sel) + { + RectI selRect(Point2I(cellOffset.x + 2, cellOffset.y), Point2I(mBounds.extent.x - 2, mBounds.extent.y)); + dglDrawRectFill(selRect, ColorI(255, 255, 255)); + } + + // check if instantGroup - should draw a bitmap or something else... + const char * instantGroupName = Con::getVariable("instantGroup"); + SimGroup * instantGroup = dynamic_cast(Sim::findObject(instantGroupName)); + if(instantGroup && instantGroup->getId() == obj->object->getId()) + { + RectI selRect(Point2I(cellOffset.x + 2, cellOffset.y), Point2I(mBounds.extent.x - 2, mBounds.extent.y)); + dglDrawRectFill(selRect, ColorI(200, 200, 200)); + } + + //find the status bmp index + S32 bmpIndex = -1; + SimSet *grp = dynamic_cast(obj->object); + if(grp && grp->size()) + { + if(grp->isExpanded()) bmpIndex = BmpParentOpen; + else bmpIndex = BmpParentClosed; + } + else bmpIndex = BmpNone; + + //add to the index based on the status + if (! prev || obj->level == 0) + { + if (!obj->lastInGroup) bmpIndex += 1; + } + else if (obj->lastInGroup) bmpIndex += 2; + else bmpIndex += 3; + + //now we have our bmpIndex, draw the status + if (bmpIndex >= 0) + { + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, Point2I(cellOffset.x + (obj->level * statusWidth), cellOffset.y), mBitmapBounds[bmpIndex]); + } + + //draw in all the required continuation lines + S32 parent = obj->parentIndex; + while(parent != -1) + { + ObjNode *p = &(mObjectList[parent]); + if (!p->lastInGroup) + { + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, Point2I(cellOffset.x + (p->level * statusWidth), cellOffset.y), mBitmapBounds[BmpParentContinue]); + } + parent = p->parentIndex; + } + + //draw the name - JFF temp hidden/locked + char buf[256]; + if(obj->object->isHidden()) + dSprintf(buf, sizeof(buf), "(H) %d: %s - %s", obj->object->getId(), obj->object->getName() ? obj->object->getName() : "", obj->object->getClassName()); + else if(obj->object->isLocked()) + dSprintf(buf, sizeof(buf), "(L) %d: %s - %s", obj->object->getId(), obj->object->getName() ? obj->object->getName() : "", obj->object->getClassName()); + else + dSprintf(buf, sizeof(buf), "%d: %s - %s", obj->object->getId(), obj->object->getName() ? obj->object->getName() : "", obj->object->getClassName()); + + dglSetBitmapModulation(sel ? mProfile->mFontColorHL : mProfile->mFontColor); + dglDrawText(mFont, Point2I(cellOffset.x + (obj->level + 1) * statusWidth, cellOffset.y), buf, mProfile->mFontColors); +} diff --git a/gui/guiTreeViewCtrl.h b/gui/guiTreeViewCtrl.h new file mode 100644 index 0000000..5aded94 --- /dev/null +++ b/gui/guiTreeViewCtrl.h @@ -0,0 +1,253 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITREEVIEWCTRL_H_ +#define _GUITREEVIEWCTRL_H_ + +#ifndef _MRECT_H_ +#include "Math/mRect.h" +#endif +#ifndef _GFONT_H_ +#include "dgl/gFont.h" +#endif +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif +#ifndef _GUIARRAYCTRL_H_ +#include "GUI/guiArrayCtrl.h" +#endif + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +#ifndef _BITSET_H_ +#include "Core/bitSet.h" +#endif +class GuiTreeViewCtrl : public GuiArrayCtrl +{ + private: + typedef GuiArrayCtrl Parent; + + protected: + + struct Item + { + enum ItemState { + Selected = BIT(0), + Expanded = BIT(1), + Focus = BIT(2), + MouseOverBmp = BIT(3), + MouseOverText = BIT(4), + }; + + BitSet32 mState; + char* mText; + char* mValue; + S16 mNormalImage; + S16 mExpandedImage; + + S32 mId; + U32 mTabLevel; + Item * mParent; + Item * mChild; + Item * mNext; + Item * mPrevious; + + Item(); + ~Item(); + }; + + Vector mItems; + Vector mVisibleItems; + + S32 mItemCount; + Item * mItemFreeList; + Item * mRoot; + S32 mMaxWidth; + S32 mSelectedItem; + + enum TreeState { + RebuildVisible = BIT(0) + }; + BitSet32 mFlags; + + TextureHandle mImagesHandle; + Vector mImageBounds; + + // persist field info... + S32 mTabSize; + StringTableEntry mImagesBitmap; + S32 mNumImages; + S32 mTextOffset; + bool mFullRowSelect; + S32 mItemHeight; + + Item * getItem(S32 itemId); + Item * createItem(); + void destroyItem(Item * item); + void destroyTree(); + + // + void buildItem(Item * item, U32 tabLevel); + void buildVisibleTree(); + + // + enum HitFlags { + OnIndent = BIT(0), + OnImage = BIT(1), + OnText = BIT(2), + OnRow = BIT(3), + }; + bool hitTest(const Point2I & pnt, Item* & item, BitSet32 & flags); + + public: + + GuiTreeViewCtrl(); + virtual ~GuiTreeViewCtrl(); + + // + bool selectItem(S32 itemId, bool select); + bool expandItem(S32 itemId, bool expand); + const char * getItemText(S32 itemId); + const char * getItemValue(S32 itemId); + + bool editItem( S32 itemId, const char* newText, const char* newValue ); + + // insertion/removal + S32 insertItem(S32 parentId, const char * text, const char * value, S16 normalImage, S16 expandedImage); + bool removeItem(S32 itemId); + + // tree items + S32 getFirstRootItem(); + S32 getChildItem(S32 itemId); + S32 getParentItem(S32 itemId); + S32 getNextSiblingItem(S32 itemId); + S32 getPrevSiblingItem(S32 itemId); + S32 getItemCount(); + S32 getSelectedItem(); + void moveItemUp( S32 itemId ); + +// // visible items +// S32 getFirstVisible(); +// S32 getLastVisible(); +// S32 getNextVisible(S32 itemId); +// S32 getPrevVisible(S32 itemId); +// S32 getVisibleCount(); + +// // misc. + //bool scrollVisible( S32 itemId ); + + // GuiControl + bool onWake(); + void onSleep(); + void onPreRender(); + bool onKeyDown( const GuiEvent &event ); + void onMouseDown(const GuiEvent &event); + void onMouseMove(const GuiEvent &event); + void onMouseEnter(const GuiEvent &event); + void onMouseLeave(const GuiEvent &event); + void onRightMouseDown(const GuiEvent &event); + + // GuiArrayCtrl + void onRenderCell(Point2I offset, Point2I cell, bool, bool); + + // + static void consoleInit(); + static void initPersistFields(); + + DECLARE_CONOBJECT(GuiTreeViewCtrl); +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class GuiTreeView : public GuiArrayCtrl +{ + private: + typedef GuiArrayCtrl Parent; + + struct ObjNode + { + S32 level; + SimObject *object; + bool lastInGroup; + S32 parentIndex; + ObjNode(S32 in_level, SimObject *in_object, bool in_last, S32 in_pi) + { + level = in_level; + object = in_object; + lastInGroup = in_last; + parentIndex = in_pi; + } + }; + + Vector mObjectList; + + enum BitmapIndices + { + BmpNone = -1, + BmpChildAbove, + BmpChildBelow, + BmpChildBetween, + + BmpParentOpen, + BmpParentOpenAbove, + BmpParentOpenBelow, + BmpParentOpenBetween, + + BmpParentClosed, + BmpParentClosedAbove, + BmpParentClosedBelow, + BmpParentClosedBetween, + + BmpParentContinue, + + BmpCount + }; + RectI mBitmapBounds[BmpCount]; //bmp is [3*n], bmpHL is [3*n + 1], bmpNA is [3*n + 2] + TextureHandle mTextureHandle; + + //font + Resource mFont; + + SimSet *mRootObject; + + // persist data + bool mAllowMultipleSelections; + bool mRecurseSets; + + ObjNode * getHitNode(const GuiEvent & event); + + public: + DECLARE_CONOBJECT(GuiTreeView); + GuiTreeView(); + static void consoleInit(); + static void initPersistFields(); + + bool onWake(); + void onSleep(); + + void setTreeRoot(SimSet *srcObj); + void buildTree(SimSet *srcObj, S32 srcLevel, S32 srcParentIndex); + + void onPreRender(); + void onMouseDown(const GuiEvent &event); + void onRightMouseDown(const GuiEvent & event); + void onRightMouseUp(const GuiEvent & event); + + void setInstantGroup(SimObject * obj); + void inspectObject(SimObject * obj); + void selectObject(SimObject * obj); + void unselectObject(SimObject * obj); + void toggleSelected(SimObject * obj); + void clearSelected(); + void setSelected(SimObject *selObj); + + void onRenderCell(Point2I offset, Point2I cell, bool, bool); +}; + +#endif diff --git a/gui/guiTypes.cc b/gui/guiTypes.cc new file mode 100644 index 0000000..f27a24a --- /dev/null +++ b/gui/guiTypes.cc @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/gFont.h" +#include "dgl/dgl.h" +#include "GUI/guiTypes.h" +#include "dgl/gBitmap.h" +#include "dgl/gTexManager.h" + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +GuiCursor::GuiCursor() +{ + mHotSpot.set(0,0); +} + +GuiCursor::~GuiCursor() +{ +} + +void GuiCursor::initPersistFields() +{ + Parent::initPersistFields(); + addField("hotSpot", TypePoint2I, Offset(mHotSpot, GuiCursor)); + addField("bitmapName", TypeString, Offset(mBitmapName, GuiCursor)); +} + +bool GuiCursor::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mTextureHandle = TextureHandle(mBitmapName); + if(!mTextureHandle) + return false; + + mExtent.set(mTextureHandle.getWidth(), mTextureHandle.getHeight()); + Sim::getGuiDataGroup()->addObject(this); + + return true; +} + +void GuiCursor::onRemove() +{ + Parent::onRemove(); +} + +static EnumTable::Enums alignEnums[] = +{ + { GuiControlProfile::LeftJustify, "left" }, + { GuiControlProfile::CenterJustify, "center" }, + { GuiControlProfile::RightJustify, "right" } +}; +static EnumTable gAlignTable(3, &alignEnums[0]); + +GuiControlProfile::GuiControlProfile(void) : + mFontColor(mFontColors[BaseColor]), + mFontColorHL(mFontColors[ColorHL]), + mFontColorNA(mFontColors[ColorNA]), + mFontColorSEL(mFontColors[ColorSEL]) +{ + mRefCount = 0; + + 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; + + mBorder = def->mBorder; + mBorderColor = def->mBorderColor; + mBorderColorHL = def->mBorderColorHL; + mBorderColorNA = def->mBorderColorNA; + + // default font + mFontType = def->mFontType; + mFontSize = def->mFontSize; + + for(U32 i = 0; i < 10; i++) + mFontColors[i] = def->mFontColors[i]; + + // default bitmap + mBitmapName = def->mBitmapName; + mBitmapBase = def->mBitmapBase; + 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; + } +} + +GuiControlProfile::~GuiControlProfile() +{ +} + + +void GuiControlProfile::initPersistFields() +{ + Parent::initPersistFields(); + + addField("tab", TypeBool, Offset(mTabable, GuiControlProfile)); + addField("canKeyFocus", TypeBool, Offset(mCanKeyFocus, 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("border", TypeBool, Offset(mBorder, GuiControlProfile)); + addField("borderColor", TypeColorI, Offset(mBorderColor, GuiControlProfile)); + addField("borderColorHL", TypeColorI, Offset(mBorderColorHL, GuiControlProfile)); + addField("borderColorNA", TypeColorI, Offset(mBorderColorNA, GuiControlProfile)); + + addField("fontType", TypeString, Offset(mFontType, GuiControlProfile)); + addField("fontSize", TypeS32, Offset(mFontSize, GuiControlProfile)); + 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("justify", TypeEnum, Offset(mAlignment, GuiControlProfile), 1, &gAlignTable); + addField("textOffset", TypePoint2I, Offset(mTextOffset, GuiControlProfile)); + addField("bitmapBase", TypeString, Offset(mBitmapBase, 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)); + + addField("bitmap", TypeString, Offset(mBitmapName, GuiControlProfile)); + + addField("soundButtonDown", TypeAudioProfilePtr, Offset(mSoundButtonDown, GuiControlProfile)); + addField("soundButtonOver", TypeAudioProfilePtr, Offset(mSoundButtonOver, GuiControlProfile)); +} + +bool GuiControlProfile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + Sim::getGuiDataGroup()->addObject(this); + + return true; +} + +void GuiControlProfile::incRefCount() +{ + if(!mRefCount++) + { + //verify the font + mFont = GFont::create(mFontType, mFontSize); + AssertFatal(bool(mFont), "Failed to create the font."); + + //verify the bitmap + mTextureHandle = TextureHandle(mBitmapName, BitmapKeepTexture); + AssertFatal(bool(mTextureHandle), "Profile has no bitmap"); + } + + AssertFatal(bool(mFont), "GuiControlProfile::incRefCount: invalid font"); + AssertFatal(bool(mTextureHandle), "GuiControlProfile::incRefCount: invalid bitmap"); +} + +void GuiControlProfile::decRefCount() +{ + AssertFatal(mRefCount, "GuiControlProfile::decRefCount: zero ref count"); + if(!mRefCount) + return; + + if(!--mRefCount) + { + mFont = NULL; + mTextureHandle = NULL; + } +} + +static void setDataTypeGuiProfile(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32) +{ + 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(); +} + +static const char *getDataTypeGuiProfile(void *dptr, EnumTable *, BitSet32) +{ + static char returnBuffer[256]; + + GuiControlProfile **obj = (GuiControlProfile**)dptr; + dSprintf(returnBuffer, sizeof(returnBuffer), "%s", *obj ? (*obj)->getName() : ""); + return returnBuffer; +} + +void RegisterGuiTypes(void) +{ + Con::registerType(TypeGuiProfile, sizeof(GuiControlProfile *), getDataTypeGuiProfile, setDataTypeGuiProfile); +} diff --git a/gui/guiTypes.h b/gui/guiTypes.h new file mode 100644 index 0000000..65e841a --- /dev/null +++ b/gui/guiTypes.h @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITYPES_H_ +#define _GUITYPES_H_ + +#ifndef _GFONT_H_ +#include "dgl/gFont.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _PLATFORMAUDIO_H_ +#include "Platform/platformAudio.h" +#endif +#ifndef _AUDIODATABLOCK_H_ +#include "audio/audioDataBlock.h" +#endif + +class GBitmap; + +struct GuiEvent +{ + U16 ascii; // ascii character code 'a', 'A', 'b', '*', etc (if device==keyboard) - possibly a uchar or something + U8 modifier; // SI_LSHIFT, etc + U8 keyCode; // for unprintables, 'tab', 'return', ... + Point2I mousePoint; // for mouse events + U8 mouseClickCount; // to determine double clicks, etc... +}; + +class GuiCursor : public SimObject +{ +private: + typedef SimObject Parent; + StringTableEntry mBitmapName; + + Point2I mHotSpot; + Point2I mExtent; + TextureHandle mTextureHandle; + +public: + TextureHandle getTextureHandle() { return mTextureHandle; } + Point2I getHotSpot() { return mHotSpot; } + Point2I getExtent() { return mExtent; } + + DECLARE_CONOBJECT(GuiCursor); + GuiCursor(void); + ~GuiCursor(void); + static void initPersistFields(); + + bool onAdd(void); + void onRemove(); +}; + + +class GuiControlProfile : public SimObject +{ +private: + typedef SimObject Parent; + +public: + S32 mRefCount; + bool mTabable; + bool mCanKeyFocus; + bool mModal; + + bool mOpaque; + ColorI mFillColor; + ColorI mFillColorHL; + ColorI mFillColorNA; + + bool mBorder; + ColorI mBorderColor; + ColorI mBorderColorHL; + ColorI mBorderColorNA; + + // font members + StringTableEntry mFontType; + S32 mFontSize; + enum { + BaseColor = 0, + ColorHL, + ColorNA, + ColorSEL, + ColorUser0, + ColorUser1, + ColorUser2, + ColorUser3, + ColorUser4, + ColorUser5, + }; + ColorI mFontColors[10]; + ColorI & mFontColor; + ColorI & mFontColorHL; + ColorI & mFontColorNA; + ColorI & mFontColorSEL; + + Resource mFont; + + enum AlignmentType + { + LeftJustify, + RightJustify, + CenterJustify + }; + + AlignmentType mAlignment; + bool mAutoSizeWidth; + bool mAutoSizeHeight; + bool mReturnTab; + bool mNumbersOnly; + ColorI mCursorColor; + + StringTableEntry mBitmapBase; + Point2I mTextOffset; + + // bitmap members + StringTableEntry mBitmapName; + TextureHandle mTextureHandle; + + // sound members + AudioProfile *mSoundButtonDown; + AudioProfile *mSoundButtonOver; + +public: + DECLARE_CONOBJECT(GuiControlProfile); + GuiControlProfile(); + ~GuiControlProfile(); + static void initPersistFields(); + bool onAdd(); + + void incRefCount(); + void decRefCount(); +}; + +void RegisterGuiTypes(void); + +#endif //_GUITYPES_H diff --git a/gui/guiVoteCtrl.cc b/gui/guiVoteCtrl.cc new file mode 100644 index 0000000..ac970ff --- /dev/null +++ b/gui/guiVoteCtrl.cc @@ -0,0 +1,231 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" + +#include "GUI/guiVoteCtrl.h" + +GuiVoteCtrl::GuiVoteCtrl() +{ + mYesProgress = 0.0f; + mNoProgress = 0.0f; + mQuorum = 0.5f; + mPassHash = 0.25f; +} + +const char* GuiVoteCtrl::getScriptValue() +{ + return NULL; +} + +void GuiVoteCtrl::setScriptValue(const char *) +{ +} + +void GuiVoteCtrl::setQuorumValue(F32 value) +{ + if(!value) + mQuorum = 0.0f; + else + mQuorum = value; + + //validate the value + mQuorum = mClampF(mQuorum, 0.f, 1.f); +} + +void GuiVoteCtrl::setPassValue(F32 value) +{ + if(!value) + mPassHash = 0.0f; + else + mPassHash = value; + + //validate the value + mPassHash = mClampF(mPassHash, 0.f, 1.f); +} + +void GuiVoteCtrl::setYesValue(F32 value) +{ + if(!value) + mYesProgress = 0.0f; + else + mYesProgress = value; + + //validate the value + mYesProgress = mClampF(mYesProgress, 0.f, 1.f); +} + +void GuiVoteCtrl::setNoValue(F32 value) +{ + if(!value) + mNoProgress = 0.0f; + else + mNoProgress = value; + + //validate the value + mNoProgress = mClampF(mNoProgress, 0.f, 1.f); +} + +void GuiVoteCtrl::onPreRender() +{ +} + +void GuiVoteCtrl::onRender(Point2I offset, const RectI & /*updateRect*/, GuiControl * /*firstResponder*/) +{ + RectI ctrlRect(offset, mBounds.extent); + RectI yesProgressRect = ctrlRect; + RectI noProgressRect = ctrlRect; + S32 yesWidth; + S32 noWidth; + + ColorI fill; + + //draw the "Yes" progress + if(mYesProgress < 1) + yesWidth = (S32)((F32)mBounds.extent.x * (1 - mYesProgress)); + else + yesWidth = ctrlRect.extent.x; + + if(yesWidth > 0) + { + yesProgressRect.point.y += 10; + yesProgressRect.point.x = ctrlRect.point.x; + yesProgressRect.extent.y -= 20; + + if(mYesProgress < 1) + yesProgressRect.extent.x = ctrlRect.extent.x - yesWidth; + else + yesProgressRect.extent.x = ctrlRect.extent.x; + + fill.set(0, 255, 0, 255); + + if(mYesProgress >= mPassHash) + { + S32 time = (S32)Platform::getVirtualMilliseconds(); + F32 alpha = F32(time % 500) / 250.0f; + + if(alpha > 1) + alpha = 1.f - (alpha - 1.f); + + fill.alpha *= alpha; + } + + dglDrawRectFill(yesProgressRect, fill); + } + + // draw the "No" progress + noWidth = (S32)((F32)mBounds.extent.x * mNoProgress); + + fill.set(255, 0, 0, 255); + + if(noWidth > 0) + { + noProgressRect.point.y += 10; + noProgressRect.point.x = ctrlRect.point.x + (S32)((F32)mBounds.extent.x * mYesProgress); + noProgressRect.extent.y -= 20; + noProgressRect.extent.x = noWidth; + + if(mNoProgress >= ( 1 - mPassHash )) + { + S32 time = (S32)Platform::getVirtualMilliseconds(); + F32 alpha = F32(time % 500) / 250.0f; + + if(alpha > 1) + alpha = 1.f - (alpha - 1.f); + + fill.alpha *= alpha; + } + + dglDrawRectFill(noProgressRect, fill); + } +} + +// console functions +//////////////////////////////////////////////////////////////// +static const char* cGetQuorumValue(SimObject *obj, S32, const char **) +{ + GuiVoteCtrl *ctrl = static_cast(obj); + + F32 quorum = ctrl->getQuorumValue(); + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%f", quorum); + return ret; +} + +static const char* cGetPassValue(SimObject *obj, S32, const char **) +{ + GuiVoteCtrl *ctrl = static_cast(obj); + + F32 hash = ctrl->getPassValue(); + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%f", hash); + return ret; +} + +static const char* cGetYesValue(SimObject *obj, S32, const char **) +{ + GuiVoteCtrl *ctrl = static_cast(obj); + + F32 yesValue = ctrl->getYesValue(); + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%f", yesValue); + return ret; +} + +static const char* cGetNoValue(SimObject *obj, S32, const char **) +{ + GuiVoteCtrl *ctrl = static_cast(obj); + + F32 noValue = ctrl->getNoValue(); + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%f", noValue); + return ret; +} + +static void cSetQuorumValue(SimObject *obj, S32, const char **argv) +{ + GuiVoteCtrl *ctrl = static_cast(obj); + + ctrl->setQuorumValue(dAtof(argv[2])); +} + +static void cSetPassValue(SimObject *obj, S32, const char **argv) +{ + GuiVoteCtrl *ctrl = static_cast(obj); + + ctrl->setPassValue(dAtof(argv[2])); +} + +static void cSetYesValue(SimObject *obj, S32, const char **argv) +{ + GuiVoteCtrl *ctrl = static_cast(obj); + + ctrl->setYesValue(dAtof(argv[2])); +} + +static void cSetNoValue(SimObject *obj, S32, const char **argv) +{ + GuiVoteCtrl *ctrl = static_cast(obj); + + ctrl->setNoValue(dAtof(argv[2])); +} + +void GuiVoteCtrl::consoleInit() +{ + Parent::consoleInit(); + + Con::addCommand("GuiVoteCtrl", "setQuorumValue", cSetQuorumValue, "ctrl.setQuorumValue(value)", 3, 3); + Con::addCommand("GuiVoteCtrl", "setPassValue", cSetPassValue, "ctrl.setPassValue(value)", 3, 3); + Con::addCommand("GuiVoteCtrl", "setYesValue", cSetYesValue, "ctrl.setYesValue(value)", 3, 3); + Con::addCommand("GuiVoteCtrl", "setNoValue", cSetNoValue, "ctrl.setNoValue(value)", 3, 3); + Con::addCommand("GuiVoteCtrl", "getQuorumValue", cGetQuorumValue, "ctrl.getQuorumValue()", 2, 2); + Con::addCommand("GuiVoteCtrl", "getPassValue", cGetPassValue, "ctrl.getPassValue()", 2, 2); + Con::addCommand("GuiVoteCtrl", "getYesValue", cGetYesValue, "ctrl.getYesValue()", 2, 2); + Con::addCommand("GuiVoteCtrl", "getNoValue", cGetNoValue, "ctrl.getNoValue()", 2, 2); +} diff --git a/gui/guiVoteCtrl.h b/gui/guiVoteCtrl.h new file mode 100644 index 0000000..fb80875 --- /dev/null +++ b/gui/guiVoteCtrl.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIVOTECTRL_H_ +#define _GUIVOTECTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +class GuiVoteCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + + F32 mNoProgress; + F32 mYesProgress; + F32 mQuorum; + F32 mPassHash; + +public: + //creation methods + DECLARE_CONOBJECT(GuiVoteCtrl); + GuiVoteCtrl(); + + //console related methods + static void consoleInit(); + virtual const char *getScriptValue(); + virtual void setScriptValue(const char *value); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + + public: + void setPassValue(F32 value); + void setQuorumValue(F32 value); + void setYesValue(F32 value); + void setNoValue(F32 value); + + F32 getPassValue(); + F32 getQuorumValue(); + F32 getYesValue(); + F32 getNoValue(); + +}; + +inline F32 GuiVoteCtrl::getQuorumValue() +{ + return mQuorum; +} + +inline F32 GuiVoteCtrl::getPassValue() +{ + return mPassHash; +} + +inline F32 GuiVoteCtrl::getYesValue() +{ + return mYesProgress; +} + +inline F32 GuiVoteCtrl::getNoValue() +{ + return mNoProgress; +} + + +#endif diff --git a/gui/guiWindowCtrl.cc b/gui/guiWindowCtrl.cc new file mode 100644 index 0000000..6ab58dd --- /dev/null +++ b/gui/guiWindowCtrl.cc @@ -0,0 +1,582 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "dgl/dgl.h" +#include "GUI/guiCanvas.h" +#include "GUI/guiWindowCtrl.h" + +GuiWindowCtrl::GuiWindowCtrl(void) +{ + mResizeWidth = true; + mResizeHeight = true; + mCanMove = true; + mCanClose = true; + mCanMinimize = true; + mCanMaximize = true; + mTitleHeight = 20; + mResizeRightWidth = 10; + mResizeBottomHeight = 10; + + mCloseCommand = StringTable->insert(""); + + mMinimized = false; + mMaximized = false; + mMouseMovingWin = false; + mMouseResizeWidth = false; + mMouseResizeHeight = false; + mBounds.extent.set(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; +} + +void GuiWindowCtrl::initPersistFields() +{ + Parent::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)); +} + +void GuiWindowCtrl::consoleInit() +{ +} + +bool GuiWindowCtrl::isMinimized(S32 &index) +{ + index = mMinimizeIndex; + return mMinimized && mVisible; +} + +bool GuiWindowCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + //get the texture for the close, minimize, and maximize buttons + mTextureHandle = mProfile->mTextureHandle; + bool result = createBitmapArray(mTextureHandle.getBitmap(), mBitmapBounds, BmpStates, BmpCount); + AssertFatal(result, "Failed to create the bitmap array"); + + mTitleHeight = mBitmapBounds[BmpStates * BmpClose].extent.y + 6; + S32 buttonWidth = mBitmapBounds[BmpStates * BmpClose].extent.x; + mResizeRightWidth = mTitleHeight / 2; + mResizeBottomHeight = mTitleHeight / 2; + + //set the button coords + RectI closeRect(mBounds.extent.x - buttonWidth - 3, 2, mTitleHeight - 6, buttonWidth); + mCloseButton = closeRect; + + closeRect.point.x -= buttonWidth + 2; + mMaximizeButton = closeRect; + + closeRect.point.x -= buttonWidth + 2; + mMinimizeButton = closeRect; + + //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; +} + +GuiControl* GuiWindowCtrl::findHitControl(const Point2I &pt, S32 initialLayer) +{ + if (! mMinimized) + return Parent::findHitControl(pt, initialLayer); + else + return this; +} + +void GuiWindowCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + Parent::resize(newPosition, newExtent); + + //set the button coords + S32 buttonWidth = mBitmapBounds[BmpStates * BmpClose].extent.x; + RectI closeRect(mBounds.extent.x - buttonWidth - 3, 2, mTitleHeight - 6, buttonWidth); + mCloseButton = closeRect; + + closeRect.point.x -= buttonWidth + 2; + mMaximizeButton = closeRect; + + closeRect.point.x -= buttonWidth + 2; + mMinimizeButton = closeRect; +} + +void GuiWindowCtrl::onMouseDown(const GuiEvent &event) +{ + setUpdate(); + + mOrigBounds = mBounds; + mPressClose = false; + mPressMaximize = false; + mPressMinimize = false; + + mMouseDownPosition = event.mousePoint; + Point2I localPoint = globalToLocalCoord(event.mousePoint); + + //select this window - move it to the front, and set the first responder + selectWindow(); + + //if we clicked within the title bar + if (localPoint.y < mTitleHeight) + { + //if we clicked on the close button + if (mCloseButton.pointInRect(localPoint)) + { + mPressClose = mCanClose; + } + else if (mMaximizeButton.pointInRect(localPoint)) + { + mPressMaximize = mCanMaximize; + } + else if (mMinimizeButton.pointInRect(localPoint)) + { + mPressMinimize = mCanMinimize; + } + + //else we clicked within the title + else + { + mMouseMovingWin = mCanMove; + mMouseResizeWidth = false; + mMouseResizeHeight = false; + } + } + else + { + mMouseMovingWin = false; + + //see if we clicked on the right edge + if (localPoint.x > mBounds.extent.x - mResizeRightWidth) + { + mMouseResizeWidth = true; + } + + //see if we clicked on the bottom edge (as well) + if (localPoint.y > mBounds.extent.y - mResizeBottomHeight) + { + mMouseResizeHeight = true; + } + } + if (mMouseMovingWin || mMouseResizeWidth || mMouseResizeHeight || + mPressClose || mPressMaximize || mPressMinimize) + { + mouseLock(); + } + else + { + GuiControl *ctrl = findHitControl(event.mousePoint); + 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 = mBounds.point; + Point2I newExtent = mBounds.extent; + bool update = false; + if (mMouseMovingWin && parent) + { + newPosition.x = getMax(0, getMin(parent->mBounds.extent.x - mBounds.extent.x, mOrigBounds.point.x + deltaMousePosition.x)); + newPosition.y = getMax(0, getMin(parent->mBounds.extent.y - mBounds.extent.y, mOrigBounds.point.y + deltaMousePosition.y)); + update = true; + } + else + { + if (mMouseResizeWidth && parent) + { + newExtent.x = getMax(0, getMax(mMinSize.x, getMin(parent->mBounds.extent.x, mOrigBounds.extent.x + deltaMousePosition.x))); + update = true; + } + if (mMouseResizeHeight && parent) + { + newExtent.y = getMax(0, getMax(mMinSize.y, getMin(parent->mBounds.extent.y, mOrigBounds.extent.y + deltaMousePosition.y))); + update = true; + } + } + if (update) + { + Point2I pos = parent->localToGlobalCoord(mBounds.point); + root->addUpdateRegion(pos, mBounds.extent); + resize(newPosition, newExtent); + } +} + +void GuiWindowCtrl::onMouseUp(const GuiEvent &event) +{ + event; + mouseUnlock(); + + mMouseMovingWin = false; + mMouseResizeWidth = false; + mMouseResizeHeight = false; + + GuiControl *parent = getParent(); + if (! parent) + return; + + //see if we take an action + Point2I localPoint = globalToLocalCoord(event.mousePoint); + if (mPressClose && mCloseButton.pointInRect(localPoint)) + { + Con::evaluate(mCloseCommand); + } + else if (mPressMaximize) + { + if (mMaximized) + { + //resize to the previous position and extent, bounded by the parent + resize(Point2I(getMax(0, getMin(parent->mBounds.extent.x - mStandardBounds.extent.x, mStandardBounds.point.x)), + getMax(0, getMin(parent->mBounds.extent.y - 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 = mBounds; + } + else + { + mMinimized = false; + } + + //resize to fit the parent + resize(Point2I(0, 0), parent->mBounds.extent); + + //set the flag + mMaximized = true; + } + } + else if (mPressMinimize) + { + if (mMinimized) + { + //resize to the previous position and extent, bounded by the parent + resize(Point2I(getMax(0, getMin(parent->mBounds.extent.x - mStandardBounds.extent.x, mStandardBounds.point.x)), + getMax(0, getMin(parent->mBounds.extent.y - mStandardBounds.extent.y, mStandardBounds.point.y))), + mStandardBounds.extent); + //set the flag + mMinimized = false; + } + else + { + if (parent->mBounds.extent.x < 100 || parent->mBounds.extent.y < mTitleHeight + 3) + return; + + //only save the position if we're not maximized + if (! mMaximized) + { + mStandardBounds = mBounds; + } + 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->mBounds.extent.x / newExtent.x + 2)); + + //find the new "mini position" + Point2I newPosition; + newPosition.x = (count % numAcross) * (newExtent.x + 2) + 2; + newPosition.y = parent->mBounds.extent.y - (((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; + } + } + +} + +GuiControl *GuiWindowCtrl::findNextTabable(GuiControl *curResponder, bool firstCall) +{ + //set the global if this is the first call (directly from the canvas) + if (firstCall) + { + GuiControl::gCurResponder = 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::gPrevResponder = 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_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); +} + +void GuiWindowCtrl::selectWindow(void) +{ + //first make sure this window is the front most of its siblings + GuiControl *parent = getParent(); + if (parent) + { + parent->bringObjectToFront(this); + } + + //also set the first responder to be the one within this window + setFirstResponder(mFirstResponder); +} + +void GuiWindowCtrl::drawWinRect(const RectI &myRect) +{ + Point2I bl = myRect.point; + Point2I tr; + tr.x = myRect.point.x + myRect.extent.x - 1; + tr.y = myRect.point.y + myRect.extent.y - 1; + dglDrawRectFill(myRect, mProfile->mFillColor); + dglDrawLine(Point2I(bl.x + 1, tr.y), Point2I(bl.x + 1, bl.y), ColorI(255, 255, 255)); + dglDrawLine(Point2I(bl.x, tr.y + 1), Point2I(tr.x, tr.y + 1), ColorI(255, 255, 255)); + dglDrawRect(myRect, ColorI(0, 0, 0)); +} + +void GuiWindowCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + //draw the outline + RectI winRect; + winRect.point = offset; + winRect.extent = mBounds.extent; + + if (mProfile->mOpaque) + { + drawWinRect(winRect); + + //title bar + ColorI titleColor = (! firstResponder || ControlIsChild(firstResponder)) ? + mProfile->mFillColorHL : mProfile->mFillColorNA; + RectI titleBarRect = winRect; + titleBarRect.extent.y = mTitleHeight; + + dglDrawRectFill(titleBarRect, titleColor); + } + + //outline the window + if (mProfile->mBorder) + dglDrawRect(winRect, mProfile->mBorderColor); + + //draw the title + dglSetBitmapModulation(mProfile->mFontColor); + S32 fontHeight = mFont->getHeight(); + dglDrawText(mFont, Point2I(offset.x + 4, offset.y + ((mTitleHeight - fontHeight) / 2)), mText); + + GuiCanvas *root = getRoot(); + AssertFatal(root, "Unable to get the root Canvas."); + Point2I localPoint = globalToLocalCoord(root->getCursorPos()); + + //draw the close button + Point2I tempUL; + Point2I tempLR; + S32 bmp = BmpStates * BmpClose; + if (! mCanClose) + bmp += BmpDisabled; + else if (mCloseButton.pointInRect(localPoint) && root->mouseButtonDown()) + bmp += BmpHilite; + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, offset + mCloseButton.point, mBitmapBounds[bmp]); + + //draw the maximize button + if (mMaximized) + bmp = BmpStates * BmpNormal; + else + bmp = BmpStates * BmpMaximize; + if (! mCanMaximize) + bmp += BmpDisabled; + else if (mMaximizeButton.pointInRect(localPoint) && root->mouseButtonDown()) + bmp += BmpHilite; + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, offset + mMaximizeButton.point, mBitmapBounds[bmp]); + + //draw the minimize button + if (mMinimized) + bmp = BmpStates * BmpNormal; + else + bmp = BmpStates * BmpMinimize; + if (! mCanMinimize) + bmp += BmpDisabled; + else if (mMinimizeButton.pointInRect(localPoint) && root->mouseButtonDown()) + bmp += BmpHilite; + dglClearBitmapModulation(); + dglDrawBitmapSR(mTextureHandle, offset + mMinimizeButton.point, mBitmapBounds[bmp]); + + if (! mMinimized) + { + //render the children + renderChildControls(offset, updateRect, firstResponder); + } +} diff --git a/gui/guiWindowCtrl.h b/gui/guiWindowCtrl.h new file mode 100644 index 0000000..edb5e02 --- /dev/null +++ b/gui/guiWindowCtrl.h @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIWINDOWCTRL_H_ +#define _GUIWINDOWCTRL_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "GUI/guiTextCtrl.h" +#endif + +class GuiWindowCtrl : public GuiTextCtrl +{ + private: + typedef GuiTextCtrl Parent; + + bool mResizeWidth; + bool mResizeHeight; + bool mCanMove; + bool mCanClose; + bool mCanMinimize; + bool mCanMaximize; + bool mPressClose; + bool mPressMinimize; + bool mPressMaximize; + Point2I mMinSize; + + StringTableEntry mCloseCommand; + + S32 mTitleHeight; + S32 mResizeRightWidth; + S32 mResizeBottomHeight; + + 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; + + protected: + enum BitmapIndices + { + BmpClose, + BmpMaximize, + BmpNormal, + BmpMinimize, + + BmpCount + }; + + enum BitmapStates + { + BmpDefault = 0, + BmpHilite, + BmpDisabled, + + BmpStates + }; + RectI mBitmapBounds[BmpStates * BmpCount]; //bmp is [3*n], bmpHL is [3*n + 1], bmpNA is [3*n + 2] + TextureHandle mTextureHandle; + + + void drawWinRect(const RectI &myRect); + + public: + GuiWindowCtrl(); + DECLARE_CONOBJECT(GuiWindowCtrl); + static void initPersistFields(); + static void consoleInit(); + + bool onWake(); + + bool isMinimized(S32 &index); + + void setFont(S32 fntTag); + + GuiControl* findHitControl(const Point2I &pt, S32 initialLayer = -1); + void resize(const Point2I &newPosition, const Point2I &newExtent); + + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + 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, GuiControl *firstResponder); +}; + +#endif //_GUI_WINDOW_CTRL_H diff --git a/gui/messageVector.cc b/gui/messageVector.cc new file mode 100644 index 0000000..f68568f --- /dev/null +++ b/gui/messageVector.cc @@ -0,0 +1,408 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/messageVector.h" +#include "Core/fileObject.h" + +IMPLEMENT_CONOBJECT(MessageVector); + +//-------------------------------------- Console functions +namespace { + +void cMVClear(SimObject *obj, S32, const char **) +{ + MessageVector* pMV = static_cast(obj); + + pMV->clear(); +} + +void cMVPushBackLine(SimObject* obj, S32 argc, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + + U32 tag = 0; + if (argc == 4) + tag = dAtoi(argv[3]); + + pMV->pushBackLine(argv[2], tag); +} + +bool cMVPopBackLine(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + + if (pMV->getNumLines() == 0) { + Con::errorf(ConsoleLogEntry::General, "MessageVector::popBackLine(): underflow"); + return false; + } + + pMV->popBackLine(); + return true; +} + +void cMVPushFrontLine(SimObject* obj, S32 argc, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + + U32 tag = 0; + if (argc == 4) + tag = dAtoi(argv[3]); + + pMV->pushFrontLine(argv[2], tag); +} + +bool cMVPopFrontLine(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + + if (pMV->getNumLines() == 0) { + Con::errorf(ConsoleLogEntry::General, "MessageVector::popFrontLine(): underflow"); + return false; + } + + pMV->popFrontLine(); + return true; +} + +bool cMVInsertLine(SimObject* obj, S32 argc, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + + U32 pos = U32(dAtoi(argv[2])); + if (pos > pMV->getNumLines()) + return false; + + S32 tag = 0; + if (argc == 5) + tag = dAtoi(argv[4]); + + pMV->insertLine(pos, argv[3], tag); + return true; +} + +bool cMVDeleteLine(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + + U32 pos = U32(dAtoi(argv[2])); + if (pos >= pMV->getNumLines()) + return false; + + pMV->deleteLine(pos); + return true; +} + +void cMVDump(SimObject* obj, S32 argc, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + if ( argc == 4 ) + pMV->dump( argv[2], argv[3] ); + else + pMV->dump( argv[2] ); +} + +S32 cMVGetNumLines(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + + return pMV->getNumLines(); +} + +const char *cMVGetLineTextByTag(SimObject *obj, S32, const char **argv) +{ + MessageVector* pMV = static_cast(obj); + U32 tag = dAtoi(argv[2]); + + for(U32 i = 0; i < pMV->getNumLines(); i++) + if(pMV->getLine(i).messageTag == tag) + return pMV->getLine(i).message; + return ""; +} + +S32 cMVGetLineIndexByTag(SimObject *obj, S32, const char **argv) +{ + MessageVector* pMV = static_cast(obj); + U32 tag = dAtoi(argv[2]); + + for(U32 i = 0; i < pMV->getNumLines(); i++) + if(pMV->getLine(i).messageTag == tag) + return i; + return -1; +} + +const char* cMVGetLineText(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + + U32 pos = U32(dAtoi(argv[2])); + if (pos >= pMV->getNumLines()) { + Con::errorf(ConsoleLogEntry::General, "MessageVector::getLineText(con): out of bounds line"); + return ""; + } + + return pMV->getLine(pos).message; +} + +S32 cMVGetLineTag(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-MessageVector get here?"); + MessageVector* pMV = static_cast(obj); + + U32 pos = U32(dAtoi(argv[2])); + if (pos >= pMV->getNumLines()) { + Con::errorf(ConsoleLogEntry::General, "MessageVector::getLineTag(con): out of bounds line"); + return 0; + } + + return pMV->getLine(pos).messageTag; +} + +} // namespace {} + + +//-------------------------------------------------------------------------- +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(); +} + + +//-------------------------------------------------------------------------- +void MessageVector::consoleInit() +{ + Con::addCommand("MessageVector", "pushBackLine", cMVPushBackLine, "[MessageVector].pushBackLine(\"Message\"[, Tag=0])", 3, 4); + Con::addCommand("MessageVector", "popBackLine", cMVPopBackLine, "[MessageVector].popBackLine()", 2, 2); + Con::addCommand("MessageVector", "pushFrontLine", cMVPushFrontLine, "[MessageVector].pushFrontLine(\"Message\"[, Tag=0])", 3, 4); + Con::addCommand("MessageVector", "popFrontLine", cMVPopFrontLine, "[MessageVector].popFrontLine()", 2, 2); + + Con::addCommand("MessageVector", "insertLine", cMVInsertLine, "[MessageVector].insertLine(InsertPos, \"Message\"[, Tag=0])", 4, 5); + Con::addCommand("MessageVector", "deleteLine", cMVDeleteLine, "[MessageVector].deleteLine(DeletePos)", 3, 3); + Con::addCommand("MessageVector", "clear", cMVClear, "[MessageVector].clear()", 2, 2); + + Con::addCommand("MessageVector", "dump", cMVDump, "[MessageVector].dump(filename{, header})", 3, 4); + + Con::addCommand("MessageVector", "getNumLines", cMVGetNumLines, "[MessageVector].getNumLines()", 2, 2); + Con::addCommand("MessageVector", "getLineText", cMVGetLineText, "[MessageVector].getLineText(Line)", 3, 3); + Con::addCommand("MessageVector", "getLineTag", cMVGetLineTag, "[MessageVector].getLineTag(Line)", 3, 3); + Con::addCommand("MessageVector", "getLineTextByTag", cMVGetLineTextByTag, "[MessageVector].getLineTextByTag(Tag)", 3, 3); + Con::addCommand("MessageVector", "getLineIndexByTag", cMVGetLineIndexByTag, "[MessageVector].getLineIndexByTag(Tag)", 3, 3); +} + + +//-------------------------------------------------------------------------- +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, U32 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(U32 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/messageVector.h b/gui/messageVector.h new file mode 100644 index 0000000..c839968 --- /dev/null +++ b/gui/messageVector.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MESSAGEVECTOR_H_ +#define _MESSAGEVECTOR_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +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)(const U32 spectatorKey, + const MessageCode code, + const U32 argument); + + void registerSpectator(SpectatorCallback, U32 spectatorKey); + void unregisterSpectator(U32 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; + U32 key; + }; + + Vector mMessageLines; + + Vector mSpectators; + void spectatorMessage(MessageCode, const U32 arg); + + public: + DECLARE_CONOBJECT(MessageVector); + static void initPersistFields(); + static void consoleInit(); +}; + + +//-------------------------------------------------------------------------- +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/hud/hudBarBaseCtrl.cc b/hud/hudBarBaseCtrl.cc new file mode 100644 index 0000000..224b02d --- /dev/null +++ b/hud/hudBarBaseCtrl.cc @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hudBarBaseCtrl.h" + +//=========================================================================== +// HudBarBaseCtrl Implementation +//=========================================================================== + +IMPLEMENT_CONOBJECT( HudBarBaseCtrl ); + +//--------------------------------------------------------------------------- +// Constructor +//--------------------------------------------------------------------------- +HudBarBaseCtrl::HudBarBaseCtrl() { + mPulseRate = 500; + mPulseThreshold = 0.3f; + mVerticalFill = false; + mPulse = false; + mDisplayMounted = false; +} + +//--------------------------------------------------------------------------- +// onWake +// +// This method is called when this HUD object is revived from a waiting state +// +// Return: True if successfuly awakened +//--------------------------------------------------------------------------- +bool HudBarBaseCtrl::onWake() { + if( !Parent::onWake() ) + return( false ); + return( true ); +} + +//--------------------------------------------------------------------------- +// onSleep +// +// This method is used to put this HUD object in a waiting state +//--------------------------------------------------------------------------- +void HudBarBaseCtrl::onSleep() { + Parent::onSleep(); +} + +//--------------------------------------------------------------------------- +// onRender +// +// This method is used to tell the bar to render itself +// +// offset - A offset value for where to render the bar +//--------------------------------------------------------------------------- +void HudBarBaseCtrl::onRender( Point2I offset, const RectI & /*updateRect*/, GuiControl * /*firstResponder*/ ) { + + // Why is this code here? + /* + GameConnection *con = GameConnection::getServerConnection(); + if(!con) + return; + + ShapeBase * obj = con->getControlObject(); + + if( !obj ) + return; + */ + + F32 val = getValue(); + + RectI rect( offset + mSubRegion.point, mSubRegion.extent ); + rect.extent.x = (S32)( rect.extent.x * val ); + + mFillColor.alpha = mOpacity; + ColorI fill; + getBarColor( fill ); + fill.alpha = mOpacity * 255; + + if( mPulse && ( mPulseRate != 0 ) ) { + S32 time = (S32)Platform::getVirtualMilliseconds(); + F32 alpha = F32( time % mPulseRate ) / F32( mPulseRate / 2 ); + + if( alpha > 1 ) + alpha = 1.f - ( alpha - 1.f ); + + fill.alpha *= alpha; + fill.alpha *= 255; + } + + dglDrawRectFill( rect, fill ); +} + +//--------------------------------------------------------------------------- +// initPersistFields +// +// Registers datamembers of this object with the game +//--------------------------------------------------------------------------- +void HudBarBaseCtrl::initPersistFields() { + Parent::initPersistFields(); + + addField( "displayMounted", TypeBool, Offset( mDisplayMounted, HudBarBaseCtrl ) ); + addField( "pulseRate", TypeS32, Offset( mPulseRate, HudBarBaseCtrl ) ); + addField( "pulseThreshold", TypeF32, Offset( mPulseThreshold, HudBarBaseCtrl ) ); + addField( "verticalFill", TypeBool, Offset( mVerticalFill, HudBarBaseCtrl ) ); + addField( "subRegion", TypeRectI, Offset( mSubRegion, HudBarBaseCtrl ) ); +} + +// hudBarBaseCtrl.cc \ No newline at end of file diff --git a/hud/hudBarBaseCtrl.h b/hud/hudBarBaseCtrl.h new file mode 100644 index 0000000..f2ef550 --- /dev/null +++ b/hud/hudBarBaseCtrl.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudCtrl.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" + +//=========================================================================== +// CLASS: HudBarBaseCtrl +// +// This HUD object is a horizontal percentile bar +//=========================================================================== + +class HudBarBaseCtrl : public HudCtrl { + private: + typedef HudCtrl Parent; + + RectI mSubRegion; + + protected: + //------------------------------------------------------------------ + // getValue + // + // Default method that is intended to be over-ridden, returns + // the current value of the bar in a percentile format + // + // Return: Value of the bar as a percentile + //------------------------------------------------------------------ + virtual F32 getValue() { + return 0.0f; + } + + //------------------------------------------------------------------ + // getBarColor + // + // This method stores the fill color of the bar in a provided variable + // + // col - This color variable will be set to the current fill color of the bar + //------------------------------------------------------------------ + virtual void getBarColor( ColorI &col ) { + col.set( mFillColor.red * 255, mFillColor.green * 255, mFillColor.blue * 255 ); + } + + public: + + HudBarBaseCtrl(); + + // GuiControl + bool onWake(); + void onSleep(); + void onRender( Point2I, const RectI &, GuiControl * ); + + S32 mPulseRate; + F32 mPulseThreshold; + bool mVerticalFill; + bool mPulse; + bool mDisplayMounted; + + static void initPersistFields(); + + DECLARE_CONOBJECT(HudBarBaseCtrl); +}; diff --git a/hud/hudBarDisplayCtrl.cc b/hud/hudBarDisplayCtrl.cc new file mode 100644 index 0000000..9a03b43 --- /dev/null +++ b/hud/hudBarDisplayCtrl.cc @@ -0,0 +1,170 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudBarDisplayCtrl.h" +#include "hud/hudGLEx.h" + +IMPLEMENT_CONOBJECT( HudBarDisplayCtrl ); + +/** + * Constructor + */ +HudBarDisplayCtrl::HudBarDisplayCtrl() { + mPulseRate = 500; + mPulseThreshold = 0.3f; + mPulse = false; + mHorizontalBar = true; + mDrawFromOrigin = true; + mShowPercentage = false; + mDisplayMounted = false; + mGradientFill = true; + mValue = 0.2f; +} + +/** + * Default method that is intended to be over-ridden, returns + * the current value of the bar in a percentile format + * + * @return Value of the bar as a percentile + */ +F32 HudBarDisplayCtrl::getValue() { + return mValue; +} + +/** + * This method returns the color of the bar, this allows for + * the changing of the bar color depending upon it's value + * and things like that. + * + * @return Bar color + */ +ColorI HudBarDisplayCtrl::getBarColor() { + return ColorI( 0, 255, 0 ); +} + +/** + * This method returns the secondary color of the bar, this allows for + * the changing of the bar color depending upon it's value + * and things like that. + * + * @return Secondary bar color + */ +ColorI HudBarDisplayCtrl::getSecondBarColor() { + return ColorI( 255, 0, 0 ); +} + +/** + * Method called to render the HUD object + * + * @param offset Basically the corner to begin drawing at + * @param updateRect The rectangle that this object updates + * @param firstResponder ? + */ +void HudBarDisplayCtrl::onRender( Point2I offset, + const RectI &updateRect, + GuiControl * firstResponder ) { + mFillColor.alpha = mOpacity; + + F32 val = getValue(); + if( val > 1.0f ) + val = 1.0f; + else if( val < 0.0f ) + val = 0.0f; + + ColorI fill, fill2; + fill = getBarColor(); + fill2 = getBarColor(); + + fill.alpha = mOpacity * 255; + fill2.alpha = mOpacity * 255; + + RectI rect( updateRect ); + + S32 modVal = ( mDrawFromOrigin ? -1 : 1 ); + + if( mHorizontalBar ) + if( mDrawFromOrigin ) + rect.extent.x *= val; + else { + rect.point.x += rect.extent.x * ( 1.0f - val ); + rect.extent.x *= val; + } + else + if( mDrawFromOrigin ) + rect.extent.y *= val; + else { + rect.point.y += rect.extent.y * ( 1.0f - val ); + rect.extent.y *= val; + rect.extent.y++; // Wierd, yeah, this is just to fix it being off by one pix from bottom + } + + if( mPulse && ( mPulseRate != 0 ) ) { + S32 time = (S32)Platform::getVirtualMilliseconds(); + F32 alpha = F32( time % mPulseRate ) / F32( mPulseRate / 2 ); + + if( alpha > 1 ) + alpha = 1.f - ( alpha - 1.f ); + + fill.alpha *= alpha; + fill.alpha *= 255; + fill2.alpha *= alpha; + fill2.alpha *= 255; + } + + if( mShowFill ) + dglDrawRectFill( updateRect, mFillColor ); + + if( mGradientFill ) { + fill2 = getSecondBarColor(); + fill2.alpha = mOpacity * 255; + } + + if( !mHorizontalBar ) + if( !mDrawFromOrigin ) + dglDrawRectFill( rect, fill, fill2, fill, fill2 ); + else + dglDrawRectFill( rect, fill2, fill, fill2, fill ); + else + if( !mDrawFromOrigin ) + dglDrawRectFill( rect, fill, fill, fill2, fill2 ); + else + dglDrawRectFill( rect, fill2, fill2, fill, fill ); + + if( mShowPercentage ) { + char buf[256]; + S32 per = (S32)mFloor( val * 100.0f ); + + dSprintf( buf, sizeof( buf ), "%d%%", per ); + + offset.x += ( updateRect.extent.x / 2 ) - ( mProfile->mFont->getStrWidth( buf ) / 2 ); + offset.y += ( updateRect.extent.y / 2 ) - ( mProfile->mFont->getHeight() / 2 ); + + drawText( offset, buf ); + } + + if( mShowFrame ) + dglDrawRect( updateRect, mFrameColor ); +} + +/** + * Registers console-modifyable datamembers + */ +void HudBarDisplayCtrl::initPersistFields() { + Parent::initPersistFields(); + + addField( "pulse", TypeBool, Offset( mPulse, HudBarDisplayCtrl ) ); + addField( "horizontalBar", TypeBool, Offset( mHorizontalBar, HudBarDisplayCtrl ) ); + addField( "drawFromOrigin", TypeBool, Offset( mDrawFromOrigin, HudBarDisplayCtrl ) ); + addField( "showPercentage", TypeBool, Offset( mShowPercentage, HudBarDisplayCtrl ) ); + addField( "displayMounted", TypeBool, Offset( mDisplayMounted, HudBarDisplayCtrl ) ); + addField( "gradientFill", TypeBool, Offset( mGradientFill, HudBarDisplayCtrl ) ); + addField( "pulseRate", TypeS32, Offset( mPulseRate, HudBarDisplayCtrl ) ); + addField( "pulseThreshold", TypeF32, Offset( mPulseThreshold, HudBarDisplayCtrl ) ); + addField( "value", TypeF32, Offset( mValue, HudBarDisplayCtrl ) ); +} + +// HudBarDisplayCtrl.cc \ No newline at end of file diff --git a/hud/hudBarDisplayCtrl.h b/hud/hudBarDisplayCtrl.h new file mode 100644 index 0000000..6892bda --- /dev/null +++ b/hud/hudBarDisplayCtrl.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HUDBARDISPLAYCTRL_H_ +#define _HUDBARDISPLAYCTRL_H_ + +#include "hud/hudObject.h" +#include "console/consoleTypes.h" + +/** + * This is a HUD object that displays data in horizontal or vertical + * percentage bars + */ +class HudBarDisplayCtrl : public HudObject { + private: + typedef HudObject Parent; + + protected: + virtual F32 getValue(); + virtual ColorI getBarColor(); + virtual ColorI getSecondBarColor(); + + public: + + HudBarDisplayCtrl(); + + void onRender( Point2I, const RectI &, GuiControl * ); + + S32 mPulseRate; + F32 mPulseThreshold; + bool mPulse; + bool mHorizontalBar; + bool mDrawFromOrigin; + bool mShowPercentage; + bool mDisplayMounted; + bool mGradientFill; + F32 mValue; + + static void initPersistFields(); + + DECLARE_CONOBJECT( HudBarDisplayCtrl ); +}; + +#endif + +// HudBarDisplayCtrl.h \ No newline at end of file diff --git a/hud/hudBezierDisplayCtrl.cc b/hud/hudBezierDisplayCtrl.cc new file mode 100644 index 0000000..926b181 --- /dev/null +++ b/hud/hudBezierDisplayCtrl.cc @@ -0,0 +1,179 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudBezierDisplayCtrl.h" +#include "hud/hudGLEx.h" + +IMPLEMENT_CONOBJECT( HudBezierDisplayCtrl ); + +HudBezierDisplayCtrl::HudBezierDisplayCtrl() { + mCurve = NULL; + + mPulseRate = 500; + mPulseThreshold = 0.3f; + mPulse = false; + mDrawFromOrigin = true; + mShowPercentage = false; + mDisplayMounted = false; + mGradientFill = true; + mValue = 0.2f; + + mOffset.set( 0, 0 ); + mTextOffset.set( 0, 0 ); + mScaleValue = 0.4f; +} + +/** + * Destructor + */ +HudBezierDisplayCtrl::~HudBezierDisplayCtrl() { + if( mCurve != NULL ) + delete mCurve; +} + +/** + * Default method that is intended to be over-ridden, returns + * the current value of the bar in a percentile format + * + * @return Value of the bar as a percentile + */ +F32 HudBezierDisplayCtrl::getValue() { + return mValue; +} + +/** + * This method returns the color of the bar, this allows for + * the changing of the bar color depending upon it's value + * and things like that. + * + * @return Bar color + */ +ColorI HudBezierDisplayCtrl::getBarColor() { + return ColorI( 0, 255, 0 ); +} + +/** + * This method returns the secondary color of the bar, this allows for + * the changing of the bar color depending upon it's value + * and things like that. + * + * @return Secondary bar color + */ +ColorI HudBezierDisplayCtrl::getSecondBarColor() { + return ColorI( 255, 0, 0 ); +} + +/** + * This method gets executed whenever the control is resized + * + * @param newPosition The new position of the top left corner of the control + * @param newExtent The new extent of the control + */ +void HudBezierDisplayCtrl::resize( const Point2I &newPosition, const Point2I &newExtent ) { + Parent::resize( newPosition, newExtent ); + + Point2F ctrlPoints[4]; + Point2I upperL, lowerR; + + upperL.set( mBounds.point.x, mBounds.point.y ); + lowerR.set( mBounds.point.x + mBounds.extent.x - 1, mBounds.point.y + mBounds.extent.y - 1 ); + + ctrlPoints[0].set( upperL.x, lowerR.y ); + ctrlPoints[1].set( upperL.x, upperL.y ); + ctrlPoints[2].set( lowerR.x, upperL.y ); + ctrlPoints[3].set( lowerR.x, lowerR.y + 10 ); + + if( mCurve == NULL ) + mCurve = new Bezier2D( ctrlPoints, 4, 49 ); + else + mCurve->setControlPoints( ctrlPoints, 4 ); +} + +/** + * Method called to render the HUD object + * + * @param offset Basically the corner to begin drawing at + * @param updateRect The rectangle that this object updates + * @param firstResponder ? + */ +void HudBezierDisplayCtrl::onRender( Point2I offset, const RectI &updateRect, GuiControl *firstResponder ) { + if( mCurve != NULL ) { + mFillColor.alpha = mOpacity; + + ColorI fill = getBarColor(); + ColorI fill2 = getSecondBarColor(); + + if( mPulse && ( mPulseRate != 0 ) ) { + S32 time = (S32)Platform::getVirtualMilliseconds(); + F32 alpha = F32( time % mPulseRate ) / F32( mPulseRate / 2 ); + + if( alpha > 1 ) + alpha = 1.f - ( alpha - 1.f ); + + fill.alpha *= alpha; + fill.alpha *= 255; + fill2.alpha *= alpha; + fill2.alpha *= 255; + } + + if( mShowFill ) + dglDrawBezierBand( mCurve, mOffset, mScaleValue, mFillColor ); + if( mGradientFill ) + dglDrawBezierBand( mCurve, mOffset, mScaleValue, fill, fill2, getValue(), !mDrawFromOrigin ); + else + dglDrawBezierBand( mCurve, mOffset, mScaleValue, fill, getValue(), !mDrawFromOrigin ); + + if( mShowFrame ) { + dglDrawBezier( mCurve, mFrameColor ); + dglDrawBezier( mCurve->getScaledCurve( mScaleValue ), mFrameColor, mOffset ); + + Point2I pt1( mCurve->getScaledCurve( mScaleValue )->getCurvePoints()[0].x + mOffset.x, + mCurve->getScaledCurve( mScaleValue )->getCurvePoints()[0].y - mOffset.y ); + Point2I pt2( mCurve->getCurvePoints()[0].x, mCurve->getCurvePoints()[0].y ); + dglDrawLine( pt1, pt2, mFrameColor ); + + pt1.set( mCurve->getScaledCurve( mScaleValue )->getCurvePoints()[mCurve->getNumCurvePoints() - 1].x + mOffset.x, + mCurve->getScaledCurve( mScaleValue )->getCurvePoints()[mCurve->getNumCurvePoints() - 1].y - mOffset.y ); + pt2.set( mCurve->getCurvePoints()[mCurve->getNumCurvePoints() - 1].x, + mCurve->getCurvePoints()[mCurve->getNumCurvePoints() - 1].y ); + dglDrawLine( pt1, pt2, mFrameColor ); + } + + if( mShowPercentage ) { + char buf[256]; + S32 per = (S32)mFloor( getValue() * 100.0f ); + + dSprintf( buf, sizeof( buf ), "%d%%", per ); + + drawText( Point2I( offset.x + mTextOffset.x - ( mProfile->mFont->getStrWidth( buf ) / 2 ), + offset.y + mTextOffset.y - ( mProfile->mFont->getHeight() / 2 ) ), buf ); + } + } +} + +/** + * Registers console-modifyable datamembers + */ +void HudBezierDisplayCtrl::initPersistFields() { + Parent::initPersistFields(); + + addField( "pulse", TypeBool, Offset( mPulse, HudBezierDisplayCtrl ) ); + addField( "drawFromOrigin", TypeBool, Offset( mDrawFromOrigin, HudBezierDisplayCtrl ) ); + addField( "showPercentage", TypeBool, Offset( mShowPercentage, HudBezierDisplayCtrl ) ); + addField( "displayMounted", TypeBool, Offset( mDisplayMounted, HudBezierDisplayCtrl ) ); + addField( "gradientFill", TypeBool, Offset( mGradientFill, HudBezierDisplayCtrl ) ); + addField( "pulseRate", TypeS32, Offset( mPulseRate, HudBezierDisplayCtrl ) ); + addField( "pulseThreshold", TypeF32, Offset( mPulseThreshold, HudBezierDisplayCtrl ) ); + addField( "value", TypeF32, Offset( mValue, HudBezierDisplayCtrl ) ); + addField( "offsetX", TypeF32, Offset( mOffset.x, HudBezierDisplayCtrl ) ); + addField( "offsetY", TypeF32, Offset( mOffset.y, HudBezierDisplayCtrl ) ); + addField( "textOffsetX", TypeF32, Offset( mTextOffset.x, HudBezierDisplayCtrl ) ); + addField( "textOffsetY", TypeF32, Offset( mTextOffset.y, HudBezierDisplayCtrl ) ); + addField( "scaleValue", TypeF32, Offset( mScaleValue, HudBezierDisplayCtrl ) ); +} + +// hudBezierDisplayCtrl.cc \ No newline at end of file diff --git a/hud/hudBezierDisplayCtrl.h b/hud/hudBezierDisplayCtrl.h new file mode 100644 index 0000000..5809984 --- /dev/null +++ b/hud/hudBezierDisplayCtrl.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HUDBEZIERDISPLAYCTRL_H_ +#define _HUDBEZIERDISPLAYCTRL_H_ + +#include "hud/hudObject.h" +#include "math/mBezier2D.h" +#include "console/consoleTypes.h" + +class HudBezierDisplayCtrl : public HudObject { + private: + typedef HudObject Parent; + + Bezier2D *mCurve; + + protected: + virtual F32 getValue(); + virtual ColorI getBarColor(); + virtual ColorI getSecondBarColor(); + + public: + HudBezierDisplayCtrl(); + ~HudBezierDisplayCtrl(); + void resize( const Point2I &newPosition, const Point2I &newExtent ); + void onRender( Point2I, const RectI &, GuiControl * ); + + S32 mPulseRate; + F32 mPulseThreshold; + bool mPulse; + bool mDrawFromOrigin; + bool mShowPercentage; + bool mDisplayMounted; + bool mGradientFill; + F32 mValue; + Point2F mOffset, mTextOffset; + F32 mScaleValue; + + static void initPersistFields(); + + DECLARE_CONOBJECT( HudBezierDisplayCtrl ); +}; + +#endif + +// hudBezierDisplayCtrl.h \ No newline at end of file diff --git a/hud/hudBitmapCtrl.cc b/hud/hudBitmapCtrl.cc new file mode 100644 index 0000000..f04304a --- /dev/null +++ b/hud/hudBitmapCtrl.cc @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudBitmapCtrl.h" + +IMPLEMENT_CONOBJECT(HudBitmapCtrl); + +HudBitmapCtrl::HudBitmapCtrl() +{ + mBitmapHandle = 0; + + // + mBitmap = 0; + mAutoResize = false; + mAutoCenter = false; + mFlipVert = false; + mFlipHorz = false; +} + +void HudBitmapCtrl::setBitmap(const char * bitmap) +{ + mBitmap = StringTable->insert(bitmap); + mBitmapHandle = TextureHandle(mBitmap, BitmapTexture); +} + +//--------------------------------------------------------------------------- + +void HudBitmapCtrl::onPreRender() +{ + GuiControl * parent = getParent(); + if(!parent) + return; + + // set extent to that of bitmap + if(mAutoResize && bool(mBitmapHandle)) + { + Point2I dim(mBitmapHandle.getWidth(), mBitmapHandle.getHeight()); + if (dim != getExtent()) + resize(getPosition(), dim); + } + + // resize to parent + if(mAutoCenter) + { + Point2I pos = parent->getExtent(); + pos = pos / 2 - (getExtent() + Point2I(1,1)) / 2; + resize(pos, getExtent()); + } + + Parent::onPreRender(); +} + +//--------------------------------------------------------------------------- + +void HudBitmapCtrl::onRender(Point2I offset, const RectI & updateRect, GuiControl * firstResponder) +{ + if(!bool(mBitmapHandle)) + { + Parent::onRender(offset, updateRect, firstResponder); + return; + } + + U32 flip = ( mFlipVert ? GFlip_Y : GFlip_None ) | ( mFlipHorz ? GFlip_X : GFlip_None ); + + mOpacity = mClampF(mOpacity, 0.f, 1.f); + + // + dglSetBitmapModulation(ColorF(1,1,1,mOpacity)); + dglDrawBitmapStretch(mBitmapHandle, updateRect, flip); + dglClearBitmapModulation(); + + renderChildControls(offset, updateRect, firstResponder); +} + +//--------------------------------------------------------------------------- + +void HudBitmapCtrl::inspectPostApply() +{ + mBitmapHandle = TextureHandle(mBitmap, BitmapTexture); + Parent::inspectPostApply(); +} + +//--------------------------------------------------------------------------- + +bool HudBitmapCtrl::onWake() +{ + if(!Parent::onWake()) + return(false); + mBitmapHandle = TextureHandle(mBitmap, BitmapTexture); + return(true); +} + +void HudBitmapCtrl::onSleep() +{ + mBitmapHandle = 0; + Parent::onSleep(); +} + +//--------------------------------------------------------------------------- + +void HudBitmapCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("bitmap", TypeString, Offset(mBitmap, HudBitmapCtrl)); + addField("autoCenter", TypeBool, Offset(mAutoCenter, HudBitmapCtrl)); + addField("autoResize", TypeBool, Offset(mAutoResize, HudBitmapCtrl)); + addField("flipVertical", TypeBool, Offset(mFlipVert, HudBitmapCtrl)); + addField("flipHorizontal", TypeBool, Offset(mFlipHorz, HudBitmapCtrl)); +} + +//--------------------------------------------------------------------------- + +ConsoleMethod( HudBitmapCtrl, setBitmap, void, 3, 3, "hudBitmapCtrl.setBitmap( bitmap )" ) +{ + argc; + static_cast( object )->setBitmap( argv[2] ); +} diff --git a/hud/hudBitmapCtrl.h b/hud/hudBitmapCtrl.h new file mode 100644 index 0000000..265f748 --- /dev/null +++ b/hud/hudBitmapCtrl.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HUDBITMAPCTRL_H_ +#define _HUDBITMAPCTRL_H_ + +#ifndef _HUDCTRL_H_ +#include "hud/hudCtrl.h" +#endif +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif +#ifndef _DGL_H_ +#include "dgl/dgl.h" +#endif + +class HudBitmapCtrl : public HudCtrl +{ + private: + typedef HudCtrl Parent; + + protected: + + TextureHandle mBitmapHandle; + + public: + + HudBitmapCtrl(); + + void setBitmap(const char * bitmap); + + // SimObject + void inspectPostApply(); + + // GuiControl + void onPreRender(); + void onRender(Point2I offset, const RectI & updateRect, GuiControl * firstResponder); + + bool onWake(); + void onSleep(); + + // field data + StringTableEntry mBitmap; + bool mAutoResize; + bool mAutoCenter; + bool mFlipVert; + bool mFlipHorz; + + static void initPersistFields(); + + DECLARE_CONOBJECT(HudBitmapCtrl); +}; + +#endif diff --git a/hud/hudBitmapFrameCtrl.cc b/hud/hudBitmapFrameCtrl.cc new file mode 100644 index 0000000..2c17e0f --- /dev/null +++ b/hud/hudBitmapFrameCtrl.cc @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudBitmapFrameCtrl.h" + +IMPLEMENT_CONOBJECT(HudBitmapFrameCtrl); + +HudBitmapFrameCtrl::HudBitmapFrameCtrl() +{ + mSubRegion.set(0,0,1,1); +} + +void HudBitmapFrameCtrl::onPreRender() +{ + if (mAutoResize && bool(mBitmapHandle)) + { + Point2I dim(mBitmapHandle.getWidth(), mBitmapHandle.getHeight()); + if (dim != getExtent()) + resize(getPosition(), dim); + } +} + +void HudBitmapFrameCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("subRegion", TypeRectI, Offset(mSubRegion, HudBitmapFrameCtrl)); +} diff --git a/hud/hudBitmapFrameCtrl.h b/hud/hudBitmapFrameCtrl.h new file mode 100644 index 0000000..f2440b2 --- /dev/null +++ b/hud/hudBitmapFrameCtrl.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HUDBITMAPFRAMECTRL_H_ +#define _HUDBITMAPFRAMECTRL_H_ + +#ifndef _HUDBITMAPCTRL_H_ +#include "hud/hudBitmapCtrl.h" +#endif + +class HudBitmapFrameCtrl : public HudBitmapCtrl +{ + private: + typedef HudBitmapCtrl Parent; + + public: + + HudBitmapFrameCtrl(); + + // GuiControl + void onPreRender(); + + // field data + RectI mSubRegion; + + static void initPersistFields(); + + DECLARE_CONOBJECT(HudBitmapFrameCtrl); +}; + +#endif diff --git a/hud/hudClock.cc b/hud/hudClock.cc new file mode 100644 index 0000000..36cfbaf --- /dev/null +++ b/hud/hudClock.cc @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudBitmapFrameCtrl.h" +#include "dgl/dgl.h" + +//--------------------------------------------------------------------------- +// CLASS: HudClockCtrl +//--------------------------------------------------------------------------- +class HudClockCtrl : public HudBitmapFrameCtrl +{ + private: + + typedef HudBitmapFrameCtrl Parent; + S32 mCurrentSetTime; + + public: + + HudClockCtrl(); + + void setTime(F32); + static void consoleInit(); + + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + DECLARE_CONOBJECT(HudClockCtrl); +}; + +IMPLEMENT_CONOBJECT(HudClockCtrl); + +//--------------------------------------------------------------------------- + +HudClockCtrl::HudClockCtrl() +{ + mCurrentSetTime = 0; +} + +//--------------------------------------------------------------------------- + +void HudClockCtrl::setTime(F32 newTime) +{ + F32 time = Platform::getVirtualMilliseconds(); + mCurrentSetTime = -(S32)((newTime*60*1000)+time); +} + +//--------------------------------------------------------------------------- + +void HudClockCtrl::onRender(Point2I /*offset*/, + const RectI& /*updateRect*/, + GuiControl* /*firstResponder*/) +{ +// Parent::onRender(offset, updateRect, firstResponder); + + char buf[256]; + Point2I localStart; + S32 secondsLeft, hours, mins, secs, hundredths; + + F32 actualTimeMS = (F32)mCurrentSetTime + Platform::getVirtualMilliseconds(); + F32 time = mAbs(actualTimeMS); + + secondsLeft = (S32)time/1000; + hundredths = (S32)((time - (secondsLeft*1000))/10); + hours = secondsLeft / 3600; + secondsLeft -= hours * 3600; + mins = secondsLeft / 60; + secondsLeft -= mins * 60; + secs = secondsLeft; + +// dSprintf(buf, sizeof(buf), "%02d:%02d:%02d.%1d", hours, mins, secs, tenths); + if (mins == 0 && actualTimeMS < 0) + dSprintf(buf, sizeof(buf), "%02d.%02d", secs, hundredths); + else + dSprintf(buf, sizeof(buf), "%02d:%02d", mins, secs); + + localStart.x = (mSubRegion.extent.x - mProfile->mFont->getStrWidth(buf)) / 2; + localStart.y = ((mSubRegion.extent.y - (mProfile->mFont->getHeight() - 2)) / 2); + localStart += mSubRegion.point; + + // + if(localStart.x < 0) + localStart.x = mSubRegion.point.x; + if(localStart.y < 0) + localStart.y = mSubRegion.point.y; + + dglSetBitmapModulation(ColorF(1,1,1,mOpacity)); + dglDrawText(mProfile->mFont, localToGlobalCoord(localStart), buf); + dglClearBitmapModulation(); +} + +//--------------------------------------------------------------------------- + +static void cHudClockCtrlSetTime(SimObject *obj, S32, const char **argv) +{ + HudClockCtrl *ctrl = static_cast(obj); + ctrl->setTime(dAtof(argv[2])); +} + +void HudClockCtrl::consoleInit() +{ + Con::addCommand("hudClock", "setTime", cHudClockCtrlSetTime, "timer.setTime(Time In Min's)", 3, 3); +} diff --git a/hud/hudClockCtrl.cc b/hud/hudClockCtrl.cc new file mode 100644 index 0000000..9fd7aeb --- /dev/null +++ b/hud/hudClockCtrl.cc @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudObject.h" +#include "hud/hudGLEx.h" +#include "console/consoleTypes.h" + +/** + * HudClock is a clock for the HUD. + */ +class HudClockCtrl : public HudObject { + + private: + typedef HudObject Parent; + S32 mStoredTime; + + public: + HudClockCtrl(); + + void setTime( F32 newTime ); + static void consoleInit(); + + void onRender( Point2I offset, const RectI &updateRect, GuiControl *firstResponder ); + + DECLARE_CONOBJECT( HudClockCtrl ); +}; + +//--------------------------------------------------------------------------- + + +IMPLEMENT_CONOBJECT( HudClockCtrl ); + +/** + * Constructor + */ +HudClockCtrl::HudClockCtrl() { + mStoredTime = 0; +} + +/** + * This method is used to set the ammount of time this clock should display. + * + * @param newTime Time to display in minutes + */ +void HudClockCtrl::setTime( F32 newTime ) { + F32 time = Platform::getVirtualMilliseconds(); + mStoredTime = -(S32)( ( newTime * 60 * 1000 )+ time ); +} + +/** + * Method called to render the HUD object + * + * @param offset Basically the corner to begin drawing at + * @param updateRect The rectangle that this object has power to draw in + * @param firstResponder ? + */ +void HudClockCtrl::onRender( Point2I offset, + const RectI &updateRect, + GuiControl* firstResponder ) { + + // Sanity check + GuiControl *parent = getParent(); + if( !parent ) + return; + + Parent::onRender( offset, updateRect, firstResponder ); + + // Declarations + char buf[256]; + S32 hours, mins, secs, hundredths; + + F32 actualTimeMS = (F32)mStoredTime + Platform::getVirtualMilliseconds(); + F32 time = mAbs( actualTimeMS ); + + // Operations + secs = (S32)time / 1000; + hundredths = (S32)( ( time - ( secs * 1000 ) ) / 10 ); + hours = secs / 3600; + secs -= hours * 3600; + mins = secs / 60; + secs -= mins * 60; + + if( mins == 0 && actualTimeMS < 0 ) + dSprintf( buf, sizeof( buf ), "%02d.%02d", secs, hundredths ); + else + dSprintf( buf, sizeof( buf ), "%02d:%02d", mins, secs ); + + offset.x += ( mBounds.extent.x / 2 ) - ( mProfile->mFont->getStrWidth( buf ) / 2 ); + offset.y += ( mBounds.extent.y / 2 ) - ( mProfile->mFont->getHeight() / 2 ); + + drawText( offset, buf ); +} + +/** + * This method is called from the console as a accessor to the setTime method + * + * @param obj Clock to set the time of, as this is a static method + * @param argv Array of string arguments from console + */ +static void cHudClockCtrlSetTime( SimObject *obj, S32, const char **argv ) { + HudClockCtrl *ctrl = static_cast( obj ); + ctrl->setTime( dAtof( argv[2] ) ); +} + +/** + * Adds to the console the methods this object supports + */ +void HudClockCtrl::consoleInit() { + Con::addCommand( "HudClockCtrl", "setTime", cHudClockCtrlSetTime, "timer.setTime(Time In Min's)", 3, 3 ); +} + +// hudClockCtrl.cc \ No newline at end of file diff --git a/hud/hudCompass.cc b/hud/hudCompass.cc new file mode 100644 index 0000000..b1bda0a --- /dev/null +++ b/hud/hudCompass.cc @@ -0,0 +1,194 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudCtrl.h" +#include "dgl/dgl.h" +#include "gui/guiTSControl.h" +#include "console/consoleTypes.h" +#include "game/gameConnection.h" +#include "game/camera.h" +#include "dgl/gTexManager.h" + +//--------------------------------------------------------------------------- +// Class: HudCompassCtrl +//--------------------------------------------------------------------------- + +class HudCompassCtrl : public HudCtrl +{ + private: + + typedef HudCtrl Parent; + + public: + + HudCompassCtrl(); + ~HudCompassCtrl(); + + // SimBase + bool onAdd(); + + // GuiControl + void onPreRender(); + void onRender(Point2I, const RectI &, GuiControl *); + bool onWake(); + void onSleep(); + + ColorI mTextColor; + + static void initPersistFields(); + + DECLARE_CONOBJECT(HudCompassCtrl); +}; + +IMPLEMENT_CONOBJECT(HudCompassCtrl); + +HudCompassCtrl::HudCompassCtrl() +{ + mTextColor.set( 0, 255, 0 ); +} + +HudCompassCtrl::~HudCompassCtrl() +{ +} + +//--------------------------------------------------------------------------- + +bool HudCompassCtrl::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + return(true); +} + +//--------------------------------------------------------------------------- + +bool HudCompassCtrl::onWake() +{ + if(!Parent::onWake()) + return(false); + + return(true); +} + +void HudCompassCtrl::onSleep() +{ + Parent::onSleep(); + +} + +//--------------------------------------------------------------------------- + +void HudCompassCtrl::onPreRender() +{ + GuiControl * parent = getParent(); + if(!parent) + return; + + setUpdate(); +} + +static float gCompassPoints[] = +{ + //'N' + 4.0f, -2.0f, -28.0f, -2.0f, -34.0f, 2.0f, -28.0f, 2.0f, -34.0f, + + //'E' + 4.0f, 28.0f, 2.0f, 28.0f, -2.0f, 34.0f, -2.0f, 34.0f, 2.0f, + 2.0f, 31.0f, -2.0f, 31.0f, 2.0f, + + //'W' + 5.0f, -34.0f, 4.0f, -28.0f, 2.0f, -34.0f, 0.0f, -28.0f, -2.0f, -34.0f, -4.0f, + + //'S' + 12.0f, 2.0f, 30.0f, 1.0f, 29.0f, -1.0f, 29.0f, -2.0f, 30.0f, -2.0f, 31.0f, -1.0f, 32.0f, + 1.0f, 32.0f, 2.0f, 33.0f, 2.0f, 34.0f, 1.0f, 35.0f, -1.0f, 35.0f, -2.0f, 34.0f, + + //4 marks + 2.0f, 25.0f, -24.0f, 29.0f, -28.0f, + 2.0f, 25.0f, 24.0f, 29.0f, 28.0f, + 2.0f, -24.0f, 24.0f, -28.0f, 28.0f, + 2.0f, -24.0f, -24.0f, -28.0f, -28.0f, + + //EOF + -1.0f +}; + +void HudCompassCtrl::onRender(Point2I offset, const RectI &, GuiControl *) +{ + GameConnection * con = GameConnection::getServerConnection(); + if( !con ) + return; + + ShapeBase *obj = con->getControlObject(); + + if( !obj ) + return; + + Point2F centerPt; + centerPt.x = 40.5f + F32( offset.x ); + centerPt.y = 38.5f + F32( offset.y ); + + MatrixF camMat; + F32 camPos = 0; + Point3F camVec; + obj->getCameraTransform(&camPos, &camMat); + + camMat.getColumn( 1, &camVec ); + camVec.z = 0.f; + camVec.normalize(); + + F32 camAngle; + F32 dot = mDot( camVec, Point3F( 0, 1, 0 ) ); + camAngle = mAcos( dot ); + + if(camVec.x > 0.f) + camAngle = M_2PI - camAngle; + + //rotate the needle around the compass + F32 cosTheta, sinTheta; + mSinCos( camAngle, sinTheta, cosTheta); + + //loop through and rotate the compass markings + Point2F markPoints[12]; + F32 *points; + points = &gCompassPoints[0]; + + while( *points > 0.0f ) + { + S32 j; + for( j = 0; j < *points; j++ ) + { + markPoints[j].x = (points[2 * j + 1] * cosTheta) - (points[2 * j + 2] * sinTheta); + markPoints[j].y = (points[2 * j + 1] * sinTheta) + (points[2 * j + 2] * cosTheta); + } + + //now draw the lines + for( j = 0; j < *points - 1; j++ ) + { + Point2I tempStart; + tempStart.x = S32( markPoints[j].x + centerPt.x ); + tempStart.y = S32( markPoints[j].y + centerPt.y ); + Point2I tempEnd; + tempEnd.x = S32( markPoints[j + 1].x + centerPt.x ); + tempEnd.y = S32( markPoints[j + 1].y + centerPt.y ); + dglDrawLine( tempStart, tempEnd, ColorI(mTextColor.red, mTextColor.green, mTextColor.blue ) ); + } + + //now increment the points pointer + points += ( S32( *points ) * 2 ) + 1; + } +} + +//--------------------------------------------------------------------------- + +void HudCompassCtrl::initPersistFields() +{ + Parent::initPersistFields(); + + addField("textColor", TypeColorI, Offset(mTextColor, HudCompassCtrl)); +} diff --git a/hud/hudCrosshair.cc b/hud/hudCrosshair.cc new file mode 100644 index 0000000..49d4e76 --- /dev/null +++ b/hud/hudCrosshair.cc @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "gui/guiCanvas.h" +#include "hud/hudBitmapCtrl.h" +#include "game/gameConnection.h" +#include "game/camera.h" + +//--------------------------------------------------------------------------- +// Class: HudCrosshairCtrl +//--------------------------------------------------------------------------- +class HudCrosshairCtrl : public HudBitmapCtrl +{ + private: + typedef HudBitmapCtrl Parent; + + public: + HudCrosshairCtrl(); + + // GuiControl + void onRender(Point2I, const RectI &, GuiControl *); + + DECLARE_CONOBJECT(HudCrosshairCtrl); +}; + +IMPLEMENT_CONOBJECT(HudCrosshairCtrl); + +//--------------------------------------------------------------------------- + +HudCrosshairCtrl::HudCrosshairCtrl() +{ + mAutoResize = true; + mAutoCenter = true; +} + +//--------------------------------------------------------------------------- + +void HudCrosshairCtrl::onRender(Point2I offset, const RectI & updateRect, GuiControl * firstResponder) +{ + if(!bool(mBitmapHandle)) + return; + + // renders in center of parent control + GuiControl * parent = getParent(); + if(!parent) + return; + + GameConnection * con = GameConnection::getServerConnection(); + if(!con) + return; + + if(!con->isFirstPerson()) + return; + + if(dynamic_cast(con->getControlObject())) + return; + + Parent::onRender(offset, updateRect, firstResponder); +} diff --git a/hud/hudCtrl.cc b/hud/hudCtrl.cc new file mode 100644 index 0000000..de92148 --- /dev/null +++ b/hud/hudCtrl.cc @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudCtrl.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" + +IMPLEMENT_CONOBJECT(HudCtrl); + +HudCtrl::HudCtrl() +{ + mFillColor.set(0.25, 0.25, 0.25, 0.25); + mFrameColor.set(0, 1, 0, 1); + mOpacity = 1.f; +} + +//-------------------------------------------------------------------------- + +void HudCtrl::onRender(Point2I offset, const RectI &updateRect, GuiControl * firstResponder) +{ + RectI ctrlRect(offset, mBounds.extent); + + // + mFillColor.alpha = mFrameColor.alpha = mOpacity; + + dglDrawRectFill(ctrlRect, mFillColor); + dglDrawRect(ctrlRect, mFrameColor); + + renderChildControls(offset, updateRect, firstResponder); +} + +void HudCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField("fillColor", TypeColorF, Offset(mFillColor, HudCtrl)); + addField("frameColor", TypeColorF, Offset(mFrameColor, HudCtrl)); + addField("opacity", TypeF32, Offset(mOpacity, HudCtrl)); +} diff --git a/hud/hudCtrl.h b/hud/hudCtrl.h new file mode 100644 index 0000000..f98e3ee --- /dev/null +++ b/hud/hudCtrl.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HUDCTRL_H_ +#define _HUDCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "GUI/guiControl.h" +#endif + +class HudCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + + public: + + HudCtrl(); + + // GuiControl + virtual void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + + // field data + ColorF mFillColor; + ColorF mFrameColor; + F32 mOpacity; + + static void initPersistFields(); + + DECLARE_CONOBJECT(HudCtrl); +}; + +#endif diff --git a/hud/hudDamageCtrl.cc b/hud/hudDamageCtrl.cc new file mode 100644 index 0000000..e125f87 --- /dev/null +++ b/hud/hudDamageCtrl.cc @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hudBarBaseCtrl.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "game/camera.h" + +//=========================================================================== +// CLASS: HudDamageCtrl +//=========================================================================== + +class HudDamageCtrl : public HudBarBaseCtrl { + private: + typedef HudBarBaseCtrl Parent; + + public: + + F32 getValue(); + void getBarColor( ColorI &col ); + + DECLARE_CONOBJECT( HudDamageCtrl ); +}; + +//=========================================================================== +// HudDamageCtrl Implementation +//=========================================================================== +IMPLEMENT_CONOBJECT( HudDamageCtrl ); + +//------------------------------------------------------------------ +// getBarColor +// +// This method ovrrides the virtual method in hudBarBaseCtrl +// the bar changes color depending on how much damage is taken +//------------------------------------------------------------------ +void HudDamageCtrl::getBarColor( ColorI &col ) { + F32 val = getValue(); + + if( val < 0.6 && val > 0.25) + col.set( 255, 178, 0 ); + else if( val <= 0.25 ) + col.set( 255, 0, 0 ); + else + col.set( mFillColor.red * 255, mFillColor.green * 255, mFillColor.blue * 255); +} + +//------------------------------------------------------------------ +// getValue +// +// This method is overridden from hudBarBaseCtrl, it returns the +// damage level of the player as a percentile +// +// Return: Damage level of the player as a percentile +//------------------------------------------------------------------ +F32 HudDamageCtrl::getValue() { + + GameConnection *con = GameConnection::getServerConnection(); + + // Sanity check connection to game. + if( !con ) + return( 0.f ); + + ShapeBase *obj = con->getControlObject(); + + if( !obj ) + return (0.f); + + mPulse = false; + + // If we're in a floating camera view... + if( dynamic_cast( con->getControlObject() ) ) + return( 0.f ); + + // Nope, we're either on a vehicle or running around + F32 damage = 0.0f; + + if( mDisplayMounted ) { + ShapeBase * mount = obj->getObjectMount(); + + while( mount && mount->isMounted() ) + mount = mount->getObjectMount(); + + if( mount ) + damage = mClampF( 1.f - mount->getDamageValue(), 0.f, 1.f ); + else + damage = 0.0f; + } + else + damage = mClampF( 1.f - obj->getDamageValue(), 0.f, 1.f ); + + + if( damage < 0.25 ) + mPulse = true; + + return damage ; +} + +// hudDamage.cc diff --git a/hud/hudEnergyCtrl.cc b/hud/hudEnergyCtrl.cc new file mode 100644 index 0000000..e5f9717 --- /dev/null +++ b/hud/hudEnergyCtrl.cc @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hudBarBaseCtrl.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" + +//=========================================================================== +// CLASS: HudEnergyCtrl +// +// This object displays the players energy level in a bar format +//=========================================================================== + +class HudEnergyCtrl : public HudBarBaseCtrl { + private: + typedef HudBarBaseCtrl Parent; + + public: + + F32 getValue(); + + DECLARE_CONOBJECT( HudEnergyCtrl ); +}; + +//=========================================================================== +// HudEnergyCtrl Implementation +//=========================================================================== +IMPLEMENT_CONOBJECT( HudEnergyCtrl ); + +//------------------------------------------------------------------ +// getValue +// +// This method is overridden from hudBarBaseCtrl, it returns the +// energy level of the player as a percentile +// +// Return: Energy level of the player as a percentile +//------------------------------------------------------------------ +F32 HudEnergyCtrl::getValue() { + GameConnection *con = GameConnection::getServerConnection(); + + if( !con ) + return(0.f); + + ShapeBase *obj = con->getControlObject(); + if( !obj ) + return(0.f); + + if( mDisplayMounted ) { + ShapeBase *mount = obj->getObjectMount(); + + while( mount && mount->isMounted() ) + mount = mount->getObjectMount(); + + if( mount ) + return( mClampF( mount->getEnergyValue(), 0.f, 1.f ) ); + else + return( 0.f ); + } + else + return mClampF( obj->getEnergyValue(), 0.f, 1.f ); +} + +// hudEnergy.cc \ No newline at end of file diff --git a/hud/hudEnergyDamage.cc b/hud/hudEnergyDamage.cc new file mode 100644 index 0000000..a718759 --- /dev/null +++ b/hud/hudEnergyDamage.cc @@ -0,0 +1,296 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudBitmapFrameCtrl.h" +#include "core/dnet.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "game/camera.h" + +//--------------------------------------------------------------------------- +// Class: HudBarBaseCtrl +//--------------------------------------------------------------------------- + +class HudBarBaseCtrl : public HudBitmapFrameCtrl +{ + private: + typedef HudBitmapFrameCtrl Parent; + + protected: + + virtual F32 getValue() { return( 0.0f ); } + virtual void getBarColor(ColorI &col){ col.set( mFillColor.red * 255, mFillColor.green * 255, mFillColor.blue * 255); } + public: + + HudBarBaseCtrl(); + + // GuiControl + bool onWake(); + void onSleep(); + void onRender(Point2I, const RectI &, GuiControl *); + + S32 mPulseRate; + F32 mPulseThreshold; + bool mVerticalFill; + bool mPulse; + bool mDisplayMounted; + + static void initPersistFields(); + + DECLARE_CONOBJECT(HudBarBaseCtrl); +}; + +IMPLEMENT_CONOBJECT(HudBarBaseCtrl); + +//--------------------------------------------------------------------------- + +HudBarBaseCtrl::HudBarBaseCtrl() +{ + mPulseRate = 500; + mPulseThreshold = 0.3f; + mVerticalFill = false; + mPulse = false; + mDisplayMounted = false; +} + +bool HudBarBaseCtrl::onWake() +{ + if(!Parent::onWake()) + return(false); + return(true); +} + +void HudBarBaseCtrl::onSleep() +{ + Parent::onSleep(); +} + +//--------------------------------------------------------------------------- + +void HudBarBaseCtrl::onRender(Point2I offset, const RectI & /*updateRect*/, GuiControl * /*firstResponder*/) +{ + GameConnection * con = GameConnection::getServerConnection(); + if(!con) + return; + + ShapeBase * obj = con->getControlObject(); + if(!obj) + return; + + F32 val = getValue(); + + RectI rect(offset + mSubRegion.point, mSubRegion.extent); + rect.extent.x = (S32)(rect.extent.x * val); + + mFillColor.alpha = mOpacity; + ColorI fill; + getBarColor( fill ); + fill.alpha = mOpacity * 255; + + if(mPulse && (mPulseRate != 0)) + { + S32 time = (S32)Platform::getVirtualMilliseconds(); + F32 alpha = F32(time % mPulseRate) / F32(mPulseRate/2); + + if(alpha > 1) + alpha = 1.f - (alpha - 1.f); + + fill.alpha *= alpha; + fill.alpha *= 255; + } + + dglDrawRectFill(rect, fill); +} + +//--------------------------------------------------------------------------- + +void HudBarBaseCtrl::initPersistFields() +{ + Parent::initPersistFields(); + + addField("displayMounted", TypeBool, Offset(mDisplayMounted, HudBarBaseCtrl)); + addField("pulseRate", TypeS32, Offset(mPulseRate, HudBarBaseCtrl)); + addField("pulseThreshold", TypeF32, Offset(mPulseThreshold, HudBarBaseCtrl)); + addField("verticalFill", TypeBool, Offset(mVerticalFill, HudBarBaseCtrl)); +} + +//--------------------------------------------------------------------------- +// Class: HudEnergy +//--------------------------------------------------------------------------- + +class HudEnergyCtrl : public HudBarBaseCtrl +{ + private: + typedef HudBarBaseCtrl Parent; + + public: + + F32 getValue(); + + DECLARE_CONOBJECT(HudEnergyCtrl); +}; + +IMPLEMENT_CONOBJECT(HudEnergyCtrl); + +F32 HudEnergyCtrl::getValue() +{ + GameConnection * con = GameConnection::getServerConnection(); + if(!con) + return(0.f); + + ShapeBase * obj = con->getControlObject(); + if(!obj) + return(0.f); + + if(mDisplayMounted) + { + ShapeBase * mount = obj->getObjectMount(); + while(mount && mount->isMounted()) + mount = mount->getObjectMount(); + + if(mount) + return(mClampF(mount->getEnergyValue(), 0.f, 1.f)); + else + return(0.f); + } + else + return(mClampF(obj->getEnergyValue(), 0.f, 1.f)); +} + +//--------------------------------------------------------------------------- +// Class: HudDamage +//--------------------------------------------------------------------------- + +class HudDamageCtrl : public HudBarBaseCtrl +{ + private: + typedef HudBarBaseCtrl Parent; + + public: + + F32 getValue(); + void getBarColor( ColorI &col ); + + DECLARE_CONOBJECT(HudDamageCtrl); +}; + +IMPLEMENT_CONOBJECT(HudDamageCtrl); + +void HudDamageCtrl::getBarColor( ColorI &col ) +{ + F32 val = getValue(); + + if( val < 0.6 && val > 0.25) + col.set( 255, 178, 0 ); + else if( val <= 0.25 ) + col.set( 255, 0, 0 ); + else + col.set( mFillColor.red * 255, mFillColor.green * 255, mFillColor.blue * 255);} + +F32 HudDamageCtrl::getValue() +{ + GameConnection * con = GameConnection::getServerConnection(); + + if(!con) + return(0.f); + + ShapeBase * obj = con->getControlObject(); + if( !obj ) + return (0.f); + + mPulse = false; + + if(dynamic_cast(con->getControlObject())) + return(0.f); + + F32 damage = 0.0f; + + if(mDisplayMounted) + { + ShapeBase * mount = obj->getObjectMount(); + while(mount && mount->isMounted()) + mount = mount->getObjectMount(); + + if(mount) + damage = mClampF(1.f - mount->getDamageValue(), 0.f, 1.f); + else + damage = 0.0f; + } + else + damage = mClampF(1.f - obj->getDamageValue(), 0.f, 1.f); + + + if( damage < 0.25 ) + mPulse = true; + + return(damage); +} + +//--------------------------------------------------------------------------- +// Class: HudHeat +//--------------------------------------------------------------------------- + +class HudHeat : public HudBarBaseCtrl +{ + private: + typedef HudBarBaseCtrl Parent; + + public: + + F32 getValue(); + void onRender(Point2I, const RectI &, GuiControl *); + + DECLARE_CONOBJECT(HudHeat); + + static void initPersistFields(); + HudHeat(); + F32 mHeatWarning; +}; + +IMPLEMENT_CONOBJECT(HudHeat); + +HudHeat::HudHeat() +{ + mHeatWarning = 0.5; +} + +F32 HudHeat::getValue() +{ + GameConnection * con = GameConnection::getServerConnection(); + if(!con) + return 0.f; + + ShapeBase *obj = con->getControlObject(); + if(!obj) + return 0.f; + + if( dynamic_cast(obj) ) + return(0.f); + + F32 heat = obj->getHeat(); + + if( heat > mHeatWarning ) + mPulse = true; + else + mPulse = false; + + return(mClampF( heat, 0.f, 1.f)); +} + +void HudHeat::onRender( Point2I offset, const RectI &rect, GuiControl *ctrl ) +{ + Parent::onRender( offset, rect, ctrl ); + + // need to draw a little hash here +} + +void HudHeat::initPersistFields() +{ + Parent::initPersistFields(); + + addField("heatWarning", TypeF32, Offset(mHeatWarning, HudHeat)); +} diff --git a/hud/hudGLEx.cc b/hud/hudGLEx.cc new file mode 100644 index 0000000..528335d --- /dev/null +++ b/hud/hudGLEx.cc @@ -0,0 +1,311 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudGLEx.h" +#include "math/mBezier2D.h" +#include "math/mPoint.h" +#include "hud/hudGLEx.h" +#include "core/color.h" +#include "math/mPoint.h" +#include "math/mRect.h" +#include "dgl/gFont.h" + +/** + * This function takes two colors and does a gradient line between them + * + * @param x1 X coordinate of the first point of the line + * @param y1 Y coordinate of the first point of the line + * @param x2 X coordinate of the second point of the line + * @param y2 Y coordinate of the second point of the line + * @param color1 Color of the first point of the line + * @param color2 Color of the second point of the line + */ +void dglDrawLine( S32 x1, S32 y1, S32 x2, S32 y2, const ColorI &color1, const ColorI &color2 ) { + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_TEXTURE_2D ); + + glBegin( GL_LINES ); + glColor4ub( color1.red, color1.green, color1.blue, color1.alpha ); + glVertex2f( (F32)x1 + 0.5, (F32)y1 + 0.5 ); + + glColor4ub( color2.red, color2.green, color2.blue, color2.alpha ); + glVertex2f( (F32)x2 + 0.5, (F32)y2 + 0.5 ); + glEnd(); +} + +/** + * This function takes two colors and does a gradient line between them + * + * @param startPt Start point of the line + * @param endPt End point of the line + * @param color1 Color of the start point of the line + * @param color2 Color of the end point of the line + */ +void dglDrawLine( const Point2I &startPt, const Point2I &endPt, const ColorI &color1, const ColorI &color2 ) { + dglDrawLine( startPt.x, startPt.y, endPt.x, endPt.y, color1, color2 ); +} + +/** + * This function draws a hollow rectangle and + * does a gradient blend across it + * + * @param upperL Upper left corner point + * @param lowerR Lower right corner point + * @param color1 Upper left corner point color + * @param color2 Lower left corner point color + * @param color3 Upper right corner point color + * @param color4 Lower right corner point color + */ +void dglDrawRect( const Point2I &upperL, const Point2I &lowerR, const ColorI &color1, + const ColorI &color2, const ColorI &color3, const ColorI &color4 ) { + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_TEXTURE_2D ); + + glBegin( GL_LINE_LOOP ); + // Upper Left + glColor4ub( color1.red, color1.green, color1.blue, color1.alpha ); + glVertex2f( (F32)upperL.x + 0.5, (F32)upperL.y + 0.5 ); + // Upper Right + glColor4ub( color3.red, color3.green, color3.blue, color3.alpha ); + glVertex2f( (F32)lowerR.x + 0.5, (F32)upperL.y + 0.5 ); + // Lower Right + glColor4ub( color4.red, color4.green, color4.blue, color4.alpha ); + glVertex2f( (F32)lowerR.x + 0.5, (F32)lowerR.y + 0.5 ); + // Lower Left + glColor4ub( color2.red, color2.green, color2.blue, color2.alpha ); + glVertex2f( (F32)upperL.x + 0.5, (F32)lowerR.y + 0.5 ); + glEnd(); +} + +/** + * This function draws a hollow rectangle and + * does a gradient blend across it + * + * @param rect Rectangle to draw + * @param color1 Upper left corner point color + * @param color2 Lower left corner point color + * @param color3 Upper right corner point color + * @param color4 Lower right corner point color + */ +void dglDrawRect( const RectI &rect, const ColorI &color1, const ColorI &color2, + const ColorI &color3, const ColorI &color4 ) { + + Point2I lowerR( rect.point.x + rect.extent.x - 1, rect.point.y + rect.extent.y - 1 ); + dglDrawRect( rect.point, lowerR, color1, color2, color3, color4 ); +} + +/** + * This function draws a filled rectangle and does a gradient + * blend across it. + * + * @param upperL Upper left corner point + * @param lowerR Lower right corner point + * @param color1 Upper left corner point color + * @param color2 Lower left corner point color + * @param color3 Upper right corner point color + * @param color4 Lower right corner point color + */ +void dglDrawRectFill( const Point2I &upperL, const Point2I &lowerR, const ColorI &color1, + const ColorI &color2, const ColorI &color3, const ColorI &color4 ) { + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_TEXTURE_2D ); + + glBegin( GL_QUADS ); + // Upper Left + glColor4ub( color1.red, color1.green, color1.blue, color1.alpha ); + glVertex2f( (F32)upperL.x + 0.5, (F32)upperL.y + 0.5 ); + // Upper Right + glColor4ub( color3.red, color3.green, color3.blue, color3.alpha ); + glVertex2f( (F32)lowerR.x + 0.5, (F32)upperL.y + 0.5 ); + // Lower Right + glColor4ub( color4.red, color4.green, color4.blue, color4.alpha ); + glVertex2f( (F32)lowerR.x + 0.5, (F32)lowerR.y + 0.5 ); + // Lower Left + glColor4ub( color2.red, color2.green, color2.blue, color2.alpha ); + glVertex2f( (F32)upperL.x + 0.5, (F32)lowerR.y + 0.5 ); + glEnd(); +} + +/** + * This function draws a hollow rectangle and + * does a gradient blend across it + * + * @param rect Rectangle to draw + * @param color1 Upper left corner point color + * @param color2 Lower left corner point color + * @param color3 Upper right corner point color + * @param color4 Lower right corner point color + */ +void dglDrawRectFill( const RectI &rect, const ColorI &color1, + const ColorI &color2, const ColorI &color3, const ColorI &color4 ) { + + Point2I lowerR( rect.point.x + rect.extent.x - 1, rect.point.y + rect.extent.y - 1 ); + dglDrawRectFill( rect.point, lowerR, color1, color2, color3, color4 ); +} + +/** + * This draws a Beizer curve using a line strip + * + * @param curve Bezier curve object to draw + * @param color Color to draw the curve + */ +void dglDrawBezier( Bezier2D *curve, const ColorI &color, const Point2F &offset ) { + + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_TEXTURE_2D ); + + glColor4ub( color.red, color.green, color.blue, color.alpha ); + + glBegin( GL_LINE_STRIP ); + for( int i = 0; i < curve->getNumCurvePoints(); i++ ) + glVertex2f( curve->getCurvePoints()[i].x - offset.x, curve->getCurvePoints()[i].y - offset.y ); + glEnd(); +} + +/** + * This draws a Beizer curve using a line strip with a gradient + * + * @param curve Bezier curve object to draw + * @param color1 First color for the curve gradient + * @param color2 Second color for the curve gradient + */ +void dglDrawBezier( Bezier2D *curve, const ColorI &color1, const ColorI &color2, const Point2F &offset ) { + ColorF colorStep; + ColorF currColor( color1.red, color1.green, color1.blue, color1.alpha ); + + // Calculate the color steps + F32 stepDelta = curve->getNumCurvePoints() / 100.0f; + + colorStep.red = ( ( color2.red - color1.red ) / 85.3 ) * stepDelta; + colorStep.green = ( ( color2.green - color1.green ) / 85.3 ) * stepDelta; + colorStep.blue = ( ( color2.blue - color1.blue ) / 85.3 ) * stepDelta; + + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_TEXTURE_2D ); + + glBegin( GL_LINE_STRIP ); + for( int i = 0; i < curve->getNumCurvePoints(); i++ ) { + currColor.red += colorStep.red * i; + currColor.green += colorStep.green * i; + currColor.blue += colorStep.blue * i; + + glColor4ub( currColor.red, currColor.green, currColor.blue, currColor.alpha ); + glVertex2f( curve->getCurvePoints()[i].x - offset.x, curve->getCurvePoints()[i].y - offset.y ); + } + glEnd(); +} + +/** + * This draws a Beizer curve using a quad strip + * + * @param curve Bezier curve object to draw + * @param offset Delta to offset the second curve by + * @param scale How big the second curve is in relation to the first + * @param color Color to draw the curve + * @param per Optional parameter, allows you to specify a percent of the graph to render + * @param rightToLeft Optional parameter, draw the graph from right to left instead of left to right (Default) + */ +void dglDrawBezierBand( Bezier2D *curve, const Point2F &offset, const F32 scale, const ColorI &color, const F32 per, bool rightToLeft ) { + Bezier2D *innerCurve = curve->getScaledCurve( scale ); + F32 percent = per; + + if( percent > 1.0f ) + percent = 1.0f; + else if( percent < 0.0f ) + percent = 0.0f; + + U32 i = 0; + U32 max = curve->getNumCurvePoints(); + + if( rightToLeft ) + i = (U32)( max * ( 1.0f - percent ) ); + else if( percent != 1.0f ) + max = (U32)( max * percent ); + + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_TEXTURE_2D ); + + glColor4ub( color.red, color.green, color.blue, color.alpha ); + + glBegin( GL_QUAD_STRIP ); + for( i; i < max; i++ ) { + glVertex2f( curve->getCurvePoints()[i].x, curve->getCurvePoints()[i].y ); + glVertex2f( innerCurve->getCurvePoints()[i].x - offset.x, + innerCurve->getCurvePoints()[i].y - offset.y ); + } + glEnd(); +} + +/** + * This draws a Beizer curve using a quad strip with a gradient + * + * @param curve Bezier curve object to draw + * @param offset Delta to offset the second curve by + * @param scale How big the second curve is in relation to the first + * @param color1 First color for the curve gradient + * @param color2 Second color for the curve gradient + * @param per Optional parameter, allows you to specify a percent of the graph to render + * @param rightToLeft Optional parameter, draw the graph from right to left instead of left to right (Default) + */ +void dglDrawBezierBand( Bezier2D *curve, const Point2F &offset, const F32 scale, const ColorI &color1, const ColorI &color2, const F32 per, bool rightToLeft ) { + Bezier2D *innerCurve = curve->getScaledCurve( scale ); + ColorF colorStep( 0, 0, 0 ); + ColorF currColor( color1.red, color1.green, color1.blue, color1.alpha ); + F32 percent = per; + U32 i = 0; + U32 max = curve->getNumCurvePoints(); + + if( percent > 1.0f ) + percent = 1.0f; + else if( percent < 0.0f ) + percent = 0.0f; + + + // Calculate the color steps + colorStep.red = ( (F32)color2.red - (F32)color1.red ) / (F32)curve->getNumCurvePoints(); + colorStep.green = ( (F32)color2.green - (F32)color1.green ) / (F32)curve->getNumCurvePoints(); + colorStep.blue = ( (F32)color2.blue - (F32)color1.blue ) / (F32)curve->getNumCurvePoints(); + + if( rightToLeft ) { + i = (U32)( max * ( 1.0f - percent ) ); + currColor.set( color2.red, color2.green, color2.blue, color2.alpha ); + + colorStep.red *= -1.0f; + colorStep.green *= -1.0f; + colorStep.blue *= -1.0f; + + currColor.red += i * colorStep.red; + currColor.green += i * colorStep.green; + currColor.blue += i * colorStep.blue; + } + else if( percent != 1.0f ) + max = (U32)( max * percent ); + + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_TEXTURE_2D ); + + glBegin( GL_QUAD_STRIP ); + for( i; i < max; i++ ) { + glColor4ub( currColor.red, currColor.green, currColor.blue, currColor.alpha ); + + glVertex2f( curve->getCurvePoints()[i].x, curve->getCurvePoints()[i].y ); + glVertex2f( innerCurve->getCurvePoints()[i].x - offset.x, + innerCurve->getCurvePoints()[i].y - offset.y ); + + currColor.red += colorStep.red; + currColor.green += colorStep.green; + currColor.blue += colorStep.blue; + } + glEnd(); +} \ No newline at end of file diff --git a/hud/hudGLEx.h b/hud/hudGLEx.h new file mode 100644 index 0000000..8a91f25 --- /dev/null +++ b/hud/hudGLEx.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HUDGLEX_H_ +#define _HUDGLEX_H_ + +#include "math/mPoint.h" +#include "dgl/dgl.h" + +class Bezier2D; + +void dglDrawBezier( Bezier2D *curve, const ColorI &color, const Point2F &offset = Point2F( 0.0f, 0.0f ) ); +void dglDrawBezier( Bezier2D *curve, const ColorI &color1, const ColorI &color2, const Point2F &offset = Point2F( 0.0f, 0.0f ) ); + +void dglDrawBezierBand( Bezier2D *curve, const Point2F &offset, const F32 scale, const ColorI &color, const F32 per = 1.0f, bool rightToLeft = false ); +void dglDrawBezierBand( Bezier2D *curve, const Point2F &offset, const F32 scale, const ColorI &color1, const ColorI &color2, const F32 per = 1.0f, bool rightToLeft = false ); + +void dglDrawRectFill( const Point2I &upperL, const Point2I &lowerR, const ColorI &color1, + const ColorI &color2, const ColorI &color3, const ColorI &color4 ); +void dglDrawRectFill( const RectI &rect, const ColorI &color1, + const ColorI &color2, const ColorI &color3, const ColorI &color4 ); + +void dglDrawRect( const Point2I &upperL, const Point2I &lowerR, const ColorI &color1, + const ColorI &color2, const ColorI &color3, const ColorI &color4 ); +void dglDrawRect( const RectI &rect, const ColorI &color1, const ColorI &color2, + const ColorI &color3, const ColorI &color4 ); + +void dglDrawLine( S32 x1, S32 y1, S32 x2, S32 y2, const ColorI &color1, const ColorI &color2 ); +void dglDrawLine( const Point2I &startPt, const Point2I &endPt, const ColorI &color1, const ColorI &color2 ); + +#endif \ No newline at end of file diff --git a/hud/hudHealthCtrl.cc b/hud/hudHealthCtrl.cc new file mode 100644 index 0000000..64ede46 --- /dev/null +++ b/hud/hudHealthCtrl.cc @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudBarDisplayCtrl.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "game/camera.h" + +/** + * This is a health display bar graph + */ +class HudHealthCtrl : public HudBarDisplayCtrl { + private: + typedef HudBarDisplayCtrl Parent; + + public: + HudHealthCtrl(); + + F32 getValue(); + ColorI getBarColor(); + + DECLARE_CONOBJECT( HudHealthCtrl ); +}; + +IMPLEMENT_CONOBJECT( HudHealthCtrl ); + +/** + * Constructor + */ +HudHealthCtrl::HudHealthCtrl() { + mHorizontalBar = false; + mDrawFromOrigin = false; +} + +/** + * This method ovrrides the virtual method in hudBarBaseCtrl + * the bar changes color depending on how much damage is taken + * + * @return ColorI that has the bar color stored in it + */ +ColorI HudHealthCtrl::getBarColor() { + F32 val = getValue(); + + if( val < 0.6 && val > 0.25) + return ColorI( 255, 178, 0 ); + else if( val <= 0.25 ) + return ColorI( 255, 0, 0 ); + else + return ColorI( 0, 210, 210 ); +} + +/** + * This method is overridden from hudBarBaseCtrl, it returns the + * damage level of the player as a percentile + * + * @return Damage level of the player as a percentile + */ +F32 HudHealthCtrl::getValue() { + + GameConnection *con = GameConnection::getServerConnection(); + + // Sanity check connection to game. + if( !con ) + return( 0.f ); + + ShapeBase *obj = con->getControlObject(); + + if( !obj ) + return (0.f); + + mPulse = false; + + // If we're in a floating camera view... + if( dynamic_cast( con->getControlObject() ) ) + return( 0.f ); + + // Nope, we're either on a vehicle or running around + F32 damage = 0.0f; + + if( mDisplayMounted ) { + ShapeBase * mount = obj->getObjectMount(); + + while( mount && mount->isMounted() ) + mount = mount->getObjectMount(); + + if( mount ) + damage = mClampF( 1.f - mount->getDamageValue(), 0.f, 1.f ); + else + damage = 0.0f; + } + else + damage = mClampF( 1.f - obj->getDamageValue(), 0.f, 1.f ); + + + if( damage < 0.25 ) + mPulse = true; + + return damage; +} + +// hudDamage.cc diff --git a/hud/hudHeat.cc b/hud/hudHeat.cc new file mode 100644 index 0000000..bfb5288 --- /dev/null +++ b/hud/hudHeat.cc @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hudBarBaseCtrl.h" +#include "game/gameConnection.h" +#include "game/shapeBase.h" +#include "game/camera.h" + +//=========================================================================== +// CLASS: HudHeat +//=========================================================================== + +class HudHeat : public HudBarBaseCtrl { + private: + typedef HudBarBaseCtrl Parent; + + public: + + F32 getValue(); + + DECLARE_CONOBJECT( HudHeat ); + + static void initPersistFields(); + HudHeat(); + F32 mHeatWarning; +}; + +//=========================================================================== +// HudHeatImplementation +//=========================================================================== +IMPLEMENT_CONOBJECT( HudHeat ); + +//------------------------------------------------------------------ +// Constructor +//------------------------------------------------------------------ +HudHeat::HudHeat() { + mHeatWarning = 0.5; +} + +//------------------------------------------------------------------ +// getValue +// +// This method is overridden from hudBarBaseCtrl, it returns the +// heat level of the player as a percentile +// +// Return: Heat level of the player as a percentile +//------------------------------------------------------------------ +F32 HudHeat::getValue() { + GameConnection *con = GameConnection::getServerConnection(); + + if(!con) + return 0.f; + + ShapeBase *obj = con->getControlObject(); + + if( !obj ) + return 0.f; + + // Make sure this isn't a free camera + if( dynamic_cast(obj) ) + return 0.f; + + F32 heat = obj->getHeat(); + + if( heat > mHeatWarning ) + mPulse = true; + else + mPulse = false; + + return mClampF( heat, 0.f, 1.f ); +} + +//------------------------------------------------------------------ +// initPersistFields +// +// Registers datamembers of this object with the game +//------------------------------------------------------------------ +void HudHeat::initPersistFields() { + Parent::initPersistFields(); + + addField( "heatWarning", TypeF32, Offset( mHeatWarning, HudHeat ) ); +} \ No newline at end of file diff --git a/hud/hudObject.cc b/hud/hudObject.cc new file mode 100644 index 0000000..e1573dd --- /dev/null +++ b/hud/hudObject.cc @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudObject.h" +#include "hud/hudGLEx.h" +#include "console/consoleTypes.h" + +IMPLEMENT_CONOBJECT( HudObject ); + +/** + * Constructor + */ +HudObject::HudObject() { + mFillColor.set( 0.25, 0.25, 0.25, 0.25 ); + mFrameColor.set( 0, 1, 0, 1 ); + mTextColor.set( 1, 1, 1, 1 ); + mShadowColor.set( 0, 0, 0, 1 ); + mOpacity = 1.f; + mShowFrame = mShowFill = true; + mUse3dText = true; +} + +/** + * Method called to render the HUD object + * + * @param offset Basically the corner to begin drawing at + * @param updateRect The rectangle that this object updates + * @param firstResponder ? + */ +void HudObject::onRender( Point2I offset, + const RectI &updateRect, + GuiControl *firstResponder ) { + + mFillColor.alpha = mFrameColor.alpha = mTextColor.alpha = mOpacity; + + if( mShowFill ) + dglDrawRectFill( updateRect, mFillColor ); + if( mShowFrame ) + dglDrawRect( updateRect, mFrameColor ); + + //renderChildControls( offset, updateRect, firstResponder ); +} + +/** + * Draws text on the HUD control, the reason for this is to + * allow for some text effects, like shadows and such + * + * @param offset Where to begin drawing + * @param buf The buffer of text to display + */ +void HudObject::drawText( Point2I offset, char *buf ) { + if( mUse3dText ) { + dglSetBitmapModulation( mShadowColor ); + dglDrawText( mProfile->mFont, Point2I( offset.x - 2, offset.y + 2 ), buf ); + } + + dglSetBitmapModulation( mTextColor ); + dglDrawText( mProfile->mFont, offset, buf ); + dglClearBitmapModulation(); +} + +/** + * Registers console-modifyable datamembers + */ +void HudObject::initPersistFields() { + Parent::initPersistFields(); + addField( "fillColor", TypeColorF, Offset( mFillColor, HudObject ) ); + addField( "frameColor", TypeColorF, Offset( mFrameColor, HudObject ) ); + addField( "textColor", TypeColorF, Offset( mTextColor, HudObject ) ); + addField( "shadowColor", TypeColorF, Offset( mShadowColor, HudObject ) ); + addField( "opacity", TypeF32, Offset( mOpacity, HudObject ) ); + addField( "showFill", TypeBool, Offset( mShowFill, HudObject ) ); + addField( "showFrame", TypeBool, Offset( mShowFrame, HudObject ) ); + addField( "use3dText", TypeBool, Offset( mUse3dText, HudObject ) ); +} + +// hudObject.cc \ No newline at end of file diff --git a/hud/hudObject.h b/hud/hudObject.h new file mode 100644 index 0000000..23e6731 --- /dev/null +++ b/hud/hudObject.h @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HUDOBJECT_H_ +#define _HUDOBJECT_H_ + +#include "gui/guiControl.h" + +/** + * This class is the top level as far as far as HUD controls go. + * This will allow for inherited behavior such as hud-movers etc. + */ +class HudObject : public GuiControl { + private: + typedef GuiControl Parent; + + protected: + void drawText( Point2I offset, char *buf ); + + public: + + HudObject(); + + // GuiControl + virtual void onRender( Point2I offset, + const RectI &updateRect, + GuiControl *firstResponder ); + + // field data + ColorF mFillColor; + ColorF mFrameColor; + ColorF mTextColor; + ColorF mShadowColor; + F32 mOpacity; + bool mShowFrame; + bool mShowFill; + bool mUse3dText; + + + static void initPersistFields(); + + DECLARE_CONOBJECT( HudObject ); +}; + +#endif + +// hudObject.h diff --git a/hud/hudZoom.cc b/hud/hudZoom.cc new file mode 100644 index 0000000..d104718 --- /dev/null +++ b/hud/hudZoom.cc @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "hud/hudBitmapCtrl.h" +#include "dgl/dgl.h" + +class HudZoom : public HudBitmapCtrl +{ + typedef HudBitmapCtrl Parent; +public: + void getPoints(Point2I *point, Point2I offset); + HudZoom(); + ~HudZoom(); + DECLARE_CONOBJECT(HudZoom); + void onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder); + void findLastColor(char *line, Vector lineStarts, ColorI *useColor); +}; + +IMPLEMENT_CONOBJECT(HudZoom); + +HudZoom::HudZoom() +{ +} + +HudZoom::~HudZoom() +{ +} + +void HudZoom::onRender(Point2I offset, const RectI &updateRect, GuiControl *firstResponder) +{ + if(mBitmapHandle) + Parent::onRender(offset, updateRect, firstResponder); + else + { + S32 x; + Point2I point[12]; + getPoints(point, offset); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_TEXTURE_2D); + + glColor4ub(mProfile->mBorderColor.red, mProfile->mBorderColor.green, mProfile->mBorderColor.blue, mProfile->mBorderColor.alpha); + + for(x=0;x<4; ++x) + { + glBegin(GL_LINE_STRIP); + glVertex2i(point[x*3 ].x, point[x*3 ].y); + glVertex2i(point[x*3+1].x, point[x*3+1].y); + glVertex2i(point[x*3+2].x, point[x*3+2].y); + glEnd(); + } + glDisable(GL_BLEND); + } +} + +void HudZoom::getPoints(Point2I *point, Point2I offset) +{ + Point2I cPoint[4]; + cPoint[0] = Point2I(offset.x+1, offset.y+1); + cPoint[1] = Point2I(offset.x-1 + mBounds.extent.x, offset.y+1); + cPoint[2] = Point2I(offset.x-1 + mBounds.extent.x, offset.y-1 + mBounds.extent.y); + cPoint[3] = Point2I(offset.x+1, offset.y-1 + mBounds.extent.y); + + point[0] = Point2I(cPoint[0].x, cPoint[0].y + mBounds.extent.y/4); + point[1] = Point2I(cPoint[0].x, cPoint[0].y); + point[2] = Point2I(cPoint[0].x + mBounds.extent.x/4, cPoint[0].y); + + point[3] = Point2I(cPoint[1].x - mBounds.extent.x/4, cPoint[1].y); + point[4] = Point2I(cPoint[1].x, cPoint[1].y); + point[5] = Point2I(cPoint[1].x, cPoint[1].y + mBounds.extent.y/4); + + point[6] = Point2I(cPoint[2].x, cPoint[2].y - mBounds.extent.y/4); + point[7] = Point2I(cPoint[2].x, cPoint[2].y); + point[8] = Point2I(cPoint[2].x - mBounds.extent.x/4, cPoint[2].y); + + point[9] = Point2I(cPoint[3].x +mBounds.extent.x/4, cPoint[3].y); + point[10] = Point2I(cPoint[3].x, cPoint[3].y); + point[11] = Point2I(cPoint[3].x, cPoint[3].y - mBounds.extent.y/4); +} \ No newline at end of file diff --git a/hud/mBezier2D.cc b/hud/mBezier2D.cc new file mode 100644 index 0000000..4776bec --- /dev/null +++ b/hud/mBezier2D.cc @@ -0,0 +1,225 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "math/mBezier2D.h" + +/** + * Constructor + * + * @param inCtrlPts Array of control points to define the curve + * @param inPtsSize Size of the array of control points + * @param numCurvePts Number of points to be calculated for the curve + */ +Bezier2D::Bezier2D( const Point2F *inCtrlPts, const U32 inPtsSize, const U32 numCurvePts ) { + mControlPoints = new Point2F[inPtsSize]; + mCurvePoints = NULL; + mLastScaledCurve = NULL; + mLastScaleValue = 1.0f; + + mNumCurvePoints = numCurvePts; + mNumCtrlPoints = inPtsSize; + + if( mNumCurvePoints < MBEZIER2D_MIN_CURVE_POINTS ) + mNumCurvePoints = MBEZIER2D_MIN_CURVE_POINTS; + else if( mNumCurvePoints > MBEZIER2D_MAX_CURVE_POINTS ) + mNumCurvePoints = MBEZIER2D_MAX_CURVE_POINTS; + + for( int i = 0; i < inPtsSize; i++ ) + mControlPoints[i] = inCtrlPts[i]; + + calcCurve(); +} + +/** + * Destructor + */ +Bezier2D::~Bezier2D() { + delete [] mControlPoints; + delete [] mCurvePoints; + + if( mLastScaledCurve != NULL ) + delete mLastScaledCurve; +} + +/** + * Calculates the curve based on the control points. + * Uses de Casteljau's algorithm. References: + * + * -http://www.css.tayloru.edu/~btoll/s99/424/res/mtu/Notes/spline/de-casteljau.htm + * -http://astronomy.swin.edu.au/pbourke/curves/bezier/ + */ +void Bezier2D::calcCurve() { + Point2F *tempPointArray = new Point2F[mNumCtrlPoints]; + + F32 precision = ( 1.0f / mNumCurvePoints ); + + if( mCurvePoints != NULL ) + delete [] mCurvePoints; + + mCurvePoints = new Point2F[mNumCurvePoints]; + S32 idx = 0; + + F32 u = 0.0f; + for( S32 i = 0; i < mNumCurvePoints; i++ ) { + for( S32 i = 0; i < mNumCtrlPoints; i++ ) + tempPointArray[i] = mControlPoints[i]; // Making copies...grrr + + u += precision; + for( S32 j = 1; j < mNumCtrlPoints; j++ ) + for( S32 k = 0; k < mNumCtrlPoints - j; k++ ) + tempPointArray[k] = ( 1 - u ) * tempPointArray[k] + u * tempPointArray[k + 1]; + + mCurvePoints[idx].set( tempPointArray[0].x, tempPointArray[0].y ); + idx++; + } + + mRescale = true; + + mCurveLength = 0.0f; + for( S32 i = 1; i < mNumCurvePoints; i++ ) { + Point2F &pt1 = mCurvePoints[i - 1]; + Point2F &pt2 = mCurvePoints[i]; + mCurveLength += mSqrt( mPow( pt2.x - pt1.x, 2.0f) + mPow( pt2.y - pt1.y, 2.0f ) ); + } +} + +/** + * Gives the curve a new set of control points and recalculates + * + * @param inCtrlPts Array of control points to define the curve + * @param inPtsSize Size of the array of control points + */ +void Bezier2D::setControlPoints( const Point2F *inCtrlPts, const U32 inPtsSize ) { + delete [] mControlPoints; + + mControlPoints = new Point2F[inPtsSize]; + + for( int i = 0; i < inPtsSize; i++ ) + mControlPoints[i] = inCtrlPts[i]; + + mNumCtrlPoints = inPtsSize; + + calcCurve(); +} + +/** + * Sets a new precision value, and recalculates the curve + * + * @param precision How precice the curve caluclations will be. 0.0f < precision < 1.0f + * Smaller value = more precise + */ +void Bezier2D::setNumCalcPoints( const U32 numCurvePts ) { + mNumCurvePoints = numCurvePts; + + calcCurve(); +} + +/** + * Returns the number of points this object is calculating + * for the Bezier it represents + * + * @return Number of curve points calculating + */ +U32 Bezier2D::getNumCurvePoints() const { + return mNumCurvePoints; +} + +/** + * Returns the number of control points in this Bezier curve + * + * @return Number of control points + */ +U32 Bezier2D::getNumCtrlPoints() const { + return mNumCtrlPoints; +} + +/** + * Returns the control points for this Bezier in an array + * + * @return Control points for this Bezier + */ +Point2F *Bezier2D::getControlPoints() const { + return mControlPoints; +} + +/** + * Returns the calculated curve points with the precision set earlier + * + * @return The points needed to render this curve + */ +Point2F *Bezier2D::getCurvePoints() const { + return mCurvePoints; +} + +/** + * Gets the length of the calculated curve + * + * @return Length of the calculated curve + */ +F32 Bezier2D::getCurveLength() const { + return mCurveLength; +} + +/** + * Scales the curve (by scaling the control points and recalculating the curve) + * Saves the last values so repeated requests for the same scale value don't require recalculation + * + * @param scaleValue Value to scale the curve by, scaleValue > 0 + * @return A Bezier2D object with the new curve contained in it + */ +Bezier2D *Bezier2D::getScaledCurve( const F32 scaleValue ) { + if( !mRescale && mLastScaleValue == scaleValue ) + return mLastScaledCurve; + + Point2F *tempPoints = new Point2F[mNumCtrlPoints]; + //F32 areaPolygon = 0.0f; + Point2F centroid( 0.0f, 0.0f ); + + for( int i = 0; i < mNumCtrlPoints; i++ ) { + tempPoints[i] = mControlPoints[i]; + + Point2F &currPt = mControlPoints[i]; + //Point2F nextPt; + //if( i + 1 == mNumCtrlPoints ) + // nextPt = mControlPoints[0]; + //else + // nextPt = mControlPoints[i + 1]; + + //areaPolygon += currPt.x * nextPt.y - nextPt.x * currPt.y; + + centroid.x += currPt.x; + centroid.y += currPt.y; + //centroid.x += ( currPt.x + nextPt.x ) * ( currPt.x * nextPt.y - nextPt.x * currPt.y ); + //centroid.y += ( currPt.y + nextPt.y ) * ( currPt.x * nextPt.y - nextPt.x * currPt.y ); + } + + //areaPolygon *= .5f; + //centroid.x *= ( 1 / 6 * areaPolygon ); + //centroid.y *= ( 1 / 6 * areaPolygon ); + centroid.x /= mNumCtrlPoints; + centroid.y /= mNumCtrlPoints; + + for( int i = 0; i < mNumCtrlPoints; i++ ) { + tempPoints[i].x -= centroid.x; + tempPoints[i].y -= centroid.y; + + tempPoints[i].x *= scaleValue; + tempPoints[i].y *= scaleValue; + + tempPoints[i].x += centroid.x; + tempPoints[i].y += centroid.y; + } + if( mLastScaledCurve != NULL ) + delete mLastScaledCurve; + + mLastScaledCurve = new Bezier2D( tempPoints, mNumCtrlPoints, mNumCurvePoints ); + mRescale = false; + + delete [] tempPoints; + + return mLastScaledCurve; +} \ No newline at end of file diff --git a/hud/mBezier2D.h b/hud/mBezier2D.h new file mode 100644 index 0000000..2451ac8 --- /dev/null +++ b/hud/mBezier2D.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- +#ifndef _BEZIER2D_H_ +#define _BEZIER2D_H_ + +// For some reason, the engine will bomb out if you try to make a curve with 50+ calculated points +#define MBEZIER2D_MIN_CURVE_POINTS 2 +#define MBEZIER2D_MAX_CURVE_POINTS 49 + +#include "math/mPoint.h" + +class Bezier2D { + + private: + Point2F *mControlPoints; + Point2F *mCurvePoints; + + U32 mNumCtrlPoints; + U32 mNumCurvePoints; + F32 mCurveLength; + + bool mRescale; + Bezier2D *mLastScaledCurve; + F32 mLastScaleValue; + + void calcCurve(); + + public: + Bezier2D( const Point2F *inCtrlPts, const U32 inPtsSize, const U32 numCurvePts ); + ~Bezier2D(); + + void setControlPoints( const Point2F *inCtrlPts, const U32 inPtsSize ); + void setNumCalcPoints( const U32 numCurvePts ); + + U32 getNumCurvePoints() const; + U32 getNumCtrlPoints() const; + Point2F *getControlPoints() const; + Point2F *getCurvePoints() const; + F32 getCurveLength() const; + + Bezier2D *getScaledCurve( const F32 scaleValue ); +}; + +#endif + +// mBezier2D.h \ No newline at end of file diff --git a/interior/floorPlanRes.cc b/interior/floorPlanRes.cc new file mode 100644 index 0000000..b2daabb --- /dev/null +++ b/interior/floorPlanRes.cc @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/stream.h" +#include "interior/FloorPlanRes.h" +#include "Math/mathIO.h" + +const U32 FloorPlanResource::smFileVersion = 0; + +FloorPlanResource::FloorPlanResource() +{ +} +FloorPlanResource::~FloorPlanResource() +{ +} + +//-------------------------------------------------------------------------- +// Define these so we can just write two vector IO functions + +static bool mathRead(Stream & S, FloorPlanResource::Area * a){ + return S.read(&a->pointCount) && S.read(&a->pointStart) && S.read(&a->plane); +} +static bool mathWrite(Stream & S, const FloorPlanResource::Area & a){ + return S.write(a.pointCount) && S.write(a.pointStart) && S.write(a.plane); +} +inline bool mathRead(Stream & S, S32 * s) { return S.read(s); } +inline bool mathWrite(Stream & S, S32 s) { return S.write(s); } + +//-------------------------------------------------------------------------- +// Read a vector of items which define mathRead(). +template +bool mathReadVector(Vector & vec, Stream & stream, const char * msg) +{ + U32 num, i; + bool Ok = true; + stream.read( & num ); + vec.setSize( num ); + for( i = 0; i < num && Ok; i++ ){ + Ok = mathRead(stream, & vec[i]); + AssertISV( Ok, avar("math vec read error (%s) on elem %d", msg, i) ); + } + return Ok; +} +// Write a vector of items which define mathWrite(). +template +bool mathWriteVector(const Vector & vec, Stream & stream, const char * msg) +{ + bool Ok = true; + stream.write( vec.size() ); + for( U32 i = 0; i < vec.size() && Ok; i++ ) { + Ok = mathWrite(stream, vec[i]); + AssertISV( Ok, avar("math vec write error (%s) on elem %d", msg, i) ); + } + return Ok; +} + +//-------------------------------------------------------------------------- + +bool FloorPlanResource::read(Stream& stream) +{ + AssertFatal(stream.hasCapability(Stream::StreamRead), "FLR::read: non-readable stream"); + AssertFatal(stream.getStatus() == Stream::Ok, "FLR::read: Error, weird stream state"); + + // Version this stream + U32 fileVersion, DohVal; + stream.read(&fileVersion); + if (fileVersion != smFileVersion) { + AssertFatal(false, "FLR::read: incompatible file version found."); + return false; + } + + // For expansion purposes + stream.read(&DohVal); stream.read(&DohVal); stream.read(&DohVal); + + // Read the vectors + mathReadVector( mPlaneTable, stream, "FLR: mPlaneTable" ); + mathReadVector( mPointTable, stream, "FLR: mPointTable" ); + mathReadVector( mPointLists, stream, "FLR: mPointLists" ); + mathReadVector( mAreas, stream, "FLR: mAreas" ); + + return (stream.getStatus() == Stream::Ok); +} + +//-------------------------------------------------------------------------- + +bool FloorPlanResource::write(Stream& stream) const +{ + AssertFatal(stream.hasCapability(Stream::StreamWrite), "FLR::write: non-writeable stream"); + AssertFatal(stream.getStatus() == Stream::Ok, "FLR::write: Error, weird stream state"); + + // Version the stream + stream.write(smFileVersion); + + U32 Doh = 0xD0bD0b; // So we don't later say Doh! + stream.write(Doh); stream.write(Doh); stream.write(Doh); + + // Write the vectors + mathWriteVector( mPlaneTable, stream, "FLR: mPlaneTable" ); + mathWriteVector( mPointTable, stream, "FLR: mPointTable" ); + mathWriteVector( mPointLists, stream, "FLR: mPointLists" ); + mathWriteVector( mAreas, stream, "FLR: mAreas" ); + + return( stream.getStatus() == Stream::Ok ); +} + +//------------------------------------------------------------------------------ +// FloorPlan Resource constructor +// +ResourceInstance * constructFloorPlanFLR(Stream& stream) +{ + FloorPlanResource * pResource = new FloorPlanResource; + + if (pResource->read(stream) == true) + return pResource; + else { + delete pResource; + return NULL; + } +} diff --git a/interior/floorPlanRes.h b/interior/floorPlanRes.h new file mode 100644 index 0000000..9c47faa --- /dev/null +++ b/interior/floorPlanRes.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FLOORPLANRES_H_ +#define _FLOORPLANRES_H_ + +#ifndef _RESMANAGER_H_ +#include "Core/resManager.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MPLANE_H_ +#include "Math/mPlane.h" +#endif +class Stream; + +class FloorPlanResource : public ResourceInstance +{ + typedef ResourceInstance Parent; + static const U32 smFileVersion; + + public: + struct Area // basically a Winding, the info we need from it + { + S16 pointCount; + S32 pointStart; + S32 plane; + Area(S16 C, S32 S, S32 P) { pointCount=C; pointStart=S; plane=P; } + }; + + protected: + Vector mPlaneTable; + Vector mPointTable; + Vector mPointLists; + Vector mAreas; + + public: + FloorPlanResource(); + ~FloorPlanResource(); + + bool read(Stream& stream); + bool write(Stream& stream) const; +}; + +extern ResourceInstance * constructFloorPlanFLR(Stream& stream); + +#endif // _H_FLOORPLANRES_ diff --git a/interior/forceField.cc b/interior/forceField.cc new file mode 100644 index 0000000..dab54be --- /dev/null +++ b/interior/forceField.cc @@ -0,0 +1,472 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/forceField.h" +#include "Core/stream.h" +#include "Math/mathIO.h" +#include "console/console.h" +#include "dgl/gTexManager.h" +#include "dgl/dgl.h" +#include "PlatformWin32/platformGL.h" +#include "Collision/abstractPolyList.h" +#include "Sim/sceneObject.h" + +//-------------------------------------------------------------------------- +ForceField::ForceField() +{ + mPreppedForRender = false; + mWhite = NULL; +} + +ForceField::~ForceField() +{ + mPreppedForRender = false; + + delete mWhite; + mWhite = NULL; +} + + +bool ForceField::prepForRendering() +{ + if (mPreppedForRender == true) + return true; + + mPreppedForRender = true; + return true; +} + + +void ForceField::render(const ColorF& rColor, const F32 fade) +{ + // All our transform what not has already been specified... + + glDisable(GL_TEXTURE_2D); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glVertexPointer(3, GL_FLOAT, sizeof(Point3F), mPoints.address()); + glEnable(GL_VERTEX_ARRAY); + + for (U32 i = 0; i < mSurfaces.size(); i++) { + Surface& rSurface = mSurfaces[i]; + + glColor4f(rColor.red, rColor.green, rColor.blue, fade); + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) + glArrayElement(mWindings[j]); + glEnd(); + } + + glDisable(GL_VERTEX_ARRAY); + glDisable(GL_CULL_FACE); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- 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..832068a --- /dev/null +++ b/interior/forceField.h @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FORCEFIELD_H_ +#define _FORCEFIELD_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/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 _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif + +//-------------------------------------- forward decls. +class EditGeometry; +class InteriorInstance; +class Stream; +class TextureHandle; +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; + TextureHandle* mWhite; + + 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.cc b/interior/interior.cc new file mode 100644 index 0000000..c1ef615 --- /dev/null +++ b/interior/interior.cc @@ -0,0 +1,2173 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interior.h" +#include "scenegraph/sceneState.h" +#include "scenegraph/sceneGraph.h" + +#include "platformWIN32/platformGL.h" +#include "dgl/dgl.h" +#include "dgl/gBitmap.h" +#include "dgl/gTexManager.h" +#include "math/mMatrix.h" +#include "math/mRect.h" +#include "dgl/materialList.h" +#include "dgl/materialPropertyMap.h" +#include "interior/interiorSubObject.h" +#include "core/bitVector.h" +#include "sim/frameAllocator.h" +#include "scenegraph/sgUtil.h" +#include "platform/profiler.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; + + +// 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 (U32 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) + { + if (fdV0 == sgStoredViewport.point.x) + { + // Up against the left side + cdV1 += 2.0; + } + else if (cdV1 == sgStoredViewport.point.x + sgStoredViewport.extent.x) + { + // Up against the right side + fdV0 -= 2.0; + } + else + { + // In the middle + fdV0 -= 1.0; + cdV1 += 1.0; + } + } + 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) + { + if (fdV0 == sgStoredViewport.point.y) + { + // Up against the top + cdV1 += 2.0; + } + else if (cdV1 == sgStoredViewport.point.y + sgStoredViewport.extent.y) + { + // Up against the bottom + fdV0 -= 2.0; + } + else + { + // In the middle + fdV0 -= 1.0; + cdV1 += 1.0; + } + } + AssertFatal((fdV0 >= sgStoredViewport.point.y && + cdV1 <= sgStoredViewport.point.y + sgStoredViewport.extent.y), + "Out of bounds viewport bounds"); + + 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; + mWhite = NULL; + mWhiteRGB = NULL; + mLightFalloff = NULL; + + // By default, no alarm state, no animated light states + mHasAlarmState = false; + + mNumLightStateEntries = 0; + mNumTriggerableLights = 0; + + mLMHandle = LM_HANDLE(-1); + + mPreppedForRender = false;; + + mSearchTag = 0; + + // Bind our vectors + VECTOR_SET_ASSOCIATION(mPlanes); + VECTOR_SET_ASSOCIATION(mPoints); + VECTOR_SET_ASSOCIATION(mBSPNodes); + VECTOR_SET_ASSOCIATION(mBSPSolidLeaves); + VECTOR_SET_ASSOCIATION(mEnvironMaps); + VECTOR_SET_ASSOCIATION(mEnvironFactors); + 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; + delete mWhite; + mWhite = NULL; + delete mWhiteRGB; + mWhiteRGB = NULL; + delete mLightFalloff; + mLightFalloff = NULL; + + // remove from lightmap manager + 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 < mEnvironMaps.size(); i++) { + delete mEnvironMaps[i]; + mEnvironMaps[i] = NULL; + } + + for (i = 0; i < mSubObjects.size(); i++) { + delete mSubObjects[i]; + mSubObjects[i] = NULL; + } +} + + +//-------------------------------------------------------------------------- +bool Interior::prepForRendering() +{ + if (mPreppedForRender == true) + return true; + + // lightmap manager steals the lightmaps here... + gInteriorLMManager.addInterior(mLMHandle, mLightmaps.size(), this); + AssertFatal(!mLightmaps.size(), "Failed to process lightmaps"); + + // Load the material list + bool matListSuccess = mMaterialList->load(InteriorTexture, false); + + // And the environment maps... + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + + mEnvironMaps.setSize(mMaterialList->getMaterialCount()); + mEnvironFactors.setSize(mMaterialList->getMaterialCount()); + mValidEnvironMaps = 0; + for (U32 i = 0; i < mMaterialList->getMaterialCount(); i++) { + mEnvironFactors[i] = 1.0f; + + const char* pName = mMaterialList->getMaterialName(i); + const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntry(pName); + if (pEntry != NULL) { + if (pEntry->environMapName != NULL) { + mEnvironMaps[i] = new TextureHandle(pEntry->environMapName, MeshTexture); + mEnvironFactors[i] = pEntry->environMapFactor; + mValidEnvironMaps += mEnvironMaps[i] != NULL ? 1 : 0; + } else { + mEnvironMaps[i] = NULL; + } + } else { + mEnvironMaps[i] = NULL; + } + } + mWhite = new TextureHandle("special/whiteAlpha255", MeshTexture); + mWhiteRGB = new TextureHandle("special/whiteNoAlpha", MeshTexture); + mLightFalloff = new TextureHandle("special/lightFalloffMono", BitmapTexture, true); + + mPreppedForRender = matListSuccess; + + // Setup the average texgen length... + setupAveTexGenLength(); + + return matListSuccess; +} + + +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[0]; + sgStoredFrustum[1] = state->getBaseZoneState().frustum[1]; + sgStoredFrustum[2] = state->getBaseZoneState().frustum[2]; + sgStoredFrustum[3] = state->getBaseZoneState().frustum[3]; + sgStoredFrustum[4] = state->getNearPlane(); + sgStoredFrustum[5] = state->getFarPlane(); + } else { + sgStoredViewport = state->getZoneState(containingZone).viewport; + sgStoredFrustum[0] = state->getZoneState(containingZone).frustum[0]; + sgStoredFrustum[1] = state->getZoneState(containingZone).frustum[1]; + sgStoredFrustum[2] = state->getZoneState(containingZone).frustum[2]; + sgStoredFrustum[3] = state->getZoneState(containingZone).frustum[3]; + sgStoredFrustum[4] = state->getNearPlane(); + sgStoredFrustum[5] = state->getFarPlane(); + } + + // Camera point is given by the state. We need the projection matrix. + // OS->WS and scale are given. This is an ugly way to do this... + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + dglSetFrustum(sgStoredFrustum[0], sgStoredFrustum[1], + sgStoredFrustum[2], sgStoredFrustum[3], + sgStoredFrustum[4], sgStoredFrustum[5]); + dglGetProjection(&sgProjMatrix); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + + MatrixF finalModelView; + dglGetModelview(&finalModelView); + 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[0] = sgZoneRenderInfo[i].frustum[0]; + rState.frustum[1] = sgZoneRenderInfo[i].frustum[1]; + rState.frustum[2] = sgZoneRenderInfo[i].frustum[2]; + rState.frustum[3] = sgZoneRenderInfo[i].frustum[3]; + 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[0] = sgZoneRenderInfo[0].frustum[0]; + rState.frustum[1] = sgZoneRenderInfo[0].frustum[1]; + rState.frustum[2] = sgZoneRenderInfo[0].frustum[2]; + rState.frustum[3] = sgZoneRenderInfo[0].frustum[3]; + 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) +{ + PROFILE_START(InteriorPrepTempRender); + sgStoredViewport = state->getZoneState(containingZone).viewport; + sgStoredFrustum[0] = state->getZoneState(containingZone).frustum[0]; + sgStoredFrustum[1] = state->getZoneState(containingZone).frustum[1]; + sgStoredFrustum[2] = state->getZoneState(containingZone).frustum[2]; + sgStoredFrustum[3] = state->getZoneState(containingZone).frustum[3]; + sgStoredFrustum[4] = state->getNearPlane(); + sgStoredFrustum[5] = state->getFarPlane(); + + // Camera point is given by the state. We need the projection matrix. + // OS->WS and scale are given. This is an ugly way to do this... + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + dglSetFrustum(sgStoredFrustum[0], sgStoredFrustum[1], + sgStoredFrustum[2], sgStoredFrustum[3], + sgStoredFrustum[4], sgStoredFrustum[5]); + dglGetProjection(&sgProjMatrix); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + + MatrixF finalModelView; + dglGetModelview(&finalModelView); + 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); + destroyZoneRectVectors(); + PROFILE_END(); +} + + +//------------------------------------------------------------------------------ +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; + + U16 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; +}; + +bool Interior::useFogCoord() +{ + if (dglDoesSupportFogCoord() && smUseTexturedFog == false) + return true; + return false; +} + +void Interior::setupActivePolyList(ZoneVisDeterminer& zoneDeterminer, + SceneState* state, + const Point3F& rPoint, + const Point3F& osCamVector, + const Point3F& osZVec, + const F32 worldZ, + const Point3F& scale) +{ + PROFILE_START(InteriorSetupActivePolyList); + U32 i; + // Here's the deal. We loop through each of the zones, and create a merged master + // list of polygons that are the union of all the zones render sets. While we're + // doing this, we'll be setting up each zone's list of planes. I've got these + // processes separated out for now, but they could be merged. After we have the + // master list of polys, and the list of active planes, we'll copy the list + // (culling the backfaces) into the ActivePolyList. + + // There's some trickiness here. We use the high bit of this U16 to test against the + // flip bit in the surfaces planeindex. + U16* planeSides = (U16*)FrameAllocator::alloc(sizeof(U16) * mPlanes.size()); + + Point3F worldP = state->getCameraPosition(); + + // We'll never need more than twice the number of zones for merging... + ItrMergeStruct* mergeArray = (ItrMergeStruct*)FrameAllocator::alloc((mZones.size() * 2) * sizeof(ItrMergeStruct)); + U32 numMergeStructs = 0; + + PROFILE_START(ISAPL_Merge); + for (i = 0; i < mZones.size(); i++) + { + if (zoneDeterminer.isZoneVisible(i) == false) + continue; + + // Setup the plane directionals + for (U32 j = mZones[i].planeStart; j < mZones[i].planeStart + mZones[i].planeCount; j++) { + if (getPlane(mZonePlanes[j]).distToPlane(rPoint) >= 0.0f) + planeSides[mZonePlanes[j]] = 0x8000; + else + planeSides[mZonePlanes[j]] = 0x0000; + } + + // Create a merge struct for this zone + ItrMergeStruct& rMerge = mergeArray[numMergeStructs++]; + rMerge.array = &mZoneSurfaces[mZones[i].surfaceStart]; + rMerge.size = mZones[i].surfaceCount; + } + AssertFatal(numMergeStructs > 0, "Error, no rendered zones, big problem."); + + // Merge the arrays into the final version + U32 finalArray = 0xFFFFFFFF; + { + U32 begin = 0; + U32 end = numMergeStructs; + while ((end - begin) > 1) + { + U32 newEnd = end; + + U32 i; + for (i = begin; (i + 1) < end; i += 2) + { + ItrMergeStruct& rMerge0 = mergeArray[i]; + ItrMergeStruct& rMerge1 = mergeArray[i+1]; + + // Create the new structure to merge into + ItrMergeStruct& rMergeOut = mergeArray[newEnd++]; + rMergeOut.array = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(U16)); + + mergeSurfaceVectors(rMerge0.array, rMerge0.size, + rMerge1.array, rMerge1.size, + rMergeOut.array, + &rMergeOut.size); + } + + begin = i; + end = newEnd; + } + + finalArray = begin; + } + AssertFatal(finalArray < mZones.size() * 2, "Error, final array out of bounds!"); + + U16* output = mergeArray[finalArray].array; + U32 outputCount = mergeArray[finalArray].size; + PROFILE_END(); + + // Before we go and fog this object, we'll test the points of our bounding box. If + // they are all unfogged, then we have no need to do any fogging. If there are + // all fogged though, we cannot turn off rendering of the object, as it's possible + // that they extend into fog planes. + Point3F temp; + + PlaneF distPlane; + F32 distOffset; + // Setup the dist plane + Point3F closest = getBoundingBox().getClosestPoint(rPoint); + Point3F n = closest - rPoint; + n.convolve(scale); + distOffset = n.len(); + if (distOffset != 0) + { + distPlane.set(closest, n); + } + else + { + // Oops, we're inside the bounding box. distnormal is the view vector in object space + distPlane.set(closest, osCamVector); + } + distPlane.x /= scale.x; + distPlane.y /= scale.y; + distPlane.z /= scale.z; + + F32 maxFog = -1; + Point3F fp[2]; + fp[0] = getBoundingBox().min; + fp[1] = getBoundingBox().max; + for (i = 0; i < 8; i++) { + Point3F test; + + if (i & 0x1) test.x = fp[0].x; + else test.x = fp[1].x; + if (i & 0x2) test.y = fp[0].y; + else test.y = fp[1].y; + if (i & 0x4) test.z = fp[0].z; + else test.z = fp[1].z; + + F32 hazeVal = state->getHazeAndFog(mFabs(distPlane.distToPlane(test)) + distOffset, + (mDot(test, osZVec) + worldZ) - worldP.z); + if (hazeVal > maxFog) + maxFog = hazeVal; + } + + if (maxFog < 1.0/255.0f) { + // Unfogged. We can turn off this part of the setup... + sgFogActive = false; + } else { + // Sigh. Gotta do it + sgFogActive = true; + } + + PROFILE_START(ISAPL_Setup); + + // Point setup + U8* activePoints = (U8*)FrameAllocator::alloc(mPoints.size()); + dMemset(activePoints, 0, mPoints.size()); + + sgActivePolyList = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(16)); + sgEnvironPolyList = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(16)); + sgFogPolyList = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(16)); + sgFogTexCoords = (Point2F*)FrameAllocator::alloc(mPoints.size() * sizeof(Point2F)); + sgActivePolyListSize = 0; + sgEnvironPolyListSize = 0; + sgFogPolyListSize = 0; + + // Move conditionals outside to reduce loop branches + bool environmentActive = (dglDoesSupportARBMultitexture() && + smRenderEnvironmentMaps && + mValidEnvironMaps != 0); + + // Totally bare + if (sgFogActive && useFogCoord()) + { + if (environmentActive) { + // Environ, fc fog + for (i = 0; i < outputCount; i++) { + const Surface& rSurface = mSurfaces[output[i]]; + + // Not back faced? Add it to the list + if (((planeSides[getPlaneIndex(rSurface.planeIndex)] ^ rSurface.planeIndex) & 0x8000) == 0) + continue; + sgActivePolyList[sgActivePolyListSize++] = output[i]; + + if (mEnvironMaps[rSurface.textureIndex] != NULL) + sgEnvironPolyList[sgEnvironPolyListSize++] = output[i]; + + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + U32 index = mWindings[j]; + if (activePoints[index] == 0) { + activePoints[index] = 1; + + mPoints[index].fogCoord = state->getHazeAndFog(mFabs(distPlane.distToPlane(mPoints[index].point)) + distOffset, + (mDot(mPoints[index].point, osZVec) + worldZ) - worldP.z); +// mPoints[index].fogCoord = +// gClientSceneGraph->getFogCoord(distPlane.distToPlane(mPoints[index].point) + distOffset, +// worldZ + mDot(mPoints[index].point, osZVec)); + AssertFatal(mPoints[index].fogCoord >= 0.0f, "Error, neg fog coord!"); + } + } + } + } + else { + // No environ, FC fog + for (i = 0; i < outputCount; i++) { + const Surface& rSurface = mSurfaces[output[i]]; + // Not back faced? Add it to the list + if (((planeSides[getPlaneIndex(rSurface.planeIndex)] ^ rSurface.planeIndex) & 0x8000) == 0) + continue; + sgActivePolyList[sgActivePolyListSize++] = output[i]; + + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + U32 index = mWindings[j]; + if (activePoints[index] == 0) { + activePoints[index] = 1; + + mPoints[index].fogCoord = state->getHazeAndFog(mFabs(distPlane.distToPlane(mPoints[index].point)) + distOffset, + (mDot(mPoints[index].point, osZVec) + worldZ) - worldP.z); +// mPoints[index].fogCoord = +// gClientSceneGraph->getFogCoord(distPlane.distToPlane(mPoints[index].point) + distOffset, +// worldZ + mDot(mPoints[index].point, osZVec)); + AssertFatal(mPoints[index].fogCoord >= 0.0f, "Error, neg fog coord!"); + } + } + } + } + } + else if (sgFogActive) { + // Environment, textured fog + if (environmentActive) { + for (i = 0; i < outputCount; i++) { + const Surface& rSurface = mSurfaces[output[i]]; + // Not back faced? Add it to the list + if (((planeSides[getPlaneIndex(rSurface.planeIndex)] ^ rSurface.planeIndex) & 0x8000) == 0) + continue; + sgActivePolyList[sgActivePolyListSize++] = output[i]; + + if (mEnvironMaps[rSurface.textureIndex] != NULL) + sgEnvironPolyList[sgEnvironPolyListSize++] = output[i]; + + // Fog the unfogged points... + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + U32 pIndex = mWindings[j]; + + if (activePoints[pIndex] == 0) { + activePoints[pIndex] = 1; + + // fog. + gClientSceneGraph->getFogCoordPair(distPlane.distToPlane(mPoints[pIndex].point) + distOffset, + worldZ + mDot(mPoints[pIndex].point, osZVec), + sgFogTexCoords[pIndex].x, + sgFogTexCoords[pIndex].y); + } + } + } + } else { + // no environment, textured fog + for (i = 0; i < outputCount; i++) { + const Surface& rSurface = mSurfaces[output[i]]; + // Not back faced? Add it to the list + if (((planeSides[getPlaneIndex(rSurface.planeIndex)] ^ rSurface.planeIndex) & 0x8000) == 0) + continue; + sgActivePolyList[sgActivePolyListSize++] = output[i]; + + // Fog the unfogged points... + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + U32 pIndex = mWindings[j]; + + if (activePoints[pIndex] == 0) { + activePoints[pIndex] = 1; + + // fog. + gClientSceneGraph->getFogCoordPair(distPlane.distToPlane(mPoints[pIndex].point) + distOffset, + worldZ + mDot(mPoints[pIndex].point, osZVec), + sgFogTexCoords[pIndex].x, + sgFogTexCoords[pIndex].y); + } + } + } + } + } else { + // No Fog + if (environmentActive) { + // Environ + for (i = 0; i < outputCount; i++) { + const Surface& rSurface = mSurfaces[output[i]]; + // Not back faced? Add it to the list + if (((planeSides[getPlaneIndex(rSurface.planeIndex)] ^ rSurface.planeIndex) & 0x8000) == 0) + continue; + sgActivePolyList[sgActivePolyListSize++] = output[i]; + + if (mEnvironMaps[mSurfaces[output[i]].textureIndex] != NULL) + sgEnvironPolyList[sgEnvironPolyListSize++] = output[i]; + } + } + else + { + for (i = 0; i < outputCount; i++) { + const Surface& rSurface = mSurfaces[output[i]]; + // Not back faced? Add it to the list + if (((planeSides[getPlaneIndex(rSurface.planeIndex)] ^ rSurface.planeIndex) & 0x8000) == 0) + continue; + sgActivePolyList[sgActivePolyListSize++] = output[i]; + } + } + } + PROFILE_END(); + + PROFILE_END(); +} + + +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) ? 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 + + // 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); + 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; + } +} + + +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 Interior::rebuildVertexColors(LM_HANDLE instanceHandle, + Vector* normal, + Vector* alarm) +{ + normal->setSize(mWindings.size()); + if (mHasAlarmState) + alarm->setSize(mWindings.size()); + else + alarm->clear(); + + for (U32 i = 0; i < mSurfaces.size(); i++) + { + TextureHandle* normalLMapHandle = gInteriorLMManager.getHandle(getLMHandle(), instanceHandle, + mNormalLMapIndices[i]); + AssertFatal(normalLMapHandle != NULL, "Bad texture handle in rebuildVertexColors"); + const GBitmap* pNormalLMap = normalLMapHandle->getBitmap(); + AssertFatal(pNormalLMap != NULL, "Bad texture handle in rebuildVertexColors"); + + TextureHandle* alarmLMapHandle = NULL; + const GBitmap* pAlarmLMap = NULL; + if (mHasAlarmState) + { + alarmLMapHandle = gInteriorLMManager.getHandle(getLMHandle(), instanceHandle, + mAlarmLMapIndices[i]); + pAlarmLMap = alarmLMapHandle->getBitmap(); + } + + const Surface& rSurface = mSurfaces[i]; + const TexGenPlanes& rPlanes = mLMTexGenEQs[i]; + + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) + { + const ItrPaddedPoint& rPoint = mPoints[mWindings[j]]; + ColorI color; + + F32 s = rPlanes.planeX.distToPlane(rPoint.point); + F32 t = rPlanes.planeY.distToPlane(rPoint.point); + + s *= pNormalLMap->getWidth(); + t *= pNormalLMap->getHeight(); + AssertFatal(s >= 0.5 && s <= (F32(pNormalLMap->getWidth()) - 0.5), "Error, bad lmap coord!"); + AssertFatal(t >= 0.5 && t <= (F32(pNormalLMap->getHeight()) - 0.5), "Error, bad lmap coord!"); + s = mFloor(s); + t = mFloor(t); + + pNormalLMap->getColor(U32(s), U32(t), color); + color.alpha = 0xFF; + (*normal)[j] = color; + + if (mHasAlarmState) + { + pAlarmLMap->getColor(U32(s), U32(t), color); + color.alpha = 0xFF; + (*alarm)[j] = color; + } + } + } +} + + +//-------------------------------------------------------------------------- +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; + } +} + + diff --git a/interior/interior.h b/interior/interior.h new file mode 100644 index 0000000..ff3ede9 --- /dev/null +++ b/interior/interior.h @@ -0,0 +1,859 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 _RESMANAGER_H_ +#include "core/resManager.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif +#ifndef _MPOINT_H_ +#include "math/mPoint.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 + + +//-------------------------------------- Forward declarations +class Stream; +class EditGeometry; +class InteriorInstance; +class GBitmap; +class TextureHandle; +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 FloorPlan; +class LightInfo; +class PlaneRange; +class EditInteriorResource; + +//-------------------------------------------------------------------------- +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 FloorPlan; + friend class EditGeometry; + friend class InteriorInstance; + friend class SceneLighting; + friend class InteriorProxy; + friend class TranslucentSubObject; + friend class MirrorSubObject; + friend class InteriorConvex; + friend class InteriorLMManager; + friend class EditInteriorResource; + + //-------------------------------------- Public interfaces + public: + Interior(); + ~Interior(); + + // Misc + U32 getDetailLevel() const; + U32 getMinPixels() const; + const Box3F& getBoundingBox() const; + S32 getNumZones() const; + + // Rendering + bool prepForRendering(); + void rebuildVertexColors(LM_HANDLE instanceHandle, + Vector* normal, + Vector* alarm); + + 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 render(const bool useAlarmLighting, MaterialList* pMaterials, + const LM_HANDLE instanceHandle, + const Vector* normalVLights, + const Vector* alarmVLights); + void render_vc_tf(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE instanceHandle, + const Vector* normalVLights, + const Vector* alarmVLights); + void render_vc_fc(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE instanceHandle, + const Vector* normalVLights, + const Vector* alarmVLights); + void renderARB_vc_tf(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE instanceHandle, + const Vector* normalVLights, + const Vector* alarmVLights); + void renderARB(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE instanceHandle); + void renderARB_FC(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE instanceHandle); + void renderLights(LightInfo* pInfo, + const MatrixF& transform, + const Point3F& scale, + U32* lightSurfaces, + U32 numLightSurfaces); + + bool useFogCoord(); + + bool scopeZones(const S32 baseZone, + const Point3F& interiorRoot, + bool* interiorScopingState); + + //-------------------------------------- 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 U16, const U16, const Point3F&, const Point3F&, RayInfo*); + void buildPolyList_r(InteriorPolytope& polytope, + SurfaceHash& hash); + void scanZone_r(const U16 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); + + //-------------------------------------- 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; + + //-------------------------------------- Persistence interface + public: + bool read(Stream& stream); + bool write(Stream& stream) const; + + bool readVehicleCollision(Stream& stream); + bool writeVehicleCollision(Stream& stream) const; + + private: + static const U32 smFileVersion; + 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; + U16 frontIndex; + U16 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(U16 index) const; + bool isBSPSolidLeaf(U16 index) const; + bool isBSPEmptyLeaf(U16 index) const; + U16 getBSPSolidLeafIndex(U16 index) const; + U16 getBSPEmptyLeafZone(U16 index) const; + + void setupAveTexGenLength(); + + void truncateZoneTree(); + void truncateZoneNode(const U16); + bool getUnifiedZone(const U16, S32*); + + public: + static U16 getPlaneIndex(U16 index); + static bool planeIsFlipped(U16 index); + const PlaneF& getPlane(U16 index) 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; + + 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 = 1 << 0, + SurfaceAmbiguous = 1 << 1, + SurfaceOrphan = 1 << 2, + SurfaceSharedLMaps = 1 << 3, // Indicates that the alarm and normal states share a lightmap (for mission lighter) + SurfaceOutsideVisible = 1 << 4, + SurfaceFlagMask = (SurfaceDetail | + SurfaceAmbiguous | + SurfaceOrphan | + SurfaceSharedLMaps | + SurfaceOutsideVisible) + }; + enum ZoneFlags { + ZoneInside = 1 << 0 + }; + + const bool isSurfaceOutsideVisible(U32 surface) const; + + public: + 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; + U8 windingCount; + + U32 fanMask; // 5 + + U32 lightStateInfoStart; // 6 + + U8 mapOffsetX; // 7 + U8 mapOffsetY; + U8 mapSizeX; + U8 mapSizeY; + }; + struct NullSurface { + U32 windingStart; + + U16 planeIndex; + U8 surfaceFlags; + U8 windingCount; + }; + + //-------------------------------------- Animated lighting structures + enum LightFlags { + AnimationAmbient = 1 << 0, + AnimationLoop = 1 << 1, + AnimationFlicker = 1 << 2, + AnimationTypeMask = (1 << 3) - 1, + + AlarmLight = 1 << 3 + }; + enum LightType { + AmbientLooping = AnimationAmbient | AnimationLoop, + AmbientFlicker = AnimationAmbient | AnimationFlicker, + + TriggerableLoop = AnimationLoop, + TriggerableFlicker = AnimationFlicker, + TriggerableRamp = 0 + }; + + private: + 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) + }; + 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; + }; + + struct CoordBin { + U32 binStart; + U32 binCount; + }; + + // + private: + LM_HANDLE mLMHandle; + public: + LM_HANDLE getLMHandle() {return(mLMHandle);} + + // SceneLighting::InteriorProxy interface + const Surface & getSurface(const U32 surface) const; + const U32 getSurfaceCount() const; + const U8 getNormalLMapIndex(const U32 surface) const; + const U8 getAlarmLMapIndex(const U32 surface) const; + const U32 getWinding(const U32 index) const; + const Point3F & getPoint(const U32 index) const; + const TexGenPlanes & getLMTexGenEQ(const U32 index) const; + bool hasAlarmState() const; + const U32 getWindingCount() const; + + //-------------------------------------- Instance Data Members + private: + 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; + + ColorF mBaseAmbient; + ColorF mAlarmAmbient; + + Vector mBSPNodes; + Vector mBSPSolidLeaves; + + bool mPreppedForRender; + MaterialList* mMaterialList; + TextureHandle* mWhite; + TextureHandle* mWhiteRGB; + + TextureHandle* mLightFalloff; + + Vector mEnvironMaps; + Vector mEnvironFactors; + U32 mValidEnvironMaps; + + Vector mWindings; + + Vector mTexGenEQs; + Vector mLMTexGenEQs; + + Vector mWindingIndices; + Vector mSurfaces; + Vector mNullSurfaces; + Vector mSolidLeafSurfaces; + + // Portals and zones + Vector mZones; + Vector mZonePlanes; + Vector mZoneSurfaces; + Vector mZonePortalList; + Vector mPortals; + + // 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; + + U16 mSearchTag; + + //-------------------------------------- Private interface + private: + const char* getName(const U32 nameIndex) const; + static const char* getLightTypeString(const LightType); + S32 getZoneForPoint(const Point3F&) const; + + void debugRender(MaterialList* pMaterials, LM_HANDLE instanceHandle); + void debugRenderPortals(); + void debugNormalRenderLines(); + void debugShowDetail(); + void debugShowAmbiguous(); + void debugShowLightmaps(LM_HANDLE instanceHandle); + void debugShowPortalZones(); + void debugShowCollisionFans(); + void debugShowOrphan(); + void debugShowStrips(); + void debugShowTexturesOnly(MaterialList* pMaterials); + void debugShowLargeTextures(MaterialList* pMaterials); + void debugShowNullSurfaces(MaterialList* pMaterials); + void debugShowOutsideVisible(); + void debugShowHullSurfaces(); + void debugShowVehicleHullSurfaces(MaterialList* pMaterials); +// void debugShowVertexColors(MaterialList* pMaterials); + void debugShowDetailLevel(); + + void debugShowOrphansFinish(); + + void collisionFanFromSurface(const Surface&, U32* fan, U32* numIndices) const; + 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 setupActivePolyList(ZoneVisDeterminer&, SceneState*, + const Point3F&, const Point3F& rViewVector, + const Point3F&, + const F32 worldz, const Point3F& scale); + + void setupFog(SceneState* state); + void clearFog(); + void setOSCamPosition(const Point3F&); + + public: + void purgeLODData(); +}; + +//------------------------------------------------------------------------------ +inline bool Interior::isBSPLeafIndex(U16 index) const +{ + return (index & 0x8000) != 0; +} + +inline bool Interior::isBSPSolidLeaf(U16 index) const +{ + AssertFatal(isBSPLeafIndex(index) == true, "Error, only call for leaves!"); + return (index & 0x4000) != 0; +} + +inline bool Interior::isBSPEmptyLeaf(U16 index) const +{ + AssertFatal(isBSPLeafIndex(index) == true, "Error, only call for leaves!"); + return (index & 0x4000) == 0; +} + +inline U16 Interior::getBSPSolidLeafIndex(U16 index) const +{ + AssertFatal(isBSPSolidLeaf(index) == true, "Error, only call for leaves!"); + return U16(index & ~0xC000); +} + +inline U16 Interior::getBSPEmptyLeafZone(U16 index) const +{ + AssertFatal(isBSPEmptyLeaf(index) == true, "Error, only call for leaves!"); + 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 U16 Interior::getPlaneIndex(U16 index) +{ + return U16(index & ~0x8000); +} + +inline bool Interior::planeIsFlipped(U16 index) +{ + return bool(index >> 15); +// return (index & 0x8000) != 0; +} + +inline bool Interior::areEqualPlanes(U16 o, U16 t) const +{ + return (o & ~0x8000) == (t & ~0x8000); +} + +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 U8 Interior::getNormalLMapIndex(const U32 surface) const +{ + AssertFatal(surface < mNormalLMapIndices.size(), "invalid index"); + return(mNormalLMapIndices[surface]); +} + +inline const U8 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 bool Interior::hasAlarmState() const +{ + return(mHasAlarmState); +} + +inline const bool Interior::isSurfaceOutsideVisible(U32 surface) const +{ + AssertFatal(surface < mSurfaces.size(), "Interior::isSrufaceOutsideVisible: Invalid surface index"); + return(mSurfaces[surface].surfaceFlags & SurfaceOutsideVisible); +} + +inline const U32 Interior::getWindingCount() const +{ + return(mWindings.size()); +} + +#endif //_INTERIOR_H_ diff --git a/interior/interiorCollision.cc b/interior/interiorCollision.cc new file mode 100644 index 0000000..1b59297 --- /dev/null +++ b/interior/interiorCollision.cc @@ -0,0 +1,1623 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interior.h" +#include "Math/mSphere.h" +#include "Sim/sceneObject.h" +#include "Collision/abstractPolyList.h" + +#include "dgl/dgl.h" +#include "PlatformWin32/platformGL.h" +#include "Sim/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.1; +} + +} // 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; + U16 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.min.x, box.min.y, box.min.z); + mVertexList[1].point = Point3F(box.min.x, box.max.y, box.min.z); + mVertexList[2].point = Point3F(box.max.x, box.max.y, box.min.z); + mVertexList[3].point = Point3F(box.max.x, box.min.y, box.min.z); + mVertexList[4].point = Point3F(box.min.x, box.min.y, box.max.z); + mVertexList[5].point = Point3F(box.min.x, box.max.y, box.max.z); + mVertexList[6].point = Point3F(box.max.x, box.max.y, box.max.z); + mVertexList[7].point = Point3F(box.max.x, box.min.y, box.max.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) + 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 U16 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); + + 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, 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, 0, 1); + } else { + info->normal = s - e; + info->normal.normalize(); + } + } + + // ok.. let's get it on! get the face that was hit + info->face = U32(-1); + + const IBSPLeafSolid & rLeaf = mBSPSolidLeaves[getBSPSolidLeafIndex(node)]; + for(U32 i = 0; i < rLeaf.surfaceCount; i++) + { + U32 surfaceIndex = mSolidLeafSurfaces[rLeaf.surfaceIndex + i]; + if(isNullSurfaceIndex(surfaceIndex)) + 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; + break; + } + } + + 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-6) { + 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. Please talk to DMoore 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.max.x - testBox.min.x; + F32 ylen = testBox.max.y - testBox.min.y; + F32 zlen = testBox.max.z - testBox.min.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; + interiorBox.min = testBox.max+testBox.min; + interiorBox.min *= 0.5f; + toItr.mulP(interiorBox.min); + interiorBox.min.x *= invScalex; + interiorBox.min.y *= invScaley; + interiorBox.min.z *= invScalez; + interiorBox.max = interiorBox.min; + interiorBox.min.x -= xrad; + interiorBox.min.y -= yrad; + interiorBox.min.z -= zrad; + interiorBox.max.x += xrad; + interiorBox.max.y += yrad; + interiorBox.max.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.max - testBox.min; + 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.min + interiorBox.max; + 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(array[0], array[1], array[2]); + 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(array[0], array[1], array[2]); + 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, 1, 1)); + + 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::scanZone_r(const U16 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, 0, 0)); + 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)++; + } + } +} + +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 = getPlane(rNode.planeIndex); + if (planeIsFlipped(rNode.planeIndex)) + plane.neg(); + + 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(); +} + +bool Interior::scanZones(const Box3F& box, + const MatrixF& transform, + U16* zones, + U32* numZones) +{ + static InteriorPolytope pTope; + pTope.clear(); + pTope.buildBox(box, transform, Point3F(1, 1, 1)); + + scanZoneNew(pTope, zones, numZones); + + bool outsideToo = false; + if (*numZones != 0) { + for (U32 i = 0; i < *numZones; /**/) { + if (zones[i] == 0) { + outsideToo = true; + + zones[i] = zones[(*numZones) - 1]; + (*numZones)--; + } + else + { + i++; + } + } + } 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.min.x, query.min.x); + F32 queryMinY = getMax(mBoundingBox.min.y, query.min.y); + F32 queryMaxX = getMin(mBoundingBox.max.x, query.max.x); + F32 queryMaxY = getMin(mBoundingBox.max.y, query.max.y); + + S32 startX = S32(mFloor((queryMinX - mBoundingBox.min.x) / xBinSize)); + S32 endX = S32( mCeil((queryMaxX - mBoundingBox.min.x) / xBinSize)); + S32 startY = S32(mFloor((queryMinY - mBoundingBox.min.y) / yBinSize)); + S32 endY = S32( mCeil((queryMaxY - mBoundingBox.min.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]; + 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.min.convolve(scale); + newBox.max.convolve(scale); + mat.mul(newBox); + return newBox; +} + +Point3F InteriorConvex::support(const VectorF& v) const +{ + U32 waterMark = FrameAllocator::getWaterMark(); + if (hullId >= 0) + { + AssertFatal(hullId < pInterior->mConvexHulls.size(), "Out of bounds hull!"); + + const Interior::ConvexHull& rHull = pInterior->mConvexHulls[hullId]; + + F32* pDots = (F32*)FrameAllocator::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; + } + FrameAllocator::setWaterMark(waterMark); + 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*)FrameAllocator::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; + } + FrameAllocator::setWaterMark(waterMark); + 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]]; + + U32 storedWaterMark = FrameAllocator::getWaterMark(); + U32* pRemaps = (U32*)FrameAllocator::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++]; + + 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]); + + 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]; + } + } + + // Reset the frame allocator. It's work is done... + FrameAllocator::setWaterMark(storedWaterMark); + } + 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]]; + + U32 storedWaterMark = FrameAllocator::getWaterMark(); + U32* pRemaps = (U32*)FrameAllocator::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]]; + 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]; + } + } + + // Reset the frame allocator. It's work is done... + FrameAllocator::setWaterMark(storedWaterMark); + } +} + + +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 + U32 waterMark = FrameAllocator::getWaterMark(); + U32* pointRemapTable = reinterpret_cast(FrameAllocator::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; + } + } + } + + // Reset the watermark on the allocator + { + FrameAllocator::setWaterMark(waterMark); + } + } + 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 + U32 waterMark = FrameAllocator::getWaterMark(); + U32* pointRemapTable = reinterpret_cast(FrameAllocator::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; + } + } + } + + // Reset the watermark on the allocator + { + FrameAllocator::setWaterMark(waterMark); + } + } +} + + diff --git a/interior/interiorDebug.cc b/interior/interiorDebug.cc new file mode 100644 index 0000000..594d713 --- /dev/null +++ b/interior/interiorDebug.cc @@ -0,0 +1,803 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interior.h" +#include "console/console.h" +#include "core/color.h" +#include "platformWIN32/platformGL.h" +#include "dgl/gTexManager.h" +#include "dgl/dgl.h" +#include "math/mMatrix.h" +#include "dgl/materialList.h" +#include "dgl/gBitmap.h" + +extern U16* sgActivePolyList; +extern U32 sgActivePolyListSize; + +namespace { + +void lineLoopFromStrip(Vector& points, + Vector& windings, + U32 windingStart, + U32 windingCount) +{ + glBegin(GL_LINE_LOOP); + glVertex3fv(points[windings[windingStart]].point); + + S32 skip = windingStart + 1; + while (skip < (windingStart + windingCount)) { + glVertex3fv(points[windings[skip]].point); + skip += 2; + } + + skip -= 1; + while (skip > windingStart) { + if (skip < (windingStart + windingCount)) + glVertex3fv(points[windings[skip]].point); + skip -= 2; + } + glEnd(); +} + +void lineStrip(Vector& points, + Vector& windings, + U32 windingStart, + U32 windingCount) +{ + U32 end = 2; + + while (end < windingCount) { + // Even + glBegin(GL_LINE_LOOP); + glVertex3fv(points[windings[windingStart + end - 2]].point); + glVertex3fv(points[windings[windingStart + end - 1]].point); + glVertex3fv(points[windings[windingStart + end - 0]].point); + glEnd(); + + end++; + if (end >= windingCount) + break; + + glBegin(GL_LINE_LOOP); + glVertex3fv(points[windings[windingStart + end - 1]].point); + glVertex3fv(points[windings[windingStart + end - 2]].point); + glVertex3fv(points[windings[windingStart + end - 0]].point); + glEnd(); + end++; + } +} + +} // namespace {} + + +void Interior::debugRender(MaterialList* pMaterials, LM_HANDLE instanceHandle) +{ + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(ItrPaddedPoint), mPoints.address()); + + switch (smRenderMode) { + case NormalRenderLines: + debugNormalRenderLines(); + break; + + case ShowDetail: + debugShowDetail(); + break; + + case ShowAmbiguous: + debugShowAmbiguous(); + break; + + case ShowLightmaps: + debugShowLightmaps(instanceHandle); + break; + + case ShowPortalZones: + debugShowPortalZones(); + break; + + case ShowCollisionFans: + debugShowCollisionFans(); + break; + + case ShowOrphan: + debugShowOrphan(); + break; + + case ShowStrips: + debugShowStrips(); + break; + + case ShowTexturesOnly: + debugShowTexturesOnly(pMaterials); + break; + + case ShowNullSurfaces: + debugShowNullSurfaces(pMaterials); + break; + + case ShowLargeTextures: + debugShowLargeTextures(pMaterials); + break; + + case ShowOutsideVisible: + debugShowOutsideVisible(); + break; + + case ShowHullSurfaces: + debugShowHullSurfaces(); + break; + + case ShowVehicleHullSurfaces: + debugShowVehicleHullSurfaces(pMaterials); + break; + + case ShowVertexColors: +// debugShowVertexColors(pMaterials); + break; + + case ShowDetailLevel: + debugShowDetailLevel(); + break; + + default: + AssertWarn(false, "Warning! Misunderstood debug render mode. Defaulting to ShowDetail"); + debugShowDetail(); + break; + } + + glDisableClientState(GL_VERTEX_ARRAY); +} + +void Interior::debugNormalRenderLines() +{ + // Ok, our verts are set up, draw our polys. + U32 currentlyBound = U32(-1); + U32 currentTexGen = U32(-1); + + // Base textures + glBlendFunc(GL_ONE, GL_ZERO); + glDisable(GL_TEXTURE_2D); + glColor3f(1, 0, 1); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + } + glEnable(GL_TEXTURE_2D); +} + + +void Interior::debugShowDetail() +{ + glDisable(GL_TEXTURE_2D); + + glBlendFunc(GL_ONE, GL_ZERO); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + if (rSurface.surfaceFlags & SurfaceDetail) + glColor3f(1.0f, 0, 0); + else { + if (smFocusedDebug == true) + continue; + else + glColor3f(1.0f, 1.0f, 1.0f); + } + + glDrawElements(GL_TRIANGLE_STRIP, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + } + + if (smFocusedDebug == false) { + glColor3f(0, 0, 0); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + } + } + + glEnable(GL_TEXTURE_2D); +} + + +void Interior::debugShowAmbiguous() +{ + glDisable(GL_TEXTURE_2D); + glBlendFunc(GL_ONE, GL_ZERO); + + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + if (rSurface.surfaceFlags & SurfaceAmbiguous) + glColor3f(0, 1.0f, 0); + else { + if (smFocusedDebug == true) + continue; + else + glColor3f(1.0f, 1.0f, 1.0f); + } + + glDrawElements(GL_TRIANGLE_STRIP, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + } + + if (smFocusedDebug == false) { + glColor3f(0, 0, 0); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + } + } + + glEnable(GL_TEXTURE_2D); +} + + +void Interior::debugShowLightmaps(LM_HANDLE instanceHandle) +{ + glBlendFunc(GL_ONE, GL_ZERO); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + U32 currentlyBound = U32(-1); + + U32 i; + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + if (mNormalLMapIndices[sgActivePolyList[i]] != currentlyBound) { + glBindTexture(GL_TEXTURE_2D, gInteriorLMManager.getHandle(mLMHandle, instanceHandle, mNormalLMapIndices[sgActivePolyList[i]])->getGLName()); + currentlyBound = mNormalLMapIndices[sgActivePolyList[i]]; + } + + // Draw the poly + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + glTexCoord2f(mLMTexGenEQs[sgActivePolyList[i]].planeX.distToPlane(mPoints[mWindings[j]].point), + mLMTexGenEQs[sgActivePolyList[i]].planeY.distToPlane(mPoints[mWindings[j]].point)); + glVertex3fv(mPoints[mWindings[j]].point); + } + glEnd(); + } + + glDisable(GL_TEXTURE_2D); + glColor3f(0, 0, 0); + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + } + glEnable(GL_TEXTURE_2D); +} + + +void Interior::debugShowPortalZones() +{ + static U8 colors[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 } + }; + + glDisable(GL_TEXTURE_2D); + glBlendFunc(GL_ONE, GL_ZERO); + + for (U32 i = 0; i < mZones.size(); i++) { + U8* color; + if (i == 0) + color = colors[0]; + else + color = colors[(i % 13) + 1]; + + for (U32 j = mZones[i].surfaceStart; j < mZones[i].surfaceStart + mZones[i].surfaceCount; j++) { + const Surface& rSurface = mSurfaces[mZoneSurfaces[j]]; + + glColor3ub(color[0], color[1], color[2]); + glDrawElements(GL_TRIANGLE_STRIP, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + glColor3ub(0, 0, 0); + lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + } + } + + glEnable(GL_TEXTURE_2D); + debugRenderPortals(); +} + +void Interior::debugRenderPortals() +{ + //-------------------------------------- Render portals... + glDisable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + 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; + + glColor4f(0.75, 0.5, 0.75, 0.45); + glBegin(GL_TRIANGLE_FAN); + for (k = 0; k < rFan.windingCount; k++) + glVertex3fv(mPoints[mWindings[rFan.windingStart + k]].point); + glEnd(); + glColor4f(0, 0, 1, 1); + glBegin(GL_LINE_LOOP); + for (k = 0; k < rFan.windingCount; k++) + glVertex3fv(mPoints[mWindings[rFan.windingStart + k]].point); + glEnd(); + } + } +} + +void Interior::debugShowCollisionFans() +{ + glDisable(GL_TEXTURE_2D); + glBlendFunc(GL_ONE, GL_ZERO); + + U32 i; + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + U32 numIndices; + U32 fanIndices[32]; + collisionFanFromSurface(rSurface, fanIndices, &numIndices); + + glColor3f(1.0f, 1.0f, 1.0f); + glDrawElements(GL_TRIANGLE_FAN, numIndices, GL_UNSIGNED_INT, fanIndices); + } + + glColor3f(0, 0, 0); + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + U32 numIndices; + U32 fanIndices[32]; + collisionFanFromSurface(rSurface, fanIndices, &numIndices); + glBegin(GL_LINE_LOOP); + for (U32 j = 0; j < numIndices; j++) + glVertex3fv(mPoints[fanIndices[j]].point); + glEnd(); + } + + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + U32 numIndices; + U32 fanIndices[32]; + collisionFanFromSurface(rSurface, fanIndices, &numIndices); + + glColor3f(1, 0, 0); + glBegin(GL_LINES); + 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.4; + else + up += norm * 0.4; + + glVertex3fv(mPoints[fanIndices[j]].point); + glVertex3fv(up); + } + glEnd(); + } + + glEnable(GL_TEXTURE_2D); +} + +void Interior::debugShowOrphan() +{ + glDisable(GL_TEXTURE_2D); + glBlendFunc(GL_ONE, GL_ZERO); + + U32 i; + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + if (rSurface.surfaceFlags & SurfaceOrphan) + glColor3f(0.0f, 0.0f, 1.0f); + else { + if (smFocusedDebug == true) + continue; + else + glColor3f(1.0f, 1.0f, 1.0f); + } + + glDrawElements(GL_TRIANGLE_STRIP, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + } + + if (smFocusedDebug == false) { + glColor3f(0, 0, 0); + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + } + } + + glEnable(GL_TEXTURE_2D); +} + +void Interior::debugShowOrphansFinish() +{ + +} + +void Interior::debugShowStrips() +{ + static U8 colors[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 } + }; + + glDisable(GL_TEXTURE_2D); + glBlendFunc(GL_ONE, GL_ZERO); + + U32 color = 0; + U32 i; + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + glColor3ub(colors[sgActivePolyList[i]%14][0], + colors[sgActivePolyList[i]%14][1], + colors[sgActivePolyList[i]%14][2]); + color++; + glDrawElements(GL_TRIANGLE_STRIP, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + } + + glColor3f(0, 0, 0); + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + lineStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + } + + glEnable(GL_TEXTURE_2D); +} + + +void Interior::debugShowNullSurfaces(MaterialList* pMaterials) +{ + glBlendFunc(GL_ONE, GL_ZERO); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + // Base textures + U32 currentlyBound = U32(-1); + U32 i; + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + U32 baseName = pMaterials->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound) { + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound = baseName; + } + + // Draw the poly + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + glTexCoord2f(mTexGenEQs[rSurface.texGenIndex].planeX.distToPlane(mPoints[mWindings[j]].point), + mTexGenEQs[rSurface.texGenIndex].planeY.distToPlane(mPoints[mWindings[j]].point)); + glVertex3fv(mPoints[mWindings[j]].point); + } + glEnd(); + } + + glDisable(GL_TEXTURE_2D); + glBlendFunc(GL_ONE, GL_ZERO); + + glColor3f(1, 0, 0); + for (i = 0; i < mNullSurfaces.size(); i++) { + const NullSurface& rSurface = mNullSurfaces[i]; + + glDrawElements(GL_TRIANGLE_FAN, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + } + + glEnable(GL_TEXTURE_2D); +} + +void Interior::debugShowTexturesOnly(MaterialList* pMaterials) +{ + glBlendFunc(GL_ONE, GL_ZERO); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + // Base textures + U32 currentlyBound = U32(-1); + U32 i; + + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + if (pMaterials->getMaterial(rSurface.textureIndex).getGLName() != currentlyBound) { + glBindTexture(GL_TEXTURE_2D, pMaterials->getMaterial(rSurface.textureIndex).getGLName()); + currentlyBound = mMaterialList->getMaterial(rSurface.textureIndex).getGLName(); + } + + // Draw the poly + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + glTexCoord2f(mTexGenEQs[rSurface.texGenIndex].planeX.distToPlane(mPoints[mWindings[j]].point), + mTexGenEQs[rSurface.texGenIndex].planeY.distToPlane(mPoints[mWindings[j]].point)); + glVertex3fv(mPoints[mWindings[j]].point); + } + glEnd(); + } +} + +void Interior::debugShowLargeTextures(MaterialList* pMaterials) +{ + glBlendFunc(GL_ONE, GL_ZERO); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // Base textures + U32 currentlyBound = U32(-1); + U32 currentTexGen = U32(-1); + U32 i; + + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + U32 baseName = pMaterials->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound) { + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound = baseName; + + U32 width = pMaterials->getMaterial(rSurface.textureIndex).getWidth(); + U32 height = pMaterials->getMaterial(rSurface.textureIndex).getHeight(); + if (width >= 256 || height >= 256) { + if (width == 256 && height == 256) { + // small large + glColor3f(0.25, 0.25, 1); + } else if (width != 512 || height != 512) { + // thin large + glColor3f(0.25, 1, 0.25); + } else { + // oh god. + glColor3f(1, 0.25, 0.25); + } + } else { + glColor3f(0.35, 0.35, 0.35); + } + } + + // Draw the poly + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + glTexCoord2f(mTexGenEQs[rSurface.texGenIndex].planeX.distToPlane(mPoints[mWindings[j]].point), + mTexGenEQs[rSurface.texGenIndex].planeY.distToPlane(mPoints[mWindings[j]].point)); + glVertex3fv(mPoints[mWindings[j]].point); + } + glEnd(); + } +} + + +void Interior::debugShowOutsideVisible() +{ + glDisable(GL_TEXTURE_2D); + + glBlendFunc(GL_ONE, GL_ZERO); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + if (rSurface.surfaceFlags & SurfaceOutsideVisible) + glColor3f(1.0f, 0, 0); + else { + if (smFocusedDebug == true) + continue; + else + glColor3f(1.0f, 1.0f, 1.0f); + } + + glDrawElements(GL_TRIANGLE_STRIP, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + } + + if (smFocusedDebug == false) { + glColor3f(0, 0, 0); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + } + } + + glEnable(GL_TEXTURE_2D); +} + +void Interior::debugShowHullSurfaces() +{ + glDisable(GL_TEXTURE_2D); + + static U8 colors[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 } + }; + U32 color = 0; + + glBlendFunc(GL_ONE, GL_ZERO); + 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)) { + } else { + const Interior::Surface& rSurface = mSurfaces[index]; + U32 fanVerts[32]; + U32 numVerts; + collisionFanFromSurface(rSurface, fanVerts, &numVerts); + + glColor3ub(colors[(i%13)+1][0], colors[(i%13)+1][1], colors[(i%13)+1][2]); + color++; + Point3F center(0, 0, 0); + glBegin(GL_TRIANGLE_FAN); + for (U32 k = 0; k < numVerts; k++) { + glVertex3fv(mPoints[fanVerts[k]].point); + center += mPoints[fanVerts[k]].point; + } + glEnd(); + center /= F32(numVerts); + glColor3f(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); + glBegin(GL_LINES); + glVertex3fv(center); + glVertex3fv(center + (plane * 0.25)); + glEnd(); + } + } + } + + glEnable(GL_TEXTURE_2D); +} + +void Interior::debugShowVehicleHullSurfaces(MaterialList* pMaterials) +{ + glBlendFunc(GL_ONE, GL_ZERO); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + // Base textures + U32 currentlyBound = U32(-1); + U32 i; + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + U32 baseName = pMaterials->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound) { + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound = baseName; + } + + // Draw the poly + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + glTexCoord2f(mTexGenEQs[rSurface.texGenIndex].planeX.distToPlane(mPoints[mWindings[j]].point), + mTexGenEQs[rSurface.texGenIndex].planeY.distToPlane(mPoints[mWindings[j]].point)); + glVertex3fv(mPoints[mWindings[j]].point); + } + glEnd(); + } + + glDisable(GL_TEXTURE_2D); + glBlendFunc(GL_ONE, GL_ZERO); + glVertexPointer(3, GL_FLOAT, sizeof(ItrPaddedPoint), mVehiclePoints.address()); + + glColor3f(1, 0, 0); + for (i = 0; i < mVehicleNullSurfaces.size(); i++) { + const NullSurface& rSurface = mNullSurfaces[i]; + + glDrawElements(GL_TRIANGLE_FAN, rSurface.windingCount, GL_UNSIGNED_INT, &mVehicleWindings[rSurface.windingStart]); + } + + glEnable(GL_TEXTURE_2D); +} + + +// void Interior::debugShowVertexColors(MaterialList* /*pMaterials*/) +// { +// glDisable(GL_TEXTURE_2D); +// glBlendFunc(GL_ONE, GL_ZERO); + +// for (U32 i = 0; i < sgActivePolyListSize; i++) { +// const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + +// glBegin(GL_TRIANGLE_STRIP); +// for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) +// { +// const ItrPaddedPoint& rPoint = mPoints[mWindings[j]]; +// glColor3ub(mVertexColorsNormal[j].red, +// mVertexColorsNormal[j].green, +// mVertexColorsNormal[j].blue); +// glVertex3fv(rPoint.point); +// } +// glEnd(); +// } + +// if (smFocusedDebug == false) { +// glColor3f(0, 0, 0); +// for (U32 i = 0; i < sgActivePolyListSize; i++) { +// const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; +// lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); +// } +// } + +// glEnable(GL_TEXTURE_2D); +// } + + +void Interior::debugShowDetailLevel() +{ + static U8 colors[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 } + }; + + glDisable(GL_TEXTURE_2D); + + glBlendFunc(GL_ONE, GL_ZERO); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + glColor3ubv(colors[getDetailLevel()]); + glDrawElements(GL_TRIANGLE_STRIP, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + } + + if (smFocusedDebug == false) { + glColor3f(0, 0, 0); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + } + } + + glEnable(GL_TEXTURE_2D); +} + + diff --git a/interior/interiorIO.cc b/interior/interiorIO.cc new file mode 100644 index 0000000..bee9ca1 --- /dev/null +++ b/interior/interiorIO.cc @@ -0,0 +1,1128 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interior.h" +#include "core/bitVector.h" +#include "core/stream.h" +#include "math/mathIO.h" +#include "dgl/gBitmap.h" +#include "dgl/materialList.h" +#include "interior/interiorSubObject.h" +#include "platformWIN32/platformGL.h" +#include "console/console.h" +#include "sim/frameAllocator.h" + +int QSORT_CALLBACK cmpU32(const void* p1, const void* p2) +{ + return S32(*((U32*)p1)) - S32(*((U32*)p2)); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- PERSISTENCE IMPLEMENTATION +// +const U32 Interior::smFileVersion = 0; + +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); + 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); + 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); + } + + // 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); + } + + // 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); + 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]); + + // 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); + 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].textureIndex); + stream.read(&mSurfaces[i].texGenIndex); + stream.read(&mSurfaces[i].surfaceFlags); + stream.read(&mSurfaces[i].fanMask); + readLMapTexGen(stream, mLMTexGenEQs[i].planeX, mLMTexGenEQs[i].planeY); + + stream.read(&mSurfaces[i].lightCount); + stream.read(&mSurfaces[i].lightStateInfoStart); + stream.read(&mSurfaces[i].mapOffsetX); + stream.read(&mSurfaces[i].mapOffsetY); + stream.read(&mSurfaces[i].mapSizeX); + stream.read(&mSurfaces[i].mapSizeY); + } + + // NormalLMapIndices + stream.read(&vectorSize); + mNormalLMapIndices.setSize(vectorSize); + stream.read(mNormalLMapIndices.size(), mNormalLMapIndices.address()); + + // AlarmLMapIndices + stream.read(&vectorSize); + mAlarmLMapIndices.setSize(vectorSize); + stream.read(mAlarmLMapIndices.size(), mAlarmLMapIndices.address()); + + // 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); + stream.read(&mNullSurfaces[i].windingCount); + } + + // mLightmaps + stream.read(&vectorSize); + mLightmaps.setSize(vectorSize); + mLightmapKeep.setSize(vectorSize); + for (i = 0; i < mLightmaps.size(); i++) { + mLightmaps[i] = new GBitmap; + mLightmaps[i]->readPNG(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); + } + + stream.read(&vectorSize); + mConvexHullEmitStrings.setSize(vectorSize); + stream.read(mConvexHullEmitStrings.size(), mConvexHullEmitStrings.address()); + + stream.read(&vectorSize); + mHullIndices.setSize(vectorSize); + for (i = 0; i < mHullIndices.size(); i++) + stream.read(&mHullIndices[i]); + + stream.read(&vectorSize); + mHullPlaneIndices.setSize(vectorSize); + for (i = 0; i < mHullPlaneIndices.size(); i++) + stream.read(&mHullPlaneIndices[i]); + + stream.read(&vectorSize); + mHullEmitStringIndices.setSize(vectorSize); + for (i = 0; i < mHullEmitStringIndices.size(); i++) + stream.read(&mHullEmitStringIndices[i]); + + stream.read(&vectorSize); + mHullSurfaceIndices.setSize(vectorSize); + for (i = 0; i < mHullSurfaceIndices.size(); i++) + stream.read(&mHullSurfaceIndices[i]); + + stream.read(&vectorSize); + mPolyListPlanes.setSize(vectorSize); + for (i = 0; i < mPolyListPlanes.size(); i++) + stream.read(&mPolyListPlanes[i]); + + stream.read(&vectorSize); + mPolyListPoints.setSize(vectorSize); + for (i = 0; i < mPolyListPoints.size(); i++) + stream.read(&mPolyListPoints[i]); + + 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); + } + stream.read(&vectorSize); + mCoordBinIndices.setSize(vectorSize); + for (i = 0; i < mCoordBinIndices.size(); i++) + stream.read(&mCoordBinIndices[i]); + stream.read(&mCoordBinMode); + + // Ambient colors + stream.read(&mBaseAmbient); + stream.read(&mAlarmAmbient); + + // For future expandability + U32 dummy; + stream.read(&dummy); if (dummy != 0) return false; + stream.read(&dummy); if (dummy != 0) return false; + stream.read(&dummy); if (dummy != 0) return false; + stream.read(&dummy); if (dummy != 0) return false; + + // Setup the zone planes + setupZonePlanes(); + truncateZoneTree(); + + 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); + 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); + } + + // 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); + stream.write(mZones[i].flags); + } + + // Zone surfaces + stream.write(mZoneSurfaces.size()); + for (i = 0; i < mZoneSurfaces.size(); i++) + stream.write(mZoneSurfaces[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); + stream.write(mSurfaces[i].windingCount); + 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); + stream.write(mSurfaces[i].mapOffsetX); + stream.write(mSurfaces[i].mapOffsetY); + stream.write(mSurfaces[i].mapSizeX); + stream.write(mSurfaces[i].mapSizeY); + } + // NormalLMapIndices + stream.write(mNormalLMapIndices.size()); + stream.write(mNormalLMapIndices.size(), mNormalLMapIndices.address()); + + // AlarmLMapIndices + stream.write(mAlarmLMapIndices.size()); + stream.write(mAlarmLMapIndices.size(), mAlarmLMapIndices.address()); + + + // 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); + stream.write(mNullSurfaces[i].windingCount); + } + + // mLightmaps + stream.write(mLightmaps.size()); + for (i = 0; i < mLightmaps.size(); i++) { + mLightmaps[i]->writePNG(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); + } + + 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); + + // For future expandability + stream.write(U32(0)); + stream.write(U32(0)); + stream.write(U32(0)); + 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::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: + AssertISV(false, "Invalid st coord encoding in Interior::readLMapTG"); + } + + 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 U16 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 U16 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.cc b/interior/interiorInstance.cc new file mode 100644 index 0000000..db595fa --- /dev/null +++ b/interior/interiorInstance.cc @@ -0,0 +1,1965 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interiorInstance.h" +#include "interior/lightUpdateGrouper.h" +#include "interior/interior.h" +#include "interior/interiorSubObject.h" +#include "console/consoleTypes.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sceneState.h" +#include "core/bitStream.h" +#include "platformWIN32/platformGL.h" +#include "dgl/dgl.h" +#include "dgl/gBitmap.h" +#include "math/mathIO.h" +#include "dgl/materialList.h" +#include "editor/editor.h" +#include "interior/interiorResObjects.h" +#include "game/trigger.h" +#include "sim/simPath.h" +#include "interior/forceField.h" +#include "dgl/materialList.h" +#include "scenegraph/lightManager.h" +#include "collision/convex.h" +#include "audio/audioDataBlock.h" +#include "sim/frameAllocator.h" + +#include "sim/netConnection.h" +#include "platform/profiler.h" + +//-------------------------------------------------------------------------- +//-------------------------------------- Local classes, data, and functions +// +namespace { + +const U32 csgMaxZoneSize = 256; +bool sgScopeBoolArray[256]; + +class InteriorRenderImage : public SceneRenderImage +{ + public: + InteriorRenderImage() : mDetailLevel(0) { } + + U32 mDetailLevel; + U32 mBaseZone; +}; +#if defined(DEBUG) || defined (INTERNAL_RELEASE) +void cInteriorSetRenderMode(SimObject*, S32, const char **argv) +{ + S32 mode = dAtoi(argv[1]); + if (mode < 0 || mode > Interior::ShowDetailLevel) + mode = 0; + + Interior::smRenderMode = mode; +} + +void cInteriorSetFocusedDebug(SimObject*, S32, const char** argv) +{ + if (dAtob(argv[1])) { + Interior::smFocusedDebug = true; + } else { + Interior::smFocusedDebug = false; + } +} +#endif +void cInteriorSetAlarmMode(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != 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(obj); + if (interior->isClientObject()) { + Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); + return; + } + + interior->setAlarmMode(alarm); +} + +void cInteriorActivateLight(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, + "Error, how did a non-interior get here?"); + + InteriorInstance* interior = static_cast(obj); + if (interior->isClientObject()) { + Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); + return; + } + + const char* pLightName = argv[2]; + interior->activateLight(pLightName); +} + +void cInteriorDeactivateLight(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, + "Error, how did a non-interior get here?"); + + InteriorInstance* interior = static_cast(obj); + if (interior->isClientObject()) { + Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); + return; + } + + const char* pLightName = argv[2]; + interior->deactivateLight(pLightName); +} + +void cInteriorEchoTriggerableLights(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, + "Error, how did a non-interior get here?"); + + InteriorInstance* interior = static_cast(obj); + if (interior->isClientObject()) { + Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); + return; + } + + interior->echoTriggerableLights(); +} + +void cInteriorAddChildren(SimObject* obj, S32, const char**) +{ + AssertFatal(dynamic_cast(obj) != NULL, + "Error, how did a non-interior get here?"); + + InteriorInstance* interior = static_cast(obj); + if (interior->isClientObject()) { + Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); + return; + } + + interior->addChildren(); +} + +void cInteriorSetSkinBase(SimObject* obj, S32, const char** argv) +{ + AssertFatal(dynamic_cast(obj) != NULL, + "Error, how did a non-interior get here?"); + + InteriorInstance* interior = static_cast(obj); + if (interior->isClientObject()) { + Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); + return; + } + + interior->setSkinBase(argv[2]); +} + +static bool cIsPointInside(SimObject *, S32 argc, const char ** argv) +{ + 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], "%f %f %f", &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); +} + +static S32 cInteriorGetNumDetailLevels(SimObject * obj, S32, const char **) +{ + InteriorInstance * instance = static_cast(obj); + return(instance->getNumDetailLevels()); +} + +static void cInteriorSetDetailLevel(SimObject * obj, S32, const char ** argv) +{ + InteriorInstance * instance = static_cast(obj); + if(instance->isServerObject()) + { + NetConnection * toServer = NetConnection::getServerConnection(); + 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])); +} + +} // namespace {} + +//-------------------------------------------------------------------------- +//-------------------------------------- Static functions +// +IMPLEMENT_CO_NETOBJECT_V1(InteriorInstance); + +//------------------------------------------------------------------------------ +//-------------------------------------- InteriorInstance +// +bool InteriorInstance::smDontRestrictOutside = false; +bool InteriorInstance::smRenderDynamicLights = true; +U32 InteriorInstance::smLightUpdatePeriod = 66; // 66 ms between updates +F32 InteriorInstance::smDetailModification = 1.0f; + + +InteriorInstance::InteriorInstance() +{ + mAlarmState = false; + + mInteriorFileName = NULL; + mTypeMask = InteriorObjectType | StaticObjectType | StaticRenderedObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + mUpdateGrouper = NULL; + + mShowTerrainInside = false; + + mSkinBase = StringTable->insert("base"); + mAudioProfile = 0; + mAudioEnvironment = 0; + mForcedDetailLevel = -1; + + mConvexList = new Convex; + mCRC = 0; +} + +InteriorInstance::~InteriorInstance() +{ + U32 i; + + for (i = 0; i < mLightInfo.size(); i++) + destructInPlace(&mLightInfo[i]); + + for (i = 0; i < mInteriorSubObjects.size(); i++) { + // DMM: Fix for first-class objects... + for (U32 j = 0; j < mInteriorSubObjects[i].size(); j++) + delete (mInteriorSubObjects[i])[j]; + + destructInPlace(&mInteriorSubObjects[i]); + } + delete mUpdateGrouper; + + delete mConvexList; + mConvexList = NULL; + + for (i = 0; i < mVertexColorsNormal.size(); i++) + { + delete mVertexColorsNormal[i]; + mVertexColorsNormal[i] = NULL; + } + for (i = 0; i < mVertexColorsAlarm.size(); i++) + { + delete mVertexColorsAlarm[i]; + mVertexColorsAlarm[i] = NULL; + } + + for (i = 0; i < mMaterialMaps.size(); i++) + { + delete mMaterialMaps[i]; + mMaterialMaps[i] = NULL; + } +} + + +void InteriorInstance::init() +{ + // Does nothing for the moment +} + + +void InteriorInstance::destroy() +{ + // Also does nothing for the moment +} + + +//-------------------------------------------------------------------------- +// Inspection +static AudioProfile * saveAudioProfile = 0; +static AudioEnvironment * saveAudioEnvironment = 0; +void InteriorInstance::inspectPreApply() +{ + saveAudioProfile = mAudioProfile; + saveAudioEnvironment = mAudioEnvironment; +} + +void InteriorInstance::inspectPostApply() +{ + if((mAudioProfile != saveAudioProfile) || (mAudioEnvironment != saveAudioEnvironment)) + setMaskBits(AudioMask); +} + +//-------------------------------------------------------------------------- +//-------------------------------------- Console functionality +// +void InteriorInstance::initPersistFields() +{ + Parent::initPersistFields(); + addField("interiorFile", TypeString, Offset(mInteriorFileName, InteriorInstance)); + addField("showTerrainInside", TypeBool, Offset(mShowTerrainInside, InteriorInstance)); + addField("audioProfile", TypeAudioProfilePtr, Offset(mAudioProfile, InteriorInstance)); + addField("audioEnvironment", TypeAudioEnvironmentPtr, Offset(mAudioEnvironment, InteriorInstance)); +} + +void InteriorInstance::consoleInit() +{ + //-------------------------------------- Class level variables + Con::addVariable("pref::Interior::LightUpdatePeriod", TypeS32, &smLightUpdatePeriod); + Con::addVariable("pref::Interior::ShowEnvironmentMaps", TypeBool, &Interior::smRenderEnvironmentMaps); + Con::addVariable("pref::Interior::DynamicLights", TypeBool, &smRenderDynamicLights); + 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!!! +#ifdef DEBUG + Con::addVariable("Interior::DontRestrictOutside", TypeBool, &smDontRestrictOutside); +#endif + + //-------------------------------------- Class level commands +#if defined(DEBUG) || defined (INTERNAL_RELEASE) + Con::addCommand("setInteriorRenderMode", cInteriorSetRenderMode, "setInteriorRenderMode(modeNum)", 2, 2); + Con::addCommand("setInteriorFocusedDebug", cInteriorSetFocusedDebug, "setInteriorFocusedDebug([\"true\" | \"false\")", 2, 2); +#endif + Con::addCommand("isPointInside", cIsPointInside, "isPointInside(point)", 2, 4); + + //-------------------------------------- Object level commands + Con::addCommand("InteriorInstance", "magicButton", cInteriorAddChildren, "[InteriorObject].magicButton()", 2, 2); + Con::addCommand("InteriorInstance", "setSkinBase", cInteriorSetSkinBase, "[InteriorObject].setSkinBase()", 3, 3); + + // Lighting + Con::addCommand("InteriorInstance", "setAlarmMode", cInteriorSetAlarmMode, "[InteriorObject].setAlarmMode(\"On\"|\"Off\")", 3, 3); + Con::addCommand("InteriorInstance", "activateLight", cInteriorActivateLight, "[InteriorObject].activateLight()", 3, 3); + Con::addCommand("InteriorInstance", "deactivateLight", cInteriorDeactivateLight, "[InteriorObject].deactivateLight()", 3, 3); + Con::addCommand("InteriorInstance", "echoTriggerableLights", cInteriorEchoTriggerableLights, "[InteriorObject].echoTriggerableLights()", 2, 2); + + Con::addCommand("InteriorInstance", "getNumDetailLevels", cInteriorGetNumDetailLevels, "[InteriorObject].getNumDetailLevels()", 2, 2); + Con::addCommand("InteriorInstance", "setDetailLevel", cInteriorSetDetailLevel, "[InteriorObject].setDetailLeve(level)", 3, 3); +} + + +//-------------------------------------------------------------------------- +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(); +} + + +//-------------------------------------------------------------------------- +// from resManager.cc +extern U32 calculateCRC(void * buffer, S32 len, U32 crcVal ); + +bool InteriorInstance::onAdd() +{ + U32 i; + + // Load resource + char buffer[256]; + dSprintf(buffer, sizeof(buffer), "interiors/%s", mInteriorFileName); + mInteriorRes = ResourceManager->load(buffer, true); + 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.getCRC()) + { + NetConnection::setLastError("Local interior file '%s' does not match version on server.", mInteriorFileName); + return false; + } + } + else + mCRC = mInteriorRes.getCRC(); + + if(!Parent::onAdd()) + return false; + + mVertexColorsNormal.setSize(mInteriorRes->getNumDetailLevels()); + mVertexColorsAlarm.setSize(mInteriorRes->getNumDetailLevels()); + for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) + { + mVertexColorsNormal[i] = new Vector; + mVertexColorsAlarm[i] = new Vector; + VECTOR_SET_ASSOCIATION((*mVertexColorsNormal[i])); + VECTOR_SET_ASSOCIATION((*mVertexColorsAlarm[i])); + } + + // 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); + + // Setup mLightInfo structure + // + mLightInfo.setSize(mInteriorRes->getNumDetailLevels()); + for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { + Interior* pInterior = mInteriorRes->getDetailLevel(i); + constructInPlace(&mLightInfo[i]); + LightInfo& rInfo = mLightInfo[i]; + + rInfo.mSurfaceInvalid.setSize(pInterior->mSurfaces.size()); + rInfo.mSurfaceInvalid.clear(); + + rInfo.mStateDataInfo.setSize(pInterior->mNumLightStateEntries); + dMemset(rInfo.mStateDataInfo.address(), 0x00, + sizeof(LightInfo::StateDataInfo) * rInfo.mStateDataInfo.size()); + + rInfo.mLights.setSize(pInterior->mAnimatedLights.size()); + for (U32 j = 0; j < rInfo.mLights.size(); j++) { + LightInfo::Light& rLight = rInfo.mLights[j]; + rLight.curState = 0; + rLight.curTime = 0; + + Interior::LightState& rState = pInterior->mLightStates[pInterior->mAnimatedLights[j].stateIndex]; + rLight.curColor.set(rState.red, rState.green, rState.blue); + rLight.active = false; + rLight.alarm = (pInterior->mAnimatedLights[j].flags & Interior::AlarmLight) != 0; + + installLight(i, j); + } + } + + // Setup lightgrouper + mUpdateGrouper = new LightUpdateGrouper(LightUpdateBitStart, LightUpdateBitEnd); + for (U32 d = 0; d < mInteriorRes->getNumDetailLevels(); d++) { + Interior* pInterior = mInteriorRes->getDetailLevel(d); + + for (U32 j = 0; j < pInterior->mNumTriggerableLights; j++) + mUpdateGrouper->addKey(makeUpdateKey(d, j)); + } + + if (isClientObject()) { + // create all the subObjects + mInteriorSubObjects.setSize(mInteriorRes->getNumDetailLevels()); + for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { + constructInPlace(&mInteriorSubObjects[i]); + + Interior* pInterior = mInteriorRes->getDetailLevel(i); + for (U32 j = 0; j < pInterior->mSubObjects.size(); j++) + mInteriorSubObjects[i].push_back(pInterior->mSubObjects[j]->clone(this)); + } + } else { + // creates all subobjects + mInteriorSubObjects.setSize(mInteriorRes->getNumDetailLevels()); + for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { + constructInPlace(&mInteriorSubObjects[i]); + + Interior* pInterior = mInteriorRes->getDetailLevel(i); + + for (U32 j = 0; j < pInterior->mSubObjects.size(); j++) + mInteriorSubObjects[i].push_back(pInterior->mSubObjects[j]->clone(this)); + } + } + + // Do any handle loading, etc. required. + if (isClientObject()) { + setLightUpdatedTime(Platform::getVirtualMilliseconds()); + + for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { + Interior* pInterior = mInteriorRes->getDetailLevel(i); + pInterior->prepForRendering(); + gInteriorLMManager.addInstance(pInterior->getLMHandle(), mLMHandle, this); + + mMaterialMaps.push_back(new MaterialList(pInterior->mMaterialList)); + + // A client interior starts up it's ambient animations on add. Ambients + // are just past the triggerables. + for (U32 j = pInterior->mNumTriggerableLights; j < pInterior->mAnimatedLights.size(); j++) + activateLight(i, j); + } + + renewOverlays(); + } else { + + } + + addToScene(); + return true; +} + + +void InteriorInstance::onRemove() +{ + mConvexList->nukeList(); + + // + if(isClientObject()) + { + if (mLMHandle != 0xFFFFFFFF) + { + for(U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) + { + Interior * pInterior = mInteriorRes->getDetailLevel(i); + if (pInterior->getLMHandle() != 0xFFFFFFFF) + gInteriorLMManager.removeInstance(pInterior->getLMHandle(), mLMHandle); + } + } + } + + removeFromScene(); + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +bool InteriorInstance::onSceneAdd(SceneGraph* pGraph) +{ + AssertFatal(bool(mInteriorRes) == true, "Error, should not have been added to the scene if there's no interior!"); + + if (Parent::onSceneAdd(pGraph) == false) + return false; + + if (mInteriorRes->getDetailLevel(0)->mZones.size() > 1) { + AssertWarn(getNumCurrZones() == 1, "There should be one and only one zone for an interior that manages zones"); + mSceneManager->registerZones(this, (mInteriorRes->getDetailLevel(0)->mZones.size() - 1)); + } + + return true; +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::onSceneRemove() +{ + AssertFatal(bool(mInteriorRes) == true, "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(bool(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(bool(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 /= 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); +} + +//-------------------------------------------------------------------------- +ColorF gInteriorFogColor(1, 1, 1); + +void InteriorInstance::renderObject(SceneState* state, SceneRenderImage* sceneImage) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + if(gEditingMission && isHidden()) + return; + PROFILE_START(InteriorRenderObject); + + PROFILE_START(IRO_GetZones); + U32 storedWaterMark = FrameAllocator::getWaterMark(); + dglSetRenderPrimType(2); + + InteriorRenderImage* interiorImage = static_cast(sceneImage); + + Point3F osPoint = state->getCameraPosition(); + mRenderWorldToObj.mulP(osPoint); + osPoint.convolveInverse(mObjScale); + + // Get the object space y vector + Point3F osCamVector; + state->mModelview.getColumn(1, &osCamVector); + mRenderWorldToObj.mulV(osCamVector); + osCamVector.convolve(getScale()); + osCamVector.normalize(); + + Point3F osZVec(0, 0, 1); + mRenderWorldToObj.mulV(osZVec); + osZVec.convolve(getScale()); + + // First, we want to test the planes and setup the fog... + U32 zoneOffset = mZoneRangeStart != 0xFFFFFFFF ? mZoneRangeStart : 0; + + Interior* pInterior = mInteriorRes->getDetailLevel(interiorImage->mDetailLevel); + + U32 baseZone = 0xFFFFFFFF; + if (getNumCurrZones() == 1) + { + baseZone = getCurrZone(0); + } + else + { + for (U32 i = 0; i < getNumCurrZones(); i++) { + if (state->getZoneState(getCurrZone(i)).render == true) { + if (baseZone == 0xFFFFFFFF) { + baseZone = getCurrZone(i); + break; + } + } + } + if (baseZone == 0xFFFFFFFF) + baseZone = getCurrZone(0); + } + PROFILE_END(); + PROFILE_START(IRO_ComputeActivePolys); + + Point3F worldOrigin; + getRenderTransform().getColumn(3, &worldOrigin); + + ZoneVisDeterminer zoneVis; + if (interiorImage->mDetailLevel == 0) { + zoneVis.runFromState(state, zoneOffset, baseZone); + pInterior->setupActivePolyList(zoneVis, state, osPoint, osCamVector, osZVec, worldOrigin.z, getScale()); + } else { + // Something else... + pInterior->prepTempRender(state, + getCurrZone(0), + 0, + mRenderObjToWorld, mObjScale, + state->mFlipCull); + zoneVis.runFromRects(state, zoneOffset, baseZone); + pInterior->setupActivePolyList(zoneVis, state, osPoint, osCamVector, osZVec, worldOrigin.z, getScale()); + } + + PROFILE_END(); + PROFILE_START(IRO_UpdateAnimatedLights); + // Update the animated lights... + if (!Interior::smUseVertexLighting) + { + LightInfo& rLightInfo = mLightInfo[interiorImage->mDetailLevel]; + downloadLightmaps(state, pInterior, rLightInfo); + } + PROFILE_END(); + PROFILE_START(IRO_RenderSolids); + + // Set up the model view and the global render state... + RectI viewport; + dglGetViewport(&viewport); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mRenderObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + glEnable(GL_BLEND); + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + + // We need to decide if we're going to use the low-res textures + Point3F camPoint = state->getCameraPosition(); + Point3F closestPoint = getRenderWorldBox().getClosestPoint(camPoint); + F32 dist = (camPoint - closestPoint).len(); + if (dist != 0.0) + { + F32 length = dglProjectRadius(dist, 1.0f / pInterior->mAveTexGenLength); + if (length < (1.0 / 16.0)) + { + TextureManager::setSmallTexturesActive(true); + } + } + + + pInterior->setupFog(state); + pInterior->setOSCamPosition(osPoint); + if (interiorImage->mBaseZone != 0) + state->setupZoneProjection(interiorImage->mBaseZone + mZoneRangeStart - 1); + else + state->setupObjectProjection(this); + + gInteriorFogColor = state->getFogColor(); + pInterior->render(mAlarmState, mMaterialMaps[interiorImage->mDetailLevel], mLMHandle, + mVertexColorsNormal[interiorImage->mDetailLevel], + mVertexColorsAlarm[interiorImage->mDetailLevel]); + pInterior->clearFog(); + + PROFILE_END(); + PROFILE_START(IRO_RenderDynamicLights); + + if (smRenderDynamicLights == true) { + S32 numLights = gClientSceneGraph->getLightManager()->getNumLights(); + if (numLights > 0) + { + U32 lightWaterMark = FrameAllocator::getWaterMark(); + ::LightInfo** lightArray = (::LightInfo**)FrameAllocator::alloc(sizeof(::LightInfo*) * numLights); + gClientSceneGraph->getLightManager()->getLights(lightArray); + for (U32 i = 0; i < numLights; i++) { + if (lightArray[i]->mType != ::LightInfo::Point) + continue; + + Point3F lightPoint = lightArray[i]->mPos; + mRenderWorldToObj.mulP(lightPoint); + lightPoint.convolveInverse(mObjScale); + + Box3F box; + box.min = lightPoint; + box.max = lightPoint; + box.min -= Point3F(lightArray[i]->mRadius, lightArray[i]->mRadius, lightArray[i]->mRadius); + box.max += Point3F(lightArray[i]->mRadius, lightArray[i]->mRadius, lightArray[i]->mRadius); + + // TODO: Account for scale... + if (mObjBox.isOverlapped(box) == false) + continue; + + // The number of light surfaces cannot exceed the total number of non-null surfaces in + // interior... + U32 subWaterMark = FrameAllocator::getWaterMark(); + U32* lightSurfaces = (U32*)FrameAllocator::alloc(pInterior->mSurfaces.size() * sizeof(U32)); + U32 numLightSurfaces = 0; + if (pInterior->buildLightPolyList(lightSurfaces, &numLightSurfaces, + box, mRenderWorldToObj, getScale()) == false) { + FrameAllocator::setWaterMark(subWaterMark); + continue; + } + + pInterior->renderLights(lightArray[i], mRenderWorldToObj, getScale(), lightSurfaces, numLightSurfaces); + FrameAllocator::setWaterMark(subWaterMark); + } + + FrameAllocator::setWaterMark(lightWaterMark); + } + } + PROFILE_END(); + + glDisable(GL_BLEND); + if (dglDoesSupportARBMultitexture()) { + glActiveTextureARB(GL_TEXTURE1_ARB); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glActiveTextureARB(GL_TEXTURE0_ARB); + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } else { + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + dglSetViewport(viewport); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); + + FrameAllocator::setWaterMark(storedWaterMark); + dglSetRenderPrimType(0); + + // Reset the small textures... + TextureManager::setSmallTexturesActive(false); + + PROFILE_END(); +} + + +//-------------------------------------------------------------------------- +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(bool(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.3) + smDetailModification = 0.3; + if (smDetailModification > 1.0) + smDetailModification = 1.0; + + // 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 + if (rState.frustum[0] < frustum[0]) frustum[0] = rState.frustum[0]; + if (rState.frustum[1] > frustum[1]) frustum[1] = rState.frustum[1]; + if (rState.frustum[2] < frustum[2]) frustum[2] = rState.frustum[2]; + if (rState.frustum[3] > frustum[3]) frustum[3] = rState.frustum[3]; + + // 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; + 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; + } + + bool render = true; + 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()); + + if (!Interior::smUseVertexLighting) + { + // Since we're rendering: update the lights and the alarm state + U32 msSinceLightsUpdated = Platform::getVirtualMilliseconds() - getLightUpdatedTime(); + if (msSinceLightsUpdated > smLightUpdatePeriod) { + setLightUpdatedTime(Platform::getVirtualMilliseconds()); + updateAllLights(msSinceLightsUpdated); + } + } + + 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->mFlipCull); + if (smDontRestrictOutside) + continueOut = true; + + InteriorRenderImage* image = new InteriorRenderImage; + image->obj = this; + image->mDetailLevel = detailLevel; + image->mBaseZone = realStartZone; + image->textureSortKey = mInteriorFileHash; + state->insertRenderImage(image); + + // Add renderimages for any sub-objects, first the independant subs, then the dependants + // for this detail... + Point3F osPoint = state->getCameraPosition(); + mRenderWorldToObj.mulP(osPoint); + osPoint.convolveInverse(mObjScale); + + U32 i; + for (i = 0; i < mInteriorSubObjects[0].size(); i++) { + InteriorSubObject* iso = (mInteriorSubObjects[0])[i]; + + if (iso->renderDetailDependant() == false) { + // We want to check the zone that this object is in. If we are traversing upwards + // though, we have no information about our parent zone, and must return the + // render image regardless. + if (modifyBaseState && iso->getZone() == 0) { + // Must return + } else { + U32 realZone; + if (iso->getZone() == 0) { + realZone = getCurrZone(0); + } else { + realZone = iso->getZone() + mZoneRangeStart - 1; + } + + if (state->getZoneState(realZone).render == false) { + // Nuke it... + continue; + } + } + + SubObjectRenderImage* sri = iso->getRenderImage(state, osPoint); + if (sri) { + sri->mDetailLevel = 0; + state->insertRenderImage(sri); + } + } + } + for (i = 0; i < mInteriorSubObjects[detailLevel].size(); i++) { + InteriorSubObject* iso = (mInteriorSubObjects[detailLevel])[i]; + + if (iso->renderDetailDependant() == true) { + // We want to check the zone that this object is in. If we are traversing upwards + // though, we have no information about our parent zone, and must return the + // render image regardless. + if (modifyBaseState && iso->getZone() == 0) { + // Must return + } else { + U32 realZone; + if (iso->getZone() == 0) { + realZone = getCurrZone(0); + } else { + realZone = iso->getZone() + mZoneRangeStart - 1; + } + + if (state->getZoneState(realZone).render == false) { + // Nuke it... + continue; + } + } + + SubObjectRenderImage* sri = iso->getRenderImage(state, osPoint); + if (sri) { + sri->mDetailLevel = detailLevel; + state->insertRenderImage(sri); + } + } + } + + PROFILE_END(); + return continueOut; +} + + +//-------------------------------------------------------------------------- +bool InteriorInstance::castRay(const Point3F& s, const Point3F& e, RayInfo* info) +{ + info->object = this; + + if (mInteriorRes->getDetailLevel(0)->castRay(s, e, info)) { + PlaneF fakePlane; + fakePlane.x = info->normal.x; + fakePlane.y = info->normal.y; + fakePlane.z = info->normal.z; + fakePlane.d = 0; + + PlaneF result; + mTransformPlane(getTransform(), getScale(), fakePlane, &result); + info->normal = result; + return true; + } + return false; +} + + +//------------------------------------------------------------------------------ +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); + + // Notify subobjects that care about the transform change... + // + if (bool(mInteriorRes)) { + for (U32 i = 0; i < mInteriorSubObjects.size(); i++) { + for (U32 j = 0; j < mInteriorSubObjects[i].size(); j++) + (mInteriorSubObjects[i])[j]->noteTransformChange(); + } + } + + 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.min.convolveInverse(mObjScale); + realBox.max.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.min.x = pInterior->mVehicleConvexHulls[hulls[i]].minX; + cp->box.min.y = pInterior->mVehicleConvexHulls[hulls[i]].minY; + cp->box.min.z = pInterior->mVehicleConvexHulls[hulls[i]].minZ; + cp->box.max.x = pInterior->mVehicleConvexHulls[hulls[i]].maxX; + cp->box.max.y = pInterior->mVehicleConvexHulls[hulls[i]].maxY; + cp->box.max.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.min.x = pInterior->mConvexHulls[hulls[i]].minX; + cp->box.min.y = pInterior->mConvexHulls[hulls[i]].minY; + cp->box.min.z = pInterior->mConvexHulls[hulls[i]].minZ; + cp->box.max.x = pInterior->mConvexHulls[hulls[i]].maxX; + cp->box.max.y = pInterior->mConvexHulls[hulls[i]].maxY; + cp->box.max.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); + + // 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); + + // Check the lights to see if we need to update any of their states + LightUpdateGrouper::BitIterator itr; + for (itr = mUpdateGrouper->begin(); itr.valid() && itr.getNumKeys(); itr++) { + if (stream->writeFlag((mask & itr.getMask()) != 0)) { + LightUpdateGrouper::BitIterator::iterator kItr; + for (kItr = itr.begin(); kItr != itr.end(); kItr++) { + U32 key = *kItr; + U32 detail = detailFromUpdateKey(key); + U32 index = indexFromUpdateKey(key); + + stream->writeFlag(mLightInfo[detail].mLights[index].active); + } + } + } + + 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()) { + // Initial Update + // CRC + stream->read(&mCRC); + + // File + mInteriorFileName = stream->readSTString(); + + // Terrain flag + mShowTerrainInside = 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; + } + else + { + // Normal update + if (stream->readFlag()) { + mathRead(*stream, &temp); + mathRead(*stream, &tempScale); + setScale(tempScale); + setTransform(temp); + } + + setAlarmMode(stream->readFlag()); + + LightUpdateGrouper::BitIterator itr; + for (itr = mUpdateGrouper->begin(); itr.valid() && itr.getNumKeys(); itr++) { + if (stream->readFlag()) { + LightUpdateGrouper::BitIterator::iterator kItr; + for (kItr = itr.begin(); kItr != itr.end(); kItr++) { + U32 key = *kItr; + U32 detail = detailFromUpdateKey(key); + U32 index = indexFromUpdateKey(key); + + if (stream->readFlag()) + activateLight(detail, index); + else + deactivateLight(detail, index); + } + } + } + + 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::rebuildVertexColors() +{ + U32 i; + for (i = 0; i < mVertexColorsNormal.size(); i++) + { + delete mVertexColorsNormal[i]; + mVertexColorsNormal[i] = NULL; + } + for (i = 0; i < mVertexColorsAlarm.size(); i++) + { + delete mVertexColorsAlarm[i]; + mVertexColorsAlarm[i] = NULL; + } + + if (bool(mInteriorRes) == false) + return; + + mVertexColorsNormal.setSize(mInteriorRes->getNumDetailLevels()); + mVertexColorsAlarm.setSize(mInteriorRes->getNumDetailLevels()); + for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) + { + mVertexColorsNormal[i] = new Vector; + mVertexColorsAlarm[i] = new Vector; + VECTOR_SET_ASSOCIATION((*mVertexColorsNormal[i])); + VECTOR_SET_ASSOCIATION((*mVertexColorsAlarm[i])); + } + for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) + { + Interior* pInterior = mInteriorRes->getDetailLevel(i); + pInterior->rebuildVertexColors(mLMHandle, + mVertexColorsNormal[i], + mVertexColorsAlarm[i]); + } +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::updateLightMap(Interior* pInterior, LightInfo& rLightInfo, const U32 surfaceIndex) +{ + AssertFatal(surfaceIndex < pInterior->mSurfaces.size(), "Error, out of range surface index"); + static U8 _newLightMapBuffer[256*256*3]; + + const Interior::Surface& rSurface = pInterior->mSurfaces[surfaceIndex]; + if (rSurface.lightCount == 0) + return; + + // Get the surface's original bitmap + TextureHandle* originalLMapHandle = ((mAlarmState == false) ? + gInteriorLMManager.getHandle(pInterior->getLMHandle(), mLMHandle, + pInterior->mNormalLMapIndices[surfaceIndex]) : + gInteriorLMManager.getHandle(pInterior->getLMHandle(), mLMHandle, + pInterior->mAlarmLMapIndices[surfaceIndex])); + + const GBitmap* pOriginalLMap = originalLMapHandle->getBitmap(); + AssertFatal(pOriginalLMap != NULL, "error, no lightmap on the handle!"); + AssertFatal(pOriginalLMap->getFormat() == GBitmap::RGB, "error, bad lightmap format!"); + + // First, we need to create a buffer that will receive the new lightmap + U32 dimX = rSurface.mapSizeX; + U32 dimY = rSurface.mapSizeY; + U8* pNewLightmap = _newLightMapBuffer; + + // copy the original lightmap + const U8 * src = pOriginalLMap->getAddress(rSurface.mapOffsetX, rSurface.mapOffsetY); + U8 * dest = pNewLightmap; + + U32 runSize = rSurface.mapSizeX * 3; + U32 srcStep = pOriginalLMap->getWidth() * 3; + + for(U32 y = 0; y < rSurface.mapSizeY; y++) + { + dMemcpy(dest, src, runSize); + dest += runSize; + src += srcStep; + } + + // ...now we have the original lightmap, add in the animateds... + for (U32 i = 0; i < rSurface.lightCount; i++) { + const LightInfo::StateDataInfo& rInfo = rLightInfo.mStateDataInfo[rSurface.lightStateInfoStart + i]; + + // Only add in states that affect this surface...duh. + if (rInfo.curMap != NULL && rInfo.alarm == (mAlarmState != Normal)) { + intensityMapMerge(pNewLightmap, dimX, dimY, + rInfo.curMap, rInfo.curColor); + } + } + + // OK, now we have the final, current lightmap. subimage it in... + glBindTexture(GL_TEXTURE_2D, originalLMapHandle->getGLName()); + + if (Con::getBoolVariable("$pref::OpenGL::disableSubImage", false)) + { + const U8 *src = pNewLightmap; + U8 *dest = (U8 *) pOriginalLMap->getAddress(rSurface.mapOffsetX, rSurface.mapOffsetY); + U32 destStep = pOriginalLMap->getWidth() * 3; + + // copy back into the original lightmap + for (U32 y = 0; y < rSurface.mapSizeY; y++) + { + dMemcpy(dest, src, runSize); + src += runSize; + dest += destStep; + } + + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGB, + pOriginalLMap->getWidth(), pOriginalLMap->getHeight(), + 0, + GL_RGB, GL_UNSIGNED_BYTE, + pOriginalLMap->getBits(0)); + } + else + glTexSubImage2D(GL_TEXTURE_2D, + 0, + rSurface.mapOffsetX, rSurface.mapOffsetY, + rSurface.mapSizeX, rSurface.mapSizeY, + GL_RGB, GL_UNSIGNED_BYTE, + pNewLightmap); +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::downloadLightmaps(SceneState* /*state*/, + Interior* pInterior, + LightInfo& rLightInfo) +{ + extern U16* sgActivePolyList; + extern U32 sgActivePolyListSize; + + for (U32 i = 0; i < sgActivePolyListSize; i++) { + if (rLightInfo.mSurfaceInvalid.test(sgActivePolyList[i]) == true) { + updateLightMap(pInterior, rLightInfo, sgActivePolyList[i]); + rLightInfo.mSurfaceInvalid.clear(sgActivePolyList[i]); + } + } +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::addChildren() +{ +// if (bool(mInteriorRes) == false) +// return; +// +// char nameBuffer[256]; +// U32 i; +// +// // First thing to do, add a group with our name +// SimGroup* myGroup = new SimGroup; +// dSprintf(nameBuffer, 255, "%s_sg", getName()); +// if (!myGroup->registerObject()) { +// Con::warnf(ConsoleLogEntry::General, "Warning, could not register interior subgroup. MagicButton aborted!"); +// return; +// } +// getGroup()->addObject(myGroup, nameBuffer); +// +// // Next, for each door in our resource, we'll be creating a group +// for (i = 0; i < mInteriorRes->getNumInteriorPathFollowers(); i++) { +// InteriorPathFollower* pSource = mInteriorRes->getInteriorPathFollower(i); +// PathedInterior* child = new PathedInterior; +// child->mName = pSource->mName; +// child->mInteriorResIndex = pSource->mInteriorResIndex; +// child->mPathIndex = pSource->mPathIndex; +// child->mOffset = pSource->mOffset; +// child->mInteriorResName = mInteriorFileName; +// child->setField("dataBlock", "defaultPathFollow"); +// +// // Create a group for the door +// SimGroup* doorGroup = new SimGroup; +// dSprintf(nameBuffer, 255, "%s_g", child->mName); +// if (!doorGroup->registerObject()) { +// Con::warnf(ConsoleLogEntry::General, "Warning, could not register door subgroup. Door skipped!"); +// delete child; +// delete doorGroup; +// continue; +// } +// myGroup->addObject(doorGroup, nameBuffer); +// +// // Ok, now we need to add the path and any triggers that affect the door here... +// Path* pSimPath = new Path; +// if (!pSimPath->registerObject()) { +// Con::warnf(ConsoleLogEntry::General, "Warning, could not register door path. Path skipped!"); +// delete child; +// delete pSimPath; +// continue; +// } +// doorGroup->addObject(pSimPath); +// +// InteriorPath* pPath = mInteriorRes->getPath(child->mPathIndex); +// for (U32 j = 0; j < pPath->mNumWayPoints; j++) { +// Marker* pMarker = new Marker; +// pMarker->mSeqNum = j; +// pMarker->mMSToNext = pPath->mWayPoints[j].msToNext; +// +// if (!pMarker->registerObject()) { +// Con::warnf(ConsoleLogEntry::General, "Warning, could not register path marker. Marker skipped!"); +// delete pMarker; +// continue; +// } +// pSimPath->addObject(pMarker); +// +// Point3F markerPos = pPath->mWayPoints[j].pos; +// markerPos.convolve(mObjScale); +// getTransform().mulP(markerPos); +// MatrixF xform(true); +// xform.setColumn(3, markerPos); +// pMarker->setTransform(xform); +// } +// pSimPath->finishPath(); +// +// // Now we need to add any triggers relevant to this door... +// // +// for (U32 k = 0; k < pSource->mTriggers.size(); k++) { +// StringTableEntry triggerName = pSource->mTriggers[k]; +// +// for (U32 j = 0; j < mInteriorRes->getNumTriggers(); j++) { +// InteriorResTrigger* pITrigger = mInteriorRes->getTrigger(j); +// +// if (dStricmp(pITrigger->mName, triggerName) == 0) { +// // Add the object... +// Trigger* pTrigger = new Trigger; +// pTrigger->setField("dataBlock", "defaultTrigger"); +// +// if (!pTrigger->registerObject()) { +// Con::warnf(ConsoleLogEntry::General, "Warning, could not register trigger. Trigger skipped!"); +// delete pTrigger; +// continue; +// } +// doorGroup->addObject(pTrigger, pITrigger->mName); +// +// MatrixF newXForm; +// createTriggerTransform(pITrigger, &newXForm); +// pTrigger->setTriggerPolyhedron(pITrigger->mPolyhedron); +// pTrigger->setScale(mObjScale); +// pTrigger->setTransform(newXForm); +// } +// } +// } +// +// // And finally, add the door, taking care to get the transform right... +// MatrixF childTransform(true); +// Point3F childOffset = child->mOffset; +// childOffset.convolve(mObjScale); +// getTransform().mulP(childOffset); +// childOffset.neg(); +// childTransform.setColumn(3, childOffset); +// childTransform.mul(getTransform()); +// child->mBaseTransform = childTransform; +// child->mBaseScale = mObjScale; +// if (!child->registerObject()) { +// Con::warnf(ConsoleLogEntry::General, "Warning, could not register door. Door skipped!"); +// delete child; +// continue; +// } +// doorGroup->addObject(child, child->mName); +// } +} + +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(bool(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 + char buffer[256]; + dSprintf(buffer, sizeof(buffer), "interiors/%s", mInteriorFileName); + Stream* pStream = ResourceManager->openStream(buffer); + if (pStream == NULL) { + Con::errorf(ConsoleLogEntry::General, "Unable to load interior: %s", mInteriorFileName); + return false; + } + + InteriorResource* pResource = new InteriorResource; + bool success = pResource->read(*pStream); + ResourceManager->closeStream(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; +} + + diff --git a/interior/interiorInstance.h b/interior/interiorInstance.h new file mode 100644 index 0000000..5a40fab --- /dev/null +++ b/interior/interiorInstance.h @@ -0,0 +1,293 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORINSTANCE_H_ +#define _INTERIORINSTANCE_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif +#ifndef _RESMANAGER_H_ +#include "core/resManager.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 + +class AbstractPolyList; +class LightUpdateGrouper; +class InteriorSubObject; +class InteriorResTrigger; +class MaterialList; +class TextureObject; +class FloorPlan; +class Convex; +class AudioProfile; +class AudioEnvironment; + +//-------------------------------------------------------------------------- +class InteriorInstance : public SceneObject +{ + typedef SceneObject Parent; + friend class SceneLighting; + friend class FloorPlan; + + public: + InteriorInstance(); + ~InteriorInstance(); + + static void init(); + static void destroy(); + + // Collision + public: + bool buildPolyList(AbstractPolyList*, const Box3F&, const SphereF&); + bool castRay(const Point3F&, const Point3F&, RayInfo*); + virtual void setTransform(const MatrixF&); + + void buildConvex(const Box3F& box,Convex* convex); + private: + Convex* mConvexList; + + // Lighting control + public: + bool inAlarmState() {return(mAlarmState);} + void setAlarmMode(const bool alarm); + void activateLight(const char* pLightName); + void deactivateLight(const char* pLightName); + void echoTriggerableLights(); + + // Subobject access interface + public: + U32 getNumDetailLevels(); + Interior* getDetailLevel(const U32); + void setDetailLevel(S32 level = -1) { mForcedDetailLevel = level; } + + // Material management for overlays + public: + void renewOverlays(); + void setSkinBase(const char*); + + public: + static bool smDontRestrictOutside; + static F32 smDetailModification; + + + DECLARE_CONOBJECT(InteriorInstance); + static void initPersistFields(); + static void consoleInit(); + + bool readLightmaps(GBitmap**** lightmaps); + + protected: + bool onAdd(); + void onRemove(); + + void inspectPreApply(); + void inspectPostApply(); + + static U32 smLightUpdatePeriod; + static bool smRenderDynamicLights; + + U32 mLightUpdatedTime; + void setLightUpdatedTime(const U32); + U32 getLightUpdatedTime() const; + + bool onSceneAdd(SceneGraph*); + void onSceneRemove(); + U32 getPointZone(const Point3F& p); + bool getOverlappingZones(SceneObject* obj, U32* zones, U32* numZones); + + U32 calcDetailLevel(SceneState*, const Point3F&); + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + bool scopeObject(const Point3F& rootPosition, + const F32 rootDistance, + bool* zoneScopeState); + + public: + void addChildren(); + static bool getRenderDynamicLights() { return(smRenderDynamicLights); } + static void setRenderDynamicLights(bool val) { smRenderDynamicLights = val; } + + private: + void activateLight(const U32 detail, const U32 lightIndex); + void deactivateLight(const U32 detail, const U32 lightIndex); + + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); + + + enum UpdateMaskBits { + InitMask = 1 << 0, + TransformMask = 1 << 1, + AlarmMask = 1 << 2, + + // Reserved for light updates (8 bits for now) + _lightupdate0 = 1 << 3, + _lightupdate1 = 1 << 4, + _lightupdate2 = 1 << 5, + _lightupdate3 = 1 << 6, + _lightupdate4 = 1 << 7, + _lightupdate5 = 1 << 8, + _lightupdate6 = 1 << 9, + _lightupdate7 = 1 << 10, + + SkinBaseMask = 1 << 11, + AudioMask = 1 << 12, + NextFreeMask = 1 << 13 + }; + enum Constants { + LightUpdateBitStart = 3, + LightUpdateBitEnd = 10 + }; + + private: + StringTableEntry mInteriorFileName; + U32 mInteriorFileHash; + Resource mInteriorRes; + Vector mMaterialMaps; + StringTableEntry mSkinBase; + + Vector< Vector > mInteriorSubObjects; + bool mShowTerrainInside; + LM_HANDLE mLMHandle; + AudioProfile * mAudioProfile; + AudioEnvironment * mAudioEnvironment; + S32 mForcedDetailLevel; + U32 mCRC; + + public: + LM_HANDLE getLMHandle() { return(mLMHandle); } + AudioProfile * getAudioProfile() { return(mAudioProfile); } + AudioEnvironment * getAudioEnvironment() { return(mAudioEnvironment); } + bool getPointInsideScale(const Point3F & pos, F32 * pScale); // ~0: outside -> 1: inside + + // SceneLighting::InteriorProxy interface + Resource & getResource() {return(mInteriorRes);} + U32 getCRC() { return(mCRC); } + + Vector*> mVertexColorsNormal; + Vector*> mVertexColorsAlarm; + void rebuildVertexColors(); + Vector* getVertexColorsNormal(U32 detail); + Vector* getVertexColorsAlarm(U32 detail); + + // Alarm state information + private: + enum AlarmState { + Normal = 0, + Alarm = 1 + }; + + bool mAlarmState; + + // LightingAnimation information + private: + struct LightInfo { + struct Light { + U32 curState; + U32 curTime; + ColorI curColor; + + bool active; + bool alarm; + }; + struct StateDataInfo { + ColorI curColor; + U8* curMap; + bool alarm; + }; + + Vector mLights; + BitVector mSurfaceInvalid; + Vector mStateDataInfo; + }; + Vector mLightInfo; // One for each detail level. + LightUpdateGrouper* mUpdateGrouper; // Cuts down on net traffic + + static U32 makeUpdateKey(const U32 detail, const U32 lightIndex); + static U32 detailFromUpdateKey(const U32 key); + static U32 indexFromUpdateKey(const U32 key); + + // Animated light functions + void updateLightTime(const U32 detail, const U32 lightIndex, const U32 ms); + void downloadLightmaps(SceneState*, Interior*, LightInfo&); + void installLight(const U32 detail, const U32 lightIndex); + void updateLoopingLight(Interior*, LightInfo::Light&, const U32 lightIndex, const U32 ms); + void updateFlickerLight(Interior*, LightInfo::Light&, const U32 lightIndex, const U32 ms); + void updateRampLight(Interior*, LightInfo::Light&, const U32 lightIndex, const U32 ms); + void updateAllLights(const U32); + + void updateLightMap(Interior*, LightInfo&, const U32 surfaceIndex); + void intensityMapMerge(U8* lightMap, + const U32 width, const U32 height, + const U8* intensityMap, const ColorI& color); + + private: + void createTriggerTransform(const InteriorResTrigger*, MatrixF*); +}; + +inline void InteriorInstance::setLightUpdatedTime(const U32 now) +{ + mLightUpdatedTime = now; +} + +inline U32 InteriorInstance::getLightUpdatedTime() const +{ + return mLightUpdatedTime; +} + +inline U32 InteriorInstance::makeUpdateKey(const U32 detail, const U32 lightIndex) +{ + AssertFatal(detail < (1 << 16) && lightIndex < (1 << 16), "Error, out of bounds key params"); + + return (detail << 16) | (lightIndex & 0x0000FFFF); +} + +inline U32 InteriorInstance::detailFromUpdateKey(const U32 key) +{ + return (key >> 16) & 0xFFFF; +} + +inline U32 InteriorInstance::indexFromUpdateKey(const U32 key) +{ + return (key >> 0) & 0xFFFF; +} + +inline Vector* InteriorInstance::getVertexColorsNormal(U32 detail) +{ + if (bool(mInteriorRes) == false || detail > mInteriorRes->getNumDetailLevels()) + return NULL; + + return mVertexColorsNormal[detail]; +} + +inline Vector* InteriorInstance::getVertexColorsAlarm(U32 detail) +{ + if (bool(mInteriorRes) == false || detail > mInteriorRes->getNumDetailLevels()) + return NULL; + + return mVertexColorsAlarm[detail]; +} + +#endif //_INTERIORBLOCK_H_ + diff --git a/interior/interiorLMManager.cc b/interior/interiorLMManager.cc new file mode 100644 index 0000000..598ab22 --- /dev/null +++ b/interior/interiorLMManager.cc @@ -0,0 +1,549 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interiorLMManager.h" +#include "dgl/gTexManager.h" +#include "dgl/gBitmap.h" +#include "PlatformWin32/platformGL.h" +#include "interior/interiorRes.h" +#include "interior/interiorInstance.h" +#include "interior/interior.h" +#include "sceneGraph/sceneLighting.h" + +//------------------------------------------------------------------------------ +// Globals +InteriorLMManager gInteriorLMManager; + +//------------------------------------------------------------------------------ +namespace { + void interiorLMTextureCallback(const U32 eventCode, const U32 userData) + { + AssertFatal(&gInteriorLMManager == reinterpret_cast(userData), "Bunk ptr!"); + gInteriorLMManager.processTextureEvent(eventCode); + } + + // '__lm_.png' + const char * getTextureName(Interior * interior, U32 instance, U32 lightmap) + { + static char buffer[256]; + dSprintf(buffer, sizeof(buffer), "%p_%d_lm_%d.png", interior, instance, lightmap); + return(buffer); + } +} + +//------------------------------------------------------------------------------ +U32 InteriorLMManager::smTextureCallbackKey = U32(-1); + +// D3D vertex buffers for Interiors are in here so they get dumped/reallocated +// along with the lightmaps on the texture events +S32 InteriorLMManager::smMTVertexBuffer = -1; +S32 InteriorLMManager::smFTVertexBuffer = -1; +S32 InteriorLMManager::smFMTVertexBuffer = -1; + +InteriorLMManager::InteriorLMManager() +{ +} + +InteriorLMManager::~InteriorLMManager() +{ + for(U32 i = 0; i < mInteriors.size(); i++) + removeInterior(LM_HANDLE(i)); +} + +//------------------------------------------------------------------------------ +void InteriorLMManager::init() +{ + smTextureCallbackKey = TextureManager::registerEventCallback(interiorLMTextureCallback, reinterpret_cast(&gInteriorLMManager)); +} + +void InteriorLMManager::destroy() +{ + if(smTextureCallbackKey != U32(-1)) + { + TextureManager::unregisterEventCallback(smTextureCallbackKey); + smTextureCallbackKey = U32(-1); + } + + if (smMTVertexBuffer != -1) + { + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(smMTVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + smMTVertexBuffer = -1; + } + if (smFTVertexBuffer != -1) + { + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(smFTVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + smFTVertexBuffer = -1; + } + if (smFMTVertexBuffer != -1) + { + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(smFMTVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + smFMTVertexBuffer = -1; + } +} + +void InteriorLMManager::processTextureEvent(U32 eventCode) +{ + switch(eventCode) + { + case TextureManager::BeginZombification: + purgeGLTextures(); + + if (smMTVertexBuffer != -1) + { + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(smMTVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + smMTVertexBuffer = -1; + } + if (smFTVertexBuffer != -1) + { + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(smFTVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + smFTVertexBuffer = -1; + } + if (smFMTVertexBuffer != -1) + { + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(smFMTVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + smFMTVertexBuffer = -1; + } + break; + + case TextureManager::CacheResurrected: + // relighting the scene will take care of things for us + if(mInteriors.size()) + SceneLighting::lightScene(0, SceneLighting::LoadOnly); + break; + } +} + +//------------------------------------------------------------------------------ +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] = new TextureHandle(getTextureName(interior, 0, i), interior->mLightmaps[i], BitmapNoDownloadTexture); + } + + 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); + dMemset(instanceInfo->mLightmapHandles.address(), 0, sizeof(TextureHandle*) * instanceInfo->mLightmapHandles.size()); +} + +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++) + delete instInfo->mLightmapHandles[i]; + + // 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::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; + + TextureObject * texObj = *instanceInfo->mLightmapHandles[k]; + if(!texObj || !texObj->bitmap) + continue; + + // don't remove 'keep' bitmaps + if(!interiorInfo->mInterior->mLightmapKeep[k]) + { + delete texObj->bitmap; + 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 + delete instanceInfo->mLightmapHandles[k]; + instanceInfo->mLightmapHandles[k] = 0; + } + } + } +} + +void InteriorLMManager::purgeGLTextures() +{ + Vector purgeList(4096); + + 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; + + TextureObject * texObj = *instanceInfo->mLightmapHandles[k]; + if(!texObj || !texObj->texGLName) + continue; + +#ifdef GATHER_METRICS + AssertFatal(texObj->textureSpace <= TextureManager::smTextureSpaceLoaded, "Doh!"); + TextureManager::smTextureSpaceLoaded -= texObj->textureSpace; + texObj->textureSpace = 0; +#endif + + purgeList.push_back(texObj->texGLName); + texObj->texGLName = 0; + } + } + } + + glDeleteTextures(purgeList.size(), purgeList.address()); +} + +void InteriorLMManager::downloadGLTextures() +{ + for(S32 i = mInteriors.size() - 1; i >= 0; i--) + { + InteriorLMInfo * interiorInfo = mInteriors[i]; + + // - skip base texture if all the instances have a texture defined + BitVector needTexture; + needTexture.setSize(mInteriors[i]->mNumLightmaps); + needTexture.clear(); + + // walk the instances and have them download their textures + 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--) + { + // need base instance's texture? + if((j == 0) && !needTexture.test(k)) + continue; + + if(!instanceInfo->mLightmapHandles[k]) + { + needTexture.set(k); + continue; + } + + TextureObject * texObj = *instanceInfo->mLightmapHandles[k]; + if(!texObj || !texObj->bitmap || texObj->texGLName) + { + needTexture.set(k); + continue; + } + +#ifdef GATHER_METRICS + texObj->textureSpace = texObj->downloadedWidth * texObj->downloadedHeight; + TextureManager::smTextureSpaceLoaded += texObj->textureSpace; +#endif + TextureManager::createGLName(texObj->bitmap, texObj->clamp, 0, texObj->type, texObj); + } + } + } +} + +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) + TextureHandle * texture = baseInstanceInfo->mLightmapHandles[0]; + if(texture && 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]) + { + TextureObject * texObj = *baseInstanceInfo->mLightmapHandles[j]; + texObj->bitmap = pBitmaps[i][j]; + } + else + baseInstanceInfo->mLightmapHandles[j] = new TextureHandle(getTextureName(interior, 0, j), pBitmaps[i][j], BitmapNoDownloadTexture); + } + } + + delete [] pBitmaps; + return(true); +} + +//------------------------------------------------------------------------------ +TextureHandle * 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); +} + +//------------------------------------------------------------------------------ +U32 InteriorLMManager::getNumLightmaps(LM_HANDLE interiorHandle) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::getNumLightmaps: invalid interior handle"); + return(mInteriors[interiorHandle]->mNumLightmaps); +} + +//------------------------------------------------------------------------------ +void InteriorLMManager::deleteLightmap(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::deleteLightmap: invalid interior handle"); + AssertFatal(instanceHandle < mInteriors[interiorHandle]->mInstances.size(), "InteriorLMManager::deleteLightmap: invalid instance handle"); + AssertFatal(index < mInteriors[interiorHandle]->mNumLightmaps, "InteriorLMManager::deleteLightmap: invalid texture index"); + + delete mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index]; + mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index] = 0; +} + +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++) + { + delete mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[i]; + mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[i] = 0; + } +} + +//------------------------------------------------------------------------------ +TextureHandle * 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? + TextureHandle * texHandle = mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index]; + if(texHandle && static_cast(*texHandle)->bitmap) + return(texHandle); + + 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) + TextureHandle * tHandle = new TextureHandle(getTextureName(mInteriors[interiorHandle]->mInterior, instanceHandle, index), dest, BitmapNoDownloadTexture); + mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index] = tHandle; + return(tHandle); +} + +S32 InteriorLMManager::getVertexBuffer(S32 format) +{ + switch (format) + { + case GL_TRIBESMTVFMT_EXT: + { + if (smMTVertexBuffer != -1) + return smMTVertexBuffer; + + smMTVertexBuffer = glAllocateVertexBufferEXT(512,GL_TRIBESMTVFMT_EXT,false); + + return smMTVertexBuffer; + } + case GL_TRIBESFTVFMT_EXT: + { + if (smFTVertexBuffer != -1) + return smFTVertexBuffer; + + smFTVertexBuffer = glAllocateVertexBufferEXT(512,GL_TRIBESFTVFMT_EXT,false); + + return smFTVertexBuffer; + } + case GL_TRIBESFMTVFMT_EXT: + { + if (smFMTVertexBuffer != -1) + return smFMTVertexBuffer; + + smFMTVertexBuffer = glAllocateVertexBufferEXT(512,GL_TRIBESFMTVFMT_EXT,false); + + return smFMTVertexBuffer; + } + } + + AssertFatal(false,"What? We should never get here!!!"); + + return -1; +} diff --git a/interior/interiorLMManager.h b/interior/interiorLMManager.h new file mode 100644 index 0000000..bbb4aca --- /dev/null +++ b/interior/interiorLMManager.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORLMMANAGER_H_ +#define _INTERIORLMMANAGER_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + +class TextureHandle; +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; + + static S32 smMTVertexBuffer; + static S32 smFTVertexBuffer; + static S32 smFMTVertexBuffer; + + public: + + static U32 smTextureCallbackKey; + + InteriorLMManager(); + ~InteriorLMManager(); + + static void init(); + static void destroy(); + + void processTextureEvent(U32 eventCode); + + void destroyBitmaps(); + void destroyTextures(); + + void purgeGLTextures(); + void downloadGLTextures(); + 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); + + U32 getNumLightmaps(LM_HANDLE interiorHandle); + void deleteLightmap(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index); + void clearLightmaps(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle); + + TextureHandle * getHandle(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index); + Vector & getHandles(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle); + + // helper's + TextureHandle * duplicateBaseLightmap(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index); + GBitmap * getBitmap(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index); + + S32 getVertexBuffer(S32 format); +}; + +extern InteriorLMManager gInteriorLMManager; + +#endif diff --git a/interior/interiorLightAnim.cc b/interior/interiorLightAnim.cc new file mode 100644 index 0000000..3123532 --- /dev/null +++ b/interior/interiorLightAnim.cc @@ -0,0 +1,404 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interiorInstance.h" +#include "interior/lightUpdateGrouper.h" +#include "interior/interior.h" +#include "Math/mRandom.h" + +void InteriorInstance::echoTriggerableLights() +{ + // DMMFIX: Only the first detail for now... + Interior* pInterior = mInteriorRes->getDetailLevel(0); + + Con::printf("Interior: %s", mInteriorFileName); + Con::printf(" %d Triggerable lights:", pInterior->mNumTriggerableLights); + + // Triggerable lights are always the first in the array... + for (U32 i = 0; i < pInterior->mNumTriggerableLights; i++) { + const char* pName = pInterior->getName(pInterior->mAnimatedLights[i].nameIndex); + U32 type = pInterior->mAnimatedLights[i].flags & Interior::AnimationTypeMask; + float duration = pInterior->mAnimatedLights[i].duration; + U32 numStates = pInterior->mAnimatedLights[i].stateCount; + + Con::printf(" - %s [%s, Duration: %f, NumStates: %d]", + pName, Interior::getLightTypeString(Interior::LightType(type)), + duration, numStates); + } +} + +void InteriorInstance::activateLight(const char* pLightName) +{ + if (bool(mInteriorRes) == false) { + AssertWarn(false, "Activating a light on an unloaded interior!"); + return; + } + + // Now, it's a real pain in the ass to try to keep track of light states on detail + // changes as we did in tribes 1. There, we analyzed the state on a detail change + // and tried to duplicate that state on the detail level we were switching to. + // Inspiration: forget that, and just animate the lights on all the details all + // the time. Unless the detail is rendering, the lightmap data will never be + // downloaded, and the amount of time necessary to keep the lights updated on + // a detail level is absolutely miniscule. Much easier. + // + for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { + Interior* pInterior = mInteriorRes->getDetailLevel(i); + + for (U32 j = 0; j < pInterior->mNumTriggerableLights; j++) { + const char* pILightName = pInterior->getName(pInterior->mAnimatedLights[j].nameIndex); + if (dStricmp(pLightName, pILightName) == 0) { + activateLight(i, j); + break; + } + } + } +} + +void InteriorInstance::deactivateLight(const char* pLightName) +{ + if (bool(mInteriorRes) == false) { + AssertWarn(false, "Deactivating a light on an unloaded interior!"); + return; + } + + for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { + Interior* pInterior = mInteriorRes->getDetailLevel(i); + + for (U32 j = 0; j < pInterior->mNumTriggerableLights; j++) { + const char* pILightName = pInterior->getName(pInterior->mAnimatedLights[j].nameIndex); + if (dStricmp(pLightName, pILightName) == 0) { + deactivateLight(i, j); + break; + } + } + } +} + +void InteriorInstance::updateAllLights(const U32 ms) +{ + if (bool(mInteriorRes) == false) + return; + + for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { + LightInfo& rLightInfo = mLightInfo[i]; + + for (U32 j = 0; j < rLightInfo.mLights.size(); j++) { + if (mAlarmState == Normal) { + if (rLightInfo.mLights[j].active == true && rLightInfo.mLights[j].alarm == false) + updateLightTime(i, j, ms); + } else { + if (rLightInfo.mLights[j].alarm == true) + updateLightTime(i, j, ms); + } + } + } +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::activateLight(const U32 detail, const U32 lightIndex) +{ + AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level"); + AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index"); + + LightInfo& rLightInfo = mLightInfo[detail]; + LightInfo::Light& rLight = rLightInfo.mLights[lightIndex]; + + if (rLight.active == false) { + rLight.active = true; + rLight.curState = 0; + rLight.curTime = 0; + + Interior* pInterior = mInteriorRes->getDetailLevel(detail); + Interior::LightState& rState = pInterior->mLightStates[pInterior->mAnimatedLights[lightIndex].stateIndex]; + rLight.curColor.set(rState.red, rState.green, rState.blue); + + installLight(detail, lightIndex); + + if (isServerObject() && lightIndex < pInterior->mNumTriggerableLights) { + U32 key = makeUpdateKey(detail, lightIndex); + U32 mask = mUpdateGrouper->getKeyMask(key); + setMaskBits(mask); + } + } else { + // Light is already active, no need to play around further... + // + } +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::deactivateLight(const U32 detail, const U32 lightIndex) +{ + AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level"); + AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index"); + + LightInfo& rLightInfo = mLightInfo[detail]; + LightInfo::Light& rLight = rLightInfo.mLights[lightIndex]; + + if (rLight.active == true) { + // DMMFIX + + rLight.active = false; + rLight.curState = 0; + rLight.curTime = 0; + + Interior* pInterior = mInteriorRes->getDetailLevel(detail); + Interior::LightState& rState = pInterior->mLightStates[pInterior->mAnimatedLights[lightIndex].stateIndex]; + rLight.curColor.set(rState.red, rState.green, rState.blue); + + installLight(detail, lightIndex); + + if (isServerObject() && lightIndex < pInterior->mNumTriggerableLights) { + U32 key = makeUpdateKey(detail, lightIndex); + U32 mask = mUpdateGrouper->getKeyMask(key); + setMaskBits(mask); + } + } else { + // Light is already inactive, no need to play around further... + // + } +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::updateLightTime(const U32 detail, const U32 lightIndex, const U32 ms) +{ + AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level"); + AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index"); + + LightInfo& rLightInfo = mLightInfo[detail]; + Interior* pInterior = mInteriorRes->getDetailLevel(detail); + + LightInfo::Light& rLight = rLightInfo.mLights[lightIndex]; + Interior::AnimatedLight& rILight = pInterior->mAnimatedLights[lightIndex]; + + U32 oldState = rLight.curState; + ColorI oldColor = rLight.curColor; + + // Ok, now we need to break this down a bit. We pass the update along to + // the specialized updating functions based on lightType. + switch (rILight.flags & Interior::AnimationTypeMask) { + case Interior::AmbientLooping: + case Interior::TriggerableLoop: + updateLoopingLight(pInterior, rLight, lightIndex, ms); + break; + + case Interior::AmbientFlicker: + case Interior::TriggerableFlicker: + updateFlickerLight(pInterior, rLight, lightIndex, ms); + break; + + case Interior::TriggerableRamp: + updateRampLight(pInterior, rLight, lightIndex, ms); + break; + + default: + AssertFatal(false, "Bad light type in updateLightTime"); + } + + if (rLight.curState != oldState || + rLight.curColor != oldColor) { + // Need to reinstall the light + installLight(detail, lightIndex); + } +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::updateLoopingLight(Interior* interior, LightInfo::Light& light, + const U32 lightIndex, const U32 ms) +{ + AssertISV( lightIndex < interior->mAnimatedLights.size( ), "out of bounds array access in InteriorInstance::updateLoopingLight" ); + Interior::AnimatedLight& rILight = interior->mAnimatedLights[lightIndex]; + + light.curTime += ms; + light.curTime %= rILight.duration; + + // Find the last state that has a active time below this new time... + light.curState = 0; + for (U32 i = 1; i < rILight.stateCount; i++) { + Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + i]; + if (rState.activeTime <= light.curTime) + light.curState = i; + else + break; + } + + // interpolate the color + Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + light.curState]; + Interior::LightState* pNextState; + + U32 msIntoState = light.curTime - rState.activeTime; + U32 msTotal; + if (light.curState != (rILight.stateCount - 1)) { + // Have one more good state + pNextState = &interior->mLightStates[rILight.stateIndex + light.curState + 1]; + msTotal = pNextState->activeTime - rState.activeTime; + } else { + // Have to interpolate against the first state... + pNextState = &interior->mLightStates[rILight.stateIndex]; + msTotal = rILight.duration - rState.activeTime; + } + + F32 interp = F32(msIntoState) / F32(msTotal); + F32 red = F32(rState.red) * (1.0f - interp) + F32(pNextState->red) * interp; + F32 green = F32(rState.green) * (1.0f - interp) + F32(pNextState->green) * interp; + F32 blue = F32(rState.blue) * (1.0f - interp) + F32(pNextState->blue) * interp; + + light.curColor.set(U8(red + 0.5f), U8(green + 0.5f), U8(blue + 0.5f)); +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::updateFlickerLight(Interior* interior, LightInfo::Light& light, + const U32 lightIndex, const U32 ms) +{ + Interior::AnimatedLight& rILight = interior->mAnimatedLights[lightIndex]; + + U32 switchPeriod = interior->mLightStates[interior->mAnimatedLights[lightIndex].stateIndex + 1].activeTime; + U32 oldTime = light.curTime; + light.curTime += ms; + if (light.curTime < switchPeriod) + return; + + light.curTime = 0; + + // Ok, pick a random number from 0 to the light duration, and find the state that + // it falls in. + + static MRandomLCG randomGen; + U32 pickedTime = randomGen.randI(0, rILight.duration); + + light.curState = 0; + for (U32 i = 1; i < rILight.stateCount; i++) { + Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + i]; + if (rState.activeTime <= pickedTime) + light.curState = i; + else + break; + } + + Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + light.curState]; + light.curColor.set(rState.red, rState.green, rState.blue); +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::updateRampLight(Interior* interior, LightInfo::Light& light, + const U32 lightIndex, const U32 ms) +{ + Interior::AnimatedLight& rILight = interior->mAnimatedLights[lightIndex]; + + light.curTime += ms; + if (light.curTime > rILight.duration) + light.curTime = rILight.duration; + + // Find the last state that has a active time below this new time... + light.curState = 0; + for (U32 i = 1; i < rILight.stateCount; i++) { + Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + i]; + if (rState.activeTime <= light.curTime) + light.curState = i; + else + break; + } + + // interpolate the color + Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + light.curState]; + Interior::LightState* pNextState; + + U32 msIntoState = light.curTime - rState.activeTime; + U32 msTotal; + if (light.curState != (rILight.stateCount - 1)) { + // Have one more good state + pNextState = &interior->mLightStates[rILight.stateIndex + light.curState + 1]; + msTotal = pNextState->activeTime - rState.activeTime; + } else { + // A ramp light does NOT NOT NOT interp against the first state + pNextState = &rState; + msTotal = msIntoState; + } + + F32 interp = F32(msIntoState) / F32(msTotal); + F32 red = F32(rState.red) * (1.0f - interp) + F32(pNextState->red) * interp; + F32 green = F32(rState.green) * (1.0f - interp) + F32(pNextState->green) * interp; + F32 blue = F32(rState.blue) * (1.0f - interp) + F32(pNextState->blue) * interp; + + light.curColor.set(U8(red + 0.5f), U8(green + 0.5f), U8(blue + 0.5f)); +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::installLight(const U32 detail, const U32 lightIndex) +{ + AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level"); + AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index"); + + LightInfo& rLightInfo = mLightInfo[detail]; + LightInfo::Light& rLight = rLightInfo.mLights[lightIndex]; + + // All we are allowed to assume is that the light time, color, and state are + // correct here. We must install all statedata, and invalidate all surfaces. + // First, let's retrieve the actual light from the Interior + // + Interior* pInterior = mInteriorRes->getDetailLevel(detail); + Interior::AnimatedLight& rILight = pInterior->mAnimatedLights[lightIndex]; + Interior::LightState& rIState = pInterior->mLightStates[rILight.stateIndex + rLight.curState]; + + // Ok. Now, cycle through the light's state data, and install it + for (U32 i = rIState.dataIndex; i < (rIState.dataIndex + rIState.dataCount); i++) { + Interior::LightStateData& rIData = pInterior->mStateData[i]; + LightInfo::StateDataInfo& rData = rLightInfo.mStateDataInfo[rIData.lightStateIndex]; + + if (rIData.mapIndex != 0xFFFFFFFFF) { + rData.curMap = &pInterior->mStateDataBuffer[rIData.mapIndex]; + } else { + rData.curMap = NULL; + } + rData.curColor = rLight.curColor; + rData.alarm = (rILight.flags & Interior::AlarmLight) != 0; + rLightInfo.mSurfaceInvalid.set(rIData.surfaceIndex); + } +} + +//-------------------------------------------------------------------------- +void InteriorInstance::intensityMapMerge(U8* lightMap, + const U32 width, + const U32 height, + const U8* intensityMap, + const ColorI& color) +{ + // lightmap is a 24bit RGB texture, intensitymap is an 8 bit intensity + // map. We want lightmap = [lightmap + (intensityMap * color)] + + // DMMFIX: SLOWSLOWSLOW! Need MMX version of this at the very least, + // this version is only for clarity; + for (U32 y = 0; y < height; y++) { + for (U32 x = 0; x < width; x++) { + U8* data = &lightMap[(y * width + x) * 3]; + U32 intensity = intensityMap[(y * width + x)]; + + U32 newRed = data[0]; + U32 newGreen = data[1]; + U32 newBlue = data[2]; + + U32 addRed = (U32(color.red) * intensity + 0x80) >> 8; + U32 addGreen = (U32(color.green) * intensity + 0x80) >> 8; + U32 addBlue = (U32(color.blue) * intensity + 0x80) >> 8; + + newRed += addRed; + newGreen += addGreen; + newBlue += addBlue; + + data[0] = (newRed <= 255) ? U8(newRed) : 0xFF; + data[1] = (newGreen <= 255) ? U8(newGreen) : 0xFF; + data[2] = (newBlue <= 255) ? U8(newBlue) : 0xFF; + } + } +} diff --git a/interior/interiorRender.cc b/interior/interiorRender.cc new file mode 100644 index 0000000..c090a86 --- /dev/null +++ b/interior/interiorRender.cc @@ -0,0 +1,1330 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interior.h" +#include "scenegraph/sceneState.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/lightManager.h" + +#include "platformWIN32/platformGL.h" +#include "dgl/dgl.h" +#include "dgl/gBitmap.h" +#include "dgl/gTexManager.h" +#include "math/mMatrix.h" +#include "math/mRect.h" +#include "dgl/materialList.h" +#include "dgl/materialPropertyMap.h" +#include "interior/interiorSubObject.h" +#include "core/bitVector.h" +#include "dgl/stripCache.h" +#include "platform/profiler.h" +// bad dave, bad dave! +#include + +extern bool sgFogActive; +extern U16* sgActivePolyList; +extern U32 sgActivePolyListSize; +extern U16* sgFogPolyList; +extern U32 sgFogPolyListSize; +extern U16* sgEnvironPolyList; +extern U32 sgEnvironPolyListSize; + +//namespace { + +Point3F sgOSCamPosition; + +struct OutputPoint +{ + Point3F point; + union { + F32 fogCoord; + U8 fogColor[4]; + }; + Point2F texCoord; + Point2F lmCoord; +}; + +struct OutputPointFC_VB +{ + Point3F point; + U8 currentColor[4]; + U8 fogColor[4]; + Point2F texCoord; + Point2F lmCoord; +}; + +struct OutputPointSP_FC_VB +{ + Point3F point; + U8 lmColor[4]; + U8 fogColor[4]; + Point2F texCoord; +}; + +OutputPoint sgRenderBuffer[512]; +U32 sgRenderIndices[2048]; +U32 csgNumAllowedPoints = 256; + +extern "C" { + + void processTriFan(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices); + void processTriFanSP(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors); + void processTriFanVC_TF(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors); + void processTriFanSP_FC(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors); + void processTriFanFC_VB(OutputPointFC_VB* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices); + void processTriFanSP_FC_VB(OutputPointSP_FC_VB* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors); + + F32 texGen0[8]; + F32 texGen1[8]; + void* fogCoordinatePointer; +} + + +void emitPrimitive(OutputPoint* renderBuffer, + U32* renderIndices, + const U32* winding, + const U32 numPoints, + const U32 offset, + ItrPaddedPoint* srcPoints) +{ + U32 currIndex = 0; + U32 last = 2; + while (last < numPoints) { + // First + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 0; + last++; + + if (last == numPoints) + break; + + // Second + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 0; + last++; + } + + processTriFan(renderBuffer, + srcPoints, + winding, + numPoints); +} + +void emitPrimitiveFC_VB(OutputPointFC_VB* renderBuffer, + U32* renderIndices, + const U32* winding, + const U32 numPoints, + const U32 offset, + ItrPaddedPoint* srcPoints) +{ + U32 currIndex = 0; + U32 last = 2; + while (last < numPoints) { + // First + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 0; + last++; + + if (last == numPoints) + break; + + // Second + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 0; + last++; + } + + processTriFanFC_VB(renderBuffer, + srcPoints, + winding, + numPoints); +} + +void emitPrimitiveSP(OutputPoint* renderBuffer, + U32* renderIndices, + const U32* winding, + const U32 numPoints, + const U32 offset, + ItrPaddedPoint* srcPoints, + const ColorI* srcColors) +{ + U32 currIndex = 0; + U32 last = 2; + while (last < numPoints) { + // First + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 0; + last++; + + if (last == numPoints) + break; + + // Second + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 0; + last++; + } + + processTriFanSP(renderBuffer, + srcPoints, + winding, numPoints, + srcColors); +} + + +void emitPrimitiveVC_TF(OutputPoint* renderBuffer, + U32* renderIndices, + const U32* winding, + const U32 numPoints, + const U32 offset, + ItrPaddedPoint* srcPoints, + const ColorI* srcColors) +{ + U32 currIndex = 0; + U32 last = 2; + while (last < numPoints) { + // First + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 0; + last++; + + if (last == numPoints) + break; + + // Second + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 0; + last++; + } + + processTriFanVC_TF(renderBuffer, + srcPoints, + winding, + numPoints, + srcColors); +} + +void emitPrimitiveSP_FC(OutputPoint* renderBuffer, + U32* renderIndices, + const U32* winding, + const U32 numPoints, + const U32 offset, + ItrPaddedPoint* srcPoints, + const ColorI* srcColors) +{ + U32 currIndex = 0; + U32 last = 2; + while (last < numPoints) { + // First + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 0; + last++; + + if (last == numPoints) + break; + + // Second + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 0; + last++; + } + + processTriFanSP_FC(renderBuffer, + srcPoints, + winding, numPoints, + srcColors); +} + +void emitPrimitiveSP_FC_VB(OutputPointSP_FC_VB* renderBuffer, + U32* renderIndices, + const U32* winding, + const U32 numPoints, + const U32 offset, + ItrPaddedPoint* srcPoints, + const ColorI* srcColors) +{ + U32 currIndex = 0; + U32 last = 2; + while (last < numPoints) { + // First + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 0; + last++; + + if (last == numPoints) + break; + + // Second + renderIndices[currIndex++] = offset + last - 1; + renderIndices[currIndex++] = offset + last - 2; + renderIndices[currIndex++] = offset + last - 0; + last++; + } + + processTriFanSP_FC_VB(renderBuffer, + srcPoints, + winding, + numPoints, + srcColors); +} + +void flushPrimitives(const U32* indices, + const U32 count, + const U32 vcount) +{ + if (count == 0) + return; + + if (Interior::smLockArrays && dglDoesSupportCompiledVertexArray()) { + glLockArraysEXT(0, vcount); + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, indices); + glUnlockArraysEXT(); + } else { + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, indices); + } +} + +void flushPrimitivesVB(const U32* indices, + const U32 count, + const U32 vcount, + const S32 handle) +{ + if (count == 0) + return; + + glSetVertexBufferEXT(handle); + + if (Interior::smLockArrays && dglDoesSupportCompiledVertexArray()) { + glLockArraysEXT(0, vcount); + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, indices); + glUnlockArraysEXT(); + } else { + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, indices); + } +} + +//} // namespace {} + +//-------------------------------------------------------------------------- +void Interior::setupFog(SceneState* state) +{ + if (sgFogActive) { + if (useFogCoord()) { + glEnable(GL_FOG); + glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT); + GLfloat fogColor[4]; + fogColor[0] = state->getFogColor().red; + fogColor[1] = state->getFogColor().green; + fogColor[2] = state->getFogColor().blue; + fogColor[3] = 1.0f; + glFogfv(GL_FOG_COLOR, fogColor); + glFogi(GL_FOG_MODE, GL_LINEAR); + glFogf(GL_FOG_START, 0.0f); + glFogf(GL_FOG_END, 1.0f); + } + } +} + +void Interior::clearFog() +{ + if (sgFogActive) { + if (useFogCoord()) { + glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FRAGMENT_DEPTH_EXT); + glDisable(GL_FOG); + } + } +} + +void Interior::setOSCamPosition(const Point3F& pos) +{ + sgOSCamPosition = pos; +} + + +//------------------------------------------------------------------------------ +void Interior::render(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE instanceHandle, + const Vector* normalVLights, + const Vector* alarmVLights) +{ + if (smRenderMode != 0) { + PROFILE_START(IRO_DebugRender); + debugRender(pMaterials, instanceHandle); + PROFILE_END(); + return; + } + + if (dglDoesSupportARBMultitexture()) + { + if (smUseVertexLighting == false) + { + if (useFogCoord()) + { + PROFILE_START(IRO_RenderARB_FC); + renderARB_FC(useAlarmLighting, pMaterials, instanceHandle); + PROFILE_END(); + } + else + { + PROFILE_START(IRO_RenderARB); + renderARB(useAlarmLighting, pMaterials, instanceHandle); + PROFILE_END(); + } + } + else + { + if (useFogCoord()) + { + PROFILE_START(IRO_Render_VC_FC); + render_vc_fc(useAlarmLighting, pMaterials, instanceHandle, normalVLights, alarmVLights); + PROFILE_END(); + } + else + { + PROFILE_START(IRO_Render_VC_TF); + render_vc_tf(useAlarmLighting, pMaterials, instanceHandle, normalVLights, alarmVLights); + PROFILE_END(); + } + } + } + else + { + if (useFogCoord()) + { + PROFILE_START(IRO_Render_VC_FC); + render_vc_fc(useAlarmLighting, pMaterials, instanceHandle, normalVLights, alarmVLights); + PROFILE_END(); + } + else + { + PROFILE_START(Render_VC_TF); + render_vc_tf(useAlarmLighting, pMaterials, instanceHandle, normalVLights, alarmVLights); + PROFILE_END(); + } + } +} + + +//------------------------------------------------------------------------------ +void Interior::render_vc_tf(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE, + const Vector* normalVLights, + const Vector* alarmVLights) +{ + const Vector* pVertexColors = useAlarmLighting ? alarmVLights : normalVLights; + + OutputPoint* pFirstOutputPoint = (OutputPoint*)sgRenderBuffer; + U32 currRenderBufferPoint = 0; + U32 currIndexPoint = 0; + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glBlendFunc(GL_ONE, GL_ZERO); + + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->point); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->texCoord); + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(OutputPoint), &pFirstOutputPoint->fogColor[0]); + + // Ok, our verts are set up, draw our polys. + U32 currentlyBound = U32(-1); + U32 currentTexGen = U32(-1); + + // Draw the polys! + U32 i; + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + // Setup the base texture... + U32 baseName = pMaterials->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound) { + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound = baseName; + } + + if (currRenderBufferPoint + rSurface.windingCount >= 512 || + currIndexPoint + (rSurface.windingCount - 2) * 3 >= 2048) { + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + } + + if (rSurface.texGenIndex != currentTexGen) + { + currentTexGen = rSurface.texGenIndex; + memcpy(texGen0, &mTexGenEQs[rSurface.texGenIndex], sizeof(F32)*8); + } + + emitPrimitiveSP(&sgRenderBuffer[currRenderBufferPoint], + &sgRenderIndices[currIndexPoint], + &mWindings[rSurface.windingStart], + rSurface.windingCount, + currRenderBufferPoint, + &mPoints[0], + &((*pVertexColors)[rSurface.windingStart])); + currRenderBufferPoint += rSurface.windingCount; + currIndexPoint += (rSurface.windingCount - 2) * 3; + AssertFatal(currRenderBufferPoint < 512 && currIndexPoint < 2048, "Aw, crap."); + } + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + + glDisableClientState(GL_COLOR_ARRAY); + if (sgFogActive) { + extern Point2F* sgFogTexCoords; + glVertexPointer(3, GL_FLOAT, sizeof(ItrPaddedPoint), &mPoints[0].point); + glTexCoordPointer(2, GL_FLOAT, sizeof(Point2F), sgFogTexCoords); + + glBindTexture(GL_TEXTURE_2D, gClientSceneGraph->getFogTexture().getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + extern ColorF gInteriorFogColor; + glColor4f(gInteriorFogColor.red, + gInteriorFogColor.green, + gInteriorFogColor.blue, + 1); + + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1,-1); + + if (dglDoesSupportCompiledVertexArray()) + glLockArraysEXT(0, mPoints.size()); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + glDrawElements(GL_TRIANGLE_STRIP, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + } + if (dglDoesSupportCompiledVertexArray()) + glUnlockArraysEXT(); + + glDisable(GL_POLYGON_OFFSET_FILL); + } + + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); +} + + +//------------------------------------------------------------------------------ +void Interior::render_vc_fc(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE, + const Vector* normalVLights, + const Vector* alarmVLights) + +{ + const Vector* pVertexColors = useAlarmLighting ? alarmVLights : normalVLights; + + OutputPoint* pFirstOutputPoint = (OutputPoint*)sgRenderBuffer; + U32 currRenderBufferPoint = 0; + U32 currIndexPoint = 0; + bool supportBuffers; + GLint bufferHandle; + OutputPointSP_FC_VB *vertexBuffer = NULL; + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glBlendFunc(GL_ONE, GL_ZERO); + + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->point); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->texCoord); + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(OutputPoint), &pFirstOutputPoint->fogColor[0]); + if (sgFogActive) { + // Need the fog coord pointer enabled here... + glEnableClientState(GL_FOG_COORDINATE_ARRAY_EXT); + glFogCoordPointerEXT(GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->lmCoord.x); + } + + // Ok, our verts are set up, draw our polys. + U32 currentlyBound = U32(-1); + U32 currentTexGen = U32(-1); + + if ((supportBuffers = dglDoesSupportVertexBuffer()) == true) + { + bufferHandle = gInteriorLMManager.getVertexBuffer(GL_TRIBESFTVFMT_EXT); + // guess we ran out of video memory + if (bufferHandle == -1) + supportBuffers = false; + } + + // Draw the polys! + U32 i; + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + // Setup the base texture... + U32 baseName = pMaterials->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound) { + if (supportBuffers) + { + if (vertexBuffer) + glUnlockVertexBufferEXT(bufferHandle); + flushPrimitivesVB(sgRenderIndices, currIndexPoint, currRenderBufferPoint, bufferHandle); + vertexBuffer = (OutputPointSP_FC_VB *) glLockVertexBufferEXT(bufferHandle,512); + } + else + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound = baseName; + } + + if (currRenderBufferPoint + rSurface.windingCount >= 512 || + currIndexPoint + (rSurface.windingCount - 2) * 3 >= 2048) { + if (supportBuffers) + { + if (vertexBuffer) + glUnlockVertexBufferEXT(bufferHandle); + flushPrimitivesVB(sgRenderIndices, currIndexPoint, currRenderBufferPoint, bufferHandle); + vertexBuffer = (OutputPointSP_FC_VB *) glLockVertexBufferEXT(bufferHandle,512); + } + else + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + } + + if (rSurface.texGenIndex != currentTexGen) + { + currentTexGen = rSurface.texGenIndex; + memcpy(texGen0, &mTexGenEQs[rSurface.texGenIndex], sizeof(F32)*8); + } + + if (supportBuffers) + emitPrimitiveSP_FC_VB(&vertexBuffer[currRenderBufferPoint], + &sgRenderIndices[currIndexPoint], + &mWindings[rSurface.windingStart], + rSurface.windingCount, + currRenderBufferPoint, + &mPoints[0], + &((*pVertexColors)[rSurface.windingStart])); + else + emitPrimitiveSP_FC(&sgRenderBuffer[currRenderBufferPoint], + &sgRenderIndices[currIndexPoint], + &mWindings[rSurface.windingStart], + rSurface.windingCount, + currRenderBufferPoint, + &mPoints[0], + &((*pVertexColors)[rSurface.windingStart])); + currRenderBufferPoint += rSurface.windingCount; + currIndexPoint += (rSurface.windingCount - 2) * 3; + AssertFatal(currRenderBufferPoint < 512 && currIndexPoint < 2048, "Aw, crap."); + } + if (supportBuffers) + { + if (vertexBuffer) + glUnlockVertexBufferEXT(bufferHandle); + flushPrimitivesVB(sgRenderIndices, currIndexPoint, currRenderBufferPoint, bufferHandle); + } + else + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + + if (sgFogActive) { + // Need the fog coord pointer enabled here... + glDisableClientState(GL_FOG_COORDINATE_ARRAY_EXT); + } + + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); +} + + +void Interior::renderARB_vc_tf(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE, + const Vector* normalVLights, + const Vector* alarmVLights) + +{ + // DMMNOTE: NON-FUNCTIONAL ON THE GEFORCE! DO NOT PUT IN RENDER FUNCTION!!! + // DMMNOTE: NON-FUNCTIONAL ON THE GEFORCE! DO NOT PUT IN RENDER FUNCTION!!! + // DMMNOTE: NON-FUNCTIONAL ON THE GEFORCE! DO NOT PUT IN RENDER FUNCTION!!! + U32 i; + const Vector* pVertexColors = useAlarmLighting ? alarmVLights : normalVLights; + + extern Point2F* sgFogTexCoords; + fogCoordinatePointer = sgFogTexCoords; + + OutputPoint* pFirstOutputPoint = (OutputPoint*)sgRenderBuffer; + U32 currRenderBufferPoint = 0; + U32 currIndexPoint = 0; + + // Base textures + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glActiveTextureARB(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + glBindTexture(GL_TEXTURE_2D, gClientSceneGraph->getFogTexture().getGLName()); + glActiveTextureARB(GL_TEXTURE0_ARB); + + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->point); + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(OutputPoint), &pFirstOutputPoint->fogColor[0]); + +// // Fog texture +// if (sgFogActive) +// { + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->texCoord); +// glClientActiveTextureARB(GL_TEXTURE0_ARB); +// } +// else +// { +// glActiveTextureARB(GL_TEXTURE1_ARB); +// glDisable(GL_TEXTURE_2D); + +// glClientActiveTextureARB(GL_TEXTURE1_ARB); +// glDisableClientState(GL_TEXTURE_COORD_ARRAY); +// } + + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->lmCoord); + + // Misc setup + glBlendFunc(GL_ONE, GL_ZERO); + glActiveTextureARB(GL_TEXTURE0_ARB); + + // Ok, our verts are set up, draw our polys. + U32 currentlyBound = U32(-1); + U32 currentTexGen = U32(-1); + + // Draw the polys! + glColor4f(1, 1, 1, 1); + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + // Setup the base texture... + U32 baseName = pMaterials->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound) { + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound = baseName; + } + + if (currRenderBufferPoint + rSurface.windingCount >= 512 || + currIndexPoint + (rSurface.windingCount - 2) * 3 >= 2048) { + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + } + if (rSurface.texGenIndex != currentTexGen) + { + currentTexGen = rSurface.texGenIndex; + memcpy(texGen0, &mTexGenEQs[rSurface.texGenIndex], sizeof(F32)*8); + } + + emitPrimitiveVC_TF(&sgRenderBuffer[currRenderBufferPoint], + &sgRenderIndices[currIndexPoint], + &mWindings[rSurface.windingStart], + rSurface.windingCount, + currRenderBufferPoint, + &mPoints[0], + &((*pVertexColors)[rSurface.windingStart])); + currRenderBufferPoint += rSurface.windingCount; + currIndexPoint += (rSurface.windingCount - 2) * 3; + AssertFatal(currRenderBufferPoint < 512 && currIndexPoint < 2048, "Aw, crap."); + } + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); +} + + +void Interior::renderARB(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE instanceHandle) +{ + U32 i; + Vector* pLMapIndices = useAlarmLighting ? &mAlarmLMapIndices : &mNormalLMapIndices; + + OutputPoint* pFirstOutputPoint = (OutputPoint*)sgRenderBuffer; + U32 currRenderBufferPoint = 0; + U32 currIndexPoint = 0; + + // Lightmaps + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + // Base textures + glActiveTextureARB(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // Misc setup + glBlendFunc(GL_ONE, GL_ZERO); + glActiveTextureARB(GL_TEXTURE0_ARB); + + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->point); + + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->texCoord); + + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->lmCoord); + + // Ok, our verts are set up, draw our polys. + U32 currentlyBound0 = U32(-1); + U32 currentlyBound1 = U32(-1); + U32 currentTexGen = U32(-1); + + // Draw the polys! + glColor4f(1, 1, 1, 1); + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + // Setup the base texture... + U32 baseName = pMaterials->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound1) { + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + + glActiveTextureARB(GL_TEXTURE1_ARB); + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound1 = baseName; + glActiveTextureARB(GL_TEXTURE0_ARB); + } + + // Setup the lightmap + baseName = (*pLMapIndices)[sgActivePolyList[i]]; + if (baseName != currentlyBound0) { + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + + U32 glName = gInteriorLMManager.getHandle(mLMHandle, instanceHandle, baseName)->getGLName(); + AssertFatal(glName, "Interior::renderARB: invalid glName for texture handle"); + + glBindTexture(GL_TEXTURE_2D, glName); + currentlyBound0 = baseName; + } + + if (currRenderBufferPoint + rSurface.windingCount >= 512 || + currIndexPoint + (rSurface.windingCount - 2) * 3 >= 2048) { + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + } + if (rSurface.texGenIndex != currentTexGen) + { + currentTexGen = rSurface.texGenIndex; + memcpy(texGen0, &mTexGenEQs[rSurface.texGenIndex], sizeof(F32)*8); + } + memcpy(texGen1, &mLMTexGenEQs[sgActivePolyList[i]], sizeof(F32)*8); + + emitPrimitive(&sgRenderBuffer[currRenderBufferPoint], + &sgRenderIndices[currIndexPoint], + &mWindings[rSurface.windingStart], + rSurface.windingCount, + currRenderBufferPoint, + &mPoints[0]); + currRenderBufferPoint += rSurface.windingCount; + currIndexPoint += (rSurface.windingCount - 2) * 3; + AssertFatal(currRenderBufferPoint < 512 && currIndexPoint < 2048, "Aw, crap."); + } + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + + if (sgFogActive) { + glVertexPointer(3, GL_FLOAT, sizeof(ItrPaddedPoint), &mPoints[0].point); + + extern Point2F* sgFogTexCoords; + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glTexCoordPointer(2, GL_FLOAT, sizeof(Point2F), sgFogTexCoords); + + glActiveTextureARB(GL_TEXTURE1_ARB); + glDisable(GL_TEXTURE_2D); + + glActiveTextureARB(GL_TEXTURE0_ARB); + glBindTexture(GL_TEXTURE_2D, gClientSceneGraph->getFogTexture().getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + extern ColorF gInteriorFogColor; + glColor4f(gInteriorFogColor.red, + gInteriorFogColor.green, + gInteriorFogColor.blue, + 1); + + if (dglDoesSupportCompiledVertexArray()) + glLockArraysEXT(0, mPoints.size()); + for (U32 i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + glDrawElements(GL_TRIANGLE_STRIP, rSurface.windingCount, GL_UNSIGNED_INT, &mWindings[rSurface.windingStart]); + } + if (dglDoesSupportCompiledVertexArray()) + glUnlockArraysEXT(); + + glActiveTextureARB(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + } + + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + // Render environment maps... + if (smRenderEnvironmentMaps && sgEnvironPolyListSize) { + currentlyBound1 = -1; + currentlyBound0 = -1; + + // Base textures + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + // Envmaps textures + glActiveTextureARB(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + // Misc setup + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glActiveTextureARB(GL_TEXTURE0_ARB); + + for (i = 0; i < sgEnvironPolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgEnvironPolyList[i]]; + + // Setup the base texture... + U32 baseName = mMaterialList->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound0) { + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound0 = baseName; + } + + // Setup the environment map... + baseName = mEnvironMaps[rSurface.textureIndex]->getGLName(); + if (baseName != currentlyBound1) { + glActiveTextureARB(GL_TEXTURE1_ARB); + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound1 = baseName; + glActiveTextureARB(GL_TEXTURE0_ARB); + } + + const PlaneF& plane = getPlane(rSurface.planeIndex); + Point3F normal = plane; + if (planeIsFlipped(rSurface.planeIndex)) + normal.neg(); + + // And the colors... + F32 baseLevel = mEnvironFactors[rSurface.textureIndex]; + + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + F32 s = mTexGenEQs[rSurface.texGenIndex].planeX.distToPlane(mPoints[mWindings[j]].point); + F32 t = mTexGenEQs[rSurface.texGenIndex].planeY.distToPlane(mPoints[mWindings[j]].point); + + Point3F u = mPoints[mWindings[j]].point - sgOSCamPosition; + Point3F r = u; + F32 dot = mDot(normal, u) * 2.0f; + u = normal * dot; + r -= u; + F32 m = 2 * mSqrt(r.x*r.x + r.y*r.y + (r.z+1)*(r.z+1)); + + glColor4f(1, 1, 1, baseLevel); + glMultiTexCoord2fARB(GL_TEXTURE0_ARB, s, t); + glMultiTexCoord2fARB(GL_TEXTURE1_ARB, r.x/m + 0.5, + r.y/m + 0.5); + glVertex3fv(mPoints[mWindings[j]].point); + } + glEnd(); + } + } // if (environment render on); + + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); +} + + +void Interior::renderARB_FC(const bool useAlarmLighting, MaterialList* pMaterials, const LM_HANDLE instanceHandle) +{ + U32 i; + Vector* pLMapIndices = useAlarmLighting ? &mAlarmLMapIndices : &mNormalLMapIndices; + + OutputPoint* pFirstOutputPoint = (OutputPoint*)sgRenderBuffer; + U32 currRenderBufferPoint = 0; + U32 currIndexPoint = 0; + bool supportBuffers; + GLint bufferHandle; + OutputPointFC_VB *vertexBuffer = NULL; + + // Lightmaps + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + // Base textures + glActiveTextureARB(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // Misc setup + glBlendFunc(GL_ONE, GL_ZERO); + glActiveTextureARB(GL_TEXTURE0_ARB); + + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->point); + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->texCoord); + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->lmCoord); + if (sgFogActive) { + // Need the fog coord pointer enabled here... + glEnableClientState(GL_FOG_COORDINATE_ARRAY_EXT); + glFogCoordPointerEXT(GL_FLOAT, sizeof(OutputPoint), &pFirstOutputPoint->fogCoord); + } + + // Ok, our verts are set up, draw our polys. + U32 currentlyBound0 = U32(-1); + U32 currentlyBound1 = U32(-1); + U32 currentTexGen = U32(-1); + + if ((supportBuffers = dglDoesSupportVertexBuffer()) == true) + { + bufferHandle = gInteriorLMManager.getVertexBuffer(GL_TRIBESFMTVFMT_EXT); + // guess we ran out of video memory + if (bufferHandle == -1) + supportBuffers = false; + } + + // Draw the polys! + glColor4f(1, 1, 1, 1); + for (i = 0; i < sgActivePolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgActivePolyList[i]]; + + // Setup the base texture... + U32 baseName = pMaterials->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound1) { + if (supportBuffers) + { + if (vertexBuffer) + glUnlockVertexBufferEXT(bufferHandle); + flushPrimitivesVB(sgRenderIndices, currIndexPoint, currRenderBufferPoint, bufferHandle); + vertexBuffer = (OutputPointFC_VB *) glLockVertexBufferEXT(bufferHandle,512); + } + else + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + + currIndexPoint = 0; + currRenderBufferPoint = 0; + + glActiveTextureARB(GL_TEXTURE1_ARB); + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound1 = baseName; + glActiveTextureARB(GL_TEXTURE0_ARB); + } + + // Setup the lightmap + baseName = (*pLMapIndices)[sgActivePolyList[i]]; + if (baseName != currentlyBound0) { + if (supportBuffers) + { + if (vertexBuffer) + glUnlockVertexBufferEXT(bufferHandle); + flushPrimitivesVB(sgRenderIndices, currIndexPoint, currRenderBufferPoint, bufferHandle); + vertexBuffer = (OutputPointFC_VB *) glLockVertexBufferEXT(bufferHandle,512); + } + else + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + + currIndexPoint = 0; + currRenderBufferPoint = 0; + + U32 glName = gInteriorLMManager.getHandle(mLMHandle, instanceHandle, baseName)->getGLName(); + AssertFatal(glName, "Interior::renderARB_FC: invalid glName for texture handle"); + + glBindTexture(GL_TEXTURE_2D, glName); + currentlyBound0 = baseName; + } + + if (currRenderBufferPoint + rSurface.windingCount >= 512 || + currIndexPoint + (rSurface.windingCount - 2) * 3 >= 2048) { + if (supportBuffers) + { + if (vertexBuffer) + glUnlockVertexBufferEXT(bufferHandle); + flushPrimitivesVB(sgRenderIndices, currIndexPoint, currRenderBufferPoint, bufferHandle); + vertexBuffer = (OutputPointFC_VB *) glLockVertexBufferEXT(bufferHandle,512); + } + else + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + currIndexPoint = 0; + currRenderBufferPoint = 0; + } + if (rSurface.texGenIndex != currentTexGen) + { + currentTexGen = rSurface.texGenIndex; + memcpy(texGen0, &mTexGenEQs[rSurface.texGenIndex], sizeof(F32)*8); + } + memcpy(texGen1, &mLMTexGenEQs[sgActivePolyList[i]], sizeof(F32)*8); + + if (supportBuffers) + emitPrimitiveFC_VB(&vertexBuffer[currRenderBufferPoint], + &sgRenderIndices[currIndexPoint], + &mWindings[rSurface.windingStart], + rSurface.windingCount, + currRenderBufferPoint, + &mPoints[0]); + else + emitPrimitive(&sgRenderBuffer[currRenderBufferPoint], + &sgRenderIndices[currIndexPoint], + &mWindings[rSurface.windingStart], + rSurface.windingCount, + currRenderBufferPoint, + &mPoints[0]); + currRenderBufferPoint += rSurface.windingCount; + currIndexPoint += (rSurface.windingCount - 2) * 3; + AssertFatal(currRenderBufferPoint < 512 && currIndexPoint < 2048, "Aw, crap."); + } + + if (supportBuffers) + { + if (vertexBuffer) + glUnlockVertexBufferEXT(bufferHandle); + flushPrimitivesVB(sgRenderIndices, currIndexPoint, currRenderBufferPoint, bufferHandle); + } + else + flushPrimitives(sgRenderIndices, currIndexPoint, currRenderBufferPoint); + + currIndexPoint = 0; + currRenderBufferPoint = 0; + + if (sgFogActive) + glDisableClientState(GL_FOG_COORDINATE_ARRAY_EXT); + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + // Render environment maps... + if (smRenderEnvironmentMaps && sgEnvironPolyListSize) { + glDisable(GL_FOG); + currentlyBound1 = -1; + currentlyBound0 = -1; + + // Base textures + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + // Envmaps textures + glActiveTextureARB(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + // Misc setup + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1,-1); + + for (i = 0; i < sgEnvironPolyListSize; i++) { + const Surface& rSurface = mSurfaces[sgEnvironPolyList[i]]; + + // Setup the base texture... + U32 baseName = mMaterialList->getMaterial(rSurface.textureIndex).getGLName(); + if (baseName != currentlyBound0) { + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound0 = baseName; + } + + // Setup the environment map... + baseName = mEnvironMaps[rSurface.textureIndex]->getGLName(); + if (baseName != currentlyBound1) { + glActiveTextureARB(GL_TEXTURE1_ARB); + glBindTexture(GL_TEXTURE_2D, baseName); + currentlyBound1 = baseName; + glActiveTextureARB(GL_TEXTURE0_ARB); + } + + const PlaneF& plane = getPlane(rSurface.planeIndex); + Point3F normal = plane; + if (planeIsFlipped(rSurface.planeIndex)) + normal.neg(); + + // And the colors... + F32 baseLevel = mEnvironFactors[rSurface.textureIndex]; + + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + F32 s = mTexGenEQs[rSurface.texGenIndex].planeX.distToPlane(mPoints[mWindings[j]].point); + F32 t = mTexGenEQs[rSurface.texGenIndex].planeY.distToPlane(mPoints[mWindings[j]].point); + + Point3F u = mPoints[mWindings[j]].point - sgOSCamPosition; + Point3F r = u; + F32 dot = mDot(normal, u) * 2.0f; + u = normal * dot; + r -= u; + F32 m = 2 * mSqrt(r.x*r.x + r.y*r.y + (r.z+1)*(r.z+1)); + + glColor4f(1, 1, 1, baseLevel * (1.0 - mPoints[mWindings[j]].fogCoord)); + glMultiTexCoord2fARB(GL_TEXTURE0_ARB, s, t); + glMultiTexCoord2fARB(GL_TEXTURE1_ARB, r.x/m + 0.5, + r.y/m + 0.5); + glVertex3fv(mPoints[mWindings[j]].point); + } + glEnd(); + } + glDisable(GL_POLYGON_OFFSET_FILL); + } // if (environment render on); + + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); +} + + +void Interior::renderLights(LightInfo* pInfo, + const MatrixF& transform, + const Point3F& scale, + U32* lightSurfaces, + U32 numLightSurfaces) +{ + Point3F lightPoint = pInfo->mPos; + transform.mulP(lightPoint); + lightPoint.convolveInverse(scale); + + if (dglDoesSupportARBMultitexture()) { + glActiveTextureARB(GL_TEXTURE1_ARB); + glDisable(GL_TEXTURE_2D); + glActiveTextureARB(GL_TEXTURE0_ARB); + } + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, mLightFalloff->getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1,-1); + + for (U32 i = 0; i < numLightSurfaces; i++) { + const Surface& rSurface = mSurfaces[lightSurfaces[i]]; + + const PlaneF& plane = getPlane(rSurface.planeIndex); + + Point3F centerPoint; + F32 d = plane.distToPlane(lightPoint); + centerPoint = lightPoint - plane * d; + d = mFabs(d); + if (d >= pInfo->mRadius) + continue; + + F32 mr = mSqrt(pInfo->mRadius*pInfo->mRadius - d*d); + + Point3F normalS; + Point3F normalT; + if (mFabs(plane.z) < 0.9) + mCross(plane, Point3F(0, 0, 1), &normalS); + else + mCross(plane, Point3F(0, 1, 0), &normalS); + mCross(plane, normalS, &normalT); + normalS.normalize(); + normalT.normalize(); + PlaneF splane(centerPoint, normalS); + PlaneF tplane(centerPoint, normalT); + + F32 factor = (pInfo->mRadius - d) / pInfo->mRadius; + glColor4f(pInfo->mColor.red, pInfo->mColor.green, pInfo->mColor.blue, factor); + + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++) { + const Point3F& rPoint = mPoints[mWindings[j]].point; + + glTexCoord2f(((splane.distToPlane(rPoint) / mr) + 1.0) / 2.0, + ((tplane.distToPlane(rPoint) / mr) + 1.0) / 2.0); + glVertex3fv(mPoints[mWindings[j]].point); + } + glEnd(); + } + + glDisable(GL_POLYGON_OFFSET_FILL); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); +} diff --git a/interior/interiorRes.cc b/interior/interiorRes.cc new file mode 100644 index 0000000..a96862e --- /dev/null +++ b/interior/interiorRes.cc @@ -0,0 +1,320 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "Core/stream.h" +#include "interior/interior.h" +#include "interior/interiorResObjects.h" +#include "dgl/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 < mPaths.size(); i++) + delete mPaths[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]; + + 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; + stream.read(&previewIncluded); + if (previewIncluded) { + GBitmap bmp; + bmp.readPNG(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; + } + } + + // Paths + U32 numPaths; + stream.read(&numPaths); + mPaths.setSize(numPaths); + for (i = 0; i < mPaths.size(); i++) + mPaths[i] = NULL; + + for (i = 0; i < mPaths.size(); i++) { + mPaths[i] = new InteriorPath; + if (mPaths[i]->read(stream) == false) { + AssertISV(false, avar("Unable to read path %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); + + 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->writePNG(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(mPaths.size()); + for (i = 0; i < mPaths.size(); i++) { + if (mPaths[i]->write(stream) == false) { + AssertISV(false, "Unable to write path 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 + 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; + stream.read(&previewIncluded); + if (previewIncluded) { + GBitmap* pBmp = new GBitmap; + if (pBmp->readPNG(stream) == true) + return pBmp; + + delete pBmp; + } + + return NULL; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Interior Resource constructor +ResourceInstance* constructInteriorDIF(Stream& stream) +{ + InteriorResource* pResource = new InteriorResource; + + if (pResource->read(stream) == true) + return pResource; + else { + delete pResource; + return NULL; + } +} + diff --git a/interior/interiorRes.h b/interior/interiorRes.h new file mode 100644 index 0000000..13047da --- /dev/null +++ b/interior/interiorRes.h @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORRES_H_ +#define _INTERIORRES_H_ + +#ifndef _RESMANAGER_H_ +#include "Core/resManager.h" +#endif + +class Stream; +class Interior; +class GBitmap; +class InteriorResTrigger; +class InteriorPath; +class InteriorPathFollower; +class ForceField; +class AISpecialNode; + +class InteriorResource : public ResourceInstance +{ + typedef ResourceInstance Parent; + static const U32 smFileVersion; + + protected: + Vector mDetailLevels; + Vector mSubObjects; + Vector mTriggers; + Vector mPaths; + Vector mInteriorPathFollowers; + Vector mForceFields; + Vector mAISpecialNodes; + + 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 getNumPaths() const; + S32 getNumInteriorPathFollowers() const; + S32 getNumForceFields() const; + S32 getNumSpecialNodes() const; + + Interior* getDetailLevel(const U32); + Interior* getSubObject(const U32); + InteriorResTrigger* getTrigger(const U32); + InteriorPath* getPath(const U32); + InteriorPathFollower* getInteriorPathFollower(const U32); + ForceField* getForceField(const U32); + AISpecialNode* getSpecialNode(const U32); +}; +extern ResourceInstance* constructInteriorDIF(Stream& stream); + +//-------------------------------------------------------------------------- +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::getNumPaths() const +{ + return mPaths.size(); +} + +inline S32 InteriorResource::getNumSpecialNodes() const +{ + return mAISpecialNodes.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!"); + + return mDetailLevels[idx]; +} + +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 InteriorPath* InteriorResource::getPath(const U32 idx) +{ + AssertFatal(idx < getNumPaths(), "Error, out of bounds path!"); + + return mPaths[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]; +} + +#endif // _H_INTERIORRES_ + diff --git a/interior/interiorResObjects.cc b/interior/interiorResObjects.cc new file mode 100644 index 0000000..1bbf225 --- /dev/null +++ b/interior/interiorResObjects.cc @@ -0,0 +1,200 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interiorResObjects.h" +#include "Core/stream.h" +#include "Math/mathIO.h" + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +bool InteriorResTrigger::read(Stream& stream) +{ + U32 i, size; + stream.readString(mName); + + // 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); + + // 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); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +InteriorPath::InteriorPath() +{ + mName[0] = '\0'; + mWayPoints = NULL; + mNumWayPoints = 0; + mLooping = false; +} + +InteriorPath::~InteriorPath() +{ + delete [] mWayPoints; + mWayPoints = NULL; + mNumWayPoints = 0; +} + +bool InteriorPath::read(Stream& stream) +{ + AssertFatal(mWayPoints == NULL, "Hm, this is probably going to cause problems, reading a path twice..."); + + stream.readString(mName); + + stream.read(&mNumWayPoints); + mWayPoints = new WayPoint[mNumWayPoints]; + for (U32 i = 0; i < mNumWayPoints; i++) { + mathRead(stream, &mWayPoints[i].pos); + mathRead(stream, &mWayPoints[i].rot); + stream.read(&mWayPoints[i].msToNext); + } + stream.read(&mTotalMS); + stream.read(&mLooping); + + return (stream.getStatus() == Stream::Ok); +} + +bool InteriorPath::write(Stream& stream) const +{ + stream.writeString(mName); + + stream.write(mNumWayPoints); + for (U32 i = 0; i < mNumWayPoints; i++) { + mathWrite(stream, mWayPoints[i].pos); + mathWrite(stream, mWayPoints[i].rot); + stream.write(mWayPoints[i].msToNext); + } + stream.write(mTotalMS); + stream.write(mLooping); + + return (stream.getStatus() == Stream::Ok); +} + + +InteriorPathFollower::InteriorPathFollower() +{ + mName = ""; + mPathIndex = 0; + mOffset.set(0, 0, 0); +} + +InteriorPathFollower::~InteriorPathFollower() +{ + +} + +bool InteriorPathFollower::read(Stream& stream) +{ + mName = stream.readSTString(); + stream.read(&mInteriorResIndex); + stream.read(&mPathIndex); + mathRead(stream, &mOffset); + + U32 numTriggers; + stream.read(&numTriggers); + mTriggers.setSize(numTriggers); + for (U32 i = 0; i < mTriggers.size(); i++) + mTriggers[i] = stream.readSTString(); + + return (stream.getStatus() == Stream::Ok); +} + +bool InteriorPathFollower::write(Stream& stream) const +{ + stream.writeString(mName); + stream.write(mInteriorResIndex); + stream.write(mPathIndex); + mathWrite(stream, mOffset); + + stream.write(mTriggers.size()); + for (U32 i = 0; i < mTriggers.size(); i++) + stream.writeString(mTriggers[i]); + + 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); +} diff --git a/interior/interiorResObjects.h b/interior/interiorResObjects.h new file mode 100644 index 0000000..b3da96a --- /dev/null +++ b/interior/interiorResObjects.h @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORRESOBJECTS_H_ +#define _INTERIORRESOBJECTS_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MBOX_H_ +#include "Math/mBox.h" +#endif +#ifndef _MMATRIX_H_ +#include "Math/mMatrix.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _POLYHEDRON_H_ +#include "Collision/polyhedron.h" +#endif + +class Stream; + +class InteriorResTrigger +{ + public: + enum Constants { + MaxNameChars = 255 + }; + + char mName[MaxNameChars+1]; + + Point3F mOffset; + Polyhedron mPolyhedron; + + public: + InteriorResTrigger() { } + + bool read(Stream& stream); + bool write(Stream& stream) const; +}; + +class InteriorPath +{ + public: + struct WayPoint { + Point3F pos; + QuatF rot; + U32 msToNext; + }; + + public: + char mName[256]; + WayPoint* mWayPoints; + U32 mNumWayPoints; + U32 mTotalMS; + bool mLooping; + + public: + InteriorPath(); + ~InteriorPath(); + + bool read(Stream& stream); + bool write(Stream& stream) const; +}; + +class InteriorPathFollower +{ + public: + StringTableEntry mName; + U32 mInteriorResIndex; + U32 mPathIndex; + Point3F mOffset; + Vector mTriggers; + + 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; + +}; + +#endif // _H_INTERIORRESOBJECTS_ diff --git a/interior/interiorSubObject.cc b/interior/interiorSubObject.cc new file mode 100644 index 0000000..1756f69 --- /dev/null +++ b/interior/interiorSubObject.cc @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Core/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..97725be --- /dev/null +++ b/interior/interiorSubObject.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// 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 "Sim/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/itf.h b/interior/itf.h new file mode 100644 index 0000000..4a7428f --- /dev/null +++ b/interior/itf.h @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ITF_H_ +#define _ITF_H_ + +#ifndef _TYPES_H_ +#include "platform/types.h" +#endif +#ifndef _COLOR_H +#include "core/color.h" +#endif +#ifndef _MPOINT_H_ +#include "math/mPoint.h" +#endif + +#ifndef _INTERIOR_H_ +// redecl struct here for now... interior.h brings in the whole fricking codebase. +struct ItrPaddedPoint +{ + Point3F point; + union { + F32 fogCoord; + U8 fogColor[4]; + }; +}; +#endif + +struct OutputPoint +{ + Point3F point; + union { + F32 fogCoord; + U8 fogColor[4]; + }; + Point2F texCoord; + Point2F lmCoord; +}; + +struct OutputPointFC_VB +{ + Point3F point; + U8 currentColor[4]; + U8 fogColor[4]; + Point2F texCoord; + Point2F lmCoord; +}; + +struct OutputPointSP_FC_VB +{ + Point3F point; + U8 lmColor[4]; + U8 fogColor[4]; + Point2F texCoord; +}; + + +#ifndef TARG_MACCARB // don't bracket on mac. +extern "C" { +#endif + + void processTriFan(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices); + void processTriFanSP(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors); + void processTriFanVC_TF(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors); + void processTriFanSP_FC(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors); + void processTriFanFC_VB(OutputPointFC_VB* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices); + void processTriFanSP_FC_VB(OutputPointSP_FC_VB* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors); + +extern F32 texGen0[8]; +extern F32 texGen1[8]; +extern void* fogCoordinatePointer; + +#ifndef TARG_MACCARB // don't bracket on mac. +} +#endif + + +#endif diff --git a/interior/itfdump.asm b/interior/itfdump.asm new file mode 100644 index 0000000..a99ddee --- /dev/null +++ b/interior/itfdump.asm @@ -0,0 +1,657 @@ +; +; NASM version of tribes2/src/interior/itfdump.asm +; + + +segment .data + +storeebp dd 0 + +srcPoints dd 0 +srcColors dd 0 +srcIndices dd 0 +numPoints dd 0 +two55 dd 0x437F0000 +alpha dd 0 + +%ifdef __linux +; No underscore needed for ELF object files +%define _texGen0 texGen0 +%define _texGen1 texGen1 +%define _fogCoordinatePointer fogCoordinatePointer +%endif +extern _texGen0 +extern _texGen1 +extern _fogCoordinatePointer + + +segment .text + +; +; these macros are good for both functions +; + +%define in_dst [ebp+8] +%define in_src_points [ebp+12] +%define in_src_indices [ebp+16] +%define in_numpoints [ebp+20] + +%define in_srcColors [ebp+24] ; Valid only for SP + +; CodeWarrior sucks :P +%ifdef __linux +global processTriFan + +processTriFan: +%else +global _processTriFan + +_processTriFan: +%endif + + ; prologue + push ebp + mov ebp, esp + + ; Store the destination and source pointers + mov eax, in_src_points + mov [srcPoints], eax + mov eax, in_src_indices + mov [srcIndices], eax + mov eax, in_numpoints + mov [numPoints], eax + + mov edi, in_dst + + mov [storeebp], ebp + xor ebp, ebp +procPointLp1: + ; This could be faster + mov esi, [srcIndices] + lea esi, [esi + ebp*4] + mov eax, dword [esi] + shl eax, 4 ; idx *= 16 + mov esi, [srcPoints] + lea esi, [esi + eax] + + mov eax, [esi + 0] ; x + mov ebx, [esi + 4] ; y + mov ecx, [esi + 8] ; z + mov edx, [esi + 12] ; f + mov [edi + 0], eax ; <- x + mov [edi + 4], ebx ; <- y + mov [edi + 8], ecx ; <- z + mov [edi + 12], edx ; <- f + + ; tc0.s + fld dword [_texGen0 + 0] ; tg0.s.x + fmul dword [esi + 0] + fld dword [_texGen0 + 4] ; tg0.s.y + fmul dword [esi + 4] + fld dword [_texGen0 + 8] ; tg0.s.z + fmul dword [esi + 8] + fld dword [_texGen0 + 12] ; tg0.s.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 16] ; tc0.s + + ; tc0.t + fld dword [_texGen0 + 16] ; tg0.t.x + fmul dword [esi + 0] + fld dword [_texGen0 + 20] ; tg0.t.y + fmul dword [esi + 4] + fld dword [_texGen0 + 24] ; tg0.t.z + fmul dword [esi + 8] + fld dword [_texGen0 + 28] ; tg0.t.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 20] ; tc0.t + + ; tc1.s + fld dword [_texGen1 + 0] ; tg1.s.x + fmul dword [esi + 0] + fld dword [_texGen1 + 4] ; tg1.s.y + fmul dword [esi + 4] + fld dword [_texGen1 + 8] ; tg1.s.z + fmul dword [esi + 8] + fld dword [_texGen1 + 12] ; tg1.s.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 24] ; tc1.s + + ; tc1.t + fld dword [_texGen1 + 16] ; tg1.t.x + fmul dword [esi + 0] + fld dword [_texGen1 + 20] ; tg1.t.y + fmul dword [esi + 4] + fld dword [_texGen1 + 24] ; tg1.t.z + fmul dword [esi + 8] + fld dword [_texGen1 + 28] ; tg1.t.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 28] ; tc1.t + + add edi, 32 + + inc ebp + cmp ebp, [numPoints] + jl near procPointLp1 + + mov ebp, [storeebp] + + ; epilogue + pop ebp + ret + + +; More suckage +%ifdef __linux +global processTriFanSP + +processTriFanSP: +%else +global _processTriFanSP + +_processTriFanSP: +%endif + + ; prologue + push ebp + mov ebp, esp + + ; Store the destination and source pointers + mov eax, in_src_points + mov [srcPoints], eax + mov eax, in_src_indices + mov [srcIndices], eax + mov eax, in_numpoints + mov [numPoints], eax + mov eax, in_srcColors + mov [srcColors], eax + + mov edi, in_dst + + mov [storeebp], ebp + xor ebp, ebp +procPointLp2: + ; This could be faster + mov esi, [srcIndices] + lea esi, [esi + ebp*4] + mov eax, dword [esi] + shl eax, 4 ; idx *= 16 + mov esi, [srcPoints] + lea esi, [esi + eax] + + mov eax, [esi + 0] ; x + mov ebx, [esi + 4] ; y + mov ecx, [esi + 8] ; z + mov edx, [srcColors] ; color + mov [edi + 0], eax ; <- x + lea edx, [edx + ebp*4] ; color + mov [edi + 4], ebx ; <- y + mov edx, [edx] ; color + mov [edi + 8], ecx ; <- z + mov [edi + 12], edx ; color + + ; tc0.s + fld dword [_texGen0 + 0] ; tg0.s.x + fmul dword [esi + 0] + fld dword [_texGen0 + 4] ; tg0.s.y + fmul dword [esi + 4] + fld dword [_texGen0 + 8] ; tg0.s.z + fmul dword [esi + 8] + fld dword [_texGen0 + 12] ; tg0.s.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 16] ; tc0.s + + ; tc0.t + fld dword [_texGen0 + 16] ; tg0.t.x + fmul dword [esi + 0] + fld dword [_texGen0 + 20] ; tg0.t.y + fmul dword [esi + 4] + fld dword [_texGen0 + 24] ; tg0.t.z + fmul dword [esi + 8] + fld dword [_texGen0 + 28] ; tg0.t.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 20] ; tc0.t + + add edi, 32 + + inc ebp + cmp ebp, [numPoints] + jl near procPointLp2 + + mov ebp, [storeebp] + + ; epilogue + pop ebp + ret + + +; More suckage +%ifdef __linux +global processTriFanVC_TF + +processTriFanVC_TF: +%else +global _processTriFanVC_TF + +_processTriFanVC_TF: +%endif + + ; prologue + push ebp + mov ebp, esp + + ; Store the destination and source pointers + mov eax, in_src_points + mov [srcPoints], eax + mov eax, in_src_indices + mov [srcIndices], eax + mov eax, in_numpoints + mov [numPoints], eax + mov eax, in_srcColors + mov [srcColors], eax + + mov edi, in_dst + + mov [storeebp], ebp + xor ebp, ebp +procPointLp4: + ; This could be faster + mov esi, [srcIndices] + lea esi, [esi + ebp*4] + mov eax, dword [esi] + shl eax, 4 ; idx *= 16 + mov esi, [srcPoints] + lea esi, [esi + eax] + + ; Fog tex coord + mov ebx, [_fogCoordinatePointer] + shr eax, 1 ; idx /= 2 + lea ebx, [ebx + eax] + mov ecx, [ebx + 0]; + mov edx, [ebx + 4]; + mov [edi + 16], ecx + mov [edi + 20], edx + + mov eax, [esi + 0] ; x + mov ebx, [esi + 4] ; y + mov ecx, [esi + 8] ; z + mov edx, [srcColors] ; color + mov [edi + 0], eax ; <- x + lea edx, [edx + ebp*4] ; color + mov [edi + 4], ebx ; <- y + mov edx, [edx] ; color + mov [edi + 8], ecx ; <- z + mov [edi + 12], edx ; color + + + ; tc0.s + fld dword [_texGen0 + 0] ; tg0.s.x + fmul dword [esi + 0] + fld dword [_texGen0 + 4] ; tg0.s.y + fmul dword [esi + 4] + fld dword [_texGen0 + 8] ; tg0.s.z + fmul dword [esi + 8] + fld dword [_texGen0 + 12] ; tg0.s.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 24] ; tc0.s + + ; tc0.t + fld dword [_texGen0 + 16] ; tg0.t.x + fmul dword [esi + 0] + fld dword [_texGen0 + 20] ; tg0.t.y + fmul dword [esi + 4] + fld dword [_texGen0 + 24] ; tg0.t.z + fmul dword [esi + 8] + fld dword [_texGen0 + 28] ; tg0.t.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 28] ; tc0.t + + add edi, 32 + + inc ebp + cmp ebp, [numPoints] + jl near procPointLp4 + + mov ebp, [storeebp] + + ; epilogue + pop ebp + ret + + +; More suckagea +%ifdef __linux +global processTriFanSP_FC + +processTriFanSP_FC: +%else +global _processTriFanSP_FC + +_processTriFanSP_FC: +%endif + + ; prologue + push ebp + mov ebp, esp + + ; Store the destination and source pointers + mov eax, in_src_points + mov [srcPoints], eax + mov eax, in_src_indices + mov [srcIndices], eax + mov eax, in_numpoints + mov [numPoints], eax + mov eax, in_srcColors + mov [srcColors], eax + + mov edi, in_dst + + mov [storeebp], ebp + xor ebp, ebp +procPointLp2_fc: + ; This could be faster + mov esi, [srcIndices] + lea esi, [esi + ebp*4] + mov eax, dword [esi] + shl eax, 4 ; idx *= 16 + mov esi, [srcPoints] + lea esi, [esi + eax] + + mov eax, [esi + 0] ; x + mov ebx, [esi + 4] ; y + mov ecx, [esi + 8] ; z + mov edx, [esi + 12] ; fc + mov [edi + 0], eax ; <- x + mov [edi + 4], ebx ; <- y + mov [edi + 8], ecx ; <- z + mov [edi + 24], edx ; <- fc (lmcoord.x) + mov edx, [srcColors] ; color + lea edx, [edx + ebp*4] ; color + mov edx, [edx] ; color + mov [edi + 12], edx ; color + + ; tc0.s + fld dword [_texGen0 + 0] ; tg0.s.x + fmul dword [esi + 0] + fld dword [_texGen0 + 4] ; tg0.s.y + fmul dword [esi + 4] + fld dword [_texGen0 + 8] ; tg0.s.z + fmul dword [esi + 8] + fld dword [_texGen0 + 12] ; tg0.s.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 16] ; tc0.s + + ; tc0.t + fld dword [_texGen0 + 16] ; tg0.t.x + fmul dword [esi + 0] + fld dword [_texGen0 + 20] ; tg0.t.y + fmul dword [esi + 4] + fld dword [_texGen0 + 24] ; tg0.t.z + fmul dword [esi + 8] + fld dword [_texGen0 + 28] ; tg0.t.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 20] ; tc0.t + + add edi, 32 + + inc ebp + cmp ebp, [numPoints] + jl near procPointLp2_fc + + mov ebp, [storeebp] + + ; epilogue + pop ebp + ret + + + +; CodeWarrior still sucks :P +%ifdef __linux +global processTriFanFC_VB + +processTriFanFC_VB: +%else +global _processTriFanFC_VB + +_processTriFanFC_VB: +%endif + + ; prologue + push ebp + mov ebp, esp + + ; Store the destination and source pointers + mov eax, in_src_points + mov [srcPoints], eax + mov eax, in_src_indices + mov [srcIndices], eax + mov eax, in_numpoints + mov [numPoints], eax + + mov edi, in_dst + + mov [storeebp], ebp + xor ebp, ebp +procPointLp1_fc_vb: + ; This could be faster + mov esi, [srcIndices] + lea esi, [esi + ebp*4] + mov eax, dword [esi] + shl eax, 4 ; idx *= 16 + mov esi, [srcPoints] + lea esi, [esi + eax] + + mov eax, [esi + 0] ; x + mov ebx, [esi + 4] ; y + mov ecx, [esi + 8] ; z + mov edx, 0xFFFFFFFF ; c + mov [edi + 0], eax ; <- x + mov [edi + 4], ebx ; <- y + mov [edi + 8], ecx ; <- z + mov [edi + 12], edx ; <- c + + fld dword [esi + 12] + fld dword [two55] + fmulp st1, st0 + fistp dword [alpha] + mov eax, 255 + sub eax, [alpha] + cmp eax, 0 + jge near procPointLp1a_fc_vb + mov eax, 0 +procPointLp1a_fc_vb: + shl eax, 24 + mov [edi + 16], eax ; <- f + + ; tc0.s + fld dword [_texGen0 + 0] ; tg0.s.x + fmul dword [esi + 0] + fld dword [_texGen0 + 4] ; tg0.s.y + fmul dword [esi + 4] + fld dword [_texGen0 + 8] ; tg0.s.z + fmul dword [esi + 8] + fld dword [_texGen0 + 12] ; tg0.s.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 28] ; tc0.s + + ; tc0.t + fld dword [_texGen0 + 16] ; tg0.t.x + fmul dword [esi + 0] + fld dword [_texGen0 + 20] ; tg0.t.y + fmul dword [esi + 4] + fld dword [_texGen0 + 24] ; tg0.t.z + fmul dword [esi + 8] + fld dword [_texGen0 + 28] ; tg0.t.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 32] ; tc0.t + + ; tc1.s + fld dword [_texGen1 + 0] ; tg1.s.x + fmul dword [esi + 0] + fld dword [_texGen1 + 4] ; tg1.s.y + fmul dword [esi + 4] + fld dword [_texGen1 + 8] ; tg1.s.z + fmul dword [esi + 8] + fld dword [_texGen1 + 12] ; tg1.s.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 20] ; tc1.s + + ; tc1.t + fld dword [_texGen1 + 16] ; tg1.t.x + fmul dword [esi + 0] + fld dword [_texGen1 + 20] ; tg1.t.y + fmul dword [esi + 4] + fld dword [_texGen1 + 24] ; tg1.t.z + fmul dword [esi + 8] + fld dword [_texGen1 + 28] ; tg1.t.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 24] ; tc1.t + + add edi, 36 + + inc ebp + cmp ebp, [numPoints] + jl near procPointLp1_fc_vb + + mov ebp, [storeebp] + + ; epilogue + pop ebp + ret + + +; More suckagea +%ifdef __linux +global processTriFanSP_FC_VB + +processTriFanSP_FC_VB: +%else +global _processTriFanSP_FC_VB + +_processTriFanSP_FC_VB: +%endif + + ; prologue + push ebp + mov ebp, esp + + ; Store the destination and source pointers + mov eax, in_src_points + mov [srcPoints], eax + mov eax, in_src_indices + mov [srcIndices], eax + mov eax, in_numpoints + mov [numPoints], eax + mov eax, in_srcColors + mov [srcColors], eax + + mov edi, in_dst + + mov [storeebp], ebp + xor ebp, ebp +procPointLp2_fc_vb: + ; This could be faster + mov esi, [srcIndices] + lea esi, [esi + ebp*4] + mov eax, dword [esi] + shl eax, 4 ; idx *= 16 + mov esi, [srcPoints] + lea esi, [esi + eax] + + mov eax, [esi + 0] ; x + mov ebx, [esi + 4] ; y + mov ecx, [esi + 8] ; z + mov [edi + 0], eax ; <- x + mov [edi + 4], ebx ; <- y + mov [edi + 8], ecx ; <- z + + fld dword [esi + 12] + fld dword [two55] + fmulp st1, st0 + fistp dword [alpha] + mov eax, 255 + sub eax, [alpha] + cmp eax, 0 + jge near procPointLp2a_fc_vb + mov eax, 0 +procPointLp2a_fc_vb: + shl eax, 24 + mov [edi + 16], eax ; <- fc + + mov edx, [srcColors] ; color + lea edx, [edx + ebp*4] ; color + mov edx, [edx] ; color + mov eax, edx + mov ebx, 0x00FF00FF + and edx, ebx + not ebx + rol edx, 16 + and eax, ebx + or edx, eax + mov [edi + 12], edx ; color + + ; tc0.s + fld dword [_texGen0 + 0] ; tg0.s.x + fmul dword [esi + 0] + fld dword [_texGen0 + 4] ; tg0.s.y + fmul dword [esi + 4] + fld dword [_texGen0 + 8] ; tg0.s.z + fmul dword [esi + 8] + fld dword [_texGen0 + 12] ; tg0.s.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 20] ; tc0.s + + ; tc0.t + fld dword [_texGen0 + 16] ; tg0.t.x + fmul dword [esi + 0] + fld dword [_texGen0 + 20] ; tg0.t.y + fmul dword [esi + 4] + fld dword [_texGen0 + 24] ; tg0.t.z + fmul dword [esi + 8] + fld dword [_texGen0 + 28] ; tg0.t.w + faddp st3, st0 + faddp st1, st0 + faddp st1, st0 + fstp dword [edi + 24] ; tc0.t + + add edi, 28 + + inc ebp + cmp ebp, [numPoints] + jl near procPointLp2_fc_vb + + mov ebp, [storeebp] + + ; epilogue + pop ebp + ret \ No newline at end of file diff --git a/interior/itfdump.cc b/interior/itfdump.cc new file mode 100644 index 0000000..62cb996 --- /dev/null +++ b/interior/itfdump.cc @@ -0,0 +1,311 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/itf.h" + +//two55 dd 0x437F0000 +//alpha dd 0 + +//============================================================ +void processTriFan(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices) +{ + U32 i, j; + F32 x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->fogCoord = srcPoints[j].fogCoord; + + dst->texCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->texCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); + + dst->lmCoord.x = (texGen1[0]*x) + + (texGen1[1]*y) + + (texGen1[2]*z) + + (texGen1[3]); + + dst->lmCoord.y = (texGen1[4]*x) + + (texGen1[5]*y) + + (texGen1[6]*z) + + (texGen1[7]); + +// move to next ptr. + dst++; + } +} + + +//============================================================ +void processTriFanSP(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors) +{ + U32 i, j; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->fogColors = srcColors[j].getARGBEndian(); + + dst->texCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->texCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); +// move to next ptr. + dst++; + } +} + + +//============================================================ +void processTriFanVC_TF(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors) +{ + U32 i, j; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->fogColors = srcColors[j].getARGBEndian(); + + // dc - I >think< I got this right... + dst->texCoord.x = fogCoordinatePointer[j].x; + dst->texCoord.y = fogCoordinatePointer[j].y; + + dst->lmCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->lmCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); +// move to next ptr. + dst++; + } +} + + +//============================================================ +void processTriFanSP_FC(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors) +{ + U32 i, j; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->fogColors = srcColors[j].getARGBEndian(); + dst->lmCoord.x = srcPoints[j].fogCoord; + + dst->texCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->texCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); +// move to next ptr. + dst++; + } +} + + +//============================================================ +// helpers: +const float two55 = (F32)0x473F0000; // !!!!!!TBD -- not sure this is right... + +#define ALPHA_CALC(a, c) \ + a = c * two55; \ + a = 255 - a; /* flip direction of value. */ \ + if (a < 0) a = 0; + +/* ASM for previous calculation: + fld dword [esi + 12] + fld dword [two55] + fmulp st1, st0 + fistp dword [alpha] + mov eax, 255 + sub eax, [alpha] + cmp eax, 0 + jge near procPointLp1a_fc_vb + mov eax, 0 +procPointLp1a_fc_vb: + shl eax, 24 // left this in the function instead of the macro. + mov [edi + 16], eax ; <- f +*/ + + +//============================================================ +void processTriFanFC_VB(OutputPointFC_VB* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices) +{ + S32 alpha; + U32 i, j; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->currentColors = 0xFFFFFFFF; + + ALPHA_CALC(alpha, srcPoints[j].fogCoord); + dst->fogColors = ((U32)alpha)<<24; // move into alpha position. + + // dc - note the texGens are used in reverse order. that's what the ASM did... + dst->texCoord.x = (texGen1[0]*x) + + (texGen1[1]*y) + + (texGen1[2]*z) + + (texGen1[3]); + + dst->texCoord.y = (texGen1[4]*x) + + (texGen1[5]*y) + + (texGen1[6]*z) + + (texGen1[7]); + + dst->lmCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->lmCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); + +// move to next ptr. + dst++; + } +} + + +//!!!!!!TBD -- is there a rotate intrinsic????? +#define ROL16(x) (x) = ((((x)<<16)&0xFFFF0000) | (((x)>>16)&0x0000FFFF)) + +//============================================================ +void processTriFanSP_FC_VB(OutputPointSP_FC_VB* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors) +{ + S32 alpha; + U32 i, j, tmp, tmp2; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + +/* + mov edx, [srcColors] ; color + lea edx, [edx + ebp*4] ; color + mov edx, [edx] ; color + mov eax, edx + mov ebx, 0x00FF00FF + and edx, ebx + not ebx + and eax, ebx + rol edx, 16 + or edx, eax + mov [edi + 12], edx ; color +*/ + + tmp = srcColors[j].getARGBEndian(); + tmp2 = tmp; + + tmp = (tmp & 0x00FF00FF); + tmp2 = (tmp2 & 0xFF00FF00); + + ROL16(tmp); + + dst->lmColors = (tmp | tmp2); + + ALPHA_CALC(alpha, srcPoints[j].fogCoord); + dst->fogColors = ((U32)alpha)<<24; // move into alpha position. + + dst->texCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->texCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); +// move to next ptr. + dst++; + } +} \ No newline at end of file diff --git a/interior/itfdump_c.cc b/interior/itfdump_c.cc new file mode 100644 index 0000000..823e48e --- /dev/null +++ b/interior/itfdump_c.cc @@ -0,0 +1,311 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/itf.h" + +//two55 dd 0x437F0000 +//alpha dd 0 + +//============================================================ +void FN_CDECL processTriFan(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices) +{ + U32 i, j; + F32 x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->fogCoord = srcPoints[j].fogCoord; + + dst->texCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->texCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); + + dst->lmCoord.x = (texGen1[0]*x) + + (texGen1[1]*y) + + (texGen1[2]*z) + + (texGen1[3]); + + dst->lmCoord.y = (texGen1[4]*x) + + (texGen1[5]*y) + + (texGen1[6]*z) + + (texGen1[7]); + +// move to next ptr. + dst++; + } +} + + +//============================================================ +void FN_CDECL processTriFanSP(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors) +{ + U32 i, j; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->fogColors = srcColors[j].getARGBEndian(); + + dst->texCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->texCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); +// move to next ptr. + dst++; + } +} + + +//============================================================ +void FN_CDECL processTriFanVC_TF(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors) +{ + U32 i, j; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->fogColors = srcColors[j].getARGBEndian(); + + // dc - I >think< I got this right... + dst->texCoord.x = fogCoordinatePointer[j].x; + dst->texCoord.y = fogCoordinatePointer[j].y; + + dst->lmCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->lmCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); +// move to next ptr. + dst++; + } +} + + +//============================================================ +void FN_CDECL processTriFanSP_FC(OutputPoint* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors) +{ + U32 i, j; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->fogColors = srcColors[j].getARGBEndian(); + dst->lmCoord.x = srcPoints[j].fogCoord; + + dst->texCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->texCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); +// move to next ptr. + dst++; + } +} + + +//============================================================ +// helpers: +const float two55 = (F32)0x473F0000; // !!!!!!TBD -- not sure this is right... + +#define ALPHA_CALC(a, c) \ + a = c * two55; \ + a = 255 - a; /* flip direction of value. */ \ + if (a < 0) a = 0; + +/* ASM for previous calculation: + fld dword [esi + 12] + fld dword [two55] + fmulp st1, st0 + fistp dword [alpha] + mov eax, 255 + sub eax, [alpha] + cmp eax, 0 + jge near procPointLp1a_fc_vb + mov eax, 0 +procPointLp1a_fc_vb: + shl eax, 24 // left this in the function instead of the macro. + mov [edi + 16], eax ; <- f +*/ + + +//============================================================ +void FN_CDECL processTriFanFC_VB(OutputPointFC_VB* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices) +{ + S32 alpha; + U32 i, j; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + dst->currentColors = 0xFFFFFFFF; + + ALPHA_CALC(alpha, srcPoints[j].fogCoord); + dst->fogColors = ((U32)alpha)<<24; // move into alpha position. + + // dc - note the texGens are used in reverse order. that's what the ASM did... + dst->texCoord.x = (texGen1[0]*x) + + (texGen1[1]*y) + + (texGen1[2]*z) + + (texGen1[3]); + + dst->texCoord.y = (texGen1[4]*x) + + (texGen1[5]*y) + + (texGen1[6]*z) + + (texGen1[7]); + + dst->lmCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->lmCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); + +// move to next ptr. + dst++; + } +} + + +//!!!!!!TBD -- is there a rotate intrinsic????? +#define ROL16(x) (x) = ((((x)<<16)&0xFFFF0000) | (((x)>>16)&0x0000FFFF)) + +//============================================================ +void FN_CDECL processTriFanSP_FC_VB(OutputPointSP_FC_VB* dst, + const ItrPaddedPoint* srcPoints, + const U32* srcIndices, + const U32 numIndices, + const ColorI* srcColors) +{ + S32 alpha; + U32 i, j, tmp, tmp2; + float x,y,z; + for (i=0; ipoint.x = x; + dst->point.y = y; + dst->point.z = z; + +/* + mov edx, [srcColors] ; color + lea edx, [edx + ebp*4] ; color + mov edx, [edx] ; color + mov eax, edx + mov ebx, 0x00FF00FF + and edx, ebx + not ebx + and eax, ebx + rol edx, 16 + or edx, eax + mov [edi + 12], edx ; color +*/ + + tmp = srcColors[j].getARGBEndian(); + tmp2 = tmp; + + tmp = (tmp & 0x00FF00FF); + tmp2 = (tmp2 & 0xFF00FF00); + + ROL16(tmp); + + dst->lmColors = (tmp | tmp2); + + ALPHA_CALC(alpha, srcPoints[j].fogCoord); + dst->fogColors = ((U32)alpha)<<24; // move into alpha position. + + dst->texCoord.x = (texGen0[0]*x) + + (texGen0[1]*y) + + (texGen0[2]*z) + + (texGen0[3]); + + dst->texCoord.y = (texGen0[4]*x) + + (texGen0[5]*y) + + (texGen0[6]*z) + + (texGen0[7]); +// move to next ptr. + dst++; + } +} \ No newline at end of file diff --git a/interior/lightUpdateGrouper.cc b/interior/lightUpdateGrouper.cc new file mode 100644 index 0000000..1dcb09b --- /dev/null +++ b/interior/lightUpdateGrouper.cc @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/lightUpdateGrouper.h" + +LightUpdateGrouper::LightUpdateGrouper(const U32 bitStart, const U32 bitEnd) +{ + AssertFatal(bitEnd >= bitStart, "Error, bitend must be greater than bit start"); + AssertFatal(bitEnd < 32, "Error, bitend too large. must be in the range 0..31"); + + mBitStart = bitStart; + mBitEnd = bitEnd; +} + + +LightUpdateGrouper::~LightUpdateGrouper() +{ + mBitStart = 0xFFFFFFFF; + mBitEnd = 0xFFFFFFFF; +} + + +void LightUpdateGrouper::addKey(const U32 key) +{ +#ifdef DEBUG + for (U32 i = 0; i < mKeys.size(); i++) + AssertFatal(mKeys[i] != key, "Error, key already in the array!"); +#endif + + mKeys.push_back(key); +} + +U32 LightUpdateGrouper::getKeyMask(const U32 key) const +{ + U32 numBits = mBitEnd - mBitStart + 1; + for (U32 i = 0; i < mKeys.size(); i++) { + if (mKeys[i] == key) { + U32 idx = i % numBits; + return (1 << (idx + mBitStart)); + } + } + + AssertFatal(false, "Error, key not in the array!"); + return 0; +} + +LightUpdateGrouper::BitIterator LightUpdateGrouper::begin() +{ + BitIterator itr; + itr.mGrouper = this; + itr.mCurrBit = mBitStart; + itr.resetKeyArray(); + + return itr; +} + +void LightUpdateGrouper::BitIterator::resetKeyArray() +{ + mKeyArray.clear(); + if (valid() == false) + return; + + // Ok, we need to select out every (mBitEnd - mBitStart - 1)th key, + // starting at mCurrBit. + + U32 numBits = mGrouper->mBitEnd - mGrouper->mBitStart + 1; + U32 numKeys = mGrouper->mKeys.size(); + + for (U32 i = mCurrBit - mGrouper->mBitStart; i < numKeys; i += numBits) { + mKeyArray.push_back(mGrouper->mKeys[i]); + } +} + diff --git a/interior/lightUpdateGrouper.h b/interior/lightUpdateGrouper.h new file mode 100644 index 0000000..fe7fc55 --- /dev/null +++ b/interior/lightUpdateGrouper.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTUPDATEGROUPER_H_ +#define _LIGHTUPDATEGROUPER_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + +class LightUpdateGrouper +{ + Vector mKeys; + U32 mBitStart; + U32 mBitEnd; + + public: + class BitIterator { + friend class LightUpdateGrouper; + + private: + Vector mKeyArray; + U32 mCurrBit; + LightUpdateGrouper* mGrouper; + + void resetKeyArray(); + + public: + typedef U32 const* iterator; + + bool valid(); + U32 getNumKeys(); + U32 getMask(); + + BitIterator& operator++(int); + BitIterator& operator++(); + + iterator begin(); + iterator end(); + }; + friend class BitIterator; + + public: + LightUpdateGrouper(const U32 bitStart, const U32 bitEnd); + ~LightUpdateGrouper(); + + void addKey(const U32 key); + U32 getKeyMask(const U32 key) const; + + BitIterator begin(); +}; + + +//-------------------------------------------------------------------------- +inline LightUpdateGrouper::BitIterator& LightUpdateGrouper::BitIterator::operator++() +{ + mCurrBit++; + resetKeyArray(); + + return *this; +} + +inline LightUpdateGrouper::BitIterator& LightUpdateGrouper::BitIterator::operator++(int) +{ + return operator++(); +} + +inline LightUpdateGrouper::BitIterator::iterator LightUpdateGrouper::BitIterator::begin() +{ + if (valid() == false) + return NULL; + + return mKeyArray.begin(); +} + +inline LightUpdateGrouper::BitIterator::iterator LightUpdateGrouper::BitIterator::end() +{ + if (valid() == false) + return NULL; + + return mKeyArray.end(); +} + +inline bool LightUpdateGrouper::BitIterator::valid() +{ + return mCurrBit <= mGrouper->mBitEnd; +} + +inline U32 LightUpdateGrouper::BitIterator::getNumKeys() +{ + return mKeyArray.size(); +} + +inline U32 LightUpdateGrouper::BitIterator::getMask() +{ + return (1 << mCurrBit); +} + + + +#endif // _H_LIGHTUPDATEGROUPER_ diff --git a/interior/mirrorSubObject.cc b/interior/mirrorSubObject.cc new file mode 100644 index 0000000..09bd1c8 --- /dev/null +++ b/interior/mirrorSubObject.cc @@ -0,0 +1,532 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// 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 "dgl/materialList.h" +#include "Core/stream.h" +#include "PlatformWin32/platformGL.h" +#include "dgl/dgl.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) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + SubObjectRenderImage* sori = static_cast(image); + + if (mZone == 0) { + state->setupZoneProjection(getInstance()->getCurrZone(0)); + } else { + state->setupZoneProjection(mZone + getInstance()->getZoneRangeStart() - 1); + } + + RectI viewport; + dglGetViewport(&viewport); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&getSOTransform()); + glScalef(getSOScale().x, getSOScale().y, getSOScale().z); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_TEXTURE_2D); + glEnable(GL_TEXTURE_GEN_S); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glEnable(GL_TEXTURE_GEN_T); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glEnableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor4f(1, 1, 1, mAlphaLevel); + + Interior* interior = getInstance()->getDetailLevel(sori->mDetailLevel); + glVertexPointer(3, GL_FLOAT, sizeof(ItrPaddedPoint), interior->mPoints.address()); + + glBindTexture(GL_TEXTURE_2D, interior->mMaterialList->getMaterial(interior->mSurfaces[surfaceStart].textureIndex).getGLName()); + glTexGenfv(GL_S, GL_OBJECT_PLANE, (GLfloat*)interior->mTexGenEQs[interior->mSurfaces[surfaceStart].texGenIndex].planeX); + glTexGenfv(GL_T, GL_OBJECT_PLANE, (GLfloat*)interior->mTexGenEQs[interior->mSurfaces[surfaceStart].texGenIndex].planeY); + + for (U32 i = 0; i < surfaceCount; i++) { + glDrawElements(GL_TRIANGLE_STRIP, + interior->mSurfaces[surfaceStart+i].windingCount, + GL_UNSIGNED_INT, + &interior->mWindings[interior->mSurfaces[surfaceStart+i].windingStart]); + } + + glDisableClientState(GL_VERTEX_ARRAY); + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + dglSetViewport(viewport); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + dglSetCanonicalState(); + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +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 F64* 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; + dglGetModelview(&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) +{ + AssertFatal(isInitialized() == true, "Error, we should have been initialized by this point!"); + AssertFatal(portalIndex == 0, "Error, mirrortests only have one portal!"); + + RectI viewport; + dglGetViewport(&viewport); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + const SceneState::ZoneState& baseState = pCurrState->getBaseZoneState(); + dglSetViewport(baseState.viewport); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_ALWAYS); + + glColor3f(1, 0, 0); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(-1, -1, 0); + glVertex3f(-1, 1, 0); + glVertex3f( 1, 1, 0); + glVertex3f( 1, -1, 0); + glEnd(); + + // This is fairly tricky-tricky, so here's what's going on. We need this poly + // to be at the far plane, but have it's rasterization coords be exactly that of + // the final poly. So we copy the w-coord post projection into the z coord. this + // ensures that after the w divide, we have the z coord == 1. This would screw + // up texturing, but we don't really care, do we? + + if (mZone == 0) + pParentState->setupZoneProjection(getInstance()->getCurrZone(0)); + else + pParentState->setupZoneProjection(mZone + getInstance()->getZoneRangeStart() - 1); + + MatrixF finalProj(true); + MatrixF currProj; + dglGetProjection(&currProj); + ((F32*)finalProj)[10] = 0.0f; + ((F32*)finalProj)[11] = 0.9999f; + finalProj.mul(currProj); + glMatrixMode(GL_PROJECTION); + dglLoadMatrix(&finalProj); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glPushMatrix(); + dglMultMatrix(&getSOTransform()); + glScalef(getSOScale().x, getSOScale().y, getSOScale().z); + + glColor3f(0, 1, 0); + Interior* interior = getInstance()->getDetailLevel(mDetailLevel); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(ItrPaddedPoint), interior->mPoints.address()); + + for (U32 i = 0; i < surfaceCount; i++) { + const Interior::Surface& rSurface = interior->mSurfaces[surfaceStart + i]; + + glBegin(GL_TRIANGLE_STRIP); + for (U32 j = 0; j < rSurface.windingCount; j++) { + const Point3F& rPoint = interior->mPoints[interior->mWindings[rSurface.windingStart + j]].point; + glVertex3fv(rPoint); + } + glEnd(); + } + + glDisableClientState(GL_VERTEX_ARRAY); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + dglSetViewport(viewport); + + glDepthFunc(GL_LEQUAL); + dglSetCanonicalState(); +} + + +//-------------------------------------------------------------------------- +void MirrorSubObject::closePortal(const U32 portalIndex, + SceneState* pCurrState, + SceneState* pParentState) +{ + AssertFatal(isInitialized() == true, "Error, we should have been initialized by this point!"); + AssertFatal(portalIndex == 0, "Error, mirrortests only have one portal!"); + + RectI viewport; + dglGetViewport(&viewport); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + const SceneState::ZoneState& baseState = pCurrState->getBaseZoneState(); + dglSetViewport(baseState.viewport); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glDepthFunc(GL_ALWAYS); + + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glColor3f(0, 0, 1); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(-1, -1, 1); + glVertex3f(-1, 1, 1); + glVertex3f( 1, 1, 1); + glVertex3f( 1, -1, 1); + glEnd(); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + if (mZone == 0) { + pParentState->setupZoneProjection(getInstance()->getCurrZone(0)); + } else { + pParentState->setupZoneProjection(mZone + getInstance()->getZoneRangeStart() - 1); + } + + glMatrixMode(GL_MODELVIEW); + dglLoadMatrix(&pParentState->mModelview); + dglMultMatrix(&getSOTransform()); + glScalef(getSOScale().x, getSOScale().y, getSOScale().z); + + // Need to have texturing turned on because of lame LSB z buffer errors + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, mWhite->getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + + glColor3f(1, 0, 1); + glEnableClientState(GL_VERTEX_ARRAY); + + Interior* interior = getInstance()->getDetailLevel(mDetailLevel); + glVertexPointer(3, GL_FLOAT, sizeof(ItrPaddedPoint), interior->mPoints.address()); + for (U32 i = 0; i < surfaceCount; i++) { + glDrawElements(GL_TRIANGLE_STRIP, + interior->mSurfaces[surfaceStart+i].windingCount, + GL_UNSIGNED_INT, + &interior->mWindings[interior->mSurfaces[surfaceStart+i].windingStart]); + } + glDisableClientState(GL_VERTEX_ARRAY); + + glDisable(GL_TEXTURE_2D); + glBlendFunc(GL_ONE, GL_ZERO); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + dglSetViewport(viewport); + + glDepthFunc(GL_LEQUAL); + dglSetCanonicalState(); +} + + +//-------------------------------------------------------------------------- +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; +} + + +SubObjectRenderImage* MirrorSubObject::getRenderImage(SceneState* state, + const Point3F& osPoint) +{ + if (isInitialized() == false) + setupTransforms(); + + + // Check to make sure that we're on the right side of the plane... + 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(); + + if (plane.whichSide(osPoint) != PlaneF::Front) + return NULL; + + // On the right side, guess we have to return an image and a portal... + // + SubObjectRenderImage* ri = new SubObjectRenderImage; + + ri->obj = this; + ri->isTranslucent = false; + + U32 realZone; + if (getInstance()->getZoneRangeStart() == 0xFFFFFFFF || mZone == 0) { + realZone = getInstance()->getCurrZone(0); + } else { + realZone = getInstance()->getZoneRangeStart() + mZone - 1; + } + + // Create the WS start point. this will be the centroid of the first poly in os space, + // transformed out for the sceneGraph, with a smidge of our normal added in to pull + // it off the surface plane... + + Point3F startPoint = mCentroid; + PlaneF temp = interior->getPlane(rSurface.planeIndex); + if (Interior::planeIsFlipped(rSurface.planeIndex)) + temp.neg(); + startPoint += Point3F(temp.x, temp.y, temp.z) * 0.01f; + getSOTransform().mulP(mCentroid); + startPoint.convolve(getSOScale()); + + state->insertTransformPortal(this, 0, realZone, startPoint, true); + + return ri; +} + + +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 TextureHandle("special/whiteAlpha0", MeshTexture); + + 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..677af15 --- /dev/null +++ b/interior/mirrorSubObject.h @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// 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 + +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; + TextureHandle* 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 F64* 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/math/mBezier2D.cc b/math/mBezier2D.cc new file mode 100644 index 0000000..fdf5794 --- /dev/null +++ b/math/mBezier2D.cc @@ -0,0 +1,223 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "math/mBezier2D.h" + +/** + * Constructor + * + * @param inCtrlPts Array of control points to define the curve + * @param inPtsSize Size of the array of control points + * @param numCurvePts Number of points to be calculated for the curve + */ +Bezier2D::Bezier2D( const Point2F *inCtrlPts, const U32 inPtsSize, const U32 numCurvePts ) { + mControlPoints = new Point2F[inPtsSize]; + mCurvePoints = NULL; + mLastScaledCurve = NULL; + mLastScaleValue = 1.0f; + + mNumCurvePoints = numCurvePts; + mNumCtrlPoints = inPtsSize; + + if( mNumCurvePoints < MBEZIER2D_MIN_CURVE_POINTS ) + mNumCurvePoints = MBEZIER2D_MIN_CURVE_POINTS; + else if( mNumCurvePoints > MBEZIER2D_MAX_CURVE_POINTS ) + mNumCurvePoints = MBEZIER2D_MAX_CURVE_POINTS; + + for( int i = 0; i < inPtsSize; i++ ) + mControlPoints[i] = inCtrlPts[i]; + + calcCurve(); +} + +/** + * Destructor + */ +Bezier2D::~Bezier2D() { + delete [] mControlPoints; + delete [] mCurvePoints; + + if( mLastScaledCurve != NULL ) + delete mLastScaledCurve; +} + +/** + * Calculates the curve based on the control points. + * Uses de Casteljau's algorithm. References: + * + * -http://www.css.tayloru.edu/~btoll/s99/424/res/mtu/Notes/spline/de-casteljau.htm + * -http://astronomy.swin.edu.au/pbourke/curves/bezier/ + */ +void Bezier2D::calcCurve() { + Point2F *tempPointArray = new Point2F[mNumCtrlPoints]; + + F32 precision = ( 1.0f / mNumCurvePoints ); + + if( mCurvePoints != NULL ) + delete [] mCurvePoints; + + mCurvePoints = new Point2F[mNumCurvePoints]; + S32 idx = 0; + + for( F32 u = 0.0f; u <= 1.0f; u += precision ) { + for( S32 i = 0; i < mNumCtrlPoints; i++ ) + tempPointArray[i] = mControlPoints[i]; // Making copies...grrr + + for( S32 j = 1; j < mNumCtrlPoints; j++ ) + for( S32 k = 0; k < mNumCtrlPoints - j; k++ ) + tempPointArray[k] = ( 1 - u ) * tempPointArray[k] + u * tempPointArray[k + 1]; + + mCurvePoints[idx].set( tempPointArray[0].x, tempPointArray[0].y ); + idx++; + } + + mRescale = true; + + mCurveLength = 0.0f; + for( int i = 1; i < mNumCurvePoints; i++ ) { + Point2F &pt1 = mCurvePoints[i - 1]; + Point2F &pt2 = mCurvePoints[i]; + mCurveLength += mSqrt( mPow( pt2.x - pt1.x, 2.0f) + mPow( pt2.y - pt1.y, 2.0f ) ); + } +} + +/** + * Gives the curve a new set of control points and recalculates + * + * @param inCtrlPts Array of control points to define the curve + * @param inPtsSize Size of the array of control points + */ +void Bezier2D::setControlPoints( const Point2F *inCtrlPts, const U32 inPtsSize ) { + delete [] mControlPoints; + + mControlPoints = new Point2F[inPtsSize]; + + for( int i = 0; i < inPtsSize; i++ ) + mControlPoints[i] = inCtrlPts[i]; + + mNumCtrlPoints = inPtsSize; + + calcCurve(); +} + +/** + * Sets a new precision value, and recalculates the curve + * + * @param precision How precice the curve caluclations will be. 0.0f < precision < 1.0f + * Smaller value = more precise + */ +void Bezier2D::setNumCalcPoints( const U32 numCurvePts ) { + mNumCurvePoints = numCurvePts; + + calcCurve(); +} + +/** + * Returns the number of points this object is calculating + * for the Bezier it represents + * + * @return Number of curve points calculating + */ +U32 Bezier2D::getNumCurvePoints() const { + return mNumCurvePoints; +} + +/** + * Returns the number of control points in this Bezier curve + * + * @return Number of control points + */ +U32 Bezier2D::getNumCtrlPoints() const { + return mNumCtrlPoints; +} + +/** + * Returns the control points for this Bezier in an array + * + * @return Control points for this Bezier + */ +Point2F *Bezier2D::getControlPoints() const { + return mControlPoints; +} + +/** + * Returns the calculated curve points with the precision set earlier + * + * @return The points needed to render this curve + */ +Point2F *Bezier2D::getCurvePoints() const { + return mCurvePoints; +} + +/** + * Gets the length of the calculated curve + * + * @return Length of the calculated curve + */ +F32 Bezier2D::getCurveLength() const { + return mCurveLength; +} + +/** + * Scales the curve (by scaling the control points and recalculating the curve) + * Saves the last values so repeated requests for the same scale value don't require recalculation + * + * @param scaleValue Value to scale the curve by, scaleValue > 0 + * @return A Bezier2D object with the new curve contained in it + */ +Bezier2D *Bezier2D::getScaledCurve( const F32 scaleValue ) { + if( !mRescale && mLastScaleValue == scaleValue ) + return mLastScaledCurve; + + Point2F *tempPoints = new Point2F[mNumCtrlPoints]; + //F32 areaPolygon = 0.0f; + Point2F centroid( 0.0f, 0.0f ); + + for( int i = 0; i < mNumCtrlPoints; i++ ) { + tempPoints[i] = mControlPoints[i]; + + Point2F &currPt = mControlPoints[i]; + //Point2F nextPt; + //if( i + 1 == mNumCtrlPoints ) + // nextPt = mControlPoints[0]; + //else + // nextPt = mControlPoints[i + 1]; + + //areaPolygon += currPt.x * nextPt.y - nextPt.x * currPt.y; + + centroid.x += currPt.x; + centroid.y += currPt.y; + //centroid.x += ( currPt.x + nextPt.x ) * ( currPt.x * nextPt.y - nextPt.x * currPt.y ); + //centroid.y += ( currPt.y + nextPt.y ) * ( currPt.x * nextPt.y - nextPt.x * currPt.y ); + } + + //areaPolygon *= .5f; + //centroid.x *= ( 1 / 6 * areaPolygon ); + //centroid.y *= ( 1 / 6 * areaPolygon ); + centroid.x /= mNumCtrlPoints; + centroid.y /= mNumCtrlPoints; + + for( int i = 0; i < mNumCtrlPoints; i++ ) { + tempPoints[i].x -= centroid.x; + tempPoints[i].y -= centroid.y; + + tempPoints[i].x *= scaleValue; + tempPoints[i].y *= scaleValue; + + tempPoints[i].x += centroid.x; + tempPoints[i].y += centroid.y; + } + if( mLastScaledCurve != NULL ) + delete mLastScaledCurve; + + mLastScaledCurve = new Bezier2D( tempPoints, mNumCtrlPoints, mNumCurvePoints ); + mRescale = false; + + delete [] tempPoints; + + return mLastScaledCurve; +} \ No newline at end of file diff --git a/math/mBezier2D.h b/math/mBezier2D.h new file mode 100644 index 0000000..2451ac8 --- /dev/null +++ b/math/mBezier2D.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- +#ifndef _BEZIER2D_H_ +#define _BEZIER2D_H_ + +// For some reason, the engine will bomb out if you try to make a curve with 50+ calculated points +#define MBEZIER2D_MIN_CURVE_POINTS 2 +#define MBEZIER2D_MAX_CURVE_POINTS 49 + +#include "math/mPoint.h" + +class Bezier2D { + + private: + Point2F *mControlPoints; + Point2F *mCurvePoints; + + U32 mNumCtrlPoints; + U32 mNumCurvePoints; + F32 mCurveLength; + + bool mRescale; + Bezier2D *mLastScaledCurve; + F32 mLastScaleValue; + + void calcCurve(); + + public: + Bezier2D( const Point2F *inCtrlPts, const U32 inPtsSize, const U32 numCurvePts ); + ~Bezier2D(); + + void setControlPoints( const Point2F *inCtrlPts, const U32 inPtsSize ); + void setNumCalcPoints( const U32 numCurvePts ); + + U32 getNumCurvePoints() const; + U32 getNumCtrlPoints() const; + Point2F *getControlPoints() const; + Point2F *getCurvePoints() const; + F32 getCurveLength() const; + + Bezier2D *getScaledCurve( const F32 scaleValue ); +}; + +#endif + +// mBezier2D.h \ No newline at end of file diff --git a/math/mBox.cc b/math/mBox.cc new file mode 100644 index 0000000..97f19c6 --- /dev/null +++ b/math/mBox.cc @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Math/mBox.h" +#include "Math/mMatrix.h" + +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 = &min.x; + const F32* bmax = &max.x; + const F32* si = &start.x; + const F32* ei = &end.x; + + Point3F na[3] = { Point3F(1, 0, 0), Point3F(0, 1, 0), Point3F(0, 0, 1) }; + Point3F finalNormal; + + for (int i = 0; i < 3; i++) { + Point3F normal = na[i]; + 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; + et = (ei[i] > bmax[i]) ? (bmax[i] - si[i]) / di : 1; + normal.neg(); + } + 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; + et = (ei[i] < bmin[i]) ? (bmin[i] - si[i]) / di : 1; + } + if (st > fst) { + fst = st; + finalNormal = normal; + } + 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 = min + max; + aCenter *= 0.5f; + p -= aCenter; // this essentially puts origin of toA target space on the center of the current box + Point3F aRadii = max - min; + 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..a584e51 --- /dev/null +++ b/math/mBox.h @@ -0,0 +1,222 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MBOX_H_ +#define _MBOX_H_ + +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif + +//------------------------------------------------------------------------------ +class Box3F +{ + public: + Point3F min; + Point3F max; + + public: + Box3F() { } + Box3F(const Point3F& in_rMin, const Point3F& in_rMax, const bool in_overrideCheck = false); + Box3F(F32 xmin, F32 ymin, F32 zmin, F32 max, F32 ymax, F32 zmax); + + bool isContained(const Point3F& in_rContained) const; + bool isOverlapped(const Box3F& in_rOverlap) const; + bool isContained(const Box3F& in_rContained) const; + + F32 len_x() const; + F32 len_y() const; + F32 len_z() const; + + void intersect(const Box3F& in_rIntersect); + void getCenter(Point3F* center) const; + + bool collideLine(const Point3F& start, const Point3F& end, F32*, Point3F*) const; + bool collideLine(const Point3F& start, const Point3F& end) const; + bool collideOrientedBox(const Point3F & radii, const MatrixF & toUs) const; + + bool isValidBox() const { return (min.x <= max.x) && + (min.y <= max.y) && + (min.z <= max.z); } + Point3F getClosestPoint(const Point3F& refPt) const; +}; + +inline Box3F::Box3F(const Point3F& in_rMin, const Point3F& in_rMax, const bool in_overrideCheck) + : min(in_rMin), + max(in_rMax) +{ + if (in_overrideCheck == false) { + min.setMin(in_rMax); + max.setMax(in_rMin); + } +} + +inline Box3F::Box3F(F32 xMin, F32 yMin, F32 zMin, F32 xMax, F32 yMax, F32 zMax) + : min(xMin,yMin,zMin), + max(xMax,yMax,zMax) +{ +} + +inline bool Box3F::isContained(const Point3F& in_rContained) const +{ + return (in_rContained.x >= min.x && in_rContained.x < max.x) && + (in_rContained.y >= min.y && in_rContained.y < max.y) && + (in_rContained.z >= min.z && in_rContained.z < max.z); +} + +inline bool Box3F::isOverlapped(const Box3F& in_rOverlap) const +{ + if (in_rOverlap.min.x > max.x || + in_rOverlap.min.y > max.y || + in_rOverlap.min.z > max.z) + return false; + if (in_rOverlap.max.x < min.x || + in_rOverlap.max.y < min.y || + in_rOverlap.max.z < min.z) + return false; + return true; +} + +inline bool Box3F::isContained(const Box3F& in_rContained) const +{ + return (min.x <= in_rContained.min.x) && + (min.y <= in_rContained.min.y) && + (min.z <= in_rContained.min.z) && + (max.x >= in_rContained.max.x) && + (max.y >= in_rContained.max.y) && + (max.z >= in_rContained.max.z); +} + +inline F32 Box3F::len_x() const +{ + return max.x - min.x; +} + +inline F32 Box3F::len_y() const +{ + return max.y - min.y; +} + +inline F32 Box3F::len_z() const +{ + return max.z - min.z; +} + +inline void Box3F::intersect(const Box3F& in_rIntersect) +{ + min.setMin(in_rIntersect.min); + max.setMax(in_rIntersect.max); +} + +inline void Box3F::getCenter(Point3F* center) const +{ + center->x = F32((min.x + max.x) * 0.5); + center->y = F32((min.y + max.y) * 0.5); + center->z = F32((min.z + max.z) * 0.5); +} + +inline Point3F Box3F::getClosestPoint(const Point3F& refPt) const +{ + Point3F closest; + if (refPt.x <= min.x) closest.x = min.x; + else if (refPt.x > max.x) closest.x = max.x; + else closest.x = refPt.x; + + if (refPt.y <= min.y) closest.y = min.y; + else if (refPt.y > max.y) closest.y = max.y; + else closest.y = refPt.y; + + if (refPt.z <= min.z) closest.z = min.z; + else if (refPt.z > max.z) closest.z = max.z; + else closest.z = refPt.z; + + return closest; +} + + +//------------------------------------------------------------------------------ +class Box3D +{ + public: + Point3D min; + Point3D max; + + 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; +}; + +inline Box3D::Box3D(const Point3D& in_rMin, const Point3D& in_rMax, const bool in_overrideCheck) + : min(in_rMin), + max(in_rMax) +{ + if (in_overrideCheck == false) { + min.setMin(in_rMax); + max.setMax(in_rMin); + } +} + +inline bool Box3D::isContained(const Point3D& in_rContained) const +{ + return (in_rContained.x >= min.x && in_rContained.x < max.x) && + (in_rContained.y >= min.y && in_rContained.y < max.y) && + (in_rContained.z >= min.z && in_rContained.z < max.z); +} + +inline bool Box3D::isOverlapped(const Box3D& in_rOverlap) const +{ + if (in_rOverlap.min.x > max.x || + in_rOverlap.min.y > max.y || + in_rOverlap.min.z > max.z) + return false; + if (in_rOverlap.max.x < min.x || + in_rOverlap.max.y < min.y || + in_rOverlap.max.z < min.z) + return false; + return true; +} + +inline F64 Box3D::len_x() const +{ + return max.x - min.x; +} + +inline F64 Box3D::len_y() const +{ + return max.y - min.y; +} + +inline F64 Box3D::len_z() const +{ + return max.z - min.z; +} + +inline void Box3D::intersect(const Box3D& in_rIntersect) +{ + min.setMin(in_rIntersect.min); + max.setMax(in_rIntersect.max); +} + +inline void Box3D::getCenter(Point3D* center) const +{ + center->x = (min.x + max.x) * 0.5; + center->y = (min.y + max.y) * 0.5; + center->z = (min.z + max.z) * 0.5; +} + + +#endif // _DBOX_H_ diff --git a/math/mConsoleFunctions.cc b/math/mConsoleFunctions.cc new file mode 100644 index 0000000..00359cb --- /dev/null +++ b/math/mConsoleFunctions.cc @@ -0,0 +1,150 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/console.h" +#include "Math/mMathFn.h" +#include "Math/mRandom.h" + +static const char *cSolveQuadratic(SimObject *, S32, const char **argv) +{ + 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; +} + +static const char *cSolveCubic(SimObject *, S32, const char **argv) +{ + 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; +} + +static const char *cSolveQuartic(SimObject *, S32, const char **argv) +{ + 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; +} + +static S32 cFloor(SimObject *, S32, const char **argv) +{ + return (S32)mFloor(dAtof(argv[1])); +} + +static S32 cCeil(SimObject *, S32, const char **argv) +{ + return (S32)mCeil(dAtof(argv[1])); +} + +static const char * cFloatLength(SimObject *, S32, const char **argv) +{ + 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; +} + +//------------------------------------------------------------------------------ + +static F32 cAbs(SimObject *, S32, const char ** argv) +{ + return(mFabs(dAtof(argv[1]))); +} + +static F32 cSqrt(SimObject *, S32, const char ** argv) +{ + return(mSqrt(dAtof(argv[1]))); +} + +static F32 cPow(SimObject *, S32, const char ** argv) +{ + return(mPow(dAtof(argv[1]), dAtof(argv[2]))); +} + +static F32 cLog(SimObject *, S32, const char ** argv) +{ + return(mLog(dAtof(argv[1]))); +} + +static F32 cSin(SimObject *, S32, const char ** argv) +{ + return(mSin(dAtof(argv[1]))); +} + +static F32 cCos(SimObject *, S32, const char ** argv) +{ + return(mCos(dAtof(argv[1]))); +} + +static F32 cTan(SimObject *, S32, const char ** argv) +{ + return(mTan(dAtof(argv[1]))); +} + +static F32 cAsin(SimObject *, S32, const char ** argv) +{ + return(mAsin(dAtof(argv[1]))); +} + +static F32 cAcos(SimObject *, S32, const char ** argv) +{ + return(mAcos(dAtof(argv[1]))); +} + +static F32 cAtan(SimObject *, S32, const char ** argv) +{ + return(mAtan(dAtof(argv[1]), dAtof(argv[2]))); +} + +static F32 cRadToDeg(SimObject *, S32, const char ** argv) +{ + return(mRadToDeg(dAtof(argv[1]))); +} + +static F32 cDegToRad(SimObject *, S32, const char ** argv) +{ + return(mDegToRad(dAtof(argv[1]))); +} + +//------------------------------------------------------------------------------ + +void MathConsoleInit() +{ + Con::addCommand("mSolveQuadratic", cSolveQuadratic, "mSolveQuadratic(a,b,c)", 4, 4); + Con::addCommand("mSolveCubic", cSolveCubic, "mSolveCubic(a,b,c,d)", 5, 5); + Con::addCommand("mSolveQuartic", cSolveQuartic, "mSolveQuartic(a,b,c,d,e)", 6, 6); + + Con::addCommand("mFloor", cFloor, "mFloor(float)", 2, 2); + Con::addCommand("mCeil", cCeil, "mCeil(float)", 2, 2); + Con::addCommand("mFloatLength", cFloatLength, "mFloatLength(float, numDecimals)", 3, 3); + + // + Con::addCommand("mAbs", cAbs, "mAbs(float)", 2, 2); + Con::addCommand("mSqrt", cSqrt, "mSqrt(float)", 2, 2); + Con::addCommand("mPow", cPow, "mPow(float, float)", 3, 3); + Con::addCommand("mLog", cLog, "mLog(float)", 2, 2); + Con::addCommand("mSin", cSin, "mSin(float)", 2, 2); + Con::addCommand("mCos", cCos, "mCos(float)", 2, 2); + Con::addCommand("mTan", cTan, "mTan(float)", 2, 2); + Con::addCommand("mAsin", cAsin, "mAsin(float)", 2, 2); + Con::addCommand("mAcos", cAcos, "mAcos(float)", 2, 2); + Con::addCommand("mAtan", cAtan, "mAtan(float, float)", 3, 3); + Con::addCommand("mRadToDeg", cRadToDeg, "mRadToDeg(float)", 2, 2); + Con::addCommand("mDegToRad", cDegToRad, "mDegToRad(float)", 2, 2); +} diff --git a/math/mConstants.h b/math/mConstants.h new file mode 100644 index 0000000..256ade1 --- /dev/null +++ b/math/mConstants.h @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MCONSTANTS_H_ +#define _MCONSTANTS_H_ + +#ifdef __GNUC__ +#include +#else +#define M_PI 3.14159265358979323846 +#define M_SQRT2 1.41421356237309504880 +#endif +#define M_2PI (3.1415926535897932384626433 * 2.0) +#define M_SQRTHALF 0.7071067811865475244008443 + +#endif diff --git a/math/mMath.h b/math/mMath.h new file mode 100644 index 0000000..eca2fb6 --- /dev/null +++ b/math/mMath.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MMATH_H_ +#define _MMATH_H_ + + +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MRECT_H_ +#include "Math/mRect.h" +#endif +#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 +#ifndef _MSPHERE_H_ +#include "Math/mSphere.h" +#endif +#ifndef _MCONSTANTS_H_ +#include "Math/mConstants.h" +#endif +#ifndef _MRANDOM_H_ +#include "Math/mRandom.h" +#endif + + +#endif //_MMATH_H_ diff --git a/math/mMathAMD.cc b/math/mMathAMD.cc new file mode 100644 index 0000000..19840da --- /dev/null +++ b/math/mMathAMD.cc @@ -0,0 +1,225 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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); + + +void Athlon_MatrixF_x_Point3F(const F32 *m, const F32 *p, F32 *presult) +{ + m; + p; + presult; +} + +// +// Here's the C code - note that the code below does it in a different order +// +// 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]; + +void Athlon_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result) +{ + __asm { + + femms + mov ecx,matA + mov eax,result + mov edx,matB + + 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 + } +} + + +#if 0 +void Athlon_MatrixF_x_VectorF(const F32 *matrix, const F32 *vector, F32 *result) +{ + __asm { + femms + mov eax,result + mov ecx,vector + mov edx,matrix + + // Here's what we're doing: + // result[0] = M[0] * v[0] + M[1] * v[1] + M[2] * v[2]; + // result[1] = M[4] * v[0] + M[5] * v[1] + M[6] * v[2]; + // result[2] = M[8] * v[0] + M[9] * v[1] + M[10]* v[2]; + + movq mm0,[ecx] // y | x + movd mm1,[ecx+8] // 0 | z + movd mm4,[edx+8] // 0 | m_13 + movq mm3,mm0 // y | x + movd mm2,[edx+40] // 0 | m_33 (M[10]) + punpckldq mm0,mm0 // x | x + punpckldq mm4,[edx+20] // m_31 | m_23 + pfmul mm0,[edx] // x * m_12 | x * m_11 + punpckhdq mm3,mm3 // y | y + pfmul mm2,mm1 // 0 | z * m_33 + punpckldq mm1,mm1 // z | z + pfmul mm4,[ecx] // y * m_31 | x * m_23 + pfmul mm3,[edx+12] // y * m_22 | y * m_21 + pfmul mm1,[edx+24] // z * m_32 | z * m_32 + pfacc mm4,mm4 // ? | y * m_31 + x * m_23 + pfadd mm3,mm0 // x * m_12 + y * m_22 | x * m_11 + y * m_21 + pfadd mm4,mm2 // ? | y * m_31 + x * m_23 + z * m_33 + pfadd mm3,mm1 // x * m_12 + y * m_22 + z * m_32 | x * m_11 + y * m_21 + z * m_32 + movd [eax+8],mm4 // r_z + movq [eax],mm3 // r_y | r_x + femms + } +} +#endif + + +void mInstall_AMD_Math() +{ + m_matF_x_matF = Athlon_MatrixF_x_MatrixF; + // 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..91ec79a --- /dev/null +++ b/math/mMathAMD_ASM.asm @@ -0,0 +1,163 @@ +;----------------------------------------------------------------------------- +; V12 Engine +; +; Copyright (c) 2001 GarageGames.Com +; Portions Copyright (c) 2001 by Sierra Online, Inc. +;----------------------------------------------------------------------------- + + + +segment .data + +matA dd 0 +result dd 0 +matB dd 0 + +segment .text + +%macro export_fn 1 + %ifdef LINUX + ; 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/mMathFn.cc b/math/mMathFn.cc new file mode 100644 index 0000000..e078637 --- /dev/null +++ b/math/mMathFn.cc @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Math/mMathFn.h" +#include "Math/mPlane.h" +#include "Math/mMatrix.h" + + +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); +} + + diff --git a/math/mMathFn.h b/math/mMathFn.h new file mode 100644 index 0000000..2bf9bca --- /dev/null +++ b/math/mMathFn.h @@ -0,0 +1,445 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MMATHFN_H_ +#define _MMATHFN_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MCONSTANTS_H_ +#include "Math/mConstants.h" +#endif +#include + +// Remove a couple of annoying macros, if they are present (In VC 6, they are.) +#ifdef min +#undef min +#endif +#ifdef max +#undef max +#endif + +class MatrixF; +class PlaneF; + +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_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_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); + +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 + + +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!"); +#if defined(__linux) && defined(__i386__) + // inline assembly version because gcc's math optimization isn't as good + int u0, u1, u2; + __asm__ __volatile__ (" + flds 0x8(%%eax) + fmuls 0x8(%%ecx) + flds 0x4(%%eax) + fmuls 0x4(%%ecx) + faddp %%st,%%st(1) + flds (%%eax) + fmuls (%%ecx) + faddp %%st,%%st(1) + fadds 0xc(%%eax) + fstps (%%edx) + flds 0x18(%%eax) + fmuls 0x8(%%ecx) + flds 0x10(%%eax) + fmuls (%%ecx) + faddp %%st,%%st(1) + flds 0x14(%%eax) + fmuls 0x4(%%ecx) + faddp %%st,%%st(1) + fadds 0x1c(%%eax) + fstps 0x4(%%edx) + flds 0x28(%%eax) + fmuls 0x8(%%ecx) + flds 0x20(%%eax) + fmuls (%%ecx) + faddp %%st,%%st(1) + flds 0x24(%%eax) + fmuls 0x4(%%ecx) + faddp %%st,%%st(1) + fadds 0x2c(%%eax) + fstps 0x8(%%edx) + " : "=&a" (u0), "=&c" (u1), "=&d" (u2) + : "0" (m), "1" (p), "2" (presult) + : "memory" ); +#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!"); +#if defined(__linux) && defined(__i386__) + // inline assembly version because gcc's math optimization isn't as good + int u0, u1, u2; + __asm__ __volatile__ (" + flds 0x8(%%ecx) + fmuls 0x8(%%eax) + flds 0x4(%%ecx) + fmuls 0x4(%%eax) + faddp %%st,%%st(1) + flds (%%ecx) + fmuls (%%eax) + faddp %%st,%%st(1) + fstps (%%edx) + flds 0x18(%%ecx) + fmuls 0x8(%%eax) + flds 0x10(%%ecx) + fmuls (%%eax) + faddp %%st,%%st(1) + flds 0x14(%%ecx) + fmuls 0x4(%%eax) + faddp %%st,%%st(1) + fstps 0x4(%%edx) + flds 0x28(%%ecx) + fmuls 0x8(%%eax) + flds 0x20(%%ecx) + fmuls (%%eax) + faddp %%st,%%st(1) + flds 0x24(%%ecx) + fmuls 0x4(%%eax) + faddp %%st,%%st(1) + fstps 0x8(%%edx) + " : "=&c" (u0), "=&a" (u1), "=&d" (u2) + : "0" (m), "1" (v), "2" (vresult) + : "memory" ); +#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 F32 mFloor(const F32 val) +{ + return floor(val); +} + +inline F32 mCeil(const F32 val) +{ + return ceil(val); +} + +inline F32 mFabs(const F32 val) +{ + return fabs(val); +} + +inline F32 mFmod(const F32 val, const F32 mod) +{ + return fmod(val, mod); +} + +inline S32 mAbs(const S32 val) +{ + // Kinda lame, and disallows intrinsic inlining by the compiler. Maybe fix? + // DMM + if (val < 0) + return -val; + + return val; +} + +inline S32 mClamp(S32 val, S32 low, S32 high) +{ + return getMax(getMin(val, high), low); +} + +inline F32 mClampF(F32 val, F32 low, F32 high) +{ + return getMax(getMin(val, high), low); +} + +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 sin(angle); +} + +inline F32 mCos(const F32 angle) +{ + return cos(angle); +} + +inline F32 mTan(const F32 angle) +{ + return tan(angle); +} + +inline F32 mAsin(const F32 val) +{ + return asin(val); +} + +inline F32 mAcos(const F32 val) +{ + return acos(val); +} + +inline F32 mAtan(const F32 x, const F32 y) +{ + return 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 tanh(angle); +} + +inline F32 mSqrt(const F32 val) +{ + return sqrt(val); +} + +inline F32 mPow(const F32 x, const F32 y) +{ + return pow(x, y); +} + +inline F32 mLog(const F32 val) +{ + return log(val); +} + + +inline F64 mSin(const F64 angle) +{ + return sin(angle); +} + +inline F64 mCos(const F64 angle) +{ + return cos(angle); +} + +inline F64 mTan(const F64 angle) +{ + return tan(angle); +} + +inline F64 mAsin(const F64 val) +{ + return asin(val); +} + +inline F64 mAcos(const F64 val) +{ + return acos(val); +} + +inline F64 mAtan(const F64 x, const F64 y) +{ + return 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 tanh(angle); +} + +inline F64 mPow(const F64 x, const F64 y) +{ + return pow(x, y); +} + +inline F64 mLog(const F64 val) +{ + return 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 fabs(val); +} + +inline F64 mFmodD(const F64 val, const F64 mod) +{ + return fmod(val, mod); +} + +inline F64 mSqrtD(const F64 val) +{ + return sqrt(val); +} + +inline F64 mFloorD(const F64 val) +{ + return floor(val); +} + +inline F64 mCeilD(const F64 val) +{ + return ceil(val); +} + + +//-------------------------------------- +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif + +inline F32 mDot(const Point3F &p1, const Point3F &p2) +{ + return (p1.x*p2.x + p1.y*p2.y + p1.z*p2.z); +} + +inline void mCross(const Point3F &a, const Point3F &b, Point3F *res) +{ + res->x = (a.y * b.z) - (a.z * b.y); + res->y = (a.z * b.x) - (a.x * b.z); + res->z = (a.x * b.y) - (a.y * b.x); +} + +inline F64 mDot(const Point3D &p1, const Point3D &p2) +{ + return (p1.x*p2.x + p1.y*p2.y + p1.z*p2.z); +} + +inline void mCross(const Point3D &a, const Point3D &b, Point3D *res) +{ + res->x = (a.y * b.z) - (a.z * b.y); + res->y = (a.z * b.x) - (a.x * b.z); + res->z = (a.x * b.y) - (a.y * b.x); +} + +inline void mCross(const F32* a, const F32* b, F32 *res) +{ + res[0] = (a[1] * b[2]) - (a[2] * b[1]); + res[1] = (a[2] * b[0]) - (a[0] * b[2]); + res[2] = (a[0] * b[1]) - (a[1] * b[0]); +} + +inline void mCross(const F64* a, const F64* b, F64* res) +{ + res[0] = (a[1] * b[2]) - (a[2] * b[1]); + res[1] = (a[2] * b[0]) - (a[0] * b[2]); + res[2] = (a[0] * b[1]) - (a[1] * b[0]); +} + +void mTransformPlane(const MatrixF& mat, const Point3F& scale, const PlaneF& plane, PlaneF* result); + + +//-------------------------------------- +inline F32 mDegToRad(F32 d) +{ + return F32((d * M_PI) / F32(180)); +} + +inline F32 mRadToDeg(F32 r) +{ + return F32((r * 180.0) / M_PI); +} + +inline F64 mDegToRad(F64 d) +{ + return (d * M_PI) / F64(180); +} + +inline F64 mRadToDeg(F64 r) +{ + return (r * 180.0) / M_PI; +} + +#endif //_MMATHFN_H_ diff --git a/math/mMathSSE.cc b/math/mMathSSE.cc new file mode 100644 index 0000000..a724df2 --- /dev/null +++ b/math/mMathSSE.cc @@ -0,0 +1,113 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMathFn.h" +#include "math/mPlane.h" +#include "math/mMatrix.h" + + +U32 gSSE_MatXMat_Calls = 0; +void SSE_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result) +{ + #ifdef INTERNAL_RELEASE + gSSE_MatXMat_Calls++; + #endif + + __asm { + mov edx, matA + mov eax, result + mov ecx, matB + + movss xmm0, dword ptr [edx] + movups xmm1, xmmword ptr [ecx] + shufps xmm0, xmm0, 0 + movss xmm2, dword ptr [edx+4] + mulps xmm0, xmm1 + shufps xmm2, xmm2, 0 + movups xmm3, xmmword ptr [ecx+10h] + movss xmm7, dword ptr [edx+8] + mulps xmm2, xmm3 + shufps xmm7, xmm7, 0 + addps xmm0, xmm2 + movups xmm4, xmmword ptr [ecx+20h] + movss xmm2, dword ptr [edx+0Ch] + mulps xmm7, xmm4 + shufps xmm2, xmm2, 0 + addps xmm0, xmm7 + movups xmm5, xmmword ptr [ecx+30h] + movss xmm6, dword ptr [edx+10h] + mulps xmm2, xmm5 + movss xmm7, dword ptr [edx+14h] + shufps xmm6, xmm6, 0 + addps xmm0, xmm2 + shufps xmm7, xmm7, 0 + movlps qword ptr [eax], xmm0 + movhps qword ptr [eax+8], xmm0 + mulps xmm7, xmm3 + movss xmm0, dword ptr [edx+18h] + mulps xmm6, xmm1 + shufps xmm0, xmm0, 0 + addps xmm6, xmm7 + mulps xmm0, xmm4 + movss xmm2, dword ptr [edx+24h] + addps xmm6, xmm0 + movss xmm0, dword ptr [edx+1Ch] + movss xmm7, dword ptr [edx+20h] + shufps xmm0, xmm0, 0 + shufps xmm7, xmm7, 0 + mulps xmm0, xmm5 + mulps xmm7, xmm1 + addps xmm6, xmm0 + shufps xmm2, xmm2, 0 + movlps qword ptr [eax+10h], xmm6 + movhps qword ptr [eax+18h], xmm6 + mulps xmm2, xmm3 + movss xmm6, dword ptr [edx+28h] + addps xmm7, xmm2 + shufps xmm6, xmm6, 0 + movss xmm2, dword ptr [edx+2Ch] + mulps xmm6, xmm4 + shufps xmm2, xmm2, 0 + addps xmm7, xmm6 + mulps xmm2, xmm5 + movss xmm0, dword ptr [edx+34h] + addps xmm7, xmm2 + shufps xmm0, xmm0, 0 + movlps qword ptr [eax+20h], xmm7 + movss xmm2, dword ptr [edx+30h] + movhps qword ptr [eax+28h], xmm7 + mulps xmm0, xmm3 + shufps xmm2, xmm2, 0 + movss xmm6, dword ptr [edx+38h] + mulps xmm2, xmm1 + shufps xmm6, xmm6, 0 + addps xmm2, xmm0 + mulps xmm6, xmm4 + movss xmm7, dword ptr [edx+3Ch] + shufps xmm7, xmm7, 0 + addps xmm2, xmm6 + mulps xmm7, xmm5 + addps xmm2, xmm7 + movups xmmword ptr [eax+30h], xmm2 + } +} + + +#if 0 +void SSE_MatrixF_x_VectorF(const F32 *matrix, const F32 *vector, F32 *result) +{ +} +#endif + + +void mInstall_Library_SSE() +{ + m_matF_x_matF = SSE_MatrixF_x_MatrixF; + // m_matF_x_point3F = Athlon_MatrixF_x_Point3F; + // m_matF_x_vectorF = Athlon_MatrixF_x_VectorF; +} + diff --git a/math/mMathSSE_ASM.asm b/math/mMathSSE_ASM.asm new file mode 100644 index 0000000..fb0918b --- /dev/null +++ b/math/mMathSSE_ASM.asm @@ -0,0 +1,114 @@ +;----------------------------------------------------------------------------- +; V12 Engine +; +; Copyright (c) 2001 GarageGames.Com +; Portions Copyright (c) 2001 by Sierra Online, Inc. +;----------------------------------------------------------------------------- + + + +segment .data + +matA dd 0 +result dd 0 +matB dd 0 + +segment .text + +%macro export_fn 1 + %ifdef LINUX + ; 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..95b2aa6 --- /dev/null +++ b/math/mMath_ASM.asm @@ -0,0 +1,267 @@ +; +; 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 + + +%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) +; +%ifdef __linux +global m_ceil_ASM +m_ceil_ASM: +%else +global _m_ceil_ASM +_m_ceil_ASM: +%endif + fld val + fadd rnd_adj + fistp qword [temp_int64] + fild qword [temp_int64] + ret + +; +; static F64 m_ceilD_ASM(F64 val64) +; +%ifdef __linux +global m_ceilD_ASM +m_ceilD_ASM: +%else +global _m_ceilD_ASM +_m_ceilD_ASM: +%endif + fld val64 + fadd rnd_adjD + fistp qword [temp_int64] + fild qword [temp_int64] + ret + +; +; static F32 m_floor_ASM(F32 val) +; +%ifdef __linux +global m_floor_ASM +m_floor_ASM: +%else +global _m_floor_ASM +_m_floor_ASM: +%endif + fld val + fsub rnd_adj + fistp qword [temp_int64] + fild qword [temp_int64] + ret + + +; +; static F32 m_floorD_ASM( F64 val64 ) +; +%ifdef __linux +global m_floorD_ASM +m_floorD_ASM: +%else +global _m_floorD_ASM +_m_floorD_ASM: +%endif + 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; +; +%ifdef __linux +global m_mulDivS32_ASM +m_mulDivS32_ASM: +%else +global _m_mulDivS32_ASM +_m_mulDivS32_ASM: +%endif + 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 +; +%ifdef __linux +global m_mulDivU32_ASM +m_mulDivU32_ASM: +%else +global _m_mulDivU32_ASM +_m_mulDivU32_ASM: +%endif + 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) +; +%ifdef __linux +global m_fmod_ASM +m_fmod_ASM: +%else +global _m_fmod_ASM +_m_fmod_ASM: +%endif + 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) +; +%ifdef __linux +global m_fmodD_ASM +m_fmodD_ASM: +%else +global _m_fmodD_ASM +_m_fmodD_ASM: +%endif + 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 ) +; +%ifdef __linux +global m_sincos_ASM +m_sincos_ASM: +%else +global _m_sincos_ASM +_m_sincos_ASM: +%endif + 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 ) +; +%ifdef __linux +global m_sincosD_ASM +m_sincosD_ASM: +%else +global _m_sincosD_ASM +_m_sincosD_ASM: +%endif + mov eax, res_cos64 + fld angle64 + fsincos + fstp qword [eax] + mov eax, res_sin64 + fstp qword [eax] + ret + + + diff --git a/math/mMath_C.cc b/math/mMath_C.cc new file mode 100644 index 0000000..57c99f9 --- /dev/null +++ b/math/mMath_C.cc @@ -0,0 +1,815 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#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_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.0) { + F32 factor = 1.0f / mSqrt(squared); + p[0] *= factor; + p[1] *= factor; + p[2] *= factor; + } else { + p[0] = 0; + p[1] = 0; + p[2] = 1; + } +} + +//-------------------------------------- +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 ) +{ + 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; +} + +//-------------------------------------- +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 ) +{ + 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; +} + + +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 ); + F32 r1 = cz; + F32 r2 = sz; + F32 r3 = 0.0f; + F32 r4 = 0.0f; + + 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; +} + + +//-------------------------------------- +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_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 relationsips to one another + mCross(col0, col1, col2); + mCross(col2, col0, col1); + + // assure their lengh 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.0 / s[0]; + pScaleElems[MatrixF::idx(1, 1)] = 1.0 / s[1]; + pScaleElems[MatrixF::idx(2, 2)] = 1.0 / s[2]; + + Point3F shear; + shear.x = m[MatrixF::idx(3, 0)]; + shear.y = m[MatrixF::idx(3, 1)]; + shear.z = m[MatrixF::idx(3, 2)]; + + Point3F row0(m[MatrixF::idx(0, 0)], m[MatrixF::idx(0, 1)], m[MatrixF::idx(0, 2)]); + Point3F row1(m[MatrixF::idx(1, 0)], m[MatrixF::idx(1, 1)], m[MatrixF::idx(1, 2)]); + Point3F row2(m[MatrixF::idx(2, 0)], m[MatrixF::idx(2, 1)], m[MatrixF::idx(2, 2)]); + + F32 A = -mDot(row0, shear); + F32 B = -mDot(row1, shear); + 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])); + } +} + + +//------------------------------------------------------------------------------ +// 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_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_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_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; + + +//------------------------------------------------------------------------------ +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_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_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; +} + diff --git a/math/mMatrix.cc b/math/mMatrix.cc new file mode 100644 index 0000000..93e819a --- /dev/null +++ b/math/mMatrix.cc @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Math/mMath.h" +#include "Math/mMatrix.h" + + +// 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; +} diff --git a/math/mMatrix.h b/math/mMatrix.h new file mode 100644 index 0000000..e0cd698 --- /dev/null +++ b/math/mMatrix.h @@ -0,0 +1,363 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MMATRIX_H_ +#define _MMATRIX_H_ + + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif + + +class MatrixF +{ +private: + F32 m[16]; // Note: this is stored in ROW MAJOR format. OpenGL is + // COLUMN MAJOR. Transpose before sending down. + +public: + explicit MatrixF(bool identity=false); // DEFAULT: UN-Initialized TRUE: Identity + explicit MatrixF( const EulerF &e); + MatrixF( const EulerF &e, const Point3F& p); + + static U32 idx(U32 i, U32 j) { return (i + j*4); } + + MatrixF& set( const EulerF &e); + MatrixF& set( const EulerF &e, const Point3F& p); + MatrixF& setCrossProduct( const Point3F &p); + MatrixF& setTensorProduct( const Point3F &p, const Point3F& q); + + operator F32*() { return (m); } + operator const F32*() const { return (m); } + + bool isAffine() const; + bool isIdentity() const; + + MatrixF& identity(); + MatrixF& inverse(); + MatrixF& affineInverse(); + MatrixF& transpose(); + MatrixF& scale(const Point3F& p); // M * Matrix(p) -> M + + bool fullInverse(); // computes inverse of full 4x4 matrix...returns false and performs no inverse if det 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. + + void transposeTo(F32 *matrix) const; + + void normalize(); + void getColumn(S32 col, Point4F *cptr) const; + void getColumn(S32 col, Point3F *cptr) const; + void setColumn(S32 col, const Point4F& cptr); + void setColumn(S32 col, const Point3F& cptr); + + void getRow(S32 col, Point4F *cptr) const; + void getRow(S32 col, Point3F *cptr) const; + void setRow(S32 col, const Point4F& cptr); + void setRow(S32 col, const Point3F& cptr); + + Point3F getPosition() const; + void setPosition( const Point3F &pos ){ setColumn( 3, pos ); } + + 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 + + +}; + +//-------------------------------------- +// 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; + 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; + m[15] = 1; + 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 void MatrixF::normalize() +{ + m_matF_normalize(m); +} + +inline MatrixF& MatrixF::mul( const MatrixF &a ) +{ // M * a -> M + 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 + 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.min.x, &b.max.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; +} + +// not too speedy, but convienient +inline Point3F MatrixF::getPosition() const +{ + Point3F pos; + getColumn( 3, &pos ); + return pos; +} + +#endif //_MMATRIX_H_ diff --git a/math/mPlane.h b/math/mPlane.h new file mode 100644 index 0000000..01e1f15 --- /dev/null +++ b/math/mPlane.h @@ -0,0 +1,292 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MPLANE_H_ +#define _MPLANE_H_ + +#ifndef _MMATHFN_H_ +#include "Math/mMathFn.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.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 ); + + // Methods + //using Point3F::set; + void set(const F32 _x, const F32 _y, const F32 _z); + + 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(); + + F32 distToPlane( const Point3F& cp ) const; + + enum Side { + Front = 1, + On = 0, + Back = -1 + }; + + Side whichSide(const Point3F& cp) const; + F32 intersect(const Point3F &start, const Point3F &end) const; + //DLLAPI bool split( const Poly3F& poly, Poly3F* front, Poly3F* back ); + + 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 ) +{ + x = _x; y = _y; z = _z; d = _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; z = 1; d = -zz; +} + +inline void PlaneF::setYZ( F32 xx ) +{ + x = 1; z = y = 0; d = -xx; +} + +inline void PlaneF::setXZ( F32 yy ) +{ + x = z = 0; y = 1; d = -yy; +} + +inline void PlaneF::setXY(const Point3F& point, F32 dir) // Normal = (0, 0, -1|1) +{ + x = y = 0; + d = -((z = dir) * point.z); +} + +inline void PlaneF::setYZ(const Point3F& point, F32 dir) // Normal = (-1|1, 0, 0) +{ + z = y = 0; + d = -((x = dir) * point.x); +} + +inline void PlaneF::setXZ(const Point3F& point, F32 dir) // Normal = (0, -1|1, 0) +{ + x = z = 0; + 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 && y == 0) ? true : false; +} + +inline bool PlaneF::isVertical() const +{ + return ((x != 0 || y != 0) && z == 0) ? true : false; +} + +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 void PlaneF::set(const F32 _x, const F32 _y, const F32 _z) +{ + Point3F::set(_x,_y,_z); +} + +//--------------------------------------------------------------------------- +// Calculate the coefficients of the plane passing through +// a point with the given normal. + +////inline void PlaneF::set( const Point3F& p, const Point3F& n ) +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; +// 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)... + F32 ax = k.x-j.x; + F32 ay = k.y-j.y; + F32 az = k.z-j.z; + F32 bx = l.x-j.x; + F32 by = l.y-j.y; + F32 bz = l.z-j.z; + x = ay*bz - az*by; + y = az*bx - ax*bz; + z = ax*by - ay*bx; + F32 squared = x*x + y*y + z*z; + AssertFatal(squared != 0.0, "Error, no plane possible!"); + + // In non-debug mode + if (squared != 0) { + F32 invSqrt = 1.0f / 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 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 +{ + F32 den = mDot(p2 - p1, *this); + if(den == 0) + return PARALLEL_PLANE; + return -distToPlane(p1) / den; +} + +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)); + + // 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; +} + +#endif diff --git a/math/mPlaneTransformer.cc b/math/mPlaneTransformer.cc new file mode 100644 index 0000000..4f5afff --- /dev/null +++ b/math/mPlaneTransformer.cc @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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, 1, 1); + transformer.set(defMat, defScale); + } + } sMakeIdentity; + + * this = sMakeIdentity.transformer; +} diff --git a/math/mPlaneTransformer.h b/math/mPlaneTransformer.h new file mode 100644 index 0000000..2ae840d --- /dev/null +++ b/math/mPlaneTransformer.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MPLANETRANSFORMER_H_ +#define _MPLANETRANSFORMER_H_ + +#ifndef _MMATRIX_H_ +#include "Math/mMatrix.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MPLANE_H_ +#include "Math/mPlane.h" +#endif + +// ========================================================= +class PlaneTransformer +{ + MatrixF mTransform; + Point3F mScale; + + MatrixF mTransposeInverse; + + public: + void set(const MatrixF& xform, const Point3F& scale); + void transform(const PlaneF& plane, PlaneF& result); + void setIdentity(); +}; + +#endif diff --git a/math/mPoint.h b/math/mPoint.h new file mode 100644 index 0000000..112b79b --- /dev/null +++ b/math/mPoint.h @@ -0,0 +1,1611 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MPOINT_H_ +#define _MPOINT_H_ + +#define POINT_EPSILON (1e-4) + +//-------------------------------------- Note: because of a circular dependancy, +// the mMathFn.h header is below the point +// class declarations... DMM +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +//------------------------------------------------------------------------------ +class Point2I +{ + //-------------------------------------- Public data + public: + S32 x; + S32 y; + + //-------------------------------------- Public interface + public: + Point2I(); + Point2I(const Point2I&); + Point2I(const S32 in_x, const S32 in_y); + + //-------------------------------------- Non-math mutators and misc functions + void set(const S32 in_x, const S32 in_y); + void setMin(const Point2I&); + void setMax(const Point2I&); + + //-------------------------------------- Math mutators + void neg(); + void convolve(const Point2I&); + + //-------------------------------------- Queries + bool isZero() const; + F32 len() const; + + //-------------------------------------- Overloaded operators + public: + // 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*(const S32) const; + Point2I& operator*=(const S32); + Point2I operator/(const S32) const; + Point2I& operator/=(const S32); + + // Unary operators + Point2I operator-() const; +}; + +//------------------------------------------------------------------------------ +class Point3I +{ + //-------------------------------------- Public data + public: + S32 x; + S32 y; + S32 z; + + //-------------------------------------- Public interface + public: + Point3I(); + Point3I(const Point3I&); + Point3I(const S32 in_x, const S32 in_y, const S32 in_z); + + //-------------------------------------- Non-math mutators and misc functions + void set(const S32 in_x, const S32 in_y, const S32 in_z); + void setMin(const Point3I&); + void setMax(const Point3I&); + + //-------------------------------------- Math mutators + void neg(); + void convolve(const Point3I&); + + //-------------------------------------- Queries + bool isZero() const; + F32 len() const; + + //-------------------------------------- Overloaded operators + public: + // 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*(const S32) const; + Point3I& operator*=(const S32); + Point3I operator/(const S32) const; + Point3I& operator/=(const S32); + + // Unary operators + Point3I operator-() const; +}; + + + +//------------------------------------------------------------------------------ +class Point2F +{ + //-------------------------------------- Public data + public: + F32 x; + F32 y; + + public: + Point2F(); + Point2F(const Point2F&); + Point2F(const F32 _x, const F32 _y); + + //-------------------------------------- Non-math mutators and misc functions + public: + void set(const F32 _x, const F32 _y); + + void setMin(const Point2F&); + void setMax(const Point2F&); + + void interpolate(const Point2F&, const Point2F&, const F32); + + operator F32*() { return (&x); } + operator const F32*() const { return (&x); } + + //-------------------------------------- Queries + public: + bool isZero() const; + F32 len() const; + F32 lenSquared() const; + + //-------------------------------------- Mathematical mutators + public: + void neg(); + void normalize(); + void normalize(F32 val); + void convolve(const Point2F&); + void convolveInverse(const Point2F&); + + //-------------------------------------- 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*(const F32) const; + Point2F operator/(const F32) const; + Point2F& operator*=(const F32); + Point2F& operator/=(const F32); + + // Unary operators + Point2F operator-() const; +}; + + +//------------------------------------------------------------------------------ +class Point2D +{ + //-------------------------------------- Public data + public: + F64 x; + F64 y; + + public: + Point2D(); + Point2D(const Point2D&); + Point2D(const F64 _x, const F64 _y); + + //-------------------------------------- Non-math mutators and misc functions + public: + void set(const F64 _x, const F64 _y); + + void setMin(const Point2D&); + void setMax(const Point2D&); + + void interpolate(const Point2D&, const Point2D&, const 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 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*(const F64) const; + Point2D operator/(const F64) const; + Point2D& operator*=(const F64); + Point2D& operator/=(const F64); + + // Unary operators + Point2D operator-() const; +}; + + +//------------------------------------------------------------------------------ +class Point3F +{ + //-------------------------------------- Public data + public: + F32 x; + F32 y; + F32 z; + + public: + Point3F(); + Point3F(const Point3F&); + Point3F(const F32 _x, const F32 _y, const F32 _z); + + //-------------------------------------- Non-math mutators and misc functions + public: + void set(const F32 _x, const F32 _y, const F32 _z); + void set(const Point3F&); + + void setMin(const Point3F&); + void setMax(const Point3F&); + + void interpolate(const Point3F&, const Point3F&, const F32); + void zero(); + + operator F32*() { return (&x); } + operator const F32*() const { return (&x); } + + //-------------------------------------- Queries + public: + bool isZero() const; + F32 len() const; + F32 lenSquared() const; + F32 magnitudeSafe(); + bool equal( Point3F &compare ); + + //-------------------------------------- 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*(const F32) const; + Point3F operator/(const F32) const; + Point3F& operator*=(const F32); + Point3F& operator/=(const F32); + + Point3F operator*(const Point3F&) const; + Point3F& operator*=(const Point3F&); + + // Unary operators + Point3F operator-() const; +}; + + +typedef Point3F VectorF; +typedef Point3F EulerF; + + +//------------------------------------------------------------------------------ +class Point3D +{ + //-------------------------------------- Public data + public: + F64 x; + F64 y; + F64 z; + + public: + Point3D(); + Point3D(const Point3D&); + Point3D(const F64 _x, const F64 _y, const F64 _z); + + //-------------------------------------- Non-math mutators and misc functions + public: + void set(const F64 _x, const F64 _y, const F64 _z); + + void setMin(const Point3D&); + void setMax(const Point3D&); + + void interpolate(const Point3D&, const Point3D&, const 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: + // 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*(const F64) const; + Point3D operator/(const F64) const; + Point3D& operator*=(const F64); + Point3D& operator/=(const F64); + + // Unary operators + Point3D operator-() const; +}; + + + +//------------------------------------------------------------------------------ +class Point4F +{ + //-------------------------------------- Public data + public: + F32 x; + F32 y; + F32 z; + F32 w; + + public: + Point4F(); + Point4F(const Point4F&); + Point4F(const F32 _x, const F32 _y, const F32 _z, const F32 _w); + + void set(const F32 _x, const F32 _y, const F32 _z, const F32 _w); + void interpolate(const Point4F& _pt1, const Point4F& _pt2, const F32 _factor); + + operator F32*() { return (&x); } + operator const F32*() const { return (&x); } +}; + + +typedef Point4F Vector4F; + + +#ifndef _MMATHFN_H_ +#include "Math/mMathFn.h" +#endif + +//------------------------------------------------------------------------------ +//-------------------------------------- Inline functions inclusions + + +//------------------------------------------------------------------------------ +//-------------------------------------- Point2I +// +inline Point2I::Point2I() +{ + // +} + + +inline Point2I::Point2I(const Point2I& _copy) + : x(_copy.x), y(_copy.y) +{ + // +} + + +inline Point2I::Point2I(const S32 _x, const S32 _y) + : x(_x), y(_y) +{ + // +} + + +inline void Point2I::set(const S32 _x, const 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 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*(const S32 mul) const +{ + return Point2I(x * mul, y * mul); +} + +inline Point2I Point2I::operator/(const S32 div) const +{ + AssertFatal(div != 0, "Error, div by zero attempted"); + return Point2I(x/div, y/div); +} + + +inline Point2I& Point2I::operator*=(const S32 mul) +{ + x *= mul; + y *= mul; + + return *this; +} + + +inline Point2I& Point2I::operator/=(const S32 div) +{ + AssertFatal(div != 0, "Error, div by zero attempted"); + + x /= div; + y /= div; + + return *this; +} + + + + +//------------------------------------------------------------------------------ +//-------------------------------------- Point3I +// +inline Point3I::Point3I() +{ + // +} + + +inline Point3I::Point3I(const Point3I& _copy) + : x(_copy.x), y(_copy.y), z(_copy.z) +{ + // +} + + +inline Point3I::Point3I(const S32 _x, const S32 _y, const S32 _z) + : x(_x), y(_y), z(_z) +{ + // +} + + +inline void Point3I::set(const S32 _x, const S32 _y, const 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*(const S32 mul) const +{ + return Point3I(x * mul, y * mul, z * mul); +} + + +inline Point3I Point3I::operator/(const S32 div) const +{ + AssertFatal(div != 0, "Error, div by zero attempted"); + return Point3I(x/div, y/div, z/div); +} + + +inline Point3I& Point3I::operator*=(const S32 mul) +{ + x *= mul; + y *= mul; + z *= mul; + + return *this; +} + + +inline Point3I& Point3I::operator/=(const S32 div) +{ + AssertFatal(div != 0, "Error, div by zero attempted"); + + x /= div; + y /= div; + z /= div; + + return *this; +} + + + + + + +//------------------------------------------------------------------------------ +//-------------------------------------- Point2F +// +inline Point2F::Point2F() +{ + // +} + + +inline Point2F::Point2F(const Point2F& _copy) + : x(_copy.x), y(_copy.y) +{ + // +} + + +inline Point2F::Point2F(const F32 _x, const F32 _y) + : x(_x), y(_y) +{ +} + + +inline void Point2F::set(const F32 _x, const 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 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 bool Point2F::operator==(const Point2F& _test) const +{ + return (x == _test.x) && (y == _test.x); +} + + +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*(const F32 _mul) const +{ + return Point2F(x * _mul, y * _mul); +} + + +inline Point2F Point2F::operator/(const 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*=(const F32 _mul) +{ + x *= _mul; + y *= _mul; + + return *this; +} + + +inline Point2F& Point2F::operator/=(const 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 +{ + 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); +} + + +//------------------------------------------------------------------------------ +//-------------------------------------- Point2D +// +inline Point2D::Point2D() +{ + // +} + + +inline Point2D::Point2D(const Point2D& _copy) + : x(_copy.x), y(_copy.y) +{ + // +} + + +inline Point2D::Point2D(const F64 _x, const F64 _y) + : x(_x), y(_y) +{ +} + + +inline void Point2D::set(const F64 _x, const 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.x); +} + + +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*(const F64 _mul) const +{ + return Point2D(x * _mul, y * _mul); +} + + +inline Point2D Point2D::operator/(const 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*=(const F64 _mul) +{ + x *= _mul; + y *= _mul; + + return *this; +} + + +inline Point2D& Point2D::operator/=(const 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); +} + + +//------------------------------------------------------------------------------ +//-------------------------------------- Point3F +// +inline Point3F::Point3F() +#ifdef __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(const F32 _x, const F32 _y, const F32 _z) + : x(_x), y(_y), z(_z) +{ + // +} + + +inline void Point3F::set(const F32 _x, const F32 _y, const 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, const 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::equal( Point3F &compare ) +{ + return( ( mFabs( x - compare.x ) < POINT_EPSILON ) && + ( mFabs( y - compare.y ) < POINT_EPSILON ) && + ( mFabs( z - compare.z ) < POINT_EPSILON ) ); +} + +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() +{ + 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*(const F32 _mul) const +{ + return Point3F(x * _mul, y * _mul, z * _mul); +} + +inline Point3F Point3F::operator*(const Point3F &_vec) const +{ + return Point3F(x * _vec.x, y * _vec.y, z * _vec.z); +} + +inline Point3F Point3F::operator/(const 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*=(const F32 _mul) +{ + x *= _mul; + y *= _mul; + z *= _mul; + + return *this; +} + + +inline Point3F& Point3F::operator*=(const Point3F &_vec) +{ + x *= _vec.x; + y *= _vec.y; + z *= _vec.z; + return *this; +} + + +inline Point3F& Point3F::operator/=(const 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 +{ + return Point3F(-x, -y, -z); +} + + +//------------------------------------------------------------------------------ +//-------------------------------------- Point3D +// +inline Point3D::Point3D() +{ + // +} + + +inline Point3D::Point3D(const Point3D& _copy) + : x(_copy.x), y(_copy.y), z(_copy.z) +{ + // +} + + +inline Point3D::Point3D(const F64 _x, const F64 _y, const F64 _z) + : x(_x), y(_y), z(_z) +{ + // +} + + +inline void Point3D::set(const F64 _x, const F64 _y, const 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, const 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*(const F64 _mul) const +{ + return Point3D(x * _mul, y * _mul, z * _mul); +} + + +inline Point3D Point3D::operator/(const 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*=(const F64 _mul) +{ + x *= _mul; + y *= _mul; + z *= _mul; + + return *this; +} + + +inline Point3D& Point3D::operator/=(const 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); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- 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(const F32 _x, const F32 _y, const F32 _z, const F32 _w) + : x(_x), y(_y), z(_z), w(_w) +{ + // +} + + +inline void Point4F::set(const F32 _x, const F32 _y, const F32 _z, const F32 _w) +{ + x = _x; + y = _y; + z = _z; + w = _w; +} + + +inline void Point4F::interpolate(const Point4F& _from, const Point4F& _to, const 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); +} + +//-------------------------------------------------------------------------- +//-------------------------------------- NON-MEMBER Operators +// +inline Point2I operator*(const S32 mul, const Point2I& multiplicand) +{ + return multiplicand * mul; +} + +inline Point3I operator*(const S32 mul, const Point3I& multiplicand) +{ + return multiplicand * mul; +} + +inline Point2F operator*(const F32 mul, const Point2F& multiplicand) +{ + return multiplicand * mul; +} + +inline Point3F operator*(const F32 mul, const Point3F& multiplicand) +{ + return multiplicand * mul; +} + +inline Point2D operator*(const F64 mul, const Point2D& multiplicand) +{ + return multiplicand * mul; +} + +inline Point3D operator*(const F64 mul, const Point3D& multiplicand) +{ + return multiplicand * mul; +} + +#endif // _POINT_H_ diff --git a/math/mQuadPatch.cc b/math/mQuadPatch.cc new file mode 100644 index 0000000..c3fc50b --- /dev/null +++ b/math/mQuadPatch.cc @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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..6bf922d --- /dev/null +++ b/math/mQuadPatch.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MQUADPATCH_H_ +#define _MQUADPATCH_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MSPLINEPATCH_H_ +#include "Math/mSplinePatch.h" +#endif + +//------------------------------------------------------------------------------ +// Quadratic spline patch +//------------------------------------------------------------------------------ +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.cc b/math/mQuat.cc new file mode 100644 index 0000000..ff4dad3 --- /dev/null +++ b/math/mQuat.cc @@ -0,0 +1,347 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Math/mQuat.h" +#include "Math/mMatrix.h" + +QuatF& QuatF::set( const EulerF & e ) +{ + F32 cx, sx; + F32 cy, sy; + F32 cz, sz; + mSinCos( -e.x * F32(0.5), sx, cx ); + mSinCos( -e.y * F32(0.5), sy, cy ); + mSinCos( -e.z * F32(0.5), 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; +} + +AngAxisF & AngAxisF::set( const QuatF & q ) +{ + angle = mAcos( q.w ) * 2; + F32 sinHalfAngle = mSqrt(1 - q.w * q.w); + if (sinHalfAngle != 0) + axis.set( q.x / sinHalfAngle, q.y / sinHalfAngle, q.z / sinHalfAngle ); + else + axis.set(1,0,0); + 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 ); +} + +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 ) +{ + F32 sinHalfAngle, cosHalfAngle; + mSinCos( a.angle * F32(0.5), sinHalfAngle, cosHalfAngle ); + x = a.axis.x * sinHalfAngle; + y = a.axis.y * sinHalfAngle; + z = a.axis.z * sinHalfAngle; + w = cosHalfAngle; + return *this; +} + +QuatF & QuatF::normalize() +{ + F32 l = mSqrt( x*x + y*y + z*z + w*w ); + if( l == F32(0.0) ) + 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.0) { + F32 s = mSqrt(trace + F32(1)); + w = s * 0.5; + s = 0.5 / 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.0); + q[i] = s * 0.5; + s = 0.5 / 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; + } + + 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) { + cos = -cos; + flip = -1; + } + else + flip = 1; + + F64 s1,s2; + if ((1.0 - cos) > 0.00001) { + F64 om = mAcos(cos) / t; + F64 sd = 1 / mSin(t * om); + s1 = flip * mSin(om) * sd; + s2 = mSin((1 - t) * om) * sd; + } + else { + // If quats are very close, do linear interpolation + s1 = flip / t; + s2 = (1 - 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); + + 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; +} + +Point3F& TQuatF::mulP(const Point3F& p, Point3F* r) +{ + QuatF a; + QuatF i = *this; + QuatF v( p.x, p.y, p.z, 0.0f); + i.inverse(); + a.mul(i, v); + v.mul(a, *this); + v.normalize(); + r->set(v.x, v.y, v.z); + + *r += p; + return ( *r ); +} + diff --git a/math/mQuat.h b/math/mQuat.h new file mode 100644 index 0000000..36c74e6 --- /dev/null +++ b/math/mQuat.h @@ -0,0 +1,331 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MQUAT_H_ +#define _MQUAT_H_ + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif + +class MatrixF; +class QuatF; + +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; +} + +//---------------------------------------------------------------------------- +// 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 ); + + int operator ==( const AngAxisF & c ) const; + int operator !=( const AngAxisF & c ) const; + + MatrixF * setMatrix( MatrixF * mat ) const; +}; + +//---------------------------------------------------------------------------- +// unit quaternion class: + +class QuatF +{ + public: + F32 x,y,z,w; + + QuatF(); + QuatF( F32 _x, F32 _y, F32 _z, F32 w ); + 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 MatrixF & m ); + QuatF& set( const AngAxisF & m ); + 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 +}; + + +//---------------------------------------------------------------------------- +// 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 int AngAxisF::operator ==( const AngAxisF & c ) const +{ + return QuatIsEqual(angle, c.angle) && (axis == c.axis); +} + +inline int AngAxisF::operator !=( const AngAxisF & c ) const +{ + return !QuatIsEqual(angle, c.angle) || (axis != c.axis); +} + +//---------------------------------------------------------------------------- +// quaternion implementation: + +inline QuatF::QuatF() +{ +} + +inline QuatF::QuatF( F32 _x, F32 _y, F32 _z, F32 _w ) +{ + set( _x, _y, _z, _w ); +} + +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); +} + + +//---------------------------------------------------------------------------- +// TQuatF classes: + +class TQuatF : public QuatF +{ + public: + enum + { + Matrix_HasRotation = 1, + Matrix_HasTranslation = 2, + Matrix_HasScale = 4 + }; + + Point3F p; + U32 flags; + + TQuatF(); + TQuatF( bool ident ); + TQuatF( const EulerF & e, const Point3F & p ); + TQuatF( const AngAxisF & aa, const Point3F & p ); + TQuatF( const QuatF & q, const Point3F & p ); + + TQuatF& set( const EulerF & euler, const Point3F & p ); + TQuatF& set( const AngAxisF & aa, const Point3F & p ); + TQuatF& set( const QuatF & quat, const Point3F & p ); + + TQuatF& inverse( void ); + TQuatF& identity( void ); + + Point3F& mulP(const Point3F& p, Point3F* r); // r = p * this +}; + +//--------------------------------------------------------------------------- + +inline TQuatF::TQuatF() +{ + // Beware: no initialization is done! +} + +inline TQuatF::TQuatF( bool ident ) +{ + if( ident ) + identity(); +} + +inline TQuatF::TQuatF( const EulerF & euler, const Point3F & p ) +{ + set( euler, p ); +} + +inline TQuatF::TQuatF( const AngAxisF & aa, const Point3F & p ) +{ + set( aa, p ); +} + +inline TQuatF::TQuatF( const QuatF & quat, const Point3F & p ) +{ + set( quat, p ); +} + +inline TQuatF& TQuatF::set( const EulerF & e, const Point3F & t ) +{ + p = t; + QuatF::set( e ); + flags |= Matrix_HasTranslation; + return *this; +} + +inline TQuatF& TQuatF::set( const AngAxisF & aa, const Point3F & t ) +{ + p = t; + QuatF::set( aa ); + flags |= Matrix_HasTranslation; + return *this; +} + +inline TQuatF& TQuatF::set( const QuatF & q, const Point3F & t ) +{ + p = t; + QuatF::set( q.x, q.y, q.z, q.w ); + flags |= Matrix_HasTranslation; + return *this; +} + +inline TQuatF& TQuatF::inverse( void ) +{ + QuatF::inverse(); + if( flags & Matrix_HasTranslation ) + { + Point3F p2 = p; + p2.neg(); + QuatF::mulP(p2,&p); + } + return *this; +} + +inline TQuatF& TQuatF::identity( void ) +{ + QuatF::identity(); + p.set(0,0,0); + flags &= ~U32(Matrix_HasTranslation); + return *this; +} + +#endif diff --git a/math/mRandom.cc b/math/mRandom.cc new file mode 100644 index 0000000..6ad8a92 --- /dev/null +++ b/math/mRandom.cc @@ -0,0 +1,158 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "Platform/event.h" +#include "Platform/gameInterface.h" +#include "Math/mRandom.h" + +MRandomLCG gRandGen; +U32 gRandGenSeed = 1376312589; + +void MRandomLCG::setGlobalRandSeed(U32 seed) +{ + U32 journalMode = Game->getJournalMode(); + if (journalMode == GameInterface::JournalLoad) + Game->journalRead(&gRandGenSeed); + else + { + gRandGenSeed = seed; + if (journalMode == GameInterface::JournalSave) + Game->journalWrite(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..ee1c6d0 --- /dev/null +++ b/math/mRandom.h @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MRANDOM_H_ +#define _MRANDOM_H_ + + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + + +//-------------------------------------- +// Base class for random number generators +class MRandomGenerator +{ +protected: + MRandomGenerator() {} + S32 mSeed; + +public: + void setSeed(); + S32 getSeed() { return mSeed; } + virtual void setSeed(S32 s) = 0; + + virtual U32 randI( void ) = 0; // 0..2^31 + virtual F32 randF( void ); // 0.0 .. 1.0 F32 generator + S32 randI(S32 i, S32 n); // i..n integer generator + F32 randF(F32 i, F32 n); // i..n F32 generator +}; + + +//-------------------------------------- +inline F32 MRandomGenerator::randF() +{ + // default: multiply by 1/(2^31) + return F32(randI()) * F32(1.0/2147483647.0); +} + +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) +// 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); + + 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); + + void setSeed(S32 s); +// using MRandomGenerator::randI; + U32 randI(); +}; + + +typedef MRandomLCG MRandom; + +extern MRandomLCG gRandGen; + + +#endif //_MRANDOM_H_ diff --git a/math/mRect.h b/math/mRect.h new file mode 100644 index 0000000..1022c5a --- /dev/null +++ b/math/mRect.h @@ -0,0 +1,310 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MRECT_H_ +#define _MRECT_H_ + +//Includes +#ifndef _MPOINT_H_ +#include "Math/mPoint.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 contains(const RectI& R) const; + bool overlaps(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); } +}; + +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 inset(F32 x, F32 y); + + bool intersect(const RectF& clipRect); + bool overlaps(const RectF&) const; + F32 len_x() const; + F32 len_y() const; + + bool isValidRect() const { return (extent.x > 0 && extent.y > 0); } +}; + +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 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 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::overlaps(const RectF& clipRect) const +{ + RectF test = *this; + return test.intersect(clipRect); +} + + +//------------------------------------------------------------------------------ +//-------------------------------------- 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.cc b/math/mSolver.cc new file mode 100644 index 0000000..5f1fa16 --- /dev/null +++ b/math/mSolver.cc @@ -0,0 +1,243 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 bool mIsZero(F32 val) +{ + return((val > -EQN_EPSILON) && (val < EQN_EPSILON)); +} + +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..3cd0a88 --- /dev/null +++ b/math/mSphere.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MSPHERE_H_ +#define _MSPHERE_H_ + +//Includes +#ifndef _MPOINT_H_ +#include "Math/mPoint.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; +}; + +//-------------------------------------- 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))); +} + +#endif //_SPHERE_H_ diff --git a/math/mSplinePatch.cc b/math/mSplinePatch.cc new file mode 100644 index 0000000..08b9a89 --- /dev/null +++ b/math/mSplinePatch.cc @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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(); + + U32 getNumPoints(){ return mPoints.size(); } + const Point3F * getPoint( U32 pointNum ); + void setPoint( Point3F &point, U32 pointNum ); + void addPoint( Point3F &point ); + void submitPoints( Point3F *pts, U32 num ); +}; + +//------------------------------------------------------------------------------ +// Base class for spline patches +//------------------------------------------------------------------------------ +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 ); + virtual void submitControlPoints( SplCtrlPts &points ){ mControlPoints = points; } + virtual void calc( F32 t, Point3F &result) = 0; + 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..dea2ec1 --- /dev/null +++ b/math/mTrig.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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..84960f0 --- /dev/null +++ b/math/mathIO.h @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATHIO_H_ +#define _MATHIO_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _STREAM_H_ +#include "Core/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->min); + success &= mathRead(stream, &b->max); + 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.min); + success &= mathWrite(stream, b.max); + 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.cc b/math/mathTypes.cc new file mode 100644 index 0000000..acf487b --- /dev/null +++ b/math/mathTypes.cc @@ -0,0 +1,546 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "Math/mPoint.h" +#include "Math/mMatrix.h" +#include "Math/mRect.h" +#include "Math/mBox.h" +#include "Math/mathTypes.h" +#include "Math/mRandom.h" + +//------------------------------------------------------------------------------ + +static const char* getDataTypePoint2I(void *dptr, EnumTable *, BitSet32 /*flag*/) +{ + Point2I *pt = (Point2I *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d %d", pt->x, pt->y); + return returnBuffer; +} + +static void setDataTypePoint2I(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 /*flag*/) +{ + 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\""); +} + +static const char* getDataTypePoint2F(void *dptr, EnumTable *, BitSet32 /*flag*/) +{ + Point2F *pt = (Point2F *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%f %f", pt->x, pt->y); + return returnBuffer; +} + +static void setDataTypePoint2F(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 /*flag*/) +{ + if(argc == 1) + dSscanf(argv[0], "%f %f", &((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\""); +} + +static const char *getDataTypePoint3F(void *dptr, EnumTable *, BitSet32 /*flag*/) +{ + Point3F *pt = (Point3F *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g %g %g", pt->x, pt->y, pt->z); + return returnBuffer; +} + +static void setDataTypePoint3F(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 /*flag*/) +{ + if(argc == 1) + dSscanf(argv[0], "%f %f %f", &((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\""); +} + +static const char *getDataTypePoint4F(void *dptr, EnumTable *, BitSet32 /*flag*/) +{ + 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; +} + +static void setDataTypePoint4F(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 /*flag*/) +{ + if(argc == 1) + dSscanf(argv[0], "%f %f %f %f", &((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("Point3F must be set as { x, y, z, w } or \"x y z w\""); +} + +static const char *getDataTypeRectI(void *dptr, EnumTable *, BitSet32 /*flag*/) +{ + 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; +} + +static void setDataTypeRectI(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 /*flag*/) +{ + 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\""); +} + +static const char *getDataTypeRectF(void *dptr, EnumTable *, BitSet32 /*flag*/) +{ + RectF *rect = (RectF *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%f %f %f %f", rect->point.x, rect->point.y, + rect->extent.x, rect->extent.y); + return returnBuffer; +} + +static void setDataTypeRectF(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 /*flag*/) +{ + if(argc == 1) + dSscanf(argv[0], "%f %f %f %f", &((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\""); +} + +static const char *getDataTypeMatrixPosition(void *dptr, EnumTable *, BitSet32 /*flag*/) +{ + 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; +} + +static void setDataTypeMatrixPosition(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 /*flag*/) +{ + 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] = dAtoi(argv[i]); + } + else + Con::printf("Matrix position must be set as { x, y, z, w } or \"x y z w\""); +} + +static const char *getDataTypeMatrixRotation(void * dptr, EnumTable *, BitSet32 /*flag*/) +{ + 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; +} + +static void setDataTypeMatrixRotation(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 /*flag*/) +{ + // 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]; +} + +static const char *getDataTypeBox3F(void * dptr, EnumTable *, BitSet32 /*flag*/) +{ + const Box3F* pBox = (const Box3F*)dptr; + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g %g %g %g %g %g", + pBox->min.x, pBox->min.y, pBox->min.z, + pBox->max.x, pBox->max.y, pBox->max.z); + + return returnBuffer; +} + +static void setDataTypeBox3F(void *dptr, S32 argc, const char **argv, EnumTable *, BitSet32 /*flag*/) +{ + Box3F* pDst = (Box3F*)dptr; + + if (argc == 1) { + U32 args = dSscanf(argv[0], "%f %f %f %f %f %f", + &pDst->min.x, &pDst->min.y, &pDst->min.z, + &pDst->max.x, &pDst->max.y, &pDst->max.z); + AssertWarn(args == 6, "Warning, box probably not read properly"); + } else { + Con::printf("Box3F must be set as \"xMin yMin zMin xMax yMax zMax\""); + } +} + + +//---------------------------------------------------------------------------- + +static const char* cVectorAdd(SimObject *, S32, const char ** argv) +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%f %f %f",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%f %f %f",&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; +} + +static const char* cVectorSub(SimObject *, S32, const char ** argv) +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%f %f %f",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%f %f %f",&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; +} + +static const char* cVectorScale(SimObject *, S32, const char ** argv) +{ + VectorF v(0,0,0); + dSscanf(argv[1],"%f %f %f",&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; +} + +static const char* cVectorNormalize(SimObject *, S32, const char ** argv) +{ + VectorF v(0,0,0); + dSscanf(argv[1],"%f %f %f",&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; +} + +static F32 cVectorDot(SimObject *, S32, const char ** argv) +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%f %f %f",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%f %f %f",&v2.x,&v2.y,&v2.z); + return mDot(v1,v2); +} + +static const char* cVectorCross(SimObject *, S32, const char ** argv) +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%f %f %f",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%f %f %f",&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; +} + +static const char* cVectorDist(SimObject *, S32, const char ** argv) +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%f %f %f",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%f %f %f",&v2.x,&v2.y,&v2.z); + VectorF v = v2 - v1; + float dist = mSqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z)); + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g",dist); + return returnBuffer; +} + +static const char* cVectorLen(SimObject *, S32, const char ** argv) +{ + VectorF v(0,0,0); + dSscanf(argv[1],"%f %f %f",&v.x,&v.y,&v.z); + float dist = mSqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z)); + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g",dist); + return returnBuffer; +} +static const char* cVectorOrthoBasis(SimObject *, S32, const char ** argv) +{ + AngAxisF aa; + dSscanf(argv[1],"%f %f %f %f", &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; +} + + +static const char* cMatrixCreate(SimObject *, S32, const char ** argv) +{ + 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; +} + + +static const char* cMatrixCreateFromEuler(SimObject *, S32, const char ** argv) +{ + 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; +} + + +static const char* cMatrixMultiply(SimObject *, S32, const char ** argv) +{ + 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; +} + + +static const char* cMatrixMulVector(SimObject *, S32, const char ** argv) +{ + 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; +} + +static const char* cMatrixMulPoint(SimObject *, S32, const char ** argv) +{ + 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; +} +//---------------------------------------------------------------------------- + +static const char* cGetBoxCenter(SimObject *, S32, const char ** argv) +{ + Box3F box; + box.min.set(0,0,0); + box.max.set(0,0,0); + dSscanf(argv[1],"%f %f %f %f %f %f", + &box.min.x,&box.min.y,&box.min.z, + &box.max.x,&box.max.y,&box.max.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; +} + + +//------------------------------------------------------------------------------ + +static void cSetRandomSeed(SimObject *, S32 argc, const char ** argv) +{ + U32 seed = Platform::getRealMilliseconds(); + if (argc == 2) + seed = dAtoi(argv[1]); + MRandomLCG::setGlobalRandSeed(seed); +} + +static S32 cGetRandomSeed(SimObject *, S32, const char **) +{ + 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); +} + +static F32 cGetRandom(SimObject *, S32 argc, const char **argv) +{ + if (argc == 2) + return F32(gRandGen.randI(0,dAtoi(argv[1]))); + 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(); +} + + +//------------------------------------------------------------------------------ + +void RegisterMathTypes(void) +{ + //register persistent types + Con::registerType(TypePoint2I, sizeof(Point2I), getDataTypePoint2I, setDataTypePoint2I); + Con::registerType(TypePoint2F, sizeof(Point2F), getDataTypePoint2F, setDataTypePoint2F); + Con::registerType(TypePoint3F, sizeof(Point3F), getDataTypePoint3F, setDataTypePoint3F); + Con::registerType(TypePoint4F, sizeof(Point4F), getDataTypePoint4F, setDataTypePoint4F); + Con::registerType(TypeRectI, sizeof(RectI), getDataTypeRectI, setDataTypeRectI); + Con::registerType(TypeRectF, sizeof(RectF), getDataTypeRectF, setDataTypeRectF); + Con::registerType(TypeMatrixPosition, sizeof(4*sizeof(F32)), getDataTypeMatrixPosition, setDataTypeMatrixPosition); + Con::registerType(TypeMatrixRotation, sizeof(MatrixF), getDataTypeMatrixRotation, setDataTypeMatrixRotation); + Con::registerType(TypeBox3F, sizeof(Box3F), getDataTypeBox3F, setDataTypeBox3F); +} + +void RegisterMathFunctions(void) +{ + Con::addCommand("VectorAdd", cVectorAdd, "VectorAdd(vec1,vec2)", 3, 3); + Con::addCommand("VectorSub", cVectorSub, "VectorSub(vec1,vec2)", 3, 3); + Con::addCommand("VectorScale", cVectorScale, "VectorScale(vec,scaler)", 3, 3); + Con::addCommand("VectorNormalize", cVectorNormalize, "VectorNormalize(vec)", 2, 2); + Con::addCommand("VectorDot", cVectorDot, "VectorDot(vec1,vec2)", 3, 3); + Con::addCommand("VectorCross", cVectorCross, "VectorCross(vec1,vec2)", 3, 3); + Con::addCommand("VectorDist", cVectorDist, "VectorDist(vec1,vec2)", 3, 3); + Con::addCommand("VectorLen", cVectorLen, "VectorLen(vec)", 2, 2); + Con::addCommand("VectorOrthoBasis", cVectorOrthoBasis, "VectorOrthoBasis(AngAxisF)", 2, 2); + + Con::addCommand("MatrixCreate", cMatrixCreate, "MatrixCreate(Pos, Rot)", 3, 3); + Con::addCommand("MatrixMultiply", cMatrixMultiply, "MatrixMultiply(Left, Right)", 3, 3); + Con::addCommand("MatrixMulVector", cMatrixMulVector, "MatrixMulVector(transform, vector)", 3, 3); + Con::addCommand("MatrixMulPoint", cMatrixMulPoint, "MatrixMulPoint(transform, point)", 3, 3); + + Con::addCommand("getBoxCenter", cGetBoxCenter, "getBoxCenter(Box)", 2, 2); + + Con::addCommand("setRandomSeed", cSetRandomSeed, "setRandomSeed([seed])", 1, 2); + Con::addCommand("getRandomSeed", cGetRandomSeed, "getRandomSeed()", 1, 1); + Con::addCommand("getRandom", cGetRandom, "getRandom([[max]||[min,max]])", 1, 3); + + Con::addCommand("MatrixCreateFromEuler", cMatrixCreateFromEuler, "MatrixCreateFromEuler(\"x y z\")", 2, 2); +} + diff --git a/math/mathTypes.h b/math/mathTypes.h new file mode 100644 index 0000000..5420aef --- /dev/null +++ b/math/mathTypes.h @@ -0,0 +1,14 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATHTYPES_H_ +#define _MATHTYPES_H_ + +void RegisterMathTypes(void); +void RegisterMathFunctions(void); + +#endif diff --git a/math/mathUtils.cc b/math/mathUtils.cc new file mode 100644 index 0000000..3fffc6f --- /dev/null +++ b/math/mathUtils.cc @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Math/mMath.h" +#include "Math/mathUtils.h" +#include "Math/mRandom.h" + +namespace MathUtils +{ + +MRandomLCG sgRandom(0xdeadbeef); + +//------------------------------------------------------------------------------ +// Creates orientation matrix from a direction vector. Assumes ( 0 0 1 ) is up. +//------------------------------------------------------------------------------ +MatrixF createOrientFromDir( Point3F &direction ) +{ + Point3F j = direction; + Point3F k(0.0, 0.0, 1.0); + Point3F i; + + mCross( j, k, &i ); + + if( i.magnitudeSafe() == 0.0 ) + { + i = Point3F( 0.0, -1.0, 0.0 ); + } + + i.normalizeSafe(); + mCross( i, j, &k ); + + MatrixF mat( true ); + mat.setColumn( 0, i ); + mat.setColumn( 1, j ); + mat.setColumn( 2, k ); + + return mat; +} + + +//------------------------------------------------------------------------------ +// Creates random direction given angle parameters similar to the particle system. +// The angles are relative to the specified axis. +//------------------------------------------------------------------------------ +Point3F randomDir( 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 / 180.0)); + AngAxisF phiRot(axis, phi * (M_PI / 180.0)); + + 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. +//------------------------------------------------------------------------------ +void getAnglesFromVector( VectorF &vec, F32 &yawAng, F32 &pitchAng ) +{ + yawAng = mAtan( vec.x, vec.y ); + + if( yawAng < 0.0 ) + { + yawAng += M_2PI; + } + + pitchAng = mAtan( fabs(vec.z), fabs(vec.y) ); + + if( vec.z < 0.0 ) + { + pitchAng = -pitchAng; + } + +} + + + + + +} // end namespace MathUtils diff --git a/math/mathUtils.h b/math/mathUtils.h new file mode 100644 index 0000000..ca97e29 --- /dev/null +++ b/math/mathUtils.h @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATHUTILS_H_ +#define _MATHUTILS_H_ + +class Point3F; +class MatrixF; + +namespace MathUtils +{ + + MatrixF createOrientFromDir( Point3F &direction ); + Point3F randomDir( Point3F &axis, F32 thetaAngleMin, F32 thetaAngleMax, F32 phiAngleMin = 0.0, F32 phiAngleMax = 360.0 ); + void getAnglesFromVector( VectorF &vec, F32 &yawAng, F32 &pitchAng ); + + + +} + +#endif // _MATHUTILS_H_ diff --git a/platform/3Dfx.h b/platform/3Dfx.h new file mode 100644 index 0000000..3410c9c --- /dev/null +++ b/platform/3Dfx.h @@ -0,0 +1,111 @@ +/* +** Copyright (c) 1995, 3Dfx Interactive, Inc. +** All Rights Reserved. +** +** This is UNPUBLISHED PROPRIETARY SOURCE CODE of 3Dfx Interactive, Inc.; +** the contents of this file may not be disclosed to third parties, copied or +** duplicated in any form, in whole or in part, without the prior written +** permission of 3Dfx Interactive, Inc. +** +** RESTRICTED RIGHTS LEGEND: +** Use, duplication or disclosure by the Government is subject to restrictions +** as set forth in subdivision (c)(1)(ii) of the Rights in Technical Data +** and Computer Software clause at DFARS 252.227-7013, and/or in similar or +** successor clauses in the FAR, DOD or NASA FAR Supplement. Unpublished - +** rights reserved under the Copyright Laws of the United States. +** +** $Revision: 1.1 $ +** $Date: 2001/05/17 02:18:56 $ +*/ +#ifndef _3DFX_H_ +#define _3DFX_H_ + +/* +** basic data types +*/ +typedef unsigned char FxU8; +typedef signed char FxI8; +typedef unsigned short FxU16; +typedef signed short FxI16; +typedef signed long FxI32; +typedef unsigned long FxU32; +typedef int FxBool; +typedef float FxFloat; +typedef double FxDouble; + +/* +** color types +*/ +typedef unsigned long FxColor_t; +typedef struct { float r, g, b, a; } FxColor4; + +/* +** fundamental types +*/ +#define FXTRUE 1 +#define FXFALSE 0 + +/* +** helper macros +*/ +#define FXUNUSED( a ) ( (a) = (a) ) +#define FXBIT( i ) ( 1L << (i) ) + +/* +** export macros +*/ + +#if defined(__MSC__) || defined(_MSC_VER) || (defined(__MWERKS__) && defined(WIN32)) + #if defined (MSVC16) + #define FX_ENTRY + #define FX_CALL + #else + #define FX_ENTRY extern + #define FX_CALL __stdcall + #endif +#elif defined(__WATCOMC__) || defined(__BORLANDC__) + #define FX_ENTRY extern + #define FX_CALL __stdcall +#elif defined (__IBMC__) || defined (__IBMCPP__) + /* IBM Visual Age C/C++: */ + #define FX_ENTRY extern + #define FX_CALL __stdcall +#elif defined(__DJGPP__) + #define FX_ENTRY extern + #define FX_CALL +#elif defined(__unix__) + #define FX_ENTRY extern + #define FX_CALL +#else + #warning define FX_ENTRY & FX_CALL for your compiler + #define FX_ENTRY extern + #define FX_CALL +#endif + +/* +** x86 compiler specific stuff +*/ +#if defined(__BORLANDC_) + # define REALMODE + + # define REGW( a, b ) ((a).x.b) + # define REGB( a, b ) ((a).h.b) + # define INT86( a, b, c ) int86(a,b,c) + # define INT86X( a, b, c, d ) int86x(a,b,c,d) + + # define RM_SEG( a ) FP_SEG( a ) + # define RM_OFF( a ) FP_OFF( a ) +#elif defined(__WATCOMC__) + # undef FP_SEG + # undef FP_OFF + + # define REGW( a, b ) ((a).w.b) + # define REGB( a, b ) ((a).h.b) + # define INT86( a, b, c ) int386(a,b,c) + # define INT86X( a, b, c, d ) int386x(a,b,c,d) + + # define RM_SEG( a ) ( ( ( ( FxU32 ) (a) ) & 0x000F0000 ) >> 4 ) + # define RM_OFF( a ) ( ( FxU16 ) (a) ) +#endif + +#endif /* !__3DFX_H__ */ diff --git a/platform/event.h b/platform/event.h new file mode 100644 index 0000000..13eda70 --- /dev/null +++ b/platform/event.h @@ -0,0 +1,373 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EVENT_H_ +#define _EVENT_H_ + +// Library-wide input events - all external events are converted into system events + +typedef int NetConnectionId; + +struct NetAddress { + int type; + enum { + IPXAddress, IPAddress + }; + U8 netNum[4]; // ip sin_addr ipx sa_netnum + U8 nodeNum[6]; // ipx sa_nodenum + U16 port; // ip sin_port, ipx sa_socket +}; + +enum +{ + MaxPacketDataSize = 1500, + MaxConsoleLineSize = 512 + +}; + +enum EventType +{ + InputEventType, + MouseMoveEventType, + PacketReceiveEventType, + TimeEventType, + QuitEventType, + ConsoleEventType, + ConnectedReceiveEventType, + ConnectedAcceptEventType, + ConnectedNotifyEventType +}; + +// Base event structure (also used for FrameEvent and QuitEvent) + +struct Event +{ + U16 type, size; + Event() { size = sizeof(Event); } +}; + +// Time event advances simulation + +struct TimeEvent : public Event +{ + U32 elapsedTime; + + TimeEvent() { type = TimeEventType; size = sizeof(TimeEvent); } +}; + +struct ConnectedNotifyEvent : public Event +{ + enum State { + DNSResolved, + DNSFailed, + Connected, + ConnectFailed, + Disconnected + }; + U32 state; + U32 tag; + ConnectedNotifyEvent() { type = ConnectedNotifyEventType; size = sizeof(ConnectedNotifyEvent); } +}; + +struct ConnectedReceiveEvent : public Event +{ + U32 tag; + U8 data[MaxPacketDataSize]; + ConnectedReceiveEvent() { type = ConnectedReceiveEventType; } +}; + +struct ConnectedAcceptEvent : public Event +{ + U32 portTag; + U32 connectionTag; + NetAddress address; + ConnectedAcceptEvent() { type = ConnectedAcceptEventType; size = sizeof(ConnectedAcceptEvent); } +}; + +enum +{ + ConnectedReceiveEventHeaderSize = U32( &( (ConnectedReceiveEvent *)0)->data) +}; + +// Packet receive event is incoming packet event - packetType is what type of packet it is + +struct PacketReceiveEvent : public Event +{ + NetAddress sourceAddress; + U8 data[MaxPacketDataSize]; // placeholder for data + PacketReceiveEvent() { type = PacketReceiveEventType; } +}; + +enum +{ + PacketReceiveEventHeaderSize = U32( &( (PacketReceiveEvent *)0)->data) +}; + +// request for connection, connection lost, etc. + +// event sent to the currently focused console + +struct ConsoleEvent : public Event +{ + char data[MaxConsoleLineSize]; + ConsoleEvent() { type = ConsoleEventType; } +}; + +enum +{ + ConsoleEventHeaderSize = U32( &( (ConsoleEvent *)0)->data) +}; + +// Input event structure - all input events generated in the platform come through here + +struct MouseMoveEvent : public Event +{ + S32 xPos, yPos; + U8 modifier; + + MouseMoveEvent() { type = MouseMoveEventType; size = sizeof(MouseMoveEvent); } +}; + +struct InputEvent : public Event +{ + U32 deviceInst; // device instance joystick0, joystick1 etc + float fValue; // value -1.0 to 1.0 + U16 deviceType; // mouse, keyboard, joystick, device( ie unidentified) + U16 objType; // SI_XAXIS, SI_BUTTON, SI_KEY ... + U16 ascii; // ascii character code 'a', 'A', 'b', '*', etc (if device==keyboard) - possibly a uchar or something + U16 objInst; // which type instance OR DIK_CODE + U8 action; // MAKE/BREAK/MOVE + U8 modifier; // SI_LSHIFT, etc + + InputEvent() { type = InputEventType; size = sizeof(InputEvent); } +}; + +// input event constants: + +enum KeyCodes { + 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_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 +}; + +enum JoystickCodes { + 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 +}; + + +enum InputDeviceTypes +{ + UnknownDeviceType, + MouseDeviceType, + KeyboardDeviceType, + JoystickDeviceType +}; + +// Device Event Action Types +#define SI_MAKE 0x01 +#define SI_BREAK 0x02 +#define SI_MOVE 0x03 +#define SI_REPEAT 0x04 + +//Device Event Types +#define SI_UNKNOWN 0x01 +#define SI_BUTTON 0x02 +#define SI_POV 0x03 +#define SI_KEY 0x0A + +// Event SubTypes +#define SI_ANY 0xff + +// Modifier Keys +#define SI_LSHIFT (1<<0) +#define SI_RSHIFT (1<<1) +#define SI_SHIFT (SI_LSHIFT|SI_RSHIFT) +#define SI_LCTRL (1<<2) +#define SI_RCTRL (1<<3) +#define SI_CTRL (SI_LCTRL|SI_RCTRL) +#define SI_LALT (1<<4) +#define SI_RALT (1<<5) +#define SI_ALT (SI_LALT|SI_RALT) + +#endif diff --git a/platform/gameInterface.cc b/platform/gameInterface.cc new file mode 100644 index 0000000..a905708 --- /dev/null +++ b/platform/gameInterface.cc @@ -0,0 +1,239 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "core/fileStream.h" +#ifdef __linux +#include "platform/platformMutex.h" + +static void* mutex = 0; +#endif +#include "console/console.h" +GameInterface *Game = NULL; + +GameInterface::GameInterface() +{ + AssertFatal(Game == NULL, "ERROR: Multiple games declared."); + Game = this; + mJournalMode = JournalOff; + mRunning = true; +#ifdef __linux + mutex = Mutex::createMutex( ); + AssertFatal( mutex == 0, "ERROR: can't create mutex" ); +#endif +} + +int GameInterface::main(int, const char**) +{ + return(0); +} + +void GameInterface::textureKill() +{ + +} + +void GameInterface::textureResurrect() +{ + +} + +void GameInterface::refreshWindow() +{ + +} + +static U32 sReentrantCount = 0; + +void GameInterface::processEvent(Event *event) +{ + if(!mRunning) + return; + +#ifdef __linux + Mutex::lockMutex( mutex ); +#else + if(PlatformAssert::processingAssert()) // ignore any events if an assert dialog is up. + return; + +#ifdef DEBUG + sReentrantCount++; + AssertFatal(sReentrantCount == 1, "Error! ProcessEvent is NOT reentrant."); +#endif +#endif + switch(event->type) + { + case PacketReceiveEventType: + processPacketReceiveEvent((PacketReceiveEvent *) event); + break; + case MouseMoveEventType: + processMouseMoveEvent((MouseMoveEvent *) event); + break; + case InputEventType: + processInputEvent((InputEvent *) event); + break; + case QuitEventType: + processQuitEvent(); + break; + case TimeEventType: + processTimeEvent((TimeEvent *) event); + break; + case ConsoleEventType: + processConsoleEvent((ConsoleEvent *) event); + break; + case ConnectedAcceptEventType: + processConnectedAcceptEvent( (ConnectedAcceptEvent *) event ); + break; + case ConnectedReceiveEventType: + processConnectedReceiveEvent( (ConnectedReceiveEvent *) event ); + break; + case ConnectedNotifyEventType: + processConnectedNotifyEvent( (ConnectedNotifyEvent *) event ); + break; + } + +#ifdef __linux + Mutex::unlockMutex( mutex ); +#else +#ifdef DEBUG + sReentrantCount--; +#endif +#endif +} + + +void GameInterface::processPacketReceiveEvent(PacketReceiveEvent*) +{ + +} + +void GameInterface::processMouseMoveEvent(MouseMoveEvent*) +{ + +} + +void GameInterface::processInputEvent(InputEvent*) +{ + +} + +void GameInterface::processQuitEvent() +{ +} + +void GameInterface::processTimeEvent(TimeEvent*) +{ +} + +void GameInterface::processConsoleEvent(ConsoleEvent*) +{ + +} + +void GameInterface::processConnectedAcceptEvent(ConnectedAcceptEvent*) +{ + +} + +void GameInterface::processConnectedReceiveEvent(ConnectedReceiveEvent*) +{ + +} + +void GameInterface::processConnectedNotifyEvent(ConnectedNotifyEvent*) +{ + +} + +struct ReadEvent : public Event +{ + U8 data[3072]; +}; + +FileStream gJournalStream; + +void GameInterface::postEvent(Event &event) +{ + if((mJournalMode == JournalLoad || mJournalMode == JournalPlay) && event.type != QuitEventType) + return; + if(mJournalMode == JournalSave) { + gJournalStream.write(event.size, &event); + gJournalStream.flush(); + } + processEvent(&event); +} + +void GameInterface::journalProcess() +{ + if(mJournalMode == JournalLoad || mJournalMode == JournalPlay) + { + ReadEvent journalReadEvent; + if(gJournalStream.read(&journalReadEvent.type)) + { + if(gJournalStream.read(&journalReadEvent.size)) + { + if(gJournalStream.read(journalReadEvent.size - sizeof(Event), &journalReadEvent.data)) + { + if(gJournalStream.getPosition() == gJournalStream.getStreamSize() && mJournalMode == JournalLoad && !Con::getBoolVariable("NoJournalBreak", false)) + Platform::debugBreak(); + processEvent(&journalReadEvent); + return; + } + } + } + if(mJournalMode == JournalLoad) + mRunning = false; + else + mJournalMode = JournalOff; + } +} + +void GameInterface::loadJournal(const char *fileName) +{ + mJournalMode = JournalLoad; + gJournalStream.open(fileName, FileStream::Read); +} + +void GameInterface::saveJournal(const char *fileName) +{ + mJournalMode = JournalSave; + gJournalStream.open(fileName, FileStream::Write); +} + +void GameInterface::playJournal(const char *fileName) +{ + mJournalMode = JournalPlay; + gJournalStream.open(fileName, FileStream::Read); +} + +FileStream *GameInterface::getJournalStream() +{ + return &gJournalStream; +} + +void GameInterface::journalRead(U32 *val) +{ + gJournalStream.read(val); +} + +void GameInterface::journalWrite(U32 val) +{ + gJournalStream.write(val); +} + +void GameInterface::journalRead(U32 size, void *buffer) +{ + gJournalStream.read(size, buffer); +} + +void GameInterface::journalWrite(U32 size, const void *buffer) +{ + gJournalStream.write(size, buffer); +} + diff --git a/platform/gameInterface.h b/platform/gameInterface.h new file mode 100644 index 0000000..5fbd62f --- /dev/null +++ b/platform/gameInterface.h @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMEINTERFACE_H_ +#define _GAMEINTERFACE_H_ + +class FileStream; + +class GameInterface +{ +public: + enum JournalMode { + JournalOff, + JournalSave, + JournalPlay, + JournalLoad, + }; +private: + JournalMode mJournalMode; + bool mRunning; +public: + GameInterface(); + + // calls from the platform into the game: + virtual int main(int argc, const char **argv); + virtual void textureKill(); + virtual void textureResurrect(); + virtual void refreshWindow(); + + virtual void postEvent(Event &event); + + // event handlers: + // default event behavior with journaling support + // default handler forwards events to appropriate routines + virtual void processEvent(Event *event); + + virtual void processPacketReceiveEvent(PacketReceiveEvent *event); + virtual void processMouseMoveEvent(MouseMoveEvent *event); + virtual void processInputEvent(InputEvent *event); + virtual void processQuitEvent(); + virtual void processTimeEvent(TimeEvent *event); + virtual void processConsoleEvent(ConsoleEvent *event); + virtual void processConnectedAcceptEvent(ConnectedAcceptEvent *event); + virtual void processConnectedReceiveEvent(ConnectedReceiveEvent *event); + virtual void processConnectedNotifyEvent(ConnectedNotifyEvent *event); + + void setRunning(bool running) { mRunning = running; } + bool isRunning() { return mRunning; } + + void journalProcess(); + void loadJournal(const char *fileName); + void saveJournal(const char *fileName); + void playJournal(const char *fileName); + + JournalMode getJournalMode() { return mJournalMode; }; + + bool isJournalReading() { return mJournalMode == JournalLoad || mJournalMode == JournalPlay; } + bool isJournalWriting() { return mJournalMode == JournalSave; } + + void journalRead(U32 *val); + void journalWrite(U32 val); + void journalRead(U32 size, void *buffer); + void journalWrite(U32 size, const void *buffer); + + FileStream *getJournalStream(); +}; + +extern GameInterface *Game; + +#endif diff --git a/platform/platform.h b/platform/platform.h new file mode 100644 index 0000000..0821967 --- /dev/null +++ b/platform/platform.h @@ -0,0 +1,495 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORM_H_ +#define _PLATFORM_H_ + +#ifndef _TYPES_H_ +#include "platform/types.h" +#endif +#ifndef _PLATFORMASSERT_H_ +#include "platform/platformAssert.h" +#endif + +//------------------------------------------------------------------------------ +// Endian conversions +#ifdef PLATFORM_LITTLE_ENDIAN + +inline U16 convertHostToLEndian(U16 i) { return i; } +inline U16 convertLEndianToHost(U16 i) { return i; } +inline U32 convertHostToLEndian(U32 i) { return i; } +inline U32 convertLEndianToHost(U32 i) { return i; } +inline S16 convertHostToLEndian(S16 i) { return i; } +inline S16 convertLEndianToHost(S16 i) { return i; } +inline S32 convertHostToLEndian(S32 i) { return i; } +inline S32 convertLEndianToHost(S32 i) { return i; } + +inline F32 convertHostToLEndian(F32 i) { return i; } +inline F32 convertLEndianToHost(F32 i) { return i; } + +inline U16 convertHostToBEndian(U16 i) +{ + return U16((i << 8) | (i >> 8)); +} + +inline U16 convertBEndianToHost(U16 i) +{ + return U16((i << 8) | (i >> 8)); +} + +inline S16 convertHostToBEndian(S16 i) +{ + return S16(convertHostToBEndian(U16(i))); +} + +inline S16 convertBEndianToHost(S16 i) +{ + return S16(convertBEndianToHost(S16(i))); +} + +inline U32 convertHostToBEndian(U32 i) +{ + return ((i << 24) & 0xff000000) | + ((i << 8) & 0x00ff0000) | + ((i >> 8) & 0x0000ff00) | + ((i >> 24) & 0x000000ff); +} + +inline U32 convertBEndianToHost(U32 i) +{ + return ((i << 24) & 0xff000000) | + ((i << 8) & 0x00ff0000) | + ((i >> 8) & 0x0000ff00) | + ((i >> 24) & 0x000000ff); +} + +inline S32 convertHostToBEndian(S32 i) +{ + return S32(convertHostToBEndian(U32(i))); +} + +inline S32 convertBEndianToHost(S32 i) +{ + return S32(convertBEndianToHost(S32(i))); +} + + +#elif defined(PLATFORM_BIG_ENDIAN) + +inline U16 convertHostToBEndian(U16 i) { return i; } +inline U16 convertBEndianToHost(U16 i) { return i; } +inline U32 convertHostToBEndian(U32 i) { return i; } +inline U32 convertBEndianToHost(U32 i) { return i; } +inline S16 convertHostToBEndian(S16 i) { return i; } +inline S16 convertBEndianToHost(S16 i) { return i; } +inline S32 convertHostToBEndian(S32 i) { return i; } +inline S32 convertBEndianToHost(S32 i) { return i; } + +inline U16 convertHostToLEndian(U16 i) +{ + return (i << 8) | (i >> 8); +} +inline U16 convertLEndianToHost(U16 i) +{ + return (i << 8) | (i >> 8); +} +inline U32 convertHostToLEndian(U32 i) +{ + return ((i << 24) & 0xff000000) | + ((i << 8) & 0x00ff0000) | + ((i >> 8) & 0x0000ff00) | + ((i >> 24) & 0x000000ff); +} +inline U32 convertLEndianToHost(U32 i) +{ + return ((i << 24) & 0xff000000) | + ((i << 8) & 0x00ff0000) | + ((i >> 8) & 0x0000ff00) | + ((i >> 24) & 0x000000ff); +} + + +inline F32 convertHostToLEndian(F32 i) +{ + U32 result = convertHostToLEndian( *reinterpret_cast(&i) ); + return *reinterpret_cast(&result); +} + +inline F32 convertLEndianToHost(F32 i) +{ + U32 result = convertLEndianToHost( *reinterpret_cast(&i) ); + return *reinterpret_cast(&result); +} + + + + +inline S16 convertHostToLEndian(S16 i) { return S16(convertHostToLEndian(U16(i))); } +inline S16 convertLEndianToHost(S16 i) { return S16(convertLEndianToHost(U16(i))); } +inline S32 convertHostToLEndian(S32 i) { return S32(convertHostToLEndian(U32(i))); } +inline S32 convertLEndianToHost(S32 i) { return S32(convertLEndianToHost(U32(i))); } + +#else +#error "Endian define not set" +#endif + + +//------------------------------------------------------------------------------ +// Input structures and functions - all input is pushed into the input event queue +template class Vector; +class Point2I; + + +// Theese emuns must be globally scoped so that they work +// with the inline assembly +enum ProcessorType +{ // x86 + CPU_X86Compatible, + CPU_Intel_Unknown, + CPU_Intel_Pentium, + CPU_Intel_PentiumII, + CPU_Intel_PentiumIII, + CPU_AMD_K6, + CPU_AMD_K6_2, + CPU_AMD_K6_3, + CPU_AMD_K7, + 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_G3 +}; + +enum x86Properties +{ // x86 properties + CPU_PROP_C = (1<<0), + CPU_PROP_FPU = (1<<1), + CPU_PROP_MMX = (1<<2), // Integer-SIMD + CPU_PROP_3DNOW = (1<<3), // AMD Float-SIMD + CPU_PROP_SSE = (1<<4), // PentiumIII SIMD + CPU_PROP_RDTSC = (1<<5) // Read Time Stamp Counter +}; + +enum PPCProperties +{ // PowerPC properties +}; + +struct Platform +{ + static void init(); + static void shutdown(); + static void process(); + static bool doCDCheck(); + static void initWindow(const Point2I &initialSize, const char *name); + static void enableKeyboardTranslation(void); + static void disableKeyboardTranslation(void); + static void setWindowLocked(bool locked); + static void minimizeWindow(); + static bool excludeOtherInstances(const char *string); + + static const Point2I &getWindowSize(); + static void setWindowSize( U32 newWidth, U32 newHeight ); + static float getRandom(); + static void AlertOK(const char *windowTitle, const char *message); + static bool AlertOKCancel(const char *windowTitle, const char *message); + static bool AlertRetry(const char *windowTitle, const char *message); + + 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 + }; + + static void getLocalTime(LocalTime &); + + struct FileInfo { + const char* pFullPath; + const char* pVirtPath; + const char* pFileName; + U32 fileSize; + }; + static bool cdFileExists(const char *filePath, const char *volumeName, S32 serialNum); + static 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 + static S32 compareFileTimes(const FileTime &a, const FileTime &b); + // Process control + public: + static void postQuitMessage(const U32 in_quitVal); + static void debugBreak(); + static void forceShutdown(int returnValue); + + static U32 getTime(); + static U32 getVirtualMilliseconds(); + static U32 getRealMilliseconds(); + static void advanceTime(U32 delta); + + // Directory functions. Dump path returns false iff the directory cannot be + // opened. + public: + static void getCurrentDirectory(char *out_pDirectory, const U32 in_bufferSize); + static bool dumpPath(const char *in_pBasePath, Vector& out_rFileVector); + static bool getFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime); + + static bool createPath(const char *path); // create a directory path + static struct SystemInfo_struct + { + struct Processor + { + ProcessorType type; + const char *name; + U32 mhz; + U32 properties; // CPU type specific enum + }processor; + }SystemInfo; + + // Web page launch function: + static bool openWebBrowser( const char* webAddress ); + + static const char* getLoginPassword(); + static bool setLoginPassword( const char* password ); + + static const char* getClipboard(); + static bool setClipboard(const char *text); +}; + + +struct Processor +{ + static void init(); +}; + + +//------------------------------------------------------------------------------ +// time manager generates a ServerTimeEvent / ClientTimeEvent, FrameEvent combo +// every other time its process is called. +struct TimeManager +{ + static void process(); +}; + +// the entry point of the app is in the platform code... +// it calls out into game code at GameMain + +// all input goes through the game input event queue +// whether or not it is used (replaying a journal, etc) +// is up to the game code. The game must copy the event data out. + +struct Event; + +//------------------------------------------------------------------------------ +// String functions + +extern char* dStrcat(char *dst, const char *src); +extern int dStrcmp(const char *str1, const char *str2); +extern int dStricmp(const char *str1, const char *str2); +extern int dStrncmp(const char *str1, const char *str2, U32 len); +extern int dStrnicmp(const char *str1, const char *str2, U32 len); +extern char* dStrcpy(char *dst, const char *src); +extern char* dStrncpy(char *dst, const char *src, U32 len); +extern U32 dStrlen(const char *str); +extern char* dStrupr(char *str); +extern char* dStrlwr(char *str); +extern char* dStrchr(char *str, int c); +extern const char* dStrchr(const char *str, int c); +extern char* dStrrchr(char *str, int c); +extern const char* dStrrchr(const char *str, int c); +extern U32 dStrspn(const char *str, const char *set); +extern U32 dStrcspn(const char *str, const char *set); +extern char* dStrstr(char *str1, char *str2); +extern const char* dStrstr(const char *str1, const char *str2); + +extern char* dStrtok(char *str, const char *sep); + +extern int dAtoi(const char *str); +extern float dAtof(const char *str); +extern bool dAtob(const char *str); + +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, ...); +extern int dFflushStdout(); +extern int dFflushStderr(); + +inline char dToupper(const char c) { if (c >= char('a') && c <= char('z')) return char(c + 'A' - 'a'); else return c; } +inline char dTolower(const char c) { if (c >= char('A') && c <= char('Z')) return char(c - 'A' + 'a'); else return c; } + +extern bool dIsalnum(const char c); +extern bool dIsalpha(const char c); +extern bool dIsdigit(const char c); +extern bool dIsspace(const char c); + +//------------------------------------------------------------------------------ +// Misc StdLib functions + + +#define QSORT_CALLBACK FN_CDECL +void dQsort(void *base, U32 nelem, U32 width, int (QSORT_CALLBACK *fcmp)(const void *, const void *)); + + +//------------------------------------------------------------------------------ +// ConsoleObject GetClassNameFn +class ConsoleObject; +const char* __dyncreate_getNameFromType(ConsoleObject *); + + +//------------------------------------------------------------------------------ +// Memory functions + +namespace Memory { + U32 getMemoryUsed(); + U32 getMemoryAllocated(); +} // namespace Memory + +extern void* FN_CDECL operator new(dsize_t size, void* ptr); + +template +inline T* constructInPlace(T* p) +{ + return new(p) T; +} + +template +inline void destructInPlace(T* p) +{ + p->~T(); +} + +#ifndef NO_MEMORY_MANAGER + 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__) +#endif // #ifndef NO_MEMORY_MANAGER + +#define placenew(x) new(x) +#define dMalloc(x) dMalloc_r(x, __FILE__, __LINE__) +#define dStrdup(x) dStrdup_r(x, __FILE__, __LINE__) + +extern char* dStrdup_r(const char *src, const char*, U32); + +extern void setBreakAlloc(U32); +extern void setMinimumAllocUnit(U32); +extern void* dMalloc_r(U32 in_size, const char*, const U32); +extern void dFree(void* in_pFree); +extern void* dRealloc(void* in_pResize, U32 in_size); +extern void* dRealMalloc(dsize_t); +extern void dRealFree(void*); + +extern void* dMemcpy(void *dst, const void *src, unsigned size); +extern void* dMemmove(void *dst, const void *src, unsigned size); +extern void* dMemset(void *dst, int c, unsigned size); +extern int dMemcmp(const void *ptr1, const void *ptr2, unsigned size); + +//------------------------------------------------------------------------------ +// Graphics functions + +class GFont; +extern GFont *createFont(const char *name, int size); + + +//------------------------------------------------------------------------------ +// FileIO functions +typedef void* FILE_HANDLE; +enum DFILE_STATUS +{ + DFILE_OK = 1 +}; + +extern bool dFileDelete(const char *name); +extern bool dFileTouch(const char *name); + +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); + + +//------------------------------------------------------------------------------ +// Math +struct Math +{ + static void init(U32 properties = 0); // 0 == detect available hardware +}; + + +//------------------------------------------------------------------------------ +// Networking +struct NetAddress; + +typedef int NetSocket; +const NetSocket InvalidSocket = -1; + +struct Net { + enum Error { + NoError, + WrongProtocolType, + InvalidPacketProtocol, + WouldBlock, + NotASocket, + UnknownError + }; + enum Protocol { + UDPProtocol, + IPXProtocol, + TCPProtocol + }; + 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(int connectPort); + static void closePort(); + static Error sendto(const NetAddress *address, const U8 *buffer, int 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 sendTo(NetSocket socket, const U8 *buffer, int bufferSize); + + static void process(); + + 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 connect(NetSocket socket, const NetAddress *address); + static Error listen(NetSocket socket, int maxConcurrentListens); + static NetSocket accept(NetSocket acceptSocket, NetAddress *remoteAddress); + + static Error bind(NetSocket socket, U16 port); + static Error setBufferSize(NetSocket socket, int bufferSize); + static Error setBroadcast(NetSocket socket, bool broadcastEnable); + static Error setBlocking(NetSocket socket, bool blockingIO); + + static Error send(NetSocket socket, const U8 *buffer, int bufferSize); + static Error recv(NetSocket socket, U8 *buffer, int bufferSize, int *bytesRead); +}; + + +#endif diff --git a/platform/platformAL.h b/platform/platformAL.h new file mode 100644 index 0000000..b10a03c --- /dev/null +++ b/platform/platformAL.h @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMAL_H_ +#define _PLATFORMAL_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +//#define AL_NO_PROTOTYPES +//#define ALC_NO_PROTOTYPES +//#define _LIB +//#include +//#include +//#include + +#include +#include + +#define AL_FUNCTION(fn_return,fn_name,fn_args, fn_value) extern fn_return (FN_CDECL *fn_name)fn_args; +#include +#include +#undef AL_FUNCTION + + + +/* +// 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 +}; +*/ + +namespace Audio +{ + +//bool libraryInit(); +//void libraryInitExtensions(); +//void libraryShutdown(); + +//inline bool doesSupportIASIG() +//{ +// return gDoesSupport_AL_EXT_IASIG; +//} + +//inline bool doesSupportDynamix() +//{ +// return gDoesSupport_AL_EXT_DYNAMIX; +//} + + +bool OpenALInit(); +void OpenALShutdown(); + +bool OpenALDLLInit(); +void OpenALDLLShutdown(); + +// special alx flags +#define AL_GAIN_LINEAR 0xFF01 + +// helpers +F32 DBToLinear(F32 value); +F32 linearToDB(F32 value); + +} // end namespace Audio + + +#endif // _H_PLATFORMAL_ diff --git a/platform/platformAssert.cc b/platform/platformAssert.cc new file mode 100644 index 0000000..2264b97 --- /dev/null +++ b/platform/platformAssert.cc @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platformAssert.h" +#include "console/console.h" +#include + +//-------------------------------------- STATIC Declaration +PlatformAssert *PlatformAssert::platformAssert = NULL; + +//-------------------------------------- +PlatformAssert::PlatformAssert() +{ + processing = false; +} + +//-------------------------------------- +void PlatformAssert::create() +{ + if (!platformAssert) + platformAssert = 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; +} + + +//-------------------------------------- +void PlatformAssert::process(Type assertType, + const char *filename, + U32 lineNumber, + const char *message) +{ + processing = true; + char *typeName[] = { "Unknown", "Fatal-ISV", "Fatal", "Warning" }; + + // always dump to the Assert to the Console + if (Con::isActive()) + { + if (assertType == Warning) + Con::warnf(ConsoleLogEntry::Assert, "%s: (%s @ %ld) %s", typeName[assertType], filename, lineNumber, message); + else + Con::errorf(ConsoleLogEntry::Assert, "%s: (%s @ %ld) %s", typeName[assertType], filename, lineNumber, 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: (%s @ %ld)", typeName[assertType], filename, lineNumber); + +#ifdef 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::debugBreak(); + else + Platform::forceShutdown(1); + } + processing = false; +} + +bool PlatformAssert::processingAssert() +{ + return platformAssert ? platformAssert->processing : false; +} + +//-------------------------------------- +void PlatformAssert::processAssert(Type assertType, + const char *filename, + U32 lineNumber, + const char *message) +{ + if (platformAssert) + platformAssert->process(assertType, filename, lineNumber, message); +} + + +//-------------------------------------- +const char* avar(const char *message, ...) +{ + static char buffer[1024]; + 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..63c47fc --- /dev/null +++ b/platform/platformAssert.h @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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; + + bool displayMessageBox(const char *title, const char *message, bool retry); + void process(Type assertType, + const char* filename, + U32 lineNumber, + const char* message); + + PlatformAssert(); + +public: + static void create(); + static void destroy(); + static void processAssert(Type assertType, + const char* filename, + U32 lineNumber, + const char* message); + static char* message(const char *message, ...); + static bool processingAssert(); +}; + + +#ifdef ENABLE_ASSERTS + #define AssertWarn(x, y) \ + { if (!bool(x)) \ + PlatformAssert::processAssert(PlatformAssert::Warning, __FILE__, __LINE__, y); } + + #define AssertFatal(x, y) \ + { if (!bool(x)) \ + PlatformAssert::processAssert(PlatformAssert::Fatal, __FILE__, __LINE__, y); } + +// #define AssertFatal(x, y) { } + +#else + #define AssertFatal(x, y) { } + #define AssertWarn(x, y) { } +#endif + + +#define AssertISV(x, y) \ + { if (!bool(x)) \ + PlatformAssert::processAssert(PlatformAssert::Fatal_ISV, __FILE__, __LINE__, y); } + + +const char* avar(const char *in_msg, ...); + + + +#endif // _PLATFORM_ASSERT_H_ + diff --git a/platform/platformAudio.h b/platform/platformAudio.h new file mode 100644 index 0000000..1e2876a --- /dev/null +++ b/platform/platformAudio.h @@ -0,0 +1,171 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMAUDIO_H_ +#define _PLATFORMAUDIO_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _PLATFORMAL_H_ +#include "PlatformWin32/platformAL.h" +#endif +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _BITSET_H_ +#include "Core/bitSet.h" +#endif + +typedef U32 AUDIOHANDLE; +#define NULL_AUDIOHANDLE 0 + +//-------------------------------------------------------------------------- + +namespace Audio +{ + enum AudioTypes { + DefaultAudioType = 0, + ChatAudioType, + GuiAudioType, + EffectAudioType, + VoiceAudioType, + MusicAudioType, + + NumAudioTypes + }; + + //-------------------------------------- + // sound property description + struct Description + { + F32 mVolume; // 0-1 1=loudest volume + bool mIsLooping; + bool mIs3D; + + F32 mMinDistance; + F32 mMaxDistance; + U32 mConeInsideAngle; + U32 mConeOutsideAngle; + F32 mConeOutsideVolume; + Point3F mConeVector; + + // environment info + F32 mEnvironmentLevel; + + // used by 'AudioEmitter' class + S32 mLoopCount; + S32 mMinLoopGap; + S32 mMaxLoopGap; + + // each 'type' can have its own volume + S32 mType; + }; + + struct DriverInfo + { + char *mName; + char *mVender; + char *mVersion; + char *mRenderer; + char *mExtensions; + }; + + void init(); + void detect(); + void destroy(); + + bool setDriver(const char *name); + const Vector* getDriverList(); + + const char* getDriverListString(); + const char* getCurrentDriverInfo(); +} + +class AudioDescription; +class AudioProfile; +class AudioEnvironment; +class AudioSampleEnvironment; + +AUDIOHANDLE alxCreateSource(const Audio::Description *desc, const char *filename, const MatrixF *transform=NULL, AudioSampleEnvironment * sampleEnvironment = 0); +AUDIOHANDLE alxCreateSource(AudioDescription *descObject, const char *filename, const MatrixF *transform=NULL, AudioSampleEnvironment * sampleEnvironment = 0); +AUDIOHANDLE alxCreateSource(const AudioProfile *profile, const MatrixF *transform=NULL); + +AUDIOHANDLE alxPlay(AUDIOHANDLE handle); +void alxStop(AUDIOHANDLE handle); +void alxStopAll(); + +// one-shot helper alxPlay functions, create and play in one call +AUDIOHANDLE alxPlay(const AudioProfile *profile, const MatrixF *transform=NULL, const Point3F *velocity=NULL); + +// Source +void alxSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value); +void alxSourcefv(AUDIOHANDLE handle, ALenum pname, ALfloat *values); +void alxSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3); +void alxSourcei(AUDIOHANDLE handle, ALenum pname, ALint value); +void alxSourceMatrixF(AUDIOHANDLE handle, const MatrixF *transform); + +void alxGetSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat *value); +void alxGetSourcefv(AUDIOHANDLE handle, ALenum pname, ALfloat *values); +void alxGetSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3); +void alxGetSourcei(AUDIOHANDLE handle, ALenum pname, ALint *value); + +inline void alxSourcePoint3F(AUDIOHANDLE handle, ALenum pname, const Point3F *value) +{ + alxSource3f(handle, pname, value->x, value->y, value->z); +} + +inline void alxSourceGetPoint3F(AUDIOHANDLE handle, ALenum pname, Point3F * value) +{ + alxGetSource3f(handle, pname, &value->x, &value->y, &value->z); +} + +// Listener +void alxListenerf(ALenum pname, ALfloat value); +void alxListenerfv(ALenum pname, ALfloat *values); +void alxListener3f(ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3); +void alxListeneri(ALenum pname, ALint value); +void alxListenerMatrixF(const MatrixF *transform); + +void alxGetListenerf(ALenum pname, ALfloat *value); +void alxGetListenerfv(ALenum pname, ALfloat *values); +void alxGetListener3f(ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3); +void alxGetListeneri(ALenum pname, ALint *value); + +inline void alxListenerPoint3F(ALenum pname, const Point3F *value) +{ + alxListener3f(pname, value->x, value->y, value->z); +} + +inline void alxListenerGetPoint3F(ALenum pname, Point3F * value) +{ + alxGetListener3f(pname, &value->x, &value->y, &value->z); +} + +// Environment +void alxEnvironmenti(ALenum pname, ALint value); +void alxEnvironmentf(ALenum pname, ALfloat value); +void alxGetEnvironmenti(ALenum pname, ALint * value); +void alxGetEnvironmentf(ALenum pname, ALfloat * value); + +void alxSetEnvironment(const AudioEnvironment * environment); +const AudioEnvironment * alxGetEnvironment(); + +// voice +struct SimVoiceStreamEvent; +struct SimVoiceEvent; + +void alxReceiveVoiceStream(SimVoiceStreamEvent *event); +void alxReceiveVoiceEvent(SimVoiceEvent *event); + +// misc +ALuint alxGetWaveLen(ALuint buffer); +bool alxIsValidHandle(AUDIOHANDLE handle); +bool alxIsPlaying(AUDIOHANDLE handle); +void alxUpdate(); + +#endif // _H_PLATFORMAUDIO_ diff --git a/platform/platformCPU.cc b/platform/platformCPU.cc new file mode 100644 index 0000000..9e44ee8 --- /dev/null +++ b/platform/platformCPU.cc @@ -0,0 +1,192 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +// $Id: platformCPU.cc,v 1.1 2002/03/17 04:27:07 jmquigs Exp $ + +#include "platform/platform.h" +#include "core/stringTable.h" + +enum +{ + BIT_FPU = (1<<0), + BIT_RDTSC = (1<<4), + BIT_MMX = (1<<23), + BIT_SSE = (1<<25), + BIT_3DNOW = (1<<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.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, Pentium family)" ); + break; + } + break; + + // Pentium Pro/II/II family + case 6: + // 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 10: + case 11: + pInfo.type = CPU_Intel_PentiumIII; + pInfo.name = StringTable->insert("Intel Pentium III"); + break; + default: + pInfo.type = CPU_Intel_PentiumPro; + pInfo.name = StringTable->insert( "Intel (unknown, Pentium Pro/II/III family)" ); + 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) + { + 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; + } + } +} diff --git a/platform/platformCPUInfo.asm b/platform/platformCPUInfo.asm new file mode 100644 index 0000000..ca695d7 --- /dev/null +++ b/platform/platformCPUInfo.asm @@ -0,0 +1,96 @@ +;----------------------------------------------------------------------------- +; V12 Engine +; +; Copyright (c) 2001 GarageGames.Com +; Portions Copyright (c) 2001 by Sierra Online, Inc. +;----------------------------------------------------------------------------- + + +segment .text + +; this is a nice macro Rick +; syntax: export_fn +%macro export_fn 1 + %ifdef LINUX + ; No underscore needed for ELF object files + global %1 + %1: + %else + global _%1 + _%1: + %endif +%endmacro + +; so is this +;%define arg(x) [esp+(x*4)] + +; void isCPUIDSupported(char *vendor, U32 *properties, U32 *processor); +export_fn isCPUIDSupported + +; mov ebp, esp + push ebp + push ebx + push edx + push ecx +; mov ecx, arg(1) +; mov edx, arg(2) +; mov eax, arg(3) + 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 + +; mov dword [vendor], ebx +; mov dword [vendor+4], edx +; mov dword [vendor+8], ecx + mov dword [esp+8], ebx + mov dword [esp+12], edx + mov dword [esp+16], ecx + popfd + pop ecx + pop edx + pop ebx + leave +; pop ebp +; mov esp, ebp + ret +;jz EXIT + ; get generic extended CPUID info + mov eax, 1 + cpuid ; eax=1, so cpuid queries feature information + + and eax, 0x0FF0 + mov [esp+20], eax ; just store the model bits + mov [esp+16], 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 [esp+16], edx +MAYBE_3DLATER: + + +EXIT: + popfd + pop ecx + pop edx + pop ebx + ret + diff --git a/platform/platformInput.h b/platform/platformInput.h new file mode 100644 index 0000000..30257ad --- /dev/null +++ b/platform/platformInput.h @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMINPUT_H_ +#define _PLATFORMINPUT_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + + +//------------------------------------------------------------------------------ +U8 TranslateOSKeyCode( U8 vcode ); + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +class InputDevice : public SimObject +{ + public: + struct ObjInfo + { + U16 mType; + U16 mInst; + S32 mMin, mMax; + }; + + protected: + char mName[30]; + + public: + const char* getDeviceName(); + virtual bool process() = NULL; +}; + + +//------------------------------------------------------------------------------ +inline const char* InputDevice::getDeviceName() +{ + return mName; +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +class InputManager : public SimGroup +{ + protected: + bool mEnabled; + + public: + bool isEnabled(); + + virtual bool enable() = NULL; + virtual void disable() = NULL; + + virtual void process() = NULL; +}; + + +//------------------------------------------------------------------------------ +inline bool InputManager::isEnabled() +{ + return mEnabled; +} + +enum KEY_STATE +{ + STATE_LOWER, + STATE_UPPER, + STATE_GOOFY +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +class Input +{ + protected: + static InputManager* smManager; + static bool smActive; + + public: + static void init(); + static void destroy(); + + static bool enable(); + static void disable(); + + static void activate(); + static void deactivate(); + static void reactivate(); + + 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(); + +#ifdef LOG_INPUT + static void log( const char* format, ... ); +#endif +}; + +#endif // _H_PLATFORMINPUT_ diff --git a/platform/platformMemory.cc b/platform/platformMemory.cc new file mode 100644 index 0000000..5f5dc64 --- /dev/null +++ b/platform/platformMemory.cc @@ -0,0 +1,1146 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/fileStream.h" +#include "console/console.h" +#include "platform/profiler.h" + +//-------------------------------------- Make sure we don't have the define set +#ifdef new +#undef new +#endif + +enum { + Allocated = 1 << 0, + Array = 1 << 1, + AllocatedGuard = 0xCEDEFEDE, + FreeGuard = 0x5555FFFF, + MaxAllocationAmount = 0xFFFFFFFF, + TreeNodeAllocCount = 2048, +}; + +enum { + Red = 0, + Black = 1 +}; + +static U32 MinPageSize = 8 * 1024 * 1024; + +//#define DEBUG_GUARD + +//--------------------------------------------------------------------------- + +namespace Memory +{ + +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 DEBUG_GUARD + U32 preguard[4]; +#endif + Header *next; + Header *prev; + U32 size; + U32 flags; +#ifdef DEBUG_GUARD + U32 unused[4]; + U32 postguard[4]; +#endif +}; + +struct AllocatedHeader +{ +#ifdef DEBUG_GUARD + U32 preguard[4]; +#endif + Header *next; + Header *prev; + U32 size; + U32 flags; + +#ifdef DEBUG_GUARD + // an allocated header will only have this stuff if debug_guard + U32 line; + U32 allocNum; + const char *fileName; + U32 realSize; + U32 postguard[4]; +#endif +}; + +struct FreeHeader +{ +#ifdef DEBUG_GUARD + U32 preguard[4]; +#endif + Header *next; + Header *prev; + U32 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 DEBUG_GUARD + U32 postguard[4]; +#endif +}; + +struct PageRecord +{ + U32 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; + +#ifdef DEBUG_GUARD +static void 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(); +} + +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 + +static void memoryError() +{ + // free all the pages + PageRecord *walk = gPageList; + while(walk) { + PageRecord *prev = walk->prevPage; + dRealFree(walk); + walk = prev; + } + Platform::AlertOK("Memory Error", "Error allocating virtual memory.\n"); + Platform::forceShutdown(-1); +} + +PageRecord *allocPage(U32 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 validateParentage() +{ + if(gFreeTreeRoot == NIL) + return; + validateParentageRecurse(gFreeTreeRoot); +} + +static void validateTree() +{ + if(gFreeTreeRoot == NIL) + return; + validateParentageRecurse(gFreeTreeRoot); + validateTreeRecurse(gFreeTreeRoot); +} + +static void validate() +{ + // 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 DEBUG_GUARD + checkGuard(walk, walk->flags & Allocated); +#endif + if(walk->prev != prev) + Platform::debugBreak(); + prev = walk; + if(walk->next && (U32(walk->next) != U32(walk) + sizeof(Header) + walk->size)) + Platform::debugBreak(); + } + } +} + +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; +} + +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; +} + +static void treeInsert(FreeHeader *fhdr) +{ +#ifdef 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(); +} + +static void treeRemove(FreeHeader *hdr) +{ +#ifdef 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(); +} + +static FreeHeader *treeFindSmallestGreaterThan(U32 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; +} + +static void check() +{ + //for (S32 i = 0; i < mNumUsedHeaders; i++) { + // MemoryHeader* probe = &mHeaderBase[i]; + // if (probe->flags & Allocated && probe->ptr->header != probe) + // Platform::debugBreak(); + //} +} + +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 DEBUG +ConsoleFunction(ValidateMemory, void, 1, 1, "ValidateMemory();") +{ + argc; argv; + validate(); +} +#endif + +ConsoleFunction(FreeMemoryDump, void, 1, 1, "FreeMemoryDump();") +{ + argc; 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: %f", + 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: %f", + fullMem.count, fullMem.maxDepth, fullMem.minDepth, F32(fullMem.depthTotal) / F32(fullMem.count)); +} + +#ifdef DEBUG_GUARD +static void initLog() +{ + static const char* sInitString = " --- INIT MEMORY LOG (ACTION): (FILE) (LINE) (SIZE) (ALLOCNUMBER) ---\r\n"; + + FileStream fws; + fws.open(gLogFilename, FileStream::Write); + fws.write(dStrlen(sInitString), sInitString); + fws.close(); +} + +static void logAlloc(const AllocatedHeader* hdr, S32 memSize) +{ + FileStream fws; + fws.open(gLogFilename, FileStream::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(); +} + +static void logRealloc(const AllocatedHeader* hdr, S32 memSize) +{ + FileStream fws; + fws.open(gLogFilename, FileStream::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(); +} + +static void logFree(const AllocatedHeader* hdr) +{ + FileStream fws; + fws.open(gLogFilename, FileStream::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 + +void enableLogging(const char* fileName) +{ + dStrcpy(gLogFilename, fileName); + gEnableLogging = true; +} + +void disableLogging() +{ + gLogFilename[0] = '\0'; + gEnableLogging = false; +} + +static void shutdown() +{ + // write out leaks and such + + const U32 maxNumLeaks = 1024; + U32 numLeaks = 0; + + PageRecord * walk; +#ifdef DEBUG_GUARD + AllocatedHeader* pLeaks[maxNumLeaks]; + for (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", FileStream::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 + + walk = gPageList; + while(walk) { + PageRecord *prev = walk->prevPage; + dRealFree(walk); + walk = prev; + } +} + +static Header *allocMemPage(U32 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 DEBUG_GUARD + setGuard(rec, true); +#endif + return rec; +} + +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 DEBUG_GUARD + setGuard((Header *) newHeader, true); +#endif + treeInsert(newHeader); + } +} + +static void* alloc(U32 size, bool array, const char* fileName, const U32 line) +{ + fileName, line; + AssertFatal(size < MaxAllocationAmount, "Size error."); + //validate(); + if (size == 0) + return NULL; + PROFILE_START(MemoryAlloc); + +#ifdef 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 DEBUG_GUARD + retHeader->line = line; + retHeader->fileName = fileName; + retHeader->allocNum = gCurrAlloc; + retHeader->realSize = size; + if (gEnableLogging) + logAlloc(retHeader, size); +#endif + if(gCurrAlloc == gBreakAlloc && gBreakAlloc != 0xFFFFFFFF) + Platform::debugBreak(); + gCurrAlloc++; + PROFILE_END(); + //validate(); + return basePtr; +} + +static void free(void* mem, bool array) +{ + //validate(); + if (!mem) + return; + PROFILE_START(MemoryFree); + AllocatedHeader *hdr = ((AllocatedHeader *)mem) - 1; + + AssertFatal(hdr->flags & Allocated, avar("Not an allocated block!")); + AssertFatal(bool(hdr->flags & Array) == array, avar("Array alloc mismatch. ")); + +#ifdef DEBUG_GUARD + if (gEnableLogging) + logFree(hdr); +#endif + + hdr->flags = 0; + + // fill the block with the fill value + +#ifdef DEBUG + dMemset(mem, 0xCE, hdr->size); +#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 hrd 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(); +} + + +static void* realloc(void* mem,int size) +{ + //validate(); + if (!size) { + free(mem, false); + return NULL; + } + if(!mem) + return alloc(size, false, NULL, 0); + + AllocatedHeader* hdr = ((AllocatedHeader *)mem) - 1; + + AssertFatal((hdr->flags & Allocated) == Allocated, "Bad block flags."); + + size = (size + 0xF) & ~0xF; + + U32 oldSize = hdr->size; + if(oldSize == size) + return mem; + PROFILE_START(MemoryRealloc); + + FreeHeader *next = (FreeHeader *) hdr->next; + +#ifdef DEBUG_GUARD + hdr->realSize = size; + if (gEnableLogging) + logRealloc(hdr, size); +#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(); + return mem; + } + else if(size < oldSize) + { + checkUnusedAlloc((FreeHeader *) hdr, size); + PROFILE_END(); + return mem; + } + void* ret = alloc(size, false, NULL, 0); + dMemcpy(ret, mem, oldSize); + free(mem, false); + PROFILE_END(); + return ret; +} + + +U32 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 DEBUG_GUARD +ConsoleFunction(dumpMemSnapshot,void,2,2,"dumpMemSnapshot(fileName);") +{ + argc; + FileStream fws; + fws.open(argv[1], FileStream::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\t%d\t%d\t%d\r\n", + pah->fileName != NULL ? pah->fileName : "Undetermined", + pah->line, pah->realSize, pah->allocNum); + fws.write(dStrlen(buffer), buffer); + } + } + + fws.close(); +} +#endif + +U32 getMemoryAllocated() +{ + return 0; +} + +void setBreakAlloc(U32 breakAlloc) +{ + gBreakAlloc = breakAlloc; +} + +} // 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; +} + +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include + +#ifndef NO_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(U32 in_size, const char* fileName, const U32 line) +{ + return Memory::alloc(in_size, false, fileName, line); +} + +void dFree(void* in_pFree) +{ + Memory::free(in_pFree, false); +} + +void* dRealloc(void* in_pResize, U32 in_size) +{ + return Memory::realloc(in_pResize, in_size); +} + +#else // #ifndef NO_MEMORY_MANAGER + +// Don't manage our own memory + +void* dMalloc_r(U32 in_size, const char* fileName, const U32 line) +{ + return malloc(in_size); +} + +void dFree(void* in_pFree) +{ + free(in_pFree); +} + +void* dRealloc(void* in_pResize, U32 in_size) +{ + return realloc(in_pResize,in_size); +} + +#endif // #else diff --git a/platform/platformMutex.h b/platform/platformMutex.h new file mode 100644 index 0000000..3d97acf --- /dev/null +++ b/platform/platformMutex.h @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMMUTEX_H_ +#define _PLATFORMMUTEX_H_ + +struct Mutex +{ + static void* createMutex( void ); + static void destroyMutex( void* ); + static void lockMutex( void* ); + static void unlockMutex( void* ); +}; + +#endif diff --git a/platform/platformRedBook.cc b/platform/platformRedBook.cc new file mode 100644 index 0000000..d3e2a8d --- /dev/null +++ b/platform/platformRedBook.cc @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#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() +{ + installConsoleCommands(); +} + +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(2, "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 +//------------------------------------------------------------------------------ +static bool cOpen(SimObject *, S32 argc, const char ** argv) +{ + if(argc == 1) + return(RedBook::open(RedBook::getDeviceName(0))); + else + return(RedBook::open(argv[1])); +} + +static bool cClose(SimObject *, S32, const char **) +{ + return(RedBook::close()); +} + +static bool cPlay(SimObject *, S32, const char ** argv) +{ + return(RedBook::play(dAtoi(argv[1]))); +} + +static bool cStop(SimObject *, S32, const char **) +{ + return(RedBook::stop()); +} + +static S32 cGetTrackCount(SimObject *, S32, const char **) +{ + U32 trackCount; + if(!RedBook::getTrackCount(&trackCount)) + return(0); + return(trackCount); +} + +static F32 cGetVolume(SimObject *, S32, const char **) +{ + F32 vol; + if(!RedBook::getVolume(&vol)) + return(0.f); + else + return(vol); +} + +static bool cSetVolume(SimObject *, S32, const char ** argv) +{ + return(RedBook::setVolume(dAtof(argv[1]))); +} + +static S32 cGetDeviceCount(SimObject *, S32, const char **) +{ + return(RedBook::getDeviceCount()); +} + +static const char * cGetDeviceName(SimObject *, S32, const char ** argv) +{ + return(RedBook::getDeviceName(dAtoi(argv[1]))); +} + +static const char * cGetLastError(SimObject *, S32, const char **) +{ + return(RedBook::getLastError()); +} + +void RedBook::installConsoleCommands() +{ + Con::addCommand("redbookOpen", cOpen, "redbookOpen()", 1, 2); + Con::addCommand("redbookClose", cClose, "redbookClose()", 1, 1); + Con::addCommand("redbookPlay", cPlay, "redbookPlay(track)", 2, 2); + Con::addCommand("redbookStop", cStop, "redbookStop()", 1, 1); + Con::addCommand("redbookGetTrackCount", cGetTrackCount, "redbookGetTrackCount()", 1, 1); + Con::addCommand("redbookGetVolume", cGetVolume, "redbookGetVolume", 1, 1); + Con::addCommand("redbookSetVolume", cSetVolume, "redbookSetVolume", 2, 2); + + Con::addCommand("redbookGetDeviceCount", cGetDeviceCount, "redbookGetDeviceCount()", 1, 1); + Con::addCommand("redbookGetDeviceName", cGetDeviceName, "redbookGetDeviceName(idx)", 2, 2); + Con::addCommand("redbookGetLastError", cGetLastError, "redbookGetLastError()", 1, 1); +} diff --git a/platform/platformRedBook.h b/platform/platformRedBook.h new file mode 100644 index 0000000..47117fd --- /dev/null +++ b/platform/platformRedBook.h @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMREDBOOK_H_ +#define _PLATFORMREDBOOK_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/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[]; + + static void installConsoleCommands(); + + 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/platformSemaphore.h b/platform/platformSemaphore.h new file mode 100644 index 0000000..8f73387 --- /dev/null +++ b/platform/platformSemaphore.h @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMSEMAPHORE_H_ +#define _PLATFORMSEMAPHORE_H_ + +#ifndef _TYPES_H_ +#include "Platform/types.h" +#endif + +struct Semaphore +{ + static void * createSemaphore(U32 initialCount = 1); + static void destroySemaphore(void * semaphore); + static bool acquireSemaphore(void * semaphore, bool block = true); + static void releaseSemaphore(void * semaphore); + + inline static bool P(void * semaphore, bool block = true) {return(acquireSemaphore(semaphore, block));} + inline static void V(void * semaphore) {releaseSemaphore(semaphore);} +}; + +#endif diff --git a/platform/platformThread.h b/platform/platformThread.h new file mode 100644 index 0000000..cbb1217 --- /dev/null +++ b/platform/platformThread.h @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMTHREAD_H_ +#define _PLATFORMTHREAD_H_ + +#ifndef _TYPES_H_ +#include "Platform/types.h" +#endif + +typedef void (*ThreadRunFunction)(S32); + +class Thread +{ + protected: + void * mData; + void start(); + + public: + Thread(ThreadRunFunction func = 0, S32 arg = 0, bool start_thread = true); + virtual ~Thread(); + + bool join(); + + virtual void run(S32 arg = 0); + + bool isAlive(); +}; + +#endif diff --git a/platform/platformVideo.cc b/platform/platformVideo.cc new file mode 100644 index 0000000..d5d1526 --- /dev/null +++ b/platform/platformVideo.cc @@ -0,0 +1,659 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platformVideo.h" +#include "GUI/guiCanvas.h" +#include "console/console.h" +#include "Platform/gameInterface.h" + +extern void GameDeactivate( bool noRender ); +extern void GameReactivate(); + +// Static class data: +Vector Video::smDeviceList; +DisplayDevice* Video::smCurrentDevice; +bool Video::smCritical = false; +bool Video::smNeedResurrect = false; + +Resolution DisplayDevice::smCurrentRes; +bool DisplayDevice::smIsFullScreen; + +//-------------------------------------------------------------------------- +static bool cSetDisplayDevice( SimObject*, S32 argc, const char** argv ) +{ + Resolution currentRes = Video::getResolution(); + + U32 width = ( argc > 2 ) ? dAtoi( argv[2] ) : currentRes.w; + U32 height = ( argc > 3 ) ? dAtoi( argv[3] ) : currentRes.h; + U32 bpp = ( argc > 4 ) ? dAtoi( argv[4] ) : currentRes.bpp; + bool fullScreen = ( argc > 5 ) ? dAtob( argv[5] ) : Video::isFullScreen(); + + return( Video::setDevice( argv[1], width, height, bpp, fullScreen ) ); +} + + +//-------------------------------------------------------------------------- +static bool cSetScreenMode( SimObject*, S32, const char** argv ) +{ + return( Video::setScreenMode( dAtoi( argv[1] ), dAtoi( argv[2] ), dAtoi( argv[3] ), dAtob( argv[4] ) ) ); +} + + +//------------------------------------------------------------------------------ +static bool cFullScreenToggle( SimObject*, S32, const char** ) +{ + return( Video::toggleFullScreen() ); +} + + +//------------------------------------------------------------------------------ +static bool cIsFullScreen( SimObject*, S32, const char** ) +{ + return( Video::isFullScreen() ); +} + + +//-------------------------------------------------------------------------- +static bool cSwitchBitDepth( SimObject*, S32, const char** ) +{ + if ( !Video::isFullScreen() ) + { + Con::warnf( ConsoleLogEntry::General, "Can only switch bit depth in full-screen mode!" ); + return( false ); + } + + Resolution res = Video::getResolution(); + return( Video::setResolution( res.w, res.h, ( res.bpp == 16 ? 32 : 16 ) ) ); +} + + +//-------------------------------------------------------------------------- +static bool cPrevRes( SimObject*, S32, const char** ) +{ + return( Video::prevRes() ); +} + + +//-------------------------------------------------------------------------- +static bool cNextRes( SimObject*, S32, const char** ) +{ + return( Video::nextRes() ); +} + + +//-------------------------------------------------------------------------- +static const char* cGetResolution( SimObject*, S32, const char** ) +{ + static char resBuf[16]; + Resolution res = Video::getResolution(); + dSprintf( resBuf, sizeof(resBuf), "%d %d %d", res.w, res.h, res.bpp ); + return( resBuf ); +} + + +//-------------------------------------------------------------------------- +static bool cSetResolution( SimObject*, S32 argc, const char** argv ) +{ + U32 width = dAtoi( argv[1] ); + U32 height = dAtoi( argv[2] ); + U32 bpp = 0; + if ( argc == 4 ) + { + bpp = dAtoi( argv[3] ); + if ( bpp != 16 && bpp != 32 ) + bpp = 0; + } + + return( Video::setResolution( width, height, bpp ) ); +} + +//------------------------------------------------------------------------------ +static const char* cVideoGetDeviceList( SimObject*, S32, const char** ) +{ + return( Video::getDeviceList() ); +} + + +//------------------------------------------------------------------------------ +static const char* cVideoGetResList( SimObject*, S32, const char** argv ) +{ + DisplayDevice* device = Video::getDevice( argv[1] ); + if ( !device ) + { + Con::warnf( ConsoleLogEntry::General, "\"%s\" display device not found!", argv[1] ); + return( NULL ); + } + + return( device->getResolutionList() ); +} + + +//------------------------------------------------------------------------------ +static const char* cVideoGetDriverInfo( SimObject*, S32, const char** ) +{ + return( Video::getDriverInfo() ); +} + + +//------------------------------------------------------------------------------ +static bool cIsDeviceFullScreenOnly( SimObject*, S32, const char** argv ) +{ + DisplayDevice* device = Video::getDevice( argv[1] ); + if ( !device ) + { + Con::warnf( ConsoleLogEntry::General, "\"%s\" display device not found!", argv[1] ); + return( false ); + } + + return( device->isFullScreenOnly() ); +} + + +//------------------------------------------------------------------------------ +static F32 sgOriginalGamma = -1.0; +static F32 sgGammaCorrection = 0.0; + +ConsoleFunction(videoSetGammaCorrection, void, 2, 2, "setGammaCorrection(gamma);") +{ + argc; + F32 g = mClampF(dAtof(argv[1]),0.0,1.0); + F32 d = -(g - 0.5); + + if (d != sgGammaCorrection && + (sgOriginalGamma != -1.0 || Video::getGammaCorrection(sgOriginalGamma))) + Video::setGammaCorrection(sgOriginalGamma+d); + sgGammaCorrection = d; +} + +//------------------------------------------------------------------------------ +void Video::init() +{ + destroy(); + + // Add console commands: + Con::addCommand( "setDisplayDevice", cSetDisplayDevice, "setDisplayDevice( deviceName{, width{, height{, bpp{, fullScreen}}}}} );", 2, 6 ); + Con::addCommand( "setScreenMode", cSetScreenMode, "setScreenMode( width, height, bpp, fullScreen );", 5, 5 ); + Con::addCommand( "toggleFullScreen", cFullScreenToggle, "toggleFullScreen();", 1, 1 ); + Con::addCommand( "isFullScreen", cIsFullScreen, "isFullScreen();", 1, 1 ); + Con::addCommand( "switchBitDepth", cSwitchBitDepth, "switchBitDepth();", 1, 1 ); + Con::addCommand( "prevResolution", cPrevRes, "prevResolution();", 1, 1 ); + Con::addCommand( "nextResolution", cNextRes, "nextResolution();", 1, 1 ); + Con::addCommand( "getResolution", cGetResolution, "getResolution();", 1, 1 ); + Con::addCommand( "setResolution", cSetResolution, "setResolution( width, height, bpp );", 3, 4 ); + Con::addCommand( "setRes", cSetResolution, "setRes( width, height, bpp );", 3, 4 ); + Con::addCommand( "getDisplayDeviceList", cVideoGetDeviceList, "getDisplayDeviceList();", 1, 1 ); + Con::addCommand( "getResolutionList", cVideoGetResList, "getResolutionList( deviceName );", 2, 2 ); + Con::addCommand( "getVideoDriverInfo", cVideoGetDriverInfo, "getVideoDriverInfo();", 1, 1 ); + Con::addCommand( "isDeviceFullScreenOnly", cIsDeviceFullScreenOnly, "isDeviceFullScreenOnly( deviceName );", 2, 2 ); +} + + +//------------------------------------------------------------------------------ +void Video::destroy() +{ + if ( smCurrentDevice ) + { + smCritical = true; + smCurrentDevice->shutdown(); + smCritical = false; + } + + smCurrentDevice = NULL; + + for ( U32 i = 0; i < smDeviceList.size(); i++ ) + delete smDeviceList[i]; + + smDeviceList.clear(); +} + + +//------------------------------------------------------------------------------ +bool Video::installDevice( DisplayDevice *dev ) +{ + if ( dev ) + { + smDeviceList.push_back( dev ); + return true; + } + return false; +} + + +//------------------------------------------------------------------------------ +bool Video::setDevice( const char *renderName, U32 width, U32 height, U32 bpp, bool fullScreen ) +{ + S32 deviceIndex = NO_DEVICE; + S32 iOpenGL = -1; + S32 iD3D = -1; + + for ( S32 i = 0; i < smDeviceList.size(); i++ ) + { + if ( dStrcmp( smDeviceList[i]->mDeviceName, renderName ) == 0 ) + deviceIndex = i; + + if ( dStrcmp( smDeviceList[i]->mDeviceName, "OpenGL" ) == 0 ) + iOpenGL = i; + if ( dStrcmp( smDeviceList[i]->mDeviceName, "D3D" ) == 0 ) + iD3D = i; + } + + if ( deviceIndex == NO_DEVICE ) + { + Con::warnf( ConsoleLogEntry::General, "\"%s\" display device not found!", renderName ); + return false; + } + + // Change the display device: + if ( smDeviceList[deviceIndex] != NULL ) + { + if (smCurrentDevice && smCurrentDevice != smDeviceList[deviceIndex]) + { + Con::printf( "Deactivating the previous display device..." ); + Game->textureKill(); + smNeedResurrect = true; + smCurrentDevice->shutdown(); + } + + Con::printf( "Activating the %s display device...", renderName ); + smCurrentDevice = smDeviceList[deviceIndex]; + + smCritical = true; + bool result = smCurrentDevice->activate( width, height, bpp, fullScreen ); + smCritical = false; + + if ( result ) + { + if (smNeedResurrect) + { + Game->textureResurrect(); + smNeedResurrect = false; + } + if (sgOriginalGamma != -1.0 || Video::getGammaCorrection(sgOriginalGamma)) + Video::setGammaCorrection(sgOriginalGamma + sgGammaCorrection); + Con::evaluate("resetCanvas();"); + } + + if (iOpenGL != -1 && !Con::getBoolVariable("$pref::Video::allowOpenGL")) + { + // change to D3D, delete OpenGL in the recursive call + if (dStrcmp(renderName,"OpenGL") == 0) + { + U32 w, h, d; + + dSscanf(Con::getVariable("$pref::Video::resolution"), "%d %d %d", &w, &h, &d); + + return setDevice("D3D",w,h,d,Con::getBoolVariable("$pref::Video::fullScreen",true)); + } + else + { + delete smDeviceList[iOpenGL]; + smDeviceList.erase(iOpenGL); + } + } + else if (iD3D != -1 && !Con::getBoolVariable("$pref::Video::allowD3D")) + { + // change to OpenGL, delete D3D in the recursive call + if (dStrcmp(renderName,"D3D") == 0) + { + U32 w, h, d; + + dSscanf(Con::getVariable("$pref::Video::resolution"), "%d %d %d", &w, &h, &d); + + return setDevice("OpenGL",w,h,d,Con::getBoolVariable("$pref::Video::fullScreen",true)); + } + else + { + delete smDeviceList[iD3D]; + smDeviceList.erase(iD3D); + } + } + else if (iD3D != -1 && + dStrcmp(renderName,"OpenGL") == 0 && + !Con::getBoolVariable("$pref::Video::preferOpenGL") && + !Con::getBoolVariable("$pref::Video::appliedPref")) + { + U32 w, h, d; + + dSscanf(Con::getVariable("$pref::Video::resolution"), "%d %d %d", &w, &h, &d); + Con::setBoolVariable("$pref::Video::appliedPref", true); + + return setDevice("D3D",w,h,d,Con::getBoolVariable("$pref::Video::fullScreen",true)); + } + else + Con::setBoolVariable("$pref::Video::appliedPref", true); + + // The video mode activate may have failed above, return that status + return( result ); + } + + return( false ); +} + + +//------------------------------------------------------------------------------ +bool Video::setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen ) +{ + if ( smCurrentDevice ) + { + smCritical = true; + bool result = smCurrentDevice->setScreenMode( width, height, bpp, fullScreen ); + smCritical = false; + + return( result ); + } + + return( false ); +} + + +//------------------------------------------------------------------------------ +void Video::deactivate() +{ + if ( smCritical ) return; + + GameDeactivate( DisplayDevice::isFullScreen() ); + if ( smCurrentDevice && DisplayDevice::isFullScreen() ) + { + smCritical = true; + + Game->textureKill(); + smCurrentDevice->shutdown(); + + Platform::minimizeWindow(); + smCritical = false; + } +} + +//------------------------------------------------------------------------------ +void Video::reactivate() +{ + if ( smCritical ) return; + + if ( smCurrentDevice && DisplayDevice::isFullScreen() ) + { + Resolution res = DisplayDevice::getResolution(); + + smCritical = true; + smCurrentDevice->activate(res.w,res.h,res.bpp,DisplayDevice::isFullScreen()); + Game->textureResurrect(); + + smCritical = false; + if (sgOriginalGamma != -1.0) + Video::setGammaCorrection(sgOriginalGamma + sgGammaCorrection); + } + GameReactivate(); +} + + +//------------------------------------------------------------------------------ +bool Video::setResolution( U32 width, U32 height, U32 bpp ) +{ + if ( smCurrentDevice ) + { + if ( bpp == 0 ) + bpp = DisplayDevice::getResolution().bpp; + + smCritical = true; + bool result = smCurrentDevice->setResolution( width, height, bpp ); + smCritical = false; + + return( result ); + } + return( false ); +} + + +//------------------------------------------------------------------------------ +bool Video::toggleFullScreen() +{ + if ( smCurrentDevice ) + { + smCritical = true; + bool result = smCurrentDevice->toggleFullScreen(); + smCritical = false; + + return( result ); + } + return( false ); +} + + +//------------------------------------------------------------------------------ +DisplayDevice* Video::getDevice( const char* renderName ) +{ + for ( S32 i = 0; i < smDeviceList.size(); i++ ) + { + if ( dStrcmp( smDeviceList[i]->mDeviceName, renderName ) == 0 ) + return( smDeviceList[i] ); + } + + return( NULL ); +} + + +//------------------------------------------------------------------------------ +bool Video::prevRes() +{ + if ( smCurrentDevice ) + { + smCritical = true; + bool result = smCurrentDevice->prevRes(); + smCritical = false; + + return( result ); + } + return( false ); +} + + +//------------------------------------------------------------------------------ +bool Video::nextRes() +{ + if ( smCurrentDevice ) + { + smCritical = true; + bool result = smCurrentDevice->nextRes(); + smCritical = false; + + return( result ); + } + return( false ); +} + + +//------------------------------------------------------------------------------ +Resolution Video::getResolution() +{ + return DisplayDevice::getResolution(); +} + + +//------------------------------------------------------------------------------ +const char* Video::getDeviceList() +{ + U32 deviceCount = smDeviceList.size(); + if ( deviceCount > 0 ) // It better be... + { + U32 strLen = 0, i; + for ( i = 0; i < deviceCount; i++ ) + strLen += ( dStrlen( smDeviceList[i]->mDeviceName ) + 1 ); + + char* returnString = Con::getReturnBuffer( strLen ); + dStrcpy( returnString, smDeviceList[0]->mDeviceName ); + for ( i = 1; i < deviceCount; i++ ) + { + dStrcat( returnString, "\t" ); + dStrcat( returnString, smDeviceList[i]->mDeviceName ); + } + + return( returnString ); + } + + return( NULL ); +} + + +//------------------------------------------------------------------------------ +const char* Video::getResolutionList() +{ + if ( smCurrentDevice ) + return smCurrentDevice->getResolutionList(); + else + return NULL; +} + + +//------------------------------------------------------------------------------ +const char* Video::getDriverInfo() +{ + if ( smCurrentDevice ) + return smCurrentDevice->getDriverInfo(); + else + return NULL; +} + + +//------------------------------------------------------------------------------ +bool Video::isFullScreen() +{ + return DisplayDevice::isFullScreen(); +} + + +//------------------------------------------------------------------------------ +void Video::swapBuffers() +{ + if ( smCurrentDevice ) + smCurrentDevice->swapBuffers(); +} + + +//------------------------------------------------------------------------------ +bool Video::getGammaCorrection(F32 &g) +{ + if (smCurrentDevice) + return smCurrentDevice->getGammaCorrection(g); + + return false; +} + + +//------------------------------------------------------------------------------ +bool Video::setGammaCorrection(F32 g) +{ + if (smCurrentDevice) + return smCurrentDevice->setGammaCorrection(g); + + return false; +} + +//------------------------------------------------------------------------------ +bool Video::setVerticalSync( bool on ) +{ + if ( smCurrentDevice ) + return( smCurrentDevice->setVerticalSync( on ) ); + + return( false ); +} + +ConsoleFunction( setVerticalSync, bool, 2, 2, "setVerticalSync( )" ) +{ + argc; + return( Video::setVerticalSync( dAtob( argv[1] ) ) ); +} + +//------------------------------------------------------------------------------ +DisplayDevice::DisplayDevice() +{ + mDeviceName = NULL; +} + + +//------------------------------------------------------------------------------ +void DisplayDevice::init() +{ + smCurrentRes = Resolution( 0, 0, 0 ); + smIsFullScreen = false; +} + + +//------------------------------------------------------------------------------ +bool DisplayDevice::prevRes() +{ + U32 resIndex; + for ( resIndex = mResolutionList.size() - 1; resIndex > 0; resIndex-- ) + { + if ( mResolutionList[resIndex].bpp == smCurrentRes.bpp + && mResolutionList[resIndex].w <= smCurrentRes.w + && mResolutionList[resIndex].h != smCurrentRes.h ) + break; + } + + if ( mResolutionList[resIndex].bpp == smCurrentRes.bpp ) + return( Video::setResolution( mResolutionList[resIndex].w, mResolutionList[resIndex].h, mResolutionList[resIndex].bpp ) ); + + return( false ); +} + + +//------------------------------------------------------------------------------ +bool DisplayDevice::nextRes() +{ + U32 resIndex; + for ( resIndex = 0; resIndex < mResolutionList.size() - 1; resIndex++ ) + { + if ( mResolutionList[resIndex].bpp == smCurrentRes.bpp + && mResolutionList[resIndex].w >= smCurrentRes.w + && mResolutionList[resIndex].h != smCurrentRes.h ) + break; + } + + if ( mResolutionList[resIndex].bpp == smCurrentRes.bpp ) + return( Video::setResolution( mResolutionList[resIndex].w, mResolutionList[resIndex].h, mResolutionList[resIndex].bpp ) ); + + return( false ); +} + + +//------------------------------------------------------------------------------ +// This function returns a string containing all of the available resolutions for this device +// in the format " ", separated by tabs. +// +const char* DisplayDevice::getResolutionList() +{ + if (Con::getBoolVariable("$pref::Video::clipHigh", false)) + for (S32 i = mResolutionList.size()-1; i >= 0; --i) + if (mResolutionList[i].w > 1152 || mResolutionList[i].h > 864) + mResolutionList.erase(i); + + if (Con::getBoolVariable("$pref::Video::only16", false)) + for (S32 i = mResolutionList.size()-1; i >= 0; --i) + if (mResolutionList[i].bpp == 32) + mResolutionList.erase(i); + + U32 resCount = mResolutionList.size(); + if ( resCount > 0 ) + { + char* tempBuffer = new char[resCount * 15]; + tempBuffer[0] = 0; + for ( U32 i = 0; i < resCount; i++ ) + { + char newString[15]; + dSprintf( newString, sizeof( newString ), "%d %d %d\t", mResolutionList[i].w, mResolutionList[i].h, mResolutionList[i].bpp ); + dStrcat( tempBuffer, newString ); + } + tempBuffer[dStrlen( tempBuffer ) - 1] = 0; + + char* returnString = Con::getReturnBuffer( dStrlen( tempBuffer ) + 1 ); + dStrcpy( returnString, tempBuffer ); + delete [] tempBuffer; + + return returnString; + } + + return NULL; +} diff --git a/platform/platformVideo.h b/platform/platformVideo.h new file mode 100644 index 0000000..6b19aca --- /dev/null +++ b/platform/platformVideo.h @@ -0,0 +1,159 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMVIDEO_H_ +#define _PLATFORMVIDEO_H_ + +#ifndef _TYPES_H_ +#include "Platform/types.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _PLATFORMASSERT_H_ +#include "Platform/platformAssert.h" +#endif + +enum devices +{ + NO_DEVICE = -1, + OPENGL_DEVICE, + VOODOO2_DEVICE, + N_DEVICES +}; + +struct Resolution; +class DisplayDevice; + + +class Video +{ +private: + static Vector smDeviceList; + static DisplayDevice* smCurrentDevice; + static bool smCritical; + +public: + static bool smNeedResurrect; + + static void init(); // enumerate all the devices + static void destroy(); // clean up and shut down + static bool installDevice( DisplayDevice *dev ); + static bool setDevice( const char *renderName, U32 width, U32 height, U32 bpp, bool fullScreen ); // set the current display device + static bool setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen ); + static void deactivate(); // deactivate current display device + static void reactivate(); // reactivate current display device + static bool setResolution( U32 width, U32 height, U32 bpp ); // set the current resolution + static bool toggleFullScreen(); // toggle full screen mode + static DisplayDevice* getDevice( const char* renderName ); + static const char* getDeviceList(); // get a tab-separated list of all the installed display devices + static const char* getResolutionList(); // get a tab-separated list of all the available resolutions for the current device + static const char* getDriverInfo(); // get info about the current display device driver + static bool prevRes(); // switch to the next smaller available resolution with the same bit depth + static bool nextRes(); // switch to the next larger available resolution with the same bit depth + static Resolution getResolution(); // get the current resolution + //static GFXSurface *getSurface(); // get a renderable surface (can return NULL if the window is minimized) + static bool isFullScreen(); // return the current screen state + static void swapBuffers(); // page flip + static bool getGammaCorrection(F32 &g); // get gamma correction + static bool setGammaCorrection(F32 g); // set gamma correction + static bool setVerticalSync( bool on ); // enable/disable vertical sync +}; + + +struct Resolution +{ + U32 w, h, bpp; + + Resolution( U32 _w = 0, U32 _h = 0, U32 _bpp = 0 ) + { + w = _w; + h = _h; + bpp = _bpp; + } + + bool operator==( const Resolution& otherRes ) const + { + return ( ( w == otherRes.w ) && ( h == otherRes.h ) && ( bpp == otherRes.bpp ) ); + } + + void operator=( const Resolution& otherRes ) + { + w = otherRes.w; + h = otherRes.h; + bpp = otherRes.bpp; + } +}; + + +class DisplayDevice +{ + public: + const char* mDeviceName; + + protected: + static Resolution smCurrentRes; + static bool smIsFullScreen; + + Vector mResolutionList; + bool mFullScreenOnly; + + public: + DisplayDevice(); + + virtual void initDevice() = NULL; + virtual bool activate( U32 width, U32 height, U32 bpp, bool fullScreen ) = NULL; + virtual void shutdown() = NULL; + + virtual bool setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen, bool forceIt = false, bool repaint = true ) = NULL; + virtual bool setResolution( U32 width, U32 height, U32 bpp ); + virtual bool toggleFullScreen(); + virtual void swapBuffers() = NULL; + virtual const char* getDriverInfo() = NULL; + virtual bool getGammaCorrection(F32 &g) = NULL; + virtual bool setGammaCorrection(F32 g) = NULL; + virtual bool setVerticalSync( bool on ) = NULL; + + bool prevRes(); + bool nextRes(); + const char* getResolutionList(); + bool isFullScreenOnly() { return( mFullScreenOnly ); } + + static void init(); + static Resolution getResolution(); + static bool isFullScreen(); +}; + + +//------------------------------------------------------------------------------ +inline bool DisplayDevice::setResolution( U32 width, U32 height, U32 bpp ) +{ + return setScreenMode( width, height, bpp, smIsFullScreen ); +} + + +//------------------------------------------------------------------------------ +inline bool DisplayDevice::toggleFullScreen() +{ + return setScreenMode( smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp, !smIsFullScreen ); +} + + +//------------------------------------------------------------------------------ +inline Resolution DisplayDevice::getResolution() +{ + return smCurrentRes; +} + + +//------------------------------------------------------------------------------ +inline bool DisplayDevice::isFullScreen() +{ + return smIsFullScreen; +} + +#endif // _H_PLATFORMVIDEO diff --git a/platform/profiler.cc b/platform/profiler.cc new file mode 100644 index 0000000..e84d427 --- /dev/null +++ b/platform/profiler.cc @@ -0,0 +1,538 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/profiler.h" +#include "core/stringTable.h" +#include // gotta use malloc and free directly +#include "console/console.h" +#include "core/tVector.h" +#include "core/fileStream.h" + +#ifdef ENABLE_PROFILER +ProfilerRootData *ProfilerRootData::sRootList = NULL; +Profiler *gProfiler = NULL; + +#ifdef _WIN32 +// 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; +} + +#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; + mRootProfilerData = mCurrentProfilerData; + + for(U32 i = 0; i < ProfilerData::HashTableSize; i++) + mCurrentProfilerData->mChildHash[i] = 0; + + mProfileList = NULL; + + mEnabled = false; + mStackDepth = 0; + mNextEnable = false; + 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) + { + ProfilerData *next = mProfileList->mNextProfilerData; + 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(); + } + } +} + +void Profiler::hashPush(ProfilerRootData *root) +{ + mStackDepth++; + 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; + } + } + 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() +{ + mStackDepth--; + AssertFatal(mStackDepth >= 0, "Stack underflow in profiler. You may have mismatched PROFILE_START and PROFILE_ENDs"); + if(mEnabled) + { + if(mCurrentProfilerData->mSubDepth) + { + mCurrentProfilerData->mSubDepth--; + return; + } + 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); + mEnabled = mNextEnable; + } +} + +static S32 QSORT_CALLBACK rootDataCompare(const void *s1, const void *s2) +{ + const ProfilerRootData *r1 = *((ProfilerRootData **) s1); + const ProfilerRootData *r2 = *((ProfilerRootData **) s2); + return (r2->mTotalTime - r2->mSubTime) - (r1->mTotalTime - r1->mSubTime); +} + +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"); + + U32 depth = 0; + 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, FileStream::Write); + AssertFatal(success, "Oh, fuck."); + 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); + + U32 depth = 0; + 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; + } + } +} + +ConsoleFunction(profilerMarkerEnable, void, 3, 3, "profilerEnable(markerName, true/false);") +{ + argc; + if(gProfiler) + gProfiler->enableMarker(argv[1], dAtob(argv[2])); +} + +ConsoleFunction(profilerEnable, void, 2, 2, "profilerEnable(true/false);") +{ + argc; + if(gProfiler) + gProfiler->enable(dAtob(argv[1])); +} + +ConsoleFunction(profilerDump, void, 1, 1, "profilerDump();") +{ + argc; argv; + if(gProfiler) + gProfiler->dumpToConsole(); +} + +ConsoleFunction(profilerDumpToFile, void, 2, 2, "profilerDumpToFile(filename);") +{ + argc; argv; + if(gProfiler) + gProfiler->dumpToFile(argv[1]); +} + +#endif diff --git a/platform/profiler.h b/platform/profiler.h new file mode 100644 index 0000000..6d3c4fb --- /dev/null +++ b/platform/profiler.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROFILER_H_ +#define _PROFILER_H_ + +#ifdef ENABLE_PROFILER + +struct ProfilerData; +struct ProfilerRootData; + +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(); + + void reset(); + void dumpToConsole(); + void dumpToFile(const char*); + void enable(bool enabled); + void hashPush(ProfilerRootData *data); + void hashPop(); + void enableMarker(const char *marker, bool enabled); +}; + +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; +}; + + +#define PROFILE_START(name) \ +static ProfilerRootData pdata##name##obj (#name); \ +if(gProfiler) gProfiler->hashPush(& pdata##name##obj ) + +#define PROFILE_END() if(gProfiler) gProfiler->hashPop() + +#else +#define PROFILE_START(x) +#define PROFILE_END() +#endif + +#endif diff --git a/platform/types.h b/platform/types.h new file mode 100644 index 0000000..4f21489 --- /dev/null +++ b/platform/types.h @@ -0,0 +1,153 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPES_H_ +#define _TYPES_H_ + + + +#ifdef __POWERPC__ +#ifndef _TYPESPPC_H_ +#include "Platform/typesPPC.h" +#endif +#endif + +#if defined(_WIN32) || defined(WIN32) +#ifndef _TYPESWIN32_H_ +#include "Platform/typesWin32.h" +#endif +#endif + +#ifdef __linux +#ifndef _TYPESLINUX_H_ +#include "Platform/typesLinux.h" +#endif +#endif + + + + +//-------------------------------------- A couple of all-around useful inlines and +// globals +// +U32 getNextPow2(U32 io_num); +U32 getBinLog2(U32 io_num); + +inline bool isPow2(const U32 in_num) +{ + return (in_num == getNextPow2(in_num)); +} + +inline U16 endianSwap(const U16 in_swap) +{ + return U16(((in_swap >> 8) & 0x00ff) | + ((in_swap << 8) & 0xff00)); +} + +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)); +} + +//----------------Many versions of min and max------------- +//---not using template functions because MS VC++ chokes--- + +inline U32 getMin(U32 a, U32 b) +{ + return a>b ? b : a; +} + +inline U16 getMin(U16 a, U16 b) +{ + return a>b ? b : a; +} + +inline U8 getMin(U8 a, U8 b) +{ + return a>b ? b : a; +} + +inline S32 getMin(S32 a, S32 b) +{ + return a>b ? b : a; +} + +inline S16 getMin(S16 a, S16 b) +{ + return a>b ? b : a; +} + +inline S8 getMin(S8 a, S8 b) +{ + return a>b ? b : a; +} + +inline float getMin(float a, float b) +{ + return a>b ? b : a; +} + +inline double getMin(double a, double b) +{ + return a>b ? b : a; +} + +inline U32 getMax(U32 a, U32 b) +{ + return a>b ? a : b; +} + +inline U16 getMax(U16 a, U16 b) +{ + return a>b ? a : b; +} + +inline U8 getMax(U8 a, U8 b) +{ + return a>b ? a : b; +} + +inline S32 getMax(S32 a, S32 b) +{ + return a>b ? a : b; +} + +inline S16 getMax(S16 a, S16 b) +{ + return a>b ? a : b; +} + +inline S8 getMax(S8 a, S8 b) +{ + return a>b ? a : b; +} + +inline float getMax(float a, float b) +{ + return a>b ? 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(U8(ch0)) << 0) | \ + (U32(U8(ch1)) << 8) | \ + (U32(U8(ch2)) << 16) | \ + (U32(U8(ch3)) << 24)) + +#define BIT(x) (1 << (x)) + +#endif //_NTYPES_H_ diff --git a/platform/typesLinux.h b/platform/typesLinux.h new file mode 100644 index 0000000..309a81c --- /dev/null +++ b/platform/typesLinux.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESLINUX_H_ +#define _TYPESLINUX_H_ + +#include + +/* 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 time_t 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); + +#endif diff --git a/platform/typesPPC.h b/platform/typesPPC.h new file mode 100644 index 0000000..273cf73 --- /dev/null +++ b/platform/typesPPC.h @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESPPC_H_ +#define _TYPESPPC_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; + +struct FileTime +{ + U64 time; // The date and time, specified in seconds since midnight, January 1, 1904. +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- 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); + + +#endif //_NTYPES_H_ diff --git a/platform/typesWin32.h b/platform/typesWin32.h new file mode 100644 index 0000000..0c232d1 --- /dev/null +++ b/platform/typesWin32.h @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 + + +#define FN_CDECL __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; + +#ifdef __BORLANDC__ +typedef signed __int64 S64; +typedef unsigned __int64 U64; +#elif defined(__MWERKS__) // This has to go b4 MSC_VER since CodeWarrior defines MSC_VER too +typedef signed long long S64; +typedef unsigned long long U64; +#elif defined(_MSC_VER) +typedef signed _int64 S64; +typedef unsigned _int64 U64; +#pragma warning(disable: 4291) // disable warning caused by memory layer... +#else +typedef signed long long S64; +typedef unsigned long long U64; +#endif + +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 int dsize_t; + +typedef const char* StringTableEntry; + +struct FileTime +{ + U32 v1; + U32 v2; +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- 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); + + +#ifdef _MSC_VER +#define for if(true) for +#endif + + +#endif //_NTYPES_H_ diff --git a/platform/typesX86UNIX.h b/platform/typesX86UNIX.h new file mode 100644 index 0000000..e7fcc1f --- /dev/null +++ b/platform/typesX86UNIX.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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/platformLinux/async.c b/platformLinux/async.c new file mode 100644 index 0000000..a352619 --- /dev/null +++ b/platformLinux/async.c @@ -0,0 +1,350 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "async.h" + +/*#define ASYNC_DEBUG*/ + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#define MAX_FDS 128 + +typedef struct { + int fd; /* file descriptor */ + AsyncCallback callback; /* callback per endpoint */ + int mask; /* event mask for fd */ +} assoc_t; + +typedef struct { + fd_set read_fds; + fd_set write_fds; + fd_set except_fds; + /* consider changing to map */ + assoc_t endpoints[MAX_FDS]; + int num_endpoints; + int high_fd; +} thread_data_t; + + +static pthread_mutex_t data_mutex; + +static pthread_t thread; + +static int thread_running; +static thread_data_t thread_data; + + +static assoc_t *search_endpoint( int fd ) +{ + int i; + assoc_t* ret = NULL; + + pthread_mutex_lock( &data_mutex ); + + for( i = 0; i < thread_data.num_endpoints; i++ ) { + + if( thread_data.endpoints[i].fd == fd ) { + ret = &thread_data.endpoints[i]; + break; + } + + } + + pthread_mutex_unlock( &data_mutex ); + + return ret; +} + +static void do_callback( assoc_t* assoc, int fd, int event, int status ) +{ + + if( assoc->callback ) { + assoc->callback( fd, event, status ); + } + +} + +static void* select_thread( void* data ) +{ + fd_set read_fds, write_fds, except_fds; + int high, fd; + + /* shut up compiler */ + data = data; + +#ifdef ASYNC_DEBUG + fprintf( stderr, "entering select_thread\n" ); +#endif + + while( thread_running ) { + struct timeval tv; + int active; + + pthread_mutex_lock( &data_mutex ); + /* copy? */ + read_fds = thread_data.read_fds; + write_fds = thread_data.write_fds; + except_fds = thread_data.except_fds; + + high = thread_data.high_fd + 1; + /* unlock here => guarded copy? */ + pthread_mutex_unlock( &data_mutex ); + + /* FIXME: 10 ms timeout? */ + tv.tv_sec = 0; + tv.tv_usec = 10000; + + active = select( high, &read_fds, &write_fds, &except_fds, &tv ); + + if( active == -1 ) { + /* Don't bother printing a message here... + perror( "select_thread(): select()" ); + */ + continue; + } + + if( active == 0 ) { + continue; + } + + /* FIXME: + * starting at 0? is this *completely* braindead? why don't + * we just iterate over the values in endpoints[i].fd? + * /me wants the STL map :( + */ + for( fd = 0; fd < high; fd++ ) { + assoc_t* assoc; + + assoc = search_endpoint( fd ); + + if( !assoc ) { + continue; + } + + if( FD_ISSET( fd, &read_fds ) ) { + + if( assoc->mask & FD_ACCEPT ) { +#ifdef ASYNC_DEBUG + fprintf( stderr, "fd %d is set for accepting\n", fd ); +#endif + do_callback( assoc, fd, FD_ACCEPT, 1 ); + } else { + + if( assoc->mask & FD_CONNECT ) { +#ifdef ASYNC_DEBUG + fprintf( stderr, "fd %d is set for reading, but is waiting\n", fd ); +#endif + } else { + int amount; + +#ifdef ASYNC_DEBUG + fprintf( stderr, "fd %d is set for reading\n", fd ); +#endif + + if( ioctl( fd, FIONREAD, &amount ) == -1 ) { + perror( "select_thread(): ioctl()" ); + continue; + } + + if( !amount ) { + + if( assoc->mask & FD_CLOSE ) { + /* Ie, do we want to hear about FD_CLOSE? */ + /* ECONNRESET? */ + do_callback( assoc, fd, FD_CLOSE, 1 ); + } + + } else { + do_callback( assoc, fd, FD_READ, 1 ); + } + + } + + } + + } + + if( FD_ISSET( fd, &write_fds ) ) { + /* data is ready to be written to the socket */ + + if( assoc->mask & FD_CONNECT ) { + int error = 0; + socklen_t error_size = sizeof( error ); + int status = 1; + + if( getsockopt( fd, SOL_SOCKET, SO_ERROR, &error, &error_size ) == -1 ) { + perror( "select_thread(): getsockopt()" ); + } + + if( error ) { + status = 0; + } + +#ifdef ASYNC_DEBUG + fprintf( stderr, "fd %d is set for connect with status %d\n", fd, status ); +#endif + assoc->mask &= ~(FD_CONNECT); + do_callback( assoc, fd, FD_CONNECT, status ); + } else { +#ifdef ASYNC_DEBUG + fprintf( stderr, "fd %d is set for writing\n", fd ); +#endif + do_callback( assoc, fd, FD_WRITE, 1 ); + /* to prevent checking for it repeatedly */ + pthread_mutex_lock( &data_mutex ); + FD_CLR( fd, &( thread_data.write_fds ) ); + pthread_mutex_unlock( &data_mutex ); + } + + } + + if( FD_ISSET( fd, &except_fds ) ) { + /* exception on the socket, OOB data */ + do_callback( assoc, fd, FD_OOB, 1 ); + } + + } /* for all our fds */ + + } /* while thread_running */ + +#ifdef ASYNC_DEBUG + fprintf( stderr, "leaving select_thread\n" ); +#endif + + return NULL; +} + +int AsyncCancel( int fd ) +{ + int i = 0; + + pthread_mutex_lock( &data_mutex ); + + if( thread_data.num_endpoints <= 0 ) { + pthread_mutex_unlock( &data_mutex ); + return -1; + } + + FD_CLR( fd, &thread_data.read_fds ); + FD_CLR( fd, &thread_data.write_fds ); + FD_CLR( fd, &thread_data.except_fds ); + + for( i = 0; i < thread_data.num_endpoints && thread_data.endpoints[i].fd != fd; i++ ) { + /* spin */ + } + + if( i == thread_data.num_endpoints ) { + /* we didn't find it */ + pthread_mutex_unlock( &data_mutex ); + return -1; + } + + thread_data.num_endpoints--; + + for( ; i < thread_data.num_endpoints; i++ ) { + thread_data.endpoints[i] = thread_data.endpoints[ i + 1 ]; + } + + assert( thread_data.num_endpoints >= 0 ); + pthread_mutex_unlock( &data_mutex ); + + return 0; +} + +int AsyncSelect( int fd, AsyncCallback callback, int mask ) +{ + int waiting, listening; + + pthread_mutex_lock( &data_mutex ); + + if( thread_data.num_endpoints >= MAX_FDS ) { + pthread_mutex_unlock( &data_mutex ); + return -1; + } + + waiting = ( mask & FD_CONNECT ) ? 1 : 0; + listening = ( mask & FD_ACCEPT ) ? 1 : 0; + + thread_data.endpoints[thread_data.num_endpoints].fd = fd; + thread_data.endpoints[thread_data.num_endpoints].callback = callback; + /* this will copy over FD_CLOSE, etc. */ + thread_data.endpoints[thread_data.num_endpoints].mask = mask; + thread_data.num_endpoints++; + +#ifdef ASYNC_DEBUG + fprintf( stderr, "added fd %d (%d/%d), %d total\n", fd, waiting, + listening, thread_data.num_endpoints ); +#endif + + if( fcntl( fd, F_SETFL, fcntl( fd, F_GETFL ) | O_NONBLOCK ) < 0 ) { + perror( "AsyncSelect(): fcntl()" ); + return -1; + } + + if( fd > thread_data.high_fd ) { + thread_data.high_fd = fd; + } + + if( mask & FD_READ || mask & FD_ACCEPT ) { + FD_SET( fd, &thread_data.read_fds ); + } + + if( mask & FD_WRITE || mask & FD_CONNECT ) { + FD_SET( fd, &thread_data.write_fds ); + } + + if( mask & FD_OOB ) { + FD_SET( fd, &thread_data.except_fds ); + } + + pthread_mutex_unlock( &data_mutex ); + + return 0; +} + +int AsyncShutdown( void ) +{ + thread_running = FALSE; + + pthread_join( thread, 0 ); + + pthread_mutex_destroy( &data_mutex ); + + return 0; +} + +int AsyncInit( void ) +{ + pthread_mutex_init( &data_mutex, 0 ); + + thread_running = TRUE; + + FD_ZERO( &thread_data.read_fds ); + FD_ZERO( &thread_data.write_fds ); + FD_ZERO( &thread_data.except_fds ); + + thread_data.high_fd = 0; + thread_data.num_endpoints = 0; + + if( pthread_create( &thread, 0, select_thread, 0 ) < 0 ) { + perror( "AsyncInit(): pthread_create()" ); + return -1; + } + + return 0; +} diff --git a/platformLinux/async.h b/platformLinux/async.h new file mode 100644 index 0000000..e2ba771 --- /dev/null +++ b/platformLinux/async.h @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ASYNC_H_ +#define _ASYNC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define FD_READ 0x0001 +#define FD_WRITE 0x0002 +#define FD_OOB 0x0004 +#define FD_ACCEPT 0x0008 +#define FD_CONNECT 0x0010 +#define FD_CLOSE 0x0020 + +typedef int (*AsyncCallback)( int fd, int event, int status ); + + +extern int AsyncInit( void ); +extern int AsyncShutdown( void ); +extern int AsyncCancel( int fd ); +extern int AsyncSelect( int fd, AsyncCallback callback, int mask ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/platformLinux/audio.cc b/platformLinux/audio.cc new file mode 100644 index 0000000..7ec4ee5 --- /dev/null +++ b/platformLinux/audio.cc @@ -0,0 +1,3574 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "audio/audio.h" +#include "audio/audioNet.h" +#include "Core/tVector.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "Test/gameConnection.h" +#ifndef __linux +#include "audio/audioMss.h" +#endif +#include "Core/fileStream.h" +#include "audio/audioThread.h" + +// Comment the following line to enable the audio thread again +#define DISABLE_AUDIO_THREAD + +// undef this to not include any immersion support +#ifdef IMMERSION_SUPPORT +#include "ImmSupport.h" +#endif + +// miles openAL implementation does not handle 3dsource volumes through listener for all providers, +// so wrapper takes care of all this (undef if not needed) +// - scores for sources are attenuated by channel gain but not by listener gain +#define HANDLE_LISTENER_GAIN + +#ifdef __linux + +// redirect ALUT to our implementation that supports null fallbacks +#undef alutInit +#undef alutExit + +#define alutInit alutInitFake +#define alutExit alutExitFake + +#endif // __linux + +//------------------------------------------------------------------------- +#ifndef __linux +namespace { +#endif + +#define RECORD_SPEED 8000 // default rate for capturing +#define RECORD_FORMAT AL_FORMAT_MONO16 // capture format +#define RECORD_BUFFERSIZE 1024 // 1k capture buffer size +#define RECORD_LEN (4 * 1000) // 4seconds max capture time +#define MAX_AUDIOSOURCES 16 // maximum number of concurrent sources +#define MIN_GAIN 0.05f // anything with lower gain will not be started +#define MIN_CAPTURE_SCALE 0.1f // for scaling captured buffers +#define MAX_CAPTURE_SCALE 5.f +#define MIN_UNCULL_PERIOD 500 // time before buffer is checked to be unculled +#define MIN_UNCULL_GAIN 0.1f // min gain of source to be unculled + +#define ALX_DEF_SAMPLE_RATE 44100 // default values for mixer +#define ALX_DEF_SAMPLE_BITS 16 +#define ALX_DEF_CHANNELS 2 + +#define FORCED_OUTER_FALLOFF 10000.f // forced falloff distance +static bool mDisableOuterFalloffs = false; // forced max falloff? +static F32 mInnerFalloffScale = 1.f; // amount to scale inner falloffs + +static Vector mDriverInfoList(__FILE__, __LINE__); +static bool mInitialized = false; +static Audio::DriverInfo *mActiveDriver = NULL; // the current audio driver (miles, none, ...) +static F32 mCaptureGainScale = 1.f; // amount to scale captured buffers before sending to server + +static F32 mAudioTypeVolume[Audio::NumAudioTypes]; // the attenuation for each of the channel types + +//------------------------------------------------------------------------- +struct LoopingImage +{ + AUDIOHANDLE mHandle; + Resource mBuffer; + Audio::Description mDescription; + EffectDescription * mpEffectDescription; + AudioSampleEnvironment * mEnvironment; + + Point3F mPosition; + Point3F mDirection; + F32 mPan; + F32 mPitch; + F32 mScore; + S32 mCullTime; + + LoopingImage() { clear(); } + + void clear() + { + mHandle = NULL_AUDIOHANDLE; + mBuffer = NULL; + dMemset(&mDescription, 0, sizeof(Audio::Description)); + mpEffectDescription = NULL; + mEnvironment = 0; + mPosition.set(0.f,0.f,0.f); + mDirection.set(0.f,1.f,0.f); + mPan = 0.f; + mPitch = 1.f; + mScore = 0.f; + mCullTime = 0; + } +}; + +#ifdef __linux +class DecoderStream +{ + private: + public: + DecoderStream() { /* STUB STUB STUB */ ; } + ~DecoderStream() { /* STUB STUB STUB */ ; } + + static bool open() { /* STUB STUB STUB */ ; } + static bool open(const char *suffix) { /* STUB STUB STUB */ ; } + static void close() { /* STUB STUB STUB */ ; } + + bool openStream() { /* STUB STUB STUB */ ; } + void closeStream() { /* STUB STUB STUB */ ; } + + void setBuffer(U8 *data, U32 size) { /* STUB STUB STUB */ ; } + U32 getBuffer(U8 **data, U32 *size) { /* STUB STUB STUB */ ; } + void process(bool flush=false) { /* STUB STUB STUB */ ; } +}; + +class EncoderStream +{ + private: + public: + //-------------------------------------- + EncoderStream() { /* STUB STUB STUB */ ; } + ~EncoderStream() { /* STUB STUB STUB */ ; } + + static bool open() { /* STUB STUB STUB */ ; } + static bool open(const char *suffix) { /* STUB STUB STUB */ ; } + static void close() { /* STUB STUB STUB */ ; } + + bool openStream() { /* STUB STUB STUB */ ; } + void closeStream() { /* STUB STUB STUB */ ; } + + bool setConnection(GameConnection *con) { /* STUB STUB STUB */ ; } + void setBuffer(const U8 *buffer, U32 size) { /* STUB STUB STUB */ ; } + void process(bool flush=false) { /* STUB STUB STUB */ ; } + void flush() { /* STUB STUB STUB */ ; } +}; +#endif + +//------------------------------------------------------------------------- +struct VoiceStream +{ + U32 mClientId; + U8 mStreamId; + DecoderStream mDecoder; + ALuint mBuffer; + AUDIOHANDLE mHandle; +}; + +static F32 mMasterVolume = 1.f; // traped from AL_LISTENER gain (miles has difficulties with 3d sources) + +static ALuint mSource[MAX_AUDIOSOURCES]; // ALSources +static AUDIOHANDLE mHandle[MAX_AUDIOSOURCES]; // unique handles +static Resource mBuffer[MAX_AUDIOSOURCES]; // each of the playing buffers (needed for AudioThread) +static F32 mScore[MAX_AUDIOSOURCES]; // for figuring out which sources to cull/uncull +static F32 mSourceVolume[MAX_AUDIOSOURCES]; // the samples current un-attenuated gain (not scaled by master/channel gains) +static U32 mType[MAX_AUDIOSOURCES]; // the channel which this source belongs +static bool mRecording = false; // currently recording? +static bool mLocalCapture = false; // recording to local buffer? +static ALboolean mCaptureInitialized = AL_FALSE; // capture initialized? + +static ALuint mStreamBuffer; // the music strean buffer (only 1 music stream currently supported) +static AUDIOHANDLE mStreamHandle; // handle to the music stream +static bool mFinishedMusicStream = false; // set in callback (al thread) to process end of music stream +static bool mFinishedMusicStreamStopState = false; // set in callback (passed along to console) + +static EffectDescription * mpEffectDescription[MAX_AUDIOSOURCES] = {NULL}; // currently playing force-feedback +static AudioSampleEnvironment * mSampleEnvironment[MAX_AUDIOSOURCES]; // currently playing sample environments +static bool mEnvironmentEnabled = false; // environment enabled? +static SimObjectPtr mCurrentEnvironment; // the last environment set + +#define LOCAL_CAPTURE_SIZE (RECORD_LEN * RECORD_SPEED) +static U8 * mLocalCaptureBuffer = 0; // buffer to contain captured data +static U32 mLocalCaptureBufferPos = 0; // current capture buffer pos (circular) +static bool mLocalCaptureFinished = false; // finished capturing? (set in callback) +static ALuint mALCaptureBuffer = 0; // the buffer used to playback local capture data +static ALuint mEnvironment = 0; // al environment handle + +struct LoopingList : VectorPtr +{ + LoopingList() : VectorPtr(__FILE__, __LINE__) { } + + LoopingList::iterator findImage(AUDIOHANDLE handle); + void sort(); +}; + +// LoopingList and LoopingFreeList own the images +static LoopingList mLoopingList; // all the looping sources +static LoopingList mLoopingFreeList; // free store +static LoopingList mLoopingInactiveList; // sources which have not been played yet +static LoopingList mLoopingCulledList; // sources which have been culled (alxPlay called) + +static VectorPtr sVoiceStreams(__FILE__, __LINE__); +static VectorPtr sVoiceStreams_free(__FILE__, __LINE__); + +#define AUDIOHANDLE_LOOPING_BIT (0x80000000) +#define AUDIOHANDLE_VOICE_BIT (0x40000000) +#define AUDIOHANDLE_INACTIVE_BIT (0x20000000) +#define AUDIOHANDLE_LOADING_BIT (0x10000000) +#define HANDLE_MASK ~(AUDIOHANDLE_LOOPING_BIT | AUDIOHANDLE_VOICE_BIT | AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT) + +// keep the 'AUDIOHANDLE_LOOPING_BIT' on the handle returned to the caller so that +// the handle can quickly be rejected from looping list queries +#define RETURN_MASK ~(AUDIOHANDLE_VOICE_BIT | AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT) +static AUDIOHANDLE mLastHandle = NULL_AUDIOHANDLE; + +static bool mForceMaxDistanceUpdate = false; // force gain setting for 3d distances +static U32 mNumSources = 0; // total number of sources to work with +static U32 mRequestSources = MAX_AUDIOSOURCES; // number of sources to request from openAL + +#define INVALID_SOURCE 0xffffffff +#define CAPTURE_BUFFER_SIZE (300) + +static U32 sCaptureTimeout = 0; +static EncoderStream mEncoderStream; + +inline bool areEqualHandles(AUDIOHANDLE a, AUDIOHANDLE b) +{ + return((a & HANDLE_MASK) == (b & HANDLE_MASK)); +} + +//------------------------------------------------------------------------- +// Looping image +//------------------------------------------------------------------------- +inline LoopingList::iterator LoopingList::findImage(AUDIOHANDLE handle) +{ + if(handle & AUDIOHANDLE_LOOPING_BIT) + { + LoopingList::iterator itr = begin(); + while(itr != end()) + { + if(areEqualHandles((*itr)->mHandle, handle)) + return(itr); + itr++; + } + } + return(0); +} + +inline int QSORT_CALLBACK loopingImageSort(const void * p1, const void * p2) +{ + const LoopingImage * ip1 = *(const LoopingImage**)p1; + const LoopingImage * ip2 = *(const LoopingImage**)p2; + + // min->max + return ip2->mScore - ip1->mScore; +} + +void LoopingList::sort() +{ + dQsort(address(), size(), sizeof(LoopingImage*), loopingImageSort); +} + +//------------------------------------------------------------------------- +LoopingImage * createLoopingImage() +{ + LoopingImage *image; + if (mLoopingFreeList.size()) + { + image = mLoopingFreeList.last(); + mLoopingFreeList.pop_back(); + } + else + image = new LoopingImage; + return(image); +} + +//------------------------------------------------------------------------- +static AUDIOHANDLE getNewHandle() +{ + mLastHandle++; + mLastHandle &= HANDLE_MASK; + if (mLastHandle == NULL_AUDIOHANDLE) + mLastHandle++; + return mLastHandle; +} +#ifndef __linux +} // namespace {} +#endif + +//------------------------------------------------------------------------- +// function declarations +void alxLoopingUpdate(); +void alxUpdateScores(bool); + +//------------------------------------------------------------------------- +static void alxFreeVoiceStream(VectorPtr::iterator itr) +{ + VoiceStream *vs = *itr; + sVoiceStreams.erase_fast(itr); + sVoiceStreams_free.push_back(vs); + vs->mHandle = NULL_AUDIOHANDLE; + Con::evaluatef("clientCmdPlayerStoppedTalking(%d, 1);", vs->mClientId); +} + +static void alxFreeVoiceStream(AUDIOHANDLE handle) +{ + VectorPtr::iterator itr = sVoiceStreams.begin(); + for (; itr != sVoiceStreams.end(); itr++) + if ((*itr)->mHandle == handle) + { + alxFreeVoiceStream(itr); + return; + } +} + +inline VectorPtr::iterator alxFindVoiceStream(AUDIOHANDLE handle) +{ + for(VectorPtr::iterator itr = sVoiceStreams.begin(); itr != sVoiceStreams.end(); itr++) + if(areEqualHandles((*itr)->mHandle, handle)) + return(itr); + return(0); +} + +static bool findFreeSource(U32 *index) +{ + for(U32 i = 0; i < mNumSources; i++) + if(mHandle[i] == NULL_AUDIOHANDLE) + { + *index = i; + return(true); + } + return(false); +} + +//-------------------------------------------------------------------------- +// - cull out the min source that is below volume +// - streams/voice/loading streams are all scored > 2 +// - volumes are attenuated by channel only +static bool cullSource(U32 *index, F32 volume) +{ + alGetError(); + + F32 minVolume = volume; + S32 best = -1; + for(S32 i = 0; i < mNumSources; i++) + { + if(mScore[i] < minVolume) + { + minVolume = mScore[i]; + best = i; + } + } + + if(best == -1) + return(false); + + // check if culling a looper + LoopingList::iterator itr = mLoopingList.findImage(mHandle[best]); + if(itr) + { + // check if culling an inactive looper + if(mHandle[best] & AUDIOHANDLE_INACTIVE_BIT) + { + AssertFatal(!mLoopingInactiveList.findImage(mHandle[best]), "cullSource: image already in inactive list"); + AssertFatal(!mLoopingCulledList.findImage(mHandle[best]), "cullSource: image should not be in culled list"); + mLoopingInactiveList.push_back(*itr); + } + else + { + (*itr)->mHandle |= AUDIOHANDLE_INACTIVE_BIT; + AssertFatal(!mLoopingCulledList.findImage(mHandle[best]), "cullSource: image already in culled list"); + AssertFatal(!mLoopingInactiveList.findImage(mHandle[best]), "cullSource: image should no be in inactive list"); + (*itr)->mCullTime = Platform::getRealMilliseconds(); + mLoopingCulledList.push_back(*itr); + } + } + + alSourceStop(mSource[best]); + mHandle[best] = NULL_AUDIOHANDLE; + mBuffer[best] = 0; + *index = best; + + return(true); +} + +//-------------------------------------------------------------------------- +// we want to compute approximate max volume at a particular distance +// ignore cone volume influnces +static F32 approximate3DVolume(const Audio::Description *desc, const Point3F &position) +{ + Point3F p1; + alxGetListener3f(AL_POSITION, &p1.x, &p1.y, &p1.z); + + p1 -= position; + F32 distance = p1.magnitudeSafe(); + + if(distance >= desc->mMaxDistance) + return(0.f); + else if(distance > desc->mMinDistance) + return(desc->mMinDistance / distance); + else + return 1.0f; +} + +//-------------------------------------------------------------------------- +inline U32 alxFindIndex(AUDIOHANDLE handle) +{ + for (U32 i=0; imDirect); + alSourcei(source, AL_ENV_SAMPLE_DIRECT_HF_EXT, env->mDirectHF); + alSourcei(source, AL_ENV_SAMPLE_ROOM_EXT, env->mRoom); + alSourcei(source, AL_ENV_SAMPLE_ROOM_HF_EXT, env->mRoomHF); + alSourcei(source, AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, env->mOutsideVolumeHF); + alSourcei(source, AL_ENV_SAMPLE_FLAGS_EXT, env->mFlags); + + alSourcef(source, AL_ENV_SAMPLE_OBSTRUCTION_EXT, env->mObstruction); + alSourcef(source, AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, env->mObstructionLFRatio); + alSourcef(source, AL_ENV_SAMPLE_OCCLUSION_EXT, env->mOcclusion); + alSourcef(source, AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, env->mOcclusionLFRatio); + alSourcef(source, AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, env->mOcclusionRoomRatio); + alSourcef(source, AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, env->mRoomRolloff); + alSourcef(source, AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, env->mAirAbsorption); +#endif +} + +static void alxSourceEnvironment(ALuint source, LoopingImage * image) +{ + AssertFatal(image, "alxSourceEnvironment: invalid looping image"); + if(image->mDescription.mIs3D) + alxSourceEnvironment(source, image->mDescription.mEnvironmentLevel, image->mEnvironment); +} + +//-------------------------------------------------------------------------- +// setup a source to play... loopers have pitch/pan cached +// - by default, pitch is 1x and pan is center (settings not defined in description) +// - all the settings are cached by openAL (miles version), so no worries setting them here +static void alxSourcePlay(ALuint source, Resource buffer, const Audio::Description *desc, const MatrixF *transform) +{ + alSourcei(source, AL_BUFFER, buffer->getALBuffer()); + alSourcef(source, AL_GAIN_LINEAR, desc->mVolume * mAudioTypeVolume[desc->mType] * mMasterVolume); + alSourcei(source, AL_SOURCE_LOOPING, desc->mIsLooping ? AL_TRUE : AL_FALSE); + alSourcef(source, AL_PITCH, 1.f); + + // stream sources are only setup for music streams (below...) +#ifndef __linux + alSourcei(source, AL_STREAMING, AL_FALSE); +#endif + + // 3d? + if(transform != NULL) + { +#ifdef __linux + alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); +#else + alSourcei(source, AL_SOURCE_AMBIENT, AL_FALSE); +#endif + alSourcei(source, AL_CONE_INNER_ANGLE, desc->mConeInsideAngle); + alSourcei(source, AL_CONE_OUTER_ANGLE, desc->mConeOutsideAngle); + alSourcef(source, AL_CONE_OUTER_GAIN, desc->mConeOutsideVolume); + + Point3F p; + transform->getColumn(3, &p); + alSource3f(source, AL_POSITION, p.x, p.y, p.z); + + transform->getRow(1, &p); + alSource3f(source, AL_DIRECTION, p.x, p.y, p.z); + + // forcing different falloffs? + if(mDisableOuterFalloffs) + { + alSourcef(source, AL_MIN_DISTANCE, mInnerFalloffScale * desc->mMinDistance); + alSourcef(source, AL_MAX_DISTANCE, FORCED_OUTER_FALLOFF); + } + else + { + alSourcef(source, AL_MIN_DISTANCE, desc->mMinDistance); + alSourcef(source, AL_MAX_DISTANCE, desc->mMaxDistance); + } + +#ifndef __linux + // environmental audio stuff: + alSourcef(source, AL_ENV_SAMPLE_REVERB_MIX_EXT, desc->mEnvironmentLevel); + if(desc->mEnvironmentLevel != 0.f) + alSourceResetEnvironment_EXT(source); +#endif + } + else // 2d source (default the pan) + { +#ifdef __linux + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0); +#else + alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE); + alSourcef(source, AL_PAN, 0.f); +#endif + } +} + +// helper for looping images +static void alxSourcePlay(ALuint source, LoopingImage * image) +{ + AssertFatal(image, "alxSourcePlay: invalid looping image"); + + // 3d source? need position/direction + if(image->mDescription.mIs3D) + { + MatrixF transform(true); + + transform.setColumn(3, image->mPosition); + transform.setRow(1, image->mDirection); + + alxSourcePlay(source, image->mBuffer, &image->mDescription, &transform); + } + else // 2d source? + { + alxSourcePlay(source, image->mBuffer, &image->mDescription, 0); + + // set the pan/pitch... + alxSourcef(source, AL_PITCH, image->mPitch); + alxSourcef(source, AL_PAN, image->mPan); + } +} + +//-------------------------------------------------------------------------- +AUDIOHANDLE alxCreateSource(const Audio::Description * desc, const char * filename, const MatrixF * transform, + EffectDescription * effectDescription, AudioSampleEnvironment * sampleEnvironment) +{ + if(desc == NULL || filename == NULL || *filename == '\0') + return NULL_AUDIOHANDLE; + + F32 volume = desc->mVolume; + +#ifdef IMMERSION_SUPPORT + bool storeEffectDescription = false; + if (effectDescription) + { + effectDescription->mVolume = volume; + storeEffectDescription = volume > 0.0f; + } +#endif + + // calculate an approximate attenuation for 3d sounds + if(transform && desc->mIs3D) + { + Point3F position; + transform->getColumn(3, &position); + volume *= approximate3DVolume(desc, position); + +#ifdef IMMERSION_SUPPORT + // Calculate Force-Feedback Effect volume + if (effectDescription) + { + Audio::Description effectDesc; + dMemcpy(&effectDesc, desc, sizeof(Audio::Description)); + effectDesc.mMinDistance = effectDescription->mMinDistance; + effectDesc.mMaxDistance = effectDescription->mMaxDistance; + effectDescription->mVolume *= approximate3DVolume(&effectDesc, position); + storeEffectDescription = effectDescription->mVolume > 0.0f; + } +#endif + } + + // check the type specific volume + AssertFatal(desc->mType < Audio::NumAudioTypes, "alxCreateSource: invalid type for source"); + if(desc->mType >= Audio::NumAudioTypes) + return(NULL_AUDIOHANDLE); + + // done if channel is muted (and not a looper) + if(!desc->mIsLooping && (mAudioTypeVolume[desc->mType] == 0.f)) + return(NULL_AUDIOHANDLE); + + // scale volume by channel attenuation + volume *= mAudioTypeVolume[desc->mType]; + + // non-loopers don't add if < minvolume + if(!desc->mIsLooping && (volume <= MIN_GAIN)) + return(NULL_AUDIOHANDLE); + + U32 index = MAX_AUDIOSOURCES; + + // try and find an available source: 0 volume loopers get added to inactive list + if(volume > MIN_GAIN) + { + if(!findFreeSource(&index)) + { + alxUpdateScores(true); + + // scores do not include master volume + if(!cullSource(&index, volume)) + index = MAX_AUDIOSOURCES; + } + } + + // make sure that loopers are added + if(index == MAX_AUDIOSOURCES) + { + if(desc->mIsLooping) + { + Resource buffer = AudioBuffer::find(filename); + if(!(bool)buffer) + return(NULL_AUDIOHANDLE); + + // create the inactive looping image + LoopingImage * image = createLoopingImage(); + + image->mHandle = getNewHandle() | AUDIOHANDLE_LOOPING_BIT | AUDIOHANDLE_INACTIVE_BIT; + image->mBuffer = buffer; + image->mDescription = *desc; + image->mScore = volume; + image->mEnvironment = sampleEnvironment; + +#ifdef IMMERSION_SUPPORT + image->mpEffectDescription = (storeEffectDescription ? effectDescription : NULL); +#endif + // grab position/direction if 3d source + if(transform) + { + transform->getColumn(3, &image->mPosition); + transform->getRow(1, &image->mDirection); + } + + AssertFatal(!mLoopingInactiveList.findImage(image->mHandle), "alxCreateSource: handle in inactive list"); + AssertFatal(!mLoopingCulledList.findImage(image->mHandle), "alxCreateSource: handle in culled list"); + + // add to the looping and inactive lists + mLoopingList.push_back(image); + mLoopingInactiveList.push_back(image); + return(image->mHandle & RETURN_MASK); + } + else + return(NULL_AUDIOHANDLE); + } + + // clear the error state + alGetError(); + + // grab the buffer + Resource buffer = AudioBuffer::find(filename); + if((bool)buffer == false) + return NULL_AUDIOHANDLE; + + // init the source (created inactive) and store needed values + mHandle[index] = getNewHandle() | AUDIOHANDLE_INACTIVE_BIT; + mType[index] = desc->mType; + mBuffer[index] = buffer; + mScore[index] = volume; + mSourceVolume[index] = desc->mVolume; + mSampleEnvironment[index] = sampleEnvironment; + + ALuint source = mSource[index]; +#ifdef IMMERSION_SUPPORT + mpEffectDescription[index] = (storeEffectDescription ? effectDescription : NULL); +#else + mpEffectDescription[index] = NULL; +#endif + + // setup play info + alxSourcePlay(source, buffer, desc, desc->mIs3D ? transform : 0); + if(mEnvironmentEnabled) + alxSourceEnvironment(source, desc->mEnvironmentLevel, sampleEnvironment); + + // setup a LoopingImage ONLY if the sound is a looper: + if(desc->mIsLooping) + { + mHandle[index] |= AUDIOHANDLE_LOOPING_BIT; + + LoopingImage * image = createLoopingImage(); + image->mHandle = mHandle[index]; + image->mBuffer = buffer; + image->mDescription = *desc; + image->mScore = volume; + image->mEnvironment = sampleEnvironment; + + // grab position/direction + if(transform) + { + transform->getColumn(3, &image->mPosition); + transform->getRow(1, &image->mDirection); + } + +#ifdef IMMERSION_SUPPORT + image->mpEffectDescription = (storeEffectDescription ? effectDescription : NULL); +#else + image->mpEffectDescription = NULL; +#endif + + AssertFatal(!mLoopingInactiveList.findImage(image->mHandle), "alxCreateSource: handle in inactive list"); + AssertFatal(!mLoopingCulledList.findImage(image->mHandle), "alxCreateSource: handle in culled list"); + + // add to the looping list + mLoopingList.push_back(image); + } + + // clear off all but looping bit + return(mHandle[index] & RETURN_MASK); +} + +//------------------------------------------------------------------------------ +AUDIOHANDLE alxCreateSource(AudioDescription *descObject, + const char *filename, + const MatrixF *transform, + EffectDescription *effectDescription, + AudioSampleEnvironment * sampleEnvironment ) +{ + if(!descObject || !descObject->getDescription()) + return(NULL_AUDIOHANDLE); + return (alxCreateSource(descObject->getDescription(), filename, transform, effectDescription, sampleEnvironment)); +} + +AUDIOHANDLE alxCreateSource(const AudioProfile *profile, const MatrixF *transform) +{ + if (profile == NULL) + return NULL_AUDIOHANDLE; + + EffectDescription *pED = NULL; +#ifdef IMMERSION_SUPPORT + EffectProfile *pEP = profile->mEffectProfile; + + // If no Immersion-compatible device is plugged in, EffectDescription will still + // exist, but the mpEffect pointer will be NULL. Save some time by NOT putting + // this EffectDescription in the active list. + if (pEP) + { + pED = &pEP->mDescription; + if (!pED->mpEffect) + pED = NULL; + } +#endif + + return alxCreateSource(profile->mDescriptionObject, profile->mFilename, transform, pED, profile->mSampleEnvironment); +} + +//-------------------------------------------------------------------------- +extern void threadPlay(AudioBuffer * buffer, AUDIOHANDLE handle); + +AUDIOHANDLE alxPlay(AUDIOHANDLE handle) +{ + U32 index = alxFindIndex(handle); + + if(index != MAX_AUDIOSOURCES) + { + // play if not already playing + if(mHandle[index] & AUDIOHANDLE_INACTIVE_BIT) + { + // jff: thread stuff +// // have the thread play this once the buffer is loaded +// if(bool(mBuffer[index]) && mBuffer[index]->isLoading()) +// { +// // move loopers into the inactive list +// LoopingList::iterator itr = mLoopingList.findImage(handle); +// if(itr) +// { +// mHandle[index] = NULL_AUDIOHANDLE; +// mBuffer[index] = 0; +// +// (*itr)->mHandle |= AUDIOHANDLE_LOADING_BIT; +// mLoopingInactiveList.push_back(*itr); +// } +// +// mHandle[index] |= AUDIOHANDLE_LOADING_BIT; +// +// if(gAudioThread) +// gAudioThread->setBufferPlayHandle(mBuffer[index], handle); +// return(handle); +// } + + mHandle[index] &= ~(AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT); + + // make sure the looping image also clears it's inactive bit + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + (*itr)->mHandle &= ~(AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT); + + alSourcePlay(mSource[index]); + + // Immersion component +#ifdef IMMERSION_SUPPORT + EffectDescription* ed = mpEffectDescription[index]; + if (ed) + ed->mpEffect->Start(ed->mLooping ? INFINITE : 1); +#endif + return(handle); + } + } + else + { + // move inactive loopers to the culled list, try to start the sound + LoopingList::iterator itr = mLoopingInactiveList.findImage(handle); + if(itr) + { + AssertFatal(!mLoopingCulledList.findImage(handle), "alxPlay: image already in culled list"); + mLoopingCulledList.push_back(*itr); + mLoopingInactiveList.erase_fast(itr); + alxLoopingUpdate(); + } + else if(mLoopingCulledList.findImage(handle)) + { + alxLoopingUpdate(); + } + else + return(NULL_AUDIOHANDLE); + } + + return(handle); +} + +//-------------------------------------------------------------------------- +// helper function.. create a source and play it +AUDIOHANDLE alxPlay(const AudioProfile *profile, const MatrixF *transform, const Point3F* /*velocity*/) +{ + if(profile == NULL) + return NULL_AUDIOHANDLE; + + EffectDescription *pED = NULL; +#ifdef IMMERSION_SUPPORT + EffectProfile *pEP = profile->mEffectProfile; + + // If no Immersion-compatible device is plugged in, EffectDescription will still + // exist, but the mpEffect pointer will be NULL. Save some time by NOT putting + // this EffectDescription in the active list. + if (pEP) + { + pED = &pEP->mDescription; + if (!pED->mpEffect) + pED = NULL; + } +#endif + + AUDIOHANDLE handle = alxCreateSource(profile->mDescriptionObject, profile->mFilename, transform, pED, profile->mSampleEnvironment); + if(handle != NULL_AUDIOHANDLE) + return(alxPlay(handle)); + return(handle); +} + +//-------------------------------------------------------------------------- +void alxStop(AUDIOHANDLE handle) +{ + U32 index = alxFindIndex(handle); + + // stop it + if(index != MAX_AUDIOSOURCES) + { +// jff: will have problems if buffer still loading + if(!(mHandle[index] & AUDIOHANDLE_INACTIVE_BIT)) + { + alSourceStop(mSource[index]); +#ifdef IMMERSION_SUPPORT + if (mpEffectDescription[index]) + mpEffectDescription[index]->mpEffect->Stop(); +#endif + } + + mSampleEnvironment[index] = 0; + mHandle[index] = NULL_AUDIOHANDLE; + mBuffer[index] = 0; +#ifdef IMMERSION_SUPPORT + mpEffectDescription[index] = NULL; +#endif + } + + // remove loopingImage and add it to the free list + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + // remove from inactive/culled list + if((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT) + { + LoopingList::iterator tmp = mLoopingInactiveList.findImage(handle); + + // inactive? + if(tmp) + mLoopingInactiveList.erase_fast(tmp); + else + { + //culled? + tmp = mLoopingCulledList.findImage(handle); + AssertFatal(tmp, "alxStop: failed to find inactive looping source"); + mLoopingCulledList.erase_fast(tmp); + } + } + + AssertFatal(!mLoopingInactiveList.findImage((*itr)->mHandle), "alxStop: handle in inactive list"); + AssertFatal(!mLoopingCulledList.findImage((*itr)->mHandle), "alxStop: handle in culled list"); + + // remove it + (*itr)->clear(); + mLoopingFreeList.push_back(*itr); + mLoopingList.erase_fast(itr); + } +} + +//-------------------------------------------------------------------------- +void alxStopAll() +{ + // stop all open sources + for(S32 i = mNumSources - 1; i >= 0; i--) + if(mHandle[i] != NULL_AUDIOHANDLE) + alxStop(mHandle[i]); + + // stop all looping sources + while(mLoopingList.size()) + alxStop(mLoopingList.last()->mHandle); +} + +void alxLoopSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_PAN: + (*itr)->mPan = value; + break; + case AL_GAIN: + (*itr)->mDescription.mVolume = Audio::DBToLinear(value); + break; + case AL_GAIN_LINEAR: + (*itr)->mDescription.mVolume = value; + break; + case AL_PITCH: + (*itr)->mPitch = value; + break; + case AL_MIN_DISTANCE: + (*itr)->mDescription.mMinDistance = value; + break; + case AL_CONE_OUTER_GAIN: + (*itr)->mDescription.mMaxDistance = value; + break; + } + } +} + +void alxLoopSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_POSITION: + (*itr)->mPosition.x = value1; + (*itr)->mPosition.y = value2; + (*itr)->mPosition.z = value3; + break; + + case AL_DIRECTION: + (*itr)->mDirection.x = value1; + (*itr)->mDirection.y = value2; + (*itr)->mDirection.z = value3; + break; + } + } +} + +void alxLoopSourcei(AUDIOHANDLE handle, ALenum pname, ALint value) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_SOURCE_AMBIENT: + (*itr)->mDescription.mIs3D = value; + break; + case AL_CONE_INNER_ANGLE: + (*itr)->mDescription.mConeInsideAngle = value; + break; + case AL_CONE_OUTER_ANGLE: + (*itr)->mDescription.mConeOutsideAngle = value; + break; + } + } +} + +void alxLoopGetSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat *value) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_PAN: + *value = (*itr)->mPan; + break; + case AL_GAIN: + *value = Audio::linearToDB((*itr)->mDescription.mVolume); + break; + case AL_GAIN_LINEAR: + *value = (*itr)->mDescription.mVolume; + break; + case AL_PITCH: + *value = (*itr)->mPitch; + break; + case AL_MIN_DISTANCE: + *value = (*itr)->mDescription.mMinDistance; + break; + case AL_CONE_OUTER_GAIN: + *value = (*itr)->mDescription.mMaxDistance; + break; + } + } +} + +void alxLoopGetSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_POSITION: + *value1 = (*itr)->mPosition.x; + *value2 = (*itr)->mPosition.y; + *value3 = (*itr)->mPosition.z; + break; + + case AL_DIRECTION: + *value1 = (*itr)->mDirection.x; + *value2 = (*itr)->mDirection.y; + *value3 = (*itr)->mDirection.z; + break; + } + } +} + +void alxLoopGetSourcei(AUDIOHANDLE handle, ALenum pname, ALint *value) +{ + LoopingList::iterator itr = mLoopingList.findImage(handle); + if(itr) + { + switch(pname) + { + case AL_SOURCE_AMBIENT: + *value = (*itr)->mDescription.mIs3D; + break; + case AL_SOURCE_LOOPING: + *value = true; + break; + case AL_CONE_INNER_ANGLE: + *value = (*itr)->mDescription.mConeInsideAngle; + break; + case AL_CONE_OUTER_ANGLE: + *value = (*itr)->mDescription.mConeOutsideAngle; + break; + } + } +} + +//-------------------------------------------------------------------------- +// AL get/set methods: Source +//-------------------------------------------------------------------------- +// - only need to worry about playing sources.. proper volume gets set on +// create source (so, could get out of sync if someone changes volume between +// a createSource and playSource call...) +void alxUpdateTypeGain(U32 typeMask) +{ + for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) + { + if(mHandle[i] == NULL_AUDIOHANDLE) + continue; + + if(!(typeMask & (1 << mType[i]))) + continue; + + ALint state = AL_STOPPED; + alGetSourcei(mSource[i], AL_SOURCE_STATE, &state); + + if(state == AL_PLAYING) + { + // volume = SourceVolume * ChannelVolume * MasterVolume + F32 vol = mSourceVolume[i] * mAudioTypeVolume[mType[i]] * mMasterVolume; + alSourcef(mSource[i], AL_GAIN_LINEAR, mClampF(vol, 0.f, 1.f)); + } + } +} + +void alxSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value) +{ +#ifdef __linux + + // ignore magical token + if( pname == AL_PAN ) { + return; + } + +#endif + ALuint source = alxFindSource(handle); + + if(source != INVALID_SOURCE) + { + // ensure gain_linear + if(pname == AL_GAIN) + { + value = Audio::DBToLinear(value); + pname = AL_GAIN_LINEAR; + } + + // need to process gain settings (so source can be affected by channel/master gains) + if(pname == AL_GAIN_LINEAR) + { + U32 idx = alxFindIndex(handle); + AssertFatal(idx != MAX_AUDIOSOURCES, "alxSourcef: handle not located for found source"); + if(idx == MAX_AUDIOSOURCES) + return; + + // update the stored value + mSourceVolume[idx] = value; + + // volume = SourceVolume * ChannelVolume * MasterVolume + F32 vol = mSourceVolume[idx] * mAudioTypeVolume[mType[idx]] * mMasterVolume; + alSourcef(source, AL_GAIN_LINEAR, mClampF(vol, 0.f, 1.f)); + } + else + alSourcef(source, pname, value); + } + alxLoopSourcef(handle, pname, value); +} + +void alxSourcefv(AUDIOHANDLE handle, ALenum pname, ALfloat *values) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + alSourcefv(source, pname, values); + + if((pname == AL_POSITION) || (pname == AL_DIRECTION) || (pname == AL_VELOCITY)) + alxLoopSource3f(handle, pname, values[0], values[1], values[2]); +} + +void alxSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + { + ALfloat values[3]; + values[0] = value1; + values[1] = value2; + values[2] = value3; + alSourcefv(source, pname, values); + } + alxLoopSource3f(handle, pname, value1, value2, value3); +} + +void alxSourcei(AUDIOHANDLE handle, ALenum pname, ALint value) +{ + ALuint source = alxFindSource(handle); +#ifdef __linux + + // reroute fictional token + if( pname == AL_SOURCE_AMBIENT ) { + if ( value ) { + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0); + } else { + alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); + } + } else { + alSourcei( source, pname, value ); + } + +#else + if(source != INVALID_SOURCE) + alSourcei(source, pname, value); +#endif + alxLoopSourcei(handle, pname, value); +} + +// sets the position and direction of the source +void alxSourceMatrixF(AUDIOHANDLE handle, const MatrixF *transform) +{ + ALuint source = alxFindSource(handle); + + Point3F pos; + transform->getColumn(3, &pos); + + Point3F dir; + transform->getRow(1, &dir); + + if(source != INVALID_SOURCE) + { + alSource3f(source, AL_POSITION, pos.x, pos.y, pos.z); + alSource3f(source, AL_DIRECTION, dir.x, dir.y, dir.z); + } + + alxLoopSource3f(handle, AL_POSITION, pos.x, pos.y, pos.z); + alxLoopSource3f(handle, AL_DIRECTION, dir.x, dir.y, dir.z); +} + +//-------------------------------------------------------------------------- +void alxGetSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat *value) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + { + // gain queries return unattenuated values + if((pname == AL_GAIN) || (pname == AL_GAIN_LINEAR)) + { + U32 idx = alxFindIndex(handle); + AssertFatal(idx != MAX_AUDIOSOURCES, "alxGetSourcef: found source but handle is invalid"); + if(idx == MAX_AUDIOSOURCES) + { + *value = 0.f; + return; + } + + if(pname == AL_GAIN) + *value = Audio::linearToDB(mSourceVolume[idx]); + else + *value = mSourceVolume[idx]; + } + else + alGetSourcef(source, pname, value); + } + else + alxLoopGetSourcef(handle, pname, value); +} + +void alxGetSourcefv(AUDIOHANDLE handle, ALenum pname, ALfloat *values) +{ + if((pname == AL_POSITION) || (pname == AL_DIRECTION) || (pname == AL_VELOCITY)) + alxGetSource3f(handle, pname, &values[0], &values[1], &values[2]); +} + +void alxGetSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + { + ALfloat values[3]; + alGetSourcefv(source, pname, values); + *value1 = values[0]; + *value2 = values[1]; + *value3 = values[2]; + } + else + alxLoopGetSource3f(handle, pname, value1, value2, value3); +} + +void alxGetSourcei(AUDIOHANDLE handle, ALenum pname, ALint *value) +{ + ALuint source = alxFindSource(handle); + if(source != INVALID_SOURCE) + alGetSourcei(source, pname, value); + else + alxLoopGetSourcei(handle, pname, value); +} + +//-------------------------------------------------------------------------- +// AL get/set methods: Listener +//-------------------------------------------------------------------------- +void alxListenerf(ALenum pname, ALfloat value) +{ +#ifdef HANDLE_LISTENER_GAIN + // listener gain is handled through wrapper + if((pname == AL_GAIN) || (pname == AL_GAIN_LINEAR)) + { + // just handles al_gain_linear + if(pname == AL_GAIN) + value = Audio::DBToLinear(value); + + mMasterVolume = mClampF(value, 0.f, 1.f); + + // update all the sources + alxUpdateTypeGain(0xffffffff); + } + else + alListenerf(pname, value); +#else + alListenerf(pname, value); +#endif +} + +void alxListenerfv(ALenum pname, ALfloat * values) +{ + alListenerfv(pname, values); +} + +void alxListener3f(ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3) +{ + alListener3f(pname, value1, value2, value3); +} + +//-------------------------------------------------------------------------- +// set the listener's position and orientation +void alxListenerMatrixF(const MatrixF *transform) +{ + Point3F p1, p2; + transform->getColumn(3, &p1); + alListener3f(AL_POSITION, p1.x, p1.y, p1.z); + + transform->getColumn(2, &p1); // Up Vector + transform->getColumn(1, &p2); // Forward Vector + F32 orientation[6]; + orientation[0] = p1.x; + orientation[1] = p1.y; + orientation[2] = p1.z; + orientation[3] = p2.x; + orientation[4] = p2.y; + orientation[5] = p2.z; + alListenerfv(AL_ORIENTATION, orientation); +} + +//-------------------------------------------------------------------------- +void alxGetListenerf(ALenum pname, ALfloat *value) +{ +#ifdef HANDLE_LISTENER_GAIN + // listener gain is handled through wrapper + if(pname == AL_GAIN) + *value = Audio::linearToDB(mMasterVolume); + else if(pname == AL_GAIN_LINEAR) + *value = mMasterVolume; + else + alGetListenerf(pname, value); +#else + alGetListenerf(pname, value); +#endif +} + +void alxGetListenerfv(ALenum pname, ALfloat *values) +{ + alGetListenerfv(pname, values); +} + +void alxGetListener3f(ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3) +{ + ALfloat val[3]; + alGetListenerfv(pname, val); + *value1 = val[0]; + *value2 = val[1]; + *value3 = val[2]; +} + +void alxGetListeneri(ALenum pname, ALint *value) +{ + alGetListeneri(pname, value); +} + +//-------------------------------------------------------------------------- +// Capture methods +//-------------------------------------------------------------------------- +void alxCaptureDestroy() +{ +#ifndef __linux + if(mCaptureInitialized) + alCaptureDestroy_EXT(); +#endif + + mEncoderStream.closeStream(); + EncoderStream::close(); + DecoderStream::close(); +} + +bool alxCaptureInit() +{ +#ifdef __linux + // Not yet supported... + return(false); +#else + alxCaptureDestroy(); + + const char *codec = Con::getVariable("$Audio::voiceCodec"); + if(!codec) + return(false); + + mCaptureInitialized = alCaptureInit_EXT(RECORD_FORMAT, RECORD_SPEED, RECORD_BUFFERSIZE); + if(mCaptureInitialized == AL_FALSE) + return(false); + + EncoderStream::open(codec); + mEncoderStream.openStream(); + DecoderStream::open(codec); + return(true); +#endif +} + +//-------------------------------------------------------------------------- +void alxCaptureStart(bool local) +{ + if(!mCaptureInitialized || mRecording) + return; + + mLocalCapture = local; + if(mLocalCapture) + { + mLocalCaptureBuffer = (U8 *)dMalloc(LOCAL_CAPTURE_SIZE); + mLocalCaptureBufferPos = 0; + Con::executef(2, "localCaptureStart", "record"); + } + else + { + GameConnection* connection = GameConnection::getServerConnection(); + if(!connection) + { + Con::errorf(ConsoleLogEntry::General, "alxCaptureStart: no server connection"); + return; + } + mEncoderStream.setConnection(connection); + } + sCaptureTimeout = Platform::getRealMilliseconds(); + +#ifndef __linux + alCaptureStart_EXT(); +#endif + mRecording = true; +} + +void scaleSamples(void * data, U32 size, U32 format, F32 scale) +{ + if((format != AL_FORMAT_MONO16) || (scale == 1.f)) + return; + + S16 * pData = (S16 *)data; + U32 samples = size >> 1; + + for(U32 i = 0; i < samples; i++) + { + S32 samp = pData[i]; + samp *= scale; + pData[i] = mClamp(samp, S16_MIN, S16_MAX); + } +} + +static void alxCaptureSend(bool flush = false) +{ +#ifndef __linux + U32 size; + U8 buffer[CAPTURE_BUFFER_SIZE]; + do { + size = alCaptureGetData_EXT(buffer, CAPTURE_BUFFER_SIZE, RECORD_FORMAT, RECORD_SPEED); + scaleSamples(buffer, size, RECORD_FORMAT, mCaptureGainScale); + mEncoderStream.setBuffer(buffer, size); + mEncoderStream.process(); + } while ((flush && size) || (size == CAPTURE_BUFFER_SIZE)); +#else + Con::warnf( ConsoleLogEntry::General, "alxCaptureSend stub"); +#endif +} + +void alxBufferCapture() +{ +#ifndef __linux + S32 len = LOCAL_CAPTURE_SIZE - mLocalCaptureBufferPos; + if(len > 0) + { + U32 size = alCaptureGetData_EXT(mLocalCaptureBuffer + mLocalCaptureBufferPos, len, RECORD_FORMAT, RECORD_SPEED); + scaleSamples(mLocalCaptureBuffer + mLocalCaptureBufferPos, size, RECORD_FORMAT, mCaptureGainScale); + mLocalCaptureBufferPos += size; + } +#else + Con::warnf( ConsoleLogEntry::General, "alxBufferCapture stub"); +#endif +} + +// called from Miles thread... must process in main thread +void localCaptureFinishedCallback(U32, bool) +{ + mLocalCaptureFinished = true; +} + +void alxCaptureStop() +{ + if(!mCaptureInitialized || !mRecording) + return; + + mRecording = false; + sCaptureTimeout = 0; +#ifndef __linux + alCaptureStop_EXT(); +#else + Con::warnf( ConsoleLogEntry::General, "alCaptureStop_EXT stub" ); +#endif + + if(mLocalCapture && mLocalCaptureBuffer) + { + Con::executef(2, "localCaptureStop", "record"); + + alGenBuffers(1, &mALCaptureBuffer); + if(alGetError() != AL_NO_ERROR) + return; + + alBufferData(mALCaptureBuffer, RECORD_FORMAT, mLocalCaptureBuffer, mLocalCaptureBufferPos, RECORD_SPEED); + dFree(mLocalCaptureBuffer); + mLocalCaptureBuffer = 0; + + U32 index; + if(findFreeSource(&index)) + { + mHandle[index] = getNewHandle() | AUDIOHANDLE_INACTIVE_BIT; + mType[index] = Audio::DefaultAudioType; + ALuint source = mSource[index]; + +#ifdef __linux + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0); +#else + alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE); +#endif + alSourcei(source, AL_SOURCE_LOOPING, AL_FALSE); + alSourcei(source, AL_BUFFER, mALCaptureBuffer); + alSourcef(source, AL_GAIN_LINEAR, mAudioTypeVolume[Audio::DefaultAudioType]); + + alSourceCallback_EXT(source, localCaptureFinishedCallback); + alxPlay(mHandle[index]); + Con::executef(2, "localCaptureStart", "play"); + } + else + alDeleteBuffers(1, &mALCaptureBuffer); + } + else + { + alxCaptureSend(true); + mEncoderStream.flush(); + } +} + +bool alxIsCapturing() +{ + return(mRecording); +} + +void alxCaptureUpdate() +{ + // check if should destroy local capture buffer + if(mLocalCaptureFinished) + { + alDeleteBuffers(1, &mALCaptureBuffer); + Con::executef(2, "localCaptureStop", "play"); + mLocalCaptureFinished = false; + } + + // check if need to stop capturing + if(mCaptureInitialized && sCaptureTimeout != 0) + { + if((Platform::getRealMilliseconds()-sCaptureTimeout) > RECORD_LEN) + alxCaptureStop(); + else + { + if(mLocalCapture) + alxBufferCapture(); + else + alxCaptureSend(); + } + } +} + +//-------------------------------------------------------------------------- +// Voice streams +//-------------------------------------------------------------------------- +Vector::iterator alxFindStream(U32 clientId, U8 streamId) +{ + Vector::iterator itr = sVoiceStreams.begin(); + for (; itr != sVoiceStreams.end(); itr++) + { + VoiceStream *vs = *itr; + if (vs->mClientId == clientId && vs->mStreamId == streamId) + return itr; + } + return NULL; +} + +Vector::iterator alxNewStream(U32 clientId, U8 streamId) +{ + // need to get a new stream + VoiceStream *vs; + if (sVoiceStreams_free.size()) + { + vs = sVoiceStreams_free.last(); + sVoiceStreams_free.pop_back(); + } + else + vs = new VoiceStream; + + vs->mClientId = clientId; + vs->mStreamId = streamId; + + vs->mDecoder.openStream(); + sVoiceStreams.push_back(vs); + return &sVoiceStreams.last(); +} + +void alxPlayStream(U32 clientId, U8 streamId) +{ + Vector::iterator itr = alxFindStream(clientId, streamId); + if (itr == NULL) + return; + + Con::evaluatef("clientCmdPlayerStartTalking(%d, 1);", clientId); + VoiceStream *vs = *itr; + + alGenBuffers(1, &vs->mBuffer); + vs->mDecoder.process(); + U8 *data; + U32 size; + vs->mDecoder.getBuffer(&data, &size); + if(size) + { + alBufferData(vs->mBuffer, RECORD_FORMAT, data, size, RECORD_SPEED); + + // find an available source, if none then (hopefully) force a cull + U32 index; + if(!findFreeSource(&index)) + if(!cullSource(&index, 2.0f)) + return; + + // clear the error state + alGetError(); + + // init and play the source + mHandle[index] = getNewHandle() | AUDIOHANDLE_VOICE_BIT; + mSourceVolume[index] = mAudioTypeVolume[Audio::VoiceAudioType]; + vs->mHandle = mHandle[index]; + ALuint source = mSource[index]; + + alSourcei(source, AL_BUFFER, vs->mBuffer); + alSourcei(source, AL_SOURCE_LOOPING, AL_FALSE); +#ifdef __linux + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0); +#else + alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE); +#endif + alSourcef(source, AL_GAIN_LINEAR, mSourceVolume[index] * mMasterVolume); + alSourcePlay(source); + } + else + Con::printf("Decode size is ZERO! client %d", clientId); + + vs->mDecoder.closeStream(); +} + +//-------------------------------------------------------------------------- +void alxReceiveVoiceStream(SimVoiceStreamEvent *event) +{ + Vector::iterator itr; + if (event->mSequence == 0) + itr = alxNewStream(event->mClientId, event->mStreamId); + else + itr = alxFindStream(event->mClientId, event->mStreamId); + + if (itr) + { + if (event->getSize()) + (*itr)->mDecoder.setBuffer(event->getData(), event->getSize()); + + if (event->getSize() < SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE) + alxPlayStream(event->mClientId, event->mStreamId); + } +} + +// Music stream: ----------------------------------------------------------- +// called from Miles thread... must process in main thread +void streamCallback(U32, bool stopped) +{ + mFinishedMusicStream = true; + mFinishedMusicStreamStopState = stopped; +} + +void alxPlayMusicStream(const char * filename) +{ + if(!alIsBuffer(mStreamBuffer)) + { + alGenBuffers(1, &mStreamBuffer); + if(alGetError() != AL_NO_ERROR) + return; + + // streamed buffers should not be managed + alBufferi_EXT(mStreamBuffer, AL_BUFFER_KEEP_RESIDENT, AL_TRUE); + } + + alxStop(mStreamHandle); + + if(!alBufferStreamFile_EXT(mStreamBuffer, (const ALubyte *)filename)) + { + const char * error = (const char *)alGetString(alGetError()); + Con::errorf(ConsoleLogEntry::General, "alxStartStream: %s", error); + } + + U32 index; + if(findFreeSource(&index)) + { + mHandle[index] = getNewHandle() | AUDIOHANDLE_INACTIVE_BIT; + mType[index] = Audio::MusicAudioType; + mSourceVolume[index] = mAudioTypeVolume[Audio::MusicAudioType]; + + ALuint source = mSource[index]; +#ifdef __linux + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0); +#else + alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE); +#endif + alSourcei(source, AL_STREAMING, AL_TRUE); + alSourcei(source, AL_BUFFER, mStreamBuffer); + alSourcef(source, AL_GAIN_LINEAR, mSourceVolume[index] * mMasterVolume); + + alSourceCallback_EXT(source, streamCallback); + alxPlay(mHandle[index]); + mStreamHandle = mHandle[index]; + } + else + { + Con::errorf(ConsoleLogEntry::General, "alxStartStream: no free sources"); + alDeleteBuffers(1, &mStreamBuffer); + } +} + +void alxStopMusicStream() +{ + alxStop(mStreamHandle); + if(alIsBuffer(mStreamBuffer)) + alDeleteBuffers(1, &mStreamBuffer); +} + +// checks if the music stream is done and calls into console.. +void alxStreamUpdate() +{ + if(mFinishedMusicStream) + { + Con::executef(2, "finishedMusicStream", mFinishedMusicStreamStopState ? "true" : "false"); + mFinishedMusicStream = false; + } +} + +//-------------------------------------------------------------------------- +// This is a somewhat crappy way of doing this, but under win32 the outer falloff +// is not always the distance at which the gain is clamped to 0 (some drivers +// ignore this setting on the dsound 3dbuffer). So, if this is enabled then +// the outer falloffs of all sources is pushed out to MAX_FALLOFF and the inner ones +// are scaled by the inner falloff scale (some hardware drivers attenuate differently) +// - sources are scaled on the play call as well +// - will trash any non-default falloffs +void alxDisableOuterFalloffs(bool disable) +{ + if(disable == mDisableOuterFalloffs) + return; + + // update all the playing sources (non-loopers cannot be reset after this) + for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) + { + if(alxIsValidHandle(mHandle[i])) + { + // only care about 3d sources... + ALint ambient = AL_FALSE; + alxGetSourcei(mHandle[i], AL_SOURCE_AMBIENT, &ambient); + if(ambient == AL_FALSE) + continue; + + if(disable) + { + ALfloat min = 1.f; + alGetSourcef(mSource[i], AL_MIN_DISTANCE, &min); + alSourcef(mSource[i], AL_MIN_DISTANCE, min * mInnerFalloffScale); + alSourcef(mSource[i], AL_MAX_DISTANCE, FORCED_OUTER_FALLOFF); + } + else + { + LoopingList::iterator itr = mLoopingList.findImage(mHandle[i]); + if(itr) + { + alSourcef(mSource[i], AL_MIN_DISTANCE, (*itr)->mDescription.mMinDistance); + alSourcef(mSource[i], AL_MAX_DISTANCE, (*itr)->mDescription.mMaxDistance); + } + } + } + } + + mDisableOuterFalloffs = disable; +} + +void alxSetInnerFalloffScale(F32 scale) +{ + mInnerFalloffScale = mClampF(scale, 0.1f, 10.f); +} + +F32 alxGetInnerFalloffScale() +{ + return(mInnerFalloffScale); +} + +//-------------------------------------------------------------------------- +// Simple metrics +//-------------------------------------------------------------------------- + +#ifdef GATHER_METRICS +static void alxGatherMetrics() +{ + S32 mNumOpenHandles = 0; + S32 mNumOpenLoopingHandles = 0; + S32 mNumOpenVoiceHandles = 0; + + S32 mNumActiveStreams = 0; + S32 mNumNullActiveStreams = 0; + S32 mNumActiveLoopingStreams = 0; + S32 mNumActiveVoiceStreams = 0; + + S32 mNumLoopingStreams = 0; + S32 mNumInactiveLoopingStreams = 0; + S32 mNumCulledLoopingStreams = 0; + + S32 mDynamicMemUsage = 0; + S32 mDynamicMemSize = 0; + S32 mMemUsage = 0; + S32 mBufferCount = 0; + S32 mDynamicBufferCount = 0; + + // count installed streams and open handles + for(U32 i = 0; i < mNumSources; i++) + { + if(mHandle[i] != NULL_AUDIOHANDLE) + { + mNumOpenHandles++; + if(mHandle[i] & AUDIOHANDLE_LOOPING_BIT) + mNumOpenLoopingHandles++; + if(mHandle[i] & AUDIOHANDLE_VOICE_BIT) + mNumOpenVoiceHandles++; + } + + ALint state = AL_STOPPED; + alGetSourcei(mSource[i], AL_SOURCE_STATE, &state); + if(state == AL_PLAYING) + { + mNumActiveStreams++; + if(mHandle[i] == NULL_AUDIOHANDLE) + mNumNullActiveStreams++; + if(mHandle[i] & AUDIOHANDLE_LOOPING_BIT) + mNumActiveLoopingStreams++; + if(mHandle[i] & AUDIOHANDLE_VOICE_BIT) + mNumActiveVoiceStreams++; + } + } + + for(LoopingList::iterator itr = mLoopingList.begin(); itr != mLoopingList.end(); itr++) + mNumLoopingStreams++; + for(LoopingList::iterator itr = mLoopingInactiveList.begin(); itr != mLoopingInactiveList.end(); itr++) + mNumInactiveLoopingStreams++; + for(LoopingList::iterator itr = mLoopingCulledList.begin(); itr != mLoopingCulledList.end(); itr++) + mNumCulledLoopingStreams++; + + alGetContexti_EXT(ALC_BUFFER_MEMORY_USAGE, &mMemUsage); + alGetContexti_EXT(ALC_BUFFER_DYNAMIC_MEMORY_SIZE, &mDynamicMemSize); + alGetContexti_EXT(ALC_BUFFER_DYNAMIC_MEMORY_USAGE, &mDynamicMemUsage); + alGetContexti_EXT(ALC_BUFFER_COUNT, &mBufferCount); + alGetContexti_EXT(ALC_BUFFER_DYNAMIC_COUNT, &mDynamicBufferCount); + + Con::setIntVariable("Audio::numOpenHandles", mNumOpenHandles); + Con::setIntVariable("Audio::numOpenLoopingHandles", mNumOpenLoopingHandles); + Con::setIntVariable("Audio::numOpenVoiceHandles", mNumOpenVoiceHandles); + + Con::setIntVariable("Audio::numActiveStreams", mNumActiveStreams); + Con::setIntVariable("Audio::numNullActiveStreams", mNumNullActiveStreams); + Con::setIntVariable("Audio::numActiveLoopingStreams", mNumActiveLoopingStreams); + Con::setIntVariable("Audio::numActiveVoiceStreams", mNumActiveVoiceStreams); + + Con::setIntVariable("Audio::numLoopingStreams", mNumLoopingStreams); + Con::setIntVariable("Audio::numInactiveLoopingStreams", mNumInactiveLoopingStreams); + Con::setIntVariable("Audio::numCulledLoopingStreams", mNumCulledLoopingStreams); + + Con::setIntVariable("Audio::memUsage", mMemUsage >> 10); + Con::setIntVariable("Audio::dynamicMemSize", mDynamicMemSize >> 10); + Con::setIntVariable("Audio::dynamicMemUsage", mDynamicMemUsage >> 10); + Con::setIntVariable("Audio::bufferCount", mBufferCount); + Con::setIntVariable("Audio::dynamicBufferCount", mDynamicBufferCount); +} +#endif + +//-------------------------------------------------------------------------- +// Audio Update... +//-------------------------------------------------------------------------- +void alxLoopingUpdate() +{ + static LoopingList culledList; + + S32 updateTime = Platform::getRealMilliseconds(); + + // check if can wakeup the inactive loopers + if(mLoopingCulledList.size()) + { + Point3F listener; + alxListenerGetPoint3F(AL_POSITION, &listener); + + // get the 'sort' value for this sound (could be based on time played...), + // and add to the culled list + LoopingList::iterator itr; + culledList.clear(); + + for(itr = mLoopingCulledList.begin(); itr != mLoopingCulledList.end(); itr++) + { + if((*itr)->mScore <= MIN_UNCULL_GAIN) + continue; + + if((updateTime - (*itr)->mCullTime) < MIN_UNCULL_PERIOD) + continue; + + culledList.push_back(*itr); + } + + if(!culledList.size()) + return; + + U32 index = MAX_AUDIOSOURCES; + + if(culledList.size() > 1) + culledList.sort(); + + for(itr = culledList.begin(); itr != culledList.end(); itr++) + { + if(!findFreeSource(&index)) + { + // score does not include master volume + if(!cullSource(&index, (*itr)->mScore)) + break; + + // check buffer + if(!bool((*itr)->mBuffer)) + { + // remove from culled list + LoopingList::iterator tmp; + tmp = mLoopingCulledList.findImage((*itr)->mHandle); + AssertFatal(tmp, "alxLoopingUpdate: failed to find culled source"); + mLoopingCulledList.erase_fast(tmp); + + // remove from looping list (and free) + tmp = mLoopingList.findImage((*itr)->mHandle); + if(tmp) + { + (*tmp)->clear(); + mLoopingFreeList.push_back(*tmp); + mLoopingList.erase_fast(tmp); + } + + continue; + } + } + + // remove from culled list + LoopingList::iterator tmp = mLoopingCulledList.findImage((*itr)->mHandle); + AssertFatal(tmp, "alxLoopingUpdate: failed to find culled source"); + mLoopingCulledList.erase_fast(tmp); + + // restore all state data + mHandle[index] = (*itr)->mHandle; + mBuffer[index] = (*itr)->mBuffer; + mScore[index] = (*itr)->mScore; + mSourceVolume[index] = (*itr)->mDescription.mVolume; + mType[index] = (*itr)->mDescription.mType; + mpEffectDescription[index] = (*itr)->mpEffectDescription; + mSampleEnvironment[index] = (*itr)->mEnvironment; + + ALuint source = mSource[index]; + + // setup play info + alGetError(); + + alxSourcePlay(source, *itr); + if(mEnvironmentEnabled) + alxSourceEnvironment(source, *itr); + + alxPlay(mHandle[index]); + } + } +} + +//-------------------------------------------------------------------------- +void alxCloseHandles() +{ + for(U32 i = 0; i < mNumSources; i++) + { + if(mHandle[i] & AUDIOHANDLE_LOADING_BIT) + continue; + + if(mHandle[i] == NULL_AUDIOHANDLE) + continue; + + ALint state = 0; + alGetSourcei(mSource[i], AL_SOURCE_STATE, &state); + if(state == AL_PLAYING) + continue; + + // voice + if(mHandle[i] & AUDIOHANDLE_VOICE_BIT) + { + VectorPtr::iterator itr = alxFindVoiceStream(mHandle[i]); + if(itr) + alxFreeVoiceStream(itr); + } + else if(!(mHandle[i] & AUDIOHANDLE_INACTIVE_BIT)) + { + // should be playing? must have encounted an error.. remove + LoopingList::iterator itr = mLoopingList.findImage(mHandle[i]); + if(itr && !((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT)) + { + AssertFatal(!mLoopingInactiveList.findImage((*itr)->mHandle), "alxCloseHandles: image incorrectly in inactive list"); + AssertFatal(!mLoopingCulledList.findImage((*itr)->mHandle), "alxCloseHandles: image already in culled list"); + mLoopingCulledList.push_back(*itr); + (*itr)->mHandle |= AUDIOHANDLE_INACTIVE_BIT; + + mHandle[i] = NULL_AUDIOHANDLE; + mBuffer[i] = 0; + } + } + + mHandle[i] = NULL_AUDIOHANDLE; + mBuffer[i] = 0; + } +} + +//---------------------------------------------------------------------------------- +// - update the score for each audio source. this is used for culing sources. +// normal ranges are between 0.f->1.f, voice/loading/music streams are scored +// outside this range so that they will not be culled +// - does not scale by attenuated volumes +void alxUpdateScores(bool sourcesOnly) +{ + Point3F listener; + alxGetListener3f(AL_POSITION, &listener.x, &listener.y, &listener.z); + + // do the base sources + for(U32 i = 0; i < mNumSources; i++) + { + if(mHandle[i] == NULL_AUDIOHANDLE) + { + mScore[i] = 0.f; + continue; + } + + // thread loading buffer or voice buffer? + if(mHandle[i] & (AUDIOHANDLE_LOADING_BIT|AUDIOHANDLE_VOICE_BIT)) + { + mScore[i] = 3.f; + continue; + } + + // streaming? + ALint val = AL_FALSE; + alGetSourcei(mSource[i], AL_STREAMING, &val); + if(val == AL_TRUE) + { + mScore[i] = 3.f; + continue; + } + + // grab the volume.. (not attenuated by master for score) + F32 volume = mSourceVolume[i] * mAudioTypeVolume[mType[i]]; + + // 3d? + val = AL_FALSE; +#ifdef __linux + alGetSourcei(mSource[i], AL_SOURCE_RELATIVE, &val); +#else + alGetSourcei(mSource[i], AL_SOURCE_AMBIENT, &val); +#endif + + if(val == AL_FALSE) + { + // approximate 3d volume + Point3F pos; + alGetSourcefv(mSource[i], AL_POSITION, (ALfloat * )((F32*)pos)); + + ALfloat min, max; + alGetSourcef(mSource[i], AL_MIN_DISTANCE, &min); + alGetSourcef(mSource[i], AL_MAX_DISTANCE, &max); + + pos -= listener; + F32 dist = pos.magnitudeSafe(); + + if(dist >= max) + mScore[i] = 0.f; + else if(dist > min) + mScore[i] = min / dist; + else + mScore[i] = volume; + } + else + mScore[i] = volume; + } + + if(sourcesOnly) + return; + + S32 updateTime = Platform::getRealMilliseconds(); + + // update the loopers + for(LoopingList::iterator itr = mLoopingList.begin(); itr != mLoopingList.end(); itr++) + { + if(!((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT)) + continue; + + if((updateTime - (*itr)->mCullTime) < MIN_UNCULL_PERIOD) + continue; + + if((*itr)->mDescription.mIs3D) + { + Point3F pos = (*itr)->mPosition - listener; + F32 dist = pos.magnitudeSafe(); + + F32 min = (*itr)->mDescription.mMinDistance; + F32 max = (*itr)->mDescription.mMaxDistance; + + if(dist >= max) + (*itr)->mScore = 0.f; + else if(dist > min) + (*itr)->mScore = (*itr)->mDescription.mVolume * (min / dist); + else + (*itr)->mScore = (*itr)->mDescription.mVolume; + } + else + (*itr)->mScore = (*itr)->mDescription.mVolume; + + // attenuate by the channel gain + (*itr)->mScore *= mAudioTypeVolume[(*itr)->mDescription.mType]; + } +} + +// the directx buffers are set to mute at max distance, but many of the providers seem to +// ignore this flag... that is why this is here +void alxUpdateMaxDistance() +{ +#ifndef __linux + Point3F listener; + alxGetListener3f(AL_POSITION, &listener.x, &listener.y, &listener.z); + + for(U32 i = 0; i < mNumSources; i++) + { + if(mHandle[i] == NULL_AUDIOHANDLE) + continue; + + ALint val = AL_FALSE; + alGetSourcei(mSource[i], AL_SOURCE_AMBIENT, &val); + if(val == AL_TRUE) + continue; + + val = AL_FALSE; + alGetSourcei(mSource[i], AL_STREAMING, &val); + if(val == AL_TRUE) + continue; + + Point3F pos; + alGetSourcefv(mSource[i], AL_POSITION, (F32*)pos); + + F32 dist = 0.f; + alGetSourcef(mSource[i], AL_MAX_DISTANCE, &dist); + + pos -= listener; + dist -= pos.len(); + + alSourcef(mSource[i], AL_GAIN_LINEAR, (dist < 0.f) ? 0.f : mSourceVolume[i] * mAudioTypeVolume[mType[i]] * mMasterVolume); + } +#endif +} + +//-------------------------------------------------------------------------- +// Called to update alx system +//-------------------------------------------------------------------------- +void alxUpdate() +{ + if(mForceMaxDistanceUpdate) + alxUpdateMaxDistance(); + + alxCloseHandles(); + alxUpdateScores(false); + alxLoopingUpdate(); + alxCaptureUpdate(); + alxStreamUpdate(); +#ifndef DISABLE_AUDIO_THREAD + AudioThread::process(); +#endif + +#ifdef GATHER_METRICS + alxGatherMetrics(); +#endif + +#ifdef __linux + alxFakeCallbackUpdate(); +#endif +} + +//-------------------------------------------------------------------------- +// Misc +//-------------------------------------------------------------------------- +// client-side function only +ALuint alxGetWaveLen(ALuint buffer) +{ + if(buffer == AL_INVALID) + return(0); + + ALint frequency = 0; + ALint bits = 0; + ALint channels = 0; + ALint size; + + alGetBufferi(buffer, AL_FREQUENCY, &frequency); + alGetBufferi(buffer, AL_BITS, &bits); + alGetBufferi(buffer, AL_CHANNELS, &channels); + alGetBufferi(buffer, AL_SIZE, &size); + + if(!frequency || !bits || !channels) + { + Con::errorf(ConsoleLogEntry::General, "alxGetWaveLen: invalid buffer"); + return(0); + } + + F64 len = (F64(size) * 8000.f) / F64(frequency * bits * channels); + return(len); +} + +//-------------------------------------------------------------------------- +// Console functions +//-------------------------------------------------------------------------- +static void cAudio_detect(SimObject *, S32, const char **) +{ + Audio::detect(); +} + +static void cAudio_destroy(SimObject *, S32, const char **) +{ + Audio::destroy(); +} + +static bool cAudio_setDriver(SimObject *, S32, const char *argv[]) +{ + return(Audio::setDriver(argv[1])); +} + +//-------------------------------------------------------------------------- +static S32 cAudio_alxCreateSource(SimObject *, S32 argc, const char *argv[]) +{ + AudioDescription *description = NULL; + AudioProfile *profile = dynamic_cast( Sim::findObject( argv[1] ) ); + if (profile == NULL) + { + description = dynamic_cast( Sim::findObject( argv[1] ) ); + if (description == NULL) + { + Con::printf("Unable to locate audio profile/description '%s'", argv[1]); + return NULL_AUDIOHANDLE; + } + } + + if (profile) + { + if (argc == 2) + return alxCreateSource(profile); + + MatrixF transform; + transform.set(EulerF(0,0,0), Point3F( dAtof(argv[2]),dAtof(argv[3]),dAtof(argv[4]) )); + return alxCreateSource(profile, &transform); + } + + if (description) + { + if (argc == 3) + return alxCreateSource(description, argv[2]); + + MatrixF transform; + transform.set(EulerF(0,0,0), Point3F( dAtof(argv[3]),dAtof(argv[4]),dAtof(argv[5]) )); + return alxCreateSource(description, argv[2], &transform); + } + + return NULL_AUDIOHANDLE; +} + +//-------------------------------------------------------------------------- +// Expose all al get/set methods... +//-------------------------------------------------------------------------- +enum { + Source = BIT(0), + Listener = BIT(1), + Context = BIT(2), + Environment = BIT(3), + Get = BIT(4), + Set = BIT(5), + Int = BIT(6), + Float = BIT(7), + Float3 = BIT(8), + Float6 = BIT(9), + DebugGet = BIT(10), + DebugSet = BIT(11), + Debug = (DebugGet|DebugSet), +}; + +static ALenum getEnum(const char * name, U32 flags) +{ + AssertFatal(name, "getEnum: bad param"); + + static struct { + char * mName; + ALenum mAlenum; + U32 mFlags; + } table[] = { + //----------------------------------------------------------------------------------------------------------------- + // "name" ENUM Flags + //----------------------------------------------------------------------------------------------------------------- + { "AL_PAN", AL_PAN, (Source|Get|Set|Float) }, + { "AL_GAIN", AL_GAIN, (Source|Listener|Get|Set|Float) }, + { "AL_GAIN_LINEAR", AL_GAIN_LINEAR, (Source|Listener|Get|Set|Float) }, + { "AL_PITCH", AL_PITCH, (Source|Get|Set|Float) }, + { "AL_MIN_DISTANCE", AL_MIN_DISTANCE, (Source|Get|Set|Float) }, + { "AL_MAX_DISTANCE", AL_MAX_DISTANCE, (Source|Get|Set|Float) }, + { "AL_CONE_OUTER_GAIN", AL_CONE_OUTER_GAIN, (Source|Get|Set|Float) }, + { "AL_POSITION", AL_POSITION, (Source|Listener|Get|Set|Float3) }, + { "AL_DIRECTION", AL_DIRECTION, (Source|Get|Set|Float3) }, + { "AL_VELOCITY", AL_VELOCITY, (Source|Listener|Get|Set|Float3) }, + { "AL_ORIENTATION", AL_ORIENTATION, (Listener|Set|Float6) }, + { "AL_CONE_INNER_ANGLE", AL_CONE_INNER_ANGLE, (Source|Get|Set|Int) }, + { "AL_CONE_OUTER_ANGLE", AL_CONE_OUTER_ANGLE, (Source|Get|Set|Int) }, + { "AL_SOURCE_LOOPING", AL_SOURCE_LOOPING, (Source|Get|Set|Int) }, + { "AL_STREAMING", AL_STREAMING, (Source|Get|Set|Int) }, + { "AL_BUFFER", AL_BUFFER, (Source|Get|Set|Int) }, +#ifdef __linux + // can't 1 to 1 map this because we need to set the position +#endif + { "AL_SOURCE_AMBIENT", AL_SOURCE_AMBIENT, (Source|Get|Set|Int) }, + + { "ALC_PROVIDER", ALC_PROVIDER, (Context|Get|Set|Int) }, + { "ALC_PROVIDER_COUNT", ALC_PROVIDER_COUNT, (Context|Get|Int) }, + { "ALC_PROVIDER_NAME", ALC_PROVIDER_NAME, (Context|Get|Int) }, + { "ALC_SPEAKER", ALC_SPEAKER, (Context|Get|Set|Int) }, + { "ALC_SPEAKER_COUNT", ALC_SPEAKER_COUNT, (Context|Get|Int) }, + { "ALC_SPEAKER_NAME", ALC_SPEAKER_NAME, (Context|Get|Int) }, + { "ALC_BUFFER_DYNAMIC_MEMORY_SIZE", ALC_BUFFER_DYNAMIC_MEMORY_SIZE, (Context|Get|Set|Int) }, + { "ALC_BUFFER_DYNAMIC_MEMORY_USAGE",ALC_BUFFER_DYNAMIC_MEMORY_USAGE, (Context|Get|Int) }, + { "ALC_BUFFER_DYNAMIC_COUNT", ALC_BUFFER_DYNAMIC_COUNT, (Context|Get|Int) }, + { "ALC_BUFFER_MEMORY_USAGE", ALC_BUFFER_MEMORY_USAGE, (Context|Get|Int) }, + { "ALC_BUFFER_COUNT", ALC_BUFFER_COUNT, (Context|Get|Int) }, + { "ALC_BUFFER_LATENCY", ALC_BUFFER_LATENCY, (Context|Get|Int) }, + + // environment + { "AL_ENV_ROOM_IASIG", AL_ENV_ROOM_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_ROOM_HIGH_FREQUENCY_IASIG", AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_REFLECTIONS_IASIG", AL_ENV_REFLECTIONS_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_REVERB_IASIG", AL_ENV_REVERB_IASIG, (Environment|Get|Set|Int) }, + { "AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG", AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DECAY_TIME_IASIG", AL_ENV_DECAY_TIME_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG", AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_REFLECTIONS_DELAY_IASIG", AL_ENV_REFLECTIONS_DELAY_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_REVERB_DELAY_IASIG", AL_ENV_REVERB_DELAY_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DIFFUSION_IASIG", AL_ENV_DIFFUSION_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_DENSITY_IASIG", AL_ENV_DENSITY_IASIG, (Environment|Get|Set|Float) }, + { "AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG", AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG, (Environment|Get|Set|Float) }, + + { "AL_ENV_ROOM_VOLUME_EXT", AL_ENV_ROOM_VOLUME_EXT, (Environment|Get|Set|Int) }, + { "AL_ENV_FLAGS_EXT", AL_ENV_FLAGS_EXT, (Environment|Get|Set|Int) }, + { "AL_ENV_EFFECT_VOLUME_EXT", AL_ENV_EFFECT_VOLUME_EXT, (Environment|Get|Set|Float) }, + { "AL_ENV_DAMPING_EXT", AL_ENV_DAMPING_EXT, (Environment|Get|Set|Float) }, + { "AL_ENV_ENVIRONMENT_SIZE_EXT", AL_ENV_ENVIRONMENT_SIZE_EXT, (Environment|Get|Set|Float) }, + + // sample environment + { "AL_ENV_SAMPLE_DIRECT_EXT", AL_ENV_SAMPLE_DIRECT_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_DIRECT_HF_EXT", AL_ENV_SAMPLE_DIRECT_HF_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_ROOM_EXT", AL_ENV_SAMPLE_ROOM_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_ROOM_HF_EXT", AL_ENV_SAMPLE_ROOM_HF_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT", AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, (Source|Get|Set|Int) }, + { "AL_ENV_SAMPLE_FLAGS_EXT", AL_ENV_SAMPLE_FLAGS_EXT, (Source|Get|Set|Int) }, + + { "AL_ENV_SAMPLE_REVERB_MIX_EXT", AL_ENV_SAMPLE_REVERB_MIX_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OBSTRUCTION_EXT", AL_ENV_SAMPLE_OBSTRUCTION_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT", AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OCCLUSION_EXT", AL_ENV_SAMPLE_OCCLUSION_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT", AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, (Source|Get|Set|Float) }, + { "AL_ENV_SAMPLE_AIR_ABSORPTION_EXT", AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, (Source|Get|Set|Float) }, + }; + + for(U32 i = 0; i < (sizeof(table) / sizeof(table[0])); i++) + { +#ifdef DEBUG + if(((table[i].mFlags & ~Debug) & flags) != flags) + continue; +#else + if((table[i].mFlags & flags) != flags) + continue; +#endif + if(!dStricmp(table[i].mName, name)) + return(table[i].mAlenum); + } + + return(AL_INVALID); +} + +//-------------------------------------------------------------------------- +// Source +//-------------------------------------------------------------------------- +static void cAudio_alxSourcef(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Set|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcef: invalid enum name '%s'", argv[2]); + return; + } + + alxSourcef(dAtoi(argv[1]), e, dAtof(argv[3])); +} + +static void cAudio_alxSource3f(SimObject *, S32 argc, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Set|Float3)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: invalid enum name '%s'", argv[2]); + return; + } + + if(argc != 3 || argc != 6) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: wrong number of args"); + return; + } + + Point3F pos; + if(argc == 3) + dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z); + else + { + pos.x = dAtof(argv[1]); + pos.y = dAtof(argv[2]); + pos.z = dAtof(argv[3]); + } + + alxSource3f(dAtoi(argv[1]), e, pos.x, pos.y, pos.z); +} + +static void cAudio_alxSourcei(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Set|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcei: invalid enum name '%s'", argv[2]); + return; + } + + alxSourcei(dAtoi(argv[1]), e, dAtoi(argv[3])); +} + +//-------------------------------------------------------------------------- + +static F32 cAudio_alxGetSourcef(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcef: invalid enum name '%s'", argv[2]); + return(0.f); + } + + F32 value; + alxGetSourcef(dAtoi(argv[1]), e, &value); + return(value); +} + +static const char * cAudio_alxGetSource3f(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSource3f: invalid enum name '%s'", argv[2]); + return("0 0 0"); + } + + F32 value1, value2, value3; + alxGetSource3f(dAtoi(argv[1]), e, &value1, &value2, &value3); + + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3); + return(ret); +} + +static S32 cAudio_alxGetSourcei(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcei: invalid enum name '%s'", argv[2]); + return(0); + } + + S32 value; + alxGetSourcei(dAtoi(argv[1]), e, &value); + return(value); +} + +//-------------------------------------------------------------------------- +// Listener +//-------------------------------------------------------------------------- +static void cAudio_alxListenerf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Listener|Set|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxListenerf: invalid enum name '%s'", argv[1]); + return; + } + + alxListenerf(e, dAtof(argv[2])); +} + +static void cAudio_alxListener3f(SimObject *, S32 argc, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Listener|Set|Float3)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxListener3f: invalid enum name '%s'", argv[1]); + return; + } + + if(argc != 3 || argc != 5) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxListener3f: wrong number of args"); + return; + } + + Point3F pos; + if(argc == 3) + dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z); + else + { + pos.x = dAtof(argv[2]); + pos.y = dAtof(argv[3]); + pos.z = dAtof(argv[4]); + } + + alxListener3f(e, pos.x, pos.y, pos.z); +} + +//-------------------------------------------------------------------------- +static F32 cAudio_alxGetListenerf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetListenerf: invalid enum name '%s'", argv[1]); + return(0.f); + } + + F32 value; + alxGetListenerf(e, &value); + return(value); +} + +static const char * cAudio_alxGetListener3f(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[2], (Source|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetListener3f: invalid enum name '%s'", argv[1]); + return("0 0 0"); + } + + F32 value1, value2, value3; + alxGetListener3f(e, &value1, &value2, &value3); + + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3); + return(ret); +} + +static S32 cAudio_alxGetListeneri(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Source|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetListeneri: invalid enum name '%s'", argv[1]); + return(0); + } + + S32 value; + alxGetListeneri(e, &value); + return(value); +} + +//-------------------------------------------------------------------------- +// Play/Stop +//-------------------------------------------------------------------------- +static S32 cAudio_alxPlay(SimObject *, S32 argc, const char *argv[]) +{ + if (argc == 2) + { + AUDIOHANDLE handle = dAtoi(argv[1]); + return alxPlay(handle); + } + + AudioProfile *profile = dynamic_cast( Sim::findObject( argv[1] ) ); + if (profile == NULL) + { + Con::printf("Unable to locate audio profile '%s'", argv[1]); + return NULL_AUDIOHANDLE; + } + + Point3F pos(0.f, 0.f, 0.f); + if(argc == 3) + dSscanf(argv[2], "%f %f %f", &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); + + return alxPlay(profile, &transform, NULL); +} + +static void cAudio_alxStop(SimObject *, S32, const char ** argv) +{ + AUDIOHANDLE handle = dAtoi(argv[1]); + if(handle == NULL_AUDIOHANDLE) + return; + alxStop(handle); +} + +static void cAudio_alxStopAll(SimObject *, S32, const char **) +{ + alxStopAll(); +} + +//-------------------------------------------------------------------------- +// Capture +//-------------------------------------------------------------------------- +static bool cAudio_alxCaptureInit(SimObject *, S32, const char **) +{ + return(alxCaptureInit()); +} + +static void cAudio_alxCaptureDestroy(SimObject *, S32, const char **) +{ + alxCaptureDestroy(); +} + +static void cAudio_alxCaptureStart(SimObject *, S32 argc, const char ** argv) +{ + alxCaptureStart((argc == 2) ? dAtob(argv[1]) : false); +} + +static void cAudio_alxCaptureStop(SimObject *, S32, const char **) +{ + alxCaptureStop(); +} + +static bool cAudio_alxIsCapturing(SimObject *, S32, const char **) +{ + return(alxIsCapturing()); +} + +//-------------------------------------------------------------------------- +// Environment: +//-------------------------------------------------------------------------- +void alxEnvironmenti(ALenum pname, ALint value) +{ + alEnvironmentiIASIG(mEnvironment, pname, value); +} + +void alxEnvironmentf(ALenum pname, ALfloat value) +{ + alEnvironmentfIASIG(mEnvironment, pname, value); +} + +void alxGetEnvironmenti(ALenum pname, ALint * value) +{ + alGetEnvironmentiIASIG_EXT(mEnvironment, pname, value); +} + +void alxGetEnvironmentf(ALenum pname, ALfloat * value) +{ + alGetEnvironmentfIASIG_EXT(mEnvironment, pname, value); +} + +void alxEnableEnvironmental(bool enable) +{ + if(mEnvironmentEnabled == enable) + return; + + // go through the playing sources and update their reverb mix + // - only 3d samples get environmental fx + // - only loopers can reenable fx + for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) + { + if(mHandle[i] == NULL_AUDIOHANDLE) + continue; + + ALint val = AL_FALSE; + + // 3d? + alGetSourcei(mSource[i], AL_SOURCE_AMBIENT, &val); + if(val == AL_TRUE) + continue; + + // stopped? + val = AL_STOPPED; + alGetSourcei(mSource[i], AL_SOURCE_STATE, &val); + + // only looping sources can reenable environmental effects (no description around + // for the non-loopers) + if(enable) + { + LoopingList::iterator itr = mLoopingList.findImage(mHandle[i]); + if(!itr) + continue; + + alSourcef(mSource[i], AL_ENV_SAMPLE_REVERB_MIX_EXT, (*itr)->mDescription.mEnvironmentLevel); + } + else + alSourcef(mSource[i], AL_ENV_SAMPLE_REVERB_MIX_EXT, 0.f); + } + + mEnvironmentEnabled = enable; +} + +void alxSetEnvironment(const AudioEnvironment * env) +{ + mCurrentEnvironment = const_cast(env); + + // reset environmental audio? + if(!env) + { + alxEnvironmenti(AL_ENV_ROOM_IASIG, AL_ENVIRONMENT_GENERIC); + return; + } + + // room trashes all the values + if(env->mUseRoom) + { + alxEnvironmenti(AL_ENV_ROOM_IASIG, env->mRoom); + return; + } + + // set all the params + alxEnvironmenti(AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, env->mRoomHF); + alxEnvironmenti(AL_ENV_REFLECTIONS_IASIG, env->mReflections); + alxEnvironmenti(AL_ENV_REVERB_IASIG, env->mReverb); + + alxEnvironmentf(AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, env->mRoomRolloffFactor); + alxEnvironmentf(AL_ENV_DECAY_TIME_IASIG, env->mDecayTime); + alxEnvironmentf(AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, env->mDecayTime); + alxEnvironmentf(AL_ENV_REFLECTIONS_DELAY_IASIG, env->mReflectionsDelay); + alxEnvironmentf(AL_ENV_REVERB_DELAY_IASIG, env->mReverbDelay); + alxEnvironmentf(AL_ENV_DENSITY_IASIG, env->mAirAbsorption); + alxEnvironmentf(AL_ENV_DIFFUSION_IASIG, env->mEnvironmentDiffusion); + + // ext: + alxEnvironmenti(AL_ENV_ROOM_VOLUME_EXT, env->mRoomVolume); + alxEnvironmenti(AL_ENV_FLAGS_EXT, env->mFlags); + + alxEnvironmentf(AL_ENV_EFFECT_VOLUME_EXT, env->mEffectVolume); + alxEnvironmentf(AL_ENV_DAMPING_EXT, env->mDamping); + alxEnvironmentf(AL_ENV_ENVIRONMENT_SIZE_EXT, env->mEnvironmentSize); +} + +const AudioEnvironment * alxGetEnvironment() +{ + return(mCurrentEnvironment); +} + +//-------------------------------------------------------------------------- +static void cAudio_alxEnvironmenti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Set|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmenti: invalid enum name '%s'", argv[1]); + return; + } + + alxEnvironmenti(e, dAtoi(argv[2])); +} + +static void cAudio_alxEnvironmentf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Set|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmentf: invalid enum name '%s'", argv[1]); + return; + } + + alxEnvironmenti(e, dAtof(argv[2])); +} + +static S32 cAudio_alxGetEnvironmenti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmenti: invalid enum name '%s'", argv[1]); + return(0); + } + + S32 value; + alxGetEnvironmenti(e, &value); + return(value); +} + +static F32 cAudio_alxGetEnvironmentf(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Environment|Get|Float)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmentf: invalid enum name '%s'", argv[1]); + return(0.f); + } + + F32 value; + alxGetEnvironmentf(e, &value); + return(value); +} + +static void cAudio_alxSetEnvironment(SimObject *, S32, const char ** argv) +{ + AudioEnvironment * environment = dynamic_cast(Sim::findObject(argv[1])); + alxSetEnvironment(environment); +} + +static void cAudio_alxEnableEnvironmental(SimObject *, S32, const char ** argv) +{ + alxEnableEnvironmental(dAtob(argv[1])); +} + +//-------------------------------------------------------------------------- +// Misc +//-------------------------------------------------------------------------- +static S32 cAudio_alxGetWaveLen(SimObject *, S32, const char ** argv) +{ + // filename or profile? + AudioProfile * profile = 0; + const char * fileName = 0; + + if(!dStrrchr(argv[1], '.')) + { + profile = dynamic_cast(Sim::findObject(argv[1])); + if(!profile) + { + Con::errorf(ConsoleLogEntry::General, "Unable to locate audio profile: '%s'", argv[1]); + return(0); + } + + fileName = profile->mFilename; + } + else + fileName = argv[1]; + + Resource buffer = AudioBuffer::find(fileName); + if(!bool(buffer)) + { + Con::errorf(ConsoleLogEntry::General, "Failed to find wave file: '%s'", fileName); + return(0); + } + + ALuint alBuffer = buffer->getALBuffer(true); + if(alBuffer == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetWaveLen: invalid buffer"); + return(0); + } + return(alxGetWaveLen(alBuffer)); +} + +static const char* cGetAudioDriverList( SimObject*, S32, const char** ) +{ + return( Audio::getDriverListString() ); +} + +static const char* cGetAudioDriverInfo( SimObject*, S32, const char** ) +{ + return( Audio::getCurrentDriverInfo() ); +} + +static F32 cAudio_alxGetChannelVolume(SimObject *, S32, const char ** argv) +{ + U32 type = dAtoi(argv[1]); + if(type >= Audio::NumAudioTypes) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetChannelVolume: invalid channel '%d'", dAtoi(argv[1])); + return(0.f); + } + + return(mAudioTypeVolume[type]); +} + +static void cAudio_alxSetChannelVolume(SimObject *, S32, const char ** argv) +{ + U32 type = dAtoi(argv[1]); + F32 volume = mClampF(dAtof(argv[2]), 0.f, 1.f); + + if(type >= Audio::NumAudioTypes) + Con::errorf(ConsoleLogEntry::General, "cAudio_alxSetChannelVolume: invalid channel '%d'", dAtoi(argv[1])); + else + { + mAudioTypeVolume[type] = volume; + alxUpdateTypeGain(1 << type); + } +} + +static void cAudio_alxSetCaptureGainScale(SimObject *, S32, const char ** argv) +{ + mCaptureGainScale = mClampF(dAtof(argv[1]), MIN_CAPTURE_SCALE, MAX_CAPTURE_SCALE); +} + +static F32 cAudio_alxGetCaptureGainScale(SimObject *, S32, const char **) +{ + return(mCaptureGainScale); +} + +static void cAudio_alxForceMaxDistanceUpdate(SimObject *, S32, const char ** argv) +{ + mForceMaxDistanceUpdate = dAtob(argv[1]); +} + +static void cAudio_alxDisableOuterFalloffs(SimObject *, S32, const char ** argv) +{ + alxDisableOuterFalloffs(dAtob(argv[1])); +} + +static void cAudio_alxSetInnerFalloffScale(SimObject *, S32, const char ** argv) +{ + alxSetInnerFalloffScale(dAtof(argv[1])); +} + +static F32 cAudio_alxGetInnerFalloffScale(SimObject *, S32, const char **) +{ + return(alxGetInnerFalloffScale()); +} + +// Music: ---------------------------------------------------------------- +static void cAudio_alxPlayMusic(SimObject *, S32, const char ** argv) +{ + alxPlayMusicStream(StringTable->insert(argv[1])); +} + +static void cAudio_alxStopMusic(SimObject *, S32, const char **) +{ + alxStopMusicStream(); +} + +// Context: ---------------------------------------------------------------- +void alxContexti(ALenum pname, ALint value) +{ + alContexti_EXT(pname, value); +} + +void alxGetContexti(ALenum pname, ALint * value) +{ + alGetContexti_EXT(pname, value); +} + +void alxGetContextstr(ALenum pname, ALuint idx, ALubyte ** value) +{ + alGetContextstr_EXT(pname, idx, value); +} + +static void cAudio_alxContexti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Context|Set|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxContexti: invalid enum name '%s'", argv[1]); + return; + } + + alxContexti(e, dAtoi(argv[2])); +} + +static S32 cAudio_alxGetContexti(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Context|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContexti: invalid enum name '%s'", argv[2]); + return(0); + } + + ALint value = 0; + alxGetContexti(e, &value); + return(value); +} + +static const char * cAudio_alxGetContextstr(SimObject *, S32, const char ** argv) +{ + ALenum e = getEnum(argv[1], (Context|Get|Int)); + if(e == AL_INVALID) + { + Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContextstr: invalid enum name '%s'", argv[2]); + return(""); + } + + ALubyte * str = (ALubyte *)""; + alxGetContextstr(e, dAtoi(argv[2]), &str); + return(StringTable->insert((const char *)str)); +} + +static bool cAudio_isEnabled(SimObject *, S32, const char ** argv) +{ + if(!dStricmp(argv[1], "system")) + return(mInitialized); + if(!dStricmp(argv[1], "capture")) + return(mCaptureInitialized); + if(!dStricmp(argv[1], "environment_iasig")) + return(mEnvironment && mEnvironmentEnabled); + return(false); +} + +static bool cAudio_isExtensionPresent(SimObject *, S32, const char ** argv) +{ + return((bool)alIsExtensionPresent((const ALubyte *)argv[1])); +} + + +// Namespace: Audio --------------------------------------------------------- +namespace Audio +{ + +//--------------------------------------------------------------------------- +// the following db<->linear conversion functions come from Loki openAL linux driver +// code, here more for completeness than anything else (all current audio code +// uses AL_GAIN_LINEAR)... in Audio:: so that looping updates and audio channel updates +// can convert gain types and to give the miles driver access +static const F32 logtab[] = { + 0.00, 0.001, 0.002, 0.003, 0.004, + 0.005, 0.01, 0.011, 0.012, 0.013, + 0.014, 0.015, 0.016, 0.02, 0.021, + 0.022, 0.023, 0.024, 0.025, 0.03, + 0.031, 0.032, 0.033, 0.034, 0.04, + 0.041, 0.042, 0.043, 0.044, 0.05, + 0.051, 0.052, 0.053, 0.054, 0.06, + 0.061, 0.062, 0.063, 0.064, 0.07, + 0.071, 0.072, 0.073, 0.08, 0.081, + 0.082, 0.083, 0.084, 0.09, 0.091, + 0.092, 0.093, 0.094, 0.10, 0.101, + 0.102, 0.103, 0.11, 0.111, 0.112, + 0.113, 0.12, 0.121, 0.122, 0.123, + 0.124, 0.13, 0.131, 0.132, 0.14, + 0.141, 0.142, 0.143, 0.15, 0.151, + 0.152, 0.16, 0.161, 0.162, 0.17, + 0.171, 0.172, 0.18, 0.181, 0.19, + 0.191, 0.192, 0.20, 0.201, 0.21, + 0.211, 0.22, 0.221, 0.23, 0.231, + 0.24, 0.25, 0.251, 0.26, 0.27, + 0.271, 0.28, 0.29, 0.30, 0.301, + 0.31, 0.32, 0.33, 0.34, 0.35, + 0.36, 0.37, 0.38, 0.39, 0.40, + 0.41, 0.43, 0.50, 0.60, 0.65, + 0.70, 0.75, 0.80, 0.85, 0.90, + 0.95, 0.97, 0.99 }; +const int logmax = sizeof logtab / sizeof *logtab; + +F32 DBToLinear(F32 value) +{ + if(value <= 0.f) + return(0.f); + if(value >= 1.f) + return(1.f); + + S32 max = logmax; + S32 min = 0; + S32 mid; + S32 last = -1; + + mid = (max - min) / 2; + do { + last = mid; + + if(logtab[mid] == value) + break; + + if(logtab[mid] < value) + min = mid; + else + max = mid; + + mid = min + ((max - min) / 2); + } while(last != mid); + + return((F32)mid / logmax); +} + +F32 linearToDB(F32 value) +{ + if(value <= 0.f) + return(0.f); + if(value >= 1.f) + return(1.f); + return(logtab[(U32)(logmax * value)]); +} +//--------------------------------------------------------------------------- + +static ALvoid errorCallback(ALbyte *msg) +{ + // used to allow our OpenAL implementation to display info on the console + Con::errorf(ConsoleLogEntry::General, (const char *)msg); +} + +//-------------------------------------------------------------------------- +bool prepareContext() +{ +// mForceMaxDistanceUpdate = Con::getBoolVariable("$pref::audio::forceMaxDistanceUpdate", false); + mForceMaxDistanceUpdate = false; + + // allocate source channels: can only get 16 sources on NT, so check the max before +// jff: todo, allow for more than 16 channels on non-NT boxes + mNumSources = mRequestSources; + alGenSources(mRequestSources, mSource); + + // invalidate all existing handles + dMemset(mHandle, NULL_AUDIOHANDLE, sizeof(mHandle)); + mCaptureInitialized = AL_FALSE; + + // pre-load profile data + SimGroup* grp = Sim::getDataBlockGroup(); + if (grp != NULL) + { + SimObjectList::iterator itr = grp->begin(); + for (; itr != grp->end(); itr++) + { + AudioProfile *profile = dynamic_cast(*itr); + if(profile != NULL && profile->isPreload()) + { + Resource buffer = AudioBuffer::find(profile->mFilename); + if((bool)buffer) + { + ALuint bufferId = buffer->getALBuffer(true); + alBufferi_EXT(bufferId, AL_BUFFER_KEEP_RESIDENT, AL_TRUE); + } + } + } + } + mInitialized = true; + return(true); +} + +void shutdownContext() +{ + alxCaptureStop(); + + // invalidate active handles + dMemset(mSource, 0, sizeof(mSource)); +} + +//-------------------------------------------------------------------------- +void init() +{ +#ifdef __linux + alutInit( 0, 0 ); + libraryInitExtensions( ); + prepareContext( ); +#ifdef DEBUG + alBombOnError_LOKI( ); +#endif +#endif + Con::addCommand("AudioDetect", cAudio_detect, "AudioDetect()", 1, 1); + Con::addCommand("AudioDestroy", cAudio_destroy, "AudioDestroy()", 1, 1); + Con::addCommand("AudioSetDriver", cAudio_setDriver, "AudioSetDriver(name)", 2, 2); + + Con::addCommand("alxIsEnabled", cAudio_isEnabled, "alxIsEnabled(name)", 2, 2); + + Con::addCommand("alxIsExtensionPresent", cAudio_isExtensionPresent, "alxIsExtensionPresent(name)", 2, 2); + Con::addCommand("alxCreateSource", cAudio_alxCreateSource, "alxCreateSource(profile, {x,y,z} | description, filename, {x,y,z})", 2, 6); + + Con::addCommand("alxSourcef", cAudio_alxSourcef, "alxSourcef(handle, ALenum, value)", 4, 4); + Con::addCommand("alxSource3f", cAudio_alxSource3f, "alxSource3f(handle, ALenum, \"x y z\" | x, y, z)", 3, 6); + Con::addCommand("alxSourcei", cAudio_alxSourcei, "alxSourcei(handle, ALenum, value)", 4, 4); + + Con::addCommand("alxGetSourcef", cAudio_alxGetSourcef, "alxGetSourcef(handle, ALenum)", 3, 3); + Con::addCommand("alxGetSource3f", cAudio_alxGetSource3f, "alxGetSource3f(handle, ALenum)", 3, 3); + Con::addCommand("alxGetSourcei", cAudio_alxGetSourcei, "alxGetSourcei(handle, ALenum)", 3, 3); + + Con::addCommand("alxListenerf", cAudio_alxListenerf, "alxListenerf(ALenum, value)", 3, 3); + Con::addCommand("alxListener3f", cAudio_alxListener3f, "alxListener3f(ALenum, \"x y z\" | x, y, z)", 3, 5); + + Con::addCommand("alxGetListenerf", cAudio_alxGetListenerf, "alxGetListenerf(Alenum)", 2, 2); + Con::addCommand("alxGetListener3f", cAudio_alxGetListener3f, "alxGetListener3f(Alenum)", 2, 2); + Con::addCommand("alxGetListeneri", cAudio_alxGetListeneri, "alxGetListeneri(Alenum)", 2, 2); + + Con::addCommand("alxContexti", cAudio_alxContexti, "alxContexti(Alenum, value)", 3, 3); + Con::addCommand("alxGetContexti", cAudio_alxGetContexti, "alxGetContexti(Alenum)", 2, 2); + Con::addCommand("alxGetContextstr", cAudio_alxGetContextstr, "alxGetContextstr(Alenum, idx)", 3, 3); + + Con::addCommand("alxPlay", cAudio_alxPlay, "alxPlay(handle) | alxPlay(profile, {x,y,z})", 2, 5); + Con::addCommand("alxStop", cAudio_alxStop, "alxStop(handle)", 2, 2); + Con::addCommand("alxStopAll", cAudio_alxStopAll, "alxStopAll()", 1, 1); + + Con::addCommand("alxCaptureInit", cAudio_alxCaptureInit, "alxCaptureInit()", 1, 1); + Con::addCommand("alxCaptureDestroy", cAudio_alxCaptureDestroy, "alxCaptureDestroy()", 1, 1); + Con::addCommand("alxCaptureStart", cAudio_alxCaptureStart, "alxCaptureStart()", 1, 2); + Con::addCommand("alxCaptureStop", cAudio_alxCaptureStop, "alxCaptureStop()", 1, 1); + Con::addCommand("alxIsCapturing", cAudio_alxIsCapturing, "alxIsCapturing()", 1, 1); + + Con::addCommand("alxEnvironmenti", cAudio_alxEnvironmenti, "alxEnvironmenti(Alenum, value)", 3, 3); + Con::addCommand("alxEnvironmentf", cAudio_alxEnvironmentf, "alxEnvironmentf(Alenum, value)", 3, 3); + Con::addCommand("alxGetEnvironmenti", cAudio_alxGetEnvironmenti, "alxGetEnvironmenti(Alenum)", 2, 2); + Con::addCommand("alxGetEnvironmentf", cAudio_alxGetEnvironmentf, "alxGetEnvironmentf(Alenum)", 2, 2); + + Con::addCommand("alxSetEnvironment", cAudio_alxSetEnvironment, "alxSetEnvironment(AudioEnvironmentData)", 2, 2); + Con::addCommand("alxEnableEnvironmental", cAudio_alxEnableEnvironmental, "alxEnableEnvironmental(bool)", 2, 2 ); + + Con::addCommand("alxGetWaveLen", cAudio_alxGetWaveLen, "alxGetWaveLen(profile|filename)", 2, 2); + + Con::addCommand("getAudioDriverList", cGetAudioDriverList, "getAudioDriverList();", 1, 1); + Con::addCommand("getAudioDriverInfo", cGetAudioDriverInfo, "getAudioDriverInfo();", 1, 1); + + ResourceManager->registerExtension(".wav", AudioBuffer::construct); +#ifdef IMMERSION_SUPPORT + ResourceManager->registerExtension(".ifr", CImmSupport::construct); +#endif + + Con::setIntVariable("DefaultAudioType", Audio::DefaultAudioType); + Con::setIntVariable("ChatAudioType", Audio::ChatAudioType); + Con::setIntVariable("GuiAudioType", Audio::GuiAudioType); + Con::setIntVariable("EffectAudioType", Audio::EffectAudioType); + Con::setIntVariable("VoiceAudioType", Audio::VoiceAudioType); + Con::setIntVariable("MusicAudioType", Audio::MusicAudioType); + + Con::addCommand("alxGetChannelVolume", cAudio_alxGetChannelVolume, "alxGetChannelVolume(channel)", 2, 2); + Con::addCommand("alxSetChannelVolume", cAudio_alxSetChannelVolume, "alxSetChannelVolume(channel, volume)", 3, 3); + + Con::addCommand("alxSetCaptureGainScale", cAudio_alxSetCaptureGainScale, "alxSetCaptureGainScale(scale)", 2, 2); + Con::addCommand("alxGetCaptureGainScale", cAudio_alxGetCaptureGainScale, "alxGetCaptureGainScale()", 1, 1); + + Con::addCommand("alxPlayMusic", cAudio_alxPlayMusic, "alxPlayMusic(file)", 2, 2); + Con::addCommand("alxStopMusic", cAudio_alxStopMusic, "alxStopMusic()", 1, 1); + + Con::addCommand("alxDisableOuterFalloffs", cAudio_alxDisableOuterFalloffs, "alxDisableOuterFalloffs(bool)", 2, 2); + Con::addCommand("alxSetInnerFalloffScale", cAudio_alxSetInnerFalloffScale, "alxSetInnerFalloffScale(scale)", 2, 2); + Con::addCommand("alxGetInnerFalloffScale", cAudio_alxGetInnerFalloffScale, "alxGetInnerFalloffScale()", 1, 1); + + Con::addCommand("alxForceMaxDistanceUpdate", cAudio_alxForceMaxDistanceUpdate, "alxForceMaxDistanceUpdate(bool)", 2, 2); + + // default all channels to full gain + for(U32 i = 0; i < Audio::NumAudioTypes; i++) + mAudioTypeVolume[i] = 1.f; + +#ifndef DISABLE_AUDIO_THREAD + AudioThread::create(); +#endif +} + +//-------------------------------------------------------------------------- +void detect() +{ + // destroy method takes down the audio thread + if(mDriverInfoList.size()) + { + destroy(); +#ifndef DISABLE_AUDIO_THREAD + AudioThread::create(); +#endif + } + + DriverInfo di; + di.mName = dStrdup("None"); + di.mVender = dStrdup((const char*)alGetString(AL_VENDOR)); + di.mVersion = dStrdup((const char*)alGetString(AL_VERSION)); + di.mRenderer = dStrdup((const char*)alGetString(AL_RENDERER)); + di.mExtensions = dStrdup((const char*)alGetString(AL_EXTENSIONS)); + mDriverInfoList.push_back(di); + mActiveDriver = mDriverInfoList.begin(); + +#ifndef __linux + const char *str = Con::getVariable("$pref::Audio::drivers"); + if (str) + { + char driverString[1024]; + dStrcpy(driverString, str); + char *tok = dStrtok(driverString, " \t"); + while (tok != NULL) + { + if(libraryInit((const char *)tok)) + { + if(alGetError() == AL_NO_ERROR) + { + if(alIsExtensionPresent((const ALubyte *)"AL_EXT_DYNAMIX")) + { + di.mName = dStrdup(tok); + di.mVender = dStrdup((const char*)alGetString(AL_VENDOR)); + di.mVersion = dStrdup((const char*)alGetString(AL_VERSION)); + di.mRenderer = dStrdup((const char*)alGetString(AL_RENDERER)); + di.mExtensions = dStrdup((const char*)alGetString(AL_EXTENSIONS)); + mDriverInfoList.push_back(di); + } + } + libraryShutdown(); + } + tok = dStrtok(NULL, " \t"); + } + } +#endif +} + +//-------------------------------------------------------------------------- +void destroy() +{ +#ifndef __linux + // FIXME: weird race condition + AudioThread::destroy(); +#endif + + alxCaptureDestroy(); + + if(mInitialized) + { + if(mEnvironment) + { + alDeleteEnvironmentIASIG(1, (ALuint*)&mEnvironment); + mEnvironment = 0; + } + alutExit(); + } + + libraryShutdown(); + + while(mLoopingList.size()) + { + mLoopingList.last()->mBuffer.purge(); + delete mLoopingList.last(); + mLoopingList.pop_back(); + } + + while(mLoopingFreeList.size()) + { + mLoopingFreeList.last()->mBuffer.purge(); + delete mLoopingFreeList.last(); + mLoopingFreeList.pop_back(); + } + + mInitialized = false; + while (mDriverInfoList.size()) + { + dFree(mDriverInfoList.last().mName); + dFree(mDriverInfoList.last().mVender); + dFree(mDriverInfoList.last().mVersion); + dFree(mDriverInfoList.last().mRenderer); + dFree(mDriverInfoList.last().mExtensions); + mDriverInfoList.pop_back(); + } + mActiveDriver = mDriverInfoList.begin(); + + for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) + mBuffer[i] = 0; +} + +//-------------------------------------------------------------------------- +bool setDriver(const char *name) +{ + // handle a couple special keywords + if (name == NULL || dStricmp(name, "none") == 0) + name = mDriverInfoList.first().mName; + + if (dStricmp(name, "default") == 0) + name = mDriverInfoList.last().mName; + + // don't reset if driver is already active + if (mActiveDriver && (dStricmp(name, mActiveDriver->mName) == 0) ) + return true; + + DriverInfo *newDriver = NULL; + + // locate the driver + Vector::iterator itr = mDriverInfoList.begin(); + for (; itr != mDriverInfoList.end(); itr++) + { + if (dStricmp(name, itr->mName) == 0) + { + newDriver = itr; + break; + } + } + + // no driver by that name + if (newDriver == NULL) + { + Con::errorf(ConsoleLogEntry::General, "Unknown OpenAL audio driver '%s'", name ); + return false; + } + + // shut down existing driver + if (mActiveDriver) + { + alxCaptureDestroy(); + alutExit(); + libraryShutdown(); + mActiveDriver = NULL; + } + + // stub driver + if (newDriver == mDriverInfoList.begin()) + { + libraryShutdown(); + prepareContext(); + mActiveDriver = mDriverInfoList.begin(); + return true; + } + + // load driver + if(!libraryInit((const char *)name)) + { + // install stub driver + libraryShutdown(); + mActiveDriver = mDriverInfoList.begin(); + return(false); + } + + + S32 attribs[] = { ALC_FREQUENCY, 0, + ALC_RESOLUTION, 0, + ALC_CHANNELS, 0, 0 }; + + // grab the console prefs + attribs[1] = Con::getIntVariable("$pref::Audio::frequency", ALX_DEF_SAMPLE_RATE); + attribs[3] = Con::getIntVariable("$pref::Audio::sampleBits", ALX_DEF_SAMPLE_BITS); + attribs[5] = Con::getIntVariable("$pref::Audio::channels", ALX_DEF_CHANNELS); + + // initialize the system + alutInit(attribs, 0); + if(alGetError() != AL_NO_ERROR) + { + // exit, load stub + alutExit(); + libraryShutdown(); + mActiveDriver = mDriverInfoList.begin(); + return(false); + } + + // must have dynamix extension + if(alIsExtensionPresent((const ALubyte *)"AL_EXT_DYNAMIX")) + { + // bind the _EXT methods + libraryInitExtensions(); + prepareContext(); + mActiveDriver = itr; + + Con::setVariable("$pref::Audio::activeDriver", mActiveDriver->mName); + mInitialized = true; + + // generate an environment if present + if(alIsExtensionPresent((const ALubyte *)"AL_EXT_IASIG")) + { + alGenEnvironmentIASIG(1, &mEnvironment); + if(alGetError() != AL_NO_ERROR) + mEnvironment = 0; + } + +// set the listener gain to full and handle through this layer? +#ifdef HANDLE_LISTENER_GAIN + alListenerf(AL_GAIN_LINEAR, 1.f); +#endif + return(true); + } + + // driver load failed, load the stub driver + libraryShutdown(); + mActiveDriver = mDriverInfoList.begin(); + return(false); +} + +//-------------------------------------------------------------------------- +const Vector* getDriverList() +{ + return &mDriverInfoList; +} + +//-------------------------------------------------------------------------- +const char* getDriverListString() +{ + U32 deviceCount = mDriverInfoList.size(); + if ( deviceCount > 0 ) // It better be... + { + U32 i, bufSize = 0; + for ( i = 0; i < deviceCount; i++ ) + bufSize += ( dStrlen( mDriverInfoList[i].mName ) + 1 ); + + char* returnString = Con::getReturnBuffer( bufSize ); + dStrcpy( returnString, mDriverInfoList[0].mName ); + for ( i = 1; i < deviceCount; i++ ) + { + dStrcat( returnString, "\t" ); + dStrcat( returnString, mDriverInfoList[i].mName ); + } + + return( returnString ); + } + + return( "" ); +} + +//-------------------------------------------------------------------------- +const char* getCurrentDriverInfo() +{ + if ( mActiveDriver ) + { + U32 bufSize = ( mActiveDriver->mName ? dStrlen( mActiveDriver->mName ) : 0 ) + + ( mActiveDriver->mVender ? dStrlen( mActiveDriver->mVender ) : 0 ) + + ( mActiveDriver->mRenderer ? dStrlen( mActiveDriver->mRenderer ) : 0 ) + + ( mActiveDriver->mVersion ? dStrlen( mActiveDriver->mVersion ) : 0 ) + + ( mActiveDriver->mExtensions ? dStrlen( mActiveDriver->mExtensions ) : 0 ) + + 5; + + char* returnString = Con::getReturnBuffer( bufSize ); + dSprintf( returnString, bufSize, "%s\t%s\t%s\t%s\t%s", + ( mActiveDriver->mName ? mActiveDriver->mName : "" ), + ( mActiveDriver->mVender ? mActiveDriver->mVender : "" ), + ( mActiveDriver->mRenderer ? mActiveDriver->mRenderer : "" ), + ( mActiveDriver->mVersion ? mActiveDriver->mVersion : "" ), + ( mActiveDriver->mExtensions ? mActiveDriver->mExtensions : "" ) ); + return( returnString ); + } + + return( "" ); +} + +} // end OpenAL namespace diff --git a/platformLinux/blender.asm b/platformLinux/blender.asm new file mode 100644 index 0000000..d0e981c --- /dev/null +++ b/platformLinux/blender.asm @@ -0,0 +1,1126 @@ +; +; NASM implementation for terrain/blender.cc +; + +segment .data + +delta_a times 2 dd 0 +delta_b times 2 dd 0 +delta_c times 2 dd 0 +delta_d times 2 dd 0 + +alpha_a0 times 2 dd 0 +alpha_b0 times 2 dd 0 +alpha_c0 times 2 dd 0 +alpha_d0 times 2 dd 0 +alpha_a1 times 2 dd 0 +alpha_b1 times 2 dd 0 +alpha_c1 times 2 dd 0 +alpha_d1 times 2 dd 0 +alpha_a2 times 2 dd 0 +alpha_b2 times 2 dd 0 +alpha_c2 times 2 dd 0 +alpha_d2 times 2 dd 0 +alpha_a3 times 2 dd 0 +alpha_b3 times 2 dd 0 +alpha_c3 times 2 dd 0 +alpha_d3 times 2 dd 0 + +ldelt_a times 2 dd 0 +ldelt_b times 2 dd 0 +ldelt_c times 2 dd 0 +ldelt_d times 2 dd 0 +rdelt_a times 2 dd 0 +rdelt_b times 2 dd 0 +rdelt_c times 2 dd 0 +rdelt_d times 2 dd 0 + +zero times 2 dd 0 + +; FIXME: get back to this +; redLightMask times 2 dd 0xf800000000000000 +; greenLightMask times 2 dd 0x07c0000000000000 +; blueLightMask times 2 dd 0x003e000000000000 + +; bluePackMask times 2 dd 0x003e0000000000000 +; greenPackMask times 2 dd 0x07c0000000000000 +; redPackMask times 2 dd 0x00f8000000000000 + +redLightMask times 2 dd 0 +greenLightMask times 2 dd 0 +blueLightMask times 2 dd 0 + +bluePackMask times 2 dd 0 +greenPackMask times 2 dd 0 +redPackMask times 2 dd 0 + +rdeltq times 2 dd 0 +ldeltq times 2 dd 0 + +ix dw 0 +iy dw 0 + +lpoints times 4 dd 0 +texelsPerLumelShift dd 0 +texelsPerLumel dd 0 +texelsPerLumelDiv2 dd 0 + +segment .text + +; global macros +%define dst [ebp+8] +%define sq_shift [ebp+12] +%define aoff [ebp+16] +%define bmp_ptrs [ebp+20] +%define alpha_ptrs [ebp+24] + +; +; void doSquare4( U32 *dst, +; int sq_shift, +; int *aoff, +; U32 **bmp_ptrs, +; U8 **alpha_ptrs ) +; + +global doSquare4 + +doSquare4: + + ; prologue + push ebp + mov ebp, esp + + ; setup ix, iy + mov eax, 1 + mov cl, sq_shift + shl eax, cl + mov dword [iy], eax + + shr eax, 1 + mov dword [ix], eax + + ; actual code from blender.cc + movd mm1, sq_shift + + ; get alpha values for the corners of the square for each texture type. + ; replicate the values into 4 words of the qwords. Also calc vertical + ; stepping values for the alpha values on left and right edges. + ; load alpha value into bh to mul by 256 for precision. then + ; punpcklwd mm0, mm0 followed by punpckldq mm0, mm0 + ; to replicate the low word into all words of mm0. + ; shift down difference by sqshift to divide by pixels per square to get + ; increment. + mov esi, aoff + mov edi, alpha_ptrs + mov eax, [edi] + mov edx, eax + add eax, [esi] + xor ebx,ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_a0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_a1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_a], mm0 + psraw mm3, mm1 + movq [rdelt_a], mm3 + + mov eax, [edi+4] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_b0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_b1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_b], mm0 + psraw mm3, mm1 + movq [rdelt_b], mm3 + + mov eax, [edi+8] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_c0], mm2 + movq [alpha_c2], mm0 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_c1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_c], mm0 + psraw mm3, mm1 + movq [rdelt_c], mm3 + + mov eax, [edi+12] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_d0], mm2 + movq [alpha_d2], mm0 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_d1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_d], mm0 + psraw mm3, mm1 + movq [rdelt_d], mm3 + + mov esi, bmp_ptrs + mov eax, [esi] + mov ebx, [esi+4] + mov ecx, [esi+8] + mov edx, [esi+12] + + movq mm0, [alpha_a1] + movq mm2, [alpha_b1] + movq mm3, [alpha_c1] + movq mm4, [alpha_a0] + movq mm5, [alpha_b0] + movq mm6, [alpha_c0] + movq mm7, [alpha_d0] + mov edi, dst + +yloop4: + ; mm1 should be sq_shift at this point + + ; calculate alpha step increments...word-size steps are replicated + ; to fill qword. + psubw mm0, mm4 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_a], mm0 ;delta = ainc ainc ainc ainc + + psubw mm2, mm5 + psraw mm2, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_b], mm2 ;delta = ainc ainc ainc ainc + + psubw mm3, mm6 + psraw mm3, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_c], mm3 ;delta = ainc ainc ainc ainc + + movq mm0, [alpha_d1] + psubw mm0, mm7 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_d], mm0 ;delta = ainc ainc ainc ainc + + mov esi, [ix] + pxor mm2, mm2 + +xloop4: + movq mm0, [eax] + movq mm1, mm0 + punpcklbw mm0, mm2 + pmulhw mm0, mm4 + paddw mm4, [delta_a] + punpckhbw mm1, mm2 + pmulhw mm1, mm4 + paddw mm4, [delta_a] + packuswb mm0, mm1 + + movq mm3, [ebx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm5 + paddw mm5, [delta_b] + punpckhbw mm1, mm2 + pmulhw mm1, mm5 + paddw mm5, [delta_b] + packuswb mm3, mm1 + paddb mm0, mm3 + + movq mm3, [ecx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm6 + paddw mm6, [delta_c] + punpckhbw mm1, mm2 + pmulhw mm1, mm6 + paddw mm6, [delta_c] + packuswb mm3, mm1 + paddb mm0, mm3 + + movq mm3, [edx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm7 + paddw mm7, [delta_d] + punpckhbw mm1, mm2 + pmulhw mm1, mm7 + paddw mm7, [delta_d] + packuswb mm3, mm1 + paddb mm0, mm3 + + ; double result, to make up for alpha vals being signed (max = 127) + ; so our math turns out a bit short, example: + ; (0x7f00 * 0xff) >> 16 = 0x7e....* 2 = 252...not quite 255 + ; would have been (0xff00 * 0xff) >> 16 = 0xfe = 254, + ; if I could do an unsigned pmulhw... + ; pmulhuw is in an intel document I found, but doesn't compile.... + paddb mm0, mm0 + + movq [edi], mm0 + + add eax, 8 + add ebx, 8 + add ecx, 8 + add edx, 8 + add edi, 8 + + dec esi + jnz near xloop4 + + movq mm4, [alpha_a0] + paddw mm4, [ldelt_a] + movq [alpha_a0], mm4 + + movq mm5, [alpha_b0] + paddw mm5, [ldelt_b] + movq [alpha_b0], mm5 + + movq mm6, [alpha_c0] + paddw mm6, [ldelt_c] + movq [alpha_c0], mm6 + + movq mm7, [alpha_d0] + paddw mm7, [ldelt_d] + movq [alpha_d0], mm7 + + movq mm0, [alpha_d1] + paddw mm0, [rdelt_d] + movq [alpha_d1], mm0 + + movq mm2, [alpha_b1] + paddw mm2, [rdelt_b] + movq [alpha_b1], mm2 + + movq mm3, [alpha_c1] + paddw mm3, [rdelt_c] + movq [alpha_c1], mm3 + + movq mm0, [alpha_a1] + paddw mm0, [rdelt_a] + movq [alpha_a1], mm0 + + movd mm1, sq_shift ; top of loop expects this + + dec dword [iy] + jnz near yloop4 + + emms + + ; epilogue + pop ebp + ret + +; +; void doSquare3( U32 *dst, +; int sq_shift, +; int *aoff, +; U32 **bmp_ptrs, +; U8 **alpha_ptrs ) +; + +global doSquare3 + +doSquare3: + + ; prologue + push ebp + mov ebp, esp + + ; setup ix, iy + mov eax, 1 + mov cl, sq_shift + shl eax, cl + mov dword [iy], eax + + shr eax, 1 + mov dword [ix], eax + + movd mm1, sq_shift + ; get alpha values for the corners of the square for each texture type. + ; replicate the values into 4 words of the qwords. Also calc vertical + ; stepping values for the alpha values on left and right edges. + ; load alpha value into bh to mul by 256 for precision. then + ; punpcklwd mm0, mm0 followed by punpckldq mm0, mm0 + ; to replicate the low word into all words of mm0. + ; shift down difference by sqshift to divide by pixels per square to get + ; increment. + + mov esi, aoff + mov edi, alpha_ptrs + mov eax, [edi] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_a0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_a1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_a], mm0 + psraw mm3, mm1 + movq [rdelt_a], mm3 + + mov eax, [edi+4] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_b0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_b1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_b], mm0 + psraw mm3, mm1 + movq [rdelt_b], mm3 + + mov eax, [edi+8] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_c0], mm2 + movq [alpha_c2], mm0 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_c1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_c], mm0 + psraw mm3, mm1 + movq [rdelt_c], mm3 + + mov esi, bmp_ptrs + mov eax, [esi] + mov ebx, [esi+4] + mov ecx, [esi+8] + + movq mm0, [alpha_a1] + movq mm2, [alpha_b1] + movq mm3, [alpha_c1] + movq mm4, [alpha_a0] + movq mm5, [alpha_b0] + movq mm6, [alpha_c0] + mov edi, dst + +yloop3: + ; mm1 should be sq_shift at this point + ; mm0 should be [alpha_a1] + ; mm2 should be [alpha_b1] + ; mm3 should be [alpha_c1] + + ; calculate alpha step increments...word-size steps are replicated + ; to fill qword. + psubw mm0, mm4 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_a], mm0 ;delta = ainc ainc ainc ainc + + psubw mm2, mm5 + psraw mm2, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_b], mm2 ;delta = ainc ainc ainc ainc + + psubw mm3, mm6 + psraw mm3, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_c], mm3 ;delta = ainc ainc ainc ainc + + mov esi, ix + pxor mm2, mm2 + + movq mm7, [delta_a] +xloop3: + movq mm0, [eax] + movq mm1, mm0 + punpcklbw mm0, mm2 + pmulhw mm0, mm4 + paddw mm4, mm7 + punpckhbw mm1, mm2 + pmulhw mm1, mm4 + paddw mm4, mm7 + packuswb mm0, mm1 + + movq mm3, [ebx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm5 + paddw mm5, [delta_b] + punpckhbw mm1, mm2 + pmulhw mm1, mm5 + paddw mm5, [delta_b] + packuswb mm3, mm1 + paddb mm0, mm3 + + movq mm3, [ecx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm6 + paddw mm6, [delta_c] + punpckhbw mm1, mm2 + pmulhw mm1, mm6 + paddw mm6, [delta_c] + packuswb mm3, mm1 + paddb mm0, mm3 + paddb mm0, mm0 + + movq [edi], mm0 + + add eax, 8 + add ebx, 8 + add ecx, 8 + add edi, 8 + + dec esi + jnz near xloop3 + + movq mm4, [alpha_a0] + paddw mm4, [ldelt_a] + movq [alpha_a0], mm4 + + movq mm5, [alpha_b0] + paddw mm5, [ldelt_b] + movq [alpha_b0], mm5 + + movq mm6, [alpha_c0] + paddw mm6, [ldelt_c] + movq [alpha_c0], mm6 + + movq mm2, [alpha_b1] + paddw mm2, [rdelt_b] + movq [alpha_b1], mm2 + + movq mm3, [alpha_c1] + paddw mm3, [rdelt_c] + movq [alpha_c1], mm3 + + movq mm0, [alpha_a1] + paddw mm0, [rdelt_a] + movq [alpha_a1], mm0 + + movd mm1, sq_shift ; top of loop expects this + + dec dword [iy] + jnz near yloop3 + + emms + + ; epilogue + pop ebp + ret + +; +; void doSquare2( U32 *dst, +; int sq_shift, +; int *aoff, +; U32 **bmp_ptrs, +; U8 **alpha_ptrs ) +; + +global doSquare2 + +doSquare2: + + ; prologue + push ebp + mov ebp, esp + + ; setup ix, iy + mov eax, 1 + mov cl, sq_shift + shl eax, cl + mov dword [iy], eax + + shr eax, 1 + mov dword [ix], eax + + movd mm1, sq_shift + ; get alpha values for the corners of the square for each texture type. + ; replicate the values into 4 words of the qwords. Also calc vertical + ; stepping values for the alpha values on left and right edges. + ; punpcklwd mm0, mm0 followed by punpckldq mm0, mm0 + ; to replicate the low word into all words of mm0. + ; shift down difference by sqshift to divide by pixels per square to get + ; increment. + + mov esi, aoff + mov edi, alpha_ptrs + mov eax, [edi] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_a0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_a1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_a], mm0 + psraw mm3, mm1 + movq [rdelt_a], mm3 + + mov eax, [edi+4] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_b0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_b1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_b], mm0 + psraw mm3, mm1 + movq [rdelt_b], mm3 + + mov esi, bmp_ptrs + mov eax, [esi] + mov ebx, [esi+4] + + movq mm0, [alpha_a1] + movq mm2, [alpha_b1] + movq mm4, [alpha_a0] + movq mm5, [alpha_b0] + mov edi, dst + +yloop2: + ; mm1 should be sq_shift at this point + ; mm0 should be [alpha_a1] + ; mm2 should be [alpha_b1] + + ; calculate alpha step increments...word-size steps are replicated + ; to fill qword. + psubw mm0, mm4 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_a], mm0 ;delta = ainc ainc ainc ainc + + psubw mm2, mm5 + psraw mm2, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_b], mm2 ;delta = ainc ainc ainc ainc + + mov esi, ix + pxor mm2, mm2 + + movq mm6, [delta_a] + movq mm7, [delta_b] + +xloop2: + movq mm0, [eax] + movq mm3, [ebx] + + movq mm1, mm0 + punpcklbw mm0, mm2 + pmulhw mm0, mm4 + paddw mm4, mm6 + punpckhbw mm1, mm2 + pmulhw mm1, mm4 + paddw mm4, mm6 + packuswb mm0, mm1 + + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm5 + paddw mm5, mm7 + punpckhbw mm1, mm2 + pmulhw mm1, mm5 + paddw mm5, mm7 + packuswb mm3, mm1 + paddb mm0, mm3 + paddb mm0, mm0 + + movq [edi], mm0 + + add edi, 8 + add eax, 8 + add ebx, 8 + + dec esi + jnz xloop2 + + movq mm4, [alpha_a0] + paddw mm4, [ldelt_a] + movq [alpha_a0], mm4 + + movq mm5, [alpha_b0] + paddw mm5, [ldelt_b] + movq [alpha_b0], mm5 + + movq mm2, [alpha_b1] + paddw mm2, [rdelt_b] + movq [alpha_b1], mm2 + + movq mm0, [alpha_a1] + paddw mm0, [rdelt_a] + movq [alpha_a1], mm0 + + movd mm1, sq_shift ; top of loop expects this + + dec dword [iy] + jnz near yloop2 + + emms + + ; epilogue + pop ebp + ret + +global setupLumel + +setupLumel: + + ; prologue + push ebp + mov ebp, esp + + ; we only need to load the high bits up, they're already 0 + ; in the low bits + mov dword [redLightMask], 0xf8000000 + mov dword [greenLightMask], 0x07c0000 + mov dword [blueLightMask], 0x003e0000 + + mov dword [bluePackMask], 0x003e0000 + mov dword [greenPackMask], 0x07c00000 + mov dword [redPackMask], 0x00f80000 + + ; epilogue + pop ebp + ret + +; +; void doLumel( U16 *dstptr, +; U32 *srcptr, +; int nextdstrow, +; int nextsrcrow ) +; + +%define dstptr [ebp+8] +%define srcptr [ebp+12] +%define nextdstrow [ebp+16] +%define nextsrcrow [ebp+20] + +global doLumel + +doLumel: + + ; prologue + push ebp + mov ebp, esp + + movd mm7, [texelsPerLumelShift] + + movd mm0, [lpoints] + movq mm4, mm0 + pand mm0, [redLightMask] + movq mm5, mm4 + pand mm4, [greenLightMask] + psllq mm0, 31 + pand mm5, [blueLightMask] + psllq mm4, 20 + paddw mm0, mm4 + psllq mm5, 9 + paddw mm0, mm5 ; mm0 = 0000rrrrggggbbbb qword for lp[0] + + movd mm1, [lpoints+8] ; get lp2 + movq mm4, mm1 + pand mm1, [redLightMask] + movq mm5, mm4 + pand mm4, [greenLightMask] + psllq mm1, 31 + pand mm5, [blueLightMask] + psllq mm4, 20 + paddw mm1, mm4 + psllq mm5, 9 + paddw mm1, mm5 ; mm1 = 0000rrrrggggbbbb qword for lp[2] + + psubw mm1, mm0 + psraw mm1, mm7 + movq [ldeltq], mm1 + + movd mm2, [lpoints+4] ; get lp[1] + movq mm4, mm2 + pand mm2, [redLightMask] + movq mm5, mm4 + pand mm4, [greenLightMask] + psllq mm2, 31 + pand mm5, [blueLightMask] + psllq mm4, 20 + paddw mm2, mm4 + psllq mm5, 9 + paddw mm2, mm5 ; mm2 = 0000rrrrggggbbbb qword for lp[1] + + movd mm3, [lpoints+12] ; get lp3 + movq mm4, mm3 + pand mm3, [redLightMask] + movq mm5, mm4 + pand mm4, [greenLightMask] + psllq mm3, 31 + pand mm5, [blueLightMask] + psllq mm4, 20 + paddw mm3, mm4 + psllq mm5, 9 + paddw mm3, mm5 ; mm3 = 0000rrrrggggbbbb qword for lp[3] + + psubw mm3, mm2 + psraw mm3, mm7 + movq [rdeltq], mm3 + + mov edi, dstptr + mov esi, srcptr + pxor mm6, mm6 + + mov eax, [texelsPerLumel] ; yloop count + cmp eax, 1 + jne not_special + + + ; special case for 1x1 lumel + movd mm4, [esi] + punpcklbw mm4, mm6 ; mm6 is expected to be 0 here + pmulhw mm4, mm0 + paddw mm4, mm4 + + movq mm7, mm4 + movq mm6, mm4 + psrlq mm4, 34 + pand mm7, [redPackMask] + psrlq mm6, 13 +; MASKALPHA +; pand mm4, [bluePackMask] + psllq mm7, 8 + pand mm6, [greenPackMask] + paddw mm4, mm7 + paddw mm4, mm6 + movd eax, mm4 + mov [edi],ax + jmp done + +not_special: + + ; mm0 = left at loop start + ; mm2 = right +yloopL: + movq mm1, mm0 ;mm1 = start + movq mm3, mm2 + + psubw mm3, mm0 + psraw mm3, mm7 ; mm3 = delta + + mov ebx, [texelsPerLumelDiv2] ; loop count + +xloopL: + movq mm4, [esi] + movq mm5, mm4 + punpcklbw mm4, mm6 ; mm6 is expected to be 0 here + pmulhw mm4, mm1 + paddw mm1, mm3 + punpckhbw mm5, mm6 + pmulhw mm5, mm1 + paddw mm1, mm3 + paddw mm4, mm4 + paddw mm5, mm5 + + movq mm7, mm4 + movq mm6, mm4 + psrlq mm4, 34 + pand mm7, [redPackMask] + psrlq mm6, 13 +; MASKALPHA +; pand mm4, [bluePackMask] + psllq mm7, 8 + pand mm6, [greenPackMask] + paddw mm4, mm7 + paddw mm4, mm6 + + movq mm7, mm5 + movq mm6, mm5 + psrlq mm5, 34 + pand mm7, [redPackMask] + psrlq mm6, 13 +; MASKALPHA +; pand mm4, [bluePackMask] + psllq mm7, 8 + pand mm6, [greenPackMask] + paddw mm5, mm7 + paddw mm5, mm6 + psllq mm5, 16 ; lazy, I reused code above and must now shift + paddw mm4, mm5 + + ; write 2 16-bit pixels. I'd consider doing 4 at a time, but + ; loop count can be as small as 1 at lowest detail already. + ; could do separate loop I guess. + movd [edi], mm4 + pxor mm6, mm6 + add edi, 4 + add esi, 8 + dec ebx + jnz near xloopL + + movd mm7, [texelsPerLumelShift] + paddw mm0, [ldeltq] + paddw mm2, [rdeltq] + add edi, nextdstrow + add esi, nextsrcrow + dec eax + jnz near yloopL + +done: + emms + + ; epilogue + pop ebp + ret diff --git a/platformLinux/fixcalls.pl b/platformLinux/fixcalls.pl new file mode 100644 index 0000000..007578a --- /dev/null +++ b/platformLinux/fixcalls.pl @@ -0,0 +1,8 @@ +#!/usr/bin/perl +# +# This is a script to read a Visual C++ object file and convert the calls +$len = sysread(STDIN, $data, 100000); +$data =~ s/POW/pow/; +$data =~ s/ACOS/acos/; +$data =~ s/\xE8\x00\x00\x00\x00/\xE8\xFC\xFF\xFF\xFF/g; +syswrite(STDOUT, $data, $len); diff --git a/platformLinux/linuxAL.cc b/platformLinux/linuxAL.cc new file mode 100644 index 0000000..0956c57 --- /dev/null +++ b/platformLinux/linuxAL.cc @@ -0,0 +1,335 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformAL.h" +#include "console/console.h" +#include "Core/fileio.h" + +#include + +ALuint alBufferSyncData_EXT(ALuint bid, + ALenum format, + ALvoid *data, + ALsizei size, + ALsizei frequency) +{ + ALuint r = 0; + + // alBufferData doesn't return a value, + // so clear the error setting, execute, + // and check again. + alGetError( ); + + alBufferData(bid, format, data, size, frequency); + + if( alGetError( ) == AL_NO_ERROR ) { + // let AL manage the memory + dFree( data ); + r = 1; + } + + return r; +} + +typedef struct fakeCallbackPair_s { + U32 sid; + void (*proc)(U32, bool); +} fakeCallbackPair_t; + +static Vector< fakeCallbackPair_t > fakeCallbackFake(__FILE__, __LINE__); + +void alxFakeCallbackUpdate( void ) +{ + fakeCallbackPair_t pi; + ALuint state; + int i; + + i = fakeCallbackFake.size(); + + while( i-- ) { + pi = fakeCallbackFake.last(); + fakeCallbackFake.pop_back(); + + state = AL_INITIAL; + + alGetSourcei( pi.sid, AL_SOURCE_STATE, &state ); + + if( state == AL_STOPPED ) + { + pi.proc( pi.sid, false ); + + Con::printf( "Calling callback for %d", pi.sid); + } else { + fakeCallbackFake.push_front( pi ); + } + } + + return; +} + +void alSourceCallback_EXT(ALuint sid, void (*dummy)(U32, bool)) +{ + fakeCallbackPair_t pusher; + + if( alIsSource( sid ) == AL_FALSE) { + Con::errorf(ConsoleLogEntry::General, + "alSourceCallback_EXT: %d not a source id", sid ); + return; + } + + pusher.sid = sid; + pusher.proc = dummy; + + Con::printf( "adding Faker sid %d", sid); + + fakeCallbackFake.push_back( pusher ); + + return; +} + +ALuint alBufferStreamFile_EXT( ALuint bid, const ALubyte* filename ) +{ + static ALboolean (*LoadMP3)(ALuint bid, ALvoid *data, ALint size) = NULL; + +#define GP(x) alGetProcAddress((const ALubyte *) x) + + if(LoadMP3 == NULL) { + LoadMP3 = (ALboolean (*)(ALuint, ALvoid *, ALint)) GP("alutLoadMP3_LOKI"); + if(LoadMP3 == NULL) { + Con::warnf( ConsoleLogEntry::General, + "could not get alutLoadMP3_LOKI, no mp3 support" ); + } + } +#undef GP + + File file; + + file.open( (const char*) filename, File::Read ); + + if( file.getStatus( ) != File::Ok ) { + return AL_FALSE; + } + + U32 size = file.getSize( ); + char* data = (char*) dMalloc( size ); + + if( data == NULL ) { + // technically, 'file' will go out of scope and + // close itself, but still... + file.close( ); + + return AL_FALSE; + } + + file.read( size, data ); + + if( file.getStatus( ) != File::Ok ) { + file.close( ); + dFree( data ); + return AL_FALSE; + } + + U32 ext = dStrlen( filename ); + ext -= 3; + ext = ( ext >= 0 ) ? ext : 0; + ALuint err = AL_FALSE; + + if( dStrnicmp( &filename[ ext ], "mp3", 3 ) != 0 ) { + alGetError(); + + alBufferData( bid, AL_FORMAT_WAVE_EXT, data, size, 0 ); + + err = (alGetError() == AL_NO_ERROR); + + } else { + + if( LoadMP3 ) { + err = LoadMP3( bid, data, size ); + } else { + Con::warnf( ConsoleLogEntry::General, + "cannot load MP3 file, no mp3 support present" ); + err = AL_FALSE; + } + + } + + dFree( data ); + file.close( ); + + if( err == AL_FALSE ) { + Con::warnf( ConsoleLogEntry::General, + "could not buffer and convert MP3 file" ); + + return AL_FALSE; + } + + return AL_TRUE; +} + +static void *context_id; +static ALCdevice *dev; + +extern void alutInitFake( int *argc, char *argv[] ) +{ + char devstr_buf[1024]; + const char *devstr; + const char *paren; + int refresh, frequency; + ALint attrlist[5]; + + // Get some basic configuration variables + refresh = Con::getIntVariable( "$pref::Audio::refresh", 15 ); + frequency = Con::getIntVariable( "$pref::Audio::frequency", 22050 ); + + // Open the audio device + devstr = Con::getVariable("$pref::OpenAL::driver"); + if ( devstr && ((paren=strrchr(devstr, ')')) != NULL) ) { + // Make sure a sane sampling rate is specified + if ( strstr(devstr, "sampling-rate") == NULL ) { + strcpy(devstr_buf, devstr); + sprintf(&devstr_buf[paren-devstr], + " (sampling-rate %d ))", frequency); + devstr = devstr_buf; + } + } else { + sprintf(devstr_buf, "'( (sampling-rate %d ))", frequency); + devstr = devstr_buf; + } + dev = alcOpenDevice( (const ALubyte *) devstr ); + + // If there's no audio, use a null device + if( dev == NULL ) { + const char *nulldev = "'( ( devices '( null )))"; + dev = alcOpenDevice( (const ALubyte *) nulldev ); + } + + if( dev == NULL ) { + Con::errorf( ConsoleLogEntry::General, "Audio device open failed, exiting..." ); + Platform::forceShutdown( 1 ); + return; + } + + attrlist[0] = ALC_FREQUENCY; + attrlist[1] = frequency; + attrlist[2] = ALC_REFRESH; + attrlist[3] = refresh; + attrlist[4] = 0; + + context_id = alcCreateContext( dev, attrlist ); + + if( context_id == NULL ) { + Con::errorf( ConsoleLogEntry::General, "Audio context creation failed, exiting..." ); + Platform::forceShutdown( 1 ); + return; + } + + alcMakeContextCurrent( context_id ); + + // Set the distance attenuation model + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + + return; +} + +extern void alutExitFake( void ) +{ + + if( context_id == NULL ) { + return; + } + + alcDestroyContext( context_id ); + alcCloseDevice( dev ); + + return; +} + +void alBufferi_EXT( ALuint bid, ALenum pname, ALint value ) +{ + // ignore +} + +void alContexti_EXT( ALenum pname, ALint value ) +{ + // ignore. +} + +void alGetContexti_EXT( ALenum pname, ALint* value ) +{ + + if( !value ) { + return; + } + + switch( pname ) { + case ALC_PROVIDER: + *value = 0; + break; + case ALC_PROVIDER_COUNT: + *value = 1; + break; + case ALC_SPEAKER: + *value = 0; + break; + case ALC_SPEAKER_COUNT: + *value = 1; + break; + case ALC_BUFFER_MEMORY_USAGE: + *value = 0; + break; + case ALC_BUFFER_MEMORY_SIZE: + *value = 1 << 16; + break; + case ALC_BUFFER_MEMORY_COUNT: + *value = 0; + break; + case ALC_BUFFER_COUNT: + *value = 0; + break; + default: + // error + break; + } + +} + +void alGetContextstr_EXT( ALenum pname, ALuint idx, ALubyte** value ) +{ + + if( !value ) { + return; + } + + switch( pname ) { + case ALC_PROVIDER_NAME: + *value = "Loki Software, Inc."; + break; + case ALC_SPEAKER_NAME: + *value = "Stereo, 2-Speaker"; + break; + default: + // error + break; + } + +} + +void alGetEnvironmentiIASIG_EXT( ALuint eid, ALenum param, ALint * value ) +{ + // Nothing we can do here yet... +} + +void alGetEnvironmentfIASIG_EXT( ALuint eid, ALenum param, ALfloat * value ) +{ + // Nothing we can do here yet... +} + +void alSourceResetEnvironment_EXT( ALuint source ) +{ + // Nothing we can do here yet... +} + diff --git a/platformLinux/linuxALStub.cc b/platformLinux/linuxALStub.cc new file mode 100644 index 0000000..5982d62 --- /dev/null +++ b/platformLinux/linuxALStub.cc @@ -0,0 +1,246 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformAL.h" +#include "platformLinux/platformLinux.h" +#include "Platform/platformAudio.h" + + +static const AudioEnvironment *mCurrentEnvironment; // the last environment set + +void* alGetProcAddress( const ALubyte* ) +{ + return 0; +} + +ALenum alGetError( ALvoid ) +{ + return AL_NO_ERROR; +} + +void alGetSourceiv( ALuint sid, ALenum param, ALint* value ) +{ + // empty +} + +ALboolean alIsBuffer( ALuint bid ) +{ + return AL_FALSE; +} + +void alGenBuffers( ALsizei n, ALuint* samples ) +{ + // empty +} + +void alDeleteBuffers( ALsizei n, ALuint* samples ) +{ + // empty +} + +void alListeneriv( int pname, int value ) +{ + // empty +} + +ALboolean alIsSource( ALuint sid ) +{ + return AL_FALSE; +} + +void alBufferData( ALuint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq ) +{ + // emtpy +} + +void alDistanceModel( ALenum distanceModel ) +{ + // empty +} + +ALCdevice* alcOpenDevice( const ALubyte* s ) +{ + return 0; +} + +ALvoid* alcCreateContext( ALCdevice* c, ALint* a ) +{ + return 0; +} + +ALCenum alcMakeContextCurrent( ALvoid* c ) +{ + return AL_FALSE; +} + +ALCenum alcDestroyContext( ALvoid* c ) +{ + return AL_FALSE; +} + +void alcCloseDevice( ALCdevice* d ) +{ + // empty +} + +namespace Audio { + + void init( void ) { + // empty + } + + void destroy( void ) { + // empty + } + +} + +AUDIOHANDLE alxCreateSource( const Audio::Description* descObject, + const char* filename, + const MatrixF* transform, + EffectDescription* effect, + AudioSampleEnvironment* sampleEnvironment ) +{ + // empty + return 0; +} + +AUDIOHANDLE alxCreateSource( AudioDescription* descObject, + const char* filename, + const MatrixF* transform, + EffectDescription* effect, + AudioSampleEnvironment* sampleEnvironment ) +{ + // empty + return 0; +} + +AUDIOHANDLE alxCreateSource( const AudioProfile* profile, const MatrixF* transform ) +{ + // empty + return 0; +} + +ALuint alxFindSource( AUDIOHANDLE handle ) +{ + // empty + return 0; +} + + +bool alxIsPlaying(AUDIOHANDLE handle) +{ + // empty + return(false); +} + +ALuint alxGetWaveLen( ALuint buffer ) +{ + // empty + return 0; +} + +void alxStop( AUDIOHANDLE handle ) +{ + // empty +} + +AUDIOHANDLE alxPlay( AUDIOHANDLE handle ) +{ + // empty + return 0; +} + +AUDIOHANDLE alxPlay( const AudioProfile* profile, const MatrixF* transform, const Point3F* velocity ) +{ + // empty + return 0; +} + +void alxSourcef( AUDIOHANDLE handle, ALenum pname, ALfloat value ) +{ + // empty +} + +void alxSourcefv( AUDIOHANDLE handle, ALenum pname, ALfloat* values ) +{ + // empty +} + +void alxSource3f( AUDIOHANDLE handle, ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3 ) +{ + // empty +} + +void alxSourcei( AUDIOHANDLE handle, ALenum pname, ALint value ) +{ + // empty +} + +void alxGetSourcefv( AUDIOHANDLE handle, ALenum pname, ALfloat* values ) +{ + // empty +} + +void alxGetSourcei( AUDIOHANDLE handle, ALenum pname, ALint* value ) +{ + // empty +} + +void alxSourceMatrixF( AUDIOHANDLE handle, const MatrixF* transform ) +{ + // empty +} + +void alxListenerMatrixF( const MatrixF* transform ) +{ + // empty +} + + +void alxListener3f( ALenum pname, ALfloat x, ALfloat y, ALfloat z ) +{ + // empty +} + +void alxGetListener3f(ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3) +{ + // empty +} + +void alxEnvironmentf(ALenum pname, ALfloat value) +{ + // empty +} + +void alxSetEnvironment(const AudioEnvironment * env) +{ + mCurrentEnvironment = env; +} + +const AudioEnvironment * alxGetEnvironment() +{ + return(mCurrentEnvironment); +} + +void alxUpdate( ) +{ + // empty +} + +struct SimVoiceStreamEvent; +struct SimVoiceEvent; + +void alxReceiveVoiceStream( SimVoiceStreamEvent* event ) +{ + // empty +} + +void alxReceiveVoiceEvent( SimVoiceEvent* event ) +{ + // empty +} diff --git a/platformLinux/linuxAsmBlit.cc b/platformLinux/linuxAsmBlit.cc new file mode 100644 index 0000000..daf3ba4 --- /dev/null +++ b/platformLinux/linuxAsmBlit.cc @@ -0,0 +1,327 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/terrData.h" +#include "Math/mMath.h" +#include "dgl/dgl.h" +#include "dgl/gBitmap.h" +#include "terrain/terrRender.h" + +void terrMipBlit_asm( U16* dest, U32 destStride, U32 squareSize, + const U8* sourcePtr, U32 sourceStep, U32 sourceRowAdd ) +{ + +#if 0 + for( U32 k = 0; k < squareSize; k++ ) { + + for( U32 l = 0; l < squareSize; l++ ) { + dest[l] = *( (U16*) sourcePtr ); + sourcePtr += sourceStep; + } + + dest -= destStride; + sourcePtr += sourceRowAdd; + } +#endif + + if( sourceStep == 2 ) { + destStride <<= 1; + sourceRowAdd += squareSize << 1; + + __asm__ __volatile__( "pushl %%eax \n\t" + "pushl %%ebx \n\t" + "pushl %%ecx \n\t" + "pushl %%edx \n\t" + "pushl %%edi \n\t" + "pushl %%esi \n\t" + " \n\t" + "movl %0, %%edx \n\t" + "movl %1, %%edi \n\t" + "movl %2, %%esi \n\t" + "shrl $1, %%edx \n\t" + "movl $0, %%ecx \n\t" + "movl $0, %%ebx \n\t" + " \n" + "pixelLoop2: \n\t" + "movl (%%esi,%%ebx,4), %%eax \n\t" + "movl %%eax, (%%edi,%%ebx,4) \n\t" + "incl %%ebx \n\t" + "cmpl %%edx, %%ebx \n\t" + "jnz pixelLoop2 \n\t" + " \n\t" + "movl $0, %%ebx \n\t" + "incl %%ecx \n\t" + "subl %3, %%edi \n\t" + "addl %4, %%esi \n\t" + "cmpl %5, %%ecx \n\t" + "jl pixelLoop2 \n\t" + " \n\t" + "popl %%esi \n\t" + "popl %%edi \n\t" + "popl %%edx \n\t" + "popl %%ecx \n\t" + "popl %%ebx \n\t" + "popl %%eax \n\t" + : /* no outputs */ + : "g" (squareSize ), "g" (dest), "g" (sourcePtr), + "g" (destStride), "g" (sourceRowAdd), "g" (squareSize) + : "eax", "ebx", "ecx", "edx", "edi", "esi", "cc", "memory" ); + } else if( sourceStep == -2 ) { + destStride <<= 1; + __asm__ __volatile__( "pushl %%eax \n\t" + "pushl %%ebx \n\t" + "pushl %%ecx \n\t" + "pushl %%edx \n\t" + "pushl %%edi \n\t" + "pushl %%esi \n\t" + " \n\t" + "movl %0, %%edx \n\t" + "movl %1, %%edi \n\t" + "movl %2, %%esi \n\t" + "shrl $1, %%edx \n\t" + "movl $0, %%ecx \n\t" + "movl $0, %%ebx \n\t" + " \n" + "pixelLoopNeg2: \n\t" + "movl -2(%%esi), %%eax \n\t" + "subl $4, %%esi \n\t" + "rorl $16, %%eax \n\t" + "movl %%eax, (%%edi,%%ebx,$4) \n\t" + "incl %%ebx \n\t" + "cmpl %%edx, %%ebx \n\t" + "jnz pixelLoopNeg2 \n\t" + " \n\t" + "movl $0, %%ebx \n\t" + "incl %%ecx \n\t" + "subl %3, %%edi \n\t" + "addl %4, %%esi \n\t" + "cmpl %5, %%ecx \n\t" + "jl pixelLoopNeg2 \n\t" + " \n\t" + "popl %%esi \n\t" + "popl %%edi \n\t" + "popl %%edx \n\t" + "popl %%ecx \n\t" + "popl %%ebx \n\t" + "popl %%eax \n\t" + : /* no outputs */ + : "g" (squareSize), "g" (dest), "g" (sourcePtr), + "g" (destStride), "g" (sourceRowAdd), "g" (squareSize) + : "eax", "ebx", "ecx", "edx", "esi", "edi", "cc", "memory" ); + } else { + destStride = ( destStride + squareSize ) << 1; + __asm__ __volatile__( "pushl %%eax \n\t" + "pushl %%ebx \n\t" + "pushl %%ecx \n\t" + "pushl %%edx \n\t" + "pushl %%edi \n\t" + "pushl %%esi \n\t" + " \n\t" + "movl %0, %%eax \n\t" + "movl %1, %%edi \n\t" + "movl %2, %%esi \n\t" + "leal (%%edi,%%eax,$2), %%edx \n\t" + "movl $0, %%ecx \n\t" + "movl %3, %%ebx \n\t" + " \n" + "pixelLoop: \n\t" + "movw (%%esi,%%ebx), %%ax \n\t" + "shll $16, %%eax \n\t" + "addl $4, %%edi \n\t" + "movw (%%esi), %%ax \n\t" + "leal (%%esi,%%ebx,$2), %%esi \n\t" + "movl %%eax, -4(%%edi) \n\t" + "cmpl %%edx, %%edi \n\t" + "jnz pixelLoop \n\t" + " \n\t" + "incl %%ecx \n\t" + "subl %4, %%edi \n\t" + "movl %0, %%eax \n\t" + "addl %5, %%esi \n\t" + "leal (%%edi,%%eax,$2), %%edx \n\t" + "cmpl %4, %%ecx \n\t" + "jl pixelLoop \n\t" + " \n\t" + "popl %%esi \n\t" + "popl %%edi \n\t" + "popl %%edx \n\t" + "popl %%ecx \n\t" + "popl %%ebx \n\t" + "popl %%eax \n\t" + : /* no outputs */ + : "g" (squareSize), "g" (dest), "g" (sourcePtr), + "g" (sourceStep), "g" (destStride), "g" (sourceRowAdd) + : "eax", "ebx", "ecx", "edx", "esi", "edi", "cc", "memory" ); + } + +} + +void bitmapExtrude5551_asm( const void* srcMip, void* mip, U32 height, U32 width ) +{ + const U16* src = static_cast( srcMip ); + U16* dst = static_cast( 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 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; + } + + // now that we've taken care of our pathological cases... + U64 ZERO = 0; + const U8* src = static_cast( srcMip ); + U8* dst = static_cast( mip ); + U32 srcStride = ( width << 1 ) * 3; + U32 dstStride = width * 3; + + for( U32 y = 0; y < height; y++ ) { + __asm__ __volatile__( "movl %0, %%eax \n\t" + "movl %%eax, %%ebx \n\t" + "addl %1, %%ebx \n\t" + "movl %2, %%ecx \n\t" + "movl %3, %%edx \n\t" + " \n" + "0: \n\t" + " \n\t" + "punpcklbw (%%eax), %%mm0 \n\t" + "psrlw $8, %%mm0 \n\t" + " \n\t" + "punpcklbw 3(%%eax), %%mm1 \n\t" + "psrlw $8, %%mm1 \n\t" + "paddw %%mm1, %%mm0 \n\t" + " \n\t" + "punpcklbw (%%ebx), %%mm1 \n\t" + "psrlw $8, %%mm1 \n\t" + "paddw %%mm1, %%mm0 \n\t" + " \n\t" + "punpcklbw 3(%%ebx), %%mm1 \n\t" + "psrlw $8, %%mm1 \n\t" + "paddw %%mm1, %%mm0 \n\t" + " \n\t" + "psrlw $2, %%mm0 \n\t" + "packuswb %4, %%mm0 \n\t" + " \n\t" + "movd %%mm0, (%%ecx) \n\t" + "addl $6, %%eax \n\t" + "addl $6, %%ebx \n\t" + "addl $3, %%ecx \n\t" + "decl %%edx \n\t" + "jnz 0b \n\t" + : /* no outputs */ + : "g" (src), "g" (srcStride), "g" (dst), + "g" (width), "m" (ZERO) + : "eax", "ebx", "ecx", "edx", "cc", "memory" ); + src += srcStride + srcStride; + dst += dstStride; + } + __asm__ __volatile__( "emms \n\t" ); + +} + +void bitmapConvertRGB_to_5551_mmx( U8*src, U32 pixels ) +{ + U64 MULFACT = 0x0008200000082000; + U64 REDBLUE = 0x00f800f800f800f8; + U64 GREEN = 0x0000f8000000f800; + U64 ALPHA = 0x0000000000010001; + U64 ZERO = 0x0000000000000000; + + U32 evenPixels = pixels >> 1; + U32 oddPixels = pixels & 1; + U16* dst = reinterpret_cast( src ); + + if( evenPixels ) { + __asm__ __volatile__( "movl %0, %%eax \n\t" + "movl %1, %%ebx \n\t" + "movl %2, %%edx \n\t" + " \n" + "pixel_loop2: \n\t" + "movd (%%eax), %%mm0 \n\t" + "movd 3(%%eax), %%mm1 \n\t" + "punpckldq %%mm1, %%mm0 \n\t" + "movq %%mm0, %%mm1 \n\t" + "pand %3, %%mm0 \n\t" + "pmaddwd %4, %%mm0 \n\t" + " \n\t" + "pand %5, %%mm1 \n\t" + "por %%mm1, %%mm0 \n\t" + "psrld $6, %%mm0 \n\t" + "packssdw %6, %%mm0 \n\t" + "pslld $1, %%mm0 \n\t" + "por %7, %%mm0 \n\t" + "movd %%mm0, (%%ebx) \n\t" + " \n\t" + "addl $6, %%eax \n\t" + "addl $4, %%ebx \n\t" + "decl %%edx \n\t" + "jnz pixel_loop2 \n\t" + " \n\t" + "movl %%eax, %0 \n\t" + "movl %%ebx, %1 \n\t" + "emms \n\t" + : /* no outputs */ + : "g" (src), "g" (dst), "g" (evenPixels), + "m" (REDBLUE), "m" (MULFACT), "m" (GREEN), + "m" (ZERO), "m" (ALPHA) + : "eax", "ebx", "edx", "cc", "memory" ); + } + + 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; + } + +} + +void PlatformBlitInit( void ) +{ + bitmapExtrude5551 = bitmapExtrude5551_asm; + bitmapExtrudeRGB = bitmapExtrudeRGB_c; + + if( Platform::SystemInfo.processor.properties & CPU_PROP_MMX ) { + bitmapExtrudeRGB = bitmapExtrudeRGB_mmx; + bitmapConvertRGB_to_5551 = bitmapConvertRGB_to_5551_mmx; + } + + // Per 10/04/2000 change to PlatformWin32/linuxAsmBlit.cc + // terrMipBlit = terrMipBlit_asm; +} diff --git a/platformLinux/linuxCPUInfo.cc b/platformLinux/linuxCPUInfo.cc new file mode 100644 index 0000000..a470305 --- /dev/null +++ b/platformLinux/linuxCPUInfo.cc @@ -0,0 +1,227 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include + +#include "Platform/platform.h" +#include "platformLinux/platformLinux.h" +#include "console/console.h" +#include "Core/stringTable.h" + +extern "C" cpu_init_ASM( char* vendor, unsigned int* processor, unsigned int* properties ); + +Platform::SystemInfo_struct Platform::SystemInfo; + +void Processor::init( void ) +{ + 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; + + char vendor[13]; + U32 properties = 0; + U32 processor = 0; + + memset( vendor, 0, 13 ); + + enum { + BIT_FPU = 1, + BIT_RDTSC = 1 << 4, + BIT_MMX = 1 << 23, + BIT_SSE = 1 << 25, + BIT_3DNOW = 1 << 31 + }; + + // when this code was previously inline, gcc would + // mysteriously optimize away the stores to properties, etc. + cpu_init_ASM( vendor, &processor, &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 ) { + + Platform::SystemInfo.processor.properties |= ( properties & BIT_SSE ) ? CPU_PROP_SSE : 0; + + switch( processor & 0xf00 ) { + case 0x600: + + switch( processor & 0xf0 ) { + case 0x10: + Platform::SystemInfo.processor.type = CPU_Intel_PentiumII; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel Pentium Pro" ); + break; + case 0x30: + case 0x50: + case 0x60: + Platform::SystemInfo.processor.type = CPU_Intel_PentiumII; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel Pentium II" ); + break; + case 0x70: + case 0x80: + case 0xa0: + Platform::SystemInfo.processor.type = CPU_Intel_PentiumIII; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel Pentium III" ); + break; + default: + Platform::SystemInfo.processor.type = CPU_Intel_PentiumII; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel (unknown, PPro family)" ); + break; + } + + break; + case 0x500: + Platform::SystemInfo.processor.type = CPU_Intel_Pentium; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel Pentium" ); + break; + default: + Platform::SystemInfo.processor.type = CPU_Intel_Unknown; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel (unknown)" ); + break; + } + + } else if( dStricmp( vendor, "AuthenticAMD" ) == 0 ) { + Platform::SystemInfo.processor.properties |= ( properties & BIT_3DNOW ) ? CPU_PROP_3DNOW : 0; + + switch( processor ) { + case 0x700: + case 0x710: + Platform::SystemInfo.processor.type = CPU_AMD_K7; + Platform::SystemInfo.processor.name = StringTable->insert( "AMD K7" ); + break; + case 0x660: + case 0x670: + Platform::SystemInfo.processor.type = CPU_AMD_K6; + Platform::SystemInfo.processor.name = StringTable->insert( "AMD K6" ); + break; + case 0x680: + Platform::SystemInfo.processor.type = CPU_AMD_K6_2; + Platform::SystemInfo.processor.name = StringTable->insert( "AMD K6-2" ); + break; + case 0x690: + Platform::SystemInfo.processor.type = CPU_AMD_K6_3; + Platform::SystemInfo.processor.name = StringTable->insert( "AMD K6-3" ); + break; + case 0x510: + case 0x520: + case 0x530: + Platform::SystemInfo.processor.type = CPU_AMD_K6_3; + Platform::SystemInfo.processor.name = StringTable->insert( "AMD K5" ); + break; + default: + Platform::SystemInfo.processor.type = CPU_AMD_Unknown; + Platform::SystemInfo.processor.name = StringTable->insert( "AMD (unknown)" ); + break; + } + + } else if( dStricmp( vendor, "CyrixInstead" ) == 0 ) { + + switch( processor ) { + case 0x520: + Platform::SystemInfo.processor.type = CPU_Cyrix_6x86; + Platform::SystemInfo.processor.name = StringTable->insert( "Cyrix 6x86" ); + break; + case 0x440: + Platform::SystemInfo.processor.type = CPU_Cyrix_MediaGX; + Platform::SystemInfo.processor.name = StringTable->insert( "Cyrix Media GX" ); + break; + case 0x600: + Platform::SystemInfo.processor.type = CPU_Cyrix_6x86MX; + Platform::SystemInfo.processor.name = StringTable->insert( "Cyrix 6x86mx/MII" ); + break; + case 0x540: + Platform::SystemInfo.processor.type = CPU_Cyrix_GXm; + Platform::SystemInfo.processor.name = StringTable->insert( "Cyrix GXm" ); + break; + default: + Platform::SystemInfo.processor.type = CPU_Cyrix_Unknown; + Platform::SystemInfo.processor.name = StringTable->insert( "Cyrix (unknown)" ); + break; + } + + } + + if( Platform::SystemInfo.processor.properties & CPU_PROP_RDTSC && + Platform::SystemInfo.processor.properties & CPU_PROP_FPU ) { + const U32 MS_INTERVAL = 750; + U32 timing[2]; + U32 ticks = 0; + + __asm__ __volatile__( "pushl %%eax \n\t" + "pushl %%edx \n\t" + "rdtsc \n\t" + "movl %%eax, (%0) \n\t" + "movl %%edx, 4(%0) \n\t" + "popl %%edx \n\t" + "popl %%eax \n\t" + : /* no outputs */ + : "r" (timing) + : "eax", "edx", "cc", "memory" ); + + U32 ms = Platform::getRealMilliseconds( ); + + while( Platform::getRealMilliseconds ( ) < ms + MS_INTERVAL ) { + // empty + } + + ms = Platform::getRealMilliseconds( ) - ms; + + __asm__ __volatile__( "pushl %%eax \n\t" + "pushl %%edx \n\t" + "rdtsc \n\t" + "subl 4(%1), %%edx \n\t" + "sbbl (%1), %%eax \n\t" + "movl %%eax, %0 \n\t" + "popl %%edx \n\t" + "popl %%eax \n\t" + : "=g" (ticks) + : "r" (timing) + : "eax", "edx", "cc", "memory" ); + + U32 mhz = static_cast( ticks ) / static_cast( ms ) / 1000.0f; + + U32 bucket25 = mhz % 25; + U32 bucket33 = mhz % 33; + U32 bucket50 = mhz % 50; + + if( bucket50 < 8 || bucket50 > 42 ) { + Platform::SystemInfo.processor.mhz = static_cast( ( mhz + ( 50.0f / 2.0f ) ) / 50.0f ) * 50; + } else if( bucket25 < 5 || bucket25 > 20 ) { + Platform::SystemInfo.processor.mhz = static_cast( ( mhz + ( 25.0f / 2.0f ) ) / 25.0f ) * 25; + } else if( bucket33 < 5 || bucket33 > 28 ) { + Platform::SystemInfo.processor.mhz = static_cast( ( mhz + ( 33.0f / 2.0f ) ) / 33.0f ) * 33; + } else { + Platform::SystemInfo.processor.mhz = static_cast( 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" ); + } + + PlatformBlitInit( ); +} diff --git a/platformLinux/linuxCPUInfo_ASM.asm b/platformLinux/linuxCPUInfo_ASM.asm new file mode 100644 index 0000000..4f06eb2 --- /dev/null +++ b/platformLinux/linuxCPUInfo_ASM.asm @@ -0,0 +1,64 @@ +; +; CPU identification fun. +; + +; +; void cpu_init_ASM( char* vendor, int* processor, int* properties ) +; + +%define in_vendor [ebp+8] +%define in_proc [ebp+12] +%define in_prop [ebp+16] + +global cpu_init_ASM + +cpu_init_ASM: + + push ebp + mov ebp, esp + + push ebx + push edx + push ecx + push esi + + pushfd + pushfd + pop eax + + mov ebx, eax + xor eax, 0x200000 + push eax + popfd + pushfd + pop eax + cmp eax, ebx + jz .L1 + + xor eax, eax + cpuid + + mov esi, in_vendor + mov dword [esi], ebx + mov dword [esi+4], edx + mov dword [esi+8], ecx + + mov eax, 1 + cpuid + + and eax, 0x0ff0 + mov esi, in_proc + mov [esi], eax + mov esi, in_prop + mov [esi], edx + +.L1: + popfd + + pop esi + pop ecx + pop edx + pop ebx + + pop ebp + ret \ No newline at end of file diff --git a/platformLinux/linuxCodeMap.cc b/platformLinux/linuxCodeMap.cc new file mode 100644 index 0000000..e6c6e1f --- /dev/null +++ b/platformLinux/linuxCodeMap.cc @@ -0,0 +1,249 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include + +#include "Platform/platform.h" +#include "Platform/event.h" + +static KeyCodes SDLCodeMap0_127[] = +{ + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_BACKSPACE, // 8 + KEY_TAB, // 9 + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_RETURN, // 13 + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_PAUSE, // 19 + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_ESCAPE, // 27 + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_SPACE, // 32 + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_APOSTROPHE, // 39 + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_COMMA, // 44 + KEY_MINUS, // 45 + KEY_PERIOD, + KEY_SLASH, + KEY_0, // 48 + KEY_1, // 49 + KEY_2, // 50 + KEY_3, // 51 + KEY_4, // 52 + KEY_5, // 53 + KEY_6, // 54 + KEY_7, // 55 + KEY_8, // 56 + KEY_9, // 57 + KEY_NULL, + KEY_SEMICOLON, // 59 + KEY_NULL, + KEY_EQUALS, // 61 + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_LBRACKET, // 91 + KEY_BACKSLASH, // 92 + KEY_RBRACKET, // 93 + KEY_NULL, + KEY_NULL, + KEY_TILDE, + KEY_A, // 97 + KEY_B, // 98 + KEY_C, // 99 + KEY_D, // 100 + KEY_E, // 101 + KEY_F, // 102 + KEY_G, // 103 + KEY_H, // 104 + KEY_I, // 105 + KEY_J, // 106 + KEY_K, // 107 + KEY_L, // 108 + KEY_M, // 109 + KEY_N, // 110 + KEY_O, // 111 + KEY_P, // 112 + KEY_Q, // 113 + KEY_R, // 114 + KEY_S, // 115 + KEY_T, // 116 + KEY_U, // 117 + KEY_V, // 118 + KEY_W, // 119 + KEY_X, // 120 + KEY_Y, // 121 + KEY_Z, // 122 + KEY_NULL, // 123 + KEY_NULL, // 124 + KEY_NULL, // 125 + KEY_NULL, // 126 + KEY_DELETE, // 127 +}; + +static KeyCodes SDLCodeMap256_319[] = +{ + KEY_NUMPAD0, // 256 + KEY_NUMPAD1, // 257 + KEY_NUMPAD2, // 258 + KEY_NUMPAD3, // 259 + KEY_NUMPAD4, // 260 + KEY_NUMPAD5, // 261 + KEY_NUMPAD6, // 262 + KEY_NUMPAD7, // 263 + KEY_NUMPAD8, // 264 + KEY_NUMPAD9, // 265 + KEY_DECIMAL, // 266 + KEY_DIVIDE, // 267 + KEY_MULTIPLY, // 268 + KEY_SUBTRACT, // 269 + KEY_ADD, // 270 + KEY_NUMPADENTER, // 271 + KEY_EQUALS, // 272 + KEY_UP, // 273 + KEY_DOWN, // 274 + KEY_RIGHT, // 275 + KEY_LEFT, // 276 + KEY_INSERT, + KEY_HOME, + KEY_END, + KEY_PAGE_UP, + KEY_PAGE_DOWN, + KEY_F1, // 282 + KEY_F2, // 283 + KEY_F3, // 284 + KEY_F4, // 285 + KEY_F5, // 286 + KEY_F6, // 287 + KEY_F7, // 288 + KEY_F8, // 289 + KEY_F9, // 290 + KEY_F10, // 291 + KEY_F11, // 292 + KEY_F12, // 293 + KEY_F13, // 294 + KEY_F14, // 295 + KEY_F15, // 296 + KEY_NULL, + KEY_NULL, + KEY_NULL, + KEY_NUMLOCK, // 300 + KEY_CAPSLOCK, // 301 + KEY_SCROLLLOCK, // 302 + KEY_RSHIFT, // 303 + KEY_LSHIFT, // 304 + KEY_RCONTROL, // 305 + KEY_LCONTROL, // 306 + KEY_RALT, // 307 + KEY_LALT, // 308 + KEY_NULL, + KEY_NULL, + KEY_WIN_LWINDOW, // 311 + KEY_WIN_RWINDOW, // 312 + KEY_OEM_102, // 313 + KEY_NULL, + KEY_HELP, // 315 + KEY_PRINT, // 316 + KEY_NULL, + KEY_NULL, + KEY_WIN_APPS, // 319 +}; + +SDLKey translateKeyCodeToSDL( KeyCodes code ) +{ + + for( S32 i = 0; i <= 127; i++ ) { + + if( SDLCodeMap0_127[i] == code ) { + return (SDLKey) i; + } + + } + + for( S32 i = 256; i <= 319; i++ ) { + + if( SDLCodeMap256_319[ i - 256 ] == code ) { + return (SDLKey) i; + } + + } + + return SDLK_UNKNOWN; +} + +KeyCodes translateSDLToKeyCode( SDLKey sym ) +{ + S32 i_sym = static_cast( sym ); + + if( i_sym >= 0 && i_sym <= 127 ) { + return SDLCodeMap0_127[i_sym]; + } + + if( i_sym >= 256 && i_sym <= 319 ) { + i_sym -= 256; + return SDLCodeMap256_319[i_sym]; + } + + return KEY_NULL; +} diff --git a/platformLinux/linuxConsole.cc b/platformLinux/linuxConsole.cc new file mode 100644 index 0000000..ba3683a --- /dev/null +++ b/platformLinux/linuxConsole.cc @@ -0,0 +1,573 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "platformLinux/platformLinux.h" +#include "platformLinux/linuxConsole.h" +#include "Platform/event.h" +#include "Platform/gameInterface.h" +#include "loki_utils.h" + +LinuxConsole* linuxConsole = 0; + +ConsoleFunction( enableWinConsole, void, 2, 2, "enableWinConsole(bool);" ) +{ + linuxConsole->enable( dAtob( argv[1] ) ); +} + +static void handle_sigwinch(int sig) +{ + if ( linuxConsole ) { + linuxConsole->check_winsize(); + } +} + +void LinuxConsole::create( void ) +{ + linuxConsole = new LinuxConsole( ); + signal(SIGWINCH, handle_sigwinch); +} + +void LinuxConsole::destroy( void ) +{ + signal(SIGWINCH, SIG_DFL); + delete linuxConsole; + linuxConsole = 0; +} + +static void linuxConsoleConsumer(ConsoleLogEntry::Level level, const char* line) +{ + if ( linuxConsole ) { + linuxConsole->processConsoleLine( level, line ); + } +} + +LinuxConsole::LinuxConsole( void ) : console_enabled( false ) +{ + init_history(); + Con::addConsumer( linuxConsoleConsumer ); +} + +LinuxConsole::~LinuxConsole( void ) +{ + enable(false); + free_history(); + Con::removeConsumer( linuxConsoleConsumer ); +} + +// Terminal manipulation routines + +void LinuxConsole::set_title(const char *title) +{ + printf("\033]0;%s\07", title); + fflush(stdout); +} + +void LinuxConsole::move_bol(void) +{ + printf("\r"); +} + +void LinuxConsole::clear_eol(void) +{ + printf("\033[K"); +} + +void LinuxConsole::clear_line(void) +{ + move_bol(); + clear_eol(); +} + +void LinuxConsole::move_to(int row, int col) +{ + printf("\033[%d;%dH", row, col); +} + +void LinuxConsole::move_back() +{ + printf("\033[D"); +} + +void LinuxConsole::set_scroll(int hi, int lo, int col) +{ + if ( ! hi && ! lo ) { + printf("\033[r"); + } else { + printf("\033[%d;%dr", hi, lo); + move_to(lo, col); + } +} + +void LinuxConsole::check_winsize(void) +{ + const char *env; + + struct /* winsize */ { + unsigned short ws_row; + unsigned short ws_col; + unsigned short ws_xpixel; + unsigned short ws_ypixel; + } mywinz; + + if ( ioctl(0, TIOCGWINSZ, &mywinz) == 0 ) { + if ( mywinz.ws_row ) + rows = mywinz.ws_row; + if ( mywinz.ws_col ) + cols = mywinz.ws_col; + } + if ( (env=getenv("LINES")) != NULL ) + rows=atoi(env); + if ( (env=getenv("COLUMNS")) != NULL ) + cols=atoi(env); + + /* Now set defaults if we can't find the window size */ + if ( ! rows ) rows=24; + if ( ! cols ) cols=80; + + // Display the prompt + display_prompt(); +} + +void LinuxConsole::enable(bool enabled) +{ + static struct termios saved_termios; + + if ( enabled == console_enabled ) { + return; + } + console_enabled = enabled; + + // Okay, if there's no terminal, we're done + if ( ! isatty(0) || ! isatty(1) ) { + return; + } + + if ( enabled ) { + // Save the terminal settings and go raw + const char *title; + const char *env; + struct termios raw_termios; + struct fd_set fdset; + struct timeval timeout; + + // Clear the input buffer + delete_line(); + + // Save the original terminal settings + tcgetattr(0, &saved_termios); + + // Set the raw terminal settings + raw_termios = saved_termios; + raw_termios.c_iflag = IGNBRK; + raw_termios.c_oflag &= ~OLCUC; + raw_termios.c_lflag = ISIG; + raw_termios.c_cc[VMIN] = 0; + raw_termios.c_cc[VTIME] = 10; + tcsetattr(0, TCSADRAIN, &raw_termios); + + // See if the terminal uses vt100 emulation + split_window = false; + printf("\033[c"); /* Vt100 test: ESC [ c */ + fflush(stdout); + FD_ZERO( &fdset ); + FD_SET( 0, &fdset ); + timeout.tv_sec = 0; + timeout.tv_usec = 500*1000; + while ( select(1, &fdset, NULL, NULL, &timeout) == 1 ) { + char ch; + + read(0, &ch, 1); + if ( ch == '\033' ) { + split_window = true; + } + timeout.tv_sec = 0; + timeout.tv_usec = 500*1000; + } + + // Set up the split window if we can + rows = 0; + cols = 0; + if ( split_window ) { + check_winsize(); + } else { + // No split window, do line-by-line input + tcsetattr(0, TCSADRAIN, &saved_termios); + } + + // Try to set the xterm title bar + title = Con::getVariable("Con::WindowTitle"); + if ( title && *title ) { + // See if the terminal uses xterm title + if ( ((env=getenv("TERM")) != NULL) && + (dStrncmp(env, "xterm", 5) == 0) ) { + set_title(title); + } + } + } else { + // Clear the split window, if any + if ( split_window ) { +#ifdef VT100_SCROLL + set_scroll(0, 0, 0); + move_to(rows, 0); + clear_eol(); +#else + printf("\n"); +#endif + // Restore the terminal settings + tcsetattr(0, TCSADRAIN, &saved_termios); + } + } +} + +void LinuxConsole::processConsoleLine( ConsoleLogEntry::Level level, const char* line ) +{ + char cleaned[256]; + + if ( isEnabled( ) ) { + // clean string--forums sometimes emit + // control characters which foo the terminal + dStrncpy( cleaned, line, 256 ); + cleaned[255] = '\0'; + int length = dStrlen( cleaned ); + + for( int i = 0; i < length; i++ ) { + + if( !isprint( cleaned[i] ) ) { + cleaned[i] = ' '; + } + } + if ( split_window ) { +#ifdef VT100_SCROLL + const char *prompt = Con::getVariable("Con::Prompt"); + set_scroll(1, rows-1, 0); + printf( "\n%s", cleaned ); + set_scroll(rows-1, rows, dStrlen(prompt)+curpos+1); +#else + clear_line(); + printf( "%s\n", cleaned ); + display_prompt(); +#endif + fflush(stdout); + } else { + printf( "%s\n", cleaned ); + } + } +} + +void LinuxConsole::forward_char(void) +{ + if ( curpos < dStrlen(line) ) { + ++curpos; + } +} + +void LinuxConsole::backward_char(void) +{ + if ( curpos > 0 ) { + --curpos; + } +} + +void LinuxConsole::forward_eol(void) +{ + curpos = dStrlen(line); +} + +void LinuxConsole::backward_bol(void) +{ + curpos = 0; +} + +void LinuxConsole::backspace_char(void) +{ + if ( curpos > 0 ) { + --curpos; + memcpy(&line[curpos], &line[curpos+1], sizeof(line)-curpos); + } +} + +void LinuxConsole::delete_char(void) +{ + memcpy(&line[curpos], &line[curpos+1], sizeof(line)-curpos); +} + +void LinuxConsole::delete_eol(void) +{ + memset(&line[curpos], 0, sizeof(line)-curpos); +} + +void LinuxConsole::delete_line(void) +{ + curpos = 0; + memset(line, 0, sizeof(line)); +} + +void LinuxConsole::insert_char(char ch) +{ + if ( line[curpos] != '\0' ) { + memmove(&line[curpos+1],&line[curpos],(sizeof(line)-curpos-1)); + } + replace_char(ch); +} + +void LinuxConsole::replace_char(char ch) +{ + line[curpos++] = ch; +} + +void LinuxConsole::replace_line(const char *text) +{ + delete_line(); + if ( text ) { + strncpy(line, text, sizeof(line)-1); + curpos = dStrlen(line); + } +} + +void LinuxConsole::init_history(void) +{ + char histfile[PATH_MAX]; + FILE *hfp; + + memset(history, 0, sizeof(history)); + current_history = 0; + + // Load the history from our history file + sprintf(histfile, "%s/history.txt", loki_getprefpath()); + hfp = fopen(histfile, "r"); + if ( hfp ) { + while ( fgets(line, sizeof(line), hfp) ) { + line[dStrlen(line)-1] = '\0'; + add_history(line); + incr_history(); + } + fclose(hfp); + } +} + +void LinuxConsole::add_history(const char *text) +{ + if ( history[current_history] ) { + free(history[current_history]); + } + history[current_history] = dStrdup(text); +} + +void LinuxConsole::incr_history(void) +{ + ++current_history; + if ( current_history == NUM_HISTORY ) { + current_history = 0; + } +} + +void LinuxConsole::decr_history(void) +{ + --current_history; + if ( current_history < 0 ) { + current_history += NUM_HISTORY; + } +} + +void LinuxConsole::prev_history(void) +{ + add_history(line); + decr_history(); + replace_line(history[current_history]); +} + +void LinuxConsole::next_history(void) +{ + add_history(line); + incr_history(); + replace_line(history[current_history]); +} + +void LinuxConsole::free_history(void) +{ + char histfile[PATH_MAX]; + FILE *hfp; + + // Save the history to our history file + sprintf(histfile, "%s/history.txt", loki_getprefpath()); + hfp = fopen(histfile, "w"); + if ( hfp ) { + int history_mark = current_history; + do { + if ( history[current_history] ) { + fprintf(hfp, "%s\n", history[current_history]); + } + incr_history(); + } while ( current_history != history_mark ); + + fclose(hfp); + } + + for ( S32 i=0; i 0 ) { + S32 eventSize; + + add_history(line); + incr_history(); + eventSize = ConsoleEventHeaderSize; + dStrcpy( postEvent.data, line ); + postEvent.size = eventSize + dStrlen(line) + 1; + Game->postEvent( postEvent ); + delete_line(); + } + break; + + // Add character to line + default: + { + if ( (ch == '\033') && (arrow_state == AT_RESET) ) { + arrow_state = AT_ESCAPE; + break; + } else + if ( (ch == '[') && (arrow_state == AT_ESCAPE) ) { + arrow_state = AT_BRACKET; + break; + } else if ( arrow_state == AT_BRACKET ) { + switch (ch) { + // Cursor up + case 'A': + prev_history(); + break; + // Cursor down + case 'B': + next_history(); + break; + // Cursor right + case 'C': + forward_char(); + break; + // Cursor left + case 'D': + backward_char(); + break; + // Unknown escape code + default: + break; + } + arrow_state = AT_RESET; + break; + } + arrow_state = AT_RESET; + if ( isprint(ch) && (curpos < (sizeof(line)-1)) ) { + insert_char(ch); + } + } + break; + } + display_prompt(); + } +} diff --git a/platformLinux/linuxConsole.h b/platformLinux/linuxConsole.h new file mode 100644 index 0000000..42b8f5e --- /dev/null +++ b/platformLinux/linuxConsole.h @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LINUXCONSOLE_H_ +#define _LINUECONSOLE_H_ + +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif +#ifndef _EVENT_H_ +#include "Platform/event.h" +#endif + +#define NUM_HISTORY 32 + +class LinuxConsole +{ + ConsoleEvent postEvent; + bool console_enabled; + bool split_window; + int rows, cols; + int curpos; + char line[1024]; + char *history[NUM_HISTORY]; + int current_history; + + void set_title(const char *title); + void move_bol(void); + void clear_eol(void); + void clear_line(void); + void move_to(int row, int col); + void move_back(void); + void set_scroll(int hi, int lo, int col); + + void forward_char(void); + void backward_char(void); + void forward_eol(void); + void backward_bol(void); + void backspace_char(void); + void delete_char(void); + void delete_eol(void); + void delete_line(void); + + void insert_char(char ch); + void replace_char(char ch); + void replace_line(const char *text); + + void init_history(void); + void add_history(const char *text); + void incr_history(void); + void decr_history(void); + void prev_history(void); + void next_history(void); + void free_history(void); + + void tab_complete(void); + + void display_prompt(void); + +public: + LinuxConsole( void ); + ~LinuxConsole( void ); + + void enable( bool enabled ); + bool isEnabled( void ) { return console_enabled; }; + void process( void ); + void processConsoleLine( ConsoleLogEntry::Level level, const char* line ); + static void create( ); + static void destroy( ); + + void check_winsize(void); +}; + +extern LinuxConsole* linuxConsole; + +#endif diff --git a/platformLinux/linuxFileio.cc b/platformLinux/linuxFileio.cc new file mode 100644 index 0000000..ee6a243 --- /dev/null +++ b/platformLinux/linuxFileio.cc @@ -0,0 +1,853 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "loki_utils.h" + +#include "engine/platformLinux/platformLinux.h" +#include "engine/core/fileio.h" +#include "engine/core/tVector.h" +#include "engine/core/stringTable.h" +#include "engine/console/console.h" + +static void build_canonical_name( const char* path, char* buffer, int size ) +{ + struct stat sb; + + /* default value is identity */ + strncpy( buffer, path, size ); + buffer[size-1] = '\0'; + + /* Shortcut, most of the time the file name is fine */ + if ( stat(buffer, &sb) != 0 ) { + char* r; + char *pathname; + char *filename; + DIR* dir; + struct dirent* de; + + /* find the last slash */ + pathname = buffer; + r = strrchr( pathname, '/' ); + if( r ) { + *r = '\0'; + filename = r+1; + } + + /* + * Look for an existing name. + */ + dir = opendir( pathname ); + if( dir ) { + while( ( de = readdir( dir ) ) != NULL ) { + + if( strcasecmp( de->d_name, filename ) == 0 ) { + strncpy(filename, de->d_name, size-(strlen(pathname)+1)); + break; + } + + } + closedir( dir ); + } + if ( r ) { + *r = '/'; + } + } +} + +int iopen( const char* path, int flags ) +{ + char buffer[PATH_MAX]; + int fd = -1; + + build_canonical_name( path, buffer, PATH_MAX ); + fd = open( buffer, flags ); + return fd; +} + +FILE* ifopen( const char* path, const char* mode ) +{ + char buffer[PATH_MAX]; + FILE* f = NULL; + + build_canonical_name( path, buffer, PATH_MAX ); + f = fopen( buffer, mode ); + return f; +} + +static void forwardSlash( char* s ) +{ + while( *s ) { + if( *s == '\\' ) { + *s = '/'; + } + s++; + } +} + +static bool copyFile(const char *src, const char *dst) +{ + int status; + FILE *i, *o; + int len; + char data[BUFSIZ]; + + i = fopen(src, "rb"); + o = fopen(dst, "wb"); + if ( !i || !o ) { + if ( i ) { + fclose(i); + } + if ( o ) { + fclose(i); + } + return(0); + } + status = 0; + while ( (len = fread(data, 1, sizeof(data), i)) > 0 ) { + if ( fwrite(data, 1, len, o) != len ) { + status = -1; + break; + } + } + fclose(i); + if ( fclose(o) != 0 ) { + status = -1; + } + if ( status < 0 ) { + unlink(dst); + } + return(status == 0); +} + +//----------------------------------------------------------------------------- +bool dFileDelete(const char * name) +{ + bool status; + + status = false; + if (name && !dStrstr(name, "../")) { + char canonical[PATH_MAX]; + char path[PATH_MAX]; + + // We can only write and delete files in the prefs directory + dSprintf(canonical, sizeof(canonical), "%s/%s", loki_getprefpath(), name); + build_canonical_name( canonical, path, sizeof(path) ); + if ( unlink(path) == 0 ) { + status = true; + } + } + return status; +} + +// change the modified time to the current time +bool dFileTouch(const char * name) +{ + bool status; + + status = false; + if (name && !dStrstr(name, "../")) { + char canonical[PATH_MAX]; + char path[PATH_MAX]; + + // We can only modify files in the prefs directory + dSprintf(canonical, sizeof(canonical), "%s/%s", loki_getprefpath(), name); + build_canonical_name( canonical, path, sizeof(path) ); + if ( utime(path, NULL) == 0 ) { + status = true; + } + } + return status; +} + +File::File( void ) : currentStatus( Closed ), capability( 0 ) +{ + handle = NULL; +} + +File::~File( void ) +{ + close( ); + handle = NULL; +} + +File::Status File::open( const char* filename, const AccessMode mode ) +{ + char modded[PATH_MAX]; + char canonical[PATH_MAX]; + + assert( filename ); + + dStrncpy( modded, filename, PATH_MAX ); + forwardSlash( modded ); + filename = modded; + + // if it's not absolute already, put it in the user dir. + if( filename[0] != '/' ) { + dSprintf( canonical, PATH_MAX, "%s/%s", loki_getprefpath( ), filename ); + } else { + dStrncpy( canonical, filename, PATH_MAX ); + } + + if( currentStatus != Closed ) { + close( ); + } + + switch( mode ) { + case Read: +#if 1 +#warning FIXME for performance increase! + // FIXME: + // Cache the contents of the preferences directory + // so we don't have to keep searching for each file + // Can we use the output of dumpPath() in the game? +#endif + handle = ifopen( canonical, "r" ); + + if( !handle ) { + // if read-only we can try to open in the game dir itself + // because of the "if absolute" check above, it's possible + // that canonical == filename. + handle = ifopen( filename, "r" ); + } + break; + case Write: + handle = ifopen( canonical, "w" ); + break; + case ReadWrite: + handle = ifopen( canonical, "a+" ); + + // mimic win32 create on missing by seeking to begin + if( handle ) { + fseek( (FILE*) handle, 0, SEEK_SET ); + } + + break; + case WriteAppend: + handle = ifopen( canonical, "r" ); + if( handle ) { + fclose(handle); + } else { + // We have to copy the original file to append + if ( ! copyFile(filename, canonical) ) { + return setStatus( ); + } + } + handle = ifopen( canonical, "a" ); + break; + } + + if( !handle ) { +#ifdef DEBUG + fprintf(stderr, "Unable to open %s\n", filename); +#endif + return setStatus( ); + } + + switch( mode ) { + case Read: + capability = static_cast( FileRead ); + break; + case Write: + case WriteAppend: + capability = static_cast( FileWrite ); + break; + case ReadWrite: + capability = static_cast( FileRead ) | + static_cast( FileWrite ); + break; + } + + return ( currentStatus = Ok ); +} + +U32 File::getPosition( void ) const +{ + return ftell( (FILE*) handle ); +} + +File::Status File::setPosition( S32 position, bool absolute ) +{ + FILE* file = (FILE*) handle; + + if( currentStatus != Ok && currentStatus != EOS ) { + return currentStatus; + } + + if( absolute ) { + fseek( file, position, SEEK_SET ); + } else { + fseek( file, position, SEEK_CUR ); + } + + long current = ftell( file ); + + if( current == -1 ) { + return setStatus( ); + } else if( current >= getSize( ) ) { + return ( currentStatus = EOS ); + } else { + return ( currentStatus = Ok ); + } + +} + +U32 File::getSize( void ) const +{ + + if( currentStatus == Ok || currentStatus == EOS ) { + struct stat buf; + + int fd = fileno( (FILE*) handle ); + + if( fstat( fd, &buf ) ) { + return 0; + } + + return buf.st_size; + } + + return 0; +} + +File::Status File::flush( void ) +{ + + if( fflush( (FILE*) handle ) ) { + return setStatus( ); + } + + return ( currentStatus = Ok ); +} + +File::Status File::close( void ) +{ + + if( currentStatus == Closed ) { + return currentStatus; + } + + if( handle ) { + + if( fclose( (FILE*) handle ) ) { + return setStatus( ); + } + + } + + handle = NULL; + return ( currentStatus = Closed ); +} + +File::Status File::getStatus( void ) const +{ + return currentStatus; +} + +File::Status File::setStatus( void ) +{ + + switch( errno ) { + case EINVAL: + case EACCES: + case EMFILE: + case ENFILE: + case ENOSPC: + return ( currentStatus = IOError ); + + default: + return ( currentStatus = UnknownError ); + } + +} + +File::Status File::setStatus( File::Status status ) +{ + return ( currentStatus = status ); +} + +File::Status File::read( U32 size, char* dst, U32* bytes ) +{ + + if( currentStatus != Ok || size == 0 ) { + return currentStatus; + } + + U32 dummyBytes = 0; + U32* bytesWritten = ( bytes == 0 ) ? &dummyBytes : bytes; + + if( ( *bytesWritten = fread( dst, 1, size, (FILE*) handle ) ) != size ) { + + if( ferror( (FILE*) handle ) ) { + return setStatus( ); + } else { + return ( currentStatus = EOS ); + } + + } + + return ( currentStatus = Ok ); +} + +File::Status File::write( U32 size, const char* src, U32* bytes ) +{ + + if( ( currentStatus != Ok && currentStatus != EOS ) || 0 == size ) { + return currentStatus; + } + + U32 dummyBytes = 0; + U32* bytesWritten = ( bytes == 0 ) ? &dummyBytes : bytes; + + if( ( *bytesWritten = fwrite( src, 1, size, (FILE*) handle ) ) != size ) { + + if( ferror( (FILE*) handle ) ) { + return setStatus( ); + } + + } + + return ( currentStatus = Ok ); +} + +bool File::hasCapability( Capability cap ) const +{ + return ( ( static_cast( cap ) & capability ) != 0 ); +} + +S32 Platform::compareFileTimes( const FileTime& a, const FileTime& b ) +{ + + if( a < b ) { + return -1; + } else if( a > b ) { + return 1; + } else { + return 0; + } + +} + +typedef void* HANDLE; +typedef unsigned char BOOL; +typedef unsigned int DWORD; +typedef char TCHAR; +typedef const char* LPCTSTR; + +#define INVALID_HANDLE_VALUE NULL +#define FILE_ATTRIBUTE_NORMAL 0x0000 +#define FILE_ATTRIBUTE_DIRECTORY 0x0040 +#define MAX_PATH PATH_MAX +#define TRUE 1 +#define FALSE 0 + +typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME; + +typedef struct _WIN32_FIND_DATA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + TCHAR cFileName[ MAX_PATH ]; + TCHAR cAlternateFileName[ 14 ]; +} WIN32_FIND_DATA, *LPWIN32_FIND_DATA; + +HANDLE FindFirstFile( LPCTSTR, LPWIN32_FIND_DATA ); +BOOL FindNextFile( HANDLE, LPWIN32_FIND_DATA ); +BOOL FindClose( HANDLE ); + +/* Opaque handle for Find* functions. */ +typedef struct tagFINDSTRUCT { + char dirpath[PATH_MAX]; + DIR* dp; + regex_t preg; +} FINDSTRUCT; + +ssize_t PreprocessFilename( const char *src, char *dst, ssize_t maxlen ) +{ + int len; + const char *tmp; + + /* Check the size of the destination buffer */ + if( dst ) { + + if( PreprocessFilename( src, NULL, 0 ) > maxlen ) { + /* Not enough space in destination */ + return -1; + } + + } + + /* "*.*" is a special match anything case */ + if( strcmp( src, "*.*") == 0 ) { + const char *match_all = "^.*$"; + + len = strlen( match_all ) + 1; + + if( dst ) { + strcpy( dst, match_all ); + } + + } else + + /* "*." is also a special case -- match anything with no suffix + Warning: "?*." also matches this case, but hope we don't hit it + */ + if( strcmp( src, "*." ) == 0 ) { + const char *match_all_nodot = "^[^\\.]*$"; + + len = strlen( match_all_nodot ) + 1; + + if ( dst ) { + strcpy( dst, match_all_nodot ); + } + + } else { + len = 1; + + for( tmp = src; *tmp; ++tmp ) { + + switch( *tmp ) { + case '*': + case '.': + ++len; + default: + ++len; + } + + } + + len += 2; + + if( dst ) { + *dst++ = '^'; + + for( tmp = src; *tmp; ++tmp ) { + + switch( *tmp ) { + case '*': + *dst++ = '.'; + *dst++ = '*'; + break; + case '?': + *dst++ = '.'; + break; + case '.': + *dst++ = '\\'; + *dst++ = '.'; + break; + default: + *dst++ = *tmp; + } + + } + + *dst++ = '$'; + *dst++ = '\0'; + } + + } + + return len; +} + +HANDLE FindFirstFile( LPCTSTR lpszSearchFile, LPWIN32_FIND_DATA lpffd ) +{ + FINDSTRUCT * findhandle; + int name_pos; + char SearchFilename[256]; + + assert( lpffd != NULL ); + assert( lpszSearchFile != NULL ); + assert( strstr( (char*)lpszSearchFile, ":" ) == NULL ); + assert( strstr( (char*)lpszSearchFile, "\\" ) == NULL ); + + /* Allocate memory for the file handle we return */ + findhandle = (FINDSTRUCT*) malloc( sizeof( *findhandle ) ); + + if( findhandle == NULL ) { + return INVALID_HANDLE_VALUE; + } + + memset( findhandle, 0, sizeof( *findhandle ) ); + + /* Detect and separate a path from a filename in the event there is one */ + strncpy( findhandle->dirpath, lpszSearchFile, 256 - 1 ); + findhandle->dirpath[256-1] = '\0'; + + for( name_pos = strlen( findhandle->dirpath ) - 1; name_pos >= 0; name_pos-- ) { + + if( findhandle->dirpath[name_pos] == '/' ) { + findhandle->dirpath[name_pos] = '\0'; + ++name_pos; + break; + } + + } + + /* Get the filename as a regular expression pattern */ + PreprocessFilename( &lpszSearchFile[name_pos], SearchFilename, 256 ); + + if( regcomp( &(findhandle->preg), SearchFilename, REG_ICASE ) != 0 ) { + free( findhandle ); + return INVALID_HANDLE_VALUE; + } + + /* Make sure we still have a directory after removing filename */ + if ( ! findhandle->dirpath[0] ) + strcpy( findhandle->dirpath, "." ); + + /* Open the directory for reading */ + findhandle->dp = opendir( findhandle->dirpath ); + + if( findhandle->dp == NULL ){ + FindClose( findhandle ); + return INVALID_HANDLE_VALUE; + } + + /* Perform search */ + if( ! FindNextFile( findhandle, lpffd ) ) { + FindClose( findhandle ); + return INVALID_HANDLE_VALUE; + } + + return findhandle; +} + +BOOL FindNextFile( HANDLE handle, LPWIN32_FIND_DATA lpffd ) +{ + FINDSTRUCT* findhandle = (FINDSTRUCT*) handle; + char filepath[256]; + struct stat statbuf; + struct dirent *entry; + + assert( lpffd != NULL ); + assert( findhandle != NULL ); + + while( ( entry = readdir( ( (FINDSTRUCT*)findhandle )->dp ) ) != NULL ) { + + if( (entry->d_name[0] != '.') && regexec( &( ( (FINDSTRUCT*) findhandle )->preg ), + entry->d_name, + 0, + 0, + 0 ) == 0 ) { + sprintf( filepath, "%s/%s", findhandle->dirpath, entry->d_name ); + + if( stat( filepath, &statbuf ) < 0 ) { + continue; + } + + lpffd->ftLastAccessTime.dwLowDateTime = statbuf.st_atime; + lpffd->ftLastWriteTime.dwLowDateTime = statbuf.st_ctime; + + //Is path also included? + strncpy( lpffd->cFileName, entry->d_name, 256 - 1 ); + lpffd->cFileName[256 - 1] = '\0'; + + if( S_ISDIR( statbuf.st_mode ) ) { + lpffd->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; + } else if( S_ISREG( statbuf.st_mode ) ) { + lpffd->dwFileAttributes = FILE_ATTRIBUTE_NORMAL; + } else { + lpffd->dwFileAttributes = 0; + } + + lpffd->nFileSizeLow = statbuf.st_size; + return TRUE; + } + } + + return FALSE; +} + +BOOL FindClose( HANDLE hFindFile ) +{ + FINDSTRUCT* findhandle; + + findhandle = (FINDSTRUCT*) hFindFile; + + if( findhandle == NULL ) { + return FALSE; + } + + regfree( &findhandle->preg ); + + if( findhandle->dp ) { + closedir( findhandle->dp ); + } + + free( findhandle ); + + return TRUE; +} + +static bool recurseDumpPath( const char* basep, + const char* currentp, + Vector& out ) +{ + char current[PATH_MAX]; + char base[PATH_MAX]; + char buffer[PATH_MAX]; + char scratch[PATH_MAX]; + + if( currentp ) { + dStrncpy( current, currentp, PATH_MAX ); + } else { + current[0] = 0; + } + + dStrncpy( base, basep, PATH_MAX ); + basep = base; + + if( current[0] ) { + dSprintf( buffer, sizeof( buffer ), "%s/%s/*", base, current ); + } else { + dSprintf( buffer, sizeof( buffer ), "%s/*", base ); + } + + WIN32_FIND_DATA findData; + HANDLE hFind = FindFirstFile( buffer, &findData ); + + if( hFind == INVALID_HANDLE_VALUE ) { + return false; + } + + while( hFind != INVALID_HANDLE_VALUE ) { + + if( ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 ) { + + if( findData.cFileName[0] != '.' ) { + scratch[0] = 0; + int length = PATH_MAX; + + if( current[0] ) { + dStrncpy( scratch, current, length ); + length -= dStrlen( current ); + dStrncat( scratch, "/", length ); + length--; + } + + dStrncat( scratch, findData.cFileName, length ); + recurseDumpPath( base, scratch, out ); + } + + } else { + out.increment( ); + Platform::FileInfo& info = out.last( ); + + if( current[0] ) { + dSprintf( scratch, sizeof( scratch ), "%s/%s", base, current ); + info.pFullPath = StringTable->insert( scratch ); + info.pVirtPath = StringTable->insert( current ); + } else { + info.pFullPath = StringTable->insert( base ); + info.pVirtPath = NULL; + } + + info.pFileName = StringTable->insert( findData.cFileName ); + info.fileSize = findData.nFileSizeLow; + } + + if( FindNextFile( hFind, &findData ) == 0 ) { + FindClose( hFind ); + hFind = INVALID_HANDLE_VALUE; + } + + } + + return true; +} + +bool Platform::getFileTimes( const char* path, FileTime* create, FileTime* modify ) +{ + WIN32_FIND_DATA findData; + HANDLE h = FindFirstFile( path, &findData ); + + if( h == INVALID_HANDLE_VALUE ) { + return false; + } + + if( create ) { + // NOTE: always 0 under Linux, but shouldn't be + // an issue with the current usage in the codebase + *create = findData.ftCreationTime.dwLowDateTime; + } + + if( modify ) { + *modify = findData.ftLastWriteTime.dwLowDateTime; + } + + FindClose( h ); + return true; +} + +bool Platform::createPath( const char* file ) +{ + char filename[PATH_MAX]; + char pathbuf[PATH_MAX]; + const char* dir = 0; + U32 length = 0; + + pathbuf[0] = 0; + dSprintf( filename, PATH_MAX, "%s/%s", loki_getprefpath( ), file ); + file = filename; + + while( ( dir = dStrchr( file, '/' ) ) != 0 ) { + dStrncpy( pathbuf + length, file, dir - file ); + pathbuf[ length + dir - file ] = 0; + mkdir( pathbuf, 0700 ); + length += dir - file; + pathbuf[ length++ ] = '/'; + file = dir + 1; + } + + return true; +} + +bool Platform::cdFileExists(const char *filePath, const char *volumeName, S32 serialNum) +{ + if (!filePath || !filePath[0]) + return true; + + // FIXME: Add code to scan for CD drives + return true; + + return false; +} + +bool Platform::dumpPath( const char* spec, Vector& out ) +{ + return recurseDumpPath( spec, 0, out ); +} + +void Platform::getCurrentDirectory( char* cwd, const U32 size ) +{ + getcwd( cwd, size ); +} + +// FIXME: Add to Platform::. +bool platformGetUserPath( const char* base, char* user, U32 size ) +{ + dSprintf( user, size, "%s/%s", loki_getprefpath( ), base ); + return true; +} diff --git a/platformLinux/linuxFont.cc b/platformLinux/linuxFont.cc new file mode 100644 index 0000000..ebe7769 --- /dev/null +++ b/platformLinux/linuxFont.cc @@ -0,0 +1,209 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include + +#include + +#include "platformLinux/platformLinux.h" +#include "dgl/gFont.h" +#include "Math/mRect.h" +#include "console/console.h" +#include "Core/fileio.h" + +#ifdef DEDICATED + +void createFontInit( void ) +{ + // empty +} + +void createFontShutdown( void ) +{ + // emtpy +} + +GFont* createFont( const char* name, S32 size ) +{ + // empty + return 0; +} + +#else + +static FT_Library library; + +void createFontInit( void ) +{ + FT_Error error = FT_Init_FreeType( &library ); + + if( error ) { + Con::warnf( ConsoleLogEntry::General, "Freetype initialization failed" ); + } + +} + +void createFontShutdown( void ) +{ + FT_Error error = FT_Done_FreeType( library ); + + if( error ) { + Con::warnf( ConsoleLogEntry::General, "Freetype shutdown failed" ); + } + +} + +GFont* createFont( const char* name, S32 size ) +{ + + if( !name ) { + return 0; + } + +#ifdef DEBUG + Con::printf( "Creating font %s %d", name, size ); +#endif + + // open the file and read the contents + char filename[PATH_MAX]; + + dSprintf( filename, PATH_MAX, "base/fonts/%s.ttf", name ); + + File file; + + file.open( filename, File::Read ); + + if( file.getStatus( ) != File::Ok ) { + file.close( ); + return 0; + } + + U32 fileSize = file.getSize( ); + U8* buffer = new U8[fileSize]; + U32 bytesRead = 0; + + if( !buffer ) { + // Don't bother trying to explain OOM :) + file.close( ); + return 0; + } + + file.read( fileSize, (char*) buffer, &bytesRead ); + + if( file.getStatus( ) != File::Ok || + bytesRead != fileSize ) { + Con::warnf( ConsoleLogEntry::General, + "Couldn't read all of %s", filename ); + file.close( ); + delete[] buffer; + return 0; + } + + file.close( ); + + // tweak point size--non-linear! + F32 points = size; + + if( points < 12.0f ) { + points *= 0.80f; + } else { + points *= 0.77f; + } + + // to fractional points + points *= 64.0f; + + // actual ft API usage + FT_Error error; + + FT_Face face; + + error = FT_New_Memory_Face( library, buffer, fileSize, 0, &face ); + + if( error ) { + Con::warnf( ConsoleLogEntry::General, "could not open font %s", filename ); + delete[] buffer; + return 0; + } + + error = FT_Set_Char_Size( face, 0, points, 0, 0 ); + + if( error ) { + Con::warnf( ConsoleLogEntry::General, "couldn't set char size" ); + delete[] buffer; + return 0; + } + + F32 units = face->units_per_EM; + F32 ascent = static_cast( face->ascender ) / units; + ascent = ceil( ascent * face->size->metrics.y_ppem ); + // tweak ascent (?) + ascent *= 1.4f; + F32 descent = static_cast( face->descender ) / units; + descent *= floor( face->size->metrics.y_ppem ); + U32 height = 0; + + if( descent > 0 ) { + height = ascent + descent; + } else { + height = ascent - descent; + } + + GFont* gfont = new GFont; + + for( S32 i = 32; i < 256; i++ ) { + int glyphIndex = FT_Get_Char_Index( face, i ); + + error = FT_Load_Glyph( face, glyphIndex, FT_LOAD_DEFAULT ); + + if( error ) { + Con::warnf( ConsoleLogEntry::General, "couldn't load glyph %d", i ); + continue; + } + + FT_GlyphSlot glyph = face->glyph; + + error = FT_Render_Glyph( glyph, ft_render_mode_normal ); + + if( error ) { + Con::warnf( ConsoleLogEntry::General, "couldn't render glyph %d", i ); + continue; + } + + FT_Bitmap* bitmap = &glyph->bitmap; + FT_Glyph_Metrics* metrics = &glyph->metrics; + + int glyphWidth = ( ( metrics->width + 63 ) & -64 ) / 64; + int glyphHeight = ( ( metrics->height + 63 ) & -64 ) / 64; + int advance = ( ( metrics->horiAdvance + 63 ) & -64 ) / 64; + int hbx = ( ( metrics->horiBearingX + 63 ) & -64 ) / 64; + int hby = ( ( metrics->horiBearingY + 63 ) & -64 ) / 64; + + gfont->insertBitmap( i, + bitmap->buffer, + bitmap->pitch, + glyphWidth, + glyphHeight, + hbx, + hby, + advance ); + } + + gfont->pack( height, ascent ); + + error = FT_Done_Face( face ); + + if( error ) { + Con::warnf( ConsoleLogEntry::General, "could not close face" ); + } + + delete[] buffer; + + return gfont; +} + +#endif diff --git a/platformLinux/linuxGL.cc b/platformLinux/linuxGL.cc new file mode 100644 index 0000000..8802958 --- /dev/null +++ b/platformLinux/linuxGL.cc @@ -0,0 +1,2409 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include + +#include + +#include "PlatformWin32/platformGL.h" +#include "platformLinux/platformLinux.h" +#include "console/console.h" +#include "console/consoleTypes.h" + +// +// Externally referencable GL renderer state. +// +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; + +typedef const GLubyte* (*gluErrorString_t) (GLenum errCode); +gluErrorString_t gluErrorString; +typedef const GLubyte* (*gluGetString_t) (GLenum name); +gluGetString_t gluGetString; +typedef void (*gluOrtho2D_t) (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top); +gluOrtho2D_t gluOrtho2D; +typedef void (*gluPerspective_t) (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar); +gluPerspective_t gluPerspective; +typedef void (*gluPickMatrix_t) (GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4]); +gluPickMatrix_t gluPickMatrix; +typedef void (*gluLookAt_t) (GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz); +gluLookAt_t gluLookAt; +typedef int (*gluProject_t) (GLdouble objx, GLdouble objy, GLdouble objz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *winx, GLdouble *winy, GLdouble *winz); +gluProject_t gluProject; +typedef int (*gluUnProject_t) (GLdouble winx, GLdouble winy, GLdouble winz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *objx, GLdouble *objy, GLdouble *objz); +gluUnProject_t gluUnProject; +typedef int (*gluScaleImage_t) (GLenum format, GLint widthin, GLint heightin, GLenum typein, const void *datain, GLint widthout, GLint heightout, GLenum typeout, void *dataout); +gluScaleImage_t gluScaleImage; +typedef int (*gluBuild1DMipmaps_t) (GLenum target, GLint components, GLint width, GLenum format, GLenum type, const void *data); +gluBuild1DMipmaps_t gluBuild1DMipmaps; +typedef int (*gluBuild2DMipmaps_t) (GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data); +gluBuild2DMipmaps_t gluBuild2DMipmaps; + +//------------------------------------------------------------------------------ +// GL Functions +typedef void (*glAccum_t )(GLenum op, GLfloat value); +glAccum_t glAccum; +typedef void (*glAlphaFunc_t )(GLenum func, GLclampf ref); +glAlphaFunc_t glAlphaFunc; +typedef GLboolean (*glAreTexturesResident_t )(GLsizei n, const GLuint *textures, GLboolean *residences); +glAreTexturesResident_t glAreTexturesResident; +typedef void (*glArrayElement_t )(GLint i); +glArrayElement_t glArrayElement; +typedef void (*glBegin_t )(GLenum mode); +glBegin_t glBegin; +typedef void (*glBindTexture_t )(GLenum target, GLuint texture); +glBindTexture_t glBindTexture; +typedef void (*glBitmap_t )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +glBitmap_t glBitmap; +typedef void (*glBlendFunc_t )(GLenum sfactor, GLenum dfactor); +glBlendFunc_t glBlendFunc; +typedef void (*glCallList_t )(GLuint list); +glCallList_t glCallList; +typedef void (*glCallLists_t )(GLsizei n, GLenum type, const GLvoid *lists); +glCallLists_t glCallLists; +typedef void (*glClear_t )(GLbitfield mask); +glClear_t glClear; +typedef void (*glClearAccum_t )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +glClearAccum_t glClearAccum; +typedef void (*glClearColor_t )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +glClearColor_t glClearColor; +typedef void (*glClearDepth_t )(GLclampd depth); +glClearDepth_t glClearDepth; +typedef void (*glClearIndex_t )(GLfloat c); +glClearIndex_t glClearIndex; +typedef void (*glClearStencil_t )(GLint s); +glClearStencil_t glClearStencil; +typedef void (*glClipPlane_t )(GLenum plane, const GLdouble *equation); +glClipPlane_t glClipPlane; +typedef void (*glColor3b_t )(GLbyte red, GLbyte green, GLbyte blue); +glColor3b_t glColor3b; +typedef void (*glColor3bv_t )(const GLbyte *v); +glColor3bv_t glColor3bv; +typedef void (*glColor3d_t )(GLdouble red, GLdouble green, GLdouble blue); +glColor3d_t glColor3d; +typedef void (*glColor3dv_t )(const GLdouble *v); +glColor3dv_t glColor3dv; +typedef void (*glColor3f_t )(GLfloat red, GLfloat green, GLfloat blue); +glColor3f_t glColor3f; +typedef void (*glColor3fv_t )(const GLfloat *v); +glColor3fv_t glColor3fv; +typedef void (*glColor3i_t )(GLint red, GLint green, GLint blue); +glColor3i_t glColor3i; +typedef void (*glColor3iv_t )(const GLint *v); +glColor3iv_t glColor3iv; +typedef void (*glColor3s_t )(GLshort red, GLshort green, GLshort blue); +glColor3s_t glColor3s; +typedef void (*glColor3sv_t )(const GLshort *v); +glColor3sv_t glColor3sv; +typedef void (*glColor3ub_t )(GLubyte red, GLubyte green, GLubyte blue); +glColor3ub_t glColor3ub; +typedef void (*glColor3ubv_t )(const GLubyte *v); +glColor3ubv_t glColor3ubv; +typedef void (*glColor3ui_t )(GLuint red, GLuint green, GLuint blue); +glColor3ui_t glColor3ui; +typedef void (*glColor3uiv_t )(const GLuint *v); +glColor3uiv_t glColor3uiv; +typedef void (*glColor3us_t )(GLushort red, GLushort green, GLushort blue); +glColor3us_t glColor3us; +typedef void (*glColor3usv_t )(const GLushort *v); +glColor3usv_t glColor3usv; +typedef void (*glColor4b_t )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +glColor4b_t glColor4b; +typedef void (*glColor4bv_t )(const GLbyte *v); +glColor4bv_t glColor4bv; +typedef void (*glColor4d_t )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +glColor4d_t glColor4d; +typedef void (*glColor4dv_t )(const GLdouble *v); +glColor4dv_t glColor4dv; +typedef void (*glColor4f_t )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +glColor4f_t glColor4f; +typedef void (*glColor4fv_t )(const GLfloat *v); +glColor4fv_t glColor4fv; +typedef void (*glColor4i_t )(GLint red, GLint green, GLint blue, GLint alpha); +glColor4i_t glColor4i; +typedef void (*glColor4iv_t )(const GLint *v); +glColor4iv_t glColor4iv; +typedef void (*glColor4s_t )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +glColor4s_t glColor4s; +typedef void (*glColor4sv_t )(const GLshort *v); +glColor4sv_t glColor4sv; +typedef void (*glColor4ub_t )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +glColor4ub_t glColor4ub; +typedef void (*glColor4ubv_t )(const GLubyte *v); +glColor4ubv_t glColor4ubv; +typedef void (*glColor4ui_t )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +glColor4ui_t glColor4ui; +typedef void (*glColor4uiv_t )(const GLuint *v); +glColor4uiv_t glColor4uiv; +typedef void (*glColor4us_t )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +glColor4us_t glColor4us; +typedef void (*glColor4usv_t )(const GLushort *v); +glColor4usv_t glColor4usv; +typedef void (*glColorMask_t )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +glColorMask_t glColorMask; +typedef void (*glColorMaterial_t )(GLenum face, GLenum mode); +glColorMaterial_t glColorMaterial; +typedef void (*glColorPointer_t )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +glColorPointer_t glColorPointer; +typedef void (*glCopyPixels_t )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +glCopyPixels_t glCopyPixels; +typedef void (*glCopyTexImage1D_t )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +glCopyTexImage1D_t glCopyTexImage1D; +typedef void (*glCopyTexImage2D_t )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +glCopyTexImage2D_t glCopyTexImage2D; +typedef void (*glCopyTexSubImage1D_t )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +glCopyTexSubImage1D_t glCopyTexSubImage1D; +typedef void (*glCopyTexSubImage2D_t )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +glCopyTexSubImage2D_t glCopyTexSubImage2D; +typedef void (*glCullFace_t )(GLenum mode); +glCullFace_t glCullFace; +typedef void (*glDeleteLists_t )(GLuint list, GLsizei range); +glDeleteLists_t glDeleteLists; +typedef void (*glDeleteTextures_t )(GLsizei n, const GLuint *textures); +glDeleteTextures_t glDeleteTextures; +typedef void (*glDepthFunc_t )(GLenum func); +glDepthFunc_t glDepthFunc; +typedef void (*glDepthMask_t )(GLboolean flag); +glDepthMask_t glDepthMask; +typedef void (*glDepthRange_t )(GLclampd zNear, GLclampd zFar); +glDepthRange_t glDepthRange; +typedef void (*glDisable_t )(GLenum cap); +glDisable_t glDisable; +typedef void (*glDisableClientState_t )(GLenum array); +glDisableClientState_t glDisableClientState; +typedef void (*glDrawArrays_t )(GLenum mode, GLint first, GLsizei count); +glDrawArrays_t glDrawArrays; +typedef void (*glDrawBuffer_t )(GLenum mode); +glDrawBuffer_t glDrawBuffer; +typedef void (*glDrawElements_t )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +glDrawElements_t glDrawElements; +typedef void (*glDrawPixels_t )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +glDrawPixels_t glDrawPixels; +typedef void (*glEdgeFlag_t )(GLboolean flag); +glEdgeFlag_t glEdgeFlag; +typedef void (*glEdgeFlagPointer_t )(GLsizei stride, const GLvoid *pointer); +glEdgeFlagPointer_t glEdgeFlagPointer; +typedef void (*glEdgeFlagv_t )(const GLboolean *flag); +glEdgeFlagv_t glEdgeFlagv; +typedef void (*glEnable_t )(GLenum cap); +glEnable_t glEnable; +typedef void (*glEnableClientState_t )(GLenum array); +glEnableClientState_t glEnableClientState; +typedef void (*glEnd_t )(void); +glEnd_t glEnd; +typedef void (*glEndList_t )(void); +glEndList_t glEndList; +typedef void (*glEvalCoord1d_t )(GLdouble u); +glEvalCoord1d_t glEvalCoord1d; +typedef void (*glEvalCoord1dv_t )(const GLdouble *u); +glEvalCoord1dv_t glEvalCoord1dv; +typedef void (*glEvalCoord1f_t )(GLfloat u); +glEvalCoord1f_t glEvalCoord1f; +typedef void (*glEvalCoord1fv_t )(const GLfloat *u); +glEvalCoord1fv_t glEvalCoord1fv; +typedef void (*glEvalCoord2d_t )(GLdouble u, GLdouble v); +glEvalCoord2d_t glEvalCoord2d; +typedef void (*glEvalCoord2dv_t )(const GLdouble *u); +glEvalCoord2dv_t glEvalCoord2dv; +typedef void (*glEvalCoord2f_t )(GLfloat u, GLfloat v); +glEvalCoord2f_t glEvalCoord2f; +typedef void (*glEvalCoord2fv_t )(const GLfloat *u); +glEvalCoord2fv_t glEvalCoord2fv; +typedef void (*glEvalMesh1_t )(GLenum mode, GLint i1, GLint i2); +glEvalMesh1_t glEvalMesh1; +typedef void (*glEvalMesh2_t )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +glEvalMesh2_t glEvalMesh2; +typedef void (*glEvalPoint1_t )(GLint i); +glEvalPoint1_t glEvalPoint1; +typedef void (*glEvalPoint2_t )(GLint i, GLint j); +glEvalPoint2_t glEvalPoint2; +typedef void (*glFeedbackBuffer_t )(GLsizei size, GLenum type, GLfloat *buffer); +glFeedbackBuffer_t glFeedbackBuffer; +typedef void (*glFinish_t )(void); +glFinish_t glFinish; +typedef void (*glFlush_t )(void); +glFlush_t glFlush; +typedef void (*glFogf_t )(GLenum pname, GLfloat param); +glFogf_t glFogf; +typedef void (*glFogfv_t )(GLenum pname, const GLfloat *params); +glFogfv_t glFogfv; +typedef void (*glFogi_t )(GLenum pname, GLint param); +glFogi_t glFogi; +typedef void (*glFogiv_t )(GLenum pname, const GLint *params); +glFogiv_t glFogiv; +typedef void (*glFrontFace_t )(GLenum mode); +glFrontFace_t glFrontFace; +typedef void (*glFrustum_t )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +glFrustum_t glFrustum; +typedef GLuint (*glGenLists_t )(GLsizei range); +glGenLists_t glGenLists; +typedef void (*glGenTextures_t )(GLsizei n, GLuint *textures); +glGenTextures_t glGenTextures; +typedef void (*glGetBooleanv_t )(GLenum pname, GLboolean *params); +glGetBooleanv_t glGetBooleanv; +typedef void (*glGetClipPlane_t )(GLenum plane, GLdouble *equation); +glGetClipPlane_t glGetClipPlane; +typedef void (*glGetDoublev_t )(GLenum pname, GLdouble *params); +glGetDoublev_t glGetDoublev; +typedef GLenum (*glGetError_t )(void); +glGetError_t glGetError; +typedef void (*glGetFloatv_t )(GLenum pname, GLfloat *params); +glGetFloatv_t glGetFloatv; +typedef void (*glGetIntegerv_t )(GLenum pname, GLint *params); +glGetIntegerv_t glGetIntegerv; +typedef void (*glGetLightfv_t )(GLenum light, GLenum pname, GLfloat *params); +glGetLightfv_t glGetLightfv; +typedef void (*glGetLightiv_t )(GLenum light, GLenum pname, GLint *params); +glGetLightiv_t glGetLightiv; +typedef void (*glGetMapdv_t )(GLenum target, GLenum query, GLdouble *v); +glGetMapdv_t glGetMapdv; +typedef void (*glGetMapfv_t )(GLenum target, GLenum query, GLfloat *v); +glGetMapfv_t glGetMapfv; +typedef void (*glGetMapiv_t )(GLenum target, GLenum query, GLint *v); +glGetMapiv_t glGetMapiv; +typedef void (*glGetMaterialfv_t )(GLenum face, GLenum pname, GLfloat *params); +glGetMaterialfv_t glGetMaterialfv; +typedef void (*glGetMaterialiv_t )(GLenum face, GLenum pname, GLint *params); +glGetMaterialiv_t glGetMaterialiv; +typedef void (*glGetPixelMapfv_t )(GLenum map, GLfloat *values); +glGetPixelMapfv_t glGetPixelMapfv; +typedef void (*glGetPixelMapuiv_t )(GLenum map, GLuint *values); +glGetPixelMapuiv_t glGetPixelMapuiv; +typedef void (*glGetPixelMapusv_t )(GLenum map, GLushort *values); +glGetPixelMapusv_t glGetPixelMapusv; +typedef void (*glGetPointerv_t )(GLenum pname, GLvoid* *params); +glGetPointerv_t glGetPointerv; +typedef void (*glGetPolygonStipple_t )(GLubyte *mask); +glGetPolygonStipple_t glGetPolygonStipple; +typedef const GLubyte * (*glGetString_t )(GLenum name); +glGetString_t glGetString; +typedef void (*glGetTexEnvfv_t )(GLenum target, GLenum pname, GLfloat *params); +glGetTexEnvfv_t glGetTexEnvfv; +typedef void (*glGetTexEnviv_t )(GLenum target, GLenum pname, GLint *params); +glGetTexEnviv_t glGetTexEnviv; +typedef void (*glGetTexGendv_t )(GLenum coord, GLenum pname, GLdouble *params); +glGetTexGendv_t glGetTexGendv; +typedef void (*glGetTexGenfv_t )(GLenum coord, GLenum pname, GLfloat *params); +glGetTexGenfv_t glGetTexGenfv; +typedef void (*glGetTexGeniv_t )(GLenum coord, GLenum pname, GLint *params); +glGetTexGeniv_t glGetTexGeniv; +typedef void (*glGetTexImage_t )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +glGetTexImage_t glGetTexImage; +typedef void (*glGetTexLevelParameterfv_t )(GLenum target, GLint level, GLenum pname, GLfloat *params); +glGetTexLevelParameterfv_t glGetTexLevelParameterfv; +typedef void (*glGetTexLevelParameteriv_t )(GLenum target, GLint level, GLenum pname, GLint *params); +glGetTexLevelParameteriv_t glGetTexLevelParameteriv; +typedef void (*glGetTexParameterfv_t )(GLenum target, GLenum pname, GLfloat *params); +glGetTexParameterfv_t glGetTexParameterfv; +typedef void (*glGetTexParameteriv_t )(GLenum target, GLenum pname, GLint *params); +glGetTexParameteriv_t glGetTexParameteriv; +typedef void (*glHint_t )(GLenum target, GLenum mode); +glHint_t glHint; +typedef void (*glIndexMask_t )(GLuint mask); +glIndexMask_t glIndexMask; +typedef void (*glIndexPointer_t )(GLenum type, GLsizei stride, const GLvoid *pointer); +glIndexPointer_t glIndexPointer; +typedef void (*glIndexd_t )(GLdouble c); +glIndexd_t glIndexd; +typedef void (*glIndexdv_t )(const GLdouble *c); +glIndexdv_t glIndexdv; +typedef void (*glIndexf_t )(GLfloat c); +glIndexf_t glIndexf; +typedef void (*glIndexfv_t )(const GLfloat *c); +glIndexfv_t glIndexfv; +typedef void (*glIndexi_t )(GLint c); +glIndexi_t glIndexi; +typedef void (*glIndexiv_t )(const GLint *c); +glIndexiv_t glIndexiv; +typedef void (*glIndexs_t )(GLshort c); +glIndexs_t glIndexs; +typedef void (*glIndexsv_t )(const GLshort *c); +glIndexsv_t glIndexsv; +typedef void (*glIndexub_t )(GLubyte c); +glIndexub_t glIndexub; +typedef void (*glIndexubv_t )(const GLubyte *c); +glIndexubv_t glIndexubv; +typedef void (*glInitNames_t )(void); +glInitNames_t glInitNames; +typedef void (*glInterleavedArrays_t )(GLenum format, GLsizei stride, const GLvoid *pointer); +glInterleavedArrays_t glInterleavedArrays; +typedef GLboolean (*glIsEnabled_t )(GLenum cap); +glIsEnabled_t glIsEnabled; +typedef GLboolean (*glIsList_t )(GLuint list); +glIsList_t glIsList; +typedef GLboolean (*glIsTexture_t )(GLuint texture); +glIsTexture_t glIsTexture; +typedef void (*glLightModelf_t )(GLenum pname, GLfloat param); +glLightModelf_t glLightModelf; +typedef void (*glLightModelfv_t )(GLenum pname, const GLfloat *params); +glLightModelfv_t glLightModelfv; +typedef void (*glLightModeli_t )(GLenum pname, GLint param); +glLightModeli_t glLightModeli; +typedef void (*glLightModeliv_t )(GLenum pname, const GLint *params); +glLightModeliv_t glLightModeliv; +typedef void (*glLightf_t )(GLenum light, GLenum pname, GLfloat param); +glLightf_t glLightf; +typedef void (*glLightfv_t )(GLenum light, GLenum pname, const GLfloat *params); +glLightfv_t glLightfv; +typedef void (*glLighti_t )(GLenum light, GLenum pname, GLint param); +glLighti_t glLighti; +typedef void (*glLightiv_t )(GLenum light, GLenum pname, const GLint *params); +glLightiv_t glLightiv; +typedef void (*glLineStipple_t )(GLint factor, GLushort pattern); +glLineStipple_t glLineStipple; +typedef void (*glLineWidth_t )(GLfloat width); +glLineWidth_t glLineWidth; +typedef void (*glListBase_t )(GLuint base); +glListBase_t glListBase; +typedef void (*glLoadIdentity_t )(void); +glLoadIdentity_t glLoadIdentity; +typedef void (*glLoadMatrixd_t )(const GLdouble *m); +glLoadMatrixd_t glLoadMatrixd; +typedef void (*glLoadMatrixf_t )(const GLfloat *m); +glLoadMatrixf_t glLoadMatrixf; +typedef void (*glLoadName_t )(GLuint name); +glLoadName_t glLoadName; +typedef void (*glLogicOp_t )(GLenum opcode); +glLogicOp_t glLogicOp; +typedef void (*glMap1d_t )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +glMap1d_t glMap1d; +typedef void (*glMap1f_t )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +glMap1f_t glMap1f; +typedef void (*glMap2d_t )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +glMap2d_t glMap2d; +typedef void (*glMap2f_t )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +glMap2f_t glMap2f; +typedef void (*glMapGrid1d_t )(GLint un, GLdouble u1, GLdouble u2); +glMapGrid1d_t glMapGrid1d; +typedef void (*glMapGrid1f_t )(GLint un, GLfloat u1, GLfloat u2); +glMapGrid1f_t glMapGrid1f; +typedef void (*glMapGrid2d_t )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +glMapGrid2d_t glMapGrid2d; +typedef void (*glMapGrid2f_t )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +glMapGrid2f_t glMapGrid2f; +typedef void (*glMaterialf_t )(GLenum face, GLenum pname, GLfloat param); +glMaterialf_t glMaterialf; +typedef void (*glMaterialfv_t )(GLenum face, GLenum pname, const GLfloat *params); +glMaterialfv_t glMaterialfv; +typedef void (*glMateriali_t )(GLenum face, GLenum pname, GLint param); +glMateriali_t glMateriali; +typedef void (*glMaterialiv_t )(GLenum face, GLenum pname, const GLint *params); +glMaterialiv_t glMaterialiv; +typedef void (*glMatrixMode_t )(GLenum mode); +glMatrixMode_t glMatrixMode; +typedef void (*glMultMatrixd_t )(const GLdouble *m); +glMultMatrixd_t glMultMatrixd; +typedef void (*glMultMatrixf_t )(const GLfloat *m); +glMultMatrixf_t glMultMatrixf; +typedef void (*glNewList_t )(GLuint list, GLenum mode); +glNewList_t glNewList; +typedef void (*glNormal3b_t )(GLbyte nx, GLbyte ny, GLbyte nz); +glNormal3b_t glNormal3b; +typedef void (*glNormal3bv_t )(const GLbyte *v); +glNormal3bv_t glNormal3bv; +typedef void (*glNormal3d_t )(GLdouble nx, GLdouble ny, GLdouble nz); +glNormal3d_t glNormal3d; +typedef void (*glNormal3dv_t )(const GLdouble *v); +glNormal3dv_t glNormal3dv; +typedef void (*glNormal3f_t )(GLfloat nx, GLfloat ny, GLfloat nz); +glNormal3f_t glNormal3f; +typedef void (*glNormal3fv_t )(const GLfloat *v); +glNormal3fv_t glNormal3fv; +typedef void (*glNormal3i_t )(GLint nx, GLint ny, GLint nz); +glNormal3i_t glNormal3i; +typedef void (*glNormal3iv_t )(const GLint *v); +glNormal3iv_t glNormal3iv; +typedef void (*glNormal3s_t )(GLshort nx, GLshort ny, GLshort nz); +glNormal3s_t glNormal3s; +typedef void (*glNormal3sv_t )(const GLshort *v); +glNormal3sv_t glNormal3sv; +typedef void (*glNormalPointer_t )(GLenum type, GLsizei stride, const GLvoid *pointer); +glNormalPointer_t glNormalPointer; +typedef void (*glOrtho_t )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +glOrtho_t glOrtho; +typedef void (*glPassThrough_t )(GLfloat token); +glPassThrough_t glPassThrough; +typedef void (*glPixelMapfv_t )(GLenum map, GLsizei mapsize, const GLfloat *values); +glPixelMapfv_t glPixelMapfv; +typedef void (*glPixelMapuiv_t )(GLenum map, GLsizei mapsize, const GLuint *values); +glPixelMapuiv_t glPixelMapuiv; +typedef void (*glPixelMapusv_t )(GLenum map, GLsizei mapsize, const GLushort *values); +glPixelMapusv_t glPixelMapusv; +typedef void (*glPixelStoref_t )(GLenum pname, GLfloat param); +glPixelStoref_t glPixelStoref; +typedef void (*glPixelStorei_t )(GLenum pname, GLint param); +glPixelStorei_t glPixelStorei; +typedef void (*glPixelTransferf_t )(GLenum pname, GLfloat param); +glPixelTransferf_t glPixelTransferf; +typedef void (*glPixelTransferi_t )(GLenum pname, GLint param); +glPixelTransferi_t glPixelTransferi; +typedef void (*glPixelZoom_t )(GLfloat xfactor, GLfloat yfactor); +glPixelZoom_t glPixelZoom; +typedef void (*glPointSize_t )(GLfloat size); +glPointSize_t glPointSize; +typedef void (*glPolygonMode_t )(GLenum face, GLenum mode); +glPolygonMode_t glPolygonMode; +typedef void (*glPolygonOffset_t )(GLfloat factor, GLfloat units); +glPolygonOffset_t glPolygonOffset; +typedef void (*glPolygonStipple_t )(const GLubyte *mask); +glPolygonStipple_t glPolygonStipple; +typedef void (*glPopAttrib_t )(void); +glPopAttrib_t glPopAttrib; +typedef void (*glPopClientAttrib_t )(void); +glPopClientAttrib_t glPopClientAttrib; +typedef void (*glPopMatrix_t )(void); +glPopMatrix_t glPopMatrix; +typedef void (*glPopName_t )(void); +glPopName_t glPopName; +typedef void (*glPrioritizeTextures_t )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +glPrioritizeTextures_t glPrioritizeTextures; +typedef void (*glPushAttrib_t )(GLbitfield mask); +glPushAttrib_t glPushAttrib; +typedef void (*glPushClientAttrib_t )(GLbitfield mask); +glPushClientAttrib_t glPushClientAttrib; +typedef void (*glPushMatrix_t )(void); +glPushMatrix_t glPushMatrix; +typedef void (*glPushName_t )(GLuint name); +glPushName_t glPushName; +typedef void (*glRasterPos2d_t )(GLdouble x, GLdouble y); +glRasterPos2d_t glRasterPos2d; +typedef void (*glRasterPos2dv_t )(const GLdouble *v); +glRasterPos2dv_t glRasterPos2dv; +typedef void (*glRasterPos2f_t )(GLfloat x, GLfloat y); +glRasterPos2f_t glRasterPos2f; +typedef void (*glRasterPos2fv_t )(const GLfloat *v); +glRasterPos2fv_t glRasterPos2fv; +typedef void (*glRasterPos2i_t )(GLint x, GLint y); +glRasterPos2i_t glRasterPos2i; +typedef void (*glRasterPos2iv_t )(const GLint *v); +glRasterPos2iv_t glRasterPos2iv; +typedef void (*glRasterPos2s_t )(GLshort x, GLshort y); +glRasterPos2s_t glRasterPos2s; +typedef void (*glRasterPos2sv_t )(const GLshort *v); +glRasterPos2sv_t glRasterPos2sv; +typedef void (*glRasterPos3d_t )(GLdouble x, GLdouble y, GLdouble z); +glRasterPos3d_t glRasterPos3d; +typedef void (*glRasterPos3dv_t )(const GLdouble *v); +glRasterPos3dv_t glRasterPos3dv; +typedef void (*glRasterPos3f_t )(GLfloat x, GLfloat y, GLfloat z); +glRasterPos3f_t glRasterPos3f; +typedef void (*glRasterPos3fv_t )(const GLfloat *v); +glRasterPos3fv_t glRasterPos3fv; +typedef void (*glRasterPos3i_t )(GLint x, GLint y, GLint z); +glRasterPos3i_t glRasterPos3i; +typedef void (*glRasterPos3iv_t )(const GLint *v); +glRasterPos3iv_t glRasterPos3iv; +typedef void (*glRasterPos3s_t )(GLshort x, GLshort y, GLshort z); +glRasterPos3s_t glRasterPos3s; +typedef void (*glRasterPos3sv_t )(const GLshort *v); +glRasterPos3sv_t glRasterPos3sv; +typedef void (*glRasterPos4d_t )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +glRasterPos4d_t glRasterPos4d; +typedef void (*glRasterPos4dv_t )(const GLdouble *v); +glRasterPos4dv_t glRasterPos4dv; +typedef void (*glRasterPos4f_t )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +glRasterPos4f_t glRasterPos4f; +typedef void (*glRasterPos4fv_t )(const GLfloat *v); +glRasterPos4fv_t glRasterPos4fv; +typedef void (*glRasterPos4i_t )(GLint x, GLint y, GLint z, GLint w); +glRasterPos4i_t glRasterPos4i; +typedef void (*glRasterPos4iv_t )(const GLint *v); +glRasterPos4iv_t glRasterPos4iv; +typedef void (*glRasterPos4s_t )(GLshort x, GLshort y, GLshort z, GLshort w); +glRasterPos4s_t glRasterPos4s; +typedef void (*glRasterPos4sv_t )(const GLshort *v); +glRasterPos4sv_t glRasterPos4sv; +typedef void (*glReadBuffer_t )(GLenum mode); +glReadBuffer_t glReadBuffer; +typedef void (*glReadPixels_t )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +glReadPixels_t glReadPixels; +typedef void (*glRectd_t )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +glRectd_t glRectd; +typedef void (*glRectdv_t )(const GLdouble *v1, const GLdouble *v2); +glRectdv_t glRectdv; +typedef void (*glRectf_t )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +glRectf_t glRectf; +typedef void (*glRectfv_t )(const GLfloat *v1, const GLfloat *v2); +glRectfv_t glRectfv; +typedef void (*glRecti_t )(GLint x1, GLint y1, GLint x2, GLint y2); +glRecti_t glRecti; +typedef void (*glRectiv_t )(const GLint *v1, const GLint *v2); +glRectiv_t glRectiv; +typedef void (*glRects_t )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +glRects_t glRects; +typedef void (*glRectsv_t )(const GLshort *v1, const GLshort *v2); +glRectsv_t glRectsv; +typedef GLint (*glRenderMode_t )(GLenum mode); +glRenderMode_t glRenderMode; +typedef void (*glRotated_t )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +glRotated_t glRotated; +typedef void (*glRotatef_t )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +glRotatef_t glRotatef; +typedef void (*glScaled_t )(GLdouble x, GLdouble y, GLdouble z); +glScaled_t glScaled; +typedef void (*glScalef_t )(GLfloat x, GLfloat y, GLfloat z); +glScalef_t glScalef; +typedef void (*glScissor_t )(GLint x, GLint y, GLsizei width, GLsizei height); +glScissor_t glScissor; +typedef void (*glSelectBuffer_t )(GLsizei size, GLuint *buffer); +glSelectBuffer_t glSelectBuffer; +typedef void (*glShadeModel_t )(GLenum mode); +glShadeModel_t glShadeModel; +typedef void (*glStencilFunc_t )(GLenum func, GLint ref, GLuint mask); +glStencilFunc_t glStencilFunc; +typedef void (*glStencilMask_t )(GLuint mask); +glStencilMask_t glStencilMask; +typedef void (*glStencilOp_t )(GLenum fail, GLenum zfail, GLenum zpass); +glStencilOp_t glStencilOp; +typedef void (*glTexCoord1d_t )(GLdouble s); +glTexCoord1d_t glTexCoord1d; +typedef void (*glTexCoord1dv_t )(const GLdouble *v); +glTexCoord1dv_t glTexCoord1dv; +typedef void (*glTexCoord1f_t )(GLfloat s); +glTexCoord1f_t glTexCoord1f; +typedef void (*glTexCoord1fv_t )(const GLfloat *v); +glTexCoord1fv_t glTexCoord1fv; +typedef void (*glTexCoord1i_t )(GLint s); +glTexCoord1i_t glTexCoord1i; +typedef void (*glTexCoord1iv_t )(const GLint *v); +glTexCoord1iv_t glTexCoord1iv; +typedef void (*glTexCoord1s_t )(GLshort s); +glTexCoord1s_t glTexCoord1s; +typedef void (*glTexCoord1sv_t )(const GLshort *v); +glTexCoord1sv_t glTexCoord1sv; +typedef void (*glTexCoord2d_t )(GLdouble s, GLdouble t); +glTexCoord2d_t glTexCoord2d; +typedef void (*glTexCoord2dv_t )(const GLdouble *v); +glTexCoord2dv_t glTexCoord2dv; +typedef void (*glTexCoord2f_t )(GLfloat s, GLfloat t); +glTexCoord2f_t glTexCoord2f; +typedef void (*glTexCoord2fv_t )(const GLfloat *v); +glTexCoord2fv_t glTexCoord2fv; +typedef void (*glTexCoord2i_t )(GLint s, GLint t); +glTexCoord2i_t glTexCoord2i; +typedef void (*glTexCoord2iv_t )(const GLint *v); +glTexCoord2iv_t glTexCoord2iv; +typedef void (*glTexCoord2s_t )(GLshort s, GLshort t); +glTexCoord2s_t glTexCoord2s; +typedef void (*glTexCoord2sv_t )(const GLshort *v); +glTexCoord2sv_t glTexCoord2sv; +typedef void (*glTexCoord3d_t )(GLdouble s, GLdouble t, GLdouble r); +glTexCoord3d_t glTexCoord3d; +typedef void (*glTexCoord3dv_t )(const GLdouble *v); +glTexCoord3dv_t glTexCoord3dv; +typedef void (*glTexCoord3f_t )(GLfloat s, GLfloat t, GLfloat r); +glTexCoord3f_t glTexCoord3f; +typedef void (*glTexCoord3fv_t )(const GLfloat *v); +glTexCoord3fv_t glTexCoord3fv; +typedef void (*glTexCoord3i_t )(GLint s, GLint t, GLint r); +glTexCoord3i_t glTexCoord3i; +typedef void (*glTexCoord3iv_t )(const GLint *v); +glTexCoord3iv_t glTexCoord3iv; +typedef void (*glTexCoord3s_t )(GLshort s, GLshort t, GLshort r); +glTexCoord3s_t glTexCoord3s; +typedef void (*glTexCoord3sv_t )(const GLshort *v); +glTexCoord3sv_t glTexCoord3sv; +typedef void (*glTexCoord4d_t )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +glTexCoord4d_t glTexCoord4d; +typedef void (*glTexCoord4dv_t )(const GLdouble *v); +glTexCoord4dv_t glTexCoord4dv; +typedef void (*glTexCoord4f_t )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +glTexCoord4f_t glTexCoord4f; +typedef void (*glTexCoord4fv_t )(const GLfloat *v); +glTexCoord4fv_t glTexCoord4fv; +typedef void (*glTexCoord4i_t )(GLint s, GLint t, GLint r, GLint q); +glTexCoord4i_t glTexCoord4i; +typedef void (*glTexCoord4iv_t )(const GLint *v); +glTexCoord4iv_t glTexCoord4iv; +typedef void (*glTexCoord4s_t )(GLshort s, GLshort t, GLshort r, GLshort q); +glTexCoord4s_t glTexCoord4s; +typedef void (*glTexCoord4sv_t )(const GLshort *v); +glTexCoord4sv_t glTexCoord4sv; +typedef void (*glTexCoordPointer_t )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +glTexCoordPointer_t glTexCoordPointer; +typedef void (*glTexEnvf_t )(GLenum target, GLenum pname, GLfloat param); +glTexEnvf_t glTexEnvf; +typedef void (*glTexEnvfv_t )(GLenum target, GLenum pname, const GLfloat *params); +glTexEnvfv_t glTexEnvfv; +typedef void (*glTexEnvi_t )(GLenum target, GLenum pname, GLint param); +glTexEnvi_t glTexEnvi; +typedef void (*glTexEnviv_t )(GLenum target, GLenum pname, const GLint *params); +glTexEnviv_t glTexEnviv; +typedef void (*glTexGend_t )(GLenum coord, GLenum pname, GLdouble param); +glTexGend_t glTexGend; +typedef void (*glTexGendv_t )(GLenum coord, GLenum pname, const GLdouble *params); +glTexGendv_t glTexGendv; +typedef void (*glTexGenf_t )(GLenum coord, GLenum pname, GLfloat param); +glTexGenf_t glTexGenf; +typedef void (*glTexGenfv_t )(GLenum coord, GLenum pname, const GLfloat *params); +glTexGenfv_t glTexGenfv; +typedef void (*glTexGeni_t )(GLenum coord, GLenum pname, GLint param); +glTexGeni_t glTexGeni; +typedef void (*glTexGeniv_t )(GLenum coord, GLenum pname, const GLint *params); +glTexGeniv_t glTexGeniv; +typedef void (*glTexImage1D_t )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +glTexImage1D_t glTexImage1D; +typedef void (*glTexImage2D_t )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +glTexImage2D_t glTexImage2D; +typedef void (*glTexParameterf_t )(GLenum target, GLenum pname, GLfloat param); +glTexParameterf_t glTexParameterf; +typedef void (*glTexParameterfv_t )(GLenum target, GLenum pname, const GLfloat *params); +glTexParameterfv_t glTexParameterfv; +typedef void (*glTexParameteri_t )(GLenum target, GLenum pname, GLint param); +glTexParameteri_t glTexParameteri; +typedef void (*glTexParameteriv_t )(GLenum target, GLenum pname, const GLint *params); +glTexParameteriv_t glTexParameteriv; +typedef void (*glTexSubImage1D_t )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +glTexSubImage1D_t glTexSubImage1D; +typedef void (*glTexSubImage2D_t )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +glTexSubImage2D_t glTexSubImage2D; +typedef void (*glTranslated_t )(GLdouble x, GLdouble y, GLdouble z); +glTranslated_t glTranslated; +typedef void (*glTranslatef_t )(GLfloat x, GLfloat y, GLfloat z); +glTranslatef_t glTranslatef; +typedef void (*glVertex2d_t )(GLdouble x, GLdouble y); +glVertex2d_t glVertex2d; +typedef void (*glVertex2dv_t )(const GLdouble *v); +glVertex2dv_t glVertex2dv; +typedef void (*glVertex2f_t )(GLfloat x, GLfloat y); +glVertex2f_t glVertex2f; +typedef void (*glVertex2fv_t )(const GLfloat *v); +glVertex2fv_t glVertex2fv; +typedef void (*glVertex2i_t )(GLint x, GLint y); +glVertex2i_t glVertex2i; +typedef void (*glVertex2iv_t )(const GLint *v); +glVertex2iv_t glVertex2iv; +typedef void (*glVertex2s_t )(GLshort x, GLshort y); +glVertex2s_t glVertex2s; +typedef void (*glVertex2sv_t )(const GLshort *v); +glVertex2sv_t glVertex2sv; +typedef void (*glVertex3d_t )(GLdouble x, GLdouble y, GLdouble z); +glVertex3d_t glVertex3d; +typedef void (*glVertex3dv_t )(const GLdouble *v); +glVertex3dv_t glVertex3dv; +typedef void (*glVertex3f_t )(GLfloat x, GLfloat y, GLfloat z); +glVertex3f_t glVertex3f; +typedef void (*glVertex3fv_t )(const GLfloat *v); +glVertex3fv_t glVertex3fv; +typedef void (*glVertex3i_t )(GLint x, GLint y, GLint z); +glVertex3i_t glVertex3i; +typedef void (*glVertex3iv_t )(const GLint *v); +glVertex3iv_t glVertex3iv; +typedef void (*glVertex3s_t )(GLshort x, GLshort y, GLshort z); +glVertex3s_t glVertex3s; +typedef void (*glVertex3sv_t )(const GLshort *v); +glVertex3sv_t glVertex3sv; +typedef void (*glVertex4d_t )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +glVertex4d_t glVertex4d; +typedef void (*glVertex4dv_t )(const GLdouble *v); +glVertex4dv_t glVertex4dv; +typedef void (*glVertex4f_t )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +glVertex4f_t glVertex4f; +typedef void (*glVertex4fv_t )(const GLfloat *v); +glVertex4fv_t glVertex4fv; +typedef void (*glVertex4i_t )(GLint x, GLint y, GLint z, GLint w); +glVertex4i_t glVertex4i; +typedef void (*glVertex4iv_t )(const GLint *v); +glVertex4iv_t glVertex4iv; +typedef void (*glVertex4s_t )(GLshort x, GLshort y, GLshort z, GLshort w); +glVertex4s_t glVertex4s; +typedef void (*glVertex4sv_t )(const GLshort *v); +glVertex4sv_t glVertex4sv; +typedef void (*glVertexPointer_t )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +glVertexPointer_t glVertexPointer; +typedef void (*glViewport_t )(GLint x, GLint y, GLsizei width, GLsizei height); +glViewport_t glViewport; + + +/* EXT_paletted_texture */ +typedef void (*glColorTable_t)(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data); +glColorTable_t glColorTableEXT; + +/* EXT_compiled_vertex_array */ +typedef void (*glLockArrays_t)(GLint first, GLsizei count); +typedef void (*glUnlockArrays_t)(); +glLockArrays_t glLockArraysEXT; +glUnlockArrays_t glUnlockArraysEXT; + +/* ARB_multitexture */ +typedef void (*glActiveTextureARB_t)(GLenum target); +typedef void (*glClientActiveTextureARB_t)(GLenum target); +typedef void (*glMultiTexCoord2fARB_t)(GLenum texture, GLfloat, GLfloat); +typedef void (*glMultiTexCoord2fvARB_t)(GLenum texture, const GLfloat*); +glActiveTextureARB_t glActiveTextureARB; +glClientActiveTextureARB_t glClientActiveTextureARB; +glMultiTexCoord2fARB_t glMultiTexCoord2fARB; +glMultiTexCoord2fvARB_t glMultiTexCoord2fvARB; + +/* NV_vertex_array_range */ +typedef void (*glVertexArrayRange_t)(GLsizei length, const GLvoid* pointer); +typedef void (*glFlushVertexArrayRange_t)(); +typedef void* (*glXAllocateMemory_t)(GLsizei, GLfloat, GLfloat, GLfloat); +typedef void (*glXFreeMemory_t)(void*); +glVertexArrayRange_t glVertexArrayRangeNV; +glFlushVertexArrayRange_t glFlushVertexArrayRangeNV; +glXAllocateMemory_t glXAllocateMemoryNV; +glXFreeMemory_t glXFreeMemoryNV; + +/* EXT_fog_coord */ +typedef void (*glFogCoordf_t)(GLfloat); +typedef void (*glFogCoordPointer_t)(GLenum, GLsizei, const GLvoid*); +glFogCoordf_t glFogCoordfEXT; +glFogCoordPointer_t glFogCoordPointerEXT; + +/* ARB_texture_compression */ +typedef void (*glCompressedTexImage3DARB_t)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid*); +typedef void (*glCompressedTexImage2DARB_t)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid*); +typedef void (*glCompressedTexImage1DARB_t)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid*); +typedef void (*glCompressedTexSubImage3DARB_t)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid*); +typedef void (*glCompressedTexSubImage2DARB_t)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid*); +typedef void (*glCompressedTexSubImage1DARB_t)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid*); +typedef void (*glGetCompressedTexImageARB_t)(GLenum target, GLint lod, GLvoid* img); +glCompressedTexImage3DARB_t glCompressedTexImage3DARB; +glCompressedTexImage2DARB_t glCompressedTexImage2DARB; +glCompressedTexImage1DARB_t glCompressedTexImage1DARB; +glCompressedTexSubImage3DARB_t glCompressedTexSubImage3DARB; +glCompressedTexSubImage2DARB_t glCompressedTexSubImage2DARB; +glCompressedTexSubImage1DARB_t glCompressedTexSubImage1DARB; +glGetCompressedTexImageARB_t glGetCompressedTexImageARB; + +/* EXT_vertex_buffer */ +typedef GLboolean (*glAvailableVertexBufferEXT_t)(); +typedef GLint (*glAllocateVertexBufferEXT_t)(GLsizei size, GLint format, GLboolean preserve); +typedef void* (*glLockVertexBufferEXT_t)(GLint handle, GLsizei size); +typedef void (*glUnlockVertexBufferEXT_t)(GLint handle); +typedef void (*glSetVertexBufferEXT_t)(GLint handle); +typedef void (*glOffsetVertexBufferEXT_t)(GLint handle, GLuint offset); +typedef void (*glFillVertexBufferEXT_t)(GLint handle, GLint first, GLsizei count); +typedef void (*glFreeVertexBufferEXT_t)(GLint handle); +glAvailableVertexBufferEXT_t glAvailableVertexBufferEXT; +glAllocateVertexBufferEXT_t glAllocateVertexBufferEXT; +glLockVertexBufferEXT_t glLockVertexBufferEXT; +glUnlockVertexBufferEXT_t glUnlockVertexBufferEXT; +glSetVertexBufferEXT_t glSetVertexBufferEXT; +glOffsetVertexBufferEXT_t glOffsetVertexBufferEXT; +glFillVertexBufferEXT_t glFillVertexBufferEXT; +glFreeVertexBufferEXT_t glFreeVertexBufferEXT; + +// From the Mesa implementation of GLU. Used with permission from Brian Paul. + +/* + * Transform a point (column vector) by a 4x4 matrix. I.e. out = m * in + * Input: m - the 4x4 matrix + * in - the 4x1 vector + * Output: out - the resulting 4x1 vector. + */ +static void +transform_point(GLdouble out[4], const GLdouble m[16], const GLdouble in[4]) +{ +#define M(row,col) m[col*4+row] + out[0] = + M(0, 0) * in[0] + M(0, 1) * in[1] + M(0, 2) * in[2] + M(0, 3) * in[3]; + out[1] = + M(1, 0) * in[0] + M(1, 1) * in[1] + M(1, 2) * in[2] + M(1, 3) * in[3]; + out[2] = + M(2, 0) * in[0] + M(2, 1) * in[1] + M(2, 2) * in[2] + M(2, 3) * in[3]; + out[3] = + M(3, 0) * in[0] + M(3, 1) * in[1] + M(3, 2) * in[2] + M(3, 3) * in[3]; +#undef M +} + +/* + * Perform a 4x4 matrix multiplication (product = a x b). + * Input: a, b - matrices to multiply + * Output: product - product of a and b + */ +static void +matmul(GLdouble * product, const GLdouble * a, const GLdouble * b) +{ + /* This matmul was contributed by Thomas Malik */ + GLdouble temp[16]; + GLint i; + +#define A(row,col) a[(col<<2)+row] +#define B(row,col) b[(col<<2)+row] +#define T(row,col) temp[(col<<2)+row] + + /* i-te Zeile */ + for (i = 0; i < 4; i++) { + T(i, 0) = + A(i, 0) * B(0, 0) + A(i, 1) * B(1, 0) + A(i, 2) * B(2, 0) + A(i, + 3) * + B(3, 0); + T(i, 1) = + A(i, 0) * B(0, 1) + A(i, 1) * B(1, 1) + A(i, 2) * B(2, 1) + A(i, + 3) * + B(3, 1); + T(i, 2) = + A(i, 0) * B(0, 2) + A(i, 1) * B(1, 2) + A(i, 2) * B(2, 2) + A(i, + 3) * + B(3, 2); + T(i, 3) = + A(i, 0) * B(0, 3) + A(i, 1) * B(1, 3) + A(i, 2) * B(2, 3) + A(i, + 3) * + B(3, 3); + } + +#undef A +#undef B +#undef T + memcpy(product, temp, 16 * sizeof(GLdouble)); +} + +/* + * Compute inverse of 4x4 transformation matrix. + * Code contributed by Jacques Leroy jle@star.be + * Return GL_TRUE for success, GL_FALSE for failure (singular matrix) + */ +static GLboolean +invert_matrix(const GLdouble * m, GLdouble * out) +{ +/* NB. OpenGL Matrices are COLUMN major. */ +#define SWAP_ROWS(a, b) { GLdouble *_tmp = a; (a)=(b); (b)=_tmp; } +#define MAT(m,r,c) (m)[(c)*4+(r)] + + GLdouble wtmp[4][8]; + GLdouble m0, m1, m2, m3, s; + GLdouble *r0, *r1, *r2, *r3; + + r0 = wtmp[0], r1 = wtmp[1], r2 = wtmp[2], r3 = wtmp[3]; + + r0[0] = MAT(m, 0, 0), r0[1] = MAT(m, 0, 1), + r0[2] = MAT(m, 0, 2), r0[3] = MAT(m, 0, 3), + r0[4] = 1.0, r0[5] = r0[6] = r0[7] = 0.0, + r1[0] = MAT(m, 1, 0), r1[1] = MAT(m, 1, 1), + r1[2] = MAT(m, 1, 2), r1[3] = MAT(m, 1, 3), + r1[5] = 1.0, r1[4] = r1[6] = r1[7] = 0.0, + r2[0] = MAT(m, 2, 0), r2[1] = MAT(m, 2, 1), + r2[2] = MAT(m, 2, 2), r2[3] = MAT(m, 2, 3), + r2[6] = 1.0, r2[4] = r2[5] = r2[7] = 0.0, + r3[0] = MAT(m, 3, 0), r3[1] = MAT(m, 3, 1), + r3[2] = MAT(m, 3, 2), r3[3] = MAT(m, 3, 3), + r3[7] = 1.0, r3[4] = r3[5] = r3[6] = 0.0; + + /* choose pivot - or die */ + if (fabs(r3[0]) > fabs(r2[0])) + SWAP_ROWS(r3, r2); + if (fabs(r2[0]) > fabs(r1[0])) + SWAP_ROWS(r2, r1); + if (fabs(r1[0]) > fabs(r0[0])) + SWAP_ROWS(r1, r0); + if (0.0 == r0[0]) + return GL_FALSE; + + /* eliminate first variable */ + m1 = r1[0] / r0[0]; + m2 = r2[0] / r0[0]; + m3 = r3[0] / r0[0]; + s = r0[1]; + r1[1] -= m1 * s; + r2[1] -= m2 * s; + r3[1] -= m3 * s; + s = r0[2]; + r1[2] -= m1 * s; + r2[2] -= m2 * s; + r3[2] -= m3 * s; + s = r0[3]; + r1[3] -= m1 * s; + r2[3] -= m2 * s; + r3[3] -= m3 * s; + s = r0[4]; + if (s != 0.0) { + r1[4] -= m1 * s; + r2[4] -= m2 * s; + r3[4] -= m3 * s; + } + s = r0[5]; + if (s != 0.0) { + r1[5] -= m1 * s; + r2[5] -= m2 * s; + r3[5] -= m3 * s; + } + s = r0[6]; + if (s != 0.0) { + r1[6] -= m1 * s; + r2[6] -= m2 * s; + r3[6] -= m3 * s; + } + s = r0[7]; + if (s != 0.0) { + r1[7] -= m1 * s; + r2[7] -= m2 * s; + r3[7] -= m3 * s; + } + + /* choose pivot - or die */ + if (fabs(r3[1]) > fabs(r2[1])) + SWAP_ROWS(r3, r2); + if (fabs(r2[1]) > fabs(r1[1])) + SWAP_ROWS(r2, r1); + if (0.0 == r1[1]) + return GL_FALSE; + + /* eliminate second variable */ + m2 = r2[1] / r1[1]; + m3 = r3[1] / r1[1]; + r2[2] -= m2 * r1[2]; + r3[2] -= m3 * r1[2]; + r2[3] -= m2 * r1[3]; + r3[3] -= m3 * r1[3]; + s = r1[4]; + if (0.0 != s) { + r2[4] -= m2 * s; + r3[4] -= m3 * s; + } + s = r1[5]; + if (0.0 != s) { + r2[5] -= m2 * s; + r3[5] -= m3 * s; + } + s = r1[6]; + if (0.0 != s) { + r2[6] -= m2 * s; + r3[6] -= m3 * s; + } + s = r1[7]; + if (0.0 != s) { + r2[7] -= m2 * s; + r3[7] -= m3 * s; + } + + /* choose pivot - or die */ + if (fabs(r3[2]) > fabs(r2[2])) + SWAP_ROWS(r3, r2); + if (0.0 == r2[2]) + return GL_FALSE; + + /* eliminate third variable */ + m3 = r3[2] / r2[2]; + r3[3] -= m3 * r2[3], r3[4] -= m3 * r2[4], + r3[5] -= m3 * r2[5], r3[6] -= m3 * r2[6], r3[7] -= m3 * r2[7]; + + /* last check */ + if (0.0 == r3[3]) + return GL_FALSE; + + s = 1.0 / r3[3]; /* now back substitute row 3 */ + r3[4] *= s; + r3[5] *= s; + r3[6] *= s; + r3[7] *= s; + + m2 = r2[3]; /* now back substitute row 2 */ + s = 1.0 / r2[2]; + r2[4] = s * (r2[4] - r3[4] * m2), r2[5] = s * (r2[5] - r3[5] * m2), + r2[6] = s * (r2[6] - r3[6] * m2), r2[7] = s * (r2[7] - r3[7] * m2); + m1 = r1[3]; + r1[4] -= r3[4] * m1, r1[5] -= r3[5] * m1, + r1[6] -= r3[6] * m1, r1[7] -= r3[7] * m1; + m0 = r0[3]; + r0[4] -= r3[4] * m0, r0[5] -= r3[5] * m0, + r0[6] -= r3[6] * m0, r0[7] -= r3[7] * m0; + + m1 = r1[2]; /* now back substitute row 1 */ + s = 1.0 / r1[1]; + r1[4] = s * (r1[4] - r2[4] * m1), r1[5] = s * (r1[5] - r2[5] * m1), + r1[6] = s * (r1[6] - r2[6] * m1), r1[7] = s * (r1[7] - r2[7] * m1); + m0 = r0[2]; + r0[4] -= r2[4] * m0, r0[5] -= r2[5] * m0, + r0[6] -= r2[6] * m0, r0[7] -= r2[7] * m0; + + m0 = r0[1]; /* now back substitute row 0 */ + s = 1.0 / r0[0]; + r0[4] = s * (r0[4] - r1[4] * m0), r0[5] = s * (r0[5] - r1[5] * m0), + r0[6] = s * (r0[6] - r1[6] * m0), r0[7] = s * (r0[7] - r1[7] * m0); + + MAT(out, 0, 0) = r0[4]; + MAT(out, 0, 1) = r0[5], MAT(out, 0, 2) = r0[6]; + MAT(out, 0, 3) = r0[7], MAT(out, 1, 0) = r1[4]; + MAT(out, 1, 1) = r1[5], MAT(out, 1, 2) = r1[6]; + MAT(out, 1, 3) = r1[7], MAT(out, 2, 0) = r2[4]; + MAT(out, 2, 1) = r2[5], MAT(out, 2, 2) = r2[6]; + MAT(out, 2, 3) = r2[7], MAT(out, 3, 0) = r3[4]; + MAT(out, 3, 1) = r3[5], MAT(out, 3, 2) = r3[6]; + MAT(out, 3, 3) = r3[7]; + + return GL_TRUE; + +#undef MAT +#undef SWAP_ROWS +} + +/* projection du point (objx,objy,obz) sur l'ecran (winx,winy,winz) */ +GLint +mesa_gluProject(GLdouble objx, GLdouble objy, GLdouble objz, + const GLdouble model[16], const GLdouble proj[16], + const GLint viewport[4], + GLdouble * winx, GLdouble * winy, GLdouble * winz) +{ + /* matrice de transformation */ + GLdouble in[4], out[4]; + + /* initilise la matrice et le vecteur a transformer */ + in[0] = objx; + in[1] = objy; + in[2] = objz; + in[3] = 1.0; + transform_point(out, model, in); + transform_point(in, proj, out); + + /* d'ou le resultat normalise entre -1 et 1 */ + if (in[3] == 0.0) + return GL_FALSE; + + in[0] /= in[3]; + in[1] /= in[3]; + in[2] /= in[3]; + + /* en coordonnees ecran */ + *winx = viewport[0] + (1 + in[0]) * viewport[2] / 2; + *winy = viewport[1] + (1 + in[1]) * viewport[3] / 2; + /* entre 0 et 1 suivant z */ + *winz = (1 + in[2]) / 2; + return GL_TRUE; +} + +/* transformation du point ecran (winx,winy,winz) en point objet */ +GLint +mesa_gluUnProject(GLdouble winx, GLdouble winy, GLdouble winz, + const GLdouble model[16], const GLdouble proj[16], + const GLint viewport[4], + GLdouble * objx, GLdouble * objy, GLdouble * objz) +{ + /* matrice de transformation */ + GLdouble m[16], A[16]; + GLdouble in[4], out[4]; + + /* transformation coordonnees normalisees entre -1 et 1 */ + in[0] = (winx - viewport[0]) * 2 / viewport[2] - 1.0; + in[1] = (winy - viewport[1]) * 2 / viewport[3] - 1.0; + in[2] = 2 * winz - 1.0; + in[3] = 1.0; + + /* calcul transformation inverse */ + matmul(A, proj, model); + invert_matrix(A, m); + + /* d'ou les coordonnees objets */ + transform_point(out, m, in); + if (out[3] == 0.0) + return GL_FALSE; + *objx = out[0] / out[3]; + *objy = out[1] / out[3]; + *objz = out[2] / out[3]; + return GL_TRUE; +} + +void QGL_Shutdown( void ) +{ + // GLU Functions + gluErrorString = NULL; + gluGetString = NULL; + gluOrtho2D = NULL; + gluPerspective = NULL; + gluPickMatrix = NULL; + gluLookAt = NULL; + gluProject = NULL; + gluUnProject = NULL; + gluScaleImage = NULL; + gluBuild1DMipmaps = NULL; + gluBuild2DMipmaps = NULL; + + // GL Functions + glAccum = NULL; + glAlphaFunc = NULL; + glAreTexturesResident = NULL; + glArrayElement = NULL; + glBegin = NULL; + glBindTexture = NULL; + glBitmap = NULL; + glBlendFunc = NULL; + glCallList = NULL; + glCallLists = NULL; + glClear = NULL; + glClearAccum = NULL; + glClearColor = NULL; + glClearDepth = NULL; + glClearIndex = NULL; + glClearStencil = NULL; + glClipPlane = NULL; + glColor3b = NULL; + glColor3bv = NULL; + glColor3d = NULL; + glColor3dv = NULL; + glColor3f = NULL; + glColor3fv = NULL; + glColor3i = NULL; + glColor3iv = NULL; + glColor3s = NULL; + glColor3sv = NULL; + glColor3ub = NULL; + glColor3ubv = NULL; + glColor3ui = NULL; + glColor3uiv = NULL; + glColor3us = NULL; + glColor3usv = NULL; + glColor4b = NULL; + glColor4bv = NULL; + glColor4d = NULL; + glColor4dv = NULL; + glColor4f = NULL; + glColor4fv = NULL; + glColor4i = NULL; + glColor4iv = NULL; + glColor4s = NULL; + glColor4sv = NULL; + glColor4ub = NULL; + glColor4ubv = NULL; + glColor4ui = NULL; + glColor4uiv = NULL; + glColor4us = NULL; + glColor4usv = NULL; + glColorMask = NULL; + glColorMaterial = NULL; + glColorPointer = NULL; + glCopyPixels = NULL; + glCopyTexImage1D = NULL; + glCopyTexImage2D = NULL; + glCopyTexSubImage1D = NULL; + glCopyTexSubImage2D = NULL; + glCullFace = NULL; + glDeleteLists = NULL; + glDeleteTextures = NULL; + glDepthFunc = NULL; + glDepthMask = NULL; + glDepthRange = NULL; + glDisable = NULL; + glDisableClientState = NULL; + glDrawArrays = NULL; + glDrawBuffer = NULL; + glDrawElements = NULL; + glDrawPixels = NULL; + glEdgeFlag = NULL; + glEdgeFlagPointer = NULL; + glEdgeFlagv = NULL; + glEnable = NULL; + glEnableClientState = NULL; + glEnd = NULL; + glEndList = NULL; + glEvalCoord1d = NULL; + glEvalCoord1dv = NULL; + glEvalCoord1f = NULL; + glEvalCoord1fv = NULL; + glEvalCoord2d = NULL; + glEvalCoord2dv = NULL; + glEvalCoord2f = NULL; + glEvalCoord2fv = NULL; + glEvalMesh1 = NULL; + glEvalMesh2 = NULL; + glEvalPoint1 = NULL; + glEvalPoint2 = NULL; + glFeedbackBuffer = NULL; + glFinish = NULL; + glFlush = NULL; + glFogf = NULL; + glFogfv = NULL; + glFogi = NULL; + glFogiv = NULL; + glFrontFace = NULL; + glFrustum = NULL; + glGenLists = NULL; + glGenTextures = NULL; + glGetBooleanv = NULL; + glGetClipPlane = NULL; + glGetDoublev = NULL; + glGetError = NULL; + glGetFloatv = NULL; + glGetIntegerv = NULL; + glGetLightfv = NULL; + glGetLightiv = NULL; + glGetMapdv = NULL; + glGetMapfv = NULL; + glGetMapiv = NULL; + glGetMaterialfv = NULL; + glGetMaterialiv = NULL; + glGetPixelMapfv = NULL; + glGetPixelMapuiv = NULL; + glGetPixelMapusv = NULL; + glGetPointerv = NULL; + glGetPolygonStipple = NULL; + glGetString = NULL; + glGetTexEnvfv = NULL; + glGetTexEnviv = NULL; + glGetTexGendv = NULL; + glGetTexGenfv = NULL; + glGetTexGeniv = NULL; + glGetTexImage = NULL; + glGetTexLevelParameterfv = NULL; + glGetTexLevelParameteriv = NULL; + glGetTexParameterfv = NULL; + glGetTexParameteriv = NULL; + glHint = NULL; + glIndexMask = NULL; + glIndexPointer = NULL; + glIndexd = NULL; + glIndexdv = NULL; + glIndexf = NULL; + glIndexfv = NULL; + glIndexi = NULL; + glIndexiv = NULL; + glIndexs = NULL; + glIndexsv = NULL; + glIndexub = NULL; + glIndexubv = NULL; + glInitNames = NULL; + glInterleavedArrays = NULL; + glIsEnabled = NULL; + glIsList = NULL; + glIsTexture = NULL; + glLightModelf = NULL; + glLightModelfv = NULL; + glLightModeli = NULL; + glLightModeliv = NULL; + glLightf = NULL; + glLightfv = NULL; + glLighti = NULL; + glLightiv = NULL; + glLineStipple = NULL; + glLineWidth = NULL; + glListBase = NULL; + glLoadIdentity = NULL; + glLoadMatrixd = NULL; + glLoadMatrixf = NULL; + glLoadName = NULL; + glLogicOp = NULL; + glMap1d = NULL; + glMap1f = NULL; + glMap2d = NULL; + glMap2f = NULL; + glMapGrid1d = NULL; + glMapGrid1f = NULL; + glMapGrid2d = NULL; + glMapGrid2f = NULL; + glMaterialf = NULL; + glMaterialfv = NULL; + glMateriali = NULL; + glMaterialiv = NULL; + glMatrixMode = NULL; + glMultMatrixd = NULL; + glMultMatrixf = NULL; + glNewList = NULL; + glNormal3b = NULL; + glNormal3bv = NULL; + glNormal3d = NULL; + glNormal3dv = NULL; + glNormal3f = NULL; + glNormal3fv = NULL; + glNormal3i = NULL; + glNormal3iv = NULL; + glNormal3s = NULL; + glNormal3sv = NULL; + glNormalPointer = NULL; + glOrtho = NULL; + glPassThrough = NULL; + glPixelMapfv = NULL; + glPixelMapuiv = NULL; + glPixelMapusv = NULL; + glPixelStoref = NULL; + glPixelStorei = NULL; + glPixelTransferf = NULL; + glPixelTransferi = NULL; + glPixelZoom = NULL; + glPointSize = NULL; + glPolygonMode = NULL; + glPolygonOffset = NULL; + glPolygonStipple = NULL; + glPopAttrib = NULL; + glPopClientAttrib = NULL; + glPopMatrix = NULL; + glPopName = NULL; + glPrioritizeTextures = NULL; + glPushAttrib = NULL; + glPushClientAttrib = NULL; + glPushMatrix = NULL; + glPushName = NULL; + glRasterPos2d = NULL; + glRasterPos2dv = NULL; + glRasterPos2f = NULL; + glRasterPos2fv = NULL; + glRasterPos2i = NULL; + glRasterPos2iv = NULL; + glRasterPos2s = NULL; + glRasterPos2sv = NULL; + glRasterPos3d = NULL; + glRasterPos3dv = NULL; + glRasterPos3f = NULL; + glRasterPos3fv = NULL; + glRasterPos3i = NULL; + glRasterPos3iv = NULL; + glRasterPos3s = NULL; + glRasterPos3sv = NULL; + glRasterPos4d = NULL; + glRasterPos4dv = NULL; + glRasterPos4f = NULL; + glRasterPos4fv = NULL; + glRasterPos4i = NULL; + glRasterPos4iv = NULL; + glRasterPos4s = NULL; + glRasterPos4sv = NULL; + glReadBuffer = NULL; + glReadPixels = NULL; + glRectd = NULL; + glRectdv = NULL; + glRectf = NULL; + glRectfv = NULL; + glRecti = NULL; + glRectiv = NULL; + glRects = NULL; + glRectsv = NULL; + glRenderMode = NULL; + glRotated = NULL; + glRotatef = NULL; + glScaled = NULL; + glScalef = NULL; + glScissor = NULL; + glSelectBuffer = NULL; + glShadeModel = NULL; + glStencilFunc = NULL; + glStencilMask = NULL; + glStencilOp = NULL; + glTexCoord1d = NULL; + glTexCoord1dv = NULL; + glTexCoord1f = NULL; + glTexCoord1fv = NULL; + glTexCoord1i = NULL; + glTexCoord1iv = NULL; + glTexCoord1s = NULL; + glTexCoord1sv = NULL; + glTexCoord2d = NULL; + glTexCoord2dv = NULL; + glTexCoord2f = NULL; + glTexCoord2fv = NULL; + glTexCoord2i = NULL; + glTexCoord2iv = NULL; + glTexCoord2s = NULL; + glTexCoord2sv = NULL; + glTexCoord3d = NULL; + glTexCoord3dv = NULL; + glTexCoord3f = NULL; + glTexCoord3fv = NULL; + glTexCoord3i = NULL; + glTexCoord3iv = NULL; + glTexCoord3s = NULL; + glTexCoord3sv = NULL; + glTexCoord4d = NULL; + glTexCoord4dv = NULL; + glTexCoord4f = NULL; + glTexCoord4fv = NULL; + glTexCoord4i = NULL; + glTexCoord4iv = NULL; + glTexCoord4s = NULL; + glTexCoord4sv = NULL; + glTexCoordPointer = NULL; + glTexEnvf = NULL; + glTexEnvfv = NULL; + glTexEnvi = NULL; + glTexEnviv = NULL; + glTexGend = NULL; + glTexGendv = NULL; + glTexGenf = NULL; + glTexGenfv = NULL; + glTexGeni = NULL; + glTexGeniv = NULL; + glTexImage1D = NULL; + glTexImage2D = NULL; + glTexParameterf = NULL; + glTexParameterfv = NULL; + glTexParameteri = NULL; + glTexParameteriv = NULL; + glTexSubImage1D = NULL; + glTexSubImage2D = NULL; + glTranslated = NULL; + glTranslatef = NULL; + glVertex2d = NULL; + glVertex2dv = NULL; + glVertex2f = NULL; + glVertex2fv = NULL; + glVertex2i = NULL; + glVertex2iv = NULL; + glVertex2s = NULL; + glVertex2sv = NULL; + glVertex3d = NULL; + glVertex3dv = NULL; + glVertex3f = NULL; + glVertex3fv = NULL; + glVertex3i = NULL; + glVertex3iv = NULL; + glVertex3s = NULL; + glVertex3sv = NULL; + glVertex4d = NULL; + glVertex4dv = NULL; + glVertex4f = NULL; + glVertex4fv = NULL; + glVertex4i = NULL; + glVertex4iv = NULL; + glVertex4s = NULL; + glVertex4sv = NULL; + glVertexPointer = NULL; + glViewport = NULL; + + // EXT_compiled_vertex_array + glLockArraysEXT = NULL; + glUnlockArraysEXT = NULL; + + // ARB_multitexture + glActiveTextureARB = NULL; + glClientActiveTextureARB = NULL; + glMultiTexCoord2fARB = NULL; + glMultiTexCoord2fvARB = NULL; + + // NV_vertex_array_range + glVertexArrayRangeNV = NULL; + glFlushVertexArrayRangeNV = NULL; + glXAllocateMemoryNV = NULL; + glXFreeMemoryNV = NULL; + + // EXT_fog_coord + glFogCoordfEXT = NULL; + glFogCoordPointerEXT = NULL; + + // ARB_texture_compression + glCompressedTexImage3DARB = NULL; + glCompressedTexImage2DARB = NULL; + glCompressedTexImage1DARB = NULL; + glCompressedTexSubImage3DARB = NULL; + glCompressedTexSubImage2DARB = NULL; + glCompressedTexSubImage1DARB = NULL; + glGetCompressedTexImageARB = NULL; + + // EXT_vertex_buffer + glAvailableVertexBufferEXT = NULL; + glAllocateVertexBufferEXT = NULL; + glLockVertexBufferEXT = NULL; + glUnlockVertexBufferEXT = NULL; + glSetVertexBufferEXT = NULL; + glOffsetVertexBufferEXT = NULL; + glFillVertexBufferEXT = NULL; + glFreeVertexBufferEXT = NULL; +} + +// NOTE: helpers--anything you want to stub you can +// assign to this, anything you want to trigger a +// breakpoint at run-time, assign to debug_stub +static void void_stub( void ) +{ + // empty +} + +static void debug_stub( void ) +{ + __asm__( "int $03" ); +} + +bool QGL_Init( const char* dllname_gl, const char* dllname_glu ) +{ +#ifndef DEDICATED + // NOTE: the video subsystem *MUST* be shutdown at this + // point, or crap will break. + + if( SDL_GL_LoadLibrary( dllname_gl ) == -1 ) { + return false; + } + + gluProject = mesa_gluProject; + gluUnProject = mesa_gluUnProject; + +#define GPA_GL(a) SDL_GL_GetProcAddress( a ) + glAccum = (glAccum_t) GPA_GL( "glAccum" ); + glAlphaFunc = (glAlphaFunc_t) GPA_GL( "glAlphaFunc" ); + glAreTexturesResident = (glAreTexturesResident_t) GPA_GL( "glAreTexturesResident" ); + glArrayElement = (glArrayElement_t) GPA_GL( "glArrayElement" ); + glBegin = (glBegin_t) GPA_GL( "glBegin" ); + glBindTexture = (glBindTexture_t) GPA_GL( "glBindTexture" ); + glBitmap = (glBitmap_t) GPA_GL( "glBitmap" ); + glBlendFunc = (glBlendFunc_t) GPA_GL( "glBlendFunc" ); + glCallList = (glCallList_t) GPA_GL( "glCallList" ); + glCallLists = (glCallLists_t) GPA_GL( "glCallLists" ); + glClear = (glClear_t) GPA_GL( "glClear" ); + glClearAccum = (glClearAccum_t) GPA_GL( "glClearAccum" ); + glClearColor = (glClearColor_t) GPA_GL( "glClearColor" ); + glClearDepth = (glClearDepth_t) GPA_GL( "glClearDepth" ); + glClearIndex = (glClearIndex_t) GPA_GL( "glClearIndex" ); + glClearStencil = (glClearStencil_t) GPA_GL( "glClearStencil" ); + glClipPlane = (glClipPlane_t) GPA_GL( "glClipPlane" ); + glColor3b = (glColor3b_t) GPA_GL( "glColor3b" ); + glColor3bv = (glColor3bv_t) GPA_GL( "glColor3bv" ); + glColor3d = (glColor3d_t) GPA_GL( "glColor3d" ); + glColor3dv = (glColor3dv_t) GPA_GL( "glColor3dv" ); + glColor3f = (glColor3f_t) GPA_GL( "glColor3f" ); + glColor3fv = (glColor3fv_t) GPA_GL( "glColor3fv" ); + glColor3i = (glColor3i_t) GPA_GL( "glColor3i" ); + glColor3iv = (glColor3iv_t) GPA_GL( "glColor3iv" ); + glColor3s = (glColor3s_t) GPA_GL( "glColor3s" ); + glColor3sv = (glColor3sv_t) GPA_GL( "glColor3sv" ); + glColor3ub = (glColor3ub_t) GPA_GL( "glColor3ub" ); + glColor3ubv = (glColor3ubv_t) GPA_GL( "glColor3ubv" ); + glColor3ui = (glColor3ui_t) GPA_GL( "glColor3ui" ); + glColor3uiv = (glColor3uiv_t) GPA_GL( "glColor3uiv" ); + glColor3us = (glColor3us_t) GPA_GL( "glColor3us" ); + glColor3usv = (glColor3usv_t) GPA_GL( "glColor3usv" ); + glColor4b = (glColor4b_t) GPA_GL( "glColor4b" ); + glColor4bv = (glColor4bv_t) GPA_GL( "glColor4bv" ); + glColor4d = (glColor4d_t) GPA_GL( "glColor4d" ); + glColor4dv = (glColor4dv_t) GPA_GL( "glColor4dv" ); + glColor4f = (glColor4f_t) GPA_GL( "glColor4f" ); + glColor4fv = (glColor4fv_t) GPA_GL( "glColor4fv" ); + glColor4i = (glColor4i_t) GPA_GL( "glColor4i" ); + glColor4iv = (glColor4iv_t) GPA_GL( "glColor4iv" ); + glColor4s = (glColor4s_t) GPA_GL( "glColor4s" ); + glColor4sv = (glColor4sv_t) GPA_GL( "glColor4sv" ); + glColor4ub = (glColor4ub_t) GPA_GL( "glColor4ub" ); + glColor4ubv = (glColor4ubv_t) GPA_GL( "glColor4ubv" ); + glColor4ui = (glColor4ui_t) GPA_GL( "glColor4ui" ); + glColor4uiv = (glColor4uiv_t) GPA_GL( "glColor4uiv" ); + glColor4us = (glColor4us_t) GPA_GL( "glColor4us" ); + glColor4usv = (glColor4usv_t) GPA_GL( "glColor4usv" ); + glColorMask = (glColorMask_t) GPA_GL( "glColorMask" ); + glColorMaterial = (glColorMaterial_t) GPA_GL( "glColorMaterial" ); + glColorPointer = (glColorPointer_t) GPA_GL( "glColorPointer" ); + glCopyPixels = (glCopyPixels_t) GPA_GL( "glCopyPixels" ); + glCopyTexImage1D = (glCopyTexImage1D_t) GPA_GL( "glCopyTexImage1D" ); + glCopyTexImage2D = (glCopyTexImage2D_t) GPA_GL( "glCopyTexImage2D" ); + glCopyTexSubImage1D = (glCopyTexSubImage1D_t) GPA_GL( "glCopyTexSubImage1D" ); + glCopyTexSubImage2D = (glCopyTexSubImage2D_t) GPA_GL( "glCopyTexSubImage2D" ); + glCullFace = (glCullFace_t) GPA_GL( "glCullFace" ); + glDeleteLists = (glDeleteLists_t) GPA_GL( "glDeleteLists" ); + glDeleteTextures = (glDeleteTextures_t) GPA_GL( "glDeleteTextures" ); + glDepthFunc = (glDepthFunc_t) GPA_GL( "glDepthFunc" ); + glDepthMask = (glDepthMask_t) GPA_GL( "glDepthMask" ); + glDepthRange = (glDepthRange_t) GPA_GL( "glDepthRange" ); + glDisable = (glDisable_t) GPA_GL( "glDisable" ); + glDisableClientState = (glDisableClientState_t) GPA_GL( "glDisableClientState" ); + glDrawArrays = (glDrawArrays_t) GPA_GL( "glDrawArrays" ); + glDrawBuffer = (glDrawBuffer_t) GPA_GL( "glDrawBuffer" ); + glDrawElements = (glDrawElements_t) GPA_GL( "glDrawElements" ); + glDrawPixels = (glDrawPixels_t) GPA_GL( "glDrawPixels" ); + glEdgeFlag = (glEdgeFlag_t) GPA_GL( "glEdgeFlag" ); + glEdgeFlagPointer = (glEdgeFlagPointer_t) GPA_GL( "glEdgeFlagPointer" ); + glEdgeFlagv = (glEdgeFlagv_t) GPA_GL( "glEdgeFlagv" ); + glEnable = (glEnable_t) GPA_GL( "glEnable" ); + glEnableClientState = (glEnableClientState_t) GPA_GL( "glEnableClientState" ); + glEnd = (glEnd_t) GPA_GL( "glEnd" ); + glEndList = (glEndList_t) GPA_GL( "glEndList" ); + glEvalCoord1d = (glEvalCoord1d_t) GPA_GL( "glEvalCoord1d" ); + glEvalCoord1dv = (glEvalCoord1dv_t) GPA_GL( "glEvalCoord1dv" ); + glEvalCoord1f = (glEvalCoord1f_t) GPA_GL( "glEvalCoord1f" ); + glEvalCoord1fv = (glEvalCoord1fv_t) GPA_GL( "glEvalCoord1fv" ); + glEvalCoord2d = (glEvalCoord2d_t) GPA_GL( "glEvalCoord2d" ); + glEvalCoord2dv = (glEvalCoord2dv_t) GPA_GL( "glEvalCoord2dv" ); + glEvalCoord2f = (glEvalCoord2f_t) GPA_GL( "glEvalCoord2f" ); + glEvalCoord2fv = (glEvalCoord2fv_t) GPA_GL( "glEvalCoord2fv" ); + glEvalMesh1 = (glEvalMesh1_t) GPA_GL( "glEvalMesh1" ); + glEvalMesh2 = (glEvalMesh2_t) GPA_GL( "glEvalMesh2" ); + glEvalPoint1 = (glEvalPoint1_t) GPA_GL( "glEvalPoint1" ); + glEvalPoint2 = (glEvalPoint2_t) GPA_GL( "glEvalPoint2" ); + glFeedbackBuffer = (glFeedbackBuffer_t) GPA_GL( "glFeedbackBuffer" ); + + if( getenv( "TRIBES2_USE_FLUSH" ) ) { + glFinish = (glFinish_t) GPA_GL( "glFinish" ); + glFlush = (glFlush_t) GPA_GL( "glFlush" ); + } else { + glFinish = (glFinish_t) void_stub; + glFlush = (glFlush_t) void_stub; + } + + glFogf = (glFogf_t) GPA_GL( "glFogf" ); + glFogfv = (glFogfv_t) GPA_GL( "glFogfv" ); + glFogi = (glFogi_t) GPA_GL( "glFogi" ); + glFogiv = (glFogiv_t) GPA_GL( "glFogiv" ); + glFrontFace = (glFrontFace_t) GPA_GL( "glFrontFace" ); + glFrustum = (glFrustum_t) GPA_GL( "glFrustum" ); + glGenLists = (glGenLists_t) GPA_GL( "glGenLists" ); + glGenTextures = (glGenTextures_t) GPA_GL( "glGenTextures" ); + glGetBooleanv = (glGetBooleanv_t) GPA_GL( "glGetBooleanv" ); + glGetClipPlane = (glGetClipPlane_t) GPA_GL( "glGetClipPlane" ); + glGetDoublev = (glGetDoublev_t) GPA_GL( "glGetDoublev" ); + glGetError = (glGetError_t) GPA_GL( "glGetError" ); + glGetFloatv = (glGetFloatv_t) GPA_GL( "glGetFloatv" ); + glGetIntegerv = (glGetIntegerv_t) GPA_GL( "glGetIntegerv" ); + glGetLightfv = (glGetLightfv_t) GPA_GL( "glGetLightfv" ); + glGetLightiv = (glGetLightiv_t) GPA_GL( "glGetLightiv" ); + glGetMapdv = (glGetMapdv_t) GPA_GL( "glGetMapdv" ); + glGetMapfv = (glGetMapfv_t) GPA_GL( "glGetMapfv" ); + glGetMapiv = (glGetMapiv_t) GPA_GL( "glGetMapiv" ); + glGetMaterialfv = (glGetMaterialfv_t) GPA_GL( "glGetMaterialfv" ); + glGetMaterialiv = (glGetMaterialiv_t) GPA_GL( "glGetMaterialiv" ); + glGetPixelMapfv = (glGetPixelMapfv_t) GPA_GL( "glGetPixelMapfv" ); + glGetPixelMapuiv = (glGetPixelMapuiv_t) GPA_GL( "glGetPixelMapuiv" ); + glGetPixelMapusv = (glGetPixelMapusv_t) GPA_GL( "glGetPixelMapusv" ); + glGetPointerv = (glGetPointerv_t) GPA_GL( "glGetPointerv" ); + glGetPolygonStipple = (glGetPolygonStipple_t) GPA_GL( "glGetPolygonStipple" ); + glGetString = (glGetString_t) GPA_GL( "glGetString" ); + glGetTexEnvfv = (glGetTexEnvfv_t) GPA_GL( "glGetTexEnvfv" ); + glGetTexEnviv = (glGetTexEnviv_t) GPA_GL( "glGetTexEnviv" ); + glGetTexGendv = (glGetTexGendv_t) GPA_GL( "glGetTexGendv" ); + glGetTexGenfv = (glGetTexGenfv_t) GPA_GL( "glGetTexGenfv" ); + glGetTexGeniv = (glGetTexGeniv_t) GPA_GL( "glGetTexGeniv" ); + glGetTexImage = (glGetTexImage_t) GPA_GL( "glGetTexImage" ); + glGetTexLevelParameterfv = (glGetTexLevelParameterfv_t) GPA_GL( "glGetLevelParameterfv" ); + glGetTexLevelParameteriv = (glGetTexLevelParameteriv_t) GPA_GL( "glGetLevelParameteriv" ); + glGetTexParameterfv = (glGetTexParameterfv_t) GPA_GL( "glGetTexParameterfv" ); + glGetTexParameteriv = (glGetTexParameteriv_t) GPA_GL( "glGetTexParameteriv" ); + glHint = (glHint_t) GPA_GL( "glHint" ); + glIndexMask = (glIndexMask_t) GPA_GL( "glIndexMask" ); + glIndexPointer = (glIndexPointer_t) GPA_GL( "glIndexPointer" ); + glIndexd = (glIndexd_t) GPA_GL( "glIndexd" ); + glIndexdv = (glIndexdv_t) GPA_GL( "glIndexdv" ); + glIndexf = (glIndexf_t) GPA_GL( "glIndexf" ); + glIndexfv = (glIndexfv_t) GPA_GL( "glIndexfv" ); + glIndexi = (glIndexi_t) GPA_GL( "glIndexi" ); + glIndexiv = (glIndexiv_t) GPA_GL( "glIndexiv" ); + glIndexs = (glIndexs_t) GPA_GL( "glIndexs" ); + glIndexsv = (glIndexsv_t) GPA_GL( "glIndexsv" ); + glIndexub = (glIndexub_t) GPA_GL( "glIndexub" ); + glIndexubv = (glIndexubv_t) GPA_GL( "glIndexubv" ); + glInitNames = (glInitNames_t) GPA_GL( "glInitNames" ); + glInterleavedArrays = (glInterleavedArrays_t) GPA_GL( "glInterleavedArrays" ); + glIsEnabled = (glIsEnabled_t) GPA_GL( "glIsEnabled" ); + glIsList = (glIsList_t) GPA_GL( "glIsList" ); + glIsTexture = (glIsTexture_t) GPA_GL( "glIsTexture" ); + glLightModelf = (glLightModelf_t) GPA_GL( "glLightModelf" ); + glLightModelfv = (glLightModelfv_t) GPA_GL( "glLightModelfv" ); + glLightModeli = (glLightModeli_t) GPA_GL( "glLightModeli" ); + glLightModeliv = (glLightModeliv_t) GPA_GL( "glLightModeliv" ); + glLightf = (glLightf_t) GPA_GL( "glLightf" ); + glLightfv = (glLightfv_t) GPA_GL( "glLightfv" ); + glLighti = (glLighti_t) GPA_GL( "glLighti" ); + glLightiv = (glLightiv_t) GPA_GL( "glLightiv" ); + glLineStipple = (glLineStipple_t) GPA_GL( "glLineStipple" ); + glLineWidth = (glLineWidth_t) GPA_GL( "glLineWidth" ); + glListBase = (glListBase_t) GPA_GL( "glListBase" ); + glLoadIdentity = (glLoadIdentity_t) GPA_GL( "glLoadIdentity" ); + glLoadMatrixd = (glLoadMatrixd_t) GPA_GL( "glLoadMatrixd" ); + glLoadMatrixf = (glLoadMatrixf_t) GPA_GL( "glLoadMatrixf" ); + glLoadName = (glLoadName_t) GPA_GL( "glLoadName" ); + glLogicOp = (glLogicOp_t) GPA_GL( "glLogicOp" ); + glMap1d = (glMap1d_t) GPA_GL( "glMap1d" ); + glMap1f = (glMap1f_t) GPA_GL( "glMap1f" ); + glMap2d = (glMap2d_t) GPA_GL( "glMap2d" ); + glMap2f = (glMap2f_t) GPA_GL( "glMap2f" ); + glMapGrid1d = (glMapGrid1d_t) GPA_GL( "glMapGrid1d" ); + glMapGrid1f = (glMapGrid1f_t) GPA_GL( "glMapGrid1f" ); + glMapGrid2d = (glMapGrid2d_t) GPA_GL( "glMapGrid2d" ); + glMapGrid2f = (glMapGrid2f_t) GPA_GL( "glMapGrid2f" ); + glMaterialf = (glMaterialf_t) GPA_GL( "glMaterialf" ); + glMaterialfv = (glMaterialfv_t) GPA_GL( "glMaterialfv" ); + glMateriali = (glMateriali_t) GPA_GL( "glMateriali" ); + glMaterialiv = (glMaterialiv_t) GPA_GL( "glMaterialiv" ); + glMatrixMode = (glMatrixMode_t) GPA_GL( "glMatrixMode" ); + glMultMatrixd = (glMultMatrixd_t) GPA_GL( "glMultMatrixd" ); + glMultMatrixf = (glMultMatrixf_t) GPA_GL( "glMultMatrixf" ); + glNewList = (glNewList_t) GPA_GL( "glNewList" ); + glNormal3b = (glNormal3b_t) GPA_GL( "glNormal3b" ); + glNormal3bv = (glNormal3bv_t) GPA_GL( "glNormal3bv" ); + glNormal3d = (glNormal3d_t) GPA_GL( "glNormal3d" ); + glNormal3dv = (glNormal3dv_t) GPA_GL( "glNormal3dv" ); + glNormal3f = (glNormal3f_t) GPA_GL( "glNormal3f" ); + glNormal3fv = (glNormal3fv_t) GPA_GL( "glNormal3fv" ); + glNormal3i = (glNormal3i_t) GPA_GL( "glNormal3i" ); + glNormal3iv = (glNormal3iv_t) GPA_GL( "glNormal3iv" ); + glNormal3s = (glNormal3s_t) GPA_GL( "glNormal3s" ); + glNormal3sv = (glNormal3sv_t) GPA_GL( "glNormal3sv" ); + glNormalPointer = (glNormalPointer_t) GPA_GL( "glNormalPointer" ); + glOrtho = (glOrtho_t) GPA_GL( "glOrtho" ); + glPassThrough = (glPassThrough_t) GPA_GL( "glPassThrough" ); + glPixelMapfv = (glPixelMapfv_t) GPA_GL( "glPixelMapfv" ); + glPixelMapuiv = (glPixelMapuiv_t) GPA_GL( "glPixelMapuiv" ); + glPixelMapusv = (glPixelMapusv_t) GPA_GL( "glPixelMapusv" ); + glPixelStoref = (glPixelStoref_t) GPA_GL( "glPixelStoref" ); + glPixelStorei = (glPixelStorei_t) GPA_GL( "glPixelStorei" ); + glPixelTransferf = (glPixelTransferf_t) GPA_GL( "glPixelTransferf" ); + glPixelTransferi = (glPixelTransferi_t) GPA_GL( "glPixelTransferi" ); + glPixelZoom = (glPixelZoom_t) GPA_GL( "glPixelZoom" ); + glPointSize = (glPointSize_t) GPA_GL( "glPointSize" ); + glPolygonMode = (glPolygonMode_t) GPA_GL( "glPolygonMode" ); + glPolygonOffset = (glPolygonOffset_t) GPA_GL( "glPolygonOffset" ); + glPolygonStipple = (glPolygonStipple_t) GPA_GL( "glPolygonStipple" ); + glPopAttrib = (glPopAttrib_t) GPA_GL( "glPopAttrib" ); + glPopClientAttrib = (glPopClientAttrib_t) GPA_GL( "glPopClientAttrib" ); + glPopMatrix = (glPopMatrix_t) GPA_GL( "glPopMatrix" ); + glPopName = (glPopName_t) GPA_GL( "glPopName" ); + glPrioritizeTextures = (glPrioritizeTextures_t) GPA_GL( "glPrioritizeTextures" ); + glPushAttrib = (glPushAttrib_t) GPA_GL( "glPushAttrib" ); + glPushClientAttrib = (glPushClientAttrib_t) GPA_GL( "glPushClientAttrib" ); + glPushMatrix = (glPushMatrix_t) GPA_GL( "glPushMatrix" ); + glPushName = (glPushName_t) GPA_GL( "glPushName" ); + glRasterPos2d = (glRasterPos2d_t) GPA_GL( "glRasterPos2d" ); + glRasterPos2dv = (glRasterPos2dv_t) GPA_GL( "glRasterPos2dv" ); + glRasterPos2f = (glRasterPos2f_t) GPA_GL( "glRasterPos2f" ); + glRasterPos2fv = (glRasterPos2fv_t) GPA_GL( "glRasterPos2fv" ); + glRasterPos2i = (glRasterPos2i_t) GPA_GL( "glRasterPos2i" ); + glRasterPos2iv = (glRasterPos2iv_t) GPA_GL( "glRasterPos2iv" ); + glRasterPos2s = (glRasterPos2s_t) GPA_GL( "glRasterPos2s" ); + glRasterPos2sv = (glRasterPos2sv_t) GPA_GL( "glRasterPos2sv" ); + glRasterPos3d = (glRasterPos3d_t) GPA_GL( "glRasterPos3d" ); + glRasterPos3dv = (glRasterPos3dv_t) GPA_GL( "glRasterPos3dv" ); + glRasterPos3f = (glRasterPos3f_t) GPA_GL( "glRasterPos3f" ); + glRasterPos3fv = (glRasterPos3fv_t) GPA_GL( "glRasterPos3fv" ); + glRasterPos3i = (glRasterPos3i_t) GPA_GL( "glRasterPos3i" ); + glRasterPos3iv = (glRasterPos3iv_t) GPA_GL( "glRasterPos3iv" ); + glRasterPos3s = (glRasterPos3s_t) GPA_GL( "glRasterPos3s" ); + glRasterPos3sv = (glRasterPos3sv_t) GPA_GL( "glRasterPos3sv" ); + glRasterPos4d = (glRasterPos4d_t) GPA_GL( "glRasterPos4d" ); + glRasterPos4dv = (glRasterPos4dv_t) GPA_GL( "glRasterPos4dv" ); + glRasterPos4f = (glRasterPos4f_t) GPA_GL( "glRasterPos4f" ); + glRasterPos4fv = (glRasterPos4fv_t) GPA_GL( "glRasterPos4fv" ); + glRasterPos4i = (glRasterPos4i_t) GPA_GL( "glRasterPos4i" ); + glRasterPos4iv = (glRasterPos4iv_t) GPA_GL( "glRasterPos4iv" ); + glRasterPos4s = (glRasterPos4s_t) GPA_GL( "glRasterPos4s" ); + glRasterPos4sv = (glRasterPos4sv_t) GPA_GL( "glRasterPos4sv" ); + glReadBuffer = (glReadBuffer_t) GPA_GL( "glReadBuffer" ); + glReadPixels = (glReadPixels_t) GPA_GL( "glReadPixels" ); + glRectd = (glRectd_t) GPA_GL( "glRectd" ); + glRectdv = (glRectdv_t) GPA_GL( "glRectdv" ); + glRectf = (glRectf_t) GPA_GL( "glRectf" ); + glRectfv = (glRectfv_t) GPA_GL( "glRectfv" ); + glRecti = (glRecti_t) GPA_GL( "glRecti" ); + glRectiv = (glRectiv_t) GPA_GL( "glRectiv" ); + glRects = (glRects_t) GPA_GL( "glRects" ); + glRectsv = (glRectsv_t) GPA_GL( "glRectsv" ); + glRenderMode = (glRenderMode_t) GPA_GL( "glRenderMode" ); + glRotated = (glRotated_t) GPA_GL( "glRotated" ); + glRotatef = (glRotatef_t) GPA_GL( "glRotatef" ); + glScaled = (glScaled_t) GPA_GL( "glScaled" ); + glScalef = (glScalef_t) GPA_GL( "glScalef" ); + glScissor = (glScissor_t) GPA_GL( "glScissor" ); + glSelectBuffer = (glSelectBuffer_t) GPA_GL( "glSelectBuffer" ); + glShadeModel = (glShadeModel_t) GPA_GL( "glShadeModel" ); + glStencilFunc = (glStencilFunc_t) GPA_GL( "glStencilFunc" ); + glStencilMask = (glStencilMask_t) GPA_GL( "glStencilMask" ); + glStencilOp = (glStencilOp_t) GPA_GL( "glStencilOp" ); + glTexCoord1d = (glTexCoord1d_t) GPA_GL( "glTexCoord1d" ); + glTexCoord1dv = (glTexCoord1dv_t) GPA_GL( "glTexCoord1dv" ); + glTexCoord1f = (glTexCoord1f_t) GPA_GL( "glTexCoord1f" ); + glTexCoord1fv = (glTexCoord1fv_t) GPA_GL( "glTexCoord1fv" ); + glTexCoord1i = (glTexCoord1i_t) GPA_GL( "glTexCoord1i" ); + glTexCoord1iv = (glTexCoord1iv_t) GPA_GL( "glTexCoord1iv" ); + glTexCoord1s = (glTexCoord1s_t) GPA_GL( "glTexCoord1s" ); + glTexCoord1sv = (glTexCoord1sv_t) GPA_GL( "glTexCoord1sv" ); + glTexCoord2d = (glTexCoord2d_t) GPA_GL( "glTexCoord2d" ); + glTexCoord2dv = (glTexCoord2dv_t) GPA_GL( "glTexCoord2dv" ); + glTexCoord2f = (glTexCoord2f_t) GPA_GL( "glTexCoord2f" ); + glTexCoord2fv = (glTexCoord2fv_t) GPA_GL( "glTexCoord2fv" ); + glTexCoord2i = (glTexCoord2i_t) GPA_GL( "glTexCoord2i" ); + glTexCoord2iv = (glTexCoord2iv_t) GPA_GL( "glTexCoord2iv" ); + glTexCoord2s = (glTexCoord2s_t) GPA_GL( "glTexCoord2s" ); + glTexCoord2sv = (glTexCoord2sv_t) GPA_GL( "glTexCoord2sv" ); + glTexCoord3d = (glTexCoord3d_t) GPA_GL( "glTexCoord3d" ); + glTexCoord3dv = (glTexCoord3dv_t) GPA_GL( "glTexCoord3dv" ); + glTexCoord3f = (glTexCoord3f_t) GPA_GL( "glTexCoord3f" ); + glTexCoord3fv = (glTexCoord3fv_t) GPA_GL( "glTexCoord3fv" ); + glTexCoord3i = (glTexCoord3i_t) GPA_GL( "glTexCoord3i" ); + glTexCoord3iv = (glTexCoord3iv_t) GPA_GL( "glTexCoord3iv" ); + glTexCoord3s = (glTexCoord3s_t) GPA_GL( "glTexCoord3s" ); + glTexCoord3sv = (glTexCoord3sv_t) GPA_GL( "glTexCoord3sv" ); + glTexCoord4d = (glTexCoord4d_t) GPA_GL( "glTexCoord4d" ); + glTexCoord4dv = (glTexCoord4dv_t) GPA_GL( "glTexCoord4dv" ); + glTexCoord4f = (glTexCoord4f_t) GPA_GL( "glTexCoord4f" ); + glTexCoord4fv = (glTexCoord4fv_t) GPA_GL( "glTexCoord4fv" ); + glTexCoord4i = (glTexCoord4i_t) GPA_GL( "glTexCoord4i" ); + glTexCoord4iv = (glTexCoord4iv_t) GPA_GL( "glTexCoord4iv" ); + glTexCoord4s = (glTexCoord4s_t) GPA_GL( "glTexCoord4s" ); + glTexCoord4sv = (glTexCoord4sv_t) GPA_GL( "glTexCoord4sv" ); + glTexCoordPointer = (glTexCoordPointer_t) GPA_GL( "glTexCoordPointer" ); + glTexEnvf = (glTexEnvf_t) GPA_GL( "glTexEnvf" ); + glTexEnvfv = (glTexEnvfv_t) GPA_GL( "glTexEnvfv" ); + glTexEnvi = (glTexEnvi_t) GPA_GL( "glTexEnvi" ); + glTexEnviv = (glTexEnviv_t) GPA_GL( "glTexEnviv" ); + glTexGend = (glTexGend_t) GPA_GL( "glTexGend" ); + glTexGendv = (glTexGendv_t) GPA_GL( "glTexGendv" ); + glTexGenf = (glTexGenf_t) GPA_GL( "glTexGenf" ); + glTexGenfv = (glTexGenfv_t) GPA_GL( "glTexGenfv" ); + glTexGeni = (glTexGeni_t) GPA_GL( "glTexGeni" ); + glTexGeniv = (glTexGeniv_t) GPA_GL( "glTexGeniv" ); + glTexImage1D = (glTexImage1D_t) GPA_GL( "glTexImage1D" ); + glTexImage2D = (glTexImage2D_t) GPA_GL( "glTexImage2D" ); + glTexParameterf = (glTexParameterf_t) GPA_GL( "glTexParameterf" ); + glTexParameterfv = (glTexParameterfv_t) GPA_GL( "glTexParameterfv" ); + glTexParameteri = (glTexParameteri_t) GPA_GL( "glTexParameteri" ); + glTexParameteriv = (glTexParameteriv_t) GPA_GL( "glTexParameteriv" ); + glTexSubImage1D = (glTexSubImage1D_t) GPA_GL( "glTexSubImage1D" ); + glTexSubImage2D = (glTexSubImage2D_t) GPA_GL( "glTexSubImage2D" ); + glTranslated = (glTranslated_t) GPA_GL( "glTranslated" ); + glTranslatef = (glTranslatef_t) GPA_GL( "glTranslatef" ); + glVertex2d = (glVertex2d_t) GPA_GL( "glVertex2d" ); + glVertex2dv = (glVertex2dv_t) GPA_GL( "glVertex2dv" ); + glVertex2f = (glVertex2f_t) GPA_GL( "glVertex2f" ); + glVertex2fv = (glVertex2fv_t) GPA_GL( "glVertex2fv" ); + glVertex2i = (glVertex2i_t) GPA_GL( "glVertex2i" ); + glVertex2iv = (glVertex2iv_t) GPA_GL( "glVertex2iv" ); + + glVertex2s = (glVertex2s_t) GPA_GL( "glVertex2s" ); + glVertex2sv = (glVertex2sv_t) GPA_GL( "glVertex2sv" ); + glVertex3d = (glVertex3d_t) GPA_GL( "glVertex3d" ); + glVertex3dv = (glVertex3dv_t) GPA_GL( "glVertex3dv" ); + glVertex3f = (glVertex3f_t) GPA_GL( "glVertex3f" ); + glVertex3fv = (glVertex3fv_t) GPA_GL( "glVertex3fv" ); + glVertex3i = (glVertex3i_t) GPA_GL( "glVertex3i" ); + glVertex3iv = (glVertex3iv_t) GPA_GL( "glVertex3iv" ); + glVertex3s = (glVertex3s_t) GPA_GL( "glVertex3s" ); + glVertex3sv = (glVertex3sv_t) GPA_GL( "glVertex3sv" ); + glVertex4d = (glVertex4d_t) GPA_GL( "glVertex4d" ); + glVertex4dv = (glVertex4dv_t) GPA_GL( "glVertex4dv" ); + glVertex4f = (glVertex4f_t) GPA_GL( "glVertex4f" ); + glVertex4fv = (glVertex4fv_t) GPA_GL( "glVertex4fv" ); + glVertex4i = (glVertex4i_t) GPA_GL( "glVertex4i" ); + glVertex4iv = (glVertex4iv_t) GPA_GL( "glVertex4iv" ); + glVertex4s = (glVertex4s_t) GPA_GL( "glVertex4s" ); + glVertex4sv = (glVertex4sv_t) GPA_GL( "glVertex4sv" ); + glVertexPointer = (glVertexPointer_t) GPA_GL( "glVertexPointer" ); + glViewport = (glViewport_t) GPA_GL( "glViewport" ); +#endif + + return true; +} + +bool QGL_EXT_Init( void ) +{ +#ifndef DEDICATED + const char* pExtString = reinterpret_cast( glGetString( GL_EXTENSIONS ) ); + gGLState.primMode = 0; + + // EXT_paletted_texture + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_paletted_texture") != NULL) { + glColorTableEXT = (glColorTable_t) GPA_GL( "glColorTableEXT" ); + gGLState.suppPalettedTexture = true; + } else { + gGLState.suppPalettedTexture = false; + } + + // EXT_compiled_vertex_array + if( pExtString && dStrstr( pExtString, (const char*) "GL_EXT_compiled_vertex_array" ) != 0 ) { + glLockArraysEXT = (glLockArrays_t) GPA_GL( "glLockArraysEXT" ); + glUnlockArraysEXT = (glUnlockArrays_t) GPA_GL( "glUnlockArraysEXT" ); + gGLState.suppLockedArrays = true; + } else { + glLockArraysEXT = 0; + glUnlockArraysEXT = 0; + gGLState.suppLockedArrays = false; + } + + // ARB_multitexture + if( pExtString && dStrstr( pExtString, (const char*) "GL_ARB_multitexture" ) != 0 ) { + glActiveTextureARB = (glActiveTextureARB_t) GPA_GL( "glActiveTextureARB" ); + glClientActiveTextureARB = (glClientActiveTextureARB_t) GPA_GL( "glClientActiveTextureARB" ); + glMultiTexCoord2fARB = (glMultiTexCoord2fARB_t) GPA_GL( "glMultiTexCoord2fARB" ); + glMultiTexCoord2fvARB = (glMultiTexCoord2fvARB_t) GPA_GL( "glMultiTexCoord2fvARB" ); + gGLState.suppARBMultitexture = true; + } else { + glActiveTextureARB = 0; + glClientActiveTextureARB = 0; + glMultiTexCoord2fARB = 0; + glMultiTexCoord2fvARB = 0; + gGLState.suppARBMultitexture = false; + } + + // NV_vertex_array_range + if( pExtString && dStrstr( pExtString, (const char*) "GL_NV_vertex_array_range" ) != 0 ) { + glVertexArrayRangeNV = (glVertexArrayRange_t) GPA_GL( "glVertexArrayRangeNV" ); + glFlushVertexArrayRangeNV = (glFlushVertexArrayRange_t) GPA_GL( "glFlushVertexArrayRangeNV" ); + glXAllocateMemoryNV = (glXAllocateMemory_t) GPA_GL( "glXAllocateMemoryNV" ); + glXFreeMemoryNV = (glXFreeMemory_t) GPA_GL( "glXFreeMemoryNV" ); + gGLState.suppVertexArrayRange = true; + } else { + glVertexArrayRangeNV = 0; + glFlushVertexArrayRangeNV = 0; + glXAllocateMemoryNV = 0; + glXFreeMemoryNV = 0; + gGLState.suppVertexArrayRange = false; + } + + // EXT_fog_coord + if( pExtString && dStrstr( pExtString, (const char*) "GL_EXT_fog_coord" ) != 0 ) { + glFogCoordfEXT = (glFogCoordf_t) GPA_GL( "glFogCoordfEXT" ); + glFogCoordPointerEXT = (glFogCoordPointer_t) GPA_GL( "glFogCoordPointerEXT" ); + gGLState.suppFogCoord = true; + } else { + glFogCoordfEXT = 0; + glFogCoordPointerEXT = 0; + gGLState.suppFogCoord = false; + } + + // ARB_texture_compression + if( pExtString && dStrstr( pExtString, (const char*) "GL_ARB_texture_compression" ) != 0 ) { + glCompressedTexImage3DARB = (glCompressedTexImage3DARB_t) GPA_GL( "glCompressedTexImage3DARB" ); + glCompressedTexImage2DARB = (glCompressedTexImage2DARB_t) GPA_GL( "glCompressedTexImage2DARB" ); + glCompressedTexImage1DARB = (glCompressedTexImage1DARB_t) GPA_GL( "glCompressedTexImage1DARB" ); + glCompressedTexSubImage3DARB = (glCompressedTexSubImage3DARB_t) GPA_GL( "glCompressedTexSubImage3DARB" ); + glCompressedTexSubImage2DARB = (glCompressedTexSubImage2DARB_t) GPA_GL( "glCompressedTexSubImage2DARB" ); + glCompressedTexSubImage1DARB = (glCompressedTexSubImage1DARB_t) GPA_GL( "glCompressedTexSubImage1DARB" ); + glGetCompressedTexImageARB = (glGetCompressedTexImageARB_t) GPA_GL( "glGetCompressedTexImageARB" ); + gGLState.suppTextureCompression = true; + } else { + glCompressedTexImage3DARB = 0; + glCompressedTexImage2DARB = 0; + glCompressedTexImage1DARB = 0; + glCompressedTexSubImage3DARB = 0; + glCompressedTexSubImage2DARB = 0; + glCompressedTexSubImage1DARB = 0; + glGetCompressedTexImageARB = 0; + gGLState.suppTextureCompression = false; + } + +#undef GPA_GL + + // 3DFX_texture_compression_FXT1 + if( pExtString && dStrstr( pExtString, (const char*) "GL_3DFX_texture_compression_FXT1" ) != 0 ) { + gGLState.suppFXT1 = true; + } else { + gGLState.suppFXT1 = false; + } + + // EXT_texture_compression_S3TC + if( pExtString && dStrstr( pExtString, (const char*) "GL_EXT_texture_compression_s3tc" ) != 0 ) { + gGLState.suppS3TC = true; + } else { + gGLState.suppS3TC = false; + } + + // Binary states, i.e., no supporting functions + // EXT_packed_pixels + // EXT_texture_env_combine + gGLState.suppPackedPixels = pExtString ? ( dStrstr( pExtString, (const char*) "GL_EXT_packed_pixels" ) != 0 ) : false; + gGLState.suppTextureEnvCombine = pExtString ? ( dStrstr( pExtString, (const char*) "GL_EXT_texture_env_combine" ) != 0 ) : false; + gGLState.suppEdgeClamp = pExtString? ( dStrstr( pExtString, (const char*) "GL_EXT_texture_edge_clamp" ) != 0 ) : false; + gGLState.suppTexEnvAdd = pExtString? ( dStrstr( pExtString, (const char*) "GL_ARB_texture_env_add" ) != 0 ) : false; + gGLState.suppTexEnvAdd |= pExtString? ( dStrstr( pExtString, (const char*) "GL_EXT_texture_env_add" ) != 0 ) : false; + + // Anisotropic filtering + gGLState.suppTexAnisotropic = pExtString? ( dStrstr( pExtString, (const char*) "GL_EXT_texture_filter_anisotropic" ) != 0 ) : 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; + + Con::printf( "OpenGL Init: Enabled Extensions" ); + + if( gGLState.suppARBMultitexture ) { + Con::printf( " ARB_multitexture (Max Texture Units: %d)", gGLState.maxTextureUnits); + } + + 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 ); + } + + Con::warnf( ConsoleLogEntry::General, "OpenGL Init: Disabled Extensions" ); + + if( !gGLState.suppARBMultitexture ) { + Con::warnf( ConsoleLogEntry::General, " ARB_multitexture" ); + } + + if( !gGLState.suppPalettedTexture ) { + Con::warnf( ConsoleLogEntry::General, " EXT_paletted_texture" ); + } + + if( !gGLState.suppLockedArrays ) { + Con::warnf( ConsoleLogEntry::General, " EXT_compiled_vertex_array" ); + } + + if( !gGLState.suppVertexArrayRange ) { + Con::warnf( ConsoleLogEntry::General, " NV_vertex_array_range" ); + } + + if( !gGLState.suppTextureEnvCombine ) { + Con::warnf( ConsoleLogEntry::General, " EXT_texture_env_combine" ); + } + + if( !gGLState.suppPackedPixels ) { + Con::warnf( ConsoleLogEntry::General, " EXT_packed_pixels" ); + } + + if( !gGLState.suppFogCoord ) { + Con::warnf( ConsoleLogEntry::General, " EXT_fog_coord" ); + } + + if( !gGLState.suppTextureCompression ) { + Con::warnf( ConsoleLogEntry::General, " ARB_texture_compression" ); + } + + if( !gGLState.suppS3TC ) { + Con::warnf( ConsoleLogEntry::General, " EXT_texture_compression_s3tc" ); + } + + if( !gGLState.suppFXT1 ) { + Con::warnf( ConsoleLogEntry::General, " 3DFX_texture_compression_FXT1" ); + } + + if( !gGLState.suppTexEnvAdd ) { + Con::warnf( ConsoleLogEntry::General, " (ARB|EXT)_texture_env_add" ); + } + + if( !gGLState.suppTexAnisotropic ) { + Con::warnf( ConsoleLogEntry::General, " EXT_texture_filter_anisotropic" ); + } + + // Set some console vars. + Con::setBoolVariable( "$TextureCompressionSupported", gGLState.suppTextureCompression ); + Con::setBoolVariable( "$AnisotropySupported", gGLState.suppTexAnisotropic ); + Con::setBoolVariable( "$PalettedTextureSupported", gGLState.suppPalettedTexture ); + + if (!gGLState.suppPalettedTexture && Con::getBoolVariable("$pref::OpenGL::forcePalettedTexture",false)) + { + Con::setBoolVariable("$pref::OpenGL::forcePalettedTexture", false); + Con::setBoolVariable("$pref::OpenGL::force16BitTexture", true); + } + +#endif + + return true; +} + +static bool loggingEnabled = false; +static bool outlineEnabled = false; +static bool perfEnabled = false; + +#if defined (DEBUG) || defined(INTERNAL_RELEASE) +ConsoleFunction(GLEnableLogging, void, 2, 2, "GLEnableLogging(bool);") +{ + // Ignore +} + +static void outlineDrawArrays(GLenum mode, GLint first, GLsizei count) +{ + if(mode == GL_POLYGON) + mode = GL_LINE_LOOP; + + if(mode == GL_POINTS || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP || mode == GL_LINES) + glDrawArrays( mode, first, count ); + else + { + glBegin(GL_LINES); + if(mode == GL_TRIANGLE_STRIP) + { + U32 i; + for(i = 0; i < count - 1; i++) + { + glArrayElement(first + i); + glArrayElement(first + i + 1); + if(i + 2 != count) + { + glArrayElement(first + i); + glArrayElement(first + i + 2); + } + } + } + else if(mode == GL_TRIANGLE_FAN) + { + for(U32 i = 1; i < count; i ++) + { + glArrayElement(first); + glArrayElement(first + i); + if(i != count - 1) + { + glArrayElement(first + i); + glArrayElement(first + i + 1); + } + } + } + else if(mode == GL_TRIANGLES) + { + for(U32 i = 3; i <= count; i += 3) + { + glArrayElement(first + i - 3); + glArrayElement(first + i - 2); + glArrayElement(first + i - 2); + glArrayElement(first + i - 1); + glArrayElement(first + i - 3); + glArrayElement(first + i - 1); + } + } + else if(mode == GL_QUADS) + { + for(U32 i = 4; i <= count; i += 4) + { + glArrayElement(first + i - 4); + glArrayElement(first + i - 3); + glArrayElement(first + i - 3); + glArrayElement(first + i - 2); + glArrayElement(first + i - 2); + glArrayElement(first + i - 1); + glArrayElement(first + i - 4); + glArrayElement(first + i - 1); + } + } + else if(mode == GL_QUAD_STRIP) + { + if(count < 4) + return; + glArrayElement(first + 0); + glArrayElement(first + 1); + for(U32 i = 4; i <= count; i += 2) + { + glArrayElement(first + i - 4); + glArrayElement(first + i - 2); + + glArrayElement(first + i - 3); + glArrayElement(first + i - 1); + + glArrayElement(first + i - 2); + glArrayElement(first + i - 1); + } + } + glEnd(); + } +} + +static U32 getIndex(GLenum type, const void *indices, U32 i) +{ + if(type == GL_UNSIGNED_BYTE) + return ((U8 *) indices)[i]; + else if(type == GL_UNSIGNED_SHORT) + return ((U16 *) indices)[i]; + else + return ((U32 *) indices)[i]; +} + +static void outlineDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices) +{ + if(mode == GL_POLYGON) + mode = GL_LINE_LOOP; + + if(mode == GL_POINTS || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP || mode == GL_LINES) + glDrawElements( mode, count, type, indices ); + else + { + glBegin(GL_LINES); + if(mode == GL_TRIANGLE_STRIP) + { + U32 i; + for(i = 0; i < count - 1; i++) + { + glArrayElement(getIndex(type, indices, i)); + glArrayElement(getIndex(type, indices, i + 1)); + if(i + 2 != count) + { + glArrayElement(getIndex(type, indices, i)); + glArrayElement(getIndex(type, indices, i + 2)); + } + } + } + else if(mode == GL_TRIANGLE_FAN) + { + for(U32 i = 1; i < count; i ++) + { + glArrayElement(getIndex(type, indices, 0)); + glArrayElement(getIndex(type, indices, i)); + if(i != count - 1) + { + glArrayElement(getIndex(type, indices, i)); + glArrayElement(getIndex(type, indices, i + 1)); + } + } + } + else if(mode == GL_TRIANGLES) + { + for(U32 i = 3; i <= count; i += 3) + { + glArrayElement(getIndex(type, indices, i - 3)); + glArrayElement(getIndex(type, indices, i - 2)); + glArrayElement(getIndex(type, indices, i - 2)); + glArrayElement(getIndex(type, indices, i - 1)); + glArrayElement(getIndex(type, indices, i - 3)); + glArrayElement(getIndex(type, indices, i - 1)); + } + } + else if(mode == GL_QUADS) + { + for(U32 i = 4; i <= count; i += 4) + { + glArrayElement(getIndex(type, indices, i - 4)); + glArrayElement(getIndex(type, indices, i - 3)); + glArrayElement(getIndex(type, indices, i - 3)); + glArrayElement(getIndex(type, indices, i - 2)); + glArrayElement(getIndex(type, indices, i - 2)); + glArrayElement(getIndex(type, indices, i - 1)); + glArrayElement(getIndex(type, indices, i - 4)); + glArrayElement(getIndex(type, indices, i - 1)); + } + } + else if(mode == GL_QUAD_STRIP) + { + if(count < 4) + return; + glArrayElement(getIndex(type, indices, 0)); + glArrayElement(getIndex(type, indices, 1)); + for(U32 i = 4; i <= count; i += 2) + { + glArrayElement(getIndex(type, indices, i - 4)); + glArrayElement(getIndex(type, indices, i - 2)); + + glArrayElement(getIndex(type, indices, i - 3)); + glArrayElement(getIndex(type, indices, i - 1)); + + glArrayElement(getIndex(type, indices, i - 2)); + glArrayElement(getIndex(type, indices, i - 1)); + } + } + glEnd(); + } +} + +ConsoleFunction(GLEnableOutline, void, 2, 2, "GLEnableOutline(bool);") +{ + argc; + bool enable = dAtob(argv[1]); + +#if 0 // Don't ever use outline mode, GL functions go recursive, not needed. + if(outlineEnabled == enable) + return; + + if(enable && (loggingEnabled || perfEnabled)) + return; + + outlineEnabled = enable; + + if ( enable ) + { + glDrawElements = outlineDrawElements; + glDrawArrays = outlineDrawArrays; + // FIXME: (outline mode only used for debugging, not critical) + // SwapBuffers should be replaced with: + // SwapBuffers; + // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + else + { + glDrawElements = glDrawElements; + glDrawArrays = glDrawArrays; + } +#endif /* 0 */ +} + +static void perfDrawArrays(GLenum mode, GLint first, GLsizei count) +{ + gGLState.primCount[gGLState.primMode]++; + U32 tc = 0; + + if(mode == GL_TRIANGLES) + tc = count / 3; + else if(mode == GL_TRIANGLE_FAN || mode == GL_TRIANGLE_STRIP) + tc = count - 2; + + gGLState.triCount[gGLState.primMode] += tc; + glDrawArrays( mode, first, count ); +} + +static void perfDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices) +{ + gGLState.primCount[gGLState.primMode]++; + U32 tc = 0; + + if(mode == GL_TRIANGLES) + tc = count / 3; + else if(mode == GL_TRIANGLE_FAN || mode == GL_TRIANGLE_STRIP) + tc = count - 2; + + gGLState.triCount[gGLState.primMode] += tc; + glDrawElements( mode, count, type, indices ); +} + +ConsoleFunction(GLEnableMetrics, void, 2, 2, "GLEnableMetrics(bool);") +{ + argc; + static bool varsAdded = false; + + if(!varsAdded) + { + Con::addVariable("OpenGL::triCount0", TypeS32, &gGLState.triCount[0]); + Con::addVariable("OpenGL::triCount1", TypeS32, &gGLState.triCount[1]); + Con::addVariable("OpenGL::triCount2", TypeS32, &gGLState.triCount[2]); + Con::addVariable("OpenGL::triCount3", TypeS32, &gGLState.triCount[3]); + + Con::addVariable("OpenGL::primCount0", TypeS32, &gGLState.primCount[0]); + Con::addVariable("OpenGL::primCount1", TypeS32, &gGLState.primCount[1]); + Con::addVariable("OpenGL::primCount2", TypeS32, &gGLState.primCount[2]); + Con::addVariable("OpenGL::primCount3", TypeS32, &gGLState.primCount[3]); + varsAdded = true; + } + + bool enable = dAtob(argv[1]); +#if 0 // Don't ever use perf mode, GL functions go recursive, not needed. + if(perfEnabled == enable) + return; + + if(enable && (loggingEnabled || outlineEnabled)) + return; + + perfEnabled = enable; + + if ( enable ) + { + glDrawElements = perfDrawElements; + glDrawArrays = perfDrawArrays; + } + else + { + glDrawElements = glDrawElements; + glDrawArrays = glDrawArrays; + } +#endif /* 0 */ +} +#endif /* DEBUG || INTERNAL_RELEASE */ diff --git a/platformLinux/linuxIO.cc b/platformLinux/linuxIO.cc new file mode 100644 index 0000000..af185c1 --- /dev/null +++ b/platformLinux/linuxIO.cc @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +int linuxOpen(const char *path, int oflag) +{ + return open(path, oflag); +} + +int linuxClose(int fd) +{ + return close(fd); +} + +ssize_t linuxRead(int fd, void *buf, size_t nbytes) +{ + return read(fd, buf, nbytes); +} + +ssize_t linuxWrite(int fd, const void *buf, size_t nbytes) +{ + return write(fd, buf, nbytes); +} diff --git a/platformLinux/linuxInput.cc b/platformLinux/linuxInput.cc new file mode 100644 index 0000000..3e9dc34 --- /dev/null +++ b/platformLinux/linuxInput.cc @@ -0,0 +1,537 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include "sdl_utils.h" + +#include "engine/platformLinux/platformLinux.h" +#include "engine/platform/platformInput.h" +#include "engine/platform/event.h" +#include "engine/console/console.h" +#include "engine/platform/gameInterface.h" + +#define USE_X11_TRANSLATE + +#ifdef USE_X11_TRANSLATE +static void fillAsciiTable( void ); +#endif + +InputManager* Input::smManager = 0; +bool Input::smActive = false; + +static SDL_Joystick* joystick = 0; +static int joyNumButtons = 0; +static int joyNumAxes = 0; +static int joyNumBalls = 0; +static int joyNumHats = 0; +static U8* joyHatStates = 0; + +static bool cIsJoystickDetected( SimObject* obj, S32 argc, const char** argv ) +{ + return ( joystick != 0 ); +} + +static const char* cGetJoystickAxes( SimObject* obj, S32 argc, const char** argv ) +{ + const char* axisNames[] = { "\tX", "\tY", "\tZ", "\tR", "\tU", "\tV" }; + + if( joystick == 0 ) { + return ""; + } + + char buffer[64]; + + dSprintf( buffer, sizeof( buffer ), "%d", joyNumAxes ); + + for( U32 i = 0; i < joyNumAxes; i++ ) { + dStrcat( buffer, axisNames[i] ); + } + + char* returnString = Con::getReturnBuffer( dStrlen( buffer ) + 1 ); + dStrcpy( returnString, buffer ); + + return returnString; +} + +// Small helpers. +U8 getHatState( U8 hat ) +{ + + if( joyHatStates == 0 ) { + return 0; + } + + if( hat >= joyNumHats ) { + return 0; + } + + return joyHatStates[hat]; +} + +void setHatState( U8 hat, U8 state ) +{ + + if( joyHatStates == 0 ) { + return; + } + + if( hat >= joyNumHats ) { + return; + } + + joyHatStates[hat] = state; +} + +void Input::init( void ) +{ + Con::printf( "Input init: " ); + smActive = false; + + Con::addCommand( "isJoystickDetected", cIsJoystickDetected, "isJoystickDetected();", 1, 1 ); + Con::addCommand( "getJoystickAxes", cGetJoystickAxes, "getJoystickAxes( device );", 2, 2 ); +#ifndef DEDICATED +#ifdef USE_X11_TRANSLATE + fillAsciiTable( ); +#endif + sdl_InitClipboard(); + + Con::printf( " Clipboard initialized" ); + Con::printf( " Keyboard initialized" ); + Con::printf( " Mouse initialized" ); + + if( SDL_Init( SDL_INIT_JOYSTICK ) < 0 ) { + Con::printf( " Joystick not initialized" ); + return; + } + + Con::printf( " Joystick initialized" ); + int numJoysticks = SDL_NumJoysticks( ); + + if( numJoysticks <= 0 ) { + Con::printf( " No joysticks detected" ); + return; + } + + int i; + + for( i = 0; i < numJoysticks; i++ ) { + SDL_Joystick* temp = SDL_JoystickOpen( i ); + + if( temp ) { + joystick = temp; + break; + } + + } + + if( !joystick ) { + Con::printf( " No openable joysticks found" ); + } + + joyNumButtons = SDL_JoystickNumButtons( joystick ); + joyNumAxes = SDL_JoystickNumAxes( joystick ); + joyNumBalls = SDL_JoystickNumBalls( joystick ); + joyNumHats = SDL_JoystickNumHats( joystick ); + + if( joyNumHats ) { + joyHatStates = new U8[joyNumHats]; + dMemset( joyHatStates, 0, joyNumHats ); + } + + Con::printf( " Found joystick: %s", SDL_JoystickName( i ) ); + Con::printf( " Buttons: %d", joyNumButtons ); + Con::printf( " Axes: %d", joyNumAxes ); + Con::printf( " Balls: %d", joyNumBalls ); + Con::printf( " Hats: %d", joyNumHats ); +#endif +} + +void Input::destroy( void ) +{ + Con::printf( "Input shutdown" ); + smActive = false; +#ifndef DEDICATED + + SDL_JoystickClose( joystick ); + joystick = 0; + joyNumButtons = 0; + joyNumAxes = 0; + joyNumBalls = 0; + joyNumHats = 0; + + if( joyHatStates ) { + delete[] joyHatStates; + } +#endif +} + +#ifdef USE_X11_TRANSLATE +typedef struct { + U16 lower; + U16 upper; + U16 goofy; +} asciiTable_t; + +#define NUM_KEYS (KEY_OEM_102+1) +#define KEY_FIRST KEY_ESCAPE + +static asciiTable_t asciiTable[NUM_KEYS]; + +// special prototype for non-visible SDL function +extern "C" Uint16 X11_KeyToUnicode( SDLKey keysym, SDLMod modifiers ); + +static void fillAsciiTable( void ) +{ +#ifndef DEDICATED + U32 keyCode = 0; + + dMemset( &asciiTable, 0, sizeof( asciiTable ) ); + + for( keyCode = KEY_FIRST; keyCode < NUM_KEYS; keyCode++ ) { + Uint16 key = 0; + SDLKey sym = translateKeyCodeToSDL( keyCode ); + SDLMod mod = 0; + + // lower case + key = X11_KeyToUnicode( sym, mod ); + asciiTable[keyCode].lower = key; + // upper case + mod = KMOD_SHIFT; + key = X11_KeyToUnicode( sym, mod ); + asciiTable[keyCode].upper = key; + // goofy (i18n) case + mod = KMOD_MODE; + key = X11_KeyToUnicode( sym, mod ); + asciiTable[keyCode].goofy = key; + } +#endif +} + +U16 Input::getKeyCode( U16 asciiCode ) +{ + + for( S32 i = KEY_FIRST; i < NUM_KEYS; i++ ) { + + if( asciiTable[i].lower == asciiCode ) { + return i; + } else if( asciiTable[i].upper == asciiCode ) { + return i; + } else if( asciiTable[i].goofy == asciiCode ) { + return i; + } + + } + + return 0; +} + +U16 Input::getAscii( U16 keyCode, KEY_STATE keyState ) +{ + + if( keyCode >= NUM_KEYS ) { + return 0; + } + + switch( keyState ) { + case STATE_LOWER: + return asciiTable[keyCode].lower; + case STATE_UPPER: + return asciiTable[keyCode].upper; + case STATE_GOOFY: + return asciiTable[keyCode].goofy; + default: + return 0; + } + +} +#else +// FIXME: 'goofy' is always '0' because we suck +typedef struct { + U16 code; + U16 lower; + U16 upper; + U16 goofy; +} asciiTable_t; + +static asciiTable_t asciiTable[128] = +{ + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_BACKSPACE, 8, 8, 0 }, + { KEY_TAB, 9, 9, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_RETURN, 13, 13, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_ESCAPE, 27, 27, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_NULL, 0, 0, 0 }, + { KEY_SPACE, 32, 32, 0 }, + { KEY_1, 49, 33, 0 }, + { KEY_APOSTROPHE, 39, 34, 0 }, + { KEY_3, 51, 35, 0 }, + { KEY_4, 52, 36, 0 }, + { KEY_5, 53, 37, 0 }, + { KEY_7, 55, 38, 0 }, + { KEY_APOSTROPHE, 39, 34, 0 }, + { KEY_9, 58, 40, 0 }, + { KEY_0, 48, 41, 0 }, + { KEY_8, 56, 42, 0 }, + { KEY_EQUALS, 61, 43, 0 }, // 43 + { KEY_COMMA, 44, 60, 0 }, + { KEY_MINUS, 45, 95, 0 }, + { KEY_PERIOD, 46, 62, 0 }, + { KEY_BACKSLASH, 47, 63, 0 }, + { KEY_0, 48, 41, 0 }, // 48 + { KEY_1, 49, 33, 0 }, + { KEY_2, 50, 64, 0 }, + { KEY_3, 51, 35, 0 }, + { KEY_4, 52, 36, 0 }, + { KEY_5, 53, 37, 0 }, + { KEY_6, 54, 94, 0 }, + { KEY_7, 54, 38, 0 }, + { KEY_8, 56, 42, 0 }, + { KEY_9, 57, 28, 0 }, + { KEY_SEMICOLON, 59, 58, 0 }, // 58 + { KEY_SEMICOLON, 59, 58, 0 }, + { KEY_COMMA, 44, 60, 0 }, + { KEY_EQUALS, 61, 43, 0 }, + { KEY_PERIOD, 46, 62, 0 }, + { KEY_BACKSLASH, 47, 63, 0 }, // 63 + { KEY_2, 50, 64, 0 }, + { KEY_A, 97, 65, 0 }, // 65 + { KEY_B, 98, 66, 0 }, + { KEY_C, 99, 67, 0 }, + { KEY_D, 100, 68, 0 }, + { KEY_E, 101, 69, 0 }, + { KEY_F, 102, 70, 0 }, + { KEY_G, 103, 71, 0 }, + { KEY_H, 104, 72, 0 }, + { KEY_I, 105, 73, 0 }, + { KEY_J, 106, 74, 0 }, + { KEY_K, 107, 75, 0 }, + { KEY_L, 108, 76, 0 }, + { KEY_M, 109, 77, 0 }, + { KEY_N, 110, 78, 0 }, + { KEY_O, 111, 79, 0 }, + { KEY_P, 112, 80, 0 }, + { KEY_Q, 113, 81, 0 }, + { KEY_R, 114, 82, 0 }, + { KEY_S, 115, 83, 0 }, + { KEY_T, 116, 84, 0 }, + { KEY_U, 117, 85, 0 }, + { KEY_V, 118, 86, 0 }, + { KEY_W, 119, 87, 0 }, + { KEY_X, 120, 88, 0 }, + { KEY_Y, 121, 89, 0 }, + { KEY_Z, 122, 90, 0 }, + { KEY_LBRACKET, 91, 123, 0 }, // 91 + { KEY_SLASH, 92, 124, 0 }, + { KEY_RBRACKET, 93, 125, 0 }, + { KEY_6, 54, 94, 0 }, + { KEY_MINUS, 45, 95, 0 }, + { KEY_TILDE, 96, 126, 0 }, + { KEY_A, 97, 65, 0 }, // 97 + { KEY_B, 98, 66, 0 }, + { KEY_C, 99, 67, 0 }, + { KEY_D, 100, 68, 0 }, + { KEY_E, 101, 69, 0 }, + { KEY_F, 102, 70, 0 }, + { KEY_G, 103, 71, 0 }, + { KEY_H, 104, 72, 0 }, + { KEY_I, 105, 73, 0 }, + { KEY_J, 106, 74, 0 }, + { KEY_K, 107, 75, 0 }, + { KEY_L, 108, 76, 0 }, + { KEY_M, 109, 77, 0 }, + { KEY_N, 110, 78, 0 }, + { KEY_O, 111, 79, 0 }, + { KEY_P, 112, 80, 0 }, + { KEY_Q, 113, 81, 0 }, + { KEY_R, 114, 82, 0 }, + { KEY_S, 115, 83, 0 }, + { KEY_T, 116, 84, 0 }, + { KEY_U, 117, 85, 0 }, + { KEY_V, 118, 86, 0 }, + { KEY_W, 119, 87, 0 }, + { KEY_X, 120, 88, 0 }, + { KEY_Y, 121, 89, 0 }, + { KEY_Z, 122, 90, 0 }, + { KEY_LBRACKET, 91, 123, 0 }, // 123 + { KEY_SLASH, 92, 124, 0 }, + { KEY_RBRACKET, 93, 125, 0 }, + { KEY_TILDE, 94, 126, 0 }, + { KEY_DELETE, 127, 127, 0 }, // 127 +}; + +U16 Input::getKeyCode( U16 asciiCode ) +{ + + if( asciiCode < sizeof( asciiTable ) ) { + return asciiTable[asciiCode].code; + } else { + return 0; + } + +} + +U16 Input::getAscii( U16 keyCode, KEY_STATE whichOne ) +{ + + for( S32 i = 0; i < sizeof( asciiTable ); i++ ) { + + if( asciiTable[i].code == keyCode ) { + + switch( whichOne ) { + case STATE_LOWER: + case STATE_GOOFY: + return asciiTable[i].lower; + case STATE_UPPER: + return asciiTable[i].upper; + } + + } + + } + + return 0; +} +#endif + +bool Input::enable( void ) +{ + return true; +} + +void Input::disable( void ) +{ +} + +void Input::activate( void ) +{ + smActive = true; +} + +void Input::deactivate( void ) +{ + smActive = false; +} + +void Input::reactivate( void ) +{ + // Sorry, needed this indirection for Windows... + Input::activate(); +} + +bool Input::isEnabled( void ) +{ + return true; +} + +bool Input::isActive( void ) +{ + return smActive; +} + +// bool Input::isKeyboardActive( void ) +// { +// return true; +// } +// +// bool Input::isMouseActive( void ) +// { +// return true; +// } +// +// bool Input::isJoystickActive( void ) +// { +// return ( joystick != 0 ); +// } + +static JoystickCodes translateAxis( U8 axis ) +{ + JoystickCodes array[] = { SI_XAXIS, SI_YAXIS, SI_ZAXIS, + SI_RXAXIS, SI_RYAXIS, SI_RZAXIS }; + axis = ( axis < 6 ) ? axis : 5; + return array[axis]; +} + +static void processAxes( void ) +{ +#ifndef DEDICATED + InputEvent event; + + // we need continuous reporting for joystick axes + for( S32 i = 0; i < joyNumAxes; i++ ) { + S16 value = SDL_JoystickGetAxis( joystick, i ); + + event.deviceInst = 0; + event.deviceType = JoystickDeviceType; + event.objType = translateAxis( i ); + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = 0; + event.ascii = 0; + event.fValue = static_cast( value ) / 32767.0f; + + Game->postEvent( event ); + } +#endif +} + +void Input::process( void ) +{ + // do our immediate processing here + processAxes( ); +} + +//----------------------------------------------------------------------------- +// Clipboard functions +const char* Platform::getClipboard() +{ +#ifdef DEDICATED + return ""; +#else + return sdl_GetClipboard(); +#endif +} + +bool Platform::setClipboard(const char *text) +{ +#ifdef DEDICATED + return false; +#else + if (!text) + return false; + + sdl_PutClipboard(text); + + return true; +#endif +} diff --git a/platformLinux/linuxMath.cc b/platformLinux/linuxMath.cc new file mode 100644 index 0000000..e487340 --- /dev/null +++ b/platformLinux/linuxMath.cc @@ -0,0 +1,459 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/console.h" +#include "Math/mMath.h" + +#include + +extern "C" { + void m_quatF_set_matF_ASM( F32 x, F32 y, F32 z, F32 w, F32* m ); + F32 m_matF_determinant_ASM( const F32* m ); + void m_matF_inverse_ASM( F32* m ); + void m_matF_affineInverse_ASM( F32* m ); + void m_matF_x_matF_ASM( const F32* a, const F32* b, F32* m ); + void m_matF_x_vectorF_ASM( const F32* m, const F32* v, F32* r ); + U32 mSolveQuadratic_ASM(F32 A, F32 B, F32 C, F32* x); + U32 mSolveCubic_ASM(F32 A, F32 B, F32 C, F32 D, F32* x); + U32 mSolveQuartic_ASM( F32 A, F32 B, F32 C, F32 D, F32 E, F32* x ); + S32 m_mulDivS32_ASM(S32 a, S32 b, S32 c); + U32 m_mulDivU32_ASM(S32 a, S32 b, U32 c); + + // Added lots more math functions optimized by Visual C++ + void m_point2F_normalize_ASM(F32 *p); + void m_point2F_normalize_f_ASM(F32 *p, F32 val); + void m_point2D_normalize_ASM(F64 *p); + void m_point2D_normalize_f_ASM(F64 *p, F64 val); + void m_point3F_normalize_ASM(F32 *p); + void m_point3F_normalize_f_ASM(F32 *p, F32 val); + void m_point3F_interpolate_ASM(const F32 *from, const F32 *to, F32 factor, F32 *result ); + void m_point3D_normalize_ASM(F64 *p); + void m_point3D_interpolate_ASM(const F64 *from, const F64 *to, F64 factor, F64 *result ); + void m_point3F_bulk_dot_ASM(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + F32* output); + void m_point3F_bulk_dot_indexed_ASM(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + const U32* pointIndices, + F32* output); + void m_matF_identity_ASM(F32 *m); + void m_matF_set_euler_ASM(const F32 *e, F32 *result); + void m_matF_set_euler_point_ASM(const F32 *e, const F32 *p, F32 *result); + void m_matF_transpose_ASM(F32 *m); + void m_matF_scale_ASM(F32 *m, const F32 *p); + void m_matF_normalize_ASM(F32 *m); + void m_matF_x_point4F_ASM(const F32 *m, const F32 *p, F32 *presult); +} + +extern void mInstallLibrary_C( void ); + +// drivers +static void m_quatF_set_matF_D( F32 x, F32 y, F32 z, F32 w, F32* m ) +{ + AssertISV( !isnan( x ), "x == NaN in QuatF::setMatrix" ); + AssertISV( !isnan( y ), "y == NaN in QuatF::setMatrix" ); + AssertISV( !isnan( z ), "z == NaN in QuatF::setMatrix" ); + AssertISV( !isnan( w ), "w == NaN in QuatF::setMatrix" ); + + m_quatF_set_matF_ASM( x, y, z, w, m ); + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in QuatF::setMatrix" ); + } + +} + +static F32 m_matF_determinant_D( const F32* m ) +{ + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in MatrixF::determinant" ); + } + + F32 d = m_matF_determinant_ASM( m ); + + AssertISV( !isnan( d ), "d == NaN in MatrixF::determinant" ); + + return d; +} + +static void m_matF_inverse_D( F32* m ) +{ + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in MatrixF::inverse" ); + } + + m_matF_inverse_ASM( m ); + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in MatrixF::inverse" ); + } + + +} + +static void m_matF_affineInverse_D( F32* m ) +{ + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in MatrixF::affineInverse" ); + } + + m_matF_affineInverse_ASM( m ); + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in MatrixF::affineInverse" ); + } + +} + +static void m_matF_x_matF_D( const F32* a, const F32* b, F32* m ) +{ + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( a[i] ), "a[i] == NaN in MatrixF::mul" ); + AssertISV( !isnan( b[i] ), "b[i] == NaN in MatrixF::mul" ); + } + + m_matF_x_matF_ASM( a, b, m ); + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in MatrixF::mul" ); + } + +} + +static void m_matF_x_vectorF_D( const F32* m, const F32* v, F32* r ) +{ + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in MatrixF::mul( VectorF )" ); + } + + for( int i = 0; i < 3; i++ ) { + AssertISV( !isnan( v[i] ), "v[i] == NaN in MatrixF::mul( VectorF )" ); + } + + m_matF_x_vectorF_ASM( m, v, r ); + + for( int i = 0; i < 3; i++ ) { + AssertISV( !isnan( r[i] ), "r[i] == NaN in MatrixF::mul( VectorF )" ); + } + +} + +static U32 mSolveQuartic_D( F32 A, F32 B, F32 C, F32 D, F32 E, F32* x ) +{ + AssertISV( !isnan( A ), "A == NaN in mSolveQuartic_D" ); + AssertISV( !isnan( B ), "B == NaN in mSolveQuartic_D" ); + AssertISV( !isnan( C ), "C == NaN in mSolveQuartic_D" ); + AssertISV( !isnan( D ), "D == NaN in mSolveQuartic_D" ); + AssertISV( !isnan( E ), "E == NaN in mSolveQuartic_D" ); + + U32 n = mSolveQuartic_ASM( A, B, C, D, E, x ); + + for( int i = 0; i < n; i++ ) { + AssertISV( !isnan( x[i] ), "x[i] == NaN in mSolveQuartic_D" ); + } + + return n; +} + +static void m_matF_transpose_D(F32 *m) +{ + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in MatrixF::transpose()" ); + } + + m_matF_transpose_ASM( m ); + + for( int i = 0; i < 16; i++ ) { + AssertISV( !isnan( m[i] ), "m[i] == NaN in MatrixF::transpose()" ); + } +} + +static void checkASM( void ) +{ + float i[] = { 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 }; + float aff[] = { 0.1, 0, 0, 0.333, + 0, 0.2, 0, 0.666, + 0, 0, 0.3, 0.999, + 0, 0, 0, 1.0 }; + float inv[] = { 1, 3, 1, 1, + 2, 5, 2, 2, + 1, 3, 8, 9, + 1, 3, 2, 2 }; + float m[] = { 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 }; + float v[] = { 1, 2 ,3 }; + float v2[] = { -1, 2, -0.333 }; + float a[16]; + float c[16]; + int j, k; + + for( int j = 0; j < 16; j++ ) { + a[j] = 0.0f; + c[j] = 0.0f; + } + + m_matF_x_vectorF_ASM( inv, v, a ); + m_matF_x_vectorF( inv, v, c ); + + AssertISV( dMemcmp( a, c, sizeof( a ) ) == 0, "m_matF_x_vectorF" ); + + m_matF_x_vectorF_ASM( inv, v2, a ); + m_matF_x_vectorF( inv, v2, c ); + + AssertISV( dMemcmp( a, c, sizeof( a ) ) == 0, "m_matF_x_vectorF" ); + + dMemcpy( a, inv, sizeof( a ) ); + dMemcpy( c, inv, sizeof( c ) ); + m_matF_inverse_ASM( a ); + m_matF_inverse( c ); + + AssertISV( dMemcmp( a, c, sizeof( a ) ) == 0, "m_matF_inverse" ); + + m_quatF_set_matF_ASM( 1, 2, 3, 4, a ); + m_quatF_set_matF( 1, 2, 3, 4, c ); + + AssertISV( dMemcmp( a, c, sizeof( a ) ) == 0, "m_quatF_set_matF" ); + + a[0] = m_matF_determinant_ASM( inv ); + c[0] = m_matF_determinant_ASM( inv ); + + AssertISV( a[0] == c[0], "m_matF_determinant" ); + + dMemcpy( a, aff, sizeof( a ) ); + dMemcpy( c, aff, sizeof( c ) ); + m_matF_affineInverse_ASM( a ); + m_matF_affineInverse( c ); + + AssertISV( dMemcmp( a, c, sizeof( a ) ) == 0, "m_matF_affineInverse" ); + + m_matF_x_matF_ASM( m, m, a ); + m_matF_x_matF( m, m, c ); + + AssertISV( dMemcmp( a, c, sizeof( a ) ) == 0, "m_matF_x_matF" ); + +#if 0 // Code generated with Visual C++ gives slighly different results + j = mSolveQuartic_ASM( 1.0, 2.0, 3.0, 4.0, 5.0, a ); + k = mSolveQuartic( 1.0, 2.0, 3.0, 4.0, 5.0, c ); + + AssertISV( dMemcmp( a, c, sizeof( a ) ) == 0, "mSolveQuartic" ); + AssertISV( j == k, "mSolveQuartic" ); + + j = mSolveQuartic_ASM( 0.0, 1.0, 2.0, 3.0, 4.0, a ); + k = mSolveQuartic( 0.0, 1.0, 2.0, 3.0, 4.0, c ); + + AssertISV( dMemcmp( a, c, sizeof( a ) ) == 0, "mSolveQuartic" ); + AssertISV( j == k, "mSolveQuartic" ); +#endif +} + +void mInstallLibrary_ASM( void ) +{ + // Experimentally determined by 10,000,000 iteration benchmarks + U32 asm_flags = 0x027FC4F3; + U32 dbg_flags = 0x00000000; + + if ( getenv("TRIBES2_ASM_FLAGS") ) { + asm_flags = strtol(getenv("TRIBES2_ASM_FLAGS"), NULL, 0); + if ( ! asm_flags ) { + return; + } + } + if ( getenv("TRIBES2_ASM_DEBUG") ) { + dbg_flags = strtol(getenv("TRIBES2_ASM_DEBUG"), NULL, 0); + if ( dbg_flags ) { + checkASM( ); + } + } + + if ( asm_flags & (1<<0) ) { + m_mulDivS32 = m_mulDivS32_ASM; + m_mulDivU32 = m_mulDivU32_ASM; + } + if ( asm_flags & (1<<1) ) { + if ( dbg_flags & (1<<1) ) + m_quatF_set_matF = m_quatF_set_matF_D; + else + m_quatF_set_matF = m_quatF_set_matF_ASM; + } + if ( asm_flags & (1<<2) ) { + if ( dbg_flags & (1<<2) ) + m_matF_determinant = m_matF_determinant_D; + else + m_matF_determinant = m_matF_determinant_ASM; + } + if ( asm_flags & (1<<3) ) { + if ( dbg_flags & (1<<3) ) + m_matF_inverse = m_matF_inverse_D; + else + m_matF_inverse = m_matF_inverse_ASM; + } + if ( asm_flags & (1<<4) ) { + if ( dbg_flags & (1<<4) ) + m_matF_affineInverse = m_matF_affineInverse_D; + else + m_matF_affineInverse = m_matF_affineInverse_ASM; + } + if ( asm_flags & (1<<5) ) { + if ( dbg_flags & (1<<5) ) + m_matF_x_matF = m_matF_x_matF_D; + else + m_matF_x_matF = m_matF_x_matF_ASM; + } + if ( asm_flags & (1<<6) ) { + mSolveQuadratic = mSolveQuadratic_ASM; + mSolveCubic = mSolveCubic_ASM; + } + if ( asm_flags & (1<<7) ) { + if ( dbg_flags & (1<<7) ) + mSolveQuartic = mSolveQuartic_D; + else + mSolveQuartic = mSolveQuartic_ASM; + } + + // Added lots more math functions optimized by Visual C++ + if ( asm_flags & (1<<8) ) + m_point2F_normalize = m_point2F_normalize_ASM; + if ( asm_flags & (1<<9) ) + m_point2F_normalize_f = m_point2F_normalize_f_ASM; + if ( asm_flags & (1<<10) ) + m_point2D_normalize = m_point2D_normalize_ASM; + if ( asm_flags & (1<<11) ) + m_point2D_normalize_f = m_point2D_normalize_f_ASM; + if ( asm_flags & (1<<12) ) + m_point3F_normalize = m_point3F_normalize_ASM; + if ( asm_flags & (1<<13) ) + m_point3F_normalize_f = m_point3F_normalize_f_ASM; + if ( asm_flags & (1<<14) ) + m_point3F_interpolate = m_point3F_interpolate_ASM; + + if ( asm_flags & (1<<15) ) + m_point3D_normalize = m_point3D_normalize_ASM; + if ( asm_flags & (1<<16) ) + m_point3D_interpolate = m_point3D_interpolate_ASM; + + if ( asm_flags & (1<<17) ) + m_point3F_bulk_dot = m_point3F_bulk_dot_ASM; + if ( asm_flags & (1<<18) ) + m_point3F_bulk_dot_indexed = m_point3F_bulk_dot_indexed_ASM; + + if ( asm_flags & (1<<19) ) + m_matF_set_euler = m_matF_set_euler_ASM; + if ( asm_flags & (1<<20) ) + m_matF_set_euler_point = m_matF_set_euler_point_ASM; + if ( asm_flags & (1<<21) ) + m_matF_identity = m_matF_identity_ASM; + if ( asm_flags & (1<<22) ) { + if ( dbg_flags & (1<<22) ) + m_matF_transpose = m_matF_transpose_D; + else + m_matF_transpose = m_matF_transpose_ASM; + } + if ( asm_flags & (1<<23) ) + m_matF_scale = m_matF_scale_ASM; + if ( asm_flags & (1<<24) ) + m_matF_normalize = m_matF_normalize_ASM; + if ( asm_flags & (1<<25) ) + m_matF_x_point4F = m_matF_x_point4F_ASM; +} + +static void cMathInit( SimObject* obj, S32 argc, const char** argv ) +{ + U32 properties = CPU_PROP_C; + + 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 ) +{ + Con::addCommand( "MathInit", cMathInit, "MathInit( detect|C|FPU|MMX|3DNOW|SSE|... )", 1, 10 ); + + if( !properties ) { + properties = Platform::SystemInfo.processor.properties; + } else { + 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" ); + } + + if( properties & CPU_PROP_SSE ) { + Con::printf( " Installing SSE extensions" ); + } + +} diff --git a/platformLinux/linuxMath_ASM.asm b/platformLinux/linuxMath_ASM.asm new file mode 100644 index 0000000..1d3b445 --- /dev/null +++ b/platformLinux/linuxMath_ASM.asm @@ -0,0 +1,1014 @@ +; +; Assembler FPU fun. Naive implementations +; +; TODO: +; - pipeline with fxch +; - remove redundant loads by leaving on stack +; + +segment .data + +; scratch array +array times 16 dd 0 +; inverse of determinant +inverse_determinant dd 0 + + +segment .text + +; +; void m_quatF_set_matF_ASM( F32 x, F32 y, F32 z, F32 w, F32* m ) +; + +%define in_x [ebp+8] +%define in_y [ebp+12] +%define in_z [ebp+16] +%define in_w [ebp+20] +%define in_m [ebp+24] + +%define xs [eax+0] +%define ys [eax+4] +%define zs [eax+8] +%define wx [eax+12] +%define wy [eax+16] +%define wz [eax+20] +%define xx [eax+24] +%define xy [eax+28] +%define xz [eax+32] +%define yy [eax+36] +%define yz [eax+40] +%define zz [eax+44] + +global m_quatF_set_matF_ASM + +m_quatF_set_matF_ASM: + + push ebp + mov ebp, esp + + ; f -> eax, m -> ecx + mov eax, array + mov ecx, in_m + + ; xs = x * 2.0f + fld dword in_x + fadd st0, st0 + fstp dword xs + + ; ys = y * 2.0f + fld dword in_y + fadd st0, st0 + fstp dword ys + + ; zs = z * 2.0f + fld dword in_z + fadd st0, st0 + fstp dword zs + + ; load w + fld dword in_w + + ; wx = w * xs + fld dword xs + fmul st0, st1 + fstp dword wx + + ; wy = w * ys + fld dword ys + fmul st0, st1 + fstp dword wy + + ; wz = w * zs + fld dword zs + fmul st0, st1 + fstp dword wz + + ; pop w + fstp st0 + + ; load x + fld dword in_x + + ; xx = x * xs + fld dword xs + fmul st0, st1 + fstp dword xx + + ; xy = x * ys + fld dword ys + fmul st0, st1 + fstp dword xy + + ; xz = x * zs + fld dword zs + fmul st0, st1 + fstp dword xz + + ; pop x + fstp st0 + + ; don't bother loading y once, the pop negates + + ; yy = y * ys + fld dword in_y + fld dword ys + fmulp st1, st0 + fstp dword yy + + ; yz = y * zs + fld dword in_y + fld dword zs + fmulp st1, st0 + fstp dword yz + + ; and forget z + + ; zz = z * zs + fld dword in_z + fld dword zs + fmulp st1, st0 + fstp dword zz + + ; m[0] = 1.0f - ( yy + zz ) + fld1 + fld dword yy + fld dword zz + faddp st1, st0 + fsubp st1, st0 + fstp dword [ecx+0] + + ; m[4] = xy - wz + fld dword xy + fld dword wz + fsubp st1, st0 + fstp dword [ecx+16] + + ; m[8] = xz + wy + fld dword xz + fld dword wy + faddp st1, st0 + fstp dword [ecx+32] + + ; m[12] = 0.0f + fldz + fstp dword [ecx+48] + + ; m[1] = xy + wz + fld dword xy + fld dword wz + faddp st1, st0 + fstp dword [ecx+4] + + ; m[5] = 1.0f - ( xx + zz ) + fld1 + fld dword xx + fld dword zz + faddp st1, st0 + fsubp st1, st0 + fstp dword [ecx+20] + + ; m[9] = yz - wx + fld dword yz + fld dword wx + fsubp st1, st0 + fstp dword [ecx+36] + + ; m[13] = 0.0f + fldz + fstp dword [ecx+52] + + ; m[2] = xz - wy + fld dword xz + fld dword wy + fsubp st1, st0 + fstp dword [ecx+8] + + ; m[6] = yz + wx + fld dword yz + fld dword wx + faddp st1, st0 + fstp dword [ecx+24] + + ; m[10] = 1.0f - ( xx + yy ) + fld1 + fld dword xx + fld dword yy + faddp st1, st0 + fsubp st1, st0 + fstp dword [ecx+40] + + ; m[14] = 0.0f + ; m[3] = 0.0f + ; m[7] = 0.0f + ; m[11] = 0.0f + fldz + fst dword [ecx+56] + fst dword [ecx+12] + fst dword [ecx+28] + fstp dword [ecx+44] + + ; m[15] = 1.0f + fld1 + fstp dword [ecx+60] + + pop ebp + ret + + +; +; F32 m_matF_determinant_ASM( const F32* m ) +; + +global m_matF_determinant_ASM + +m_matF_determinant_ASM: + +%define in_m [ebp+8] + + push ebp + mov ebp, esp + + mov eax, in_m + + fld dword [eax+20] + fld dword [eax+40] + fmulp st1, st0 + + fld dword [eax+24] + fld dword [eax+36] + fmulp st1, st0 + + fsubp st1, st0 + fld dword [eax+0] + fmulp st1, st0 + + fld dword [eax+8] + fld dword [eax+36] + fmulp st1, st0 + + fld dword [eax+4] + fld dword [eax+40] + fmulp st1, st0 + + fsubp st1, st0 + fld dword [eax+16] + fmulp st1, st0 + + fld dword [eax+4] + fld dword [eax+24] + fmulp st1, st0 + + fld dword [eax+8] + fld dword [eax+20] + fmulp st1, st0 + + fsubp st1, st0 + fld dword [eax+32] + fmulp st1, st0 + + faddp st1, st0 + faddp st1, st0 + + ; leave it on the FPU stack + + pop ebp + ret + + +; +; void m_matF_adjoint_ASM( F32* dst, const F32* src, F32 invDet ) +; + +%define in_dst [ebp+8] +%define in_src [ebp+12] +%define in_invDet [ebp+16] + +global m_matF_adjoint_ASM + +m_matF_adjoint_ASM: + + push ebp + mov ebp, esp + + mov edx, in_dst + mov eax, in_src + + ; dst[0] = (src[5] * src[10]- src[6] * src[9]) * invDet; + fld dword [eax+20] + fld dword [eax+40] + fmulp st1, st0 + + fld dword [eax+24] + fld dword [eax+36] + fmulp st1, st0 + + fsubp st1, st0 + fmul dword in_invDet + fstp dword [edx+0] + + ; dst[1] = (src[9] * src[2] - src[10]* src[1]) * invDet; + fld dword [eax+36] + fld dword [eax+8] + fmulp st1, st0 + + fld dword [eax+40] + fld dword [eax+4] + fmulp st1, st0 + + fsubp st1, st0 + fmul dword in_invDet + fstp dword [edx+4] + + ; dst[2] = (src[1] * src[6] - src[2] * src[5]) * invDet; + fld dword [eax+4] + fld dword [eax+24] + fmulp st1, st0 + + fld dword [eax+8] + fld dword [eax+20] + fmulp st1, st0 + + fsubp st1, st0 + fmul dword in_invDet + fstp dword [edx+8] + + + ; dst[4] = (src[6] * src[8] - src[4] * src[10])* invDet; + fld dword [eax+24] + fld dword [eax+32] + fmulp st1, st0 + + fld dword [eax+16] + fld dword [eax+40] + fmulp st1, st0 + + fsubp st1, st0 + fmul dword in_invDet + fstp dword [edx+16] + + ; dst[5] = (src[10]* src[0] - src[8] * src[2]) * invDet; + fld dword [eax+40] + fld dword [eax+0] + fmulp st1, st0 + + fld dword [eax+32] + fld dword [eax+8] + fmulp st1, st0 + + fsubp st1, st0 + fmul dword in_invDet + fstp dword [edx+20] + + ; dst[6] = (src[2] * src[4] - src[0] * src[6]) * invDet; + fld dword [eax+8] + fld dword [eax+16] + fmulp st1, st0 + + fld dword [eax+0] + fld dword [eax+24] + fmulp st1, st0 + + fsubp st1, st0 + fmul dword in_invDet + fstp dword [edx+24] + + + ; dst[8] = (src[4] * src[9] - src[5] * src[8]) * invDet; + fld dword [eax+16] + fld dword [eax+36] + fmulp st1, st0 + + fld dword [eax+20] + fld dword [eax+32] + fmulp st1, st0 + + fsubp st1, st0 + fmul dword in_invDet + fstp dword [edx+32] + + ; dst[9] = (src[8] * src[1] - src[9] * src[0]) * invDet; + fld dword [eax+32] + fld dword [eax+4] + fmulp st1, st0 + + fld dword [eax+36] + fld dword [eax+0] + fmulp st1, st0 + + fsubp st1, st0 + fmul dword in_invDet + fstp dword [edx+36] + + ; dst[10]= (src[0] * src[5] - src[1] * src[4]) * invDet; + fld dword [eax+0] + fld dword [eax+20] + fmulp st1, st0 + + fld dword [eax+4] + fld dword [eax+16] + fmulp st1, st0 + + fsubp st1, st0 + fmul dword in_invDet + fstp dword [edx+40] + + pop ebp + ret + +; +; m_matF_x_vectorF_ASM( const F32* m, const F32* v, F32* r ) +; + +%define in_m [ebp+8] +%define in_v [ebp+12] +%define in_r [ebp+16] + +global m_matF_x_vectorF_ASM + +m_matF_x_vectorF_ASM: + + push ebp + mov ebp, esp + + mov eax, in_m + mov ecx, in_v + mov edx, in_r + + ; vresult[0] = m[0]*v[0] + m[1]*v[1] + m[2]*v[2]; + fld dword [eax] + fld dword [eax+4] + fmul dword [ecx+4] + fxch + fmul dword [ecx] + fld dword [eax+8] + fmul dword [ecx+8] + fxch st2 + faddp st1, st0 + faddp st1, st0 + fstp dword [edx] + + ; vresult[1] = m[4]*v[0] + m[5]*v[1] + m[6]*v[2]; + fld dword [eax+16] + fld dword [eax+20] + fmul dword [ecx+4] + fxch + fmul dword [ecx] + fld dword [eax+24] + fmul dword [ecx+8] + fxch st2 + faddp st1, st0 + faddp st1, st0 + fstp dword [edx+4] + + ; vresult[2] = m[8]*v[0] + m[9]*v[1] + m[10]*v[2]; + fld dword [eax+32] + fld dword [eax+36] + fmul dword [ecx+4] + fxch + fmul dword [ecx] + fld dword [eax+40] + fmul dword [ecx+8] + fxch st2 + faddp st1, st0 + faddp st1, st0 + fstp dword [edx+8] + + pop ebp + ret + +; +; void m_matF_inverse_ASM( F32* m ) +; + +%define in_m [ebp+8] + +global m_matF_inverse_ASM + +m_matF_inverse_ASM: + + push ebp + mov ebp, esp + + ; calculate determinant + push dword in_m + call m_matF_determinant_ASM + add esp, byte 4 + + ; calculate inverse of determinant + fld1 + fxch + fdivp st1, st0 + fstp dword [inverse_determinant] + + ; calculate adjoint matrix + push dword [inverse_determinant] + push dword in_m + push dword array + call m_matF_adjoint_ASM + add esp, byte 12 + + mov eax, array + mov edx, in_m + + ; assign back + fld dword [eax] + fstp dword [edx] + fld dword [eax+4] + fstp dword [edx+4] + fld dword [eax+8] + fstp dword [edx+8] + + fld dword [eax+16] + fstp dword [edx+16] + fld dword [eax+20] + fstp dword [edx+20] + fld dword [eax+24] + fstp dword [edx+24] + + fld dword [eax+32] + fstp dword [edx+32] + fld dword [eax+36] + fstp dword [edx+36] + fld dword [eax+40] + fstp dword [edx+40] + + ; invert the translation + fld dword [edx+12] + fchs + fstp dword [eax] + fld dword [edx+28] + fchs + fstp dword [eax+4] + fld dword [edx+44] + fchs + fstp dword [eax+8] + + mov ecx, eax + add ecx, 16 + push dword ecx + push dword eax + push edx + call m_matF_x_vectorF_ASM + add esp, byte 12 + mov eax, array + mov edx, in_m + + fld dword [eax+16] + fstp dword [edx+12] + fld dword [eax+20] + fstp dword [edx+28] + fld dword [eax+24] + fstp dword [edx+44] + + pop ebp + ret + +; +; void m_matF_affineInverse_ASM( F32* m ) +; + +%define in_m [ebp+8] + +global m_matF_affineInverse_ASM + +m_matF_affineInverse_ASM: + + push ebp + mov ebp, esp + + mov eax, array + mov edx, in_m + + ; copy to temp + fld dword [edx] + fstp dword [eax] + + fld dword [edx+4] + fstp dword [eax+4] + + fld dword [edx+8] + fstp dword [eax+8] + + fld dword [edx+12] + fstp dword [eax+12] + + fld dword [edx+16] + fstp dword [eax+16] + + fld dword [edx+20] + fstp dword [eax+20] + + fld dword [edx+24] + fstp dword [eax+24] + + fld dword [edx+28] + fstp dword [eax+28] + + fld dword [edx+32] + fstp dword [eax+32] + + fld dword [edx+36] + fstp dword [eax+36] + + fld dword [edx+40] + fstp dword [eax+40] + + fld dword [edx+44] + fstp dword [eax+44] + + fld dword [edx+48] + fstp dword [eax+48] + + fld dword [edx+52] + fstp dword [eax+52] + + fld dword [edx+56] + fstp dword [eax+56] + + fld dword [edx+60] + fstp dword [eax+60] + + ; switcheroos + fld dword [eax+16] + fstp dword [edx+4] + fld dword [eax+4] + fstp dword [edx+16] + fld dword [eax+32] + fstp dword [edx+8] + fld dword [eax+8] + fstp dword [edx+32] + fld dword [eax+36] + fstp dword [edx+24] + fld dword [eax+24] + fstp dword [edx+36] + + ; m[3] = -(temp[0]*temp[3] + temp[4]*temp[7] + temp[8]*temp[11]); + fld dword [eax+0] + fld dword [eax+16] + fmul dword [eax+28] + fxch + fmul dword [eax+12] + fld dword [eax+32] + fmul dword [eax+44] + fxch st2 + faddp st1, st0 + faddp st1, st0 + fchs + fstp dword [edx+12] + + ; m[7] = -(temp[1]*temp[3] + temp[5]*temp[7] + temp[9]*temp[11]); + fld dword [eax+4] + fld dword [eax+20] + fmul dword [eax+28] + fxch + fmul dword [eax+12] + fld dword [eax+36] + fmul dword [eax+44] + fxch st2 + faddp st1, st0 + faddp st1, st0 + fchs + fstp dword [edx+28] + + ; m[11] = -(temp[2]*temp[3] + temp[6]*temp[7] + temp[10]*temp[11]); + fld dword [eax+8] + fld dword [eax+24] + fmul dword [eax+28] + fxch + fmul dword [eax+12] + fld dword [eax+40] + fmul dword [eax+44] + fxch st2 + faddp st1, st0 + faddp st1, st0 + fchs + fstp dword [edx+44] + + pop ebp + ret + +; +; void m_matF_x_matF_ASM( const F32* a, const F32* b, F32* mresult ) +; + +%define in_a [ebp+8] +%define in_b [ebp+12] +%define in_m [ebp+16] + +global m_matF_x_matF_ASM + +m_matF_x_matF_ASM: + + push ebp + mov ebp, esp + + mov edx, in_a + mov eax, in_b + mov ecx, in_m + + ; mresult[0] = a[0]*b[0] + a[1]*b[4] + a[2]*b[8] + a[3]*b[12]; + fld dword [edx] + fld dword [edx+4] + fxch + fmul dword [eax] + fxch + fmul dword [eax+16] + faddp st1, st0 + + fld dword [edx+8] + fmul dword [eax+32] + faddp st1, st0 + + fld dword [edx+12] + fmul dword [eax+48] + faddp st1, st0 + + fstp dword [ecx] + + ; mresult[1] = a[0]*b[1] + a[1]*b[5] + a[2]*b[9] + a[3]*b[13]; + fld dword [edx] + fld dword [edx+4] + fxch + fmul dword [eax+4] + fxch + fmul dword [eax+20] + faddp st1, st0 + + fld dword [edx+8] + fmul dword [eax+36] + faddp st1, st0 + + fld dword [edx+12] + fmul dword [eax+52] + faddp st1, st0 + + fstp dword [ecx+4] + + ; mresult[2] = a[0]*b[2] + a[1]*b[6] + a[2]*b[10] + a[3]*b[14]; + fld dword [edx] + fld dword [edx+4] + fxch + fmul dword [eax+8] + fxch + fmul dword [eax+24] + faddp st1, st0 + + fld dword [edx+8] + fmul dword [eax+40] + faddp st1, st0 + + fld dword [edx+12] + fmul dword [eax+56] + faddp st1, st0 + + fstp dword [ecx+8] + + ; mresult[3] = a[0]*b[3] + a[1]*b[7] + a[2]*b[11] + a[3]*b[15]; + fld dword [edx] + fld dword [edx+4] + fxch + fmul dword [eax+12] + fxch + fmul dword [eax+28] + faddp st1, st0 + + fld dword [edx+8] + fmul dword [eax+44] + faddp st1, st0 + + fld dword [edx+12] + fmul dword [eax+60] + faddp st1, st0 + + fstp dword [ecx+12] + + ; mresult[4] = a[4]*b[0] + a[5]*b[4] + a[6]*b[8] + a[7]*b[12]; + fld dword [edx+16] + fld dword [edx+20] + fxch + fmul dword [eax] + fxch + fmul dword [eax+16] + faddp st1, st0 + + fld dword [edx+24] + fmul dword [eax+32] + faddp st1, st0 + + fld dword [edx+28] + fmul dword [eax+48] + faddp st1, st0 + + fstp dword [ecx+16] + + ; mresult[5] = a[4]*b[1] + a[5]*b[5] + a[6]*b[9] + a[7]*b[13]; + fld dword [edx+16] + fld dword [edx+20] + fxch + fmul dword [eax+4] + fxch + fmul dword [eax+20] + faddp st1, st0 + + fld dword [edx+24] + fmul dword [eax+36] + faddp st1, st0 + + fld dword [edx+28] + fmul dword [eax+52] + faddp st1, st0 + + fstp dword [ecx+20] + + ; mresult[6] = a[4]*b[2] + a[5]*b[6] + a[6]*b[10] + a[7]*b[14]; + fld dword [edx+16] + fld dword [edx+20] + fxch + fmul dword [eax+8] + fxch + fmul dword [eax+24] + faddp st1, st0 + + fld dword [edx+24] + fmul dword [eax+40] + faddp st1, st0 + + fld dword [edx+28] + fmul dword [eax+56] + faddp st1, st0 + + fstp dword [ecx+24] + + ; mresult[7] = a[4]*b[3] + a[5]*b[7] + a[6]*b[11] + a[7]*b[15]; + fld dword [edx+16] + fld dword [edx+20] + fxch + fmul dword [eax+12] + fxch + fmul dword [eax+28] + faddp st1, st0 + + fld dword [edx+24] + fmul dword [eax+44] + faddp st1, st0 + + fld dword [edx+28] + fmul dword [eax+60] + faddp st1, st0 + + fstp dword [ecx+28] + + ; mresult[8] = a[8]*b[0] + a[9]*b[4] + a[10]*b[8] + a[11]*b[12]; + fld dword [edx+32] + fld dword [edx+36] + fxch + fmul dword [eax] + fxch + fmul dword [eax+16] + faddp st1, st0 + + fld dword [edx+40] + fmul dword [eax+32] + faddp st1, st0 + + fld dword [edx+44] + fmul dword [eax+48] + faddp st1, st0 + + fstp dword [ecx+32] + + ; mresult[9] = a[8]*b[1] + a[9]*b[5] + a[10]*b[9] + a[11]*b[13]; + fld dword [edx+32] + fld dword [edx+36] + fxch + fmul dword [eax+4] + fxch + fmul dword [eax+20] + faddp st1, st0 + + fld dword [edx+40] + fmul dword [eax+36] + faddp st1, st0 + + fld dword [edx+44] + fmul dword [eax+52] + faddp st1, st0 + + fstp dword [ecx+36] + + ; mresult[10]= a[8]*b[2] + a[9]*b[6] + a[10]*b[10] + a[11]*b[14]; + fld dword [edx+32] + fld dword [edx+36] + fxch + fmul dword [eax+8] + fxch + fmul dword [eax+24] + faddp st1, st0 + + fld dword [edx+40] + fmul dword [eax+40] + faddp st1, st0 + + fld dword [edx+44] + fmul dword [eax+56] + faddp st1, st0 + + fstp dword [ecx+40] + + ; mresult[11]= a[8]*b[3] + a[9]*b[7] + a[10]*b[11] + a[11]*b[15]; + fld dword [edx+32] + fld dword [edx+36] + fxch + fmul dword [eax+12] + fxch + fmul dword [eax+28] + faddp st1, st0 + + fld dword [edx+40] + fmul dword [eax+44] + faddp st1, st0 + + fld dword [edx+44] + fmul dword [eax+60] + faddp st1, st0 + + fstp dword [ecx+44] + + ; mresult[12]= a[12]*b[0] + a[13]*b[4] + a[14]*b[8] + a[15]*b[12]; + fld dword [edx+48] + fld dword [edx+52] + fxch + fmul dword [eax] + fxch + fmul dword [eax+16] + faddp st1, st0 + + fld dword [edx+56] + fmul dword [eax+32] + faddp st1, st0 + + fld dword [edx+60] + fmul dword [eax+48] + faddp st1, st0 + + fstp dword [ecx+48] + + ; mresult[13]= a[12]*b[1] + a[13]*b[5] + a[14]*b[9] + a[15]*b[13]; + fld dword [edx+48] + fld dword [edx+52] + fxch + fmul dword [eax+4] + fxch + fmul dword [eax+20] + faddp st1, st0 + + fld dword [edx+56] + fmul dword [eax+36] + faddp st1, st0 + + fld dword [edx+60] + fmul dword [eax+52] + faddp st1, st0 + + fstp dword [ecx+52] + + ; mresult[14]= a[12]*b[2] + a[13]*b[6] + a[14]*b[10] + a[15]*b[14]; + fld dword [edx+48] + fld dword [edx+52] + fxch + fmul dword [eax+8] + fxch + fmul dword [eax+24] + faddp st1, st0 + + fld dword [edx+56] + fmul dword [eax+40] + faddp st1, st0 + + fld dword [edx+60] + fmul dword [eax+56] + faddp st1, st0 + + fstp dword [ecx+56] + + ; mresult[15]= a[12]*b[3] + a[13]*b[7] + a[14]*b[11] + a[15]*b[15]; + fld dword [edx+48] + fld dword [edx+52] + fxch + fmul dword [eax+12] + fxch + fmul dword [eax+28] + faddp st1, st0 + + fld dword [edx+56] + fmul dword [eax+44] + faddp st1, st0 + + fld dword [edx+60] + fmul dword [eax+60] + faddp st1, st0 + + fstp dword [ecx+60] + + pop ebp + ret diff --git a/platformLinux/linuxMath_VC.c b/platformLinux/linuxMath_VC.c new file mode 100644 index 0000000..9222b24 --- /dev/null +++ b/platformLinux/linuxMath_VC.c @@ -0,0 +1,1145 @@ + +/* + Sigh, we're going to get around the stupid gcc math bugs by compiling + this code with Visual C++ and then using the object file in our code. + + HACK HACK HACK +*/ + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define TRIBES2_NEW_MATH + +#if defined(_MSC_VER) +#define __inline__ __inline +#else +#define __inline__ inline +#endif + +typedef unsigned char U8; +typedef signed int S32; +typedef unsigned int U32; +typedef float F32; +typedef double F64; + +/* Utility routines */ + +static __inline__ void* dMemcpy(void *dst, const void *src, unsigned size) +{ + return memcpy(dst,src,size); +} + +static __inline__ F32 getMin(F32 a, F32 b) +{ + return a>b ? b : a; +} + +static __inline__ F32 getMax(F32 a, F32 b) +{ + return a>b ? a : b; +} + +static __inline__ F32 mClampF(F32 val, F32 low, F32 high) +{ + return getMax(getMin(val, high), low); +} + +static __inline__ F32 mFabs(const F32 val) +{ + return fabs(val); +} + +static __inline__ F32 mSin(const F32 angle) +{ + return sin(angle); +} + +static __inline__ F32 mCos(const F32 angle) +{ + return cos(angle); +} + +#define mSinCos(angle, s, c) \ +{ \ + s = mSin(angle); \ + c = mCos(angle); \ +} + +static __inline__ F32 mAcos(const F32 val) +{ +#if defined(_MSC_VER) + extern double ACOS(double X); + return ACOS(val); +#else + return acos(val); +#endif +} + +static __inline__ F32 mSqrt(const F32 val) +{ + return sqrt(val); +} + +static __inline__ F64 mSqrtD(const F64 val) +{ + return sqrt(val); +} + +static __inline__ F32 mPow(const F32 x, const F32 y) +{ +#if defined(_MSC_VER) + extern double POW(double X, double Y); + return POW(x, y); +#else + return pow(x, y); +#endif +} + +#define swap(x, y) { F32 t = y; y = x; x = t; } + +static __inline__ F32 square( F32 d ) +{ + return ( d * d ); +} + +static __inline__ F32 cube( F32 d ) +{ + return ( d * d * d ); +} + + +/* The actual math routines */ + +void m_matF_x_point3F_ASM(const F32 *m, const F32 *p, F32 *presult) +{ + 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]; +} + +void m_matF_x_vectorF_ASM(const F32 *m, const F32 *v, F32 *vresult) +{ + 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]; +} + +void m_quatF_set_matF_ASM( 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 +} + +F32 m_matF_determinant_ASM(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]) ; +} + +void m_matF_inverse_ASM(F32 *m) +{ + // using Cramers Rule find the Inverse + // Minv = (1/det(M)) * adjoint(M) + F32 det = m_matF_determinant_ASM( m ); + + 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_ASM(m, temp, &temp[4]); + m[3] = temp[4]; + m[7] = temp[5]; + m[11]= temp[6]; +} + +void m_matF_affineInverse_ASM(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]); +} + +void m_matF_x_matF_ASM(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]; +} + +#ifdef TRIBES2_NEW_MATH + +// Code taken from Math/mSolver.cc + +//-------------------------------------------------------------------------- +#define EQN_EPSILON (1e-8) + +static __inline__ int mIsZero(F32 val) +{ + return((val > -EQN_EPSILON) && (val < EQN_EPSILON)); +} + +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); +} + +U32 mSolveQuadratic_ASM(F32 a, F32 b, F32 c, F32 * x) +{ + F32 desc; + + // really linear? + if(mIsZero(a)) + return(mSolveLinear(b, c, x)); + + // get the descriminant: (b^2 - 4ac) + 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_ASM(F32 a, F32 b, F32 c, F32 d, F32 * x) +{ + F32 A, B, C, D; + F32 A2, A3; + F32 p, q; + F32 p3, q2; + U32 num; + F32 sub; + U32 i; + S32 j, k; + + if(mIsZero(a)) + return(mSolveQuadratic_ASM(b, c, d, x)); + + // normal form: x^3 + Ax^2 + BX + C = 0 + A = b / a; + B = c / a; + C = d / a; + + // substitute x = y - A/3 to eliminate quadric term and depress + // the cubic equation to (x^3 + px + q = 0) + A2 = A * A; + A3 = A2 * A; + + p = (1.f/3.f) * (((-1.f/3.f) * A2) + B); + 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 + p3 = p * p * p; + q2 = q * q; + + D = q2 + p3; + + 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 + sub = (1.f/3.f) * A; + for(i = 0; i < num; i++) + x[i] -= sub; + + // sort the roots + for(j = 0; j < (num - 1); j++) + for(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_ASM(F32 a, F32 b, F32 c, F32 d, F32 e, F32 * x) +{ + F32 A, B, C, D; + F32 A2, A3, A4; + F32 p, q, r; + U32 num; + F32 sub; + U32 i; + S32 j, k; + + if(mIsZero(a)) + return(mSolveCubic_ASM(b, c, d, e, x)); + + // normal form: x^4 + ax^3 + bx^2 + cx + d = 0 + A = b / a; + B = c / a; + C = d / a; + D = e / a; + + // substitue x = y - A/4 to eliminate cubic term: + // x^4 + px^2 + qx + r = 0 + A2 = A * A; + A3 = A2 * A; + A4 = A2 * A2; + + p = ((-3.f/8.f) * A2) + B; + q = ((1.f/8.f) * A3) - ((1.f/2.f) * A * B) + C; + r = ((-3.f/256.f) * A4) + ((1.f/16.f) * A2 * B) - ((1.f/4.f) * A * C) + D; + + num = 0; + if(mIsZero(r)) // no absolute term: y(y^3 + py + q) = 0 + { + num = mSolveCubic_ASM(1.f, 0.f, p, q, x); + x[num++] = 0.f; + } + else + { + F32 q2; + F32 z; + F32 u, v; + + // solve the resolvent cubic + 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_ASM(a, b, c, d, x); + + z = x[0]; + + // build 2 quadratic equations from the one solution + u = (z * z) - r; + 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_ASM(a, b, c, x); + + a = 1.f; + b = -v; + c = z + u; + num += mSolveQuadratic_ASM(a, b, c, x + num); + } + + // resubstitute + sub = (1.f/4.f) * A; + for(i = 0; i < num; i++) + x[i] -= sub; + + // sort the roots + for(j = 0; j < (num - 1); j++) + for(k = j + 1; k < num; k++) + if(x[k] < x[j]) + swap(x[k], x[j]); + + return(num); +} + +#else + +/* Old cubic and quadratic math functions */ + +U32 mSolveCubic_ASM(F32 A, F32 B, F32 C, F32 D, F32* x) +{ + int i; + U32 nreal; + F32 w, p, q, qp, dis, h, phi, phip; + F32 AP, BP, CP, DP, XP; + + if( A != 0.0f ) { + // is it a cubic? + w = B / ( 3.0f * A ); + CP = C / ( 3.0f * A ); + p = cube( CP - square( w ) ); + CP = ( C * w ) - D; + DP = CP / A; + CP = 2.0f * cube( w ); + q = -0.5f *( CP - DP ); + dis = square( q ) + p; // discriminant + + if( dis < 0.0 ) { + // 3 real solutions + h = q / mSqrt( -p ); + h = mClampF( h, -1.0f, 1.0f ); + phi = mAcos( h ); + p = 2.0f * mPow( -p, ( 1.0f / 6.0f ) ); + + for( i = 0; i < 3; i++ ) { + //x[i] = p*cos((phi+2*i*M_PI)/3.0) - w; + phip = ( 2.0f * i ) * M_PI; + phip = ( phip + phi ) / 3.0f; + x[i] = ( p * mCos( phip ) ) - w; + } + + // sort results + if( x[1] < x[0] ) { + swap( x[0], x[1] ); + } + + if( x[2] < x[1] ) { + swap( x[1], x[2] ); + } + + if( x[1] < x[0] ) { + swap( x[0], x[1] ); + } + + nreal = 3; + } else { + // only one real solution + dis = mSqrt( dis ); + //h = pow(fabs(q+dis), 1.f/3.f); + //p = pow(fabs(q-dis), 1.f/3.f); + qp = mFabs( q + dis ); + h = mPow( qp, ( 1.0f / 3.0f ) ); + qp = mFabs( q - dis ); + p = mPow( qp, ( 1.0f / 3.0f ) ); + + //x[0] = ((q+dis>0.0)? h : -h) + ((q-dis>0.0)? p : -p) - w; + if( ( q + dis ) <= 0.0f ) { + h = -h; + } + + if( ( q - dis ) <= 0.0f ) { + p = -p; + } + + x[0] = ( h + p ) - w; + nreal = 1; + } + + // Perform one step of a Newton iteration in order to minimize + // round-off errors + for (i = 0; i < nreal; i++) + { + //if ((h = C + x[i] * (2 * B + 3 * x[i] * A)) != 0.0) { + AP = x[i] * 3.0f * A; + BP = 2.0f * B; + XP = x[i] * ( BP + AP ); + CP = C + XP; + + h = CP; + + if ( h != 0.0f ) { + //x[i] -= (D + x[i] * (C + x[i] * (B + x[i] * A)))/h; + AP = x[i] * A; + BP = B + AP; + XP = x[i] * BP; + CP = C + XP; + XP = x[i] * CP; + DP = D + XP; + DP = DP / h; + x[i] = x[i] - DP; + } + + } + + } else if( B != 0.0f ) { + // is it a quadratic? + p = 0.5f * ( C / B ); + dis = square( p ) - ( D / B ); + + if( dis >= 0.0 ) { + // are there solutions? + dis = mSqrt( dis ); + x[0] = -p - dis; + x[1] = -p + dis; + nreal = 2; + } else { + // no real solution + nreal = 0; + } + + } else if( C != 0.0f ) { + // is it a linear? + x[0] = -D / C; + nreal = 1; + } else { + // not an equation + nreal = 0; + } + + return nreal; +} + +U32 mSolveQuartic_ASM( F32 A, F32 B, F32 C, F32 D, F32 E, F32* x ) +{ + int i; + U32 nreal; + F32 w, b0, b1, b2, d0, d1, d2, h, hw, t, z; + F32* px; + F32 w2; + F32 CA, DA, EA; + F32 CP, DP; + F32 zp; + F32 tp; + + // check if it's a cubic. + if( A == 0.0f ) { + return mSolveCubic_ASM( B, C, D, E, x ); + } + + //w = B/(4*A); // offset + w = B / ( 4.0f * A ); + w2 = square( w ); + + //b2 = -6*square(w) + C/A; // coeffs. of shifted polynomial + CA = C / A; + b2 = ( -6.0f * w2 ) + CA; + + //b1 = (8*square(w) - 2*C/A)*w + D/A; + DA = D / A; + CP = 2.0f * CA; + CP = ( 8.0f * w2 ) - CP; + b1 = ( CP * w ) + DA; + + //b0 = ((-3*square(w) + C/A)*w - D/A)*w + E/A; + EA = E / A; + CP = ( -3.0f * w2 ) + CA; + DP = ( CP * w ) - DA; + b0 = ( DP * w ) + EA; + + // cubic resolvent + { + F32 cubeA = 1.0; + F32 cubeB = b2; + F32 cubeC = -4 * b0; + F32 bp = 4 * ( b0 * b2 ); + F32 cubeD = square( b1 ) - bp; + + // only lowermost root needed + mSolveCubic_ASM( cubeA, cubeB, cubeC, cubeD, x ); + } + + z = x[0]; + + nreal = 0; + px = x; + zp = 0.25f * square( z ); + t = mSqrt( zp - b0 ); + + for( i = -1; i <= 1; i += 2 ) { + //d0 = -0.5*z + i*t; + d0 = ( -0.5 * z ) + ( i * t ); // coeffs. of quadratic factor + + //d1 = (t!=0.0)? -i*0.5*b1/t : i*mSqrt(-z - b2); + if( t != 0.0f ) { + tp = -i * 0.5f; + tp = tp * b1; + tp = tp / t; + } else { + tp = mSqrt( -z - b2 ); + tp = i * tp; + } + + d1 = tp; + h = ( 0.25f * square( d1 ) ) - d0; + + if( h >= 0.0f ) { + h = mSqrt( h ); + nreal += 2; + + //*px++ = -0.5*d1 - h - w; + //*px++ = -0.5*d1 + h - w; + d2 = -0.5f * d1; + hw = h - w; + *px = d2 - hw; + px++; + *px = d2 + hw; + px++; + } + + } + + // sort results in ascending order + if( nreal == 4 ) { + + if( x[2] < x[0] ) { + swap( x[0], x[2] ); + } + + if( x[3] < x[1] ) { + swap( x[1], x[3] ); + } + + if( x[1] < x[0] ) { + swap( x[0], x[1] ); + } + + if( x[3] < x[2] ) { + swap( x[2], x[3] ); + } + + if( x[2] < x[1] ) { + swap( x[1], x[2] ); + } + + } + + return nreal; +} + +#endif /* TRIBES2_NEW_MATH */ + +/* Added lots more math functions optimized by Visual C++ */ + +//-------------------------------------- +void m_point2F_normalize_ASM(F32 *p) +{ + F32 factor = 1.0f / mSqrt(p[0]*p[0] + p[1]*p[1] ); + p[0] *= factor; + p[1] *= factor; +} + +//-------------------------------------- +void m_point2F_normalize_f_ASM(F32 *p, F32 val) +{ + F32 factor = val / mSqrt(p[0]*p[0] + p[1]*p[1] ); + p[0] *= factor; + p[1] *= factor; +} + +//-------------------------------------- +void m_point2D_normalize_ASM(F64 *p) +{ + F64 factor = 1.0f / mSqrtD(p[0]*p[0] + p[1]*p[1] ); + p[0] *= factor; + p[1] *= factor; +} + +//-------------------------------------- +void m_point2D_normalize_f_ASM(F64 *p, F64 val) +{ + F64 factor = val / mSqrtD(p[0]*p[0] + p[1]*p[1] ); + p[0] *= factor; + p[1] *= factor; +} + +//-------------------------------------- +void m_point3F_normalize_ASM(F32 *p) +{ + F32 squared = p[0]*p[0] + p[1]*p[1] + p[2]*p[2]; + if (squared != 0.0) { + F32 factor = 1.0f / mSqrt(squared); + p[0] *= factor; + p[1] *= factor; + p[2] *= factor; + } else { + p[0] = 0; + p[1] = 0; + p[2] = 1; + } +} + +//-------------------------------------- +void m_point3F_normalize_f_ASM(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; +} + +//-------------------------------------- +void m_point3F_interpolate_ASM(const F32 *from, const F32 *to, F32 factor, F32 *result ) +{ + 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; +} + +//-------------------------------------- +void m_point3D_normalize_ASM(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; +} + + +//-------------------------------------- +void m_point3D_interpolate_ASM(const F64 *from, const F64 *to, F64 factor, F64 *result ) +{ + 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; +} + +void m_point3F_bulk_dot_ASM(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + F32* output) +{ + U32 i; + for (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_ASM(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + const U32* pointIndices, + F32* output) +{ + U32 i; + for (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])); + } +} + +//-------------------------------------- +void m_matF_identity_ASM(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; +} + +//-------------------------------------- +void m_matF_set_euler_ASM(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_ASM(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; + F32 r1, r2, r3, r4; + mSinCos( e[2], sz, cz ); + r1 = cz; + r2 = sz; + r3 = 0.0f; + r4 = 0.0f; + + 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; + F32 cy,sy; + F32 cz,sz; + F32 r1, r2, r3, r4; + mSinCos( e[0], sx, cx ); + mSinCos( e[1], sy, cy ); + mSinCos( e[2], sz, cz ); + r1 = cy * cz; + r2 = cy * sz; + r3 = sy * cz; + 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; + } + } +} + + +//-------------------------------------- +void m_matF_set_euler_point_ASM(const F32 *e, const F32 *p, F32 *result) +{ + m_matF_set_euler_ASM(e, result); + result[3] = p[0]; + result[7] = p[1]; + result[11]= p[2]; +} + +//-------------------------------------- +void m_matF_transpose_ASM(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]); +} + +//-------------------------------------- +void m_matF_scale_ASM(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 __inline__ void mCross(const F32* a, const F32* b, F32 *res) +{ + res[0] = (a[1] * b[2]) - (a[2] * b[1]); + res[1] = (a[2] * b[0]) - (a[0] * b[2]); + res[2] = (a[0] * b[1]) - (a[1] * b[0]); +} + +//-------------------------------------- +void m_matF_normalize_ASM(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 relationsips to one another + mCross(col0, col1, col2); + mCross(col2, col0, col1); + + // assure their lengh is 1.0f + m_point3F_normalize_ASM( col0 ); + m_point3F_normalize_ASM( col1 ); + m_point3F_normalize_ASM( 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]; +} + +//-------------------------------------- +void m_matF_x_point4F_ASM(const F32 *m, const F32 *p, F32 *presult) +{ + 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]; +} diff --git a/platformLinux/linuxMemory.cc b/platformLinux/linuxMemory.cc new file mode 100644 index 0000000..8ca9aee --- /dev/null +++ b/platformLinux/linuxMemory.cc @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformLinux/platformLinux.h" + +#include +#include + +void* dMemcpy( void* d, const void* s, U32 n ) +{ + return memcpy( d, s, n ); +} + +void* dMemmove( void* d, const void* s, U32 n ) +{ + return memmove( d, s, n ); +} + +void* dMemset( void* d, S32 c, U32 n ) +{ + return memset( d, c, n ); +} + +S32 dMemcmp( const void* p1, const void* p2, U32 n ) +{ + return memcmp( p1, p2, n ); +} + +void* dRealMalloc( dsize_t n ) +{ + return malloc( n ); +} + +void dRealFree( void* p ) +{ + free( p ); +} diff --git a/platformLinux/linuxMutex.cc b/platformLinux/linuxMutex.cc new file mode 100644 index 0000000..148c889 --- /dev/null +++ b/platformLinux/linuxMutex.cc @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platformMutex.h" + +#include + +void* Mutex::createMutex( void ) +{ + pthread_mutex_t* mutex = new pthread_mutex_t; + + if( mutex == 0 ) { + return 0; + } + + pthread_mutexattr_t attr; + pthread_mutexattr_init( &attr ); + pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE_NP ); + + if( pthread_mutex_init( mutex, &attr ) != 0 ) { + delete mutex; + return 0; + } + + return mutex; +} + +void Mutex::destroyMutex( void* mutex ) +{ + + if( mutex ) { + pthread_mutex_destroy( (pthread_mutex_t*) mutex ); + delete mutex; + } + +} + +void Mutex::lockMutex( void* mutex ) +{ + + if( mutex ) { + pthread_mutex_lock( (pthread_mutex_t*) mutex ); + } + +} + +void Mutex::unlockMutex( void* mutex ) +{ + + if( mutex ) { + pthread_mutex_unlock( (pthread_mutex_t*) mutex ); + } + +} diff --git a/platformLinux/linuxNet.cc b/platformLinux/linuxNet.cc new file mode 100644 index 0000000..c43807e --- /dev/null +++ b/platformLinux/linuxNet.cc @@ -0,0 +1,738 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The ares library is a C API */ +extern "C" { +#define class ares_class +#include +#undef class +} + +#include "platformLinux/async.h" + +#include "platformLinux/platformLinux.h" +#include "Platform/platform.h" +#include "Platform/event.h" +#include "console/console.h" +#include "Platform/gameInterface.h" +#include "Core/fileStream.h" + + +// +// Local types +// +typedef struct { + int fd; + U16 port; +} NameLookup; + + +// +// Sundry module-local variables. +// +static ares_channel channel; +static U16 defaultPort = 28000; +static U16 netPort = 0; +static NetSocket udpSocket = -1; + + +// +// Our asychronous callbacks. +// +static int selectCallback( int fd, int event, int status ) +{ + static ConnectedNotifyEvent notifyEvent; + static ConnectedReceiveEvent receiveEvent; + static ConnectedAcceptEvent acceptEvent; + + int bytesRead = 0; + Net::Error error = Net::NoError; + + switch( event ) { + case FD_READ: + error = Net::recv( fd, receiveEvent.data, MaxPacketDataSize, &bytesRead ); + + if( error == Net::NoError & bytesRead != 0 ) { + receiveEvent.tag = fd; + receiveEvent.size = ConnectedReceiveEventHeaderSize + bytesRead; + Game->postEvent( receiveEvent ); + } + + break; + case FD_CONNECT: + notifyEvent.tag = fd; + + if( status ) { + notifyEvent.state = ConnectedNotifyEvent::Connected; + } else { + notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; + } + + Game->postEvent( notifyEvent ); + break; + case FD_CLOSE: + + for( ; ; ) { + error = Net::recv( fd, receiveEvent.data, MaxPacketDataSize, &bytesRead ); + + if( error != Net::NoError || bytesRead == 0 ) { + break; + } + + receiveEvent.tag = fd; + receiveEvent.size = ConnectedReceiveEventHeaderSize + bytesRead; + Game->postEvent( receiveEvent ); + } + + notifyEvent.tag = fd; + notifyEvent.state = ConnectedNotifyEvent::Disconnected; + Game->postEvent( notifyEvent ); + break; + case FD_ACCEPT: + acceptEvent.portTag = fd; + acceptEvent.connectionTag = Net::accept( fd, &acceptEvent.address ); + + if( acceptEvent.connectionTag != InvalidSocket ) { + Net::setBlocking( acceptEvent.connectionTag, false ); + AsyncSelect( acceptEvent.connectionTag, selectCallback, FD_READ | FD_CONNECT | FD_CLOSE ); + Game->postEvent( acceptEvent ); + } + + break; + } + +} + +static void lookupCallback( void* arg, int status, struct hostent* host ) +{ + static ConnectedNotifyEvent notifyEvent; + + char* errorMem = 0; + char** addr = 0; + NameLookup* lookup = static_cast( arg ); + + if( status != ARES_SUCCESS ) { + ares_strerror( status, &errorMem ); + Con::printf( "couldn't perform lookup: %s", errorMem ); + ares_free_errmem( errorMem ); + delete lookup; + return; + } + + struct sockaddr_in ipAddr; + + dMemcpy( &ipAddr.sin_addr.s_addr, host->h_addr_list[0], sizeof( struct in_addr ) ); + ipAddr.sin_port = lookup->port; + ipAddr.sin_family = AF_INET; + + notifyEvent.tag = lookup->fd; + + int error = ::connect( lookup->fd, (struct sockaddr*) &ipAddr, sizeof( ipAddr ) ); + + if( error == -1 && errno != EINPROGRESS ) { + notifyEvent.state = ConnectedNotifyEvent::DNSFailed; + ::close( lookup->fd ); + } else { + AsyncSelect( lookup->fd, selectCallback, FD_READ | FD_CONNECT | FD_CLOSE ); + notifyEvent.state = ConnectedNotifyEvent::DNSResolved; + } + + Game->postEvent( notifyEvent ); + delete lookup; +} + + +// +// Utilities. +// +static void netToIPSocketAddress( const NetAddress* addr, struct sockaddr_in* ip ) +{ + memset( ip, 0, sizeof( struct sockaddr_in ) ); + ip->sin_family = AF_INET; + ip->sin_port = htons( addr->port ); + ip->sin_addr.s_addr = *( (U32*) addr->netNum ); +} + +static void IPSocketToNetAddress( const sockaddr_in* ip, NetAddress* addr ) +{ + addr->type = NetAddress::IPAddress; + addr->port = htons( ip->sin_port ); + *( (U32*) addr->netNum ) = ip->sin_addr.s_addr; +} + +static Net::Error getLastError( void ) +{ + + // God knows what distinguishes Invalid from Wrong... + switch( errno ) { + case 0: + return Net::NoError; + case EBADF: + case ENOTSOCK: + return Net::NotASocket; + case EAGAIN: + return Net::WouldBlock; + case ENOTCONN: + return Net::InvalidPacketProtocol; + case EOPNOTSUPP: + return Net::WrongProtocolType; + default: + return Net::UnknownError; + } + +} + + +// +// Net:: implementation +// +bool Net::init( void ) +{ + int status; + char* errorMem; + + if( AsyncInit( ) == -1 ) { + Con::printf( "AsyncInit failed" ); + return false; + } + + status = ares_init( &channel ); + + if( status != ARES_SUCCESS ) { + Con::printf( "ares_init: %s", ares_strerror( status, &errorMem ) ); + ares_free_errmem( errorMem ); + return false; + } + + return true; +} + +void Net::shutdown( void ) +{ + AsyncShutdown( ); + ares_destroy( channel ); + closePort( ); +} + +NetSocket Net::openListenPort( U16 port ) +{ + + if( Game->isJournalReading( ) ) { + U32 ret; + Game->journalRead( &ret ); + return ret; + } + + NetSocket fd = openSocket( ); + bind( fd, port ); + listen( fd, 4 ); + setBlocking( fd, false ); + + if( AsyncSelect( fd, selectCallback, FD_ACCEPT ) == -1 ) { + Con::printf( "Error: %d", errno ); + } + + if( Game->isJournalWriting( ) ) { + Game->journalWrite( fd ); + } + + return fd; +} + +NetSocket Net::openConnectTo( const char* addressString ) +{ + + if( !dStrnicmp( addressString, "ip:", 3 ) ) { + addressString += 3; + } + + char remoteAddr[256]; + dStrncpy( remoteAddr, addressString, 256 ); + + 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 ret; + } + + NetSocket fd = openSocket( ); + setBlocking( fd, false ); + + struct sockaddr_in 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; + + int error = ::connect( fd, (struct sockaddr*) &ipAddr, sizeof( ipAddr ) ); + + if( error == -1 && errno != EINPROGRESS ) { + Con::printf( "Last error: %d", errno ); + ::close( fd ); + fd = InvalidSocket; + } + + if( fd != InvalidSocket ) { + AsyncSelect( fd, selectCallback, FD_READ | FD_CONNECT | FD_CLOSE ); + } + + } else { + NameLookup* lookup = new NameLookup; + lookup->fd = fd; + lookup->port = port; + ares_gethostbyname( channel, remoteAddr, AF_INET, lookupCallback, lookup ); + } + + if( Game->isJournalWriting( ) ) { + Game->journalWrite( fd ); + } + + return fd; +} + +void Net::closeConnectTo( NetSocket fd ) +{ + + if( Game->isJournalReading( ) ) { + return; + } + + AsyncCancel( fd ); + + ::close( fd ); +} + +Net::Error Net::sendTo( NetSocket fd, const U8* buffer, int size ) +{ + + if( Game->isJournalReading( ) ) { + U32 error; + Game->journalRead( &error ); + return static_cast( error ); + } + + Net::Error error = send( fd, buffer, size ); + + if( Game->isJournalWriting( ) ) { + Game->journalWrite( static_cast( error ) ); + } + + return error; +} + +bool Net::openPort( S32 port ) +{ + closePort( ); + + udpSocket = socket( AF_INET, SOCK_DGRAM, 0 ); + + if( udpSocket != -1 ) { + 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 = -1; + Con::printf( "Unable to initialize UDP -- error %d", error ); + } + + } + + netPort = port; + return ( udpSocket != -1 ); +} + +void Net::closePort( void ) +{ + + if( udpSocket != -1 ) { + close( udpSocket ); + } + +} + +Net::Error Net::sendto( const NetAddress* addr, const U8* buffer, S32 size ) +{ + + if( Game->isJournalReading( ) ) { + return NoError; + } + + struct sockaddr_in ip; + netToIPSocketAddress( addr, &ip ); + + if( ::sendto( udpSocket, buffer, size, 0, (struct sockaddr*) &ip, sizeof( ip ) ) == -1 ) { + return getLastError( ); + } + + return NoError; +} + +void Net::process( void ) +{ + PacketReceiveEvent event; + + for( ; ; ) { + struct sockaddr sa; + socklen_t length = sizeof( sa ); + S32 bytes = -1; + + if( udpSocket != -1 ) { + bytes = recvfrom( udpSocket, event.data, MaxPacketDataSize, 0, &sa, &length ); + } + + if( bytes == -1 ) { + break; + } + + if( sa.sa_family == AF_INET ) { + IPSocketToNetAddress( (struct sockaddr_in*) &sa, &event.sourceAddress ); + } else { + continue; + } + + NetAddress& addr = event.sourceAddress; + + if( addr.type == NetAddress::IPAddress && + addr.netNum[0] == 127 && addr.netNum[1] == 0 && + addr.netNum[1] == 0 && addr.netNum[2] == 1 && + addr.port == netPort ) { + continue; + } + + if( bytes <= 0 ) { + continue; + } + + event.size = PacketReceiveEventHeaderSize + bytes; + Game->postEvent( event ); + } + + // do ares DNS processing + fd_set read_fds, write_fds; + int nfds = 0; + struct timeval* timeout = 0; + struct timeval tv; + struct timezone tz; + + FD_ZERO( &read_fds ); + FD_ZERO( &write_fds ); + nfds = ares_fds( channel, &read_fds, &write_fds ); + + if( nfds == 0 ) { + return; + } + + timeout = ares_timeout( channel, 0, &tv ); + select( nfds, &read_fds, &write_fds, 0, timeout ); + ares_process( channel, &read_fds, &write_fds ); +} + +NetSocket Net::openSocket( void ) +{ + int fd; + + if( ( fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 ) { + return InvalidSocket; + } + + return fd; +} + +Net::Error Net::closeSocket( NetSocket fd ) +{ + + if( fd == InvalidSocket ) { + return NotASocket; + } + + if( close( fd ) ) { + return getLastError( ); + } + + return NoError; +} + +Net::Error Net::connect( NetSocket fd, const NetAddress* addr ) +{ + + if( addr->type != NetAddress::IPAddress ) { + return WrongProtocolType; + } + + struct sockaddr_in addrIn; + + netToIPSocketAddress( addr, &addrIn ); + + if( ::connect( fd, (struct sockaddr*) &addrIn, sizeof( addrIn ) ) ) { + return getLastError( ); + } + + return NoError; +} + +Net::Error Net::listen( NetSocket fd, S32 backlog ) +{ + + if( ::listen( fd, backlog ) ) { + return getLastError( ); + } + + return NoError; +} + +NetSocket Net::accept( NetSocket fd, NetAddress* addr ) +{ + struct sockaddr_in ip; + socklen_t length = sizeof( ip ); + + int ret = ::accept( fd, (struct sockaddr*) &ip, &length ); + + if( ret != -1 ) { + IPSocketToNetAddress( &ip, addr ); + return ret; + } + + return InvalidSocket; +} + +Net::Error Net::bind( NetSocket fd, U16 port ) +{ + struct sockaddr_in ip; + + dMemset( &ip, 0, sizeof( ip ) ); + ip.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( "Host::BindAddress" ); + // serverIP is guaranteed to be non-0. + AssertFatal( serverIP, "serverIP is NULL!" ); + + if( serverIP[0] != '\0' ) { + // we're not empty + ip.sin_addr.s_addr = inet_addr( serverIP ); + + if( ip.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 ); + ip.sin_addr.s_addr = INADDR_ANY; + } + + } else { + Con::printf( "Binding server port to default IP" ); + ip.sin_addr.s_addr = INADDR_ANY; + } + + ip.sin_port = htons( port ); + + if( ::bind( fd, (struct sockaddr*) &ip, sizeof( ip ) ) ) { + return getLastError( ); + } + + return NoError; +} + +Net::Error Net::setBufferSize( NetSocket fd, S32 size ) +{ + + if( setsockopt( fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof( size ) ) ) { + return getLastError( ); + } + + if( setsockopt( fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof( size ) ) ) { + return getLastError( ); + } + + return NoError; +} + +Net::Error Net::setBroadcast( NetSocket fd, bool broadcast ) +{ + S32 bc = broadcast; + + if( setsockopt( fd, SOL_SOCKET, SO_BROADCAST, &bc, sizeof( bc ) ) ) { + return getLastError( ); + } + + return NoError; +} + +Net::Error Net::setBlocking( NetSocket fd, bool blocking ) +{ + int flags; + + // Let's do it the Posix.1g way! + if( ( flags = fcntl( fd, F_GETFL, 0 ) ) < 0 ) { + return getLastError( ); + } + + if( blocking ) { + flags &= ~O_NONBLOCK; + } else { + flags |= O_NONBLOCK; + } + + if( fcntl( fd, F_SETFL, flags ) < 0 ) { + return getLastError( ); + } + + return NoError; +} + +Net::Error Net::send( NetSocket fd, const U8* buffer, S32 size ) +{ + + if( ::send( fd, buffer, size, 0 ) == -1 ) { + return getLastError( ); + } + + return NoError; +} + +Net::Error Net::recv( NetSocket fd, U8* buffer, S32 size, S32* read ) +{ + + if( ( *read = ::recv( fd, buffer, size, 0 ) ) == -1 ) { + return getLastError( ); + } + + return NoError; +} + +bool Net::compareAddresses( const NetAddress* a1, const NetAddress* a2 ) +{ + // cast our separated ipv4 into a 32-bit word for compare + bool sameType = ( a1->type == a2->type ); + bool sameNum = ( *( (U32*) a1->netNum ) == *( (U32*) a2->netNum ) ); + bool samePort = ( a1->port == a2->port ); + return ( sameType && sameNum && samePort ); +} + +bool Net::stringToAddress( const char* addressString, NetAddress* address ) +{ + + if( dStrnicmp( addressString, "ip:", 3 ) == 0 ) { + addressString += 3; + } + + if( dStrnicmp( addressString, "ipx:", 4 ) == 0 ) { + // foo on IPX. + return false; + } + + if( dStrlen( addressString ) > 255 ) { + return false; + } + + struct sockaddr_in ipAddr; + char remoteAddr[256]; + dStrcpy( remoteAddr, addressString ); + char* portString = dStrchr( remoteAddr, ':' ); + + if( portString ) { + *portString++ = 0; + } + + struct hostent* he = 0; + + if( dStricmp( remoteAddr, "broadcast" ) == 0 ) { + 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 ) { + + if( ( he == gethostbyname( remoteAddr ) ) == NULL ) { + return false; + } else { + memcpy( &ipAddr.sin_addr.s_addr, he->h_addr, sizeof( struct 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; +} + +void Net::addressToString( const NetAddress* address, char addressString[256] ) +{ + + if( address->type == NetAddress::IPAddress ) { + struct 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:%d.%d.%d.%d:%d", + ( ipAddr.sin_addr.s_addr ) & 0xff, + ( ipAddr.sin_addr.s_addr >> 8 ) & 0xff, + ( ipAddr.sin_addr.s_addr >> 16 ) & 0xff, + ( ipAddr.sin_addr.s_addr >> 24 ) & 0xff, + ntohs( ipAddr.sin_port ) ); + } + + } + +} diff --git a/platformLinux/linuxOGLVideo.cc b/platformLinux/linuxOGLVideo.cc new file mode 100644 index 0000000..4f114d9 --- /dev/null +++ b/platformLinux/linuxOGLVideo.cc @@ -0,0 +1,612 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef DEDICATED + +#include +#include + +#include + +#include "PlatformWin32/platformGL.h" +#include "platformLinux/platformLinux.h" +#include "Platform/platformAudio.h" +#include "platformLinux/linuxOGLVideo.h" +#include "console/console.h" +#include "Math/mPoint.h" +#include "Platform/event.h" +#include "Platform/gameInterface.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "Core/fileStream.h" + +//------------------------------------------------------------------------------ + +struct CardProfile +{ + const char *vendor; // manufacturer + const char *renderer; // driver name + + bool safeMode; // destroy rendering context for resolution change + bool lockArray; // allow compiled vertex arrays + bool subImage; // allow glTexSubImage* + bool fogTexture; // require bound texture for combine extension + bool noEnvColor; // no texture environment color + bool clipHigh; // clip high resolutions + bool deleteContext; // delete rendering context + bool texCompress; // allow texture compression + bool interiorLock; // lock arrays for Interior render + bool skipFirstFog; // skip first two-pass fogging (dumb 3Dfx hack) + bool only16; // inhibit 32-bit resolutions + bool noArraysAlpha; // don't use glDrawArrays with a GL_ALPHA texture + + const char *proFile; // explicit profile of graphic settings +}; + +static Vector sCardProfiles(__FILE__, __LINE__); + +struct ProcessorProfile +{ + U16 clock; // clock range max + U16 adjust; // CPU adjust +}; + +static U8 sNumProcessors = 4; +static ProcessorProfile sProcessorProfiles[] = +{ + { 400, 0 }, + { 600, 5 }, + { 800, 10 }, + { 1000, 15 }, +}; + +struct SettingProfile +{ + U16 performance; // metric range max + const char *settings; // default file +}; + +static U8 sNumSettings = 3; +static SettingProfile sSettingProfiles[] = +{ + { 33, "LowProfile.cs" }, + { 66, "MediumProfile.cs" }, + { 100, "HighProfile.cs" }, +}; + +//------------------------------------------------------------------------------ + +static void cAddCardProfile(SimObject *, S32, const char **argv) +{ + CardProfile profile; + + profile.vendor = dStrdup(argv[1]); + profile.renderer = dStrdup(argv[2]); + + profile.safeMode = dAtob(argv[3]); + profile.lockArray = dAtob(argv[4]); + profile.subImage = dAtob(argv[5]); + profile.fogTexture = dAtob(argv[6]); + profile.noEnvColor = dAtob(argv[7]); + profile.clipHigh = dAtob(argv[8]); + profile.deleteContext = dAtob(argv[9]); + profile.texCompress = dAtob(argv[10]); + profile.interiorLock = dAtob(argv[11]); + profile.skipFirstFog = dAtob(argv[12]); + profile.only16 = dAtob(argv[13]); + profile.noArraysAlpha = dAtob(argv[14]); + + if (strcmp(argv[15],"")) + profile.proFile = dStrdup(argv[15]); + else + profile.proFile = NULL; + + sCardProfiles.push_back(profile); +} + +static void clearCardProfiles() +{ + while (sCardProfiles.size()) + { + dFree((char *) sCardProfiles.last().vendor); + dFree((char *) sCardProfiles.last().renderer); + + dFree((char *) sCardProfiles.last().proFile); + + sCardProfiles.decrement(); + } +} + +static void execScript(const char *scriptFile) +{ + // execute the script + FileStream str; + + if (!str.open(scriptFile, FileStream::Read)) + return; + + U32 size = str.getStreamSize(); + char *script = new char[size + 1]; + + str.read(size, script); + str.close(); + + script[size] = 0; + Con::executef(2, "eval", script); + delete[] script; +} + +static void profileSystem(const char *vendor, const char *renderer) +{ + Con::addCommand("addCardProfile", cAddCardProfile, "addCardProfile(vendor,renderer,safeMode,lockArray,subImage,fogTexture,noEnvColor,clipHigh,deleteContext,texCompress,interiorLock,skipFirstFog,only16,noArraysAlpha,proFile);", 16, 16); + + execScript("CardProfiles.cs"); + + const char *arch = ""; + const char *os = "Linux"; + char osProfiles[64]; + + if ( os != NULL ) + { + dSprintf(osProfiles,64,"%s%sCardProfiles.cs",arch,os); + //Con::executef(2, "exec", osProfiles); + execScript(osProfiles); + } + + const char *proFile = NULL; + U32 i; + + for (i = 0; i < sCardProfiles.size(); ++i) + if (dStrstr(vendor, sCardProfiles[i].vendor) && + (!dStrcmp(sCardProfiles[i].renderer, "*") || + dStrstr(renderer, sCardProfiles[i].renderer))) + { + Con::setBoolVariable("$pref::Video::safeModeOn", sCardProfiles[i].safeMode); + Con::setBoolVariable("$pref::OpenGL::disableEXTCompiledVertexArray", !sCardProfiles[i].lockArray); + Con::setBoolVariable("$pref::OpenGL::disableSubImage", !sCardProfiles[i].subImage); + Con::setBoolVariable("$pref::TS::fogTexture", sCardProfiles[i].fogTexture); + Con::setBoolVariable("$pref::OpenGL::noEnvColor", sCardProfiles[i].noEnvColor); + Con::setBoolVariable("$pref::Video::clipHigh", sCardProfiles[i].clipHigh); + Con::setBoolVariable("$pref::Video::deleteContext", true); + Con::setBoolVariable("$pref::OpenGL::disableARBTextureCompression", !sCardProfiles[i].texCompress); + Con::setBoolVariable("$pref::Interior::lockArrays", sCardProfiles[i].interiorLock); + Con::setBoolVariable("$pref::TS::skipFirstFog", sCardProfiles[i].skipFirstFog); + Con::setBoolVariable("$pref::Video::only16", sCardProfiles[i].only16); + Con::setBoolVariable("$pref::OpenGL::noDrawArraysAlpha", sCardProfiles[i].noArraysAlpha); + + proFile = sCardProfiles[i].proFile; + + break; + } + + // defaults + U16 glProfile; + + if (!proFile) + { + // no driver GL profile -- make one via weighting GL extensions + glProfile = 25; + + glProfile += gGLState.suppARBMultitexture * 25; + glProfile += gGLState.suppLockedArrays * 15; + glProfile += gGLState.suppVertexArrayRange * 10; + glProfile += gGLState.suppTextureEnvCombine * 5; + glProfile += gGLState.suppPackedPixels * 5; + glProfile += gGLState.suppTextureCompression * 5; + glProfile += gGLState.suppS3TC * 5; + glProfile += gGLState.suppFXT1 * 5; + + Con::setBoolVariable("$pref::Video::safeModeOn", true); + Con::setBoolVariable("$pref::OpenGL::disableEXTCompiledVertexArray", false); + Con::setBoolVariable("$pref::OpenGL::disableSubImage", false); + Con::setBoolVariable("$pref::TS::fogTexture", false); + Con::setBoolVariable("$pref::OpenGL::noEnvColor", false); + Con::setBoolVariable("$pref::Video::clipHigh", false); + Con::setBoolVariable("$pref::Video::deleteContext", true); + Con::setBoolVariable("$pref::OpenGL::disableARBTextureCompression", false); + Con::setBoolVariable("$pref::Interior::lockArrays", true); + Con::setBoolVariable("$pref::TS::skipFirstFog", false); + Con::setBoolVariable("$pref::Video::only16", false); + Con::setBoolVariable("$pref::OpenGL::noDrawArraysAlpha", false); + } + + Con::setVariable("$pref::Video::profiledVendor", vendor); + Con::setVariable("$pref::Video::profiledRenderer", renderer); + + if (!Con::getBoolVariable("$DisableSystemProfiling") && + ( dStrcmp(vendor, Con::getVariable("$pref::Video::defaultsVendor")) || + dStrcmp(renderer, Con::getVariable("$pref::Video::defaultsRenderer")) )) + { + if (proFile) + { + char settings[64]; + + dSprintf(settings,64,"%s.cs",proFile); + //Con::executef(2, "exec", settings); + execScript(settings); + } + else + { + U16 adjust; + + // match clock with profile + for (i = 0; i < sNumProcessors; ++i) + { + adjust = sProcessorProfiles[i].adjust; + + if (Platform::SystemInfo.processor.mhz < sProcessorProfiles[i].clock) break; + } + + const char *settings; + + // match performance metric with profile + for (i = 0; i < sNumSettings; ++i) + { + settings = sSettingProfiles[i].settings; + + if (glProfile+adjust <= sSettingProfiles[i].performance) break; + } + + //Con::executef(2, "exec", settings); + execScript(settings); + } + + Con::setVariable("$pref::Video::defaultsVendor", vendor); + Con::setVariable("$pref::Video::defaultsRenderer", renderer); + } + + // write out prefs + gEvalState.globalVars.exportVariables("$pref::*", "prefs/ClientPrefs.cs", false); + + clearCardProfiles(); +} + +OpenGLDevice::OpenGLDevice( void ) +{ + initDevice( ); +} + +void OpenGLDevice::initDevice( void ) +{ + mDeviceName = "OpenGL"; + mFullScreenOnly = false; + + // generate valid resolutions + mResolutionList.clear( ); + + if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { + // Yes, of course this is bad. But we + // need to support it for the dedicated + // codepath in the full client, and we + // do an SDL_Init( SDL_INIT_VIDEO ) in + // the ::activate code, anyway. + return; + } + + // make sure we have desktop info + GetDesktopState( ); + SDL_Rect** rects = SDL_ListModes( 0, 0 ); + + if( rects == 0 ) { + // eek + AssertFatal( 0, "No video modes available" ); + Con::errorf( ConsoleLogEntry::General, + "No video modes available, exiting..." ); + Platform::forceShutdown( 1 ); + } else if( rects == (SDL_Rect**) -1 ) { + // okay, we're not fullscreen, so technically any + // dimension is possible, so let's just use some + // nice defaults + int defaults[5][2] = { { 640, 480 }, + { 800, 600 }, + { 1024, 768 }, + { 1280, 1024 }, + { 1600, 1200 } }; + + for( int i = 0; i < 5; i++ ) { + int w = defaults[i][0]; + int h = defaults[i][1]; + + // but we don't want them too big + if( w <= linuxState.width && h <= linuxState.height ) { + Resolution rez( w, h, linuxState.bpp ); + mResolutionList.push_back( rez ); + } + + } + + } else { + + for( int i = 0; rects[i] != 0; i++ ) { + Resolution rez( rects[i]->w, rects[i]->h, linuxState.bpp ); + mResolutionList.push_back( rez ); + } + + } + + if( getenv( "MESA_GLX_FX" ) == 0 ) { + putenv( "MESA_GLX_FX=f" ); + } + + if( getenv( "MESA_FX_NO_SIGNALS" ) == 0 ) { + putenv( "MESA_FX_NO_SIGNALS=1" ); + } + +} + +bool OpenGLDevice::activate( U32 width, U32 height, U32 bpp, bool fullScreen ) +{ + const char* libraryName = Con::getVariable( "$pref::OpenGL::driver" ); + assert( libraryName ); + + if( !dStrcmp( libraryName, "" ) ) { + libraryName = "libGL.so.1"; + } + + { static bool loaded_gl = false; + + if( ! loaded_gl && !QGL_Init( libraryName, "libGLU.so.1" ) ) { + return false; + } + loaded_gl = true; + } + + if( !setScreenMode( width, height, bpp, fullScreen, true, false ) ) { + return false; + } + + 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 ); + } + + QGL_EXT_Init( ); + + Con::setVariable( "$pref::Video::displayDevice", mDeviceName ); + + if( Con::getBoolVariable( "$DisableSystemProfiling" ) ) { + return true; + } + + // profile on changed display device (including first time) + if( dStrcmp( vendorString, Con::getVariable( "$pref::Video::profiledVendor" ) ) || + dStrcmp( rendererString, Con::getVariable( "$pref::Video::profiledRenderer" ) ) ) { + bool fullscreen = Con::getBoolVariable( "$pref::Video::fullScreen", true ); + + profileSystem( vendorString, rendererString ); + + U32 width, height, bpp; + const char* resolution = Con::getVariable( "$pref::Video::resolution" ); + + // restart the system now that we've possibly changed + // things from the exec calls in profileSystem(). + dSscanf( resolution, "%d %d %d", &width, &height, &bpp ); + setScreenMode( width, height, bpp, fullscreen, false, false ); + } + + return true; +} + +void OpenGLDevice::shutdown( void ) +{ + Con::printf( "Shutting down video subsystem..." ); + + linuxState.videoInitted = false; +} + +bool OpenGLDevice::setScreenMode( U32 width, + U32 height, + U32 bpp, + bool fullScreen, + bool forceIt, + bool repaint ) +{ + bool needResurrect = false; + + // shutdown if we're already up + if( linuxState.videoInitted ) { + Con::printf( "Killing the texture manager..." ); + Game->textureKill( ); + needResurrect = true; + } + + if( bpp != 16 && bpp != 32 ) { + // Occurs because T2 passes 'Default' to atoi() + // which is passed down as the bpp. Is usually + // converted to '0', but we'll play it safe. + bpp = linuxState.bpp; + } + + int flags = SDL_OPENGL; + + if( fullScreen ) { + flags |= SDL_FULLSCREEN; + } + + Con::setBoolVariable( "$pref::Video::fullScreen", fullScreen ); + Con::printf( "Setting video mode to %d %d %d (%s)...", + width, height, bpp, + ( fullScreen ? "fs" : "w" ) ); + + // these are the deafults in SDL, actually + SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 ); + SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 ); + SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 ); + SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 ); + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + + if( SDL_SetVideoMode( width, height, bpp, flags ) == 0 ) { + return false; + } + + linuxState.videoInitted = true; + + if( needResurrect ) { + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect( ); + } + + // Save the current resolution for fullscreen toggling + if ( SDL_GetVideoSurface()->flags & SDL_FULLSCREEN ) { + smIsFullScreen = true; + } else { + smIsFullScreen = false; + } + smCurrentRes = Resolution( width, height, bpp ); + + Platform::setWindowSize( smCurrentRes.w, smCurrentRes.h ); + char buffer[16]; + dSprintf( buffer, sizeof( buffer ), "%d %d %d", + smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp ); + Con::setVariable( "$pref::Video::resolution", buffer ); + + if( repaint ) { + Con::evaluate( "resetCanvas();" ); + } + + return true; +} + +void OpenGLDevice::swapBuffers( void ) +{ + SDL_GL_SwapBuffers( ); +} + +const char* OpenGLDevice::getDriverInfo( void ) +{ + const char* vendor = (const char*) glGetString( GL_VENDOR ); + const char* renderer = (const char*) glGetString( GL_RENDERER ); + const char* version = (const char*) glGetString( GL_VERSION ); + const char* extensions = (const char*) glGetString( GL_EXTENSIONS ); + + U32 length = 4; + + if( vendor ) { + length += dStrlen( vendor ); + } + + if( renderer ) { + length += dStrlen( renderer ); + } + + if( version ) { + length += dStrlen( version ); + } + + if( extensions ) { + length += dStrlen( extensions ); + } + + char* info = Con::getReturnBuffer( length ); + + dSprintf( info, length, "%s\t%s\t%s\t%s", + ( vendor ? vendor : "" ), + ( renderer ? renderer : "" ), + ( version ? version : "" ), + ( extensions ? extensions : "" ) ); + + return info; +} + +extern "C" int SDL_GetGamma( float* red, float* green, float* blue ); + +bool OpenGLDevice::getGammaCorrection( F32& gamma ) +{ +#ifdef USE_GAMMA_RAMPS + U16 red[256]; + U16 green[256]; + U16 blue[256]; + + if( SDL_GetGammaRamp( red, green, blue ) == -1 ) { + return false; + } + + F32 sum = 0.0f; + U32 count = 0; + + for( U16 i = 1; i < 256; i++ ) { + + if( red[i] != 0 && red[i] != 65535 ) { + F64 b = i / 256.0; + F64 a = red[i] / 65535.0; + F32 c = mLog( a ) / mLog( b ); + + sum += c; + count++; + } + + } + + gamma = sum / count; + + return true; +#else + F32 red = 0; + F32 green = 0; + F32 blue = 0; + + int result = SDL_GetGamma( &red, &green, &blue ); + + gamma = ( red + green + blue ) / 3.0f; + + return ( result != -1 ); +#endif +} + +bool OpenGLDevice::setGammaCorrection( F32 gamma ) +{ +#ifdef USE_GAMMA_RAMPS + U16 red[256]; + U16 green[256]; + U16 blue[256]; + + for( U16 i = 0; i < 256; i++ ) { + red[i] = mPow( static_cast( i ) / 256.0f, gamma ) * 65535.0f; + } + + dMemcpy( green, red, sizeof( green ) ); + dMemcpy( blue, red, sizeof( blue ) ); + + int result = SDL_SetGammaRamp( red, green, blue ); + + return ( result != -1 ); +#else + // no idea why these are foo + gamma = 2.0f - gamma; + return ( SDL_SetGamma( gamma, gamma, gamma ) ); +#endif +} + +bool OpenGLDevice::setVerticalSync( bool on ) +{ + // Implement this is you want to... + on; + return( false ); +} + +DisplayDevice* OpenGLDevice::create( void ) +{ + // NOTE: We're skipping all that awful hoodoo about checking + // for a fullscreen only implementation. + OpenGLDevice* device = new OpenGLDevice( ); + return device; +} + +#endif diff --git a/platformLinux/linuxOGLVideo.h b/platformLinux/linuxOGLVideo.h new file mode 100644 index 0000000..1f96525 --- /dev/null +++ b/platformLinux/linuxOGLVideo.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LINUXOGLVIDEO_H_ +#define _LINUXOGLVIDEO_H_ + +#ifndef _PLATFORMVIDEO_H_ +#include "Platform/platformVideo.h" +#endif + +class OpenGLDevice : public DisplayDevice +{ + public: + OpenGLDevice( ); + + void initDevice( void ); + bool activate( U32 width, U32 height, U32 bpp, bool fullScreen ); + void shutdown( void ); + void destroy( void ); + bool setScreenMode( U32 width, + U32 height, + U32 bpp, + bool fullScree, + bool forceIt = false, + bool repaint = true ); + void swapBuffers( void ); + + const char* getDriverInfo( void ); + bool getGammaCorrection( F32& gamma ); + bool setGammaCorrection( F32 gamma ); + bool setVerticalSync( bool on ); + + static DisplayDevice* create( void ); +}; + +#endif diff --git a/platformLinux/linuxOpenAL.cc b/platformLinux/linuxOpenAL.cc new file mode 100644 index 0000000..e2d31cd --- /dev/null +++ b/platformLinux/linuxOpenAL.cc @@ -0,0 +1,241 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifdef DYNAMIC_LINKING +#include +#endif + +#include "engine/console/console.h" +#include "engine/platformLinux/lokiOpenAL.h" +#include +#include +#include + + +static void *hinstOpenAL = NULL; + +// Stub functions: --------------------------------------------------------- +// AL: +ALvoid stub_alEnable( ALenum ) {} +ALvoid stub_alDisable( ALenum ) {} +ALboolean stub_alIsEnabled( ALenum ) {return(AL_FALSE);} +ALvoid stub_alHint( ALenum , ALenum ) {} +ALboolean stub_alGetBoolean( ALenum ) {return(AL_FALSE);} +ALint stub_alGetInteger( ALenum ) {return(0);} +ALfloat stub_alGetFloat( ALenum ) {return(0.f);} +ALdouble stub_alGetDouble( ALenum ) {return(0.f);} +ALvoid stub_alGetBooleanv( ALenum, ALboolean* ) {} +ALvoid stub_alGetIntegerv( ALenum, ALint* ) {} +ALvoid stub_alGetFloatv( ALenum, ALfloat* ) {} +ALvoid stub_alGetDoublev( ALenum, ALdouble* ) {} +const ALubyte* stub_alGetString( ALenum pname ) +{ + switch(pname) + { + case AL_VENDOR: return (ALubyte*)"None"; + case AL_VERSION: return (ALubyte*)"OpenAL 0.1"; + case AL_RENDERER: return (ALubyte*)"None"; + case AL_EXTENSIONS: return (ALubyte*)""; + } + return(0); +} +ALenum stub_alGetError( ALvoid ) {return(0);} +ALboolean stub_alIsExtensionPresent( const ALubyte* ) {return(AL_FALSE);} +ALvoid* stub_alGetProcAddress( const ALubyte* ) {return(0);} +ALenum stub_alGetEnumValue( const ALubyte* ) {return(0);} +ALvoid stub_alListenerf( ALenum, ALfloat ) {} +ALvoid stub_alListener3f( ALenum, ALfloat, ALfloat, ALfloat ) {} +ALvoid stub_alListenerfv( ALenum, ALfloat* ) {} +ALvoid stub_alGetListeneri( ALenum, ALint* ) {} +ALvoid stub_alGetListenerf( ALenum, ALfloat* ) {} +ALvoid stub_alGetListenerfv( ALenum, ALfloat* ) {} +ALvoid stub_alGenSources( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteSources( ALsizei, ALuint* ) {} +ALboolean stub_alIsSource( ALuint ) {return(AL_FALSE);} +ALvoid stub_alSourcei( ALuint, ALenum, ALint ) {} +ALvoid stub_alSourcef( ALuint, ALenum, ALfloat ) {} +ALvoid stub_alSource3f( ALuint, ALenum, ALfloat, ALfloat, ALfloat ) {} +ALvoid stub_alSourcefv( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alGetSourcei( ALuint, ALenum, ALint* ) {} +ALvoid stub_alGetSourcef( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alGetSourcefv( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alSourcePlayv( ALuint, ALuint* ) {} +ALvoid stub_alSourceStopv( ALuint, ALuint* ) {} +ALvoid stub_alSourcePlay( ALuint ) {} +ALvoid stub_alSourcePause( ALuint ) {} +ALvoid stub_alSourceStop( ALuint ) {} +ALvoid stub_alGenBuffers( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteBuffers( ALsizei, ALuint* ) {} +ALboolean stub_alIsBuffer( ALuint ) {return(AL_FALSE);} +ALvoid stub_alBufferData( ALuint, ALenum, ALvoid*, ALsizei, ALsizei ) {} +ALsizei stub_alBufferAppendData( ALuint, ALenum, ALvoid*, ALsizei, ALsizei ) {return(0);} +ALvoid stub_alGetBufferi( ALuint, ALenum, ALint* ) {} +ALvoid stub_alGetBufferf( ALuint, ALenum, ALfloat* ) {} + +// ALC: +ALvoid* stub_alcCreateContext( ALint *) {return(0);} +ALCenum stub_alcMakeContextCurrent( ALvoid *) {return(ALC_INVALID);} +ALvoid* stub_alcUpdateContext( ALvoid * ) {return(0);} +ALCenum stub_alcDestroyContext( ALvoid * ) {return(ALC_INVALID);} +ALCenum stub_alcGetError( ALvoid ) {return(ALC_INVALID);} +const ALubyte* stub_alcGetErrorString( ALvoid ) {return(0);} +ALvoid* stub_alcGetCurrentContext( ALvoid ) {return(0);} + +// ALUT: +void stub_alutInit( int *, char ** ) {} +void stub_alutExit( ALvoid ) {} +ALboolean stub_alutLoadWAV( const char *, ALvoid **, ALsizei *, ALsizei *, ALsizei *, ALsizei *) {return(AL_FALSE);} + +// Extension: IASIG +ALvoid stub_alGenEnvironmentIASIG( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteEnvironmentIASIG( ALsizei, ALuint*) {} +ALboolean stub_alIsEnvironmentIASIG( ALuint ) {return(AL_FALSE);} +ALvoid stub_alEnvironmentiIASIG( ALuint, ALenum, ALint ) {} +ALvoid stub_alEnvironmentfIASIG( ALuint, ALenum, ALfloat ) {} +ALvoid stub_alGetEnvironmentiIASIG_EXT( ALuint, ALenum, ALint * ) {} +ALvoid stub_alGetEnvironmentfIASIG_EXT( ALuint, ALenum, ALfloat * ) {} + +// Extension: Dynamix +ALboolean stub_alBufferi_EXT( ALuint, ALenum, ALint ) { return(AL_FALSE); } +ALboolean stub_alBufferSyncData_EXT( ALuint, ALenum, ALvoid *, ALsizei, ALsizei ) {return(AL_FALSE); } +ALboolean stub_alBufferStreamFile_EXT( ALuint, const ALubyte * ) { return(AL_FALSE); } +ALboolean stub_alSourceCallback_EXT( ALuint, ALvoid * ) { return(AL_FALSE); } +ALvoid stub_alSourceResetEnvironment_EXT( ALuint ) {} +ALboolean stub_alContexti_EXT( ALenum, ALint) { return(AL_FALSE); } +ALboolean stub_alGetContexti_EXT( ALenum, ALint * ) { return(AL_FALSE); } +ALboolean stub_alGetContextstr_EXT( ALenum, ALuint, ALubyte ** ) { return(AL_FALSE); } +ALboolean stub_alCaptureInit_EXT( ALenum, ALuint, ALsizei ) { return(AL_FALSE); } +ALboolean stub_alCaptureDestroy_EXT( ALvoid ) { return(AL_FALSE); } +ALboolean stub_alCaptureStart_EXT( ALvoid ) { return(AL_FALSE); } +ALboolean stub_alCaptureStop_EXT( ALvoid ) { return(AL_FALSE); } +ALsizei stub_alCaptureGetData_EXT( ALvoid *, ALsizei, ALenum, ALuint ) { return(AL_FALSE); } + +// declare OpenAL functions +#define AL_EXTENSION(ext_name) bool gDoesSupport_##ext_name = false; +#define AL_FUNCTION(fn_return,fn_name,fn_args) fn_return (*fn_name)fn_args = stub_##fn_name; +#define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_return (*fn_name)fn_args = stub_##fn_name; +#include "lib/openal/win32/openALFn.h" + +// DLL's: ------------------------------------------------------------------ +static bool findExtension( const char* name ) +{ + bool result = false; + if (alGetString != NULL) + { + result = dStrstr( (const char*)alGetString(AL_EXTENSIONS), name) != NULL; + } + return result; +} + +static bool bindFunction( void *&fnAddress, const char *name ) +{ +#ifdef DYNAMIC_LINKING + fnAddress = dlsym( hinstOpenAL, name ); + if( !fnAddress ) + Con::errorf(ConsoleLogEntry::General, " Missing OpenAL function '%s'", name); + return (fnAddress != NULL); +#endif // DYNAMIC_LINKING +} + +static void bindExtension( void *&fnAddress, const char *name, bool &supported ) +{ + if (supported) + { + bindFunction(fnAddress, name); + if (fnAddress == NULL) + supported = NULL; + } + else + fnAddress = NULL; +} + +static bool bindDLLFunctions() +{ + bool result = true; + #define AL_FUNCTION(fn_return,fn_name,fn_args) result &= bindFunction( *(void**)&fn_name, #fn_name); +#include "lib/openal/win32/openALFn.h" + return result; +} + +// Stub: ------------------------------------------------------------------- +static void bindStubFunctions() +{ + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = false; + #define AL_FUNCTION(fn_return,fn_name,fn_parms) fn_name = stub_##fn_name; + #define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_name = stub_##fn_name; +#include "lib/openal/win32/openALFn.h" +} + +namespace Audio +{ + +static bool sStaticLibrary; + +void libraryShutdown() +{ +#ifdef DYNAMIC_LINKING + if (hinstOpenAL) + dlclose(hinstOpenAL); + hinstOpenAL = NULL; +#endif // DYNAMIC_LINKING + + // static drivers... + unbindOpenALFunctions(); + + bindStubFunctions(); + sStaticLibrary = true; +} + +bool libraryInit(const char *library) +{ + libraryShutdown(); + + if(!library || !library[0]) + return(false); + + // static drivers... + if(!dStricmp(library, "OpenAL")) + { + if ( bindOpenALFunctions() ) { + return(true); + } + libraryShutdown(); + } + +#ifdef DYNAMIC_LINKING + hinstOpenAL = dlopen( avar("./%s.so", library), RTLD_NOW ); + if(hinstOpenAL != NULL) + { + if(bindDLLFunctions()) + { + sStaticLibrary = false; + return(true); + } + libraryShutdown(); + } +#endif // DYNAMIC_LINKING + + return(false); +} + +void libraryInitExtensions() +{ + // static library extensions are bound on libraryInit (need to be defined anyways...) + if(sStaticLibrary) + { + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = findExtension( #ext_name ); +#include "lib/openal/win32/openALFn.h" + } + else + { + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = findExtension( #ext_name ); + #define AL_EXT_FUNCTION(ext_name, fn_return,fn_name,fn_args) bindExtension( *(void**)&fn_name, #fn_name, gDoesSupport_##ext_name ); +#include "lib/openal/win32/openALFn.h" + } +} + +} // end namespace Audio diff --git a/platformLinux/linuxProcessControl.cc b/platformLinux/linuxProcessControl.cc new file mode 100644 index 0000000..1b55857 --- /dev/null +++ b/platformLinux/linuxProcessControl.cc @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include "platformLinux/platformLinux.h" +#include "console/console.h" + +void Platform::postQuitMessage( const U32 value ) +{ +#ifdef DEDICATED + linuxPostQuitMessage( ); +#else + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent( &event ); +#endif +} + +void Platform::debugBreak( void ) +{ + __asm__( "int $03" ); +} + +void Platform::forceShutdown( S32 value ) +{ + // We have to use _exit() because of really bad + // global constructor/destructor mojo they have. +#ifdef DEDICATED + _exit( value ); +#else + SDL_Quit( ); + _exit( value ); +#endif +} diff --git a/platformLinux/linuxRedBook.cc b/platformLinux/linuxRedBook.cc new file mode 100644 index 0000000..497e1f1 --- /dev/null +++ b/platformLinux/linuxRedBook.cc @@ -0,0 +1,205 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef DEDICATED + +#include + +#include "platformLinux/platformLinux.h" +#include "Platform/platformRedBook.h" +#include "PlatformWin32/platformAL.h" + +class LinuxRedBookDevice : public RedBookDevice +{ +private: + int mDeviceId; + SDL_CD* cdrom; + + void setLastError( const char* ); + +public: + LinuxRedBookDevice( int id = -1 ); + ~LinuxRedBookDevice( void ); + + U32 getDeviceId( void ); + + bool open( void ); + bool close( void ); + bool play( U32 ); + bool stop( void ); + bool getTrackCount( U32* ); + bool getVolume( F32* ); + bool setVolume( F32 ); +}; + +static ALfloat (*alcGetAudioChannel)( ALuint channel ) = 0; +static void (*alcSetAudioChannel)( ALuint channel, ALfloat value ) = 0; + +void installRedBookDevices( void ) +{ + + if( SDL_Init( SDL_INIT_CDROM ) < 0 ) { + return; + } + + int numCDs = SDL_CDNumDrives( ); + + if( numCDs <= 0 ) { + return; + } + + for( int i = 0; i < numCDs; i++ ) { + SDL_CD* temp = SDL_CDOpen( i ); + + if( temp ) { + LinuxRedBookDevice* device = new LinuxRedBookDevice( i ); + const char* name = SDL_CDName( i ); + device->mDeviceName = new char[ dStrlen( name ) + 1 ]; + dStrcpy( device->mDeviceName, name ); + + RedBook::installDevice( device ); + SDL_CDClose( temp ); + } + + } + +} + +LinuxRedBookDevice::LinuxRedBookDevice( int id ) + : mDeviceId( id ), cdrom( 0 ) +{ + // empty +} + +LinuxRedBookDevice::~LinuxRedBookDevice( void ) +{ + close( ); +} + +U32 LinuxRedBookDevice::getDeviceId( void ) +{ + return static_cast( mDeviceId ); +} + +bool LinuxRedBookDevice::open( void ) +{ + + if( mAcquired ) { + setLastError( "already open" ); + return false; + } + + cdrom = SDL_CDOpen( mDeviceId ); + + if( !cdrom ) { + setLastError( "open failed" ); + return false; + } + + mAcquired = true; + return true; +} + +bool LinuxRedBookDevice::close( void ) +{ + + if( !mAcquired ) { + setLastError( "not acquired" ); + return false; + } + + stop( ); + + if( cdrom ) { + SDL_CDClose( cdrom ); + cdrom = 0; + } + + mAcquired = false; + return true; +} + +bool LinuxRedBookDevice::play( U32 track ) +{ + + if( !mAcquired ) { + setLastError( "not acquired" ); + return false; + } + + if( track >= cdrom->numtracks ) { + setLastError( "track out of range" ); + return false; + } + + if( CD_INDRIVE( SDL_CDStatus( cdrom ) ) ) { + + if( SDL_CDPlayTracks( cdrom, track, 0, 0, cdrom->track[track].length ) ) { + setLastError( SDL_GetError( ) ); + return false; + } + + } + + return true; +} + +bool LinuxRedBookDevice::stop( void ) +{ + + if( !mAcquired ) { + setLastError( "not acquired" ); + return false; + } + + if( SDL_CDStop( cdrom ) ) { + setLastError( SDL_GetError( ) ); + return false; + } + + return true; +} + +bool LinuxRedBookDevice::getTrackCount( U32* numTracks ) +{ + + if( !mAcquired ) { + setLastError( "not acquried" ); + return false; + } + + return cdrom->numtracks; +} + +bool LinuxRedBookDevice::getVolume( F32* volume ) +{ + + if( volume && alcGetAudioChannel ) { + *volume = alcGetAudioChannel( ALC_CHAN_CD_LOKI ); + return true; + } + + return false; +} + +bool LinuxRedBookDevice::setVolume( F32 volume ) +{ + + if( alcSetAudioChannel ) { + alcSetAudioChannel( ALC_CHAN_CD_LOKI, volume ); + return true; + } + + return false; +} + +void LinuxRedBookDevice::setLastError( const char* error ) +{ + RedBook::setLastError( error ); +} + +#endif diff --git a/platformLinux/linuxSemaphore.cc b/platformLinux/linuxSemaphore.cc new file mode 100644 index 0000000..2f981bc --- /dev/null +++ b/platformLinux/linuxSemaphore.cc @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platformSemaphore.h" + +#include + +void* Semaphore::createSemaphore( U32 initialCount ) +{ + sem_t* sem = new sem_t; + + if( sem ) { + + if( sem_init( sem, 0, initialCount ) ) { + delete sem; + return 0; + } + + } + + return sem; +} + +void Semaphore::destroySemaphore( void* semaphore ) +{ + + if( semaphore ) { + sem_t* sem = (sem_t*) semaphore; + + sem_destroy( sem ); + delete sem; + } + +} + +bool Semaphore::acquireSemaphore( void* semaphore, bool block ) +{ + bool r = false; + + if( !semaphore ) { + return false; + } + + sem_t* sem = (sem_t*) semaphore; + + if( block ) { + sem_wait( sem ); + r = true; + } else { + + if( sem_trywait( sem ) == 0 ) { + r = true; + } else { + r = false; + } + + } + + return r; +} + +void Semaphore::releaseSemaphore( void* semaphore ) +{ + + if( semaphore ) { + sem_post( (sem_t*) semaphore ); + } + +} diff --git a/platformLinux/linuxStrings.cc b/platformLinux/linuxStrings.cc new file mode 100644 index 0000000..8d4759c --- /dev/null +++ b/platformLinux/linuxStrings.cc @@ -0,0 +1,229 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "platformLinux/platformLinux.h" + +// dMalloc heap. +char* dStrdup_r( const char* s, const char *fileName, U32 lineNumber ) +{ + char* sp = (char*) dMalloc_r( dStrlen( s ) + 1, fileName, lineNumber ); + dStrcpy( sp, s ); + return sp; +} + +char* dStrcat( char* d, const char* s ) +{ + return strcat( d, s ); +} + +char* dStrncat( char* d, const char* s, size_t n ) +{ + return strncat( d, s, n ); +} + +S32 dStrcmp( const char* s1, const char* s2 ) +{ + return strcmp( s1, s2 ); +} + +S32 dStricmp( const char* s1, const char* s2 ) +{ + return strcasecmp( s1, s2 ); +} + +S32 dStrncmp( const char* s1, const char* s2, U32 n ) +{ + return strncmp( s1, s2, n ); +} + +S32 dStrnicmp( const char* s1, const char* s2, U32 n ) +{ + return strncasecmp( s1, s2, n ); +} + +char* dStrcpy( char* d, const char* s ) +{ + return strcpy( d, s ); +} + +char* dStrncpy( char* d, const char* s, U32 n ) +{ + return strncpy( d, s, n ); +} + +U32 dStrlen( const char* s ) +{ + return strlen( s ); +} + +char* dStrupr( char* s ) +{ + U32 l = dStrlen( s ); + + for( int i = 0; i < l; i++ ) { + s[i] = toupper( s[i] ); + } + + return s; +} + +char* dStrlwr( char* s ) +{ + U32 l = dStrlen( s ); + + for( int i = 0; i < l; i++ ) { + s[i] = tolower( s[i] ); + } + + return s; +} + +char* dStrchr( char* s, S32 c ) +{ + return strchr( s, c ); +} + +const char* dStrchr( const char* s, S32 c ) +{ + return strchr( s, c ); +} + +char* dStrrchr( char* s, S32 c ) +{ + return strrchr( s, c ); +} + +const char* dStrrchr( const char* s, S32 c ) +{ + return strrchr( s, c ); +} + +U32 dStrspn( const char* s, const char* t ) +{ + return strspn( s, t ); +} + +U32 dStrcspn( const char* s, const char* t ) +{ + return strcspn( s, t ); +} + +char* dStrstr( char* s1, char* s2 ) +{ + return strstr( s1, s2 ); +} + +const char* dStrstr( const char* s1, const char* s2 ) +{ + return strstr( s1, s2 ); +} + +char* dStrtok( char* s, const char* t ) +{ + return strtok( s, t ); +} + +S32 dAtoi( const char* s ) +{ + return static_cast( atoi( s ) ); +} + +F32 dAtof( const char* s ) +{ + double f = atof( s ); + + if( isinf( f ) ) { + return 0.0f; + } + + return static_cast( f ); +} + +bool dAtob( const char* s ) +{ + return !dStricmp( s, "true" ) || dAtof( s ); +} + +bool dIsalnum( const char c ) +{ + return isalnum( c ); +} + +bool dIsalpha( const char c ) +{ + return isalpha( c ); +} + +bool dIsspace( const char c ) +{ + return isspace( c ); +} + +bool dIsdigit( const char c ) +{ + return isdigit( c ); +} + +void dPrintf( const char* f, ... ) +{ + va_list a; + va_start( a, f ); + vprintf( f, a ); + va_end( a ); +} + +S32 dVprintf( const char* f, void* a ) +{ + S32 l = vprintf( f, (va_list) a ); + // supposedly we're going to do something neat here. + return l; +} + +S32 dSprintf( char* b, U32 n, const char* f, ... ) +{ + va_list a; + va_start( a, f ); + S32 l = vsnprintf( b, n, f, a ); + va_end( a ); + return l; +} + +S32 dVsprintf( char* b, U32 n, const char* f, void* a ) +{ + S32 l = vsnprintf( b, n, f, (va_list) a ); + return l; +} + +S32 dSscanf( const char* b, const char* f, ... ) +{ + va_list a; + va_start( a, f ); + S32 l = vsscanf( b, f, a ); + va_end( a ); + return l; +} + +S32 dFflushStdout( void ) +{ + return fflush( stdout ); +} + +S32 dFflushStderr( void ) +{ + return fflush( stderr ); +} + +void dQsort( void* b, U32 n, U32 w, S32 (QSORT_CALLBACK* f)( const void*, const void* ) ) +{ + qsort( b, n, w, f ); +} diff --git a/platformLinux/linuxThread.cc b/platformLinux/linuxThread.cc new file mode 100644 index 0000000..2cba786 --- /dev/null +++ b/platformLinux/linuxThread.cc @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platformThread.h" +#include "Platform/platformSemaphore.h" + +#include + +typedef struct { + ThreadRunFunction mRunFunc; + S32 mRunArg; + Thread* mThread; + void* mSemaphore; + + LinuxThreadData( void ) { + mRunFunc = 0; + mRunArg = 0; + mThread = 0; + mSemaphore = 0; + } + +} LinuxThreadData; + +Thread::Thread( ThreadRunFunction func, S32 arg, bool start_thread ) +{ + LinuxThreadData* threadData = new LinuxThreadData( ); + + threadData->mRunFunc = func; + threadData->mRunArg = arg; + threadData->mThread = this; + threadData->mSemaphore = Semaphore::createSemaphore( ); + + mData = threadData; + + if ( start_thread ) { + start( ); + } +} + +Thread::~Thread( void ) +{ + join( ); + + LinuxThreadData* threadData = reinterpret_cast( mData ); + Semaphore::destroySemaphore( threadData->mSemaphore ); + + delete threadData; +} + +static int threadRunHandler( void* arg ) +{ + LinuxThreadData* threadData = reinterpret_cast( arg ); + + threadData->mThread->run( threadData->mRunArg ); + Semaphore::releaseSemaphore( threadData->mSemaphore ); + + return 0; +} + +typedef void* (*pthread_func)( void* ); + +void Thread::start( void ) +{ + + if( isAlive( ) ) { + return; + } + + LinuxThreadData* threadData = reinterpret_cast( mData ); + Semaphore::acquireSemaphore( threadData->mSemaphore ); + + pthread_attr_t attr; + pthread_t thread; + + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); + pthread_create( &thread, &attr, (pthread_func) threadRunHandler, threadData ); +} + +bool Thread::join( void ) +{ + + if( !isAlive( ) ) { + return false; + } + + LinuxThreadData* threadData = reinterpret_cast( mData ); + bool result = Semaphore::acquireSemaphore( threadData->mSemaphore ); + + return result; +} + +void Thread::run( S32 arg ) +{ + LinuxThreadData* threadData = reinterpret_cast( mData ); + + if( threadData->mRunFunc ) { + threadData->mRunFunc( arg ); + } + +} + +bool Thread::isAlive( void ) +{ + LinuxThreadData* threadData = reinterpret_cast( mData ); + + bool signal = Semaphore::acquireSemaphore( threadData->mSemaphore, false ); + + if( signal ) { + Semaphore::releaseSemaphore( threadData->mSemaphore ); + } + + return !signal; +} diff --git a/platformLinux/linuxTime.cc b/platformLinux/linuxTime.cc new file mode 100644 index 0000000..65ced53 --- /dev/null +++ b/platformLinux/linuxTime.cc @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include "platformLinux/platformLinux.h" + +static U32 virtualTime = 0; + +#if !defined(__i386__) +#define USE_GETTIMEOFDAY +#endif + +#ifdef USE_GETTIMEOFDAY + +/* gettimeofday() based time implementation */ + +static struct timeval start; + +void linuxInitTicks( void ) +{ + gettimeofday( &start, 0 ); +} + +static inline U32 linuxGetTicks( void ) +{ + struct timeval now; + U32 ticks; + + gettimeofday( &now, 0 ); + ticks = ( now.tv_sec - start.tv_sec ) * 1000 + ( now.tv_usec - start.tv_usec ) / 1000; + + return ticks; +} + +#else + +/* rdtsc based time implementation */ + +static unsigned long long int start; +static unsigned long long int freq; + +static unsigned int cpu_KHz(void) +{ + struct timeval t1, t2; + unsigned long long int r1, r2; + unsigned int z1, z2; + unsigned int ms; + int delta; + const int max_delta = 1; + int tries; + const int max_tries = 20; + + tries = 0; + z2 = 0; + do { + z1 = z2; + gettimeofday(&t1, NULL); + __asm__ __volatile__ ("rdtsc" : "=A" (r1)); + do { + gettimeofday(&t2, NULL); + ms = (t2.tv_sec-t1.tv_sec)*1000 + + (t2.tv_usec-t1.tv_usec)/1000; + } while ( ms < 100 ); + __asm__ __volatile__ ("rdtsc" : "=A" (r2)); + z2 = (r2-r1)/ms; + if ( z2 > z1 ) { + delta = (int)(z2 - z1); + } else { + delta = (int)(z1 - z2); + } + } while ( (delta > max_delta) && (tries++ < max_tries) ); + + return(z2); +} + +void linuxInitTicks( void ) +{ + __asm__ ("rdtsc" : "=A" (start)); + freq = cpu_KHz(); +} + +static inline U32 linuxGetTicks( void ) +{ + unsigned long long int ticks; + + __asm__ ("rdtsc" : "=A" (ticks)); + return (U32)((ticks-start)/freq); +} + +#endif /* USE_GETTIMEOFDAY */ + + +void Platform::getLocalTime( LocalTime < ) +{ + struct tm *systime; + time_t long_time; + + time( &long_time ); + systime = localtime( &long_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; +} + +U32 Platform::getTime( void ) +{ + time_t when; + time( &when ); + return static_cast( when ); +} + +U32 Platform::getRealMilliseconds( void ) +{ + return linuxGetTicks( ); +} + +U32 Platform::getVirtualMilliseconds( void ) +{ + return virtualTime; +} + +void Platform::advanceTime( U32 delta ) +{ + virtualTime += delta; +} diff --git a/platformLinux/linuxWindow.cc b/platformLinux/linuxWindow.cc new file mode 100644 index 0000000..f767026 --- /dev/null +++ b/platformLinux/linuxWindow.cc @@ -0,0 +1,1026 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include + +#include +#include +#include "loki_utils.h" +#include "sdl_utils.h" + +#include "engine/platform/platformInput.h" +#include "engine/platformLinux/platformLinux.h" +#include "engine/platform/platform.h" +#include "engine/platformWIN32/platformGL.h" +#include "engine/platformWIN32/platformAL.h" +#include "engine/platform/platformVideo.h" +#include "engine/platformLinux/linuxOGLVideo.h" +#include "engine/platformLinux/linuxConsole.h" +#include "engine/platform/event.h" +#include "engine/console/console.h" +#include "engine/math/mPoint.h" +#include "engine/platform/gameInterface.h" +#include "game/src/t2Version.h" +#include "engine/core/fileio.h" +#include "engine/math/mRandom.h" + +// random-ness +static MRandomLCG sgPlatRandom; + +// platform-specific state +LinuxPlatformState linuxState; + +// windowing state +static bool windowActive = false; +static bool windowLocked = false; +static Point2I windowSize; + +// timing +static U32 lastTimeTick = 0; + +// uber-cheesy modifier key var +static U32 modifierKeys = 0; + +#ifdef DEDICATED +static bool shouldQuit = false; +#endif + +void Platform::AlertOK( const char* title, const char* message ) +{ + Con::warnf( ConsoleLogEntry::General, message ); +} + +bool Platform::AlertOKCancel( const char* title, const char* message ) +{ +#ifdef DEDICATED + char response[32]; + + fprintf( stderr, "tribes2: %s\n", message ); + + while( 1 ) { + fputs( "OK? > ", stderr ); + fgets( response, 32, stdin ); + + if( strncasecmp( response, "yes", 3 ) == 0 ) { + return true; + } else if( strncasecmp( response, "no", 2 ) == 0 ) { + return false; + } else { + fputs( "Please answer \"yes\" or \"no\"\n", stderr ); + } + + } +#else + Con::warnf( ConsoleLogEntry::General, message ); + return false; +#endif +} + +bool Platform::AlertRetry( const char* title, const char* message ) +{ +#ifdef DEDICATED + char response[32]; + + fprintf( stderr, "tribes2: %s\n", message ); + + while( 1 ) { + fputs( "Retry? > ", stderr ); + fgets( response, 32, stdin ); + + if( strncasecmp( response, "yes", 3 ) == 0 ) { + return true; + } else if( strncasecmp( response, "no", 2 ) == 0 ) { + return false; + } else { + fputs( "Please answer \"yes\" or \"no\"\n", stderr ); + } + + } +#else + Con::warnf( ConsoleLogEntry::General, message ); + return false; +#endif +} + +static void setMouseClipping( void ) +{ +#ifndef DEDICATED + + if( windowActive ) { + SDL_ShowCursor( 0 ); + + if( windowLocked ) { + SDL_WM_GrabInput( SDL_GRAB_ON ); + } else { + SDL_WM_GrabInput( SDL_GRAB_OFF ); + } + + } else { + SDL_ShowCursor( 1 ); + } + +#endif +} + +static void InitInput( void ) +{ + windowActive = true; + setMouseClipping( ); +} + +//-------------------------------------- +void Platform::enableKeyboardTranslation(void) +{ +#ifndef DEDICATED + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, + SDL_DEFAULT_REPEAT_INTERVAL); + SDL_EnableUNICODE( 1 ); +#endif +} + + +//-------------------------------------- +void Platform::disableKeyboardTranslation(void) +{ +#ifndef DEDICATED + SDL_EnableKeyRepeat(0, 0); + SDL_EnableUNICODE( 0 ); +#endif +} + +void Platform::setWindowLocked( bool locked ) +{ + windowLocked = locked; + setMouseClipping( ); +} + +void Platform::minimizeWindow() +{ +#ifndef DEDICATED + SDL_WM_IconifyWindow( ); +#endif +} + +#ifdef DEDICATED +void linuxPostQuitMessage( void ) +{ + shouldQuit = true; +} +#endif + +static U32 translateMods( SDLMod mods ) +{ + + if( mods == KMOD_NONE ) { + return 0; + } + + S32 out = 0; + + if( mods & KMOD_LSHIFT ) { + out |= SI_LSHIFT; + } + + if( mods & KMOD_RSHIFT ) { + out |= SI_RSHIFT; + } + + if( mods & KMOD_LCTRL ) { + out |= SI_LCTRL; + } + + if( mods & KMOD_RCTRL ) { + out |= SI_RCTRL; + } + + if( mods & KMOD_LALT ) { + out |= SI_LALT; + } + + if( mods & KMOD_RALT ) { + out |= SI_RALT; + } + + return out; +} + +static void processKeyMessage( SDL_keysym* keysym, bool down ) +{ + InputEvent event; + + S32 key = translateSDLToKeyCode( keysym->sym ); +#if (defined DEBUG) && !(defined DEDICATED) + + if( key == KEY_NULL ) { + fprintf( stderr, "Keyboard weirdness: %d / %d / %d / %d / %s\n", + keysym->scancode, keysym->sym, keysym->mod, keysym->unicode, + SDL_GetKeyName( keysym->sym ) ); + } + +#endif + U32 mods = translateMods( keysym->mod ); + modifierKeys = mods; + + event.deviceInst = 0; + event.deviceType = KeyboardDeviceType; + event.objType = SI_KEY; + event.objInst = key; + event.action = down ? SI_MAKE : SI_BREAK; + event.modifier = mods; + event.ascii = keysym->unicode; + event.fValue = down ? 1.0 : 0.0; + + Game->postEvent( event ); +} + +static void processDeltaMessage( int x, int y ) +{ + InputEvent event; + float xp = static_cast( x ); + float yp = static_cast( y ); + + event.deviceType = MouseDeviceType; + // always 0 for mouse, only one mouse + event.deviceInst = 0; + // always 0 for mouse, only one axis of given type + event.objInst = 0; + event.modifier = modifierKeys; + event.action = SI_MOVE; + + event.objType = SI_XAXIS; + event.fValue = xp; + Game->postEvent( event ); + + event.objType = SI_YAXIS; + event.fValue = yp; + Game->postEvent( event ); +} + +KeyCodes translateMouseButton( int button ) +{ + KeyCodes key; + + switch( button ) { + case SDL_BUTTON_LEFT: + key = KEY_BUTTON0; + break; + case SDL_BUTTON_MIDDLE: + key = KEY_BUTTON2; + break; + case SDL_BUTTON_RIGHT: + key = KEY_BUTTON1; + break; + case 4: + case 5: + /* This should never happen + - Buttons 4 and 5 are interpreted as mouse wheel + */ + default: + key = KEY_BUTTON0 + button - 1; + } + return key; +} + +static void processMouseMessage( int button, int state ) +{ + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_BUTTON; + event.objInst = translateMouseButton( button ); + event.action = state ? SI_MAKE : SI_BREAK; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = ( event.action == SI_MAKE ) ? 1.0 : 0.0; + + Game->postEvent( event ); +} + +static void processWheelMessage( int button, int state ) +{ + + if( !state ) { + // we don't want button "down" + // events for the wheel + return; + } + + InputEvent event; + F32 delta = 0.0f; + + if( button == 4 ) { + delta = 5.0f; + } else { + delta = -5.0f; + } + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_ZAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = delta; + + Game->postEvent( event ); +} + +static JoystickCodes translatePOV( U8 hat ) +{ + JoystickCodes array[] = { SI_XPOV, SI_YPOV, SI_UPOV, + SI_DPOV, SI_LPOV, SI_RPOV }; + hat = ( hat < 6 ) ? hat : 5; + return array[hat]; +} + +static bool findHatDelta( U8 oldHat, U8 newHat, JoystickCodes* hat, U8* action ) +{ + bool wasUp = ( oldHat & SDL_HAT_UP ); + bool isUp = ( newHat & SDL_HAT_UP ); + bool wasDown = ( oldHat & SDL_HAT_DOWN ); + bool isDown = ( newHat & SDL_HAT_DOWN ); + bool wasLeft = ( oldHat & SDL_HAT_LEFT ); + bool isLeft = ( newHat & SDL_HAT_LEFT ); + bool wasRight = ( oldHat & SDL_HAT_RIGHT ); + bool isRight = ( newHat & SDL_HAT_RIGHT ); + + if( hat == 0 || action == 0 ) { + return false; + } + + *action = SI_BREAK; + + if( wasUp != isUp ) { + *hat = SI_UPOV; + + if( isUp ) { + *action = SI_MAKE; + } + + return true; + } + + if( wasDown != isDown ) { + *hat = SI_DPOV; + + if( isDown ) { + *action = SI_MAKE; + } + + return true; + } + + if( wasLeft != isLeft ) { + *hat = SI_LPOV; + + if( isLeft ) { + *action = SI_MAKE; + } + + return true; + } + + if( wasRight != isRight ) { + *hat = SI_RPOV; + + if( isRight ) { + *action = SI_MAKE; + } + + return true; + } + + return false; +} + +static void processJoyHatMessage( U8 hat, U8 newValue ) +{ + InputEvent event; + U8 oldValue = getHatState( hat ); + JoystickCodes pov; + U8 action; + + // this is a little more complicated than I'd like + // because SDL doesn't report hat events very well. + // it just says "hey, the hat changed", and you have + // to manage the on/off-ness of it. + // also, I'm assuming I'll only see one delta per + // packet, which Sam says is an okay assumption + // for now. + + event.deviceInst = 0; + event.deviceType = JoystickDeviceType; + // which hat + event.objInst = hat; + + if( findHatDelta( oldValue, newValue, &pov, &action ) == false ) { + return; + } + + setHatState( hat, newValue ); + // set to SI_RPOV, SI_LPOV, etc. + event.objType = pov; + // set to SI_MAKE, SI_BREAK + event.action = action; + + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = ( event.action == SI_MAKE ) ? 1.0 : 0.0; + + Game->postEvent( event ); +} + +static KeyCodes translateJoyButton( int button ) +{ + + if( button < 32 ) { + return static_cast( KEY_BUTTON0 + button ); + } else { + return KEY_NULL; + } + +} + +static void processJoyButtonMessage( int button, int state ) +{ + InputEvent event; + + event.deviceInst = 0; + event.deviceType = JoystickDeviceType; + event.objType = SI_BUTTON; + event.objInst = translateJoyButton( button ); + event.action = state ? SI_MAKE : SI_BREAK; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = ( event.action == SI_MAKE ) ? 1.0 : 0.0; + + Game->postEvent( event ); +} + +static bool processMessages( void ) +{ +#ifdef DEDICATED + return ( shouldQuit == false ); +#else + SDL_Event event; + bool cont = true; + + while( SDL_PollEvent( &event ) ) { + + switch( event.type ) { + case SDL_ACTIVEEVENT: + if ( event.active.state & SDL_APPACTIVE ) { + windowActive = event.active.gain; + + if( windowActive ) { + Video::reactivate( ); + Con::evaluate( "resetCanvas();" ); + Input::activate( ); + } else { + Input::deactivate( ); + Video::deactivate( ); + } + setMouseClipping( ); + } + break; + case SDL_VIDEOEXPOSE: + Con::evaluate( "resetCanvas();" ); + break; + case SDL_KEYDOWN: + processKeyMessage( &event.key.keysym, true ); + break; + case SDL_KEYUP: + processKeyMessage( &event.key.keysym, false ); + break; + case SDL_MOUSEMOTION: + + if( windowLocked ) { + processDeltaMessage( event.motion.xrel, event.motion.yrel ); + } else { + MouseMoveEvent move; + + move.xPos = event.motion.x; + move.yPos = event.motion.y; + move.modifier = modifierKeys; + Game->postEvent( move ); + } + + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + + if( event.button.button == 4 || + event.button.button == 5 ) { + processWheelMessage( event.button.button, event.button.state ); + } else { + processMouseMessage( event.button.button, event.button.state ); + } + + break; + case SDL_JOYAXISMOTION: + // we ignore this because it needs to be reported + // continusously, but this is only discrete + // see Input::process() + break; + case SDL_JOYHATMOTION: + processJoyHatMessage( event.jhat.hat, event.jhat.value ); + break; + case SDL_JOYBUTTONDOWN: + processJoyButtonMessage( event.jbutton.button, true ); + break; + case SDL_JOYBUTTONUP: + processJoyButtonMessage( event.jbutton.button, false ); + break; + case SDL_JOYBALLMOTION: + processDeltaMessage( event.jball.xrel, event.jball.yrel ); + break; + case SDL_QUIT: + cont = false; + break; + } + + } + + return cont; +#endif +} + +void Platform::process( void ) +{ + linuxConsole->process( ); + + if( !processMessages( ) ) { + Event quitEvent; + quitEvent.type = QuitEventType; + Game->postEvent( quitEvent ); + } + +#ifdef DEDICATED + usleep( 1000 ); +#else + Input::process( ); +#endif +} + +// FIXME: I'm not sure exactly what this is supposed to do. +// Does the CD have to be in the drive at all times? Just for the server? +// The Win32 code looks like it's looking for a stub executable of some kind. +// eh? :) For now, return true... +bool Platform::doCDCheck() +{ + return true; +} + +void GetDesktopState( void ) +{ +#ifndef DEDICATED + + if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { + Con::warnf( ConsoleLogEntry::General, "SDL video init failed..." ); + return; + } + + const SDL_VideoInfo* info = SDL_GetVideoInfo( ); + + if( info == 0 ) { + return; + } + + SDL_PixelFormat* format = info->vfmt; + int bpp = format->BitsPerPixel; + + int width = 0; + int height = 0; + + if( sdl_GetScreenSize( &width, &height ) == 0 ) { + return; + } + + linuxState.bpp = bpp; + linuxState.width = width; + linuxState.height = height; +#endif +} + +const Point2I& Platform::getWindowSize( void ) +{ + return windowSize; +} + +void Platform::setWindowSize( U32 w, U32 h ) +{ + windowSize.set( w, h ); +} + +static void InitWindow( const Point2I& initialSize ) +{ + windowSize = initialSize; +} + +static void InitOpenGL( void ) +{ +#ifdef DEDICATED + Con::printf( "Ignoring OpenGL initialization." ); +#else + DisplayDevice::init( ); + + int width = 640; + int height = 480; + int bpp = 16; + bool fullScreen = true; + + const char* resolution = Con::getVariable( "$pref::Video::resolution" ); + + if( resolution[0] != '\0' ) { + sscanf( resolution, "%d %d %d", &width, &height, &bpp ); + } + + // default to fullscreen + fullScreen = Con::getBoolVariable( "$pref::Video::fullScreen", true ); + + // we don't have a separate voodoo 2 driver + if( !Video::setDevice( "OpenGL", width, height, bpp, fullScreen ) ) { + Con::errorf( ConsoleLogEntry::General, + "Couldn't activate OpenGL, exiting..." ); + Platform::forceShutdown( 1 ); + return; + } + + // hack from Win32 stuff + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); +#endif +} + +static char cBuffer[512]; + +static const char* cGetDesktopResolution( SimObject*, S32, const char** ) +{ + dSprintf( cBuffer, sizeof( cBuffer ), "%d %d %d", linuxState.width, linuxState.height, linuxState.bpp ); + char* returnString = Con::getReturnBuffer( dStrlen( cBuffer ) + 1 ); + dStrcpy( returnString, cBuffer ); + return returnString; +} + +void Platform::init( void ) +{ + // Set the platform variable for the scripts + Con::setVariable( "$platform", "linux" ); + + LinuxConsole::create( ); +#ifndef DEDICATED + GetDesktopState( ); + installRedBookDevices( ); + + Con::printf( "Video Init:" ); + Video::init( ); + + if( Video::installDevice( OpenGLDevice::create( ) ) ) { + Con::printf( " OpenGL display device created." ); + } else { + Con::printf( " OpenGL display device *not* created." ); + } + + Input::init( ); + InitInput( ); + + Con::addCommand( "getDesktopResolution", cGetDesktopResolution, "getDesktopResolution();", 1, 1 ); +#endif +} + +void Platform::shutdown( void ) +{ + LinuxConsole::destroy( ); +#ifndef DEDICATED + setWindowLocked( false ); + Input::destroy( ); + Video::destroy( ); + SDL_Quit(); +#endif +} + +S32 run( S32 argc, const char** argv ) +{ +#ifdef DEDICATED + // start the timers + linuxInitTicks( ); + lastTimeTick = Platform::getRealMilliseconds( ); + + return Game->main( argc, argv ); +#else + + // start the timers + linuxInitTicks( ); + lastTimeTick = Platform::getRealMilliseconds( ); + createFontInit( ); + windowSize.set( 0, 0 ); + S32 ret = Game->main( argc, argv ); + createFontShutdown( ); + + return ret; +#endif +} + +void Platform::initWindow( const Point2I& initialSize, const char* name ) +{ + InitWindow( initialSize ); + InitOpenGL( ); +#ifndef DEDICATED + SDL_WM_SetCaption( name, "tribes2" ); + SDL_WM_SetIcon(SDL_LoadBMP("icon.bmp"), NULL); +#endif +} + +static void setupFPU( void ) +{ + // The correct combination to catch NaNs is: IM, ZM, OM + bool im = getenv( "TRIBES2_FPU_IM" ); + bool dm = getenv( "TRIBES2_FPU_DM" ); + bool zm = getenv( "TRIBES2_FPU_ZM" ); + bool om = getenv( "TRIBES2_FPU_OM" ); + bool um = getenv( "TRIBES2_FPU_UM" ); + + fpu_control_t cw; + + _FPU_GETCW( cw ); + + // triggers problem in NVIDIA drivers + if( im ) { + cw &= ~_FPU_MASK_IM; + } else { + cw |= _FPU_MASK_IM; + } + + // triggers problems everywhere :) + if( dm ) { + cw &= ~_FPU_MASK_DM; + } else { + cw |= _FPU_MASK_DM; + } + + // div0 + if( zm ) { + cw &= ~_FPU_MASK_ZM; + } else { + cw |= _FPU_MASK_ZM; + } + + // *numeric* overflow + if( om ) { + cw &= ~_FPU_MASK_OM; + } else { + cw |= _FPU_MASK_OM; + } + + // *numeric* underflow + if( um ) { + cw &= ~_FPU_MASK_UM; + } else { + cw |= _FPU_MASK_UM; + } + + _FPU_SETCW( cw ); + + if ( getenv("TRIBES2_FPU_WINDOWS") ) { + cw = 0x0f7f; + _FPU_SETCW( cw ); + } +} + +int main( int argc, char* argv[] ) +{ + int dedicatedArgc = 0; + int missionArgc = 0; + char version[32]; + + sprintf( version, "#%d", getTribes2VersionNumber( ) ); + loki_setgamename( "tribes2", version, "Tribes 2 for Linux" ); + loki_isdemo( 0 ); +#ifndef DEDICATED + loki_signalcleanup(SDL_Quit); +#endif + + loki_registeroption( "help", 'h', "Display this help message" ); + loki_registeroption( "version", 'v', "Display the game version" ); +#ifndef DEDICATED + loki_registeroption( "fullscreen", 'f', "Run the game fullscreen" ); + loki_registeroption( "windowed", 'w', "Run the game in a window" ); + loki_registeroption( "nosound", 's', "Do not access the soundcard" ); +#endif + loki_registeroption( "update", 'u', "Run the Loki auto-update tool" ); + loki_registeroption( "qagent", 'q', "Run the Loki QAgent support tool" ); +#ifndef DEDICATED + loki_registeroption( "gllibrary", 'g', "Select 3D rendering library" ); +#endif + + loki_initialize_noparse( argc, argv ); + + // HACK: path creation at startup + Platform::createPath( "base/prefs/" ); + + for( int i = 1; i < argc; i++ ) { +#define IS_ARG(x,l,s) ( !strcasecmp( x, l ) || !strcasecmp( x, s ) ) + + if( IS_ARG( argv[i], "--help", "-h" ) ) { + loki_printusage( argv[0], 0 ); + exit( 0 ); + } else if( IS_ARG( argv[i], "--version", "-v" ) ) { + printf( "%s\nBuilt with glibc-%d.%d on %s\n", + loki_getgamedescription( ), + __GLIBC__, __GLIBC_MINOR__, + loki_getarch( ) ); + exit( 0 ); + } else if( IS_ARG( argv[i], "--update", "-u" ) ) { + loki_runupdate( argc, argv ); + } else if( IS_ARG( argv[i], "--qagent", "-q" ) ) { + loki_runqagent( 0 ); + } else { + + // do our last check, and pass to engine + if( strcasecmp( argv[i], "-dedicated" ) == 0 ) { + dedicatedArgc = i; + } else if( strcasecmp( argv[i], "-mission" ) == 0 ) { + missionArgc = i; + } + + } + +#undef IS_ARG + } + + chdir( loki_getdatapath( ) ); + +#if defined(DEDICATED) && !defined(BUILD_TOOLS) + // make sure we have -dedicated + if( dedicatedArgc ) { + + // if you specify mission, you must specify args, + // but you don't need to specify it + if( missionArgc ) { + + if( missionArgc + 2 >= argc ) { + // uh oh, need two args + loki_printusage( argv[0], + "\nFATAL: \"-mission\" requires " + " and \"" ); + exit( 1 ); + } + + } + + } else { + loki_printusage( argv[0], + "\nFATAL: The dedicated server " + "*must* be run with \"-dedicated\"." ); + exit( 1 ); + } + + printf( "Dedicated server by:\n\tLoki Software, Inc.\n\thttp://www.lokigames.com\n" ); +#else + // make sure we don't have -dedicated + if( dedicatedArgc ) { + loki_printusage( argv[0], + "\nFATAL: The client " + "*may not* be run with \"-dedicated\"." ); + exit( 1 ); + } +#endif + + setupFPU( ); + + int retval = run( argc, const_cast( argv ) ); + + return retval; +} + +void TimeManager::process( void ) +{ + U32 currentTime = Platform::getRealMilliseconds( ); + TimeEvent event; + + event.elapsedTime = currentTime - lastTimeTick; + lastTimeTick = currentTime; + + Game->postEvent( event ); +} + +F32 Platform::getRandom( void ) +{ + return sgPlatRandom.randF( ); +} + +bool Platform::openWebBrowser( const char* webAddress ) +{ + if ( Video::isFullScreen() ) { + minimizeWindow( ); + } + loki_launchURL( webAddress ); +} + +const char* Platform::getLoginPassword() +{ + File file; + + file.open( "password", File::Read ); + + if( file.getStatus( ) != File::Ok ) { + return ""; + } + + U32 size = file.getSize( ); + + if( !size ) { + return ""; + } + + static char buffer[256]; + + file.read( size, buffer ); + + if( file.getStatus( ) != File::Ok ) { + file.close( ); + return ""; + } + + file.close( ); + + return buffer; +} + +bool Platform::setLoginPassword( const char* password ) +{ + File file; + + file.open( "password", File::Write ); + + if( file.getStatus( ) != File::Ok ) { + return false; + } + + file.write( dStrlen( password ), password ); + + if( file.getStatus( ) != File::Ok ) { + return false; + } + + file.close( ); + + return true; +} + +bool Platform::excludeOtherInstances( const char* mutexName ) +{ + // foo on that + return true; +} + +//-------------------------------------- +// Dedicated server launcher: +//-------------------------------------- +ConsoleFunction( launchDedicatedServer, bool, 4, 4, "launchDedicatedServer( map, missionType, botCount )" ) +{ + // run "tribes2 -dedicated -mission argv[1] argv[2] -bot dAtoi( argv[3] )" + // Tell what we are doing + char cmdLine[512]; + + dSprintf( cmdLine, sizeof( cmdLine ), "tribes2d -dedicated -mission %s %s -bot %d", argv[1], argv[2], dAtoi( argv[3] ) ); + Con::errorf( "** launching dedicated server - command line = \"%s\" **", cmdLine ); + + // Fork and run the server + pid_t child = fork(); + if ( child == 0 ) { + // The child runs the dedicated server + int i; + char *args[12]; + + i = 0; + // Always pop up an xterm so we can have console input + args[i++] = "xterm"; + args[i++] = "-e"; + // The rest of the dedicated server command line goes here + args[i++] = "./tribes2d"; + args[i++] = "-dedicated"; + args[i++] = "-mission"; + args[i++] = argv[1]; + args[i++] = argv[2]; + args[i++] = "-bot"; + args[i++] = argv[3]; + args[i++] = NULL; + // Execute it! + execvp(args[0], args); + + // Fall through only if exec() fails... + perror(args[0]); _exit(255); + } + return( child != -1 ); +} diff --git a/platformLinux/lokiOpenAL.cc b/platformLinux/lokiOpenAL.cc new file mode 100644 index 0000000..eb3a82d --- /dev/null +++ b/platformLinux/lokiOpenAL.cc @@ -0,0 +1,866 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifdef DEDICATED + +// Stubs for the dedicated server + +bool bindOpenALFunctions(void) +{ + return false; +} + +void unbindOpenALFunctions(void) +{ + return; +} + +#else + +// Code for the Linux client + +#include + +#include "engine/console/console.h" +#include "engine/core/fileio.h" +#include "engine/core/tVector.h" +#include "engine/platformWIN32/platformAL.h" + +// For the MP3 playback support +#include "SDL.h" +#include "smpeg.h" + +static void *hinstOpenAL = NULL; + +// declare DLL loaded OpenAL functions +#define AL_FUNCTION(fn_return,fn_name,fn_args) fn_return (*OpenAL_##fn_name)fn_args = NULL; +#define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_return (*OpenAL_##fn_name)fn_args = NULL; +// An API change in OpenAL 1.0 - the context creation takes a device handle +AL_FUNCTION(ALCdevice *, alcOpenDevice, ( const ALubyte *tokstr )); +AL_FUNCTION(ALvoid, alcCloseDevice, ( ALCdevice *dev )); +AL_FUNCTION(ALvoid, alDistanceModel, ( ALenum distanceModel )); +ALvoid* (*OpenAL10_alcCreateContext)( struct _AL_device *dev, ALint* attrlist ) = NULL; +#include "lib/openal/win32/openALFn.h" + +// Loki functions: --------------------------------------------------------- + +// Used by alutInit() and alutExit() for device context handling +static void *context_id; +static ALCdevice *dev; + +typedef struct fakeCallbackPair_s { + U32 sid; + void (*proc)(U32, bool); +} fakeCallbackPair_t; + +static Vector< fakeCallbackPair_t > fakeCallbackFake(__FILE__, __LINE__); + +void alxFakeCallbackUpdate( void ) +{ + fakeCallbackPair_t pi; + ALuint state; + int i; + + i = fakeCallbackFake.size(); + + while( i-- ) { + pi = fakeCallbackFake.last(); + fakeCallbackFake.pop_back(); + + state = AL_INITIAL; + + alGetSourcei( pi.sid, AL_SOURCE_STATE, &state ); + + if( state == AL_STOPPED ) + { + Con::printf( "Calling callback for %d", pi.sid); + pi.proc( pi.sid, false ); + } else { + fakeCallbackFake.push_front( pi ); + } + } +} + +// Code for MP3 playback support + +static SMPEG *mpeg = NULL; + +#define MAX_MPEG_READ 512 + +static ALint MP3_Callback(ALuint sid, + ALuint bid, + ALshort *outdata, + ALenum format, + ALint freq, + ALint samples) +{ + int bytesRequested = samples * sizeof( ALshort ); + int bytesPlayed; + int i; + + if(samples > MAX_MPEG_READ) { + int first, second; + first = MP3_Callback(sid, bid, outdata, format, freq, MAX_MPEG_READ); + second = MP3_Callback(sid, bid, outdata + MAX_MPEG_READ, format, freq, samples - MAX_MPEG_READ); + return first + second; + } + + if( SMPEG_status(mpeg) != SMPEG_PLAYING ) { + SMPEG_play( mpeg ); + } + + memset( outdata, 0, bytesRequested ); + + bytesPlayed = SMPEG_playAudio( mpeg, (ALubyte *) outdata, bytesRequested ); + bytesPlayed /= sizeof( ALshort ); + + if(bytesPlayed < samples) { + SMPEG_stop( mpeg ); + SMPEG_rewind( mpeg ); + + return bytesPlayed; + } + return samples; +} + +ALboolean alutLoadMP3_LOKI(ALuint bid, ALvoid *data, ALint size) +{ + void (*alBufferDataWithCallback)(ALuint bid, + int (*Callback)(ALuint, ALuint, ALshort *, ALenum, ALint, ALint)); + SDL_AudioSpec spec; + + alBufferDataWithCallback = (void (*)(ALuint bid, + int (*Callback)(ALuint, ALuint, ALshort *, ALenum, ALint, ALint))) + alGetProcAddress((ALubyte *) "alBufferDataWithCallback_LOKI"); + + if(alBufferDataWithCallback == NULL) { + Con::errorf(ConsoleLogEntry::General, "Need alBufferDataWithCallback()"); + return AL_FALSE; + } + + if ( mpeg != NULL ) { + SMPEG_stop(mpeg); + SMPEG_delete(mpeg); + } + mpeg = SMPEG_new_data( data, size, NULL, 0 ); + if ( mpeg == NULL ) { + Con::errorf( ConsoleLogEntry::General, "Unable to allocate MP3 data"); + return AL_FALSE; + } + + SMPEG_wantedSpec( mpeg, &spec ); + + spec.freq = Con::getIntVariable( "$pref::Audio::frequency", 22050 ); + spec.format = AUDIO_S16; + + /* implicitly multichannel */ + alBufferi_EXT( bid, AL_CHANNELS, spec.channels ); + + SMPEG_actualSpec( mpeg, &spec ); + + SMPEG_enableaudio( mpeg, 1 ); + SMPEG_enablevideo( mpeg, 0 ); /* sanity check */ + + alBufferDataWithCallback(bid, MP3_Callback); + + return AL_TRUE; +} + +// AL functions +ALvoid loki_alEnable( ALenum capability ) +{ + OpenAL_alEnable( capability ); +} +ALvoid loki_alDisable( ALenum capability ) +{ + OpenAL_alDisable( capability ); +} +ALboolean loki_alIsEnabled( ALenum capability ) +{ + return OpenAL_alIsEnabled( capability ); +} +ALvoid loki_alHint( ALenum target, ALenum mode ) +{ + OpenAL_alHint( target, mode ); +} +ALboolean loki_alGetBoolean( ALenum param ) +{ + return OpenAL_alGetBoolean( param ); +} +ALint loki_alGetInteger( ALenum param ) +{ + return OpenAL_alGetInteger( param ); +} +ALfloat loki_alGetFloat( ALenum param ) +{ + return OpenAL_alGetFloat( param ); +} +ALdouble loki_alGetDouble( ALenum param ) +{ + return OpenAL_alGetDouble( param ); +} +ALvoid loki_alGetBooleanv( ALenum param, ALboolean* data ) +{ + OpenAL_alGetBooleanv( param, data ); +} +ALvoid loki_alGetIntegerv( ALenum param, ALint* data ) +{ + OpenAL_alGetIntegerv( param, data ); +} +ALvoid loki_alGetFloatv( ALenum param, ALfloat* data ) +{ + OpenAL_alGetFloatv( param, data ); +} +ALvoid loki_alGetDoublev( ALenum param, ALdouble* data ) +{ + OpenAL_alGetDoublev( param, data ); +} +const ALubyte* loki_alGetString( ALenum param ) +{ + const ALubyte* string; + + switch (param) { + case AL_EXTENSIONS: + string = "AL_EXT_DYNAMIX"; + break; + default: + string = OpenAL_alGetString( param ); + break; + } + return string; +} +ALenum loki_alGetError( ALvoid ) +{ + ALenum error; + + error = AL_NO_ERROR; + error += OpenAL_alGetError( ); + error += OpenAL_alcGetError( ); + return error; +} +ALboolean loki_alIsExtensionPresent( const ALubyte* fname ) +{ + ALboolean value; + + if ( dStrcmp(fname, "AL_EXT_DYNAMIX") == 0 ) { + value = true; + } else { + value = OpenAL_alIsExtensionPresent( fname ); + } + return value; +} +ALvoid* loki_alGetProcAddress( const ALubyte* fname ) +{ + return OpenAL_alGetProcAddress( fname ); +} +ALenum loki_alGetEnumValue( const ALubyte* ename ) +{ + return OpenAL_alGetEnumValue( ename ); +} +ALvoid loki_alListenerf( ALenum pname, ALfloat param ) +{ + OpenAL_alListenerf( pname, param ); +} +ALvoid loki_alListener3f( ALenum pname, ALfloat param1, ALfloat param2, ALfloat param3 ) +{ + OpenAL_alListener3f( pname, param1, param2, param3 ); +} +ALvoid loki_alListenerfv( ALenum pname, ALfloat* param ) +{ + OpenAL_alListenerfv( pname, param ); +} +ALvoid loki_alGetListeneri( ALenum pname, ALint* value ) +{ + OpenAL_alGetListeneri( pname, value ); +} +ALvoid loki_alGetListenerf( ALenum pname, ALfloat* values ) +{ + OpenAL_alGetListenerf( pname, values ); +} +ALvoid loki_alGetListenerfv( ALenum pname, ALfloat* values ) +{ + OpenAL_alGetListenerfv( pname, values ); +} +ALvoid loki_alGenSources( ALsizei n, ALuint* sources ) +{ + OpenAL_alGenSources( n, sources ); +} +ALvoid loki_alDeleteSources( ALsizei n, ALuint* sources ) +{ + OpenAL_alDeleteSources( n, sources ); +} +ALboolean loki_alIsSource( ALuint sid ) +{ + return OpenAL_alIsSource( sid ); +} +ALvoid loki_alSourcei( ALuint sid, ALenum param, ALint value ) +{ + switch (param) { + case AL_STREAMING: + case AL_ENV_SAMPLE_DIRECT_EXT: + case AL_ENV_SAMPLE_DIRECT_HF_EXT: + case AL_ENV_SAMPLE_ROOM_EXT: + case AL_ENV_SAMPLE_ROOM_HF_EXT: + case AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT: + case AL_ENV_SAMPLE_FLAGS_EXT: + // Not yet supported + break; + case AL_SOURCE_AMBIENT: + // Special case, emulated by positioning relative sources + if ( value ) { + alSourcei(sid, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(sid, AL_POSITION, 0.0, 0.0, 0.0); + } else { + alSourcei(sid, AL_SOURCE_RELATIVE, AL_FALSE); + } + break; + default: + // Pass through to OpenAL 1.0 + OpenAL_alSourcei( sid, param, value ); + break; + } +} +ALvoid loki_alSourcef( ALuint sid, ALenum param, ALfloat value ) +{ + switch (param) { + case AL_PAN: + case AL_ENV_SAMPLE_REVERB_MIX_EXT: + case AL_ENV_SAMPLE_OBSTRUCTION_EXT: + case AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT: + case AL_ENV_SAMPLE_OCCLUSION_EXT: + case AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT: + case AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT: + case AL_ENV_SAMPLE_AIR_ABSORPTION_EXT: + // Not yet supported + break; + default: + // Pass through to OpenAL 1.0 + OpenAL_alSourcef( sid, param, value ); + break; + } +} +ALvoid loki_alSource3f( ALuint sid, ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ) +{ + OpenAL_alSource3f( sid, param, v1, v2, v3 ); +} +ALvoid loki_alSourcefv( ALuint sid, ALenum param, ALfloat* values ) +{ + OpenAL_alSourcefv( sid, param, values ); +} +ALvoid loki_alGetSourcei( ALuint sid, ALenum pname, ALint* value ) +{ + switch (pname) { + case AL_SOURCE_AMBIENT: + // Special case, emulated by positioning relative sources + alGetSourcei(sid, AL_SOURCE_RELATIVE, value); + break; + default: + // Pass through to OpenAL 1.0 + OpenAL_alGetSourcei( sid, pname, value ); + break; + } +} +ALvoid loki_alGetSourcef( ALuint sid, ALenum pname, ALfloat* value ) +{ + OpenAL_alGetSourcef( sid, pname, value ); +} +ALvoid loki_alGetSourcefv( ALuint sid, ALenum pname, ALfloat* values ) +{ + OpenAL_alGetSourcefv( sid, pname, values ); +} +ALvoid loki_alSourcePlayv( ALuint ns, ALuint* ids ) +{ + OpenAL_alSourcePlayv( ns, ids ); +} +ALvoid loki_alSourceStopv( ALuint ns, ALuint* ids ) +{ + OpenAL_alSourceStopv( ns, ids ); +} +ALvoid loki_alSourcePlay( ALuint sid ) +{ + OpenAL_alSourcePlay( sid ); +} +ALvoid loki_alSourcePause( ALuint sid ) +{ + OpenAL_alSourcePause( sid ); +} +ALvoid loki_alSourceStop( ALuint sid ) +{ + OpenAL_alSourceStop( sid ); +} +ALvoid loki_alGenBuffers( ALsizei n, ALuint* samples ) +{ + OpenAL_alGenBuffers( n, samples ); +} +ALvoid loki_alDeleteBuffers( ALsizei n, ALuint* samples ) +{ + OpenAL_alDeleteBuffers( n, samples ); +} +ALboolean loki_alIsBuffer( ALuint buffer ) +{ + return OpenAL_alIsBuffer( buffer ); +} +ALvoid loki_alBufferData( ALuint buffer, ALenum format, ALvoid* data, ALsizei size, ALsizei freq ) +{ + OpenAL_alBufferData( buffer, format, data, size, freq ); +} +ALsizei loki_alBufferAppendData( ALuint buffer, ALenum format, ALvoid* data, ALsizei size, ALsizei freq ) +{ + return OpenAL_alBufferAppendData( buffer, format, data, size, freq ); +} +ALvoid loki_alGetBufferi( ALuint buffer, ALenum param, ALint* value ) +{ + OpenAL_alGetBufferi( buffer, param, value ); +} +ALvoid loki_alGetBufferf( ALuint buffer, ALenum param, ALfloat* value ) +{ + OpenAL_alGetBufferf( buffer, param, value ); +} + +// ALC functions +ALvoid* loki_alcCreateContext( ALint* attrlist ) +{ + return OpenAL10_alcCreateContext( dev, attrlist ); +} +ALCenum loki_alcMakeContextCurrent( ALvoid* context ) +{ + return OpenAL_alcMakeContextCurrent( context ); +} +ALvoid* loki_alcUpdateContext( ALvoid* context ) +{ + return OpenAL_alcUpdateContext( context ); +} +ALCenum loki_alcDestroyContext( ALvoid* context ) +{ + return OpenAL_alcDestroyContext( context ); +} +ALCenum loki_alcGetError( ALvoid ) +{ + return OpenAL_alcGetError( ); +} +const ALubyte * loki_alcGetErrorString( ALvoid ) +{ + return OpenAL_alcGetErrorString( ); +} +ALvoid* loki_alcGetCurrentContext( ALvoid ) +{ + return OpenAL_alcGetCurrentContext( ); +} + +// ALUT functions +void loki_alutInit( int* argc, char** argv ) +{ + char devstr_buf[1024]; + const char *devstr; + const char *paren; + int refresh, frequency; + ALint attrlist[5]; + + // Get some basic configuration variables + refresh = Con::getIntVariable( "$pref::Audio::refresh", 15 ); + frequency = Con::getIntVariable( "$pref::Audio::frequency", 22050 ); + + // Open the audio device + devstr = Con::getVariable("$pref::OpenAL::driver"); + if ( devstr && ((paren=strrchr(devstr, ')')) != NULL) ) { + // Make sure a sane sampling rate is specified + if ( strstr(devstr, "sampling-rate") == NULL ) { + strcpy(devstr_buf, devstr); + sprintf(&devstr_buf[paren-devstr], + " (sampling-rate %d ))", frequency); + devstr = devstr_buf; + } + } else { + sprintf(devstr_buf, "'( (sampling-rate %d ))", frequency); + devstr = devstr_buf; + } + dev = OpenAL_alcOpenDevice( (const ALubyte *) devstr ); + + if( dev == NULL ) { + Con::errorf( ConsoleLogEntry::General, "Audio device open failed..." ); + return; + } + + attrlist[0] = ALC_FREQUENCY; + attrlist[1] = frequency; + attrlist[2] = ALC_REFRESH; + attrlist[3] = refresh; + attrlist[4] = 0; + + context_id = alcCreateContext( attrlist ); + + if( context_id == NULL ) { + Con::errorf( ConsoleLogEntry::General, "Audio context creation failed, exiting..." ); + Platform::forceShutdown( 1 ); + return; + } + + alcMakeContextCurrent( context_id ); + + // Set the distance attenuation model + OpenAL_alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); +} +void loki_alutExit( ALvoid ) +{ + if( context_id != NULL ) { + alcDestroyContext( context_id ); + OpenAL_alcCloseDevice( dev ); + context_id = NULL; + } +} +ALboolean loki_alutLoadWAV( const char* fname, ALvoid** data, ALsizei* format, ALsizei* size, ALsizei* bits, ALsizei* freq ) +{ + return OpenAL_alutLoadWAV( fname, data, format, size, bits, freq ); +} + +// Extensions +ALvoid loki_alGenEnvironmentIASIG( ALsizei n, ALuint* environs ) +{ + OpenAL_alGenEnvironmentIASIG( n, environs ); +} +ALvoid loki_alDeleteEnvironmentIASIG( ALsizei n, ALuint* environs ) +{ + OpenAL_alDeleteEnvironmentIASIG( n, environs ); +} +ALboolean loki_alIsEnvironmentIASIG( ALuint environment ) +{ + return OpenAL_alIsEnvironmentIASIG( environment ); +} +ALvoid loki_alEnvironmentiIASIG( ALuint eid, ALenum param, ALint value ) +{ + OpenAL_alEnvironmentiIASIG( eid, param, value ); +} + +ALboolean loki_alBufferi_EXT( ALuint buffer, ALenum pname, ALint value ) +{ + ALboolean status; + + alGetError(); + switch (pname) { + case AL_BUFFER_KEEP_RESIDENT: + status = false; + break; + default: + OpenAL_alBufferi_EXT( buffer, pname, value ); + status = (alGetError() == AL_NO_ERROR); + break; + } + return status; +} +ALboolean loki_alBufferSyncData_EXT( ALuint buffer, ALenum format, ALvoid* data, ALsizei size, ALsizei freq ) +{ + ALboolean status; + + // alBufferData doesn't return a value, + // so clear the error setting, execute, + // and check again. + alGetError( ); + + alBufferData(buffer, format, data, size, freq); + + if( alGetError( ) == AL_NO_ERROR ) { + // let AL manage the memory + dFree( data ); + status = true; + } else { + status = false; + } + return status; +} +ALboolean loki_alBufferStreamFile_EXT( ALuint buffer, const ALubyte* filename ) +{ + File file; + + file.open( (const char*) filename, File::Read ); + + if( file.getStatus( ) != File::Ok ) { + return AL_FALSE; + } + + U32 size = file.getSize( ); + char* data = (char*) dMalloc( size ); + + if( data == NULL ) { + // technically, 'file' will go out of scope and + // close itself, but still... + file.close( ); + + return AL_FALSE; + } + + file.read( size, data ); + + if( file.getStatus( ) != File::Ok ) { + file.close( ); + dFree( data ); + return AL_FALSE; + } + + U32 ext = dStrlen( filename ); + ext -= 3; + ext = ( ext >= 0 ) ? ext : 0; + ALuint ok = AL_FALSE; + + if( dStrnicmp( &filename[ ext ], "mp3", 3 ) != 0 ) { + alGetError(); + + alBufferData( buffer, AL_FORMAT_WAVE_EXT, data, size, 0 ); + + ok = (alGetError() == AL_NO_ERROR); + + } else { + ok = alutLoadMP3_LOKI( buffer, data, size ); + } + + dFree( data ); + file.close( ); + + if( ok == AL_FALSE ) { + Con::warnf( ConsoleLogEntry::General, + "could not buffer and convert %s", filename ); + + return AL_FALSE; + } + + return AL_TRUE; +} +ALboolean loki_alSourceCallback_EXT( ALuint source, ALvoid* callback ) +{ + fakeCallbackPair_t pusher; + + if( alIsSource( source ) == AL_FALSE) { + Con::errorf(ConsoleLogEntry::General, + "alSourceCallback_EXT: %d not a source id", source ); + return; + } + + pusher.sid = source; + pusher.proc = callback; + + fakeCallbackFake.push_back( pusher ); +} +ALvoid loki_alSourceResetEnvironment_EXT( ALuint source ) +{ + return; // Nothing we can do here yet... +} +ALboolean loki_alContexti_EXT( ALenum pname, ALint value ) +{ + return false; +} +ALboolean loki_alGetContexti_EXT( ALenum pname, ALint* value ) +{ + ALboolean status; + + status = false; + if( value ) { + switch( pname ) { + case ALC_PROVIDER: + *value = 0; + status = true; + break; + case ALC_PROVIDER_COUNT: + *value = 1; + status = true; + break; + case ALC_SPEAKER: + *value = 0; + status = true; + break; + case ALC_SPEAKER_COUNT: + *value = 1; + status = true; + break; + case ALC_BUFFER_MEMORY_USAGE: + *value = 0; + status = true; + break; + case ALC_BUFFER_MEMORY_SIZE: + *value = 1 << 16; + status = true; + break; + case ALC_BUFFER_MEMORY_COUNT: + *value = 0; + status = true; + break; + case ALC_BUFFER_COUNT: + *value = 0; + status = true; + break; + default: + // error + break; + } + } + return status; +} +ALboolean loki_alGetContextstr_EXT( ALenum pname, ALuint idx, ALubyte** value ) +{ + ALboolean status; + + status = false; + if( value ) { + switch( pname ) { + case ALC_PROVIDER_NAME: + *value = "Loki Software, Inc."; + status = true; + break; + case ALC_SPEAKER_NAME: + *value = "Stereo, 2-Speaker"; + status = true; + break; + default: + // error + break; + } + } + return status; +} +ALboolean loki_alCaptureInit_EXT( ALenum format, ALuint rate, ALsizei bufferSize ) +{ + return OpenAL_alCaptureInit_EXT( format, rate, bufferSize ); +} +ALboolean loki_alCaptureDestroy_EXT( ALvoid ) +{ + return OpenAL_alCaptureDestroy_EXT( ); +} +ALboolean loki_alCaptureStart_EXT( ALvoid ) +{ + return OpenAL_alCaptureStart_EXT( ); +} +ALboolean loki_alCaptureStop_EXT( ALvoid ) +{ + return OpenAL_alCaptureStop_EXT( ); +} +ALsizei loki_alCaptureGetData_EXT( ALvoid* data, ALsizei n, ALenum format, ALuint rate ) +{ + return OpenAL_alCaptureGetData_EXT( data, n, format, rate ); +} +ALvoid loki_alEnvironmentfIASIG( ALuint eid, ALenum param, ALfloat value ) +{ + return; // Nothing we can do here yet... +} +ALvoid loki_alGetEnvironmentiIASIG_EXT( ALuint eid, ALenum param, ALint * value ) +{ + return; // Nothing we can do here yet... +} +ALvoid loki_alGetEnvironmentfIASIG_EXT( ALuint eid, ALenum param, ALfloat * value ) +{ + return; // Nothing we can do here yet... +} + +// DLL's: ------------------------------------------------------------------ +static bool findExtension( const char* name ) +{ + bool result = false; + if (alGetString != NULL) + { + result = dStrstr( (const char*)alGetString(AL_EXTENSIONS), name) != NULL; + } + return result; +} + +static bool bindFunction( void *&fnAddress, const char *name ) +{ + fnAddress = dlsym( hinstOpenAL, name ); +#ifdef DEBUG_OPENAL + if( !fnAddress ) { + Con::errorf(ConsoleLogEntry::General, " Missing OpenAL function '%s'", name); + } +#endif + return (fnAddress != NULL); +} + +static void bindExtension( void *&fnAddress, const char *name, bool &supported ) +{ + if (supported) + { + bindFunction(fnAddress, name); +#if 0 + if (fnAddress == NULL) + supported = false; +#endif + } + else + fnAddress = NULL; +} + +static bool bindDLLFunctions() +{ + bool result = true; + /* It's okay if the following functions are not found: + alGetBoolean() + alGetInteger() + alGetFloat() + alGetDouble() + alcUpdateContext() + alcGetErrorString() + */ + #define AL_EXTENSION(ext_name) \ + gDoesSupport_##ext_name = findExtension(#ext_name); + #define AL_FUNCTION(fn_return,fn_name,fn_args) \ + { const char *name = #fn_name; \ + /* Do some renaming of functions to match OpenAL 1.0 API */ \ + if ( dStrcmp(name, "alGetListeneri") == 0 ) \ + name = "alGetListeneriv"; \ + else \ + if ( dStrcmp(name, "alGetListenerf") == 0 ) \ + name = "alGetListenerfv"; \ + else \ + if ( dStrcmp(name, "alGetSourcei") == 0 ) \ + name = "alGetSourceiv"; \ + else \ + if ( dStrcmp(name, "alGetSourcef") == 0 ) \ + name = "alGetSourcefv"; \ + else \ + if ( dStrcmp(name, "alBufferi_EXT") == 0 ) \ + name = "alBufferi_LOKI"; \ + result &= bindFunction( *(void**)&OpenAL_##fn_name, name); \ + } + #define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) \ + { const char *name = #fn_name; \ + /* Do some renaming of functions to match OpenAL 1.0 API */ \ + if ( dStrcmp(name, "alBufferi_EXT") == 0 ) \ + name = "alBufferi_LOKI"; \ + bindExtension( *(void**)&OpenAL_##fn_name, name, gDoesSupport_##ext_name); \ + } + AL_FUNCTION(ALCdevice *, alcOpenDevice, ( const ALubyte *tokstr )); + AL_FUNCTION(ALvoid, alcCloseDevice, ( ALCdevice *dev )); + AL_FUNCTION(ALvoid, alDistanceModel, ( ALenum distanceModel )); + result &= bindFunction( *(void**)&OpenAL10_alcCreateContext, "alcCreateContext"); +#include "lib/openal/win32/openALFn.h" + return result; +} + +bool bindOpenALFunctions(void) +{ + hinstOpenAL = dlopen( "./libopenal.so.0", RTLD_NOW ); + if(hinstOpenAL == NULL) + { + return false; + } + + // Set the Loki OpenAL function pointers (needed for extension loading) + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = false; + #define AL_FUNCTION(fn_return,fn_name,fn_parms) fn_name = loki_##fn_name; + #define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_name = loki_##fn_name; +#include "lib/openal/win32/openALFn.h" + + // Load the real OpenAL functions from the shared library + bindDLLFunctions(); + + // Yay, we're done! + return true; +} + +void unbindOpenALFunctions(void) +{ + if (hinstOpenAL) + dlclose(hinstOpenAL); + hinstOpenAL = NULL; +} + +#endif // DEDICATED diff --git a/platformLinux/lokiOpenAL.h b/platformLinux/lokiOpenAL.h new file mode 100644 index 0000000..48c7c59 --- /dev/null +++ b/platformLinux/lokiOpenAL.h @@ -0,0 +1,11 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +extern int bindOpenALFunctions(void); + +// Unload the Loki OpenAL functions +extern void unbindOpenALFunctions(void); diff --git a/platformLinux/mSolver_ASM.asm b/platformLinux/mSolver_ASM.asm new file mode 100644 index 0000000..d3fc4b9 --- /dev/null +++ b/platformLinux/mSolver_ASM.asm @@ -0,0 +1,387 @@ +; +; ASM implementations of mSolver code +; + +; externs +extern mSolveCubic_c__FffffPf + +; data +segment .data + +; float constants +f_quarter dd 0.25 +f_half dd 0.5 +f_n_half dd -0.5 +f_two dd 2.0 +f_n_three dd -3.0 +f_four dd 4.0 +f_n_four dd -4.0 +f_n_six dd -6.0 +f_eight dd 8.0 + +; integer constants +i_n_one dd 1 + +; variables +i dd 0 +nreal dd 0 +w dd 0 +b0 dd 0 +b1 dd 0 +b2 dd 0 +d0 dd 0 +d1 dd 0 +h dd 0 +t dd 0 +z dd 0 +px dd 0 +cubeA dd 0 +cubeB dd 0 +cubeC dd 0 +cubeD dd 0 + +p dd 0 +q dd 0 +dis dd 0 +phi dd 0 + +segment .text + +; functions + +; +; U32 mSolveQuartic_ASM(F32 A, F32 B, F32 C, F32 D, F32 E, F32* x) +; + +%define A [ebp+8] +%define B [ebp+12] +%define C [ebp+16] +%define D [ebp+20] +%define E [ebp+24] +%define x [ebp+28] + +global mSolveQuartic_ASM + +mSolveQuartic_ASM: + + push ebp + mov ebp, esp + + ; if (A==0.0) + ; return mSolveCubic(B, C, D, E, x); + fld dword A + fldz + fcompp + fnstsw ax + and ah, 68 + xor ah, 64 + jne .L1 + + push dword x + push dword E + push dword D + push dword C + push dword B + call mSolveCubic_c__FffffPf + add esp, byte 20 + jmp .L2 + +.L1: + ; w = B/(4*A); /* offset */ + fld dword A + fmul dword [f_four] + fld dword B + fxch + fdivp st1, st0 + fstp dword [w] + + ; b2 = -6*square(w) + C/A; /* coeffs. of shifted polynomial */ + fld dword [w] + fmul st0, st0 + fld dword [f_n_six] + fmulp st1, st0 + fld dword C + fld dword A + fdivp st1, st0 + faddp st1, st0 + fstp dword [b2] + + ; b1 = (8*square(w) - 2*C/A)*w + D/A; + fld dword [w] + fmul st0, st0 + fmul dword [f_eight] + fld dword C + fld dword A + fdivp st1, st0 + fmul dword [f_two] + fsubp st1, st0 + fmul dword [w] + fld dword D + fld dword A + fdivp st1, st0 + faddp st1, st0 + fstp dword [b1] + + ; b0 = ((-3*square(w) + C/A)*w - D/A)*w + E/A; + fld dword [w] + fmul st0, st0 + fmul dword [f_n_three] + fld dword C + fld dword A + fdivp st1, st0 + faddp st1, st0 + fmul dword [w] + fld dword D + fld dword A + fdivp st1, st0 + fsubp st1, st0 + fmul dword [w] + fld dword E + fld dword A + fdivp st1, st0 + faddp st1, st0 + fstp dword [b0] + + ; // cubic resolvent + ; F32 cubeA = 1.0; + ; F32 cubeB = b2; + ; F32 cubeC = -4 * b0; + ; F32 cubeD = square(b1) - 4 * b0 * b2; + fld1 + fstp dword [cubeA] + fld dword [b2] + fstp dword [cubeB] + fld dword [b0] + fmul dword [f_n_four] + fstp dword [cubeC] + fld dword [b1] + fmul st0, st0 + fld dword [f_four] + fmul dword [b0] + fmul dword [b2] + fsubp st1, st0 + fstp dword [cubeD] + + ; mSolveCubic(cubeA, cubeB, cubeC, cubeD, x); + push dword x + push dword [cubeD] + push dword [cubeC] + push dword [cubeB] + push dword [cubeA] + call mSolveCubic_c__FffffPf + add esp, byte 20 + + mov eax, dword x + ; z = x[0]; + fld dword [eax+0] + fstp dword [z] + ; nreal = 0; + xor dword [nreal], 0 + ; px = x; + mov dword [px], eax + ; t = mSqrt(0.25 * square(z) - b0); + fld dword [z] + fmul st0, st0 + fmul dword [f_quarter] + fsub dword [b0] + fsqrt + fstp dword [t] + + ; for (i=-1; i<=1; i+=2) { + mov ecx, dword [i_n_one] +.L3 + ; d0 = -0.5*z + i*t; /* coeffs. of quadratic factor */ + fld dword [f_n_half] + fmul dword [z] + fld dword [i] + fmul dword [t] + faddp st1, st0 + fstp dword [d0] + ; d1 = (t!=0.0)? -i*0.5*b1/t : i*mSqrt(-z - b2); + fild dword [i] + fld dword [t] + fldz + fcompp + fnstsw ax + ;; if t == 0.0 jump to .L4 + ;; if C3 is set, jump to .L4 + ;; if ah & C3 is not-zero, jump to .L4 + and ah, 64 + jnz .L4 + + fchs + fmul dword [f_half] + fmul dword [b1] + fdiv dword [t] + jmp .L5 + +.L4: + fld dword [z] + fchs + fsub dword [b2] + fsqrt + fmulp st1, st0 + +.L5: + fstp dword [d1] + + ; h = 0.25 * square(d1) - d0; + fld dword [d1] + fmul st0, st0 + fmul dword [f_quarter] + fsub dword [d0] + fst dword [h] + + ; if (h>=0.0) { + ; h = mSqrt(h); + ; nreal += 2; + ; *px = -0.5*d1 - h - w; + ; px++; + ; *px = -0.5*d1 + h - w; + ; px++; + ; } + fldz + fcomp st1 + ; if h < 0.0, jump to .L6 + ; if C0 is 0, jump to .L6 + ; if ( ah & 0x1 ) == 0.0, jump to .L6 + fnstsw ax + and ah, 0x1 + jz .L6 + + fsqrt + fstp dword [h] + add dword [nreal], 2 + + fld dword [d1] + fmul dword [f_n_half] + fsub dword [h] + fsub dword [w] + fstp dword [px] + mov eax, dword [px] + add eax, 4 + mov dword [px], eax + fld dword [d1] + fmul dword [f_n_half] + fadd dword [h] + fsub dword [w] + fstp dword [px] + mov eax, dword [px] + add eax, 4 + mov dword [px], eax + + ; } +.L6: + add ecx, 2 + cmp ecx, 1 + jle near .L3 + + ; // sort results in ascending order + ; if (nreal == 4) + ; { + mov ecx, dword [nreal] + cmp ecx, 4 + jnz near .L7 + + mov ecx, x + + ; if (x[2] < x[0]) + fld dword [ecx] + fld dword [ecx+8] + fcom st1 + fnstsw ax + and ah, 0x1 + jz .L8 + + ; swap(x[0], x[2]); + fstp dword [ecx] + fstp dword [ecx+8] + jmp .L9 + +.L8: + fstp st0 + fstp st0 + +.L9: + ; if (x[3] < x[1]) + fld dword [ecx+4] + fld dword [ecx+12] + fcom st1 + fnstsw ax + and ah, 0x1 + jz .L10 + + ; swap(x[1], x[3]); + fstp dword [ecx+4] + fstp dword [ecx+12] + jmp .L11 + +.L10: + fstp st0 + fstp st0 + +.L11: + ; if (x[1] < x[0]) + fld dword [ecx] + fld dword [ecx+4] + fcom st1 + fnstsw ax + and ah, 0x1 + jz .L12 + + ; swap(x[0], x[1]); + fstp dword [ecx] + fstp dword [ecx+4] + jmp .L13 + +.L12: + fstp st0 + fstp st0 + +.L13: + ; if (x[3] < x[2]) + fld dword [ecx+12] + fld dword [ecx+8] + fcom st1 + fnstsw ax + and ah, 0x1 + jz .L14 + + ; swap(x[2], x[3]); + fstp dword [ecx+12] + fstp dword [ecx+8] + jmp .L15 + +.L14: + fstp st0 + fstp st0 + +.L15: + ; if (x[2] < x[1]) + fld dword [ecx+4] + fld dword [ecx+8] + fcom st1 + fnstsw ax + and ah, 0x1 + jz .L16 + + ; swap(x[1], x[2]); + fstp dword [ecx+4] + fstp dword [ecx+8] + jmp .L17 + +.L16: + fstp st0 + fstp st0 + +.L17 + ; } + ; + +.L7 + ; return(nreal); + mov eax, dword [nreal] + +.L2: + pop ebp + ret diff --git a/platformLinux/platformAL.h b/platformLinux/platformAL.h new file mode 100644 index 0000000..e34a964 --- /dev/null +++ b/platformLinux/platformAL.h @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMAL_H_ +#define _PLATFORMAL_H_ + +#ifndef _PLATFORM_H_ +#include "engine/platform/platform.h" +#endif +#include +#include +#include + + +// rename +#define AL_GAIN_LINEAR AL_GAIN_LINEAR_LOKI +#define AL_SOURCE_LOOPING AL_LOOPING +#define AL_MIN_DISTANCE AL_REFERENCE_DISTANCE + +// fictional tokens +#define AL_PAN 0xFFFFFFFE +#define AL_SOURCE_AMBIENT 0xFFFFFFFD +#define AL_BUFFER_KEEP_RESIDENT 0xFFFFFFFC + +#define ALC_PROVIDER 0x1FFFFFFE +#define ALC_PROVIDER_COUNT 0x1FFFFFFD +#define ALC_PROVIDER_NAME 0x1FFFFFFC + +#define ALC_SPEAKER 0x1FFFFFFB +#define ALC_SPEAKER_COUNT 0x1FFFFFFA +#define ALC_SPEAKER_NAME 0x1FFFFFF9 + +#define ALC_BUFFER_MEMORY_USAGE 0x1FFFFFF8 +#define ALC_BUFFER_MEMORY_SIZE 0x1FFFFFF7 +#define ALC_BUFFER_MEMORY_COUNT 0x1FFFFFF6 +#define ALC_BUFFER_COUNT 0x1FFFFFF5 +#define ALC_BUFFER_DYNAMIC_MEMORY_USAGE 0x1FFFFFF4 +#define ALC_BUFFER_DYNAMIC_MEMORY_SIZE 0x1FFFFFF3 +#define ALC_BUFFER_DYNAMIC_MEMORY_COUNT 0x1FFFFFF2 +#define ALC_BUFFER_DYNAMIC_COUNT 0x1FFFFFF1 +#define ALC_BUFFER_LATENCY 0x1FFFFFF0 +#define ALC_RESOLUTION 0x1FFFFFEF +#define ALC_CHANNELS 0x1FFFFFEE + +#define AL_ENV_FLAGS_EXT 0x2FFFFFFE +#define AL_ENV_ROOM_VOLUME_EXT 0x2FFFFFFD +#define AL_ENV_EFFECT_VOLUME_EXT 0x2FFFFFFC +#define AL_ENV_DAMPING_EXT 0x2FFFFFFB +#define AL_ENV_ENVIRONMENT_SIZE_EXT 0x2FFFFFFA +#define AL_ENV_SAMPLE_REVERB_MIX_EXT 0x2FFFFFF9 +#define AL_ENV_SAMPLE_DIRECT_EXT 0x2FFFFFF8 +#define AL_ENV_SAMPLE_DIRECT_HF_EXT 0x2FFFFFF7 +#define AL_ENV_SAMPLE_ROOM_EXT 0x2FFFFFF6 +#define AL_ENV_SAMPLE_ROOM_HF_EXT 0x2FFFFFF5 +#define AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT 0x2FFFFFF4 +#define AL_ENV_SAMPLE_FLAGS_EXT 0x2FFFFFF3 +#define AL_ENV_SAMPLE_OBSTRUCTION_EXT 0x2FFFFFF2 +#define AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT 0x2FFFFFF1 +#define AL_ENV_SAMPLE_OCCLUSION_EXT 0x2FFFFFF0 +#define AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT 0x2FFFFFEF +#define AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT 0x2FFFFFEE +#define AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT 0x2FFFFFED +#define AL_ENV_SAMPLE_AIR_ABSORPTION_EXT 0x2FFFFFEC + + +// 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 "lib/openal/win32/openALFn.h" +#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 + +// Used by the Linux client audio +extern void alxFakeCallbackUpdate( void ); + +#endif diff --git a/platformLinux/platformGL.h b/platformLinux/platformGL.h new file mode 100644 index 0000000..f85fec7 --- /dev/null +++ b/platformLinux/platformGL.h @@ -0,0 +1,3997 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMGL_H_ +#define _PLATFORMGL_H_ + + +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_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_CLIENT_ALL_ATTRIB_BITS 0xFFFFFFFF + + +extern void (*glClearIndex)( GLfloat c ); + +extern void (*glClearColor)( GLclampf red, + GLclampf green, + GLclampf blue, + GLclampf alpha ); + +extern void (*glClear)( GLbitfield mask ); + +extern void (*glIndexMask)( GLuint mask ); + +extern void (*glColorMask)( GLboolean red, GLboolean green, + GLboolean blue, GLboolean alpha ); + +extern void (*glAlphaFunc)( GLenum func, GLclampf ref ); + +extern void (*glBlendFunc)( GLenum sfactor, GLenum dfactor ); + +extern void (*glLogicOp)( GLenum opcode ); + +extern void (*glCullFace)( GLenum mode ); + +extern void (*glFrontFace)( GLenum mode ); + +extern void (*glPointSize)( GLfloat size ); + +extern void (*glLineWidth)( GLfloat width ); + +extern void (*glLineStipple)( GLint factor, GLushort pattern ); + +extern void (*glPolygonMode)( GLenum face, GLenum mode ); + +extern void (*glPolygonOffset)( GLfloat factor, GLfloat units ); + +extern void (*glPolygonStipple)( const GLubyte *mask ); + +extern void (*glGetPolygonStipple)( GLubyte *mask ); + +extern void (*glEdgeFlag)( GLboolean flag ); + +extern void (*glEdgeFlagv)( const GLboolean *flag ); + +extern void (*glScissor)( GLint x, GLint y, + GLsizei width, GLsizei height); + +extern void (*glClipPlane)( GLenum plane, const GLdouble *equation ); + +extern void (*glGetClipPlane)( GLenum plane, GLdouble *equation ); + +extern void (*glDrawBuffer)( GLenum mode ); + +extern void (*glReadBuffer)( GLenum mode ); + +extern void (*glEnable)( GLenum cap ); + +extern void (*glDisable)( GLenum cap ); + +extern GLboolean (*glIsEnabled)( GLenum cap ); + + +extern void (*glEnableClientState)( GLenum cap ); /* 1.1 */ + +extern void (*glDisableClientState)( GLenum cap ); /* 1.1 */ + + +extern void (*glGetBooleanv)( GLenum pname, GLboolean *params ); + +extern void (*glGetDoublev)( GLenum pname, GLdouble *params ); + +extern void (*glGetFloatv)( GLenum pname, GLfloat *params ); + +extern void (*glGetIntegerv)( GLenum pname, GLint *params ); + + +extern void (*glPushAttrib)( GLbitfield mask ); + +extern void (*glPopAttrib)( void ); + + +extern void (*glPushClientAttrib)( GLbitfield mask ); /* 1.1 */ + +extern void (*glPopClientAttrib)( void ); /* 1.1 */ + + +extern GLint (*glRenderMode)( GLenum mode ); + +extern GLenum (*glGetError)( void ); + +extern const GLubyte* (*glGetString)( GLenum name ); + +extern void (*glFinish)( void ); + +extern void (*glFlush)( void ); + +extern void (*glHint)( GLenum target, GLenum mode ); + + + +/* + * Depth Buffer + */ + +extern void (*glClearDepth)( GLclampd depth ); + +extern void (*glDepthFunc)( GLenum func ); + +extern void (*glDepthMask)( GLboolean flag ); + +extern void (*glDepthRange)( GLclampd near_val, GLclampd far_val ); + + +/* + * Accumulation Buffer + */ + +extern void (*glClearAccum)( GLfloat red, GLfloat green, + GLfloat blue, GLfloat alpha ); + +extern void (*glAccum)( GLenum op, GLfloat value ); + + + +/* + * Transformation + */ + +extern void (*glMatrixMode)( GLenum mode ); + +extern void (*glOrtho)( GLdouble left, GLdouble right, + GLdouble bottom, GLdouble top, + GLdouble near_val, GLdouble far_val ); + +extern void (*glFrustum)( GLdouble left, GLdouble right, + GLdouble bottom, GLdouble top, + GLdouble near_val, GLdouble far_val ); + +extern void (*glViewport)( GLint x, GLint y, + GLsizei width, GLsizei height ); + +extern void (*glPushMatrix)( void ); + +extern void (*glPopMatrix)( void ); + +extern void (*glLoadIdentity)( void ); + +extern void (*glLoadMatrixd)( const GLdouble *m ); +extern void (*glLoadMatrixf)( const GLfloat *m ); + +extern void (*glMultMatrixd)( const GLdouble *m ); +extern void (*glMultMatrixf)( const GLfloat *m ); + +extern void (*glRotated)( GLdouble angle, + GLdouble x, GLdouble y, GLdouble z ); +extern void (*glRotatef)( GLfloat angle, + GLfloat x, GLfloat y, GLfloat z ); + +extern void (*glScaled)( GLdouble x, GLdouble y, GLdouble z ); +extern void (*glScalef)( GLfloat x, GLfloat y, GLfloat z ); + +extern void (*glTranslated)( GLdouble x, GLdouble y, GLdouble z ); +extern void (*glTranslatef)( GLfloat x, GLfloat y, GLfloat z ); + + + +/* + * Display Lists + */ + +extern GLboolean (*glIsList)( GLuint list ); + +extern void (*glDeleteLists)( GLuint list, GLsizei range ); + +extern GLuint (*glGenLists)( GLsizei range ); + +extern void (*glNewList)( GLuint list, GLenum mode ); + +extern void (*glEndList)( void ); + +extern void (*glCallList)( GLuint list ); + +extern void (*glCallLists)( GLsizei n, GLenum type, + const GLvoid *lists ); + +extern void (*glListBase)( GLuint base ); + + + +/* + * Drawing Functions + */ + +extern void (*glBegin)( GLenum mode ); + +extern void (*glEnd)( void ); + + +extern void (*glVertex2d)( GLdouble x, GLdouble y ); +extern void (*glVertex2f)( GLfloat x, GLfloat y ); +extern void (*glVertex2i)( GLint x, GLint y ); +extern void (*glVertex2s)( GLshort x, GLshort y ); + +extern void (*glVertex3d)( GLdouble x, GLdouble y, GLdouble z ); +extern void (*glVertex3f)( GLfloat x, GLfloat y, GLfloat z ); +extern void (*glVertex3i)( GLint x, GLint y, GLint z ); +extern void (*glVertex3s)( GLshort x, GLshort y, GLshort z ); + +extern void (*glVertex4d)( GLdouble x, GLdouble y, GLdouble z, GLdouble w ); +extern void (*glVertex4f)( GLfloat x, GLfloat y, GLfloat z, GLfloat w ); +extern void (*glVertex4i)( GLint x, GLint y, GLint z, GLint w ); +extern void (*glVertex4s)( GLshort x, GLshort y, GLshort z, GLshort w ); + +extern void (*glVertex2dv)( const GLdouble *v ); +extern void (*glVertex2fv)( const GLfloat *v ); +extern void (*glVertex2iv)( const GLint *v ); +extern void (*glVertex2sv)( const GLshort *v ); + +extern void (*glVertex3dv)( const GLdouble *v ); +extern void (*glVertex3fv)( const GLfloat *v ); +extern void (*glVertex3iv)( const GLint *v ); +extern void (*glVertex3sv)( const GLshort *v ); + +extern void (*glVertex4dv)( const GLdouble *v ); +extern void (*glVertex4fv)( const GLfloat *v ); +extern void (*glVertex4iv)( const GLint *v ); +extern void (*glVertex4sv)( const GLshort *v ); + + +extern void (*glNormal3b)( GLbyte nx, GLbyte ny, GLbyte nz ); +extern void (*glNormal3d)( GLdouble nx, GLdouble ny, GLdouble nz ); +extern void (*glNormal3f)( GLfloat nx, GLfloat ny, GLfloat nz ); +extern void (*glNormal3i)( GLint nx, GLint ny, GLint nz ); +extern void (*glNormal3s)( GLshort nx, GLshort ny, GLshort nz ); + +extern void (*glNormal3bv)( const GLbyte *v ); +extern void (*glNormal3dv)( const GLdouble *v ); +extern void (*glNormal3fv)( const GLfloat *v ); +extern void (*glNormal3iv)( const GLint *v ); +extern void (*glNormal3sv)( const GLshort *v ); + + +extern void (*glIndexd)( GLdouble c ); +extern void (*glIndexf)( GLfloat c ); +extern void (*glIndexi)( GLint c ); +extern void (*glIndexs)( GLshort c ); +extern void (*glIndexub)( GLubyte c ); /* 1.1 */ + +extern void (*glIndexdv)( const GLdouble *c ); +extern void (*glIndexfv)( const GLfloat *c ); +extern void (*glIndexiv)( const GLint *c ); +extern void (*glIndexsv)( const GLshort *c ); +extern void (*glIndexubv)( const GLubyte *c ); /* 1.1 */ + +extern void (*glColor3b)( GLbyte red, GLbyte green, GLbyte blue ); +extern void (*glColor3d)( GLdouble red, GLdouble green, GLdouble blue ); +extern void (*glColor3f)( GLfloat red, GLfloat green, GLfloat blue ); +extern void (*glColor3i)( GLint red, GLint green, GLint blue ); +extern void (*glColor3s)( GLshort red, GLshort green, GLshort blue ); +extern void (*glColor3ub)( GLubyte red, GLubyte green, GLubyte blue ); +extern void (*glColor3ui)( GLuint red, GLuint green, GLuint blue ); +extern void (*glColor3us)( GLushort red, GLushort green, GLushort blue ); + +extern void (*glColor4b)( GLbyte red, GLbyte green, + GLbyte blue, GLbyte alpha ); +extern void (*glColor4d)( GLdouble red, GLdouble green, + GLdouble blue, GLdouble alpha ); +extern void (*glColor4f)( GLfloat red, GLfloat green, + GLfloat blue, GLfloat alpha ); +extern void (*glColor4i)( GLint red, GLint green, + GLint blue, GLint alpha ); +extern void (*glColor4s)( GLshort red, GLshort green, + GLshort blue, GLshort alpha ); +extern void (*glColor4ub)( GLubyte red, GLubyte green, + GLubyte blue, GLubyte alpha ); +extern void (*glColor4ui)( GLuint red, GLuint green, + GLuint blue, GLuint alpha ); +extern void (*glColor4us)( GLushort red, GLushort green, + GLushort blue, GLushort alpha ); + + +extern void (*glColor3bv)( const GLbyte *v ); +extern void (*glColor3dv)( const GLdouble *v ); +extern void (*glColor3fv)( const GLfloat *v ); +extern void (*glColor3iv)( const GLint *v ); +extern void (*glColor3sv)( const GLshort *v ); +extern void (*glColor3ubv)( const GLubyte *v ); +extern void (*glColor3uiv)( const GLuint *v ); +extern void (*glColor3usv)( const GLushort *v ); + +extern void (*glColor4bv)( const GLbyte *v ); +extern void (*glColor4dv)( const GLdouble *v ); +extern void (*glColor4fv)( const GLfloat *v ); +extern void (*glColor4iv)( const GLint *v ); +extern void (*glColor4sv)( const GLshort *v ); +extern void (*glColor4ubv)( const GLubyte *v ); +extern void (*glColor4uiv)( const GLuint *v ); +extern void (*glColor4usv)( const GLushort *v ); + + +extern void (*glTexCoord1d)( GLdouble s ); +extern void (*glTexCoord1f)( GLfloat s ); +extern void (*glTexCoord1i)( GLint s ); +extern void (*glTexCoord1s)( GLshort s ); + +extern void (*glTexCoord2d)( GLdouble s, GLdouble t ); +extern void (*glTexCoord2f)( GLfloat s, GLfloat t ); +extern void (*glTexCoord2i)( GLint s, GLint t ); +extern void (*glTexCoord2s)( GLshort s, GLshort t ); + +extern void (*glTexCoord3d)( GLdouble s, GLdouble t, GLdouble r ); +extern void (*glTexCoord3f)( GLfloat s, GLfloat t, GLfloat r ); +extern void (*glTexCoord3i)( GLint s, GLint t, GLint r ); +extern void (*glTexCoord3s)( GLshort s, GLshort t, GLshort r ); + +extern void (*glTexCoord4d)( GLdouble s, GLdouble t, GLdouble r, GLdouble q ); +extern void (*glTexCoord4f)( GLfloat s, GLfloat t, GLfloat r, GLfloat q ); +extern void (*glTexCoord4i)( GLint s, GLint t, GLint r, GLint q ); +extern void (*glTexCoord4s)( GLshort s, GLshort t, GLshort r, GLshort q ); + +extern void (*glTexCoord1dv)( const GLdouble *v ); +extern void (*glTexCoord1fv)( const GLfloat *v ); +extern void (*glTexCoord1iv)( const GLint *v ); +extern void (*glTexCoord1sv)( const GLshort *v ); + +extern void (*glTexCoord2dv)( const GLdouble *v ); +extern void (*glTexCoord2fv)( const GLfloat *v ); +extern void (*glTexCoord2iv)( const GLint *v ); +extern void (*glTexCoord2sv)( const GLshort *v ); + +extern void (*glTexCoord3dv)( const GLdouble *v ); +extern void (*glTexCoord3fv)( const GLfloat *v ); +extern void (*glTexCoord3iv)( const GLint *v ); +extern void (*glTexCoord3sv)( const GLshort *v ); + +extern void (*glTexCoord4dv)( const GLdouble *v ); +extern void (*glTexCoord4fv)( const GLfloat *v ); +extern void (*glTexCoord4iv)( const GLint *v ); +extern void (*glTexCoord4sv)( const GLshort *v ); + + +extern void (*glRasterPos2d)( GLdouble x, GLdouble y ); +extern void (*glRasterPos2f)( GLfloat x, GLfloat y ); +extern void (*glRasterPos2i)( GLint x, GLint y ); +extern void (*glRasterPos2s)( GLshort x, GLshort y ); + +extern void (*glRasterPos3d)( GLdouble x, GLdouble y, GLdouble z ); +extern void (*glRasterPos3f)( GLfloat x, GLfloat y, GLfloat z ); +extern void (*glRasterPos3i)( GLint x, GLint y, GLint z ); +extern void (*glRasterPos3s)( GLshort x, GLshort y, GLshort z ); + +extern void (*glRasterPos4d)( GLdouble x, GLdouble y, GLdouble z, GLdouble w ); +extern void (*glRasterPos4f)( GLfloat x, GLfloat y, GLfloat z, GLfloat w ); +extern void (*glRasterPos4i)( GLint x, GLint y, GLint z, GLint w ); +extern void (*glRasterPos4s)( GLshort x, GLshort y, GLshort z, GLshort w ); + +extern void (*glRasterPos2dv)( const GLdouble *v ); +extern void (*glRasterPos2fv)( const GLfloat *v ); +extern void (*glRasterPos2iv)( const GLint *v ); +extern void (*glRasterPos2sv)( const GLshort *v ); + +extern void (*glRasterPos3dv)( const GLdouble *v ); +extern void (*glRasterPos3fv)( const GLfloat *v ); +extern void (*glRasterPos3iv)( const GLint *v ); +extern void (*glRasterPos3sv)( const GLshort *v ); + +extern void (*glRasterPos4dv)( const GLdouble *v ); +extern void (*glRasterPos4fv)( const GLfloat *v ); +extern void (*glRasterPos4iv)( const GLint *v ); +extern void (*glRasterPos4sv)( const GLshort *v ); + + +extern void (*glRectd)( GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2 ); +extern void (*glRectf)( GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2 ); +extern void (*glRecti)( GLint x1, GLint y1, GLint x2, GLint y2 ); +extern void (*glRects)( GLshort x1, GLshort y1, GLshort x2, GLshort y2 ); + + +extern void (*glRectdv)( const GLdouble *v1, const GLdouble *v2 ); +extern void (*glRectfv)( const GLfloat *v1, const GLfloat *v2 ); +extern void (*glRectiv)( const GLint *v1, const GLint *v2 ); +extern void (*glRectsv)( const GLshort *v1, const GLshort *v2 ); + + + +/* + * Vertex Arrays (1.1) + */ + +extern void (*glVertexPointer)( GLint size, GLenum type, + GLsizei stride, const GLvoid *ptr ); + +extern void (*glNormalPointer)( GLenum type, GLsizei stride, + const GLvoid *ptr ); + +extern void (*glColorPointer)( GLint size, GLenum type, + GLsizei stride, const GLvoid *ptr ); + +extern void (*glIndexPointer)( GLenum type, GLsizei stride, + const GLvoid *ptr ); + +extern void (*glTexCoordPointer)( GLint size, GLenum type, + GLsizei stride, const GLvoid *ptr ); + +extern void (*glEdgeFlagPointer)( GLsizei stride, const GLvoid *ptr ); + +extern void (*glGetPointerv)( GLenum pname, void **params ); + +extern void (*glArrayElement)( GLint i ); + +extern void (*glDrawArrays)( GLenum mode, GLint first, GLsizei count ); + +extern void (*glDrawElements)( GLenum mode, GLsizei count, + GLenum type, const GLvoid *indices ); + +extern void (*glInterleavedArrays)( GLenum format, GLsizei stride, + const GLvoid *pointer ); + + +/* + * Lighting + */ + +extern void (*glShadeModel)( GLenum mode ); + +extern void (*glLightf)( GLenum light, GLenum pname, GLfloat param ); +extern void (*glLighti)( GLenum light, GLenum pname, GLint param ); +extern void (*glLightfv)( GLenum light, GLenum pname, + const GLfloat *params ); +extern void (*glLightiv)( GLenum light, GLenum pname, + const GLint *params ); + +extern void (*glGetLightfv)( GLenum light, GLenum pname, + GLfloat *params ); +extern void (*glGetLightiv)( GLenum light, GLenum pname, + GLint *params ); + +extern void (*glLightModelf)( GLenum pname, GLfloat param ); +extern void (*glLightModeli)( GLenum pname, GLint param ); +extern void (*glLightModelfv)( GLenum pname, const GLfloat *params ); +extern void (*glLightModeliv)( GLenum pname, const GLint *params ); + +extern void (*glMaterialf)( GLenum face, GLenum pname, GLfloat param ); +extern void (*glMateriali)( GLenum face, GLenum pname, GLint param ); +extern void (*glMaterialfv)( GLenum face, GLenum pname, const GLfloat *params ); +extern void (*glMaterialiv)( GLenum face, GLenum pname, const GLint *params ); + +extern void (*glGetMaterialfv)( GLenum face, GLenum pname, GLfloat *params ); +extern void (*glGetMaterialiv)( GLenum face, GLenum pname, GLint *params ); + +extern void (*glColorMaterial)( GLenum face, GLenum mode ); + + + + +/* + * Raster functions + */ + +extern void (*glPixelZoom)( GLfloat xfactor, GLfloat yfactor ); + +extern void (*glPixelStoref)( GLenum pname, GLfloat param ); +extern void (*glPixelStorei)( GLenum pname, GLint param ); + +extern void (*glPixelTransferf)( GLenum pname, GLfloat param ); +extern void (*glPixelTransferi)( GLenum pname, GLint param ); + +extern void (*glPixelMapfv)( GLenum map, GLint mapsize, + const GLfloat *values ); +extern void (*glPixelMapuiv)( GLenum map, GLint mapsize, + const GLuint *values ); +extern void (*glPixelMapusv)( GLenum map, GLint mapsize, + const GLushort *values ); + +extern void (*glGetPixelMapfv)( GLenum map, GLfloat *values ); +extern void (*glGetPixelMapuiv)( GLenum map, GLuint *values ); +extern void (*glGetPixelMapusv)( GLenum map, GLushort *values ); + +extern void (*glBitmap)( GLsizei width, GLsizei height, + GLfloat xorig, GLfloat yorig, + GLfloat xmove, GLfloat ymove, + const GLubyte *bitmap ); + +extern void (*glReadPixels)( GLint x, GLint y, + GLsizei width, GLsizei height, + GLenum format, GLenum type, + GLvoid *pixels ); + +extern void (*glDrawPixels)( GLsizei width, GLsizei height, + GLenum format, GLenum type, + const GLvoid *pixels ); + +extern void (*glCopyPixels)( GLint x, GLint y, + GLsizei width, GLsizei height, + GLenum type ); + + + +/* + * Stenciling + */ + +extern void (*glStencilFunc)( GLenum func, GLint ref, GLuint mask ); + +extern void (*glStencilMask)( GLuint mask ); + +extern void (*glStencilOp)( GLenum fail, GLenum zfail, GLenum zpass ); + +extern void (*glClearStencil)( GLint s ); + + + +/* + * Texture mapping + */ + +extern void (*glTexGend)( GLenum coord, GLenum pname, GLdouble param ); +extern void (*glTexGenf)( GLenum coord, GLenum pname, GLfloat param ); +extern void (*glTexGeni)( GLenum coord, GLenum pname, GLint param ); + +extern void (*glTexGendv)( GLenum coord, GLenum pname, const GLdouble *params ); +extern void (*glTexGenfv)( GLenum coord, GLenum pname, const GLfloat *params ); +extern void (*glTexGeniv)( GLenum coord, GLenum pname, const GLint *params ); + +extern void (*glGetTexGendv)( GLenum coord, GLenum pname, GLdouble *params ); +extern void (*glGetTexGenfv)( GLenum coord, GLenum pname, GLfloat *params ); +extern void (*glGetTexGeniv)( GLenum coord, GLenum pname, GLint *params ); + + +extern void (*glTexEnvf)( GLenum target, GLenum pname, GLfloat param ); +extern void (*glTexEnvi)( GLenum target, GLenum pname, GLint param ); + +extern void (*glTexEnvfv)( GLenum target, GLenum pname, const GLfloat *params ); +extern void (*glTexEnviv)( GLenum target, GLenum pname, const GLint *params ); + +extern void (*glGetTexEnvfv)( GLenum target, GLenum pname, GLfloat *params ); +extern void (*glGetTexEnviv)( GLenum target, GLenum pname, GLint *params ); + + +extern void (*glTexParameterf)( GLenum target, GLenum pname, GLfloat param ); +extern void (*glTexParameteri)( GLenum target, GLenum pname, GLint param ); + +extern void (*glTexParameterfv)( GLenum target, GLenum pname, + const GLfloat *params ); +extern void (*glTexParameteriv)( GLenum target, GLenum pname, + const GLint *params ); + +extern void (*glGetTexParameterfv)( GLenum target, + GLenum pname, GLfloat *params); +extern void (*glGetTexParameteriv)( GLenum target, + GLenum pname, GLint *params ); + +extern void (*glGetTexLevelParameterfv)( GLenum target, GLint level, + GLenum pname, GLfloat *params ); +extern void (*glGetTexLevelParameteriv)( GLenum target, GLint level, + GLenum pname, GLint *params ); + + +extern void (*glTexImage1D)( GLenum target, GLint level, + GLint internalFormat, + GLsizei width, GLint border, + GLenum format, GLenum type, + const GLvoid *pixels ); + +extern void (*glTexImage2D)( GLenum target, GLint level, + GLint internalFormat, + GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, + const GLvoid *pixels ); + +extern void (*glGetTexImage)( GLenum target, GLint level, + GLenum format, GLenum type, + GLvoid *pixels ); + + + +/* 1.1 functions */ + +extern void (*glGenTextures)( GLsizei n, GLuint *textures ); + +extern void (*glDeleteTextures)( GLsizei n, const GLuint *textures); + +extern void (*glBindTexture)( GLenum target, GLuint texture ); + +extern void (*glPrioritizeTextures)( GLsizei n, + const GLuint *textures, + const GLclampf *priorities ); + +extern GLboolean (*glAreTexturesResident)( GLsizei n, + const GLuint *textures, + GLboolean *residences ); + +extern GLboolean (*glIsTexture)( GLuint texture ); + + +extern void (*glTexSubImage1D)( GLenum target, GLint level, + GLint xoffset, + GLsizei width, GLenum format, + GLenum type, const GLvoid *pixels ); + + +extern void (*glTexSubImage2D)( GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, + GLenum format, GLenum type, + const GLvoid *pixels ); + + +extern void (*glCopyTexImage1D)( GLenum target, GLint level, + GLenum internalformat, + GLint x, GLint y, + GLsizei width, GLint border ); + + +extern void (*glCopyTexImage2D)( GLenum target, GLint level, + GLenum internalformat, + GLint x, GLint y, + GLsizei width, GLsizei height, + GLint border ); + + +extern void (*glCopyTexSubImage1D)( GLenum target, GLint level, + GLint xoffset, GLint x, GLint y, + GLsizei width ); + + +extern void (*glCopyTexSubImage2D)( GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLint x, GLint y, + GLsizei width, GLsizei height ); + + + + +/* + * Evaluators + */ + +extern void (*glMap1d)( GLenum target, GLdouble u1, GLdouble u2, + GLint stride, + GLint order, const GLdouble *points ); +extern void (*glMap1f)( GLenum target, GLfloat u1, GLfloat u2, + GLint stride, + GLint order, const GLfloat *points ); + +extern void (*glMap2d)( GLenum target, + GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, + GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, + const GLdouble *points ); +extern void (*glMap2f)( GLenum target, + GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, + GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, + const GLfloat *points ); + +extern void (*glGetMapdv)( GLenum target, GLenum query, GLdouble *v ); +extern void (*glGetMapfv)( GLenum target, GLenum query, GLfloat *v ); +extern void (*glGetMapiv)( GLenum target, GLenum query, GLint *v ); + +extern void (*glEvalCoord1d)( GLdouble u ); +extern void (*glEvalCoord1f)( GLfloat u ); + +extern void (*glEvalCoord1dv)( const GLdouble *u ); +extern void (*glEvalCoord1fv)( const GLfloat *u ); + +extern void (*glEvalCoord2d)( GLdouble u, GLdouble v ); +extern void (*glEvalCoord2f)( GLfloat u, GLfloat v ); + +extern void (*glEvalCoord2dv)( const GLdouble *u ); +extern void (*glEvalCoord2fv)( const GLfloat *u ); + +extern void (*glMapGrid1d)( GLint un, GLdouble u1, GLdouble u2 ); +extern void (*glMapGrid1f)( GLint un, GLfloat u1, GLfloat u2 ); + +extern void (*glMapGrid2d)( GLint un, GLdouble u1, GLdouble u2, + GLint vn, GLdouble v1, GLdouble v2 ); +extern void (*glMapGrid2f)( GLint un, GLfloat u1, GLfloat u2, + GLint vn, GLfloat v1, GLfloat v2 ); + +extern void (*glEvalPoint1)( GLint i ); + +extern void (*glEvalPoint2)( GLint i, GLint j ); + +extern void (*glEvalMesh1)( GLenum mode, GLint i1, GLint i2 ); + +extern void (*glEvalMesh2)( GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2 ); + + + +/* + * Fog + */ + +extern void (*glFogf)( GLenum pname, GLfloat param ); + +extern void (*glFogi)( GLenum pname, GLint param ); + +extern void (*glFogfv)( GLenum pname, const GLfloat *params ); + +extern void (*glFogiv)( GLenum pname, const GLint *params ); + + + +/* + * Selection and Feedback + */ + +extern void (*glFeedbackBuffer)( GLsizei size, GLenum type, GLfloat *buffer ); + +extern void (*glPassThrough)( GLfloat token ); + +extern void (*glSelectBuffer)( GLsizei size, GLuint *buffer ); + +extern void (*glInitNames)( void ); + +extern void (*glLoadName)( GLuint name ); + +extern void (*glPushName)( GLuint name ); + +extern void (*glPopName)( void ); + + + +/* 1.2 functions */ +extern void (*glDrawRangeElements)( GLenum mode, GLuint start, + GLuint end, GLsizei count, GLenum type, const GLvoid *indices ); + +extern void (*glTexImage3D)( GLenum target, GLint level, + GLint internalFormat, + GLsizei width, GLsizei height, + GLsizei depth, GLint border, + GLenum format, GLenum type, + const GLvoid *pixels ); + +extern void (*glTexSubImage3D)( GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLint zoffset, GLsizei width, + GLsizei height, GLsizei depth, + GLenum format, + GLenum type, const GLvoid *pixels); + +extern void (*glCopyTexSubImage3D)( GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLint zoffset, GLint x, + GLint y, GLsizei width, + GLsizei height ); + + +/* 1.2 imaging extension functions */ + +extern void (*glColorTable)( GLenum target, GLenum internalformat, + GLsizei width, GLenum format, + GLenum type, const GLvoid *table ); + +extern void (*glColorSubTable)( GLenum target, + GLsizei start, GLsizei count, + GLenum format, GLenum type, + const GLvoid *data ); + +extern void (*glColorTableParameteriv)( GLenum target, GLenum pname, + const GLint *params); + +extern void (*glColorTableParameterfv)( GLenum target, GLenum pname, + const GLfloat *params); + +extern void (*glCopyColorSubTable)( GLenum target, GLsizei start, + GLint x, GLint y, GLsizei width ); + +extern void (*glCopyColorTable)( GLenum target, GLenum internalformat, + GLint x, GLint y, GLsizei width ); + +extern void (*glGetColorTable)( GLenum target, GLenum format, + GLenum type, GLvoid *table ); + +extern void (*glGetColorTableParameterfv)( GLenum target, GLenum pname, + GLfloat *params ); + +extern void (*glGetColorTableParameteriv)( GLenum target, GLenum pname, + GLint *params ); + +extern void (*glBlendEquation)( GLenum mode ); + +extern void (*glBlendColor)( GLclampf red, GLclampf green, + GLclampf blue, GLclampf alpha ); + +extern void (*glHistogram)( GLenum target, GLsizei width, + GLenum internalformat, GLboolean sink ); + +extern void (*glResetHistogram)( GLenum target ); + +extern void (*glGetHistogram)( GLenum target, GLboolean reset, + GLenum format, GLenum type, + GLvoid *values ); + +extern void (*glGetHistogramParameterfv)( GLenum target, GLenum pname, + GLfloat *params ); + +extern void (*glGetHistogramParameteriv)( GLenum target, GLenum pname, + GLint *params ); + +extern void (*glMinmax)( GLenum target, GLenum internalformat, + GLboolean sink ); + +extern void (*glResetMinmax)( GLenum target ); + +extern void (*glGetMinmax)( GLenum target, GLboolean reset, + GLenum format, GLenum types, + GLvoid *values ); + +extern void (*glGetMinmaxParameterfv)( GLenum target, GLenum pname, + GLfloat *params ); + +extern void (*glGetMinmaxParameteriv)( GLenum target, GLenum pname, + GLint *params ); + +extern void (*glConvolutionFilter1D)( GLenum target, + GLenum internalformat, GLsizei width, GLenum format, GLenum type, + const GLvoid *image ); + +extern void (*glConvolutionFilter2D)( GLenum target, + GLenum internalformat, GLsizei width, GLsizei height, GLenum format, + GLenum type, const GLvoid *image ); + +extern void (*glConvolutionParameterf)( GLenum target, GLenum pname, + GLfloat params ); + +extern void (*glConvolutionParameterfv)( GLenum target, GLenum pname, + const GLfloat *params ); + +extern void (*glConvolutionParameteri)( GLenum target, GLenum pname, + GLint params ); + +extern void (*glConvolutionParameteriv)( GLenum target, GLenum pname, + const GLint *params ); + +extern void (*glCopyConvolutionFilter1D)( GLenum target, + GLenum internalformat, GLint x, GLint y, GLsizei width ); + +extern void (*glCopyConvolutionFilter2D)( GLenum target, + GLenum internalformat, GLint x, GLint y, GLsizei width, + GLsizei height); + +extern void (*glGetConvolutionFilter)( GLenum target, GLenum format, + GLenum type, GLvoid *image ); + +extern void (*glGetConvolutionParameterfv)( GLenum target, GLenum pname, + GLfloat *params ); + +extern void (*glGetConvolutionParameteriv)( GLenum target, GLenum pname, + GLint *params ); + +extern void (*glSeparableFilter2D)( GLenum target, + GLenum internalformat, GLsizei width, GLsizei height, GLenum format, + GLenum type, const GLvoid *row, const GLvoid *column ); + +extern void (*glGetSeparableFilter)( GLenum target, GLenum format, + GLenum type, GLvoid *row, GLvoid *column, GLvoid *span ); + + + +/* + * GL_ARB_multitexture (ARB extension 1 and OpenGL 1.2.1) + */ +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 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 + +extern void (*glActiveTextureARB)( GLenum texture); +extern void (*glClientActiveTextureARB)( GLenum texture); +extern void (*glMultiTexCoord1dARB)( GLenum target, GLdouble s); +extern void (*glMultiTexCoord1dvARB)( GLenum target, const GLdouble *v); +extern void (*glMultiTexCoord1fARB)( GLenum target, GLfloat s); +extern void (*glMultiTexCoord1fvARB)( GLenum target, const GLfloat *v); +extern void (*glMultiTexCoord1iARB)( GLenum target, GLint s); +extern void (*glMultiTexCoord1ivARB)( GLenum target, const GLint *v); +extern void (*glMultiTexCoord1sARB)( GLenum target, GLshort s); +extern void (*glMultiTexCoord1svARB)( GLenum target, const GLshort *v); +extern void (*glMultiTexCoord2dARB)( GLenum target, GLdouble s, GLdouble t); +extern void (*glMultiTexCoord2dvARB)( GLenum target, const GLdouble *v); +extern void (*glMultiTexCoord2fARB)( GLenum target, GLfloat s, GLfloat t); +extern void (*glMultiTexCoord2fvARB)( GLenum target, const GLfloat *v); +extern void (*glMultiTexCoord2iARB)( GLenum target, GLint s, GLint t); +extern void (*glMultiTexCoord2ivARB)( GLenum target, const GLint *v); +extern void (*glMultiTexCoord2sARB)( GLenum target, GLshort s, GLshort t); +extern void (*glMultiTexCoord2svARB)( GLenum target, const GLshort *v); +extern void (*glMultiTexCoord3dARB)( GLenum target, GLdouble s, GLdouble t, GLdouble r); +extern void (*glMultiTexCoord3dvARB)( GLenum target, const GLdouble *v); +extern void (*glMultiTexCoord3fARB)( GLenum target, GLfloat s, GLfloat t, GLfloat r); +extern void (*glMultiTexCoord3fvARB)( GLenum target, const GLfloat *v); +extern void (*glMultiTexCoord3iARB)( GLenum target, GLint s, GLint t, GLint r); +extern void (*glMultiTexCoord3ivARB)( GLenum target, const GLint *v); +extern void (*glMultiTexCoord3sARB)( GLenum target, GLshort s, GLshort t, GLshort r); +extern void (*glMultiTexCoord3svARB)( GLenum target, const GLshort *v); +extern void (*glMultiTexCoord4dARB)( GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern void (*glMultiTexCoord4dvARB)( GLenum target, const GLdouble *v); +extern void (*glMultiTexCoord4fARB)( GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void (*glMultiTexCoord4fvARB)( GLenum target, const GLfloat *v); +extern void (*glMultiTexCoord4iARB)( GLenum target, GLint s, GLint t, GLint r, GLint q); +extern void (*glMultiTexCoord4ivARB)( GLenum target, const GLint *v); +extern void (*glMultiTexCoord4sARB)( GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +extern void (*glMultiTexCoord4svARB)( GLenum target, const GLshort *v); + +#endif /* GL_ARB_multitexture */ + + +/* + * GLU function pointers + */ +extern GLint (*gluProject)( GLdouble objx, GLdouble objy, GLdouble objz, + const GLdouble modelMatrix[16], + const GLdouble projMatrix[16], + const GLint viewport[4], + GLdouble *winx, GLdouble *winy, + GLdouble *winz ); + +extern GLint (*gluUnProject)( GLdouble winx, GLdouble winy, + GLdouble winz, + const GLdouble modelMatrix[16], + const GLdouble projMatrix[16], + const GLint viewport[4], + GLdouble *objx, GLdouble *objy, + GLdouble *objz ); + +/* + * GLext + */ +#ifndef 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 +#endif + +#ifndef 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 +#endif + +#ifndef 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 + +#ifndef 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 +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef 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 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef 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 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef 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 GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef 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 +#endif + +#ifndef 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 + +#ifndef 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 +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef 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 + +#ifndef 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 +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#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_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 + +#ifndef 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 +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#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 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef 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 + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifndef 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 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef 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 + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#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_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 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#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 + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef 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 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef 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 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef 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 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef 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 + +#ifndef 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 +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef 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 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef 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 +#endif + +#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 + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef 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 +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef 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 +#endif + +#ifndef 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 + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef 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 + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef 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 + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef 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 + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW_MATRIX1_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#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 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef 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 +#endif + +#ifndef 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 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef 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 + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef 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 + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef 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 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef 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 + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef 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 +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + +/* A forgotten token. */ +#define GL_CLAMP_TO_EDGE_EXT 0x812F + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +extern void (*glLoadTransposeMatrixfARB) (const GLfloat *); +extern void (*glLoadTransposeMatrixdARB) (const GLdouble *); +extern void (*glMultTransposeMatrixfARB) (const GLfloat *); +extern void (*glMultTransposeMatrixdARB) (const GLdouble *); +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +extern void (*glSampleCoverageARB) (GLclampf, GLboolean); +extern void (*glSamplePassARB) (GLenum); +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +extern void (*glCompressedTexImage3DARB) (GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void (*glCompressedTexImage2DARB) (GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void (*glCompressedTexImage1DARB) (GLenum, GLint, GLint, GLsizei, GLint, GLsizei, const GLvoid *); +extern void (*glCompressedTexSubImage3DARB) (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void (*glCompressedTexSubImage2DARB) (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void (*glCompressedTexSubImage1DARB) (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void (*glGetCompressedTexImageARB) (GLenum, GLint, void *); +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +extern void (*glBlendColorEXT) (GLclampf, GLclampf, GLclampf, GLclampf); +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +extern void (*glPolygonOffsetEXT) (GLfloat, GLfloat); +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +extern void (*glTexImage3DEXT) (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +#endif + +#ifndef GL_EXT_subtexture +extern void (*glTexSubImage3DEXT) (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +extern void (*glGetTexFilterFuncSGIS) (GLenum, GLenum, GLfloat *); +extern void (*glTexFilterFuncSGIS) (GLenum, GLenum, GLsizei, const GLfloat *); +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +extern void (*glTexSubImage1DEXT) (GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *); +extern void (*glTexSubImage2DEXT) (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +extern void (*glCopyTexImage1DEXT) (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint); +extern void (*glCopyTexImage2DEXT) (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint); +extern void (*glCopyTexSubImage1DEXT) (GLenum, GLint, GLint, GLint, GLint, GLsizei); +extern void (*glCopyTexSubImage2DEXT) (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +extern void (*glCopyTexSubImage3DEXT) (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +extern void (*glGetHistogramEXT) (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void (*glGetHistogramParameterfvEXT) (GLenum, GLenum, GLfloat *); +extern void (*glGetHistogramParameterivEXT) (GLenum, GLenum, GLint *); +extern void (*glGetMinmaxEXT) (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void (*glGetMinmaxParameterfvEXT) (GLenum, GLenum, GLfloat *); +extern void (*glGetMinmaxParameterivEXT) (GLenum, GLenum, GLint *); +extern void (*glHistogramEXT) (GLenum, GLsizei, GLenum, GLboolean); +extern void (*glMinmaxEXT) (GLenum, GLenum, GLboolean); +extern void (*glResetHistogramEXT) (GLenum); +extern void (*glResetMinmaxEXT) (GLenum); +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +extern void (*glConvolutionFilter1DEXT) (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void (*glConvolutionFilter2DEXT) (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void (*glConvolutionParameterfEXT) (GLenum, GLenum, GLfloat); +extern void (*glConvolutionParameterfvEXT) (GLenum, GLenum, const GLfloat *); +extern void (*glConvolutionParameteriEXT) (GLenum, GLenum, GLint); +extern void (*glConvolutionParameterivEXT) (GLenum, GLenum, const GLint *); +extern void (*glCopyConvolutionFilter1DEXT) (GLenum, GLenum, GLint, GLint, GLsizei); +extern void (*glCopyConvolutionFilter2DEXT) (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +extern void (*glGetConvolutionFilterEXT) (GLenum, GLenum, GLenum, GLvoid *); +extern void (*glGetConvolutionParameterfvEXT) (GLenum, GLenum, GLfloat *); +extern void (*glGetConvolutionParameterivEXT) (GLenum, GLenum, GLint *); +extern void (*glGetSeparableFilterEXT) (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +extern void (*glSeparableFilter2DEXT) (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +extern void (*glColorTableSGI) (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void (*glColorTableParameterfvSGI) (GLenum, GLenum, const GLfloat *); +extern void (*glColorTableParameterivSGI) (GLenum, GLenum, const GLint *); +extern void (*glCopyColorTableSGI) (GLenum, GLenum, GLint, GLint, GLsizei); +extern void (*glGetColorTableSGI) (GLenum, GLenum, GLenum, GLvoid *); +extern void (*glGetColorTableParameterfvSGI) (GLenum, GLenum, GLfloat *); +extern void (*glGetColorTableParameterivSGI) (GLenum, GLenum, GLint *); +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +extern void (*glPixelTexGenSGIX) (GLenum); +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +extern void (*glPixelTexGenParameteriSGIS) (GLenum, GLint); +extern void (*glPixelTexGenParameterivSGIS) (GLenum, const GLint *); +extern void (*glPixelTexGenParameterfSGIS) (GLenum, GLfloat); +extern void (*glPixelTexGenParameterfvSGIS) (GLenum, const GLfloat *); +extern void (*glGetPixelTexGenParameterivSGIS) (GLenum, GLint *); +extern void (*glGetPixelTexGenParameterfvSGIS) (GLenum, GLfloat *); +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +extern void (*glTexImage4DSGIS) (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void (*glTexSubImage4DSGIS) (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +extern GLboolean (*glAreTexturesResidentEXT) (GLsizei, const GLuint *, GLboolean *); +extern void (*glBindTextureEXT) (GLenum, GLuint); +extern void (*glDeleteTexturesEXT) (GLsizei, const GLuint *); +extern void (*glGenTexturesEXT) (GLsizei, GLuint *); +extern GLboolean (*glIsTextureEXT) (GLuint); +extern void (*glPrioritizeTexturesEXT) (GLsizei, const GLuint *, const GLclampf *); +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +extern void (*glDetailTexFuncSGIS) (GLenum, GLsizei, const GLfloat *); +extern void (*glGetDetailTexFuncSGIS) (GLenum, GLfloat *); +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +extern void (*glSharpenTexFuncSGIS) (GLenum, GLsizei, const GLfloat *); +extern void (*glGetSharpenTexFuncSGIS) (GLenum, GLfloat *); +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +extern void (*glSampleMaskSGIS) (GLclampf, GLboolean); +extern void (*glSamplePatternSGIS) (GLenum); +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +extern void (*glArrayElementEXT) (GLint); +extern void (*glColorPointerEXT) (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void (*glDrawArraysEXT) (GLenum, GLint, GLsizei); +extern void (*glEdgeFlagPointerEXT) (GLsizei, GLsizei, const GLboolean *); +extern void (*glGetPointervEXT) (GLenum, GLvoid* *); +extern void (*glIndexPointerEXT) (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void (*glNormalPointerEXT) (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void (*glTexCoordPointerEXT) (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void (*glVertexPointerEXT) (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +extern void (*glBlendEquationEXT) (GLenum); +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +extern void (*glSpriteParameterfSGIX) (GLenum, GLfloat); +extern void (*glSpriteParameterfvSGIX) (GLenum, const GLfloat *); +extern void (*glSpriteParameteriSGIX) (GLenum, GLint); +extern void (*glSpriteParameterivSGIX) (GLenum, const GLint *); +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +extern void (*glPointParameterfEXT) (GLenum, GLfloat); +extern void (*glPointParameterfvEXT) (GLenum, const GLfloat *); +extern void (*glPointParameterfSGIS) (GLenum, GLfloat); +extern void (*glPointParameterfvSGIS) (GLenum, const GLfloat *); +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +extern GLint (*glGetInstrumentsSGIX) (void); +extern void (*glInstrumentsBufferSGIX) (GLsizei, GLint *); +extern GLint (*glPollInstrumentsSGIX) (GLint *); +extern void (*glReadInstrumentsSGIX) (GLint); +extern void (*glStartInstrumentsSGIX) (void); +extern void (*glStopInstrumentsSGIX) (GLint); +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +extern void (*glFrameZoomSGIX) (GLint); +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +extern void (*glTagSampleBufferSGIX) (void); +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +extern void (*glReferencePlaneSGIX) (const GLdouble *); +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +extern void (*glFlushRasterSGIX) (void); +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +extern void (*glFogFuncSGIS) (GLsizei, const GLfloat *); +extern void (*glGetFogFuncSGIS) (const GLfloat *); +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +extern void (*glImageTransformParameteriHP) (GLenum, GLenum, GLint); +extern void (*glImageTransformParameterfHP) (GLenum, GLenum, GLfloat); +extern void (*glImageTransformParameterivHP) (GLenum, GLenum, const GLint *); +extern void (*glImageTransformParameterfvHP) (GLenum, GLenum, const GLfloat *); +extern void (*glGetImageTransformParameterivHP) (GLenum, GLenum, GLint *); +extern void (*glGetImageTransformParameterfvHP) (GLenum, GLenum, GLfloat *); +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +extern void (*glColorSubTableEXT) (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void (*glCopyColorSubTableEXT) (GLenum, GLsizei, GLint, GLint, GLsizei); +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +extern void (*glHintPGI) (GLenum, GLint); +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +extern void (*glColorTableEXT) (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void (*glGetColorTableEXT) (GLenum, GLenum, GLenum, GLvoid *); +extern void (*glGetColorTableParameterivEXT) (GLenum, GLenum, GLint *); +extern void (*glGetColorTableParameterfvEXT) (GLenum, GLenum, GLfloat *); +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +extern void (*glGetListParameterfvSGIX) (GLuint, GLenum, GLfloat *); +extern void (*glGetListParameterivSGIX) (GLuint, GLenum, GLint *); +extern void (*glListParameterfSGIX) (GLuint, GLenum, GLfloat); +extern void (*glListParameterfvSGIX) (GLuint, GLenum, const GLfloat *); +extern void (*glListParameteriSGIX) (GLuint, GLenum, GLint); +extern void (*glListParameterivSGIX) (GLuint, GLenum, const GLint *); +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +extern void (*glIndexMaterialEXT) (GLenum, GLenum); +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +extern void (*glIndexFuncEXT) (GLenum, GLclampf); +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +extern void (*glLockArraysEXT) (GLint, GLsizei); +extern void (*glUnlockArraysEXT) (void); +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +extern void (*glCullParameterdvEXT) (GLenum, GLdouble *); +extern void (*glCullParameterfvEXT) (GLenum, GLfloat *); +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +extern void (*glFragmentColorMaterialSGIX) (GLenum, GLenum); +extern void (*glFragmentLightfSGIX) (GLenum, GLenum, GLfloat); +extern void (*glFragmentLightfvSGIX) (GLenum, GLenum, const GLfloat *); +extern void (*glFragmentLightiSGIX) (GLenum, GLenum, GLint); +extern void (*glFragmentLightivSGIX) (GLenum, GLenum, const GLint *); +extern void (*glFragmentLightModelfSGIX) (GLenum, GLfloat); +extern void (*glFragmentLightModelfvSGIX) (GLenum, const GLfloat *); +extern void (*glFragmentLightModeliSGIX) (GLenum, GLint); +extern void (*glFragmentLightModelivSGIX) (GLenum, const GLint *); +extern void (*glFragmentMaterialfSGIX) (GLenum, GLenum, GLfloat); +extern void (*glFragmentMaterialfvSGIX) (GLenum, GLenum, const GLfloat *); +extern void (*glFragmentMaterialiSGIX) (GLenum, GLenum, GLint); +extern void (*glFragmentMaterialivSGIX) (GLenum, GLenum, const GLint *); +extern void (*glGetFragmentLightfvSGIX) (GLenum, GLenum, GLfloat *); +extern void (*glGetFragmentLightivSGIX) (GLenum, GLenum, GLint *); +extern void (*glGetFragmentMaterialfvSGIX) (GLenum, GLenum, GLfloat *); +extern void (*glGetFragmentMaterialivSGIX) (GLenum, GLenum, GLint *); +extern void (*glLightEnviSGIX) (GLenum, GLint); +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +extern void (*glDrawRangeElementsEXT) (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +extern void (*glApplyTextureEXT) (GLenum); +extern void (*glTextureLightEXT) (GLenum); +extern void (*glTextureMaterialEXT) (GLenum, GLenum); +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +extern void (*glVertexPointervINTEL) (GLint, GLenum, const GLvoid* *); +extern void (*glNormalPointervINTEL) (GLenum, const GLvoid* *); +extern void (*glColorPointervINTEL) (GLint, GLenum, const GLvoid* *); +extern void (*glTexCoordPointervINTEL) (GLint, GLenum, const GLvoid* *); +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +extern void (*glPixelTransformParameteriEXT) (GLenum, GLenum, GLint); +extern void (*glPixelTransformParameterfEXT) (GLenum, GLenum, GLfloat); +extern void (*glPixelTransformParameterivEXT) (GLenum, GLenum, const GLint *); +extern void (*glPixelTransformParameterfvEXT) (GLenum, GLenum, const GLfloat *); +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +extern void (*glSecondaryColor3bEXT) (GLbyte, GLbyte, GLbyte); +extern void (*glSecondaryColor3bvEXT) (const GLbyte *); +extern void (*glSecondaryColor3dEXT) (GLdouble, GLdouble, GLdouble); +extern void (*glSecondaryColor3dvEXT) (const GLdouble *); +extern void (*glSecondaryColor3fEXT) (GLfloat, GLfloat, GLfloat); +extern void (*glSecondaryColor3fvEXT) (const GLfloat *); +extern void (*glSecondaryColor3iEXT) (GLint, GLint, GLint); +extern void (*glSecondaryColor3ivEXT) (const GLint *); +extern void (*glSecondaryColor3sEXT) (GLshort, GLshort, GLshort); +extern void (*glSecondaryColor3svEXT) (const GLshort *); +extern void (*glSecondaryColor3ubEXT) (GLubyte, GLubyte, GLubyte); +extern void (*glSecondaryColor3ubvEXT) (const GLubyte *); +extern void (*glSecondaryColor3uiEXT) (GLuint, GLuint, GLuint); +extern void (*glSecondaryColor3uivEXT) (const GLuint *); +extern void (*glSecondaryColor3usEXT) (GLushort, GLushort, GLushort); +extern void (*glSecondaryColor3usvEXT) (const GLushort *); +extern void (*glSecondaryColorPointerEXT) (GLint, GLenum, GLsizei, GLvoid *); +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +extern void (*glTextureNormalEXT) (GLenum); +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +extern void (*glMultiDrawArraysEXT) (GLenum, GLint *, GLsizei *, GLsizei); +extern void (*glMultiDrawElementsEXT) (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +extern void (*glFogCoordfEXT) (GLfloat); +extern void (*glFogCoordfvEXT) (const GLfloat *); +extern void (*glFogCoorddEXT) (GLdouble); +extern void (*glFogCoorddvEXT) (const GLdouble *); +extern void (*glFogCoordPointerEXT) (GLenum, GLsizei, const GLvoid *); +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +extern void (*glTangent3bEXT) (GLbyte, GLbyte, GLbyte); +extern void (*glTangent3bvEXT) (const GLbyte *); +extern void (*glTangent3dEXT) (GLdouble, GLdouble, GLdouble); +extern void (*glTangent3dvEXT) (const GLdouble *); +extern void (*glTangent3fEXT) (GLfloat, GLfloat, GLfloat); +extern void (*glTangent3fvEXT) (const GLfloat *); +extern void (*glTangent3iEXT) (GLint, GLint, GLint); +extern void (*glTangent3ivEXT) (const GLint *); +extern void (*glTangent3sEXT) (GLshort, GLshort, GLshort); +extern void (*glTangent3svEXT) (const GLshort *); +extern void (*glBinormal3bEXT) (GLbyte, GLbyte, GLbyte); +extern void (*glBinormal3bvEXT) (const GLbyte *); +extern void (*glBinormal3dEXT) (GLdouble, GLdouble, GLdouble); +extern void (*glBinormal3dvEXT) (const GLdouble *); +extern void (*glBinormal3fEXT) (GLfloat, GLfloat, GLfloat); +extern void (*glBinormal3fvEXT) (const GLfloat *); +extern void (*glBinormal3iEXT) (GLint, GLint, GLint); +extern void (*glBinormal3ivEXT) (const GLint *); +extern void (*glBinormal3sEXT) (GLshort, GLshort, GLshort); +extern void (*glBinormal3svEXT) (const GLshort *); +extern void (*glTangentPointerEXT) (GLenum, GLsizei, const GLvoid *); +extern void (*glBinormalPointerEXT) (GLenum, GLsizei, const GLvoid *); +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +extern void (*glFinishTextureSUNX) (void); +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +extern void (*glGlobalAlphaFactorbSUN) (GLbyte); +extern void (*glGlobalAlphaFactorsSUN) (GLshort); +extern void (*glGlobalAlphaFactoriSUN) (GLint); +extern void (*glGlobalAlphaFactorfSUN) (GLfloat); +extern void (*glGlobalAlphaFactordSUN) (GLdouble); +extern void (*glGlobalAlphaFactorubSUN) (GLubyte); +extern void (*glGlobalAlphaFactorusSUN) (GLushort); +extern void (*glGlobalAlphaFactoruiSUN) (GLuint); +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +extern void (*glReplacementCodeuiSUN) (GLuint); +extern void (*glReplacementCodeusSUN) (GLushort); +extern void (*glReplacementCodeubSUN) (GLubyte); +extern void (*glReplacementCodeuivSUN) (const GLuint *); +extern void (*glReplacementCodeusvSUN) (const GLushort *); +extern void (*glReplacementCodeubvSUN) (const GLubyte *); +extern void (*glReplacementCodePointerSUN) (GLenum, GLsizei, const GLvoid* *); +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +extern void (*glColor4ubVertex2fSUN) (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat); +extern void (*glColor4ubVertex2fvSUN) (const GLubyte *, const GLfloat *); +extern void (*glColor4ubVertex3fSUN) (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void (*glColor4ubVertex3fvSUN) (const GLubyte *, const GLfloat *); +extern void (*glColor3fVertex3fSUN) (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glColor3fVertex3fvSUN) (const GLfloat *, const GLfloat *); +extern void (*glNormal3fVertex3fSUN) (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glNormal3fVertex3fvSUN) (const GLfloat *, const GLfloat *); +extern void (*glColor4fNormal3fVertex3fSUN) (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glColor4fNormal3fVertex3fvSUN) (const GLfloat *, const GLfloat *, const GLfloat *); +extern void (*glTexCoord2fVertex3fSUN) (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glTexCoord2fVertex3fvSUN) (const GLfloat *, const GLfloat *); +extern void (*glTexCoord4fVertex4fSUN) (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glTexCoord4fVertex4fvSUN) (const GLfloat *, const GLfloat *); +extern void (*glTexCoord2fColor4ubVertex3fSUN) (GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void (*glTexCoord2fColor4ubVertex3fvSUN) (const GLfloat *, const GLubyte *, const GLfloat *); +extern void (*glTexCoord2fColor3fVertex3fSUN) (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glTexCoord2fColor3fVertex3fvSUN) (const GLfloat *, const GLfloat *, const GLfloat *); +extern void (*glTexCoord2fNormal3fVertex3fSUN) (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glTexCoord2fNormal3fVertex3fvSUN) (const GLfloat *, const GLfloat *, const GLfloat *); +extern void (*glTexCoord2fColor4fNormal3fVertex3fSUN) (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glTexCoord2fColor4fNormal3fVertex3fvSUN) (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void (*glTexCoord4fColor4fNormal3fVertex4fSUN) (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glTexCoord4fColor4fNormal3fVertex4fvSUN) (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void (*glReplacementCodeuiVertex3fSUN) (GLenum, GLfloat, GLfloat, GLfloat); +extern void (*glReplacementCodeuiVertex3fvSUN) (const GLenum *, const GLfloat *); +extern void (*glReplacementCodeuiColor4ubVertex3fSUN) (GLenum, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void (*glReplacementCodeuiColor4ubVertex3fvSUN) (const GLenum *, const GLubyte *, const GLfloat *); +extern void (*glReplacementCodeuiColor3fVertex3fSUN) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glReplacementCodeuiColor3fVertex3fvSUN) (const GLenum *, const GLfloat *, const GLfloat *); +extern void (*glReplacementCodeuiNormal3fVertex3fSUN) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glReplacementCodeuiNormal3fVertex3fvSUN) (const GLenum *, const GLfloat *, const GLfloat *); +extern void (*glReplacementCodeuiColor4fNormal3fVertex3fSUN) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glReplacementCodeuiColor4fNormal3fVertex3fvSUN) (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void (*glReplacementCodeuiTexCoord2fVertex3fSUN) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glReplacementCodeuiTexCoord2fVertex3fvSUN) (const GLenum *, const GLfloat *, const GLfloat *); +extern void (*glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN) (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void (*glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN) (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +extern void (*glBlendFuncSeparateEXT) (GLenum, GLenum, GLenum, GLenum); +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +extern void (*glVertexWeightfEXT) (GLfloat); +extern void (*glVertexWeightfvEXT) (const GLfloat *); +extern void (*glVertexWeightPointerEXT) (GLsizei, GLenum, GLsizei, const GLvoid *); +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +extern void (*glFlushVertexArrayRangeNV) (void); +extern void (*glVertexArrayRangeNV) (GLsizei, const GLvoid *); +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +extern void (*glCombinerParameterfvNV) (GLenum, const GLfloat *); +extern void (*glCombinerParameterfNV) (GLenum, GLfloat); +extern void (*glCombinerParameterivNV) (GLenum, const GLint *); +extern void (*glCombinerParameteriNV) (GLenum, GLint); +extern void (*glCombinerInputNV) (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); +extern void (*glCombinerOutputNV) (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); +extern void (*glFinalCombinerInputNV) (GLenum, GLenum, GLenum, GLenum); +extern void (*glGetCombinerInputParameterfvNV) (GLenum, GLenum, GLenum, GLenum, GLfloat *); +extern void (*glGetCombinerInputParameterivNV) (GLenum, GLenum, GLenum, GLenum, GLint *); +extern void (*glGetCombinerOutputParameterfvNV) (GLenum, GLenum, GLenum, GLfloat *); +extern void (*glGetCombinerOutputParameterivNV) (GLenum, GLenum, GLenum, GLint *); +extern void (*glGetFinalCombinerInputParameterfvNV) (GLenum, GLenum, GLfloat *); +extern void (*glGetFinalCombinerInputParameterivNV) (GLenum, GLenum, GLint *); +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +extern void (*glResizeBuffersMESA) (void); +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +extern void (*glWindowPos2dMESA) (GLdouble, GLdouble); +extern void (*glWindowPos2dvMESA) (const GLdouble *); +extern void (*glWindowPos2fMESA) (GLfloat, GLfloat); +extern void (*glWindowPos2fvMESA) (const GLfloat *); +extern void (*glWindowPos2iMESA) (GLint, GLint); +extern void (*glWindowPos2ivMESA) (const GLint *); +extern void (*glWindowPos2sMESA) (GLshort, GLshort); +extern void (*glWindowPos2svMESA) (const GLshort *); +extern void (*glWindowPos3dMESA) (GLdouble, GLdouble, GLdouble); +extern void (*glWindowPos3dvMESA) (const GLdouble *); +extern void (*glWindowPos3fMESA) (GLfloat, GLfloat, GLfloat); +extern void (*glWindowPos3fvMESA) (const GLfloat *); +extern void (*glWindowPos3iMESA) (GLint, GLint, GLint); +extern void (*glWindowPos3ivMESA) (const GLint *); +extern void (*glWindowPos3sMESA) (GLshort, GLshort, GLshort); +extern void (*glWindowPos3svMESA) (const GLshort *); +extern void (*glWindowPos4dMESA) (GLdouble, GLdouble, GLdouble, GLdouble); +extern void (*glWindowPos4dvMESA) (const GLdouble *); +extern void (*glWindowPos4fMESA) (GLfloat, GLfloat, GLfloat, GLfloat); +extern void (*glWindowPos4fvMESA) (const GLfloat *); +extern void (*glWindowPos4iMESA) (GLint, GLint, GLint, GLint); +extern void (*glWindowPos4ivMESA) (const GLint *); +extern void (*glWindowPos4sMESA) (GLshort, GLshort, GLshort, GLshort); +extern void (*glWindowPos4svMESA) (const GLshort *); +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +extern void (*glMultiModeDrawArraysIBM) (GLenum, const GLint *, const GLsizei *, GLsizei, GLint); +extern void (*glMultiModeDrawElementsIBM) (const GLenum *, const GLsizei *, GLenum, const GLvoid* *, GLsizei, GLint); +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +extern void (*glColorPointerListIBM) (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void (*glSecondaryColorPointerListIBM) (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void (*glEdgeFlagPointerListIBM) (GLint, const GLboolean* *, GLint); +extern void (*glFogCoordPointerListIBM) (GLenum, GLint, const GLvoid* *, GLint); +extern void (*glIndexPointerListIBM) (GLenum, GLint, const GLvoid* *, GLint); +extern void (*glNormalPointerListIBM) (GLenum, GLint, const GLvoid* *, GLint); +extern void (*glTexCoordPointerListIBM) (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void (*glVertexPointerListIBM) (GLint, GLenum, GLint, const GLvoid* *, GLint); +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +extern void (*glTbufferMask3DFX) (GLuint); +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +extern void (*glSampleMaskEXT) (GLclampf, GLboolean); +extern void (*glSamplePatternEXT) (GLenum); +#endif + +#ifndef GL_SGI_vertex_preclip +#define GL_SGI_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +extern void (*glTextureColorMaskSGIS) (GLboolean, GLboolean, GLboolean, GLboolean); +#endif + +#ifndef GL_EXT_vertex_buffer +#define GL_EXT_vertex_buffer 1 + +#define GL_TRIBESMTVFMT_EXT 0x8702 +#define GL_TRIBESMTNVFMT_EXT 0x8703 +#define GL_TRIBESFTVFMT_EXT 0x8704 +#define GL_TRIBESFMTVFMT_EXT 0x8705 + +extern GLboolean (*glAvailableVertexBufferEXT)(); +extern GLint (*glAllocateVertexBufferEXT)(GLsizei size, GLint format, GLboolean preserve); +extern void* (*glLockVertexBufferEXT)(GLint handle, GLsizei size); +extern void (*glUnlockVertexBufferEXT)(GLint handle); +extern void (*glSetVertexBufferEXT)(GLint handle); +extern void (*glOffsetVertexBufferEXT)(GLint handle, GLuint offset); +extern void (*glFillVertexBufferEXT)(GLint handle, GLint first, GLsizei count); +extern void (*glFreeVertexBufferEXT)(GLint handle); +#endif + + +/* + * GL state information. + */ +struct GLState +{ + bool suppARBMultitexture; + bool suppPackedPixels; + bool suppTexEnvAdd; + bool suppLockedArrays; + bool suppTextureEnvCombine; + bool suppVertexArrayRange; + bool suppFogCoord; + bool suppEdgeClamp; + bool suppTextureCompression; + bool suppS3TC; + bool suppFXT1; + bool suppTexAnisotropic; + bool suppPalettedTexture; + unsigned int triCount[4]; + unsigned int primCount[4]; + unsigned int primMode; // 0-3 + + GLfloat maxAnisotropy; + GLint maxTextureUnits; +}; + +extern GLState gGLState; + +extern bool gOpenGLDisablePT; +extern bool gOpenGLDisableCVA; +extern bool gOpenGLDisableTEC; +extern bool gOpenGLDisableARBMT; +extern bool gOpenGLDisableFC; +extern bool gOpenGLDisableTCompress; +extern bool gOpenGLNoEnvColor; +extern float gOpenGLGammaCorrection; +extern bool gOpenGLNoDrawArraysAlpha; + +/* + * Inline state helpers. + */ +inline void dglSetRenderPrimType(unsigned int type) +{ + gGLState.primMode = type; +} + +inline void dglClearPrimMetrics() +{ + for(int i = 0; i < 4; i++) + gGLState.triCount[i] = gGLState.primCount[i] = 0; +} + +inline bool dglDoesSupportPalettedTexture() +{ + return gGLState.suppPalettedTexture && (gOpenGLDisablePT == false); +} + +inline bool dglDoesSupportCompiledVertexArray() +{ + return gGLState.suppLockedArrays && (gOpenGLDisableCVA == false); +} + +inline bool dglDoesSupportTextureEnvCombine() +{ + return gGLState.suppTextureEnvCombine && (gOpenGLDisableTEC == false); +} + +inline bool dglDoesSupportARBMultitexture() +{ + return gGLState.suppARBMultitexture && (gOpenGLDisableARBMT == false); +} + +inline bool dglDoesSupportVertexArrayRange() +{ + return gGLState.suppVertexArrayRange; +} + +inline bool dglDoesSupportFogCoord() +{ + return gGLState.suppFogCoord && (gOpenGLDisableFC == false); +} + +inline bool dglDoesSupportEdgeClamp() +{ + return gGLState.suppEdgeClamp; +} + +inline bool dglDoesSupportTextureCompression() +{ + return gGLState.suppTextureCompression && (gOpenGLDisableTCompress == false); +} + +inline bool dglDoesSupportS3TC() +{ + return gGLState.suppS3TC; +} + +inline bool dglDoesSupportFXT1() +{ + return gGLState.suppFXT1; +} + +inline bool dglDoesSupportTexEnvAdd() +{ + return gGLState.suppTexEnvAdd; +} + +inline bool dglDoesSupportTexAnisotropy() +{ + return gGLState.suppTexAnisotropic; +} + +inline bool dglDoesSupportVertexBuffer() +{ + return false; +} + +inline GLfloat dglGetMaxAnisotropy() +{ + return gGLState.maxAnisotropy; +} + +inline GLint dglGetMaxTextureUnits() +{ + if (dglDoesSupportARBMultitexture()) + return gGLState.maxTextureUnits; + else + return 1; +} + +#endif diff --git a/platformLinux/platformLinux.h b/platformLinux/platformLinux.h new file mode 100644 index 0000000..0477cb9 --- /dev/null +++ b/platformLinux/platformLinux.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMLINUX_H_ +#define _PLATFORMLINUX_H_ + +#include +#include +#include + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _EVENT_H_ +#include "Platform/event.h" +#endif + +class LinuxPlatformState +{ + public: + S32 width; + S32 height; + S32 bpp; + bool videoInitted; + + LinuxPlatformState( ) { + width = 0; + height = 0; + bpp = 0; + videoInitted = false; + } +}; + +extern LinuxPlatformState linuxState; + +extern bool QGL_Init( const char* glName, const char* gluName ); +extern bool QGL_EXT_Init( void ); +extern void QGL_Shutdown( void ); + +extern void GetDesktopState( void ); + +extern char* dStrncat( char* d, const char* s, size_t n ); + +extern void linuxInitTicks( void ); + +#ifdef DEDICATED +extern void linuxPostQuitMessage( void ); +#endif + +extern void createFontInit( void ); +extern void createFontShutdown( void ); + +extern void installRedBookDevices( void ); + +extern void PlatformBlitInit( void ); + +extern U8 getHatState( U8 hat ); +extern void setHatState( U8 hat, U8 state ); + +extern SDLKey translateKeyCodeToSDL( KeyCodes code ); +extern KeyCodes translateSDLToKeyCode( SDLKey sym ); + +#endif diff --git a/platformMacCarb/macCarb.rsrc b/platformMacCarb/macCarb.rsrc new file mode 100644 index 0000000..5c8efa2 --- /dev/null +++ b/platformMacCarb/macCarb.rsrc @@ -0,0 +1 @@ +(This file must be converted with BinHex 4.0) :!!"bFh*M8P0&4#!!N!F0Y'b-!*!%!3!!!!`1!!!,$J!!!DBS!5N"5S$@96#pSEj 9D`)!a%T%f"%q2!3!J(S"B")-4!abCA4bEbke,R*cFQ0bEA0dB@%aER4cG@ePF(P NCA)T!J"bFh*M8P0&4!%!rj!%!*!5TAi&A`#3"3)i[#"Zrr`6S#NJE[rm!5e3p9F "rm!S!J(d1J!e%D!U!8T&EH`e%U!MF!!d!f!Q5N4Q)M)"CK`e!bC3+%[Cr%X#!)! Y6#i#)"Z"R"'hlLi"CIDh@h-X!IqN,3(ra&`"!`J[Kia2!Me'rpl8![rc,@aH@`% Y9&m"(@cN%Ira%6!XG(KkRbF!!!%!!!IJ!!!-R!!!e!F!!`$4`!Brq@!)Irm`'2r rH"$rhkJMr[r%&rrch%ri'q*2XFrUMZr[mXrrr[12[rhjMcr'qCdqCrRr2q0rhEr "HhkIm,YqMr2qId#6pRricr`hf(rX(rrrf"[rrrJ2rrq`"Irri!2rqX!!qpm!!$h q!!!(i!!!2r`!!2rr!!(rri!(rrrJ$rrrm"rrrrJIrrri2rrrr(rrrrjrrrrqrj! `IrrrrRrrrrirrrrm(rrrq"rrrrJ2rrr`"rrri!(rri!!rrm!!$rm!*!%"d&38%` !N!8%!*!,pIG9KUUUUkU'@rIe!*!5pPYk-)#V@rH'9IQ!UUTEpJ#3$e@UUc#V9Ar i+P9996&9Ki#U93#3$2DUJ$&99P8a9999J)"EIrNU+hqVK[B!N!RfKS"EpP@'Ii# VXBHVXDZUIrG9J&9rK[B!N!IeUP@3"&c1L)K!3%!F3%"NL+U'A2C9UkVf!*!'KUT 99LZUUf3F(%!F(%!F3%"!C+Yr-BDUXB!!N!99IbY@@e9M3"a!3"a!N!6jqN!F3)L U9B"9UP8!!!$fUe[h+UU(3"a!(%"GAIRkA9hlA@3F3)HU+e99XIB!!&9EUe9rV%! F3"aGAI[mr+bXTV)cq8!F3)G9KSHU@`$eU[FUKV&N(%!FqPhl9LXVp[AfV)MjC"a !C+Vj+[QUp6'!@e@!Ud!F32PGJ9Eiq&eNC2Ifq[T!3"`Fr&5!-DVhIrJV-DTN($P GAB(jAB*!3%!FC$0M38!rJKaUT$&jX9@!UP99Ud"!A[TGq[YG3"a!("`FC$TFqrP G3%#V@e@!KSC999@V(&f"A9$lL%!F3%"!C'4GJB(jJN!F3+Z!99@VKP99-9e!qPe @+rYH(%!FAIb"q9C@qi(j3"a!Khp@@kUU9P9rKd$jAIIhr%"!(%!V+eeG9[HXL2T M3"baJ&9@X+ZUX+ZV3&eGpr@XC"a!3%"H3%$h!2R3L2P"(+ZUKUUUKS!VUSG!1IT G!2[G("a!3"a!q9Iep[Rm9iG!UkViKS"EUS#UUQ4!9ehfprkbC"a!3&f"AIAe9[R k1BLUKe@UA2HUKS#`Kd!jJ6)!pi,@V2hmqS')q9EkqPe!L,#NKUVhpBD!J+UV3%$ jJM,e!2Ah-[Vjq9hj9PeN(@5UUeZ!K[8!A+TFKXk(3$TFC'4Gq2FVqPe@9PeH3"a !Ul#!J+Vj!!$fUhpkKUU)4MVjAITA9PEjqS&H3%!F3)HUUeZ!U[B!!!"9KParUkU )3%"HC'4HC&dk3%!F(%#)X+YEJ+T9!*!&J+T@Il'UKf3F(%!F(%!F3"`FC+ZUUS# !USB!N!EfKUTFIkZ`UiGN3%!F3%"!C)HVKUZ!@kU(p3#3"rD'qB"rJ+UaUUZ(UiH (XDUVKB#'J)#'pJ#3#ICrKi#!J(qVKUUUXDU!UV"kKP@UJ2B!N!a9USD'J)"EJ*! %@i#VKS#U93#3$rC9USD!J&Z!J)DNKUUUA2B!N",e6eZ!UkUUUSC9-I8!N!`#!*! '$-99j9h!!*!*cGcHh&cG9G`!N!F-AXl0c-c-cYA!!*!&$&h-h-c-hGhFcHA!!*! %aGh-AGjHjHAFcFeF!*!%A-c0EZ-c)c[PAFcP`!!!"9cFAV)M)M)c1qh&9G!!!-h 0h,-M-M-ch6)qA0aF!!cYc&ib-MZpflkl)qA-aF!-hXhM)bZqlZl[h6)qaHA3"Fa 9XM,ElFc!cZfb1eh08-hFhM)p[Gc,ZmcG-b,Xh&cFc&XVZpfl-c+pXc5b0FeFeFc M1p[HXb-L+lhYXclFe9c-iVfmlM)c1l[Gfc)qh-jFc,2Eh1XM+qhGhYdb2Yh9AFh MfmcM-Mc,[FlYXbAFeH9Hil[!kb-cXc`0RY-Z999GaH1pX1SL-b2G$0lH2PaGeG9 E2Ec2qb-lf`$GfqAXAF9GAM[F$,rUlGlGhE2P99`&h9icfm!!c0hEhEXVAYe3$9e @ilfl[-cEhEXb2Ph9d!cYe9ilfphGhGXc)qAYeF!!aGhPicZlZlXc)MjHh9`!!!e Ge9kb)b)b-L[PAG93!!!-9GhPlV-b-c[ZAYeH!*!%aGhG99lZlPAPeGeF!*!%$0l GhH999G9GA&h!!*!&$&9GhC!%jGA!!*!(c&AGhG999G`!N!N-hH99A-!!N!K!!q! Hq$rmErjrrVrr[rfrrrIrrprlhrlqIrirr"ri"r!2m$rmIrjrr[q3%(rqIrirr!r `!!!"!*!&p[HVpk[jprB!N!EfUUZVIrL!IrQUUrB!!!$epS$fcUZaXD[hK[EfpJ! !KUTEUf4!3%$kC+ZUXDS!pU[hV%"GqrckqrP!USDap[AhX@6jq[[ip[IkC'6mqII i9DYHq[Z#3%"NBrZ#UhQaKP@VqPhl3&hmqI[j3+Z!UkZ`UrRhr%"!A[Ijq[QaUV# UUUVkp[kb3&hjp[ckUrLUpiD`32RhpIhmqIRkC,#NprDVcUTFqIVhqS&H3+Z`U[N !KUUaUQ4NC&e!C+Z`UUS!!2EfqE#aUkZ(XDZVJ2Ee!!!!pSHUKUZUXDU`UUVf!*! 'p[@!UkUNpIB!N!H!!!$GlHh0!!!!cHlFhHhX!!$0hZlZlGl!$GhV-cfqcG$1hM[ Zlp2XlGhVhHc1kllFh1[HXchH[Yh-lGillHdqhZlYcMc0lYlGhHh2mlh1lZh0kpc qlYfqh0lHhGhGXqlG$GlVZl1qlF!-cHlZkqlH!!$1hHhGlG`!!!$-hYc0!*!&4!I J'"JJ"(!1H"lm2rjrrj!%rRrm2hJHF!iJ""JB"q!(i"ri2rarrRrqrj!-IrjrrMr m(rJ(i!!(!!F!!!"%"q!I'$m-I`Cr![m"r`(r!B$rJ2q!rd$qB2i`r"Mi"q!(i"r i2rarrRrqrj!-IrjrrMrm(rJ(i!!(!!F!!!"%"q!Iq$rmArT2mSIKJm'"N!5$`BI K6r*IqMrm(rJ(i!IJ(rJrr(rqIrlrN!arrRrq2r`Iq!IJ!!F!"`!!!%3(i"Mi-2a JrN$qJ2q!ri$rr`(r!Im"I`*r"Mm-(aJ(i!IJ(rJrr(rqIrlrN!arrRrq2r`Iq!I J!!F!"`!!!!3!!3#!!!!!(J#!!*!)rj!%!43)3@*[GA3Z,Li!N!J9%%eeE(4T3RP dC80SBA*6CA4%9e*%!!!!G3"4)!!!!"@T-M!`-5"(BA*KCf9(B@ePFbjMEfeB9M% b)%9ZCfPZC5#T-M!`-5"(BA*KCf9(B@ePFbjMEfd08'pbG'P[ER-JU6)`-$%J8fP PFR*K)%pZE'PZC5`J5@jM,Je"E'`J8QPRD(4c)&*PFf9bGQ9N,J!!!"a(9M%b!!! !!8C548B!N!@!5801)`#3"B!!N!FK)&Ba-L"&EQGTEQ8JU6)`-$%J4f&bB@GP4f& YCA-ZBfpY!!!!!J#3"!%!!!!-$J!!#`i!!!'Q%mE*e!0H!!!!(!'#!!p*3diM!!! !JNC548B!!!#1GQ9bF`!!!*TTBf`i!!!!TQPME$3!!!#bD@0c)`!!!,jTBh-i!!! !bQPMFc3!!!$@689193!!!1*$99*6!!-!lNe#39)!!!%H9%e36!!!!5T#6N4-!!! "0Q0KFQ)!!!eBa-J!!!8j03N06!!!"@J#!rrm!N!36aXGJ!)$rr`!!!336aXL X!!(rr`!!#NB6aXM!!)$rr`!!!3m6aXFB!)$rr`!!"4-6aXFF!)$rr`!!"aF6aXF 8!)$rr`!!"eX6aXF3!)$rr`!!#&m6aXFJ!)$rr`!!#JX6aXKB!-J!N!3*Za2'b+! !b3!%!!!)ia2'b*`!bJ!)!!!*+a2'b*J!b`!-!!!*Fa2'b*3!J2rr!!!+!a2'b)` !JJ!I!!!+,42'bB`!J2rr!!!+[a2'afJ!!2rr!!!+h`#3"a!!!!VM%mE(A!#!rrm !!!X)!*!%!f*L-30LBM)$BQ)c!f*L0!j2GfjPFL"bCA0[GA*MC3403N068G`: \ No newline at end of file diff --git a/platformMacCarb/macCarbAudio.cc b/platformMacCarb/macCarbAudio.cc new file mode 100644 index 0000000..7a9dfcc --- /dev/null +++ b/platformMacCarb/macCarbAudio.cc @@ -0,0 +1,260 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformMacCarb/platformMacCarb.h" +#include "console/console.h" +#include "PlatformMacCarb/platformAL.h" + +#define TRY_REAL_OPENAL 0 + +#if TRY_REAL_OPENAL + +#else + +#define MILES_REENTRANT_CHECK + +// Stub functions: --------------------------------------------------------- +// AL: +ALvoid stub_alEnable( ALenum ) {} +ALvoid stub_alDisable( ALenum ) {} +ALboolean stub_alIsEnabled( ALenum ) {return(AL_FALSE);} +ALvoid stub_alHint( ALenum , ALenum ) {} +ALboolean stub_alGetBoolean( ALenum ) {return(AL_FALSE);} +ALint stub_alGetInteger( ALenum ) {return(0);} +ALfloat stub_alGetFloat( ALenum ) {return(0.f);} +ALdouble stub_alGetDouble( ALenum ) {return(0.f);} +ALvoid stub_alGetBooleanv( ALenum, ALboolean* ) {} +ALvoid stub_alGetIntegerv( ALenum, ALint* ) {} +ALvoid stub_alGetFloatv( ALenum, ALfloat* ) {} +ALvoid stub_alGetDoublev( ALenum, ALdouble* ) {} +const ALubyte* stub_alGetString( ALenum pname ) +{ + switch(pname) + { + case AL_VENDOR: return (ALubyte*)"None"; + case AL_VERSION: return (ALubyte*)"OpenAL 0.1"; + case AL_RENDERER: return (ALubyte*)"None"; + case AL_EXTENSIONS: return (ALubyte*)""; + } + return(0); +} +ALenum stub_alGetError( ALvoid ) {return(0);} +ALboolean stub_alIsExtensionPresent( const ALubyte* ) {return(AL_FALSE);} +ALvoid* stub_alGetProcAddress( const ALubyte* ) {return(0);} +ALenum stub_alGetEnumValue( const ALubyte* ) {return(0);} +ALvoid stub_alListenerf( ALenum, ALfloat ) {} +ALvoid stub_alListener3f( ALenum, ALfloat, ALfloat, ALfloat ) {} +ALvoid stub_alListenerfv( ALenum, ALfloat* ) {} +ALvoid stub_alGetListeneri( ALenum, ALint* ) {} +ALvoid stub_alGetListenerf( ALenum, ALfloat* ) {} +ALvoid stub_alGetListenerfv( ALenum, ALfloat* ) {} +ALvoid stub_alGenSources( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteSources( ALsizei, ALuint* ) {} +ALboolean stub_alIsSource( ALuint ) {return(AL_FALSE);} +ALvoid stub_alSourcei( ALuint, ALenum, ALint ) {} +ALvoid stub_alSourcef( ALuint, ALenum, ALfloat ) {} +ALvoid stub_alSource3f( ALuint, ALenum, ALfloat, ALfloat, ALfloat ) {} +ALvoid stub_alSourcefv( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alGetSourcei( ALuint, ALenum, ALint* ) {} +ALvoid stub_alGetSourcef( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alGetSourcefv( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alSourcePlayv( ALuint, ALuint* ) {} +ALvoid stub_alSourceStopv( ALuint, ALuint* ) {} +ALvoid stub_alSourcePlay( ALuint ) {} +ALvoid stub_alSourcePause( ALuint ) {} +ALvoid stub_alSourceStop( ALuint ) {} +ALvoid stub_alGenBuffers( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteBuffers( ALsizei, ALuint* ) {} +ALboolean stub_alIsBuffer( ALuint ) {return(AL_FALSE);} +ALvoid stub_alBufferData( ALuint, ALenum, ALvoid*, ALsizei, ALsizei ) {} +ALsizei stub_alBufferAppendData( ALuint, ALenum, ALvoid*, ALsizei, ALsizei ) {return(0);} +ALvoid stub_alGetBufferi( ALuint, ALenum, ALint* ) {} +ALvoid stub_alGetBufferf( ALuint, ALenum, ALfloat* ) {} + +// ALC: +ALvoid* stub_alcCreateContext( ALint *) {return(0);} +ALCenum stub_alcMakeContextCurrent( ALvoid *) {return(ALC_INVALID);} +ALvoid* stub_alcUpdateContext( ALvoid * ) {return(0);} +ALCenum stub_alcDestroyContext( ALvoid * ) {return(ALC_INVALID);} +ALCenum stub_alcGetError( ALvoid ) {return(ALC_INVALID);} +const ALubyte* stub_alcGetErrorString( ALvoid ) {return(0);} +ALvoid* stub_alcGetCurrentContext( ALvoid ) {return(0);} + +// ALUT: +void stub_alutInit( int *, char ** ) {} +void stub_alutExit( ALvoid ) {} +ALboolean stub_alutLoadWAV( const char *, ALvoid **, ALsizei *, ALsizei *, ALsizei *, ALsizei *) {return(AL_FALSE);} + +// Extension: IASIG +ALvoid stub_alGenEnvironmentIASIG( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteEnvironmentIASIG( ALsizei, ALuint*) {} +ALboolean stub_alIsEnvironmentIASIG( ALuint ) {return(AL_FALSE);} +ALvoid stub_alEnvironmentiIASIG( ALuint, ALenum, ALint ) {} +ALvoid stub_alEnvironmentfIASIG( ALuint, ALenum, ALfloat ) {} +ALvoid stub_alGetEnvironmentiIASIG_EXT( ALuint, ALenum, ALint * ) {} +ALvoid stub_alGetEnvironmentfIASIG_EXT( ALuint, ALenum, ALfloat * ) {} + +// Extension: Dynamix +ALboolean stub_alBufferi_EXT( ALuint, ALenum, ALint ) { return(AL_FALSE); } +ALboolean stub_alBufferSyncData_EXT( ALuint, ALenum, ALvoid *, ALsizei, ALsizei ) {return(AL_FALSE); } +ALboolean stub_alBufferStreamFile_EXT( ALuint, const ALubyte * ) { return(AL_FALSE); } +ALboolean stub_alSourceCallback_EXT( ALuint, ALvoid * ) { return(AL_FALSE); } +ALvoid stub_alSourceResetEnvironment_EXT( ALuint ) {} +ALboolean stub_alContexti_EXT( ALenum, ALint) { return(AL_FALSE); } +ALboolean stub_alGetContexti_EXT( ALenum, ALint * ) { return(AL_FALSE); } +ALboolean stub_alGetContextstr_EXT( ALenum, ALuint, ALubyte ** ) { return(AL_FALSE); } +ALboolean stub_alCaptureInit_EXT( ALenum, ALuint, ALsizei ) { return(AL_FALSE); } +ALboolean stub_alCaptureDestroy_EXT( ALvoid ) { return(AL_FALSE); } +ALboolean stub_alCaptureStart_EXT( ALvoid ) { return(AL_FALSE); } +ALboolean stub_alCaptureStop_EXT( ALvoid ) { return(AL_FALSE); } +ALsizei stub_alCaptureGetData_EXT( ALvoid *, ALsizei, ALenum, ALuint ) { return(AL_FALSE); } + +// declare OpenAL functions +#define AL_EXTENSION(ext_name) bool gDoesSupport_##ext_name = false; +#define AL_FUNCTION(fn_return,fn_name,fn_args) fn_return (*fn_name)fn_args = stub_##fn_name; +#define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_return (*fn_name)fn_args = stub_##fn_name; +#include + +// DLL's: ------------------------------------------------------------------ +static bool findExtension( const char* name ) +{ + bool result = false; + if (alGetString != NULL) + { + result = dStrstr( (const char*)alGetString(AL_EXTENSIONS), name) != NULL; + } + return result; +} + +static bool bindFunction( void *&fnAddress, const char *name ) +{ + fnAddress = NULL; //GetProcAddress( winState.hinstOpenAL, name ); + if( !fnAddress ) + Con::errorf(ConsoleLogEntry::General, " Missing OpenAL function '%s'", name); + return (fnAddress != NULL); +} + +static void bindExtension( void *&fnAddress, const char *name, bool &supported ) +{ + if (supported) + { + bindFunction(fnAddress, name); + if (fnAddress == NULL) + supported = NULL; + } + else + fnAddress = NULL; +} + +static bool bindDLLFunctions() +{ + bool result = true; + #define AL_FUNCTION(fn_return,fn_name,fn_args) result &= bindFunction( *(void**)&fn_name, #fn_name); +#include + return result; +} + +// Stub: ------------------------------------------------------------------- +static void bindStubFunctions() +{ + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = false; + #define AL_FUNCTION(fn_return,fn_name,fn_parms) fn_name = stub_##fn_name; + #define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_name = stub_##fn_name; +#include +} + +// Miles: ------------------------------------------------------------------ +#ifndef NO_MILES_OPENAL + #ifdef MILES_REENTRANT_CHECK + #define AL_FUNCTION(fn_return, fn_name, fn_args) extern fn_return miles_api_##fn_name fn_args; + #define AL_EXT_FUNCTION(ext_name, fn_return, fn_name, fn_args) extern fn_return miles_api_##fn_name fn_args; + #else + #define AL_FUNCTION(fn_return, fn_name, fn_args) extern fn_return miles_##fn_name fn_args; + #define AL_EXT_FUNCTION(ext_name, fn_return, fn_name, fn_args) extern fn_return miles_##fn_name fn_args; + #endif +#include +#endif + +static void bindMilesFunctions() +{ +#ifndef NO_MILES_OPENAL + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = false; + #ifdef MILES_REENTRANT_CHECK + #define AL_FUNCTION(fn_return,fn_name,fn_parms) fn_name = miles_api_##fn_name; + #define AL_EXT_FUNCTION(ext_name, fn_return, fn_name, fn_args) fn_name = miles_api_##fn_name; + #else + #define AL_FUNCTION(fn_return,fn_name,fn_parms) fn_name = miles_##fn_name; + #define AL_EXT_FUNCTION(ext_name, fn_return, fn_name, fn_args) fn_name = miles_##fn_name; + #endif +#include +#endif +} +#endif // TRY REAL OPENAL + +namespace Audio +{ + +static bool sStaticLibrary; + +void libraryShutdown() +{ +/* + if (winState.hinstOpenAL) + FreeLibrary(winState.hinstOpenAL); + winState.hinstOpenAL = NULL; +*/ + + bindStubFunctions(); + sStaticLibrary = true; +} + +bool libraryInit(const char *library) +{ + libraryShutdown(); + + if(!library || !library[0]) + return(false); + +/* + // static drivers... + if(!dStricmp(library, "Miles")) + { + bindMilesFunctions(); + return(true); + } + + winState.hinstOpenAL = LoadLibrary( avar("%s.dll", library) ); + if(winState.hinstOpenAL != NULL) + { + if(bindDLLFunctions()) + { + sStaticLibrary = false; + return(true); + } + libraryShutdown(); + } +*/ + return(false); +} + +void libraryInitExtensions() +{ + // static library extensions are bound on libraryInit (need to be defined anyways...) + if(sStaticLibrary) + { + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = findExtension( #ext_name ); +#include + } + else + { + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = findExtension( #ext_name ); + #define AL_EXT_FUNCTION(ext_name, fn_return,fn_name,fn_args) bindExtension( *(void**)&fn_name, #fn_name, gDoesSupport_##ext_name ); +#include + } +} + +} // end namespace Audio diff --git a/platformMacCarb/macCarbCPUInfo.cc b/platformMacCarb/macCarbCPUInfo.cc new file mode 100644 index 0000000..dc56378 --- /dev/null +++ b/platformMacCarb/macCarbCPUInfo.cc @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformMacCarb/platformMacCarb.h" +#include "console/console.h" +#include "Core/stringTable.h" +#include + +#include +// possibly useful gestalts: +//gestaltCarbonVersion + +// gestaltNativeCPUtype = FOUR_CHAR_CODE('cput'), /* Native CPU type */ +// gestaltNativeCPUfamily = FOUR_CHAR_CODE('cpuf'), /* Native CPU family */ +// gestaltCPU601 = 0x0101, /* IBM 601 */ +// gestaltCPU603 = 0x0103, +// gestaltCPU604 = 0x0104, +// gestaltCPU603e = 0x0106, +// gestaltCPU603ev = 0x0107, +// gestaltCPU750 = 0x0108, /* Also 740 - "G3" */ +// gestaltCPU604e = 0x0109, +// gestaltCPU604ev = 0x010A /* Mach 5, 250Mhz and up */ + +// gestaltDisplayMgrVers = FOUR_CHAR_CODE('dplv') /* Display Manager version */ +// gestaltDisplayMgrAttr = FOUR_CHAR_CODE('dply'), /* Display Manager attributes */ + +// gestaltLogicalRAMSize = FOUR_CHAR_CODE('lram') /* logical ram size -- can be lower than physram... */ +// gestaltPhysicalRAMSize = FOUR_CHAR_CODE('ram ') /* physical RAM size */ + +// gestaltOSAttr = FOUR_CHAR_CODE('os '), /* o/s attributes */ +// gestaltSysZoneGrowable = 0, /* system heap is growable */ +// gestaltLaunchCanReturn = 1, /* can return from launch */ +// gestaltLaunchFullFileSpec = 2, /* can launch from full file spec */ +// gestaltLaunchControl = 3, /* launch control support available */ +// gestaltTempMemSupport = 4, /* temp memory support */ +// gestaltRealTempMemory = 5, /* temp memory handles are real */ +// gestaltTempMemTracked = 6, /* temporary memory handles are tracked */ +// gestaltIPCSupport = 7, /* IPC support is present */ +// gestaltSysDebuggerSupport = 8 /* system debugger support is present */ + +// gestaltPowerMgrAttr = FOUR_CHAR_CODE('powr'), /* power manager attributes */ +// gestaltPMgrExists = 0, +// gestaltPMgrCPUIdle = 1, +// gestaltPMgrSCC = 2, +// gestaltPMgrSound = 3, +// gestaltPMgrDispatchExists = 4, +// gestaltPMgrSupportsAVPowerStateAtSleepWake = 5 + +// gestaltPowerPCProcessorFeatures = FOUR_CHAR_CODE('ppcf'), /* Optional PowerPC processor features */ +// gestaltPowerPCHasGraphicsInstructions = 0, /* has fres, frsqrte, and fsel instructions */ +// gestaltPowerPCHasSTFIWXInstruction = 1, /* has stfiwx instruction */ +// gestaltPowerPCHasSquareRootInstructions = 2, /* has fsqrt and fsqrts instructions */ +// gestaltPowerPCHasDCBAInstruction = 3, /* has dcba instruction */ +// gestaltPowerPCHasVectorInstructions = 4, /* has vector instructions */ +// gestaltPowerPCHasDataStreams = 5 /* has dst, dstt, dstst, dss, and dssall instructions */ + +// should add multiprocessing check +// should add threadmgr check -- and we'll need optional code to do something diff. + +Platform::SystemInfo_struct Platform::SystemInfo; + +void Processor::init() +{ +#pragma message("we need code to determine processor speed and specific processor class") + Platform::SystemInfo.processor.type = CPU_PowerPC_G3; + Platform::SystemInfo.processor.name = StringTable->insert("Unknown PowerPC"); + Platform::SystemInfo.processor.mhz = 200; //!!!!!!!TBD - safe min value. + Platform::SystemInfo.processor.properties = CPU_PROP_PPCMIN; + + Con::printf("Processor Init:"); + Con::printf(" %s, %d Mhz", Platform::SystemInfo.processor.name, Platform::SystemInfo.processor.mhz); + if (Platform::SystemInfo.processor.properties & CPU_PROP_PPCMIN) + Con::printf(" FPU detected"); + Con::printf(" "); +} diff --git a/platformMacCarb/macCarbConsole.cc b/platformMacCarb/macCarbConsole.cc new file mode 100644 index 0000000..b3369bd --- /dev/null +++ b/platformMacCarb/macCarbConsole.cc @@ -0,0 +1,322 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformMacCarb/platformMacCarb.h" +#include "PlatformMacCarb/maccarbConsole.h" +#include "Platform/event.h" +#include "Platform/gameInterface.h" + +#include +#include +#include + +#if !__APPLE__ +#include +#endif + +MacConsole *gConsole = NULL; + +ConsoleFunction(enableWinConsole, void, 2, 2, "enableWinConsole(bool);") +{ + argc; + gConsole->enable(dAtob(argv[1])); +} + +void MacConsole::create() +{ + gConsole = new MacConsole(); +} + +void MacConsole::destroy() +{ + delete gConsole; + gConsole = NULL; +} + +void MacConsole::enable(bool enabled) +{ + consoleEnabled = enabled; + if(consoleEnabled) + { +// AllocConsole(); + printf("Console Initialized.\n"); + const char *title = Con::getVariable("Con::WindowTitle"); + if (title && *title) + { + unsigned char t2[256]; + str2p(title, t2); +#if !__APPLE__ + SIOUXSetTitle(t2); +#endif + } +// stdOut = GetStdHandle(STD_OUTPUT_HANDLE); +// stdIn = GetStdHandle(STD_INPUT_HANDLE); +// stdErr = GetStdHandle(STD_ERROR_HANDLE); +// + printf("%s", Con::getVariable("Con::Prompt")); + } +} + +bool MacConsole::isEnabled() +{ + if ( gConsole ) + return gConsole->consoleEnabled; + + return false; +} + +static void macConsoleConsumer(ConsoleLogEntry::Level, const char *line) +{ + gConsole->processConsoleLine(line); +} + +MacConsole::MacConsole() +{ + for (S32 iIndex = 0; iIndex < MAX_CMDS; iIndex ++) + rgCmds[iIndex][0] = '\0'; + + iCmdIndex = 0; + + consoleEnabled = false; + currMsg = NULL; + + Con::addConsumer(macConsoleConsumer); + + inpos = 0; + lineOutput = false; + +// Con::addVariable("MacConsoleEnabled", consoleEnableCallback, "false"); + +#if !__APPLE__ + // set up the SIOUX stuff here for now. + SIOUXSettings.standalone = false; + SIOUXSettings.setupmenus = false; + SIOUXSettings.initializeTB = false; + SIOUXSettings.asktosaveonclose = false; + SIOUXSettings.toppixel = 4 + GetMBarHeight(); + SIOUXSettings.leftpixel = 4; + SIOUXSettings.columns = 120; + SIOUXSettings.rows = 40; +#endif +} + +void MacConsole::printf(const char *s, ...) +{ +// static char buffer[512]; + int bytes; + va_list args; + va_start(args, s); + + vprintf(s, args); + +// vsprintf(buffer, s, args); +// WriteFile(stdOut, buffer, strlen(buffer), &bytes, NULL); +// FlushFileBuffers( stdOut ); +} + +void MacConsole::processConsoleLine(const char *consoleLine) +{ + if(consoleEnabled) + { + inbuf[inpos] = 0; + if(lineOutput) + printf("%s\n", consoleLine); + else + printf("%c%s\n%s%s", '\r', consoleLine, Con::getVariable("Con::Prompt"), inbuf); + } +} + + +//-------------------------------------- +bool MacConsole::handleEvent(const EventRecord *msg) +{ +#if !__APPLE__ +// if (MacConsole::isEnabled()) + { + if (SIOUXHandleOneEvent((EventRecord*)msg)) + { + currMsg = msg; + process(); + currMsg = NULL; + + return(true); + } + } +#endif + + return(false); +} + + +//-------------------------------------- +// on the mac, we call this after the console has consumed a keyboard event. +void MacConsole::process() +{ + // for now, minimal processing. + + if(consoleEnabled && currMsg) + { +/* + DWORD numEvents; + GetNumberOfConsoleInputEvents(stdIn, &numEvents); + if(numEvents) + { + INPUT_RECORD rec[20]; +*/ + char outbuf[256]; + 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(currMsg->what==keyDown || currMsg->what==autoKey) + { + U8 ascii = currMsg->message & charCodeMask; + U8 keycode = TranslateOSKeyCode( (currMsg->message & keyCodeMask) >> 8 ); + + switch (ascii) + { + // If no ASCII char, check if it's a handled virtual key + case 28: + case 29: + case 30: + case 31: + switch (keycode) + { + // UP ARROW + case KEY_UP: + { + // 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 KEY_DOWN: + { + // 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 KEY_LEFT: + break; + + // RIGHT ARROW + case KEY_RIGHT: + break; + + default : + break; + } + break; + + // backspace??? + case '\b': + if(inpos > 0) + { + outbuf[outpos++] = '\b'; + outbuf[outpos++] = ' '; + outbuf[outpos++] = '\b'; + inpos--; + } + break; + + // some kind of CR/LF + case '\n': + case '\r': +// outbuf[outpos++] = '\r'; + outbuf[outpos++] = '\n'; + + inbuf[inpos] = 0; + outbuf[outpos] = 0; + printf("%s", outbuf); + + S32 eventSize; + eventSize = ConsoleEventHeaderSize; + + dStrcpy(postEvent.data, inbuf); + postEvent.size = eventSize + dStrlen(inbuf) + 1; + Game->postEvent(postEvent); + + // 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++] = ascii; + outbuf[outpos++] = ascii; + break; + } + } + //} + //} + + if(outpos) + { + outbuf[outpos] = 0; + printf("%s", outbuf); + } + //} + } +} diff --git a/platformMacCarb/macCarbConsole.h b/platformMacCarb/macCarbConsole.h new file mode 100644 index 0000000..a2d7922 --- /dev/null +++ b/platformMacCarb/macCarbConsole.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MACCARBCONSOLE_H_ +#define _MACCARBCONSOLE_H_ + +#define MAX_CMDS 10 +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif +#ifndef _EVENT_H_ +#include "Platform/event.h" +#endif + +#include + +class MacConsole +{ + bool consoleEnabled; + + const EventRecord *currMsg; + + ConsoleEvent postEvent; + + 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: + MacConsole(); + void process(); + void enable(bool); + void processConsoleLine(const char *consoleLine); + + bool handleEvent(const EventRecord *msg); + + static void create(); + static void destroy(); + static bool isEnabled(); +}; + +extern MacConsole *gConsole; + +#endif diff --git a/platformMacCarb/macCarbFileio.cc b/platformMacCarb/macCarbFileio.cc new file mode 100644 index 0000000..ccae3ed --- /dev/null +++ b/platformMacCarb/macCarbFileio.cc @@ -0,0 +1,944 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformMacCarb/platformMacCarb.h" +#include "core/fileio.h" +#include "core/tVector.h" +#include "core/stringTable.h" +#include "console/console.h" +#include +#include +#include + +#include + +#pragma message("todo: file io needs some real work...") + +// this is the main working dir path. +static StringTableEntry cwd = NULL; +static Boolean homeInPackage = FALSE; + +//--------- Helper Functions ------------------------------ +#define CAT_FOLDER_ATTRIB 0x10 // which bit stores file vs folder status. +#define CAT_IS_FOLDER(c) (((c).dirInfo.ioFlAttrib & CAT_FOLDER_ATTRIB) != 0) +// look for PC/unix delimiters +#define ISA_SLASH(c) ((c) == '\\' || (c) == '/') +// while mac paths are typically <255, I think HFS+ and UDF can have +// like 1024 or something. I'll cap to something larger, so we don't +// go running off into memory... +#define MAX_MAC_PATH_LONG 1200 +// the str255 maxsize, allowing null termination and length byte. +#define MAX_MAC_PATH 254 + +//----------------------------------------------------------------------------- +static U32 makeMacPath(const char *src, char *dst) +{ + U32 num = 0; + // don't need these aliases, but aids in debugging. + char *s = (char*)src, *d = dst; + + // clear dest. + *d = '\0'; + + // if the first char is a slash, then it's a top-level folder + if (ISA_SLASH(*s)) + { + // then no leading colon. + s++; // skip the slash now. + } + else // curr-dir-relative + { + // start new string with a colon. + *d++ = ':'; + num++; + } + + while(*s && num +#define MACCARB_CONTENT_OUTSIDE_BUNDLE 1 +#endif + +void macGetHomeDirectory() +{ + HGetVol(NULL, &platState.volRefNum, &platState.dirID); + + // then, we need to check if we're in a package/bundle. if so, need to find what dir + // we are really in (potentially!!!!!tbd - we may WANT to do everything inside the + // bundle dir anyway, so... + +#ifdef __APPLE__ // for now, only do this for OSX Project Builder compiles... +#if MACCARB_CONTENT_OUTSIDE_BUNDLE + CFBundleRef ref; + ref = CFBundleGetMainBundle(); + if (ref) + { // then we need to find the matching dirID. + CFURLRef url; + Boolean success = FALSE; + Boolean wantAbsolute = TRUE; // !!!!TBD for now, get the full path. + + OSErr err; + CInfoPBRec catinfo; + Str255 dirname; + char wdbuf[2048]; + U32 len = 0, totallen = 0; + + url = CFBundleCopyBundleURL(ref); + success = CFURLGetFileSystemRepresentation(url, wantAbsolute, wdbuf, sizeof(wdbuf)); + if (!success) return; // done. + + // find the directory above the bundle's dir in the path. + // walk backward through the string to the next / or \ or : + len = dStrlen(wdbuf); + while(len) + { + len--; + if (wdbuf[len]=='/') + { // stop here and null. + wdbuf[len]=0; + break; + } + } + + if (len==0) return; // done. + + // we now have the valid home path. save the workingdir refstring and copy into platState. + // offset the buffer by the length of the prefix "/Volumes" to strip it off... + dMemmove(platState.absAppPath, wdbuf+8, len-8); + + // now, we need to decode what our parent dirID really is. + // convert into dirname as pstr. + totallen = makeMacPath(platState.absAppPath, &dirname[1]); + dirname[0] = totallen; + + // clear the paramblock. + dMemset((void*)&catinfo, 0L, sizeof(catinfo)); + + catinfo.dirInfo.ioVRefNum = platState.volRefNum; + catinfo.dirInfo.ioDrParID = 0; // hoping the dirname suffices... + catinfo.dirInfo.ioDrDirID = 0; // hoping the dirname suffices... + catinfo.dirInfo.ioNamePtr = (StringPtr)&dirname; + // ask for info about the folder named in ioNamePtr by specifying zero as the dirindex. + catinfo.dirInfo.ioFDirIndex = 0; + + err = PBGetCatInfoSync(&catinfo); + if (err!=noErr) return; + + // now we have our REAL dirID. + platState.dirID = catinfo.dirInfo.ioDrDirID; + + // can't do this here! +// cwd = StringTable->insert(wdbuf); +// Platform::getWorkingDirectory(); + + homeInPackage = TRUE; + } + else // we're not in a bundle +#endif +#endif + { // run the normal working-dir-retrieve code. + // can't do this here! +// Platform::getWorkingDirectory(); + } +} + + +//----------------------------------------------------------------------------- +bool dFileDelete(const char * name) +{ + if(!name || (dStrlen(name) >= MAX_MAC_PATH)) + return(false); + return(remove(name)); +} + + +//----------------------------------------------------------------------------- +bool dFileTouch(const char *path) +{ + if (!path || !*path) + return false; + + OSErr err = noErr; + CInfoPBRec catinfo; + U8 macpath[MAX_MAC_PATH_LONG]; + U32 size; + + size = makeMacPath(path, (char*)(macpath+1)); + if (size>MAX_MAC_PATH) + return false; + macpath[0] = size; // set PString length. + + // clear the paramblock. + dMemset((void*)&catinfo, 0L, sizeof(catinfo)); + + // fill in paramblock. + catinfo.hFileInfo.ioVRefNum = macpath[1]==':'?platState.volRefNum:0; + catinfo.hFileInfo.ioDirID = macpath[1]==':'?platState.dirID:0; + catinfo.hFileInfo.ioNamePtr = macpath; + // need to clear the dir index. + catinfo.hFileInfo.ioFDirIndex = 0; + + // get the current info on this file. + err = PBGetCatInfoSync(&catinfo); + if (err!=noErr) + return false; // !!!!TBD should log? + + // get curr time and update the modified time field + unsigned long curtime; + GetDateTime(&curtime); + catinfo.hFileInfo.ioFlMdDat = curtime; + + // reset the dirID, as it came back with the fileID. + catinfo.hFileInfo.ioDirID = macpath[1]==':'?platState.dirID:0; + + // save it all back. + err = PBSetCatInfoSync(&catinfo); + if (err!=noErr) + return false; // !!!!TBD should log? + + return true; +} + + +//----------------------------------------------------------------------------- +// 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) +{ + char hostFilename[256]; + char tmpFilename[256]; + + AssertFatal(dStrlen(filename) <= 255, "File::open: Max Mac file length exceeded. MAX=255"); + AssertFatal(NULL != filename, "File::open: NULL filename"); + AssertWarn(NULL == handle, "File::open: handle already valid"); + + // if the string BEGINS with a slash, assume it's a full path reference + // otherwise, create a reference off the /Volumes dir + the working directory (since we stripped off /Volumes...) +#if __APPLE__ + if (homeInPackage && filename[0]!='/') + { + dStrcpy(tmpFilename, "/Volumes"); + dStrcat(tmpFilename, platState.absAppPath); + dStrcat(tmpFilename, "/"); + dStrcat(tmpFilename, filename); + } + else +#endif + dStrcpy(tmpFilename, filename); + +#if __APPLE__ + dStrcpy(hostFilename, tmpFilename); +#else + // change from posix to Mac + makeMacPath(tmpFilename, hostFilename); +#endif + + // Close the file if it was already open... + if (Closed != currentStatus) + close(); + + // create the appropriate type of file... + switch (openMode) + { + case Read: + handle = (void *)fopen(hostFilename, "rb"); + break; + case Write: + handle = (void *)fopen(hostFilename, "wb"); + break; + case ReadWrite: + handle = (void *)fopen(hostFilename, "ab+"); + break; + default: + AssertFatal(false, "File::open: bad access mode"); // impossible + } + + if (handle == NULL) // 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: + capability = U32(FileWrite); + break; + case ReadWrite: + capability = U32(FileRead) | U32(FileWrite); + break; + default: + AssertFatal(false, "File::open: bad access mode"); + } + + if (openMode == ReadWrite) + setPosition(0); + + return currentStatus = Ok; // success! + } +} + + +#define HANDLE_CAST void * +#define INVALID_HANDLE_VALUE ((HANDLE_CAST)0L) + +//----------------------------------------------------------------------------- +// Get the current position of the file pointer. +//----------------------------------------------------------------------------- +U32 File::getPosition() const +{ + AssertFatal(Closed != currentStatus, "File::getPosition: file closed"); + AssertFatal(INVALID_HANDLE_VALUE != (HANDLE_CAST)handle, "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 (Ok != currentStatus && EOS != currentStatus) + return currentStatus; + + U32 finalPos; + switch (absolutePos) + { + case true: // 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); + break; + case false: // relative position + AssertFatal((getPosition() >= (U32)abs(position) && 0 > position) || 0 <= position, "File::setPosition: negative relative position"); + // position beyond EOS is OK + fseek((FILE*)handle, position, SEEK_CUR); + finalPos = ftell((FILE*)handle); + } + + 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(handle != NULL, "File::getSize: invalid file handle"); + + if (Ok == currentStatus || EOS == currentStatus) + { + // this is not a very good way to do this + U32 pos = ftell((FILE*)handle); + fseek((FILE*)handle, 0, SEEK_END); + U32 size = ftell((FILE*)handle); + fseek((FILE*)handle, pos, SEEK_SET); + return size; + } + 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(handle != NULL, "File::flush: invalid file handle"); + AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file"); + + if (fflush((FILE*)handle) == EOF) + 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 (handle != NULL) + { + if (fclose((FILE*)handle) == EOF) + return setStatus(); // unsuccessful + } + 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() +{ +#pragma message("todo: File::setStatus") + + switch (errno) + { +/* + case EACCESS: // permission denied + return currentStatus = IOError; + case EBADF: // Bad File Pointer + errno++; + case EINVAL: // Invalid argument + errno++; + case ENOENT: // file not found + errno++; +*/ + default: + return currentStatus = UnknownError; + } + + 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(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; + else + { + U32 lastBytes; + U32 *bytes = (NULL == bytesRead) ? &lastBytes : bytesRead; + if (fread(dst, size, 1, (FILE*)handle) != 1) + { // fread onlu reports the number of chunks read not bytes + // so we don't know exactly how much was read + *bytes = getPosition(); + return currentStatus = EOS; // end of stream + } + else + { + *bytes = size; + return currentStatus = Ok; // 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(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; + else + { + U32 lastBytes; + U32 *bytes = (NULL == bytesWritten) ? &lastBytes : bytesWritten; + if (fwrite(src, size, 1, (FILE*)handle) != 1) + { // fwrite onlu reports the number of chunks written not bytes + // so we don't know exactly how much was written + *bytes = getPosition(); + return setStatus(); + } + else + { + *bytes = size; + return currentStatus = Ok; // success! + } + } +} + + +//----------------------------------------------------------------------------- +// Self-explanatory. +//----------------------------------------------------------------------------- +bool File::hasCapability(Capability cap) const +{ + return (0 != (U32(cap) & capability)); +} + + +//----------------------------------------------------------------------------- +static bool _recurseDumpPath(const char* in_pBasePath, const char* in_pCurPath, S32 dirID, Vector& out_rFileVector) +{ + char curPath[1024]; + char basePath[1024]; + char scratchBuf[1024]; + + if(in_pCurPath) + dStrcpy(curPath, in_pCurPath); + else + curPath[0] = 0; + + dStrcpy(basePath, in_pBasePath); + in_pBasePath = basePath; + + CInfoPBRec cinfo; + Str63 nameField; + OSErr result; + S32 index = 1; + + do + { // setup a catalog information request structure + cinfo.hFileInfo.ioVRefNum = platState.volRefNum; // volume ID to search in + cinfo.hFileInfo.ioDirID = dirID; // directory ID to search in + cinfo.hFileInfo.ioFDirIndex = index++; // specify which entry you are interested in + cinfo.hFileInfo.ioNamePtr = nameField; // supply a buffer to store the name + + result = PBGetCatInfoSync(&cinfo); + if (result == noErr) + { + if (cinfo.dirInfo.ioFlAttrib & ioDirMask) + { // it's a directory + char *dirname = p2str(cinfo.hFileInfo.ioNamePtr); + scratchBuf[0] = '\0'; + if (curPath[0] != '\0') + { + dStrcpy(scratchBuf, curPath); + dStrcat(scratchBuf, "/"); + } + dStrcat(scratchBuf, dirname); + + _recurseDumpPath(basePath, scratchBuf, cinfo.hFileInfo.ioDirID, out_rFileVector); + } + else + { // it's a file + char *filename = p2str(cinfo.hFileInfo.ioNamePtr); + + out_rFileVector.increment(); + Platform::FileInfo& rInfo = out_rFileVector.last(); + + if (curPath[0] != '\0') + { + dSprintf(scratchBuf, sizeof(scratchBuf), "%s/%s", basePath, curPath); + rInfo.pFullPath = StringTable->insert(scratchBuf); + } + else + { + rInfo.pFullPath = StringTable->insert(basePath); + } + + rInfo.pFileName = StringTable->insert(filename); + rInfo.fileSize = cinfo.hFileInfo.ioFlLgLen; + //rInfo.createTime.time = cinfo.hFileInfo.ioFlCrDat; + //rInfo.modifyTime.time = cinfo.hFileInfo.ioFlMdDat; + } + } + } + while (result == noErr); + + return true; +} + + +//----------------------------------------------------------------------------- +S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b) +{ + if(a.time > b.time) + return 1; + if(a.time < b.time) + return -1; + return 0; +} + + +//----------------------------------------------------------------------------- +// either time param COULD be null. +//----------------------------------------------------------------------------- +bool Platform::getFileTimes(const char *path, FileTime *createTime, FileTime *modifyTime) +{ + if (!path || !*path) + return false; + + OSErr err = noErr; + CInfoPBRec catinfo; + U8 macpath[MAX_MAC_PATH_LONG]; + U32 size; + + size = makeMacPath(path, (char*)(macpath+1)); + if (size>MAX_MAC_PATH) + return false; + macpath[0] = size; // set PString length. + + // clear the paramblock. + dMemset((void*)&catinfo, 0L, sizeof(catinfo)); + + // fill in paramblock. + catinfo.hFileInfo.ioVRefNum = macpath[1]==':'?platState.volRefNum:0; + catinfo.hFileInfo.ioDirID = macpath[1]==':'?platState.dirID:0; + catinfo.hFileInfo.ioNamePtr = macpath; + err = PBGetCatInfoSync(&catinfo); + if (err!=noErr) + return false; // !!!!TBD should log? + + if (createTime) + createTime->time = catinfo.hFileInfo.ioFlCrDat; + if (modifyTime) + modifyTime->time = catinfo.hFileInfo.ioFlMdDat; + + return true; +} + + +//----------------------------------------------------------------------------- +bool Platform::createPath(const char *file) +{ + char pathBuf[1024]; + char dirBuf[256]; + const char *dir; + U32 pathLen = 0; + S32 parentDirID = platState.dirID; + + pathBuf[0] = 0; + while((dir = dStrchr(file, '/')) != NULL) + { + U32 len = dir-file; + dStrncpy(dirBuf, file, len); + dirBuf[len] = 0; + + dStrncpy(pathBuf + pathLen, file, dir - file); + pathBuf[pathLen + dir-file] = 0; + + // does directory/name already exist? + CInfoPBRec cinfo; + Str63 nameField; + + cinfo.hFileInfo.ioVRefNum = platState.volRefNum; // volume ID to search in + cinfo.hFileInfo.ioDirID = parentDirID; // directory ID to search in + cinfo.hFileInfo.ioFDirIndex = 0; // get info on ioNamePtr + cinfo.hFileInfo.ioNamePtr = nameField; // supply a buffer with name + str2p(dirBuf, nameField); + OSErr err = PBGetCatInfoSync(&cinfo); + switch(err) + { + case noErr: + if (cinfo.dirInfo.ioFlAttrib & ioDirMask) + { // it's a directory + parentDirID = cinfo.hFileInfo.ioDirID; + } + else + { // the name existed and it was NOT a directory + Con::printf("CreateDirectory(%s) - failed", pathBuf); + return false; + } + break; + + case fnfErr: + { // the name did not exist so create the directory + long newId; + OSErr err = DirCreate(platState.volRefNum, parentDirID, str2p(dirBuf), &newId); + if (err != noErr) + { + Con::printf("CreateDirectory(%s) - failed", pathBuf); + return false; + } + parentDirID = newId; + } + break; + } + + file = dir + 1; + pathLen += len; + pathBuf[pathLen++] = '/'; + pathBuf[pathLen] = 0; + Con::printf("CreateDirectory(%s) - Succeeded", pathBuf); + + } + return true; +} + + +//----------------------------------------------------------------------------- +bool Platform::cdFileExists(const char *filePath, const char *volumeName, S32 serialNum) +{ + return true; // !!!!!TBD +} + + +//----------------------------------------------------------------------------- +bool Platform::dumpPath(const char *in_pBasePath, Vector& out_rFileVector) +{ + // for now we can only search directories in the apps current workinng directory + // specifying another drive, path or sub path (base/art) will not work. + S32 dirID = platState.dirID; + + if (in_pBasePath) + { + CInfoPBRec cinfo; + Str63 nameField; + OSErr result; + + cinfo.hFileInfo.ioVRefNum = platState.volRefNum; // volume ID to search in + cinfo.hFileInfo.ioDirID = dirID; // directory ID to search in + cinfo.hFileInfo.ioFDirIndex = 0; // get info on ioNamePtr + cinfo.hFileInfo.ioNamePtr = nameField; // supply a buffer with name + str2p(in_pBasePath, nameField); + result = PBGetCatInfoSync(&cinfo); + if (result == noErr) + { + if (cinfo.dirInfo.ioFlAttrib & ioDirMask) + { // it's a directory + char *dirname = p2str(cinfo.hFileInfo.ioNamePtr); + return _recurseDumpPath(in_pBasePath, NULL, cinfo.hFileInfo.ioDirID, out_rFileVector); + } + return false; // not a directory + } + } + return _recurseDumpPath(in_pBasePath, NULL, dirID, out_rFileVector); +} + + +//----------------------------------------------------------------------------- +StringTableEntry Platform::getWorkingDirectory() +{ + if (!cwd) + { + OSErr err; + CInfoPBRec catinfo; + Str255 dirname; + char cwd_buf[2048]; + U32 len = 0, totallen = 0; + + *cwd_buf = 0; + + // clear the paramblock. + dMemset((void*)&catinfo, 0L, sizeof(catinfo)); + + catinfo.dirInfo.ioVRefNum = platState.volRefNum; + catinfo.dirInfo.ioDrParID = platState.dirID; + catinfo.dirInfo.ioNamePtr = (StringPtr)&dirname; + // ask for info about this directory. + catinfo.dirInfo.ioFDirIndex = -1; + + do + { + dirname[0] = 0; // make sure to null the dirname! + // reset dir id each time through to the parent's id. + catinfo.dirInfo.ioDrDirID = catinfo.dirInfo.ioDrParID; + err = PBGetCatInfoSync(&catinfo); + if (err!=noErr) break; + + // capture st255 len and null terminate it. + len = dirname[0]+1; + dirname[len] = 0; + + // put a slash as the first char (eff converting it into a cstr...). + dirname[0] = '/'; + + // copy the current path forward in the buffer to allow us to prepend the new dir. + // MUST USE MEMMOVE, as we are copying in-place, and memmove allows moving within + // a given buffer. memcpy doesn't. + // and remember to allow for the null on the end... + if (totallen) dMemmove(cwd_buf + len, cwd_buf, totallen+1); + + // copy the new path in, prepending the string. don't copy the null here... + dMemmove(cwd_buf, dirname, len); + + // inc the total size. + totallen += len; + + // make damn sure we're null terminated. + cwd_buf[totallen] = 0; + } + while (catinfo.dirInfo.ioDrDirID != fsRtDirID); + + cwd = StringTable->insert(cwd_buf); + } + return cwd; +} + + +//----------------------------------------------------------------------------- +bool Platform::isFile(const char *path) +{ + if (!path || !*path) + return false; + + OSErr err = noErr; + CInfoPBRec catinfo; + U8 macpath[MAX_MAC_PATH_LONG]; + U32 size; + + size = makeMacPath(path, (char*)(macpath+1)); + if (size>MAX_MAC_PATH) + return false; + macpath[0] = size; // set PString length. + + // clear the paramblock. + dMemset((void*)&catinfo, 0L, sizeof(catinfo)); + + // fill in paramblock. + catinfo.dirInfo.ioVRefNum = macpath[1]==':'?platState.volRefNum:0; + catinfo.dirInfo.ioDrDirID = macpath[1]==':'?platState.dirID:0; + catinfo.dirInfo.ioNamePtr = macpath; + err = PBGetCatInfoSync(&catinfo); + if (err!=noErr) + return false; // !!!!TBD should log? + + if (CAT_IS_FOLDER(catinfo)) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +bool Platform::isDirectory(const char *path) +{ + if (!path || !*path) + return false; + + OSErr err = noErr; + CInfoPBRec catinfo; + U8 macpath[MAX_MAC_PATH_LONG]; + U32 size; + + size = makeMacPath(path, (char*)(macpath+1)); + if (size>MAX_MAC_PATH) + return false; + macpath[0] = size; // set PString length. + + // clear the paramblock. + dMemset((void*)&catinfo, 0L, sizeof(catinfo)); + + // fill in paramblock. + catinfo.dirInfo.ioVRefNum = macpath[1]==':'?platState.volRefNum:0; + catinfo.dirInfo.ioDrDirID = macpath[1]==':'?platState.dirID:0; + catinfo.dirInfo.ioNamePtr = macpath; + err = PBGetCatInfoSync(&catinfo); + if (err!=noErr) + return false; // !!!!TBD should log? + + if (CAT_IS_FOLDER(catinfo)) + return true; + + return false; +} + + + +//----------------------------------------------------------------------------- +bool Platform::isSubDirectory(const char *pathParent, const char *pathSub) +{ + char fullpath[MAX_MAC_PATH_LONG]; + dStrcpyl(fullpath, sizeof(fullpath), pathParent, "/", pathSub, NULL); + return isDirectory((const char *)fullpath); +} + diff --git a/platformMacCarb/macCarbFileio.h b/platformMacCarb/macCarbFileio.h new file mode 100644 index 0000000..27acbbf --- /dev/null +++ b/platformMacCarb/macCarbFileio.h @@ -0,0 +1,14 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MACCARBFILEIO_H_ +#define _MACCARBFILEIO_H_ + +// only one exported func at the moment. +void macGetHomeDirectory(void); + +#endif // _MACCARBFILEIO_H_ diff --git a/platformMacCarb/macCarbFont.cc b/platformMacCarb/macCarbFont.cc new file mode 100644 index 0000000..62097f6 --- /dev/null +++ b/platformMacCarb/macCarbFont.cc @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformMacCarb/platformMacCarb.h" +#include "dgl/gFont.h" +#include "dgl/gBitmap.h" +#include "Math/mRect.h" +#include "console/console.h" + +//#include + +static GWorldPtr fontgw = NULL; +static Rect fontrect = {0,0,256,256}; +static short fontdepth = 32; +static U8 rawmap[256*256]; + + +void createFontInit(void); +void createFontShutdown(void); +S32 CopyCharToRaw(U8 *raw, PixMapHandle pm, const Rect &r); + +// !!!!! TBD this should be returning error, or raising exception... +void createFontInit() +{ + OSErr err = NewGWorld(&fontgw, fontdepth, &fontrect, NULL, NULL, keepLocal); + AssertWarn(err==noErr, "Font system failed to initialize GWorld."); +} + +void createFontShutdown() +{ + DisposeGWorld(fontgw); + fontgw = NULL; +} + +U8 ColorAverage8(RGBColor &rgb) +{ + return ((rgb.red>>8) + (rgb.green>>8) + (rgb.blue>>8)) / 3; +} + +S32 CopyCharToRaw(U8 *raw, PixMapHandle pmh, const Rect &r) +{ + // walk the pixmap, copying into raw buffer. + // we want bg black, fg white, which is opposite 'sign' from the pixmap (bg white, with black text) + +// int off = GetPixRowBytes(pmh); +// U32 *c = (U32*)GetPixBaseAddr(pmh); +// U32 *p; + + RGBColor rgb; + + S32 i, j; + U8 val; + S32 lastRow = -1; + + for (i = r.left; i <= r.right; i++) + { + for (j = r.top; j <= r.bottom; j++) + { +// p = (U32*)(((U8*)c) + (j*off)); // since rowbytes is bytes not pixels, need to convert to byte * before doing the math... + val = 0; +// if (((*p)&0x00FFFFFF)==0) // then black pixel in current port, so want white in new buffer. + GetCPixel(i, j, &rgb); + if ((ColorAverage8(rgb)>>2) < 2) // get's us some grays with a small slop factor. + { + val = 255; + lastRow = j; // track the last row in the rect that we actually saw an active pixel, finding descenders basically... + } + raw[i + (j<<8)] = val; +// p++; + } + } + + return(lastRow); +} + +GFont *createFont(const char *name, S32 size) +{ + if(!name) + return NULL; + if(size < 1) + return NULL; + + short fontid; + GetFNum(str2p(name), &fontid); + if (fontid == 0) + { + Con::errorf(ConsoleLogEntry::General,"Error creating font -- it doesn't exist: %s %d",name, size); + return(NULL); + } + + Boolean aaWas; + S16 aaSize; + CGrafPtr savePort; + GDHandle saveGD; + GetGWorld(&savePort, &saveGD); + + aaWas = IsAntiAliasedTextEnabled(&aaSize); + if (aaWas) + SetAntiAliasedTextEnabled(0, 0); + + RGBColor white = {0xFFFF, 0xFFFF, 0xFFFF}; + RGBColor black = {0, 0, 0}; + PixMapHandle pmh; + + SetGWorld(fontgw, NULL); + PenNormal(); // reset pen attribs. + // shouldn't really need to do this, right? + RGBBackColor(&white); + RGBForeColor(&black); + + // set proper font. + TextSize(size); + TextFont(fontid); + TextMode(srcCopy); + + // get font info + int cx, cy, ry, my; + FontInfo fi; + GetFontInfo(&fi); // gets us basic glyphs. + cy = fi.ascent + fi.descent + fi.leading; // !!!! HACK. Not per-char-specific. + + pmh = GetGWorldPixMap(fontgw); + + GFont *retFont = new GFont; + + Rect b = {0,0,0,0}; + for(S32 i = 32; i < 256; i++) + { + // clear the port. + EraseRect(&fontrect); + // reset position to left edge, bottom of line for this font style. + MoveTo(0,cy); + // draw char & calc its pixel width. + DrawChar(i); + cx = CharWidth(i); + + if (!LockPixels(pmh)) + { + UpdateGWorld(&fontgw, fontdepth, &fontrect, NULL, NULL, keepLocal); + pmh = GetGWorldPixMap(fontgw); + // for now, assume we're good to go... TBD!!!! + LockPixels(pmh); + + MoveTo(0,fi.ascent); // reset position to left edge, bottom of line for this font style. + DrawChar(i); + cx = CharWidth(i); + } + + b.right = cx+1; + b.bottom = cy+2; // in case descenders drop too low, we want to catch the chars. + ry = CopyCharToRaw(rawmap, pmh, b); + + UnlockPixels(pmh); + + if (ry<0) // bitmap was blank. + { + Con::printf("Blank character %c", i); + if (cx) + retFont->insertBitmap(i, rawmap, 0, 0, 0, 0, 0, cx); + } + else + { + retFont->insertBitmap(i, rawmap, 256, cx+1, cy+2 /*ry*/, 0, fi.ascent+fi.descent, cx+1); + } + } + + retFont->pack(cy, fi.ascent); + +// if (actualChars==0) +// Con::errorf(ConsoleLogEntry::General,"Error creating font: %s %d",name, size); + + //clean up local vars + if (aaWas) + SetAntiAliasedTextEnabled(aaWas, aaSize); + SetGWorld(savePort, saveGD); + + return retFont; +} diff --git a/platformMacCarb/macCarbGG.icns b/platformMacCarb/macCarbGG.icns new file mode 100644 index 0000000..e9e8c7e Binary files /dev/null and b/platformMacCarb/macCarbGG.icns differ diff --git a/platformMacCarb/macCarbGL.cc b/platformMacCarb/macCarbGL.cc new file mode 100644 index 0000000..b99f791 --- /dev/null +++ b/platformMacCarb/macCarbGL.cc @@ -0,0 +1,265 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformMacCarb/platformGL.h" +#include "PlatformMacCarb/platformMacCarb.h" +#include "console/console.h" + +GLState gGLState; + +bool gOpenGLDisablePT = false; +bool gOpenGLDisableCVA = false; +bool gOpenGLDisableTEC = false; +bool gOpenGLDisableARBMT = false; +bool gOpenGLDisableFC = true; //false; +bool gOpenGLDisableTCompress = false; +bool gOpenGLNoEnvColor = false; +float gOpenGLGammaCorrection = 0.5; +bool gOpenGLNoDrawArraysAlpha = false; + + +#if 1 // until we decide how to actually implement these... + +GLboolean mglAvailVB() { return(false); } +GLint mglAllocVB(GLsizei size, GLint format, GLboolean preserve) { return(0); } +void* mglLockVB(GLint handle, GLsizei size) { return(NULL); } +void mglUnlockVB(GLint handle) {} +void mglSetVB(GLint handle) {} +void mglOffsetVB(GLint handle, GLuint offset) {} +void mglFillVB(GLint handle, GLint first, GLsizei count) {} +void mglFreeVB(GLint handle) {} +void mglFogCP(GLenum type, GLsizei stride, const GLvoid *pointer) {} +void mglColorTable(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table) {} + +GLboolean glAvailableVertexBufferEXT() { return(false); } +GLint glAllocateVertexBufferEXT(GLsizei size, GLint format, GLboolean preserve) { return(0); } +void* glLockVertexBufferEXT(GLint handle, GLsizei size) { return(NULL); } +void glUnlockVertexBufferEXT(GLint handle) {} +void glSetVertexBufferEXT(GLint handle) {} +void glOffsetVertexBufferEXT(GLint handle, GLuint offset) {} +void glFillVertexBufferEXT(GLint handle, GLint first, GLsizei count) {} +void glFreeVertexBufferEXT(GLint handle) {} +void glFogCoordPointerEXT(GLenum type, GLsizei stride, const GLvoid *pointer) {} +void glColorTableEXT(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table) {} + +#endif + + + +bool QGL_EXT_Init( ) +{ + // Load extensions... + // + const char* pExtString = reinterpret_cast(glGetString(GL_EXTENSIONS)); + + // OpenGL Exists ======================================== + if (glBegin == (void *)kUnresolvedCFragSymbolAddress) + { // !!!!!!! TBD + Con::printf("OpenGL Init: Failed to find OpenGL system library."); + return(false); + } + + // EXT_paletted_texture ======================================== +// glColorTableEXT = NULL; + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_paletted_texture") != NULL) + gGLState.suppPalettedTexture = true; + else + gGLState.suppPalettedTexture = false; + + // EXT_compiled_vertex_array ======================================== +/* + glLockArraysEXT = NULL; + glUnlockArraysEXT = NULL; +*/ +#if FOR_ATI_TRUFORM_DEMO //!!!!!TBD!!!!!!!! HACK HACK HACK + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_compiled_vertex_array") != NULL) + gGLState.suppLockedArrays = true; + else +#endif + gGLState.suppLockedArrays = false; + + // ARB_multitexture ======================================== +/* + glActiveTextureARB = NULL; + glClientActiveTextureARB = NULL; + glMultiTexCoord2fARB = NULL; + glMultiTexCoord2fvARB = NULL; +*/ + if (pExtString && dStrstr(pExtString, (const char*)"GL_ARB_multitexture") != NULL) + gGLState.suppARBMultitexture = true; + else + gGLState.suppARBMultitexture = false; + + // NV_vertex_array_range ======================================== +/* + glVertexArrayRangeNV = dllVertexArrayRangeNV = NULL; + glFlushVertexArrayRangeNV = dllFlushVertexArrayRangeNV = NULL; +*/ +/* + wglAllocateMemoryNV = NULL; + wglFreeMemoryNV = NULL; +*/ + if (pExtString && dStrstr(pExtString, (const char*)"GL_NV_vertex_array_range") != NULL) + gGLState.suppVertexArrayRange = true; + else + gGLState.suppVertexArrayRange = false; + + + // EXT_fog_coord ======================================== +/* + glFogCoordfEXT = NULL; + glFogCoordPointerEXT = NULL; +*/ + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_fog_coord") != NULL) + gGLState.suppFogCoord = true; + else + gGLState.suppFogCoord = false; + + // ARB_texture_compression ======================================== +/* + glCompressedTexImage3DARB = NULL; + glCompressedTexImage2DARB = NULL; + glCompressedTexImage1DARB = NULL; + glCompressedTexSubImage3DARB = NULL; + glCompressedTexSubImage2DARB = NULL; + glCompressedTexSubImage1DARB = NULL; + glGetCompressedTexImageARB = NULL; +*/ + if (pExtString && dStrstr(pExtString, (const char*)"GL_ARB_texture_compression") != NULL) + gGLState.suppTextureCompression = true; + else + gGLState.suppTextureCompression = false; + + + // 3DFX_texture_compression_FXT1 ======================================== + if (pExtString && dStrstr(pExtString, (const char*)"GL_3DFX_texture_compression_FXT1") != NULL) + gGLState.suppFXT1 = true; + else + gGLState.suppFXT1 = false; + + + // EXT_texture_compression_S3TC ======================================== + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_texture_compression_s3tc") != NULL) + gGLState.suppS3TC = true; + else + gGLState.suppS3TC = false; + + + // WGL_3DFX_gamma_control ======================================== +/* + qwglGetDeviceGammaRamp3DFX = NULL; + qwglSetDeviceGammaRamp3DFX = NULL; +*/ + if (pExtString && dStrstr(pExtString, (const char*)"WGL_3DFX_gamma_control" ) != NULL) + { +// qwglGetDeviceGammaRamp3DFX = (qwglGetDeviceGammaRamp3DFX_t) qwglGetProcAddress( "wglGetDeviceGammaRamp3DFX" ); +// qwglSetDeviceGammaRamp3DFX = (qwglSetDeviceGammaRamp3DFX_t) qwglGetProcAddress( "wglSetDeviceGammaRamp3DFX" ); + } + else + { + } + + + // EXT_vertex_buffer ======================================== +/* + glAvailableVertexBufferEXT = NULL; + glAllocateVertexBufferEXT = NULL; + glLockVertexBufferEXT = NULL; + glUnlockVertexBufferEXT = NULL; + glSetVertexBufferEXT = NULL; + glOffsetVertexBufferEXT = NULL; + glFillVertexBufferEXT = NULL; + glFreeVertexBufferEXT = NULL; +*/ + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_vertex_buffer") != NULL) + { + gGLState.suppVertexBuffer = true; + AssertWarn(gGLState.suppVertexBuffer == false, "We're assuming no vertex bufffer support on Mac for now!"); + } + else + gGLState.suppVertexBuffer = false; + + // Binary states, i.e., no supporting functions ======================================== + // EXT_packed_pixels + // EXT_texture_env_combine ... + 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.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); + + + // Texture combine units ======================================== + if (gGLState.suppARBMultitexture) + glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &gGLState.maxTextureUnits); + else + gGLState.maxTextureUnits = 1; + + + // Swap interval ======================================== +/* + qwglSwapIntervalEXT = NULL; +*/ + if (pExtString && dStrstr(pExtString, (const char*)"WGL_EXT_swap_control") != NULL) + gGLState.suppSwapInterval = true; //( qwglSwapIntervalEXT != NULL ); + else + gGLState.suppSwapInterval = false; + + + // console out all the extensions... + Con::printf("OpenGL Init: Enabled Extensions"); + if (gGLState.suppARBMultitexture) Con::printf(" ARB_multitexture (Max Texture Units: %d)", gGLState.maxTextureUnits); + 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.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/platformMacCarb/macCarbHeaders.h b/platformMacCarb/macCarbHeaders.h new file mode 100644 index 0000000..db1e6ac --- /dev/null +++ b/platformMacCarb/macCarbHeaders.h @@ -0,0 +1,205 @@ +// >>> MODIFIED CARBON.H FOR DIRECT INCLUSION IN V12 CODEBASE +// >>> INCLUDES MINIMUM-NECESSARY GENERAL-USE HEADERS. + +#ifndef __MINCARBON__ +#define __MINCARBON__ + +//#ifndef __CORESERVICES__ +//#include +//#endif + +//#ifndef __APPLICATIONSERVICES__ +//#include +//#endif + + +#ifndef __EVENTS__ +#include +#endif + +#ifndef __PROCESSES__ +#include +#endif + +#ifndef __NOTIFICATION__ +#include +#endif + +//#ifndef __DRAG__ +//#include +//#endif + +//#ifndef __ICONS__ +//#include +//#endif + +#ifndef __CONTROLS__ +#include +#endif + +#ifndef __APPEARANCE__ +#include +#endif + +#ifndef __MACWINDOWS__ +#include +#endif + +#ifndef __TEXTEDIT__ +#include +#endif + +#ifndef __MENUS__ +#include +#endif + +#ifndef __DIALOGS__ +#include +#endif + +//#ifndef __LISTS__ +//#include +//#endif + +//#ifndef __CARBONEVENTS__ +//#include +//#endif + +//#ifndef __TEXTSERVICES__ +//#include +//#endif + +#ifndef __SCRAP__ +#include +#endif + +//#ifndef __MACTEXTEDITOR__ +//#include +//#endif + +//#ifndef __MACHELP__ +//#include +//#endif + +#ifndef __CONTROLDEFINITIONS__ +#include +#endif + +//#ifndef __TSMTE__ +//#include +//#endif + +//#ifndef __TRANSLATIONEXTENSIONS__ +//#include +//#endif + +//#ifndef __TRANSLATION__ +//#include +//#endif + +//#ifndef __AEINTERACTION__ +//#include +//#endif + +//#ifndef __TYPESELECT__ +//#include +//#endif + +//#ifndef __INTERNETCONFIG__ +//#include +//#endif + +#ifndef __SOUND__ +#include +#endif + +//#ifndef __OSA__ +//#include +//#endif + +//#ifndef __OSACOMP__ +//#include +//#endif + +//#ifndef __OSAGENERIC__ +//#include +//#endif + +//#ifndef __APPLESCRIPT__ +//#include +//#endif + +//#ifndef __ASDEBUGGING__ +//#include +//#endif + +//#ifndef __ASREGISTRY__ +//#include +//#endif + +//#ifndef __FINDERREGISTRY__ +//#include +//#endif + +//#ifndef __PMAPPLICATION__ +//#include +//#endif + +#ifndef __NAVIGATION__ +#include +#endif + +#ifndef __URLACCESS__ +#include +#endif + +#ifndef __COLORPICKER__ +#include +#endif + +//#ifndef __CMCALIBRATOR__ +//#include +//#endif + +//#ifndef __HTMLRENDERING__ +//#include +//#endif + +//#ifndef __SPEECHRECOGNITION__ +//#include +//#endif + +//#ifndef __NSL__ +//#include +//#endif + +//#ifndef __FINDBYCONTENT__ +//#include +//#endif + +//#ifndef __KEYCHAINHI__ +//#include +//#endif + +//#ifndef __IBCARBONRUNTIME__ +//#include +//#endif + +//#ifndef __APPLEHELP__ +//#include +//#endif + +//#ifndef __ICAAPPLICATION__ +//#include +//#endif + +//#ifndef __ICADEVICE__ +//#include +//#endif + +//#ifndef __ICACAMERA__ +//#include +//#endif + +#endif /* __MINCARBON__ */ + diff --git a/platformMacCarb/macCarbInput.cc b/platformMacCarb/macCarbInput.cc new file mode 100644 index 0000000..68a591b --- /dev/null +++ b/platformMacCarb/macCarbInput.cc @@ -0,0 +1,1316 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformMacCarb/platformMacCarb.h" +#include "platform/platformInput.h" +#include "platform/event.h" +#include "console/console.h" +#include "platform/gameInterface.h" + + +Boolean noDirectMouse = TRUE; // set to FALSE for ISp enable. TRUE for debug. + +#if __APPLE__ // Mach-O + +#else // carbon build. + +#define CALL_IN_SPOCKETS_BUT_NOT_IN_CARBON 1 +#include + +#define MAX_MOUSE_ELS 16 // more than enough for lots of axes and buttons... + +bool gISpAround = TRUE; // until we say otherwise... + +OSStatus status = noErr; + +ISpDeviceReference mouseRef = NULL; +ISpElementListReference mouseElList; +UInt32 mouseElCount = 0; +ISpElementReference mouseEl[MAX_MOUSE_ELS]; +ISpElementInfo mouseElInfo[MAX_MOUSE_ELS]; + +ISpDeviceReference kbRef = NULL; + +#endif + + +// Static class variables: +InputManager* Input::smManager; +bool Input::smActive; + +bool gInputEnabled = false; +bool gMouseEnabled = false; +bool gKBEnabled = false; +bool gMouseActive = false; +bool gKBActive = false; + + +//------------------------------------------------------------------------------ +// Helper functions. Should migrate to an InputManager object at some point. +bool enableKeyboard(void); +void disableKeyboard(void); +bool activateKeyboard(void); +void deactivateKeyboard(void); +bool enableMouse(void); +void disableMouse(void); +bool activateMouse(void); +void deactivateMouse(void); + + + +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(); + + smManager = NULL; + smActive = false; + +#if __APPLE__ +#else + if (ISpInit) + { +// if we have no needs, do we bother calling ISpInit?? !!!!!TBD +// ispErr = ISpInit(); + + // setup the mouse. we only currently look for the very first one. + UInt32 count; + status = ISpDevices_ExtractByClass((UInt32)kISpDeviceClass_Mouse, (UInt32)1, &count, &mouseRef); + if (status!=noErr) goto initError; + if (count!=1) + { + Con::warnf("Error: InputSprocket detects %d mice.", count); + goto initError; + } + + // retrieve all the buttons & axes. + // first, grab the list reference. + status = ISpDevice_GetElementList(mouseRef, &mouseElList); + if (status!=noErr) goto initError; + + // then, extract the elements from the list into an array. + status = ISpElementList_Extract(mouseElList, (UInt32)MAX_MOUSE_ELS, &mouseElCount, mouseEl); + if (status!=noErr) goto initError; + + // then, save away the static info on each element + for (int i=0; i Found mouse axis: %s.", p2str(mouseElInfo[i].theString)); + else + if (mouseElInfo[i].theKind==kISpElementKind_Button) + Con::printf(" > Found mouse button: %s.", p2str(mouseElInfo[i].theString)); + else + Con::printf(" > Found mouse element: %s.", p2str(mouseElInfo[i].theString)); + } + } + + Con::printf( " InputSprocket enabled - setup complete." ); + + // we exit as we're all set. + return; + } + +initError: + Con::printf( " InputSprocket not enabled." ); + gISpAround = false; + +#endif + +/* + smManager = new DInputManager; + if ( !smManager->enable() ) + { + Con::printf( " DirectInput not enabled." ); + delete smManager; + smManager = NULL; + } + else + { + DInputManager::init(); + Con::printf( " DirectInput enabled." ); + } +*/ + +// fillAsciiTable(); + Con::printf( "" ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( isJoystickDetected, bool, 1, 1, "isJoystickDetected()" ) +{ +/* + argc; argv; + return( DInputDevice::joystickDetected() ); +*/ + return(false); +} + +//------------------------------------------------------------------------------ +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 ); + 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() +{ +#ifdef LOG_INPUT + if ( gInputLog ) + { + log( "*** CLOSING LOG ***\n" ); + CloseHandle( gInputLog ); + gInputLog = NULL; + } +#endif + + // turn us off. + if (gInputEnabled) + disable(); + +/* + if ( smManager && smManager->isEnabled() ) + { + smManager->disable(); + delete smManager; + smManager = NULL; + } +*/ +} + +//------------------------------------------------------------------------------ +bool Input::enable() +{ +//Con::printf( "[]Input::enable." ); + + gInputEnabled = true; + +// if ( smManager && !smManager->isEnabled() ) +// return( smManager->enable() ); + + enableMouse(); + //enableKeyboard(); + + return( gInputEnabled ); +} + +//------------------------------------------------------------------------------ +void Input::disable() +{ +//Con::printf( "[]Input::disable." ); + + gInputEnabled = false; + +// if ( smManager && smManager->isEnabled() ) +// smManager->disable(); + + disableMouse(); + //disableKeyboard(); +} + +//------------------------------------------------------------------------------ +void Input::activate() +{ +//Con::printf( "[]Input::activate." ); + + smActive = true; + +#if __APPLE__ // Mach-O + +#else // carbon. + if (gISpAround) + { + ISpResume(); + + enableMouse(); +// enableKeyboard()); + } +#endif + +/* + DInputDevice::resetModifierKeys(); + 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->isKeyboardEnabled() ) + dInputManager->activateKeyboard(); + + if ( Video::isFullScreen() ) + { + // DirectInput Mouse Hook-Up: + if ( dInputManager->isMouseEnabled() ) + dInputManager->activateMouse(); + } + else + dInputManager->deactivateMouse(); + + if ( dInputManager->isJoystickEnabled() ) + dInputManager->activateJoystick(); + } + } +*/ +} + +//------------------------------------------------------------------------------ +void Input::deactivate() +{ +//Con::printf( "[]Input::deactivate." ); + +#if __APPLE__ // Mach-O + +#else // carbon. + if (gISpAround) + { + deactivateMouse(); + //deactivateKeyboard(); + ISpSuspend(); + +// this is now extra... +// though, extra show can't hurt. +// ShowCursor(); + } +#endif + + smActive = false; + +/* + if ( smManager && smManager->isEnabled() && smActive ) + { +#ifdef LOG_INPUT + Input::log( "Deactivating DirectInput...\n" ); +#endif + DInputManager* dInputManager = dynamic_cast( smManager ); + if ( dInputManager ) + { + dInputManager->deactivateKeyboard(); + dInputManager->deactivateMouse(); + dInputManager->deactivateJoystick(); + } + + smActive = false; + Con::printf( "DirectInput deactivated." ); + } +*/ +} + +//------------------------------------------------------------------------------ +void Input::reactivate() +{ + // don't think mac needs to do anything right now!!!!!! TBD + + // This is soo hacky... +// SetForegroundWindow( winState.appWindow ); +// PostMessage( winState.appWindow, WM_ACTIVATE, WA_ACTIVE, NULL ); +} + +//------------------------------------------------------------------------------ +bool Input::isEnabled() +{ +// if ( smManager ) +// return smManager->isEnabled(); + + return(gInputEnabled); +} + +//------------------------------------------------------------------------------ +bool Input::isActive() +{ + return smActive; +} + +//------------------------------------------------------------------------------ +void Input::process() +{ + if (!smActive || !gInputEnabled) + return; + +#if __APPLE__ // Mach-O + +#else // carbon. + if (gISpAround) + if (gMouseEnabled && gMouseActive) + { + ISpElementReference currEl; + ISpElementKind currKind; + ISpElementLabel currLabel; + ISpElementEvent mouseMove; + Boolean gotEvent; + UInt32 simple; + UInt32 btnNum = 0; + static int btnState[8] = {0,0,0,0,0,0,0,0}; + bool btnChange; + +//#define MOUSE_DATA_INC (256.0) +#define MOUSE_DATA_INC (128.0) +#define INV_MOUSERANGE (1.0/MOUSE_DATA_INC) + +#define MOUSE_START_VAL -111 + static int mouseX = MOUSE_START_VAL; + static int mouseY = MOUSE_START_VAL; + static int mouseZ = MOUSE_START_VAL; + + InputEvent event; + + // loop through the mouse's elements that we extracted, and + for (int i=0; ipostEvent(event); + } + } + else + if (currKind == kISpElementKind_Button) + { + btnChange = false; + status = ISpElement_GetSimpleState(currEl, &simple); + if (status==noErr) + { + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_BUTTON; + event.objInst = KEY_BUTTON0 + btnNum; + event.modifier = 0; + event.ascii = 0; + if (simple == kISpButtonDown) + { + if (!btnState[btnNum]) + { + event.action = SI_MAKE; + event.fValue = 1.0; + btnState[btnNum] = 1; + btnChange = true; + } + } + else + { + if (btnState[btnNum]) + { + event.action = SI_BREAK; + event.fValue = 0.0; + btnState[btnNum] = 0; + btnChange = true; + } + } + + if (btnChange) + Game->postEvent(event); + } + + // we need to keep a count of which button is which, in order. + // !!!!!TBD can we look at the info string to figure out which is which? + btnNum++; + } + // else we don't handle it. + + // we want to flush the ISp event queue for this els. + ISpElement_Flush(currEl); + } + } +#endif + + +// if ( smManager && smManager->isEnabled() && smActive ) +// smManager->process(); +} + +//------------------------------------------------------------------------------ +InputManager* Input::getManager() +{ + return( smManager ); +} + + +//-------------------------------------------------------------------------- +#pragma message("input remap table might need tweaking - rumors of ibooks having diff virt keycodes, might need intermediate remap...") +static U8 VcodeRemap[256] = +{ +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 +KEY_Y, // 0x0A // this is questionable - not normal Y code +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 +0, // 0x36 //? +KEY_ALT, // 0x37 // best mapping for mac Cmd key +KEY_LSHIFT, // 0x38 +KEY_CAPSLOCK, // 0x39 +KEY_MAC_OPT, // 0x3A // direct map mac Option key -- better than KEY_WIN_WINDOWS +KEY_CONTROL, // 0x3B +KEY_RSHIFT, // 0x3C +0, // 0x3D +0, // 0x3E +0, // 0x3F +0, // 0x40 +KEY_DECIMAL, // 0x41 +0, // 0x42 +KEY_MULTIPLY, // 0x43 +0, // 0x44 +KEY_ADD, // 0x45 +KEY_SUBTRACT, // 0x46 // secondary code? +KEY_NUMLOCK, // 0x47 // also known as Clear on mac... +KEY_SEPARATOR, // 0x48 // secondary code? for KPEqual +0, // 0x49 +0, // 0x4A +KEY_DIVIDE, // 0x4B +KEY_NUMPADENTER, // 0x4C +KEY_DIVIDE, // 0x4D // secondary code? +KEY_SUBTRACT, // 0x4E +0, // 0x4F +0, // 0x50 +KEY_SEPARATOR, // 0x51 // WHAT IS SEP? This is KPEqual on mac. +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_PRINT, // 0x69 +0, // 0x6A +KEY_SCROLLLOCK, // 0x6B +0, // 0x6C +KEY_F10, // 0x6D +0, // 0x6E +KEY_F12, // 0x6F +0, // 0x70 +KEY_PAUSE, // 0x71 +KEY_INSERT, // 0x72 // also known as mac Help +KEY_HOME, // 0x73 +KEY_PAGE_UP, // 0x74 +KEY_DELETE, // 0x75 // FwdDel +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 +0, // 0x7F +0, // 0x80 +0, // 0x81 +0, // 0x82 +0, // 0x83 +0, // 0x84 +0, // 0x85 +0, // 0x86 +0, // 0x87 +0, // 0x88 +0, // 0x89 +0, // 0x8A +0, // 0x8B +0, // 0x8C +0, // 0x8D +0, // 0x8E +0, // 0x8F + +0, // 0x90 +0, // 0x91 +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 + +0, // 0xA0 +0, // 0xA1 +0, // 0xA2 +0, // 0xA3 +0, // 0xA4 +0, // 0xA5 +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 +0, // 0xBA +0, // 0xBB +0, // 0xBC +0, // 0xBD +0, // 0xBE +0, // 0xBF +0, // 0xC0 +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 +0, // 0xDB +0, // 0xDC +0, // 0xDD +0, // 0xDE +0, // 0xDF +0, // 0xE0 +0, // 0xE1 +0, // 0xE2 +0, // 0xE3 +0, // 0xE4 + +0, // 0xE5 + +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 +0, // 0xF7 +0, // 0xF8 +0, // 0xF9 +0, // 0xFA +0, // 0xFB +0, // 0xFC +0, // 0xFD +0, // 0xFE +0 // 0xFF +}; + + +U8 TranslateOSKeyCode(U8 vcode) +{ + return VcodeRemap[vcode]; +} + + +//----------------------------------------------------------------------------- +// 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(); +*/ + char *returnBuf = Con::getReturnBuffer(16); + returnBuf[0] = 0; + + //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; +*/ + return false; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +bool enableKeyboard() +{ +#if __APPLE__ +#else + if ( !gISpAround ) return(false); +#endif + + if ( !gInputEnabled ) + return( false ); + + if ( gKBEnabled && gKBActive ) + return( true ); + + gKBEnabled = true; + if ( Input::isActive() ) + gKBEnabled = activateKeyboard(); + + if ( gKBEnabled ) + { + Con::printf( "Hardware-direct keyboard enabled." ); +#ifdef LOG_INPUT + Input::log( "Keyboard enabled.\n" ); +#endif + } + else + { + Con::warnf( "Hardware-direct keyboard failed to enable!" ); +#ifdef LOG_INPUT + Input::log( "Keyboard failed to enable!\n" ); +#endif + } + + return( gKBEnabled ); +} + +//------------------------------------------------------------------------------ +void disableKeyboard() +{ +#if __APPLE__ +#else + if ( !gISpAround ) return; +#endif + + if ( !gInputEnabled || !gKBEnabled ) + return; + + deactivateKeyboard(); + gKBEnabled = false; + + Con::printf( "Hardware-direct keyboard disabled." ); +#ifdef LOG_INPUT + Input::log( "Keyboard disabled.\n" ); +#endif +} + +//------------------------------------------------------------------------------ +bool activateKeyboard() +{ +#if __APPLE__ +#else + if ( !gISpAround ) return(false); +#endif + + if ( !gInputEnabled || !Input::isActive() || !gKBEnabled ) + return( false ); + +#if __APPLE__ +#else + status = ISpDevices_Activate(1, &kbRef); + if (status==noErr) + gKBActive = true; +#endif + +#ifdef LOG_INPUT + Input::log( gKBActive ? "Keyboard activated.\n" : "Keyboard failed to activate!\n" ); +#endif + return( gKBActive ); +} + +//------------------------------------------------------------------------------ +void deactivateKeyboard() +{ +#if __APPLE__ +#else + if ( !gISpAround ) return; +#endif + + if ( gInputEnabled && gKBActive ) + { +#if __APPLE__ +#else + status = ISpDevices_Deactivate(1, &kbRef); +// if (status!=noErr) goto initError; + gKBActive = false; +#endif + +#ifdef LOG_INPUT + Input::log( "Keyboard deactivated.\n" ); +#endif + } +} + +//------------------------------------------------------------------------------ +bool enableMouse() +{ +#if __APPLE__ +#else + if ( !gISpAround ) return (false); +#endif + +// Con::printf( ">> em one." ); + + if ( !gInputEnabled || noDirectMouse ) + return( false ); + +// Con::printf( ">> em two." ); + + if ( gMouseEnabled && gMouseActive ) + return( true ); + +// Con::printf( ">> em three." ); + + gMouseEnabled = true; //i.e., allowed. needed to call activate... + gMouseEnabled = activateMouse(); + + if ( gMouseEnabled ) + { + Con::printf( "Hardware-direct mouse enabled." ); +#ifdef LOG_INPUT + Input::log( "Mouse enabled.\n" ); +#endif + } + else + { + Con::warnf( "Hardware-direct mouse failed to enable!" ); +#ifdef LOG_INPUT + Input::log( "Mouse failed to enable!\n" ); +#endif + } + + return( gMouseEnabled ); +} + +//------------------------------------------------------------------------------ +void disableMouse() +{ +#if __APPLE__ +#else + if ( !gISpAround ) return; +#endif + + if ( !gInputEnabled || !gMouseEnabled ) + return; + + deactivateMouse(); + gMouseEnabled = false; + + Con::printf( "Hardware-direct mouse disabled." ); +#ifdef LOG_INPUT + Input::log( "Mouse disabled.\n" ); +#endif +} + +//------------------------------------------------------------------------------ +bool activateMouse() +{ +#if __APPLE__ +#else + if ( !gISpAround ) return(false); +#endif + +// Con::printf( ">> am one. %s %s %s", gInputEnabled?"true":"false", Input::isActive()?"true":"false", gMouseEnabled?"true":"false"); + if ( !gInputEnabled || !Input::isActive() || !gMouseEnabled ) + return( false ); + + if (gMouseActive) + return(true); // we're already set up. + +// Con::printf( ">> am two." ); +#if __APPLE__ +#else + status = ISpDevices_Activate(1, &mouseRef); + if (status==noErr) + { + gMouseActive = true; + HideCursor(); +// Con::printf( ">> am three." ); + } +#endif + +// Con::printf( ">> am four." ); + +#ifdef LOG_INPUT + Input::log( gMouseActive ? "Mouse activated.\n" : "Mouse failed to activate!\n" ); +#endif + return( gMouseActive ); +} + +//------------------------------------------------------------------------------ +void deactivateMouse() +{ +#if __APPLE__ +#else + if ( !gISpAround ) return; +#endif + +// Con::printf( ">> dm one. %s %s %s", gInputEnabled?"true":"false", gMouseActive?"true":"false"); + if ( !gInputEnabled || !gMouseActive ) return; + +#if __APPLE__ +#else + status = ISpDevices_Deactivate(1, &mouseRef); +// if (status!=noErr) goto initError; + gMouseActive = false; + ShowCursor(); +#endif + +// Con::printf( ">> dm two." ); + +#ifdef LOG_INPUT + Input::log( "Mouse deactivated.\n" ); +#endif +} + + +//------------------------------------------------------------------------------ +ConsoleFunction( enableMouse, bool, 1, 1, "enableMouse()" ) +{ + return( enableMouse() ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( disableMouse, void, 1, 1, "disableMouse()" ) +{ + disableMouse(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( permDisableMouse, void, 1, 1, "permDisableMouse()" ) +{ + disableMouse(); + noDirectMouse = TRUE; +} + +//------------------------------------------------------------------------------ +void printInputState(void) +{ +#if __APPLE__ +#else + if ( !gISpAround ) return; +#endif + + if ( gInputEnabled ) + { +#if __APPLE__ +#else + Con::printf( "InputSprocket is enabled %s.", Input::isActive() ? "and active" : "but inactive" ); +#endif + Con::printf( "- Keyboard is %sabled and %sactive.", + gKBEnabled ? "en" : "dis", + gKBActive ? "" : "in" ); + Con::printf( "- Mouse is %sabled and %sactive.", + gMouseEnabled ? "en" : "dis", + gMouseActive ? "" : "in" ); +/* + Con::printf( "- Joystick is %sabled and %sactive.", + gJoystickEnabled() ? "en" : "dis", + gJoystickActive() ? "" : "in" ); +*/ + } + else +#if __APPLE__ +#else + Con::printf( "InputSprocket is not currently enabled." ); +#endif +} + +//------------------------------------------------------------------------------ +ConsoleFunction( echoInputState, void, 1, 1, "echoInputState()" ) +{ + printInputState(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( toggleInputState, void, 1, 1, "toggleInputState()" ) +{ + if (gInputEnabled) + Input::disable(); + else + Input::enable(); + + printInputState(); +} diff --git a/platformMacCarb/macCarbMath.cc b/platformMacCarb/macCarbMath.cc new file mode 100644 index 0000000..adf255a --- /dev/null +++ b/platformMacCarb/macCarbMath.cc @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/console.h" +#include "Math/mMath.h" + + +extern void mInstallLibrary_C(); + + +//-------------------------------------- +static void cMathInit(SimObject *obj, S32 argc, const char **argv) +{ + obj; + argc; + argv; + 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; + } + Con::printf("Error: MathInit(): ignoring unknown math extension '%s'", *argv); + } + Math::init(properties); +} + + + +//------------------------------------------------------------------------------ +void Math::init(U32 properties) +{ + Con::addCommand( "MathInit", cMathInit, "MathInit(detect|C|FPU|MMX|3DNOW|SSE|...)", 1, 10); + + 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 (properties & CPU_PROP_FPU) + { + Con::printf(" Installing FPU extensions"); + } + Con::printf(" "); +} + + diff --git a/platformMacCarb/macCarbMemory.cc b/platformMacCarb/macCarbMemory.cc new file mode 100644 index 0000000..27c57bc --- /dev/null +++ b/platformMacCarb/macCarbMemory.cc @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformMacCarb/platformMacCarb.h" +#include +#include + +//-------------------------------------- +#ifdef new +#undef new +#endif + +void* FN_CDECL operator new(dsize_t dt, void* ptr) +{ + dMemset(ptr, 0xFF, dt); + return (ptr); +} + + +//-------------------------------------- +void* dRealMalloc(dsize_t in_size) +{ + return malloc(in_size); +} + + +//-------------------------------------- +void dRealFree(void* in_pFree) +{ + free(in_pFree); +} + + +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)); +} diff --git a/platformMacCarb/macCarbNPatch.h b/platformMacCarb/macCarbNPatch.h new file mode 100644 index 0000000..fcfe831 --- /dev/null +++ b/platformMacCarb/macCarbNPatch.h @@ -0,0 +1,55 @@ +// macCarbNPatch.h +// +// mac specific implementation(s) of NPatch functionality +// since each platform might use slightly diff methods to control. + +// current Mac NPatches is ATI TRUFORM implementation, accessed on OS9 via a +// back door method. OSX tests for the ATIX extension. + +#if !__APPLE__ +#define AGLSETINT_NPATCH_FLAG ((unsigned long)500) +#define AGLSETINT_NPATCH_LOD ((unsigned long)501) +#endif + +// for the moment, this seems to be the best roundup of +// the npatch extensions on the PC. + +#ifndef GL_ATIX_pn_triangles +#define GL_ATIX_pn_triangles 1 +#define GL_PN_TRIANGLES_ATIX 0x6090 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATIX 0x6091 +#define GL_PN_TRIANGLES_POINT_MODE_ATIX 0x6092 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATIX 0x6093 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATIX 0x6094 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATIX 0x6095 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATIX 0x6096 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATIX 0x6097 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATIX 0x6098 + +#if __APPLE__ // for the moment... +extern void glPNTrianglesiATIX(GLenum pname, GLint param); +extern void glPNTrianglesfATIX(GLenum pname, GLfloat param); +#endif +#endif + +typedef void (*PFNGLPNTRIANGLESIATIPROC)(GLenum pname, GLint param); +//typedef void (APIENTRY *PFNGLPNTRIANGLESFATIPROC)(GLenum pname, GLfloat param); + +#define GL_NPATCH_EXT_STRING "GL_ATIX_pn_triangles" +#define GL_NPATCH_SETINT_STRING "glPNTrianglesiATIX" +typedef PFNGLPNTRIANGLESIATIPROC PFNNPatchSetInt; + +#define GETINT_NPATCH_MAX_LEVEL GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATIX + +#define GL_NPATCH_FLAG GL_PN_TRIANGLES_ATIX + +#define SETINT_NPATCH_LOD GL_PN_TRIANGLES_TESSELATION_LEVEL_ATIX + +#define SETINT_NPATCH_POINTINTERP GL_PN_TRIANGLES_POINT_MODE_ATIX +#define SETINT_NPATCH_NORMALINTERP GL_PN_TRIANGLES_NORMAL_MODE_ATIX + +#define NPATCH_POINTINTERP_MIN GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATIX +#define NPATCH_POINTINTERP_MAX GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATIX + +#define NPATCH_NORMALINTERP_MIN GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATIX +#define NPATCH_NORMALINTERP_MAX GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATIX diff --git a/platformMacCarb/macCarbNet.cc b/platformMacCarb/macCarbNet.cc new file mode 100644 index 0000000..e1814a1 --- /dev/null +++ b/platformMacCarb/macCarbNet.cc @@ -0,0 +1,982 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +//!!!!!!!!! NOTE !!!!!!!!! +// Since the platform layers are currently separate, the Mac implementation takes +// extreme advantage of knowing that all values are already in 'network order', and +// that a byte array of an ip address is the same byte order as a U32 ip address. + + +// dc - need to inlucde OT stuff first, as V12 does some nasty stuff with redeclaring +// base new() implementation to take filename/linenum, and screws up the OT hdrs. +#include +#if __APPLE__ +#include +#include +#define IP_BROADCAST kIP_BROADCAST +#define IP_REUSEADDR kIP_REUSEADDR +#else +#include +#endif + +#include "PlatformMacCarb/platformMacCarb.h" +#include "Platform/platform.h" +#include "Platform/event.h" +#include "console/console.h" +#include "Platform/gameInterface.h" +#include "Core/fileStream.h" + + + +struct Connection +{ + NetSocket socket; + S32 state; + S32 prevState; + bool listenConnection; + Connection *nextConnection; + Connection *nextInTable; +// HANDLE connectThreadHandle; + U32 tag; +}; + +struct Status +{ + bool mSignal; + OSErr mErr; + void *mData; + Status() + { + mSignal = false; + mErr = kOTNoError; + mData = NULL; + } +}; + + +// main control vars. +static Net::Error getLastError(); +static S32 defaultPort = 28000; +static S32 netPort = 0; +static bool sendPackets = true; + + +static U32 nextConnectionId = 1; +static U32 nextAcceptId = 1000000000; +static Connection *connectionList = NULL; +enum { + ConnectionTableSize = 256, + ConnectionTableMask = 0xFF +}; +static Connection *connectionTable[ConnectionTableSize] = { 0, }; + + +// OpenTransport init controls. +static U32 OTReferenceCount = 0; + + +//====================================================================== +// NEW MAC GLOBALS +//====================================================================== +// structures we want/need to hang around. +static EndpointRef mainEndpoint = NULL; +static InetAddress mainPortAddr, destPortAddr; +static TBind tbRequest, tbBound; +static TUnitData tSend, tRecv; +static OTFlags tFlags = 0L; + + +// error holders. +static OSErr macErr = kOTNoError; +static const OSStatus kStatusFine = 0L; +static OSStatus macStatus = kStatusFine; +static OTResult macResult = kOTNoError; + +#define HOSTFROMBYTES(a) (*((InetHost*)(a))) +static const InetHost broadcastIP = 0xFFFFFFFF; +static const InetHost loopbackIP = 0x7F000001; // 127.0.0.1 + +#if V12_DEBUG +static InetHost testloop = 0; +static U8 test2[4]; +#endif + +//====================================================================== +static pascal void EventProc( void *_pSession, OTEventCode event, OTResult result, void *cookie ) +{ + Status *status = (Status *) _pSession; + + switch ( event ) + { + case T_DATA: + //pSess->m_dataToRead = 1; + break; + + case T_BINDCOMPLETE: + status->mErr = (OSErr) result; + status->mSignal = true; + break; + + case T_OPENCOMPLETE: + status->mErr = (OSErr) result; + status->mData = cookie; // the EndPoint + status->mSignal = true; + break; + } +} + + +/* +//====================================================================== +void Net::setJournaling(bool jrn) +{ + sendPackets = !jrn; +} +*/ + + +//====================================================================== +bool Net::init() +{ + if ( OTReferenceCount == 0 ) + if (InitOpenTransport() != kOTNoError) + return false; + +#if V12_DEBUG // this just validates the byte ordering of host ips in a byte array. + OTInetStringToHost("127.0.2.1", &testloop); + OTInetStringToHost("127.0.2.1", (InetHost*)test2); +#endif + + // successfully opened Open Transport, inncrement the reference count + OTReferenceCount++; + return true; +} + + +//====================================================================== +void Net::shutdown() +{ + while(connectionList) + Net::closeConnectTo(connectionList->tag); + + // make sure that we shut down the main open port. + closePort(); + if ( OTReferenceCount <= 0 ) + return; + + // close Open Transport if there are no more references to it + OTReferenceCount--; + if ( OTReferenceCount == 0 ) + CloseOpenTransport(); +} + + +//====================================================================== +static void netToIPSocketAddress(const NetAddress *address, InetAddress *inAddr) +{ + dMemset(inAddr, 0, sizeof(InetAddress)); + inAddr->fAddressType = AF_INET; + inAddr->fHost = HOSTFROMBYTES(address->netNum); + inAddr->fPort = address->port; +} + + +//====================================================================== +static void IPSocketToNetAddress(const InetAddress *inAddr, NetAddress *address) +{ + address->type = NetAddress::IPAddress; + address->port = inAddr->fPort; + HOSTFROMBYTES(address->netNum) = inAddr->fHost; +} + + +//====================================================================== +bool Net::openPort(S32 port) +{ + // check if we have an endpoint already. + if (mainEndpoint != NULL) // just unbind it - no need to close & recreate... + OTUnbind(mainEndpoint); + else // need to create a new endpoint. + { + mainEndpoint = OTOpenEndpoint(OTCreateConfiguration(kUDPName), 0L, NULL, &macStatus); + if (mainEndpoint == NULL) + { + // !!!!!!TBD add error case. + // macStatus may need to be converted... + return false; + } + } + + // then, bind the endpoint to the specified address. + + // first, clear the bind value struct. + tbBound.qlen = 0; + tbBound.addr.maxlen = 0; + tbBound.addr.len = 0; + tbBound.addr.buf = NULL; + + // then, setup the bind request struct to point to a port addr struct. + tbRequest.qlen = 4; + tbRequest.addr.maxlen = sizeof(InetAddress); + tbRequest.addr.len = sizeof(InetAddress); + tbRequest.addr.buf = (U8*)(&mainPortAddr); + + // then, fill the port address structure for the given port. + // this will bind to whichever net device it first finds. !!!!!TBD???? + OTInitInetAddress(&mainPortAddr, port, kOTAnyInetAddress); + + // then do the bind. this is sync, obv. + macStatus = OTBind(mainEndpoint, &tbRequest, &tbBound); + if (macStatus != kStatusFine) + { + return false; + } + + // now that we're bound, need to 'negotiate' for capabilities. + U8 omBuffer[kOTFourByteOptionSize]; + TOptMgmt omRequest; + TOptMgmt omReply; + // alias the buffer for cleaner structure access. + TOption *omOpts = (TOption*)omBuffer; + + // set up the request and reply structures. + omRequest.flags = T_NEGOTIATE; + omRequest.opt.len = kOTFourByteOptionSize; + omRequest.opt.buf = omBuffer; + + omReply.opt.maxlen = kOTFourByteOptionSize; + omReply.opt.buf = omBuffer; + + // main thing we need is BROADCAST cap. + omOpts->len = kOTFourByteOptionSize; + omOpts->status = 0; + omOpts->level = INET_IP; // we're dealing with IP protocols. + omOpts->name = IP_BROADCAST; // we want BROADCAST. + *(U32*)(omOpts->value) = TRUE; // we want broadcast to be ENABLED. + + macStatus = OTOptionManagement(mainEndpoint, &omRequest, &omReply); + if(macStatus != kStatusFine) + { + // need to look deeper to see what really happened. + if(omOpts->status != T_SUCCESS) + { + // !!!!!TBD convert/handle the error! + } + + return false; + } + + // we also want REUSE_ADDR + omOpts->len = kOTFourByteOptionSize; + omOpts->status = 0; + omOpts->level = INET_IP; // we're dealing with IP protocols. + omOpts->name = IP_REUSEADDR; // we want REUSE_ADDR. + *(U32*)(omOpts->value) = TRUE; // we want broadcast to be ENABLED. + + macStatus = OTOptionManagement(mainEndpoint, &omRequest, &omReply); + if(macStatus != kStatusFine) + { + // need to look deeper to see what really happened. + if(omOpts->status != T_SUCCESS) + { + // !!!!!TBD convert/handle the error! + } + + return false; + } + + // we're ready. hold onto assigned portnum in a global... + netPort = port; + + return true; +} + + +//====================================================================== +void Net::closePort() +{ + // check if we have an endpoint. + if (mainEndpoint != NULL) + { + // for now, unbind it + OTUnbind(mainEndpoint); + netPort = 0; + + // we'll try this for nicer cleanup... + OTCloseProvider(mainEndpoint); + mainEndpoint = NULL; + } +} + + +//====================================================================== +Net::Error Net::sendto(const NetAddress *address, const U8 *buffer, S32 bufferSize) +{ + if (!sendPackets) + return NoError; + + if (mainEndpoint == NULL) + return NoError; // !!!!!TBD???? + + // clear any send struct fields that should be clean... + tSend.opt.len = 0; + + // setup the send buffer. + tSend.udata.maxlen = bufferSize; + tSend.udata.len = bufferSize; + tSend.udata.buf = (U8*)buffer; + + // setup the dest addr struct. + tSend.addr.maxlen = sizeof(InetAddress); + tSend.addr.len = sizeof(InetAddress); + tSend.addr.buf = (U8*)(&mainPortAddr); + + // fill in the dest address. + netToIPSocketAddress(address, &mainPortAddr); + + // then send it. + macResult = OTSndUData(mainEndpoint, &tSend); + if(macResult < 0) + { + //!!!!!!TBD handle error. + return UnknownError; + } + + return NoError; +} + + +//====================================================================== +static PacketReceiveEvent receiveEvent; // why create on stack each time... +//====================================================================== +void Net::process() +{ + S32 bytesRead; + + if (mainEndpoint==NULL) + return; + + macResult = kOTNoError; + + while(1) + { + bytesRead = 0L; + + // clear any send struct fields that should be clean... + tRecv.opt.len = 0; + + // setup the recv buffer to point to the receive event packet.. + tRecv.udata.maxlen = MaxPacketDataSize; + tRecv.udata.len = MaxPacketDataSize; + tRecv.udata.buf = receiveEvent.data; + + // setup the dest addr struct. + tRecv.addr.maxlen = sizeof(InetAddress); + tRecv.addr.len = sizeof(InetAddress); + tRecv.addr.buf = (U8*)(&destPortAddr); + + tFlags = 0L; + macResult = OTRcvUData(mainEndpoint, &tRecv, &tFlags); + if (macResult == kOTNoError) + bytesRead = tRecv.udata.len; + else + if (macResult < 0) + { + if (macResult == kOTNoDataErr) + break; // clean return. + else + { + // !!!!TBD error handling. + break; + } + } +// else // !!!!TBD????? what to do with positive error? +// { +// } + + + IPSocketToNetAddress(&destPortAddr, &receiveEvent.sourceAddress); + + // check for loopback. + 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; + + // validate that we read something, else loop again. + if (bytesRead <= 0) + continue; + + // post the received data. + receiveEvent.size = PacketReceiveEventHeaderSize + bytesRead; + Game->postEvent(receiveEvent); + } + + // !!!!!!TBD + // may need to convert macResult into another variable + // for referencing error later on... +} + + +// THE NEXT SET OF METHODS ARE FOR HANDLING TCP-STREAM PORTS. + + +//====================================================================== +//DWORD WINAPI connectThreadFunction(LPVOID param) +//{ +// Connection *con = (Connection *) param; +// con; +// return 0; +//} + + +//====================================================================== +static Connection *newConnection(U32 id) +{ + Connection *conn = new Connection; + conn->nextConnection = connectionList; + conn->tag = id; + connectionList = conn; + conn->nextInTable = connectionTable[conn->tag & ConnectionTableMask]; + connectionTable[conn->tag & ConnectionTableMask] = conn; + return conn; +} + + +//====================================================================== +NetSocket Net::openListenPort(U16 port) +{ + nextConnectionId++; + if(!sendPackets) + return nextConnectionId; + + Connection *conn = newConnection(nextConnectionId); + + conn->listenConnection = true; + + conn->socket = openSocket(); + bind(conn->socket, port); + listen(conn->socket, 4); + setBlocking(conn->socket, false); + return conn->tag; +} + + +//====================================================================== +NetSocket Net::openConnectTo(const char *stringAddress) +{ + nextConnectionId++; + if(!sendPackets) + return nextConnectionId; + + Connection *conn = newConnection(nextConnectionId); + + conn->listenConnection = false; + conn->prevState = ConnectedNotifyEvent::DNSResolved; + conn->state = ConnectedNotifyEvent::DNSResolved; + + conn->socket = openSocket(); + NetAddress netaddr; + connect(conn->socket, &netaddr); + setBlocking(conn->socket, false); + +//threadHandle = CreateThread(NULL, 0, connectThreadFunction, (LPVOID) conn, 0, &threadId); + + +//CloseHandle(threadHandle); + conn->state = ConnectedNotifyEvent::Connected; + return conn->tag; +} + + +#if later +//====================================================================== +void Net::processConnected() +{ + Connection **walk = &connectionList; + Connection *con; + while((con = *walk) != NULL) + { + bool del = false; + if(con->listenConnection) + { + NetSocket newSocket; + ConnectedAcceptEvent event; + newSocket = accept(con->socket, &event.address); + + if(newSocket != InvalidSocket) + { + Connection *nc = newConnection(nextAcceptId++); + nc->listenConnection = false; + nc->prevState = ConnectedNotifyEvent::Connected; + nc->state = ConnectedNotifyEvent::Connected; + nc->socket = newSocket; + setBlocking(nc->socket, false); + + event.portTag = con->tag; + event.connectionTag = nc->tag; + Game->postEvent(event); + } + walk = &con->nextConnection; + } + else + { + if(con->state != con->prevState) + { + ConnectedNotifyEvent event; + event.tag = con->tag; + event.state = con->state; + Game->postEvent(event); + con->prevState = con->state; + } + if(con->state == ConnectedNotifyEvent::Connected) + { + ConnectedReceiveEvent event; + Net::Error err; + S32 bytesRead; + event.tag = con->tag; + + do { +// err = recv(con->socket, event.data, MaxPacketDataSize, &bytesRead); + if(err == NoError && bytesRead != 0) + { + event.size = ConnectedReceiveEventHeaderSize + bytesRead; + Game->postEvent(event); + } + else if(err != WouldBlock) + { + // bad news... this disconnected + ConnectedNotifyEvent event; + event.tag = con->tag; + event.state = ConnectedNotifyEvent::Disconnected; + Game->postEvent(event); + del = true; + } + } + while(err == NoError && bytesRead != 0); + } + if(del) + { + *walk = con->nextConnection; + closeSocket(con->socket); + for(Connection **tbWalk = &connectionTable[con->tag & ConnectionTableMask]; *tbWalk != NULL; tbWalk = &(*tbWalk)->nextInTable) + { + Connection *dc = *tbWalk; + if(dc->tag == con->tag) + { + *tbWalk = dc->nextInTable; + break; + } + } + delete con; + } + else + walk = &con->nextConnection; + } + } +} +#endif + + +//====================================================================== +void Net::closeConnectTo(NetSocket sock) +{ + if(Game->isJournalReading()) + return; + + Connection **walk; + for(walk = &connectionList; *walk != NULL; walk = &(*walk)->nextConnection) + { + Connection *con = *walk; + if(con->tag == sock) + { + *walk = con->nextConnection; + closeSocket(con->socket); + break; + } + } + for(walk = &connectionTable[sock & ConnectionTableMask]; *walk != NULL; walk = &(*walk)->nextInTable) + { + Connection *con = *walk; + if(con->tag == sock) + { + *walk = con->nextInTable; + delete con; + return; + } + } +} + + +//====================================================================== +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; +} + + +//====================================================================== +NetSocket Net::openSocket() +{ + return InvalidSocket; +#if later + SOCKET retSocket; + retSocket = socket(AF_INET, SOCK_STREAM, 0); + + if(retSocket == INVALID_SOCKET) + return InvalidSocket; + else + return retSocket; +#endif +} + + +//====================================================================== +Net::Error Net::closeSocket(NetSocket socket) +{ + if(socket != InvalidSocket) + { +#if later + if(!closesocket(socket)) + return NoError; + else +#endif + return getLastError(); + } + else + return NotASocket; +} + + +//====================================================================== +Net::Error Net::connect(NetSocket socket, const NetAddress *address) +{ + if(address->type != NetAddress::IPAddress) + return WrongProtocolType; +#if later + SOCKADDR_IN socketAddress; + netToIPSocketAddress(address, &socketAddress); + if(!::connect(socket, (PSOCKADDR) &socketAddress, sizeof(socketAddress))) + return NoError; +#endif + return getLastError(); +} + +Net::Error Net::listen(NetSocket socket, S32 backlog) +{ +#if later + if(!::listen(socket, backlog)) + return NoError; +#endif + return getLastError(); +} + +//====================================================================== +NetSocket Net::accept(NetSocket acceptSocket, NetAddress *remoteAddress) +{ +#if later + SOCKADDR_IN socketAddress; + S32 addrLen = sizeof(socketAddress); + + SOCKET retVal = ::accept(acceptSocket, (PSOCKADDR) &socketAddress, &addrLen); + if(retVal != INVALID_SOCKET) + { + IPSocketToNetAddress(&socketAddress, remoteAddress); + return retVal; + } +#endif + return InvalidSocket; +} + + +//====================================================================== +Net::Error Net::bind(NetSocket socket, U16 port) +{ +#if later + 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( "Host::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, (PSOCKADDR) &socketAddress, sizeof(socketAddress)); + + if(!error) + return NoError; +#endif + return getLastError(); +} + + +//====================================================================== +Net::Error Net::setBufferSize(NetSocket socket, S32 bufferSize) +{ + socket, 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) +{ + socket, 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) +{ + socket, blockingIO; +// DWORD notblock = !blockingIO; +// S32 error = ioctlsocket(socket, FIONBIO, ¬block); +// if(!error) +// return NoError; + return getLastError(); +} + + +//====================================================================== +Net::Error Net::send(NetSocket socket, const U8 *buffer, S32 bufferSize) +{ + Net::Error e; +// e = ::send(socket, (const char*)buffer, bufferSize, 0); + + return getLastError(); +// return e; +} + + +//====================================================================== +Net::Error Net::recv(NetSocket socket, U8 *buffer, S32 bufferSize, S32 *bytesRead) +{ + socket, buffer, bufferSize, bytesRead; +// *bytesRead = ::recv(socket, (char*)buffer, bufferSize, 0); +// if(*bytesRead == SOCKET_ERROR) +// return getLastError(); + return NoError; +} + + +//====================================================================== +bool Net::compareAddresses(const NetAddress *a1, const NetAddress *a2) +{ + if(a1->type != a2->type) + return false; + if(*((U32 *)a1->netNum) != *((U32 *)a2->netNum)) + return false; + +/* this was for IPX only. !!!!tbd + 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) +{ + char sRemote[256]; // temporary space so we can muck with the inString. + char *sPort; + + // clear destination. + dMemset((void*)address, 0, sizeof(NetAddress)); + + // assume IP if it doesn't have ipx: at the front. + if (dStrnicmp(addressString, "ipx:", 4)) + { + if (!dStrnicmp(addressString, "ip:", 3)) + addressString += 3; // eat off the ip: + + if (dStrlen(addressString) > 255) // waaay too long. + return false; + + dStrcpy(sRemote, addressString); + + sPort = dStrchr(sRemote, ':'); + if (sPort) // if non-null... + *sPort++ = 0; // null the : and set ahead past the : + + // first, look for broadcast. + if(!dStricmp(sRemote, "broadcast")) + { + *((InetHost*)address->netNum) = broadcastIP; + } + else + { + // try to do a simple string convert, in hopes it's a dotted-numeral address. + OTInetStringToHost(sRemote, ((InetHost*)(address->netNum))); + + // if host numbers are all zero, safe bet that we need to do DNS lookup... + if(*((InetHost*)address->netNum) == 0) + { +/* THIS WAS OLD CODE... NEED TO VERIFY OR REWRITE!!!!TBD + U8 dnsBuffer[1024]; + TLookupRequest dnsRequest; + TLookupReply dnsReply; + MapperRef dns; + + // create a mapper for DNS lookup + dns = OTOpenMapper(OTCreateConfiguration(kDNRName), 0, &macStatus); + + // set the mapper to be sync and blocking, so we don't need callbacks + // !!!!TBD change to async handling of this! + if (macStatus == kOTNoError) + oterr = OTSetSynchronous(dns); + if (macStatus == kOTNoError) + oterr = OTSetBlocking(dns); + + // then, if we haven't yet hit an error, do the DNS lookup. + if (macStatus == kOTNoError) + { + dMemset((void*)&dnsRequest, 0, sizeof(TLookupRequest)); + dnsRequest.maxcnt = 1; // just looking for a single IP + dnsRequest.timeout = 5000; // # ms before we timeout. + dnsRequest.name.buf = sRemote; + dnsRequest.name.len = dStrlen(sRemote); + + dMemset((char *)&dnsReply, 0, sizeof(TLookupReply)); + dnsReply.names.maxlen = 1024; + dnsReply.names.buf = dnsBuffer; + + macStatus = OTLookupName(dns, &dnsRequest, &dnsReply); + if (macStatus == kOTNoError) + { + if (!dnsReply.rspcount) + { + // !!!!TBD handle error of not found. hopefully, macStatus was set already. + } + else // copy over the result. + { +// address->netNumIP = ((InetAddress*)(((TLookupBuffer*)dnsBuffer)->fAddressBuffer)); + } + } + } + + // HERE we handle ALL drop through error cases. + // this way, it's handled in one place, and cleanup + // is simpler as well... + if (macStatus != kOTNoError) + { + //!!!!TBD -- handle the error by setting global lookup val. + } + + // close down dns if valid. + if (dns != kOTInvalidMapperRef) + { + macStatus = OTCloseProvider(dns); + if (macStatus != kOTNoError) + { + // !!!! NOT SURE. we don't want to necessarily blow away + // the failure above with this one. + //!!!!TBD -- handle the error by setting global lookup val. + } + } +*/ +// if((hp = gethostbyname(remoteAddr)) == NULL) + return false; + } + } + + if (sPort) + address->port = dAtoi(sPort); + else + address->port = defaultPort; + + address->type = NetAddress::IPAddress; + + return true; + } + + return false; +} + + +//====================================================================== +void Net::addressToString(const NetAddress *address, char addressString[256]) +{ + addressString[0] = 0; // clear string... + + if(address->type == NetAddress::IPAddress) + { + if (HOSTFROMBYTES(address->netNum) == broadcastIP) + dSprintf(addressString, 256, "IP:Broadcast:%d", address->port); + else + dSprintf(addressString, 256, "IP:%d.%d.%d.%d:%d", + address->netNum[0], address->netNum[1], address->netNum[2], address->netNum[3], + address->port); + } +} + + +//====================================================================== +Net::Error getLastError() +{ + return Net::UnknownError; +// S32 err = WSAGetLastError(); +// switch(err) +// { +// case WSAEWOULDBLOCK: +// return Net::WouldBlock; +// default: +// return Net::UnknownError; +// } +} diff --git a/platformMacCarb/macCarbOGLVideo.cc b/platformMacCarb/macCarbOGLVideo.cc new file mode 100644 index 0000000..6bfa7c7 --- /dev/null +++ b/platformMacCarb/macCarbOGLVideo.cc @@ -0,0 +1,1734 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformMacCarb/platformGL.h" +#include "platformMacCarb/platformMacCarb.h" +#include "platformMacCarb/maccarbOGLVideo.h" +#include "platform/platformAudio.h" +#include "console/console.h" +#include "math/mPoint.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "core/fileStream.h" + +// !!!!!!!TBD +// Note 1: I found some comments at one point that noted that under OSX, using DSp AT ALL +// was worse than JUST using the AGL_FULLSCREEN system. Right now, code uses a combo +// of the two under OSX, so we'll want to performance-test the two options... +// Note 2: Card Profiling code isn't doing anything. +// Note 3: Screen-res list building is hardcoded, and should tap DSp. +// Note 4: Gamma support. + +// flip this if we want smooth gamma-fades when switching screenres or in/out of DSp. +// generally, it's less nice but easier to find probs if we just leave it off, even for release. +#define FADE_ON_SWITCH 0 + +#define CALL_IN_SPOCKETS_BUT_NOT_IN_CARBON 1 +#if __APPLE__ +#include +#else +#include +#include +#include +#endif + +//----------------------------------------------------------------------------------------- +// prototypes and globals -- !!!!!!TBD - globals should mostly go away, into platState. +//----------------------------------------------------------------------------------------- + +static UInt32 CheckMacOSX (void); +void CToPStr (StringPtr outString, const char *inString); +void ReportError (char * strError); +OSStatus DSpDebugStr (OSStatus error); +GLenum aglDebugStr (void); + +CGrafPtr SetupDSp (GDHandle *phGD, short *numDevices, int width, int height, int bpp); +void ShutdownDSp (CGrafPtr pDSpPort); + +AGLContext SetupAGL (GDHandle hGD, AGLDrawable win); +AGLContext SetupAGLFullScreen (short display, int width, int height, int bpp); +void CleanupAGL (AGLContext ctx); + +const RGBColor rgbBlack = { 0x0000, 0x0000, 0x0000 }; + +NumVersion gVersionDSp; +DSpContextAttributes gContextAttributes; +DSpContextReference gContext = 0; + +AGLDrawable gpDSpPort = NULL; // will be NULL for full screen under X +Rect gRectPort = {0, 0, 0, 0}; + +GDHandle hGD; +short numDevices = 0; + + +//----------------------------------------------------------------------------------------- +// are we running on Mac OS X +// returns 0 if < Mac OS X or version number of Mac OS X (10.0 for GM) +//----------------------------------------------------------------------------------------- +static UInt32 CheckMacOSX (void) +{ + UInt32 response; + + if ((Gestalt(gestaltSystemVersion, (SInt32 *) &response) == noErr) && (response >= 0x01000)) + return response; + else + return 0; +} + + +//----------------------------------------------------------------------------------------- +// Copy C string to Pascal string +//----------------------------------------------------------------------------------------- +void CToPStr (StringPtr outString, const char *inString) +{ + unsigned char x = 0; + do + *(((char*)outString) + x) = *(inString + x++); + while ((*(inString + x) != 0) && (x < 256)); + *((char*)outString) = (char) x; +} + + +//----------------------------------------------------------------------------------------- +// display errors -- for the moment, dumps to the console. !!!!TBD +//----------------------------------------------------------------------------------------- +void ReportError (char * strError) +{ +#if 1 // for now, dump all errors to console. + Con::printf(strError); +#else + char errMsgCStr [256]; + Str255 strErr; + + sprintf (errMsgCStr, "%s", strError); + + // out as debug string + CToPStr (strErr, errMsgCStr); + DebugStr (strErr); +#endif +} + + +//----------------------------------------------------------------------------------------- +// translate DSp codes to strings. +//----------------------------------------------------------------------------------------- +OSStatus DSpDebugStr (OSStatus error) +{ + switch (error) + { + case noErr: + break; + case kDSpNotInitializedErr: + ReportError ("DSp Error: Not initialized"); + break; + case kDSpSystemSWTooOldErr: + ReportError ("DSp Error: system Software too old"); + break; + case kDSpInvalidContextErr: + ReportError ("DSp Error: Invalid context"); + break; + case kDSpInvalidAttributesErr: + ReportError ("DSp Error: Invalid attributes"); + break; + case kDSpContextAlreadyReservedErr: + ReportError ("DSp Error: Context already reserved"); + break; + case kDSpContextNotReservedErr: + ReportError ("DSp Error: Context not reserved"); + break; + case kDSpContextNotFoundErr: + ReportError ("DSp Error: Context not found"); + break; + case kDSpFrameRateNotReadyErr: + ReportError ("DSp Error: Frame rate not ready"); + break; + case kDSpConfirmSwitchWarning: +// ReportError ("DSp Warning: Must confirm switch"); // removed since it is just a warning, add back for debugging + return 0; // don't want to fail on this warning + break; + case kDSpInternalErr: + ReportError ("DSp Error: Internal error"); + break; + case kDSpStereoContextErr: + ReportError ("DSp Error: Stereo context"); + break; + } + return error; +} + + +//----------------------------------------------------------------------------------------- +// if error dump agl errors to debugger string, return error +//----------------------------------------------------------------------------------------- +GLenum aglDebugStr (void) +{ + GLenum err = aglGetError(); + if (AGL_NO_ERROR != err) + ReportError ((char *)aglErrorString(err)); + return err; +} + +#pragma mark - + +//----------------------------------------------------------------------------------------- +// Set up DSp screens, handles multi-monitor correctly +// side effect: sets both gpDSpWindow and gpPort +//----------------------------------------------------------------------------------------- +CGrafPtr SetupDSp (GDHandle *phGD, short *numDevices, int width, int height, int bpp) +{ + GDHandle hDevice; + DSpContextAttributes foundAttributes; + DisplayIDType displayID; + + *numDevices = 0; + *phGD = NULL; + + // check for DSp + if ((Ptr) kUnresolvedCFragSymbolAddress == (Ptr) DSpStartup) + ReportError ("DSp not installed"); + + if (noErr != DSpDebugStr (DSpStartup())) + return NULL; + + if ((Ptr) kUnresolvedCFragSymbolAddress == (Ptr) DSpGetVersion) + return NULL; + else + gVersionDSp = DSpGetVersion (); + + if ((gVersionDSp.majorRev == 0x01) && (gVersionDSp.minorAndBugRev < 0x99)) + { + // this version of DrawSprocket is not completely functional on Mac OS X + if (CheckMacOSX ()) + return NULL; + } + + hDevice = DMGetFirstScreenDevice (true); // check number of screens + do + { + (*numDevices)++; + hDevice = DMGetNextScreenDevice (hDevice, true); + } + while (hDevice); + + // Note: DSp < 1.7.3 REQUIRES the back buffer attributes even if only one buffer is required + dMemset(&gContextAttributes, 0, sizeof (DSpContextAttributes)); + gContextAttributes.displayWidth = width; + gContextAttributes.displayHeight = height; + gContextAttributes.colorNeeds = kDSpColorNeeds_Require; + gContextAttributes.displayBestDepth = bpp; + gContextAttributes.backBufferBestDepth = bpp; + gContextAttributes.displayDepthMask = kDSpDepthMask_All; + gContextAttributes.backBufferDepthMask = kDSpDepthMask_All; + gContextAttributes.pageCount = 1; // only the front buffer is needed + + if (noErr != DSpDebugStr (DSpFindBestContext(&gContextAttributes, &gContext))) + { + ReportError ("DSpFindBestContext() had an error."); + return NULL; + } + + if (noErr != DSpDebugStr (DSpContext_GetAttributes (gContext, &foundAttributes))) // see what we actually found + { + ReportError ("DSpContext_GetAttributes() had an error."); + return NULL; + } + + // reset width and height to full screen and handle our own centering + // HWA will not correctly center less than full screen size contexts + gContextAttributes.displayWidth = foundAttributes.displayWidth; + gContextAttributes.displayHeight = foundAttributes.displayHeight; + gContextAttributes.pageCount = 1; // only the front buffer is needed + gContextAttributes.contextOptions = 0 | kDSpContextOption_DontSyncVBL; // no page flipping and no VBL sync needed + + DSpSetBlankingColor(&rgbBlack); + if (noErr != DSpDebugStr (DSpContext_GetDisplayID(gContext, &displayID))) // get our device for future use + { + ReportError ("DSpContext_GetDisplayID() had an error."); + return NULL; + } + + if (noErr != DMGetGDeviceByDisplayID (displayID, phGD, false)) // get GDHandle for ID'd device + { + ReportError ("DMGetGDeviceByDisplayID() had an error."); + return NULL; + } + + if (noErr != DSpDebugStr (DSpContext_Reserve ( gContext, &gContextAttributes))) // reserve our context + { + ReportError ("DSpContext_Reserve() had an error."); + return NULL; + } + +// HideCursor (); + +#if FADE_ON_SWITCH + DSpDebugStr (DSpContext_FadeGammaOut (NULL, NULL)); // fade display, remove for debug +#endif + if (noErr != DSpDebugStr (DSpContext_SetState (gContext, kDSpContextState_Active))) // activate our context + { + ReportError ("DSpContext_SetState() had an error."); + return NULL; + } + + + if ((CheckMacOSX ()) && !((gVersionDSp.majorRev > 0x01) || ((gVersionDSp.majorRev == 0x01) && (gVersionDSp.minorAndBugRev >= 0x99))))// DSp should be supported in version after 1.98 + { + ReportError ("Mac OS X with DSp < 1.99 does not support DrawSprocket for OpenGL full screen"); + return NULL; + } + else if (CheckMacOSX ()) // DSp should be supported in versions 1.99 and later + { + CGrafPtr pPort; + // use DSp's front buffer on Mac OS X + if (noErr != DSpDebugStr (DSpContext_GetFrontBuffer (gContext, &pPort))) + { + ReportError ("DSpContext_GetFrontBuffer() had an error."); + return NULL; + } + // there is a problem in Mac OS X GM CoreGraphics that may not size the port pixmap correctly + // this will check the vertical sizes and offset if required to fix the problem + // this will not center ports that are smaller then a particular resolution + { + long deltaV, deltaH; + Rect portBounds; + PixMapHandle hPix = GetPortPixMap (pPort); + Rect pixBounds = (**hPix).bounds; + GetPortBounds (pPort, &portBounds); + deltaV = (portBounds.bottom - portBounds.top) - (pixBounds.bottom - pixBounds.top) + + (portBounds.bottom - portBounds.top - height) / 2; + deltaH = -(portBounds.right - portBounds.left - width) / 2; + if (deltaV || deltaH) + { + GrafPtr pPortSave; + GetPort (&pPortSave); + SetPort ((GrafPtr)pPort); + // set origin to account for CG offset and if requested drawable smaller than screen rez + SetOrigin (deltaH, deltaV); + SetPort (pPortSave); + } + } +#if FADE_ON_SWITCH + DSpDebugStr (DSpContext_FadeGammaIn (NULL, NULL)); +#endif + platState.appWindow = GetWindowFromPort(pPort); + return pPort; + } + else // Mac OS 9 or less + { + WindowPtr pWindow; + Rect rectWin; + RGBColor rgbSave; + GrafPtr pGrafSave; + // create a new window in our context + // note: OpenGL is expecting a window so it can enumerate the devices it spans, + // center window in our context's gdevice + rectWin.top = (short) ((***phGD).gdRect.top + ((***phGD).gdRect.bottom - (***phGD).gdRect.top) / 2); // h center + rectWin.top -= (short) (height / 2); + rectWin.left = (short) ((***phGD).gdRect.left + ((***phGD).gdRect.right - (***phGD).gdRect.left) / 2); // v center + rectWin.left -= (short) (width / 2); + rectWin.right = (short) (rectWin.left + width); + rectWin.bottom = (short) (rectWin.top + height); + + pWindow = NewCWindow (NULL, &rectWin, "\p", 0, plainDBox, (WindowPtr)-1, 0, 0); + + // paint back ground black before fade in to avoid white background flash + ShowWindow(pWindow); + GetPort (&pGrafSave); + SetPortWindowPort (pWindow); + GetForeColor (&rgbSave); + RGBForeColor (&rgbBlack); + { + Rect paintRect; + GetWindowPortBounds (pWindow, &paintRect); + PaintRect (&paintRect); + } + RGBForeColor (&rgbSave); // ensure color is reset for proper blitting + SetPort (pGrafSave); +#if FADE_ON_SWITCH + DSpDebugStr (DSpContext_FadeGammaIn (NULL, NULL)); +#endif + platState.appWindow = pWindow; + return (GetWindowPort (pWindow)); + } +} + + +//----------------------------------------------------------------------------------------- +// clean up DSp +//----------------------------------------------------------------------------------------- +void ShutdownDSp (CGrafPtr pDSpPort) +{ +#if FADE_ON_SWITCH + DSpContext_FadeGammaOut(NULL, NULL); +#endif + if ((NULL != pDSpPort)) + { + WindowPtr w = GetWindowFromPort(pDSpPort); + if (w == platState.appWindow) // then clear. + platState.appWindow = NULL; + if (!CheckMacOSX()) // then we actually created a window. + DisposeWindow(w); + } + DSpContext_SetState(gContext, kDSpContextState_Inactive); +#if FADE_ON_SWITCH + DSpContext_FadeGammaIn(NULL, NULL); +#endif + ShowCursor(); + DSpContext_Release(gContext); + DSpShutdown(); + + gContext = NULL; + gpDSpPort = NULL; +} + +#pragma mark - + +//----------------------------------------------------------------------------------------- +// OpenGL Setup, for any case where we already have a drawable (whether windowed or DSp-screen) +//----------------------------------------------------------------------------------------- +AGLContext SetupAGL (GDHandle hGD, AGLDrawable drawable) +{ +// software renderer = AGL_RENDERER_ID + AGL_RENDERER_GENERIC_ID + AGL_ALL_RENDERERS +// any renderer = AGL_ALL_RENDERERS instead of AGL_ACCELERATED +// OpenGL compliant ATI renderer = AGL_RENDERER_ID + AGL_RENDERER_ATI_ID + AGL_ACCELERATED + + short i = 0; + GLint attrib[64]; + AGLPixelFormat fmt; + AGLContext ctx; + + if ((Ptr) kUnresolvedCFragSymbolAddress == (Ptr) aglChoosePixelFormat) // check for existance of OpenGL + { + ReportError ("OpenGL not installed"); + return NULL; + } + + attrib [i++] = AGL_RGBA; // red green blue and alpha + attrib [i++] = AGL_DOUBLEBUFFER; // double buffered + attrib [i++] = AGL_ACCELERATED; // HWA pixel format only + attrib [i++] = AGL_NO_RECOVERY; // HWA pixel format only + attrib [i++] = AGL_DEPTH_SIZE; // MUST request a depth buffer. + attrib [i++] = 16; // !!!!!!!TBD this should be from a pref variable. + +// attrib [i++] = AGL_ALL_RENDERERS; // choose even non-compliant renderers +// attrib [i++] = AGL_RENDERER_ID; // choose only renderer type specified in next parameter +// attrib [i++] = AGL_RENDERER_ATI_ID; // ATI renderer +// attrib [i++] = AGL_RENDERER_GENERIC_ID; // generic renderer + +// !!!!!TBD -- do we need to have a pixelsize in here? +// attrib [i++] = AGL_FULLSCREEN; +// attrib [i++] = AGL_PIXEL_SIZE; +// attrib [i++] = bpp; + + // terminate the list. + attrib [i++] = AGL_NONE; + + if (hGD) + fmt = aglChoosePixelFormat (&hGD, 1, attrib); // get an appropriate pixel format + else + fmt = aglChoosePixelFormat(NULL, 0, attrib); // get an appropriate pixel format + aglDebugStr (); + if (NULL == fmt) + { + ReportError("Could not find valid pixel format"); + return NULL; + } + + ctx = aglCreateContext (fmt, NULL); // Create an AGL context + aglDebugStr (); + if (NULL == ctx) + { + ReportError ("Could not create context"); + return NULL; + } + + if (!aglSetDrawable (ctx, drawable)) // attach the window to the context + { + ReportError ("SetDrawable failed"); + aglDebugStr (); + return NULL; + } + + + if (!aglSetCurrentContext (ctx)) // make the context the current context + { + aglDebugStr (); + aglSetDrawable (ctx, NULL); + return NULL; + } + + aglDestroyPixelFormat(fmt); // pixel format is no longer needed + + return ctx; +} + +//----------------------------------------------------------------------------------------------------------------------- + +// OpenGL Setup + +AGLContext SetupAGLFullScreen (short display, int width, int height, int bpp) +{ + GLint attrib[64]; + AGLPixelFormat fmt; + AGLContext ctx; + +// different possible pixel format choices for different renderers +// basics requirements are RGBA and double buffer +// OpenGL will select acclerated context if available + + short i = 0; + attrib [i++] = AGL_RGBA; // red green blue and alpha + attrib [i++] = AGL_DOUBLEBUFFER; // double buffered + attrib [i++] = AGL_ACCELERATED; // HWA pixel format only + attrib [i++] = AGL_NO_RECOVERY; // HWA pixel format only + attrib [i++] = AGL_DEPTH_SIZE; + attrib [i++] = 16; + +// attrib [i++] = AGL_ALL_RENDERERS; // choose even non-compliant renderers +// attrib [i++] = AGL_RENDERER_ID; // choose only renderer type specified in next parameter +// attrib [i++] = AGL_RENDERER_ATI_ID; // ATI renderer +// attrib [i++] = AGL_RENDERER_GENERIC_ID; // generic renderer + + attrib [i++] = AGL_FULLSCREEN; + attrib [i++] = AGL_PIXEL_SIZE; + attrib [i++] = bpp; + + attrib [i++] = AGL_NONE; + + if ((Ptr) kUnresolvedCFragSymbolAddress == (Ptr) aglChoosePixelFormat) // check for existance of OpenGL + { + ReportError ("OpenGL not installed"); + return NULL; + } + + fmt = aglChoosePixelFormat(NULL, 0, attrib); // this may fail if looking for acclerated across multiple monitors + if (NULL == fmt) + { + ReportError("Could not find valid pixel format"); + aglDebugStr (); + return NULL; + } + + ctx = aglCreateContext (fmt, NULL); // Create an AGL context + if (NULL == ctx) + { + ReportError ("Could not create context"); + aglDebugStr (); + return NULL; + } + + static int hzrate = 60; + if (!aglSetFullScreen (ctx, width, height, hzrate, display)) // attach fulls screen device to the context + { + ReportError ("SetFullScreen failed"); + aglDebugStr (); + return NULL; + } + + + if (!aglSetCurrentContext (ctx)) // make the context the current context + { + ReportError ("SetCurrentContext failed"); + aglDebugStr (); + aglSetDrawable (ctx, NULL); // turn off full screen + return NULL; + } + + aglDestroyPixelFormat(fmt); // pixel format is no longer needed + + return ctx; +} + +//----------------------------------------------------------------------------------------------------------------------- + +// OpenGL Cleanup + +void CleanupAGL(AGLContext ctx) +{ + aglSetCurrentContext(NULL); + aglSetDrawable(ctx, NULL); + aglDestroyContext(ctx); +} + +#pragma mark - + +//=============================================================================== + +Boolean sCanDoFullscreen = TRUE; + +struct CardProfile +{ + const char *vendor; // manufacturer + const char *renderer; // driver name + + bool safeMode; // destroy rendering context for resolution change + bool lockArray; // allow compiled vertex arrays + bool subImage; // allow glTexSubImage* + bool fogTexture; // require bound texture for combine extension + bool noEnvColor; // no texture environment color + bool clipHigh; // clip high resolutions + bool deleteContext; // delete rendering context + bool texCompress; // allow texture compression + bool interiorLock; // lock arrays for Interior render + bool skipFirstFog; // skip first two-pass fogging (dumb 3Dfx hack) + bool only16; // inhibit 32-bit resolutions + bool noArraysAlpha; // don't use glDrawArrays with a GL_ALPHA texture + + const char *proFile; // explicit profile of graphic settings +}; + +struct OSCardProfile +{ + const char *vendor; // manufacturer + const char *renderer; // driver name +}; + +static Vector sCardProfiles(__FILE__, __LINE__); +static Vector sOSCardProfiles(__FILE__, __LINE__); + +struct ProcessorProfile +{ + U16 clock; // clock range max + U16 adjust; // CPU adjust +}; + +static U8 sNumProcessors = 4; +static ProcessorProfile sProcessorProfiles[] = +{ + { 400, 0 }, + { 600, 5 }, + { 800, 10 }, + { 1000, 15 }, +}; + +struct SettingProfile +{ + U16 performance; // metric range max + const char *settings; // default file +}; + +static U8 sNumSettings = 3; +static SettingProfile sSettingProfiles[] = +{ + { 33, "LowProfile.cs" }, + { 66, "MediumProfile.cs" }, + { 100, "HighProfile.cs" }, +}; + +//------------------------------------------------------------------------------ + +static void cAddCardProfile(SimObject *, S32, const char **argv) +{ + CardProfile profile; + + profile.vendor = dStrdup(argv[1]); + profile.renderer = dStrdup(argv[2]); + + profile.safeMode = dAtob(argv[3]); + profile.lockArray = dAtob(argv[4]); + profile.subImage = dAtob(argv[5]); + profile.fogTexture = dAtob(argv[6]); + profile.noEnvColor = dAtob(argv[7]); + profile.clipHigh = dAtob(argv[8]); + profile.deleteContext = dAtob(argv[9]); + profile.texCompress = dAtob(argv[10]); + profile.interiorLock = dAtob(argv[11]); + profile.skipFirstFog = dAtob(argv[12]); + profile.only16 = dAtob(argv[13]); + profile.noArraysAlpha = dAtob(argv[14]); + + if (dStrcmp(argv[15],"")) + profile.proFile = dStrdup(argv[15]); + else + profile.proFile = NULL; + + sCardProfiles.push_back(profile); +} + +static void cAddOSCardProfile(SimObject *, S32, const char **argv) +{ + OSCardProfile profile; + + profile.vendor = dStrdup(argv[1]); + profile.renderer = dStrdup(argv[2]); + +/* + profile.allowOpenGL = dAtob(argv[3]); + profile.allowD3D = dAtob(argv[4]); + profile.preferOpenGL = dAtob(argv[5]); +*/ + + sOSCardProfiles.push_back(profile); +} + +static void clearCardProfiles() +{ + while (sCardProfiles.size()) + { + dFree((char *) sCardProfiles.last().vendor); + dFree((char *) sCardProfiles.last().renderer); + + dFree((char *) sCardProfiles.last().proFile); + + sCardProfiles.decrement(); + } +} + +static void clearOSCardProfiles() +{ + while (sOSCardProfiles.size()) + { + dFree((char *) sOSCardProfiles.last().vendor); + dFree((char *) sOSCardProfiles.last().renderer); + + sOSCardProfiles.decrement(); + } +} + +static void execScript(const char *scriptFile) +{ + // execute the script + FileStream str; + + if (!str.open(scriptFile, FileStream::Read)) + return; + + U32 size = str.getStreamSize(); + char *script = new char[size + 1]; + + str.read(size, script); + str.close(); + + script[size] = 0; + Con::executef(2, "eval", script); + delete[] script; +} + +static void profileSystem(const char *vendor, const char *renderer) +{ + Con::addCommand("addCardProfile", cAddCardProfile, "addCardProfile(vendor,renderer,safeMode,lockArray,subImage,fogTexture,noEnvColor,clipHigh,deleteContext,texCompress,interiorLock,skipFirstFog,only16,noArraysAlpha,proFile);", 16, 16); + Con::addCommand("addOSCardProfile", cAddOSCardProfile, "addOSCardProfile(vendor,renderer,allowOpenGL,allowD3D,preferOpenGL);", 6, 6); + + //Con::executef(2, "exec", "scripts/CardProfiles.cs"); + execScript("CardProfiles.cs"); + + const char *os = NULL; + char osProfiles[64]; + +#pragma message("todo: implement full profile checking") + os = "MACCARB"; + + if ( os != NULL ) + { + dSprintf(osProfiles,64,"%s%sCardProfiles.cs","PPC",os); + //Con::executef(2, "exec", osProfiles); + execScript(osProfiles); + } + + const char *proFile = NULL; + U32 i; + + for (i = 0; i < sCardProfiles.size(); ++i) + if (dStrstr(vendor, sCardProfiles[i].vendor) && + (!dStrcmp(sCardProfiles[i].renderer, "*") || + dStrstr(renderer, sCardProfiles[i].renderer))) + { + Con::setBoolVariable("$pref::Video::safeModeOn", sCardProfiles[i].safeMode); + Con::setBoolVariable("$pref::OpenGL::disableEXTCompiledVertexArray", !sCardProfiles[i].lockArray); + Con::setBoolVariable("$pref::OpenGL::disableSubImage", !sCardProfiles[i].subImage); + Con::setBoolVariable("$pref::TS::fogTexture", sCardProfiles[i].fogTexture); + Con::setBoolVariable("$pref::OpenGL::noEnvColor", sCardProfiles[i].noEnvColor); + Con::setBoolVariable("$pref::Video::clipHigh", sCardProfiles[i].clipHigh); + if (!sCardProfiles[i].deleteContext) + { + Con::setBoolVariable("$pref::Video::deleteContext", false); +/* + // HACK: The Voodoo3/5 on W2K crash when deleting a rendering context + // So we're not deleting it. + // Oh, and the Voodoo3 returns a Banshee renderer string under W2K + dMemset( &OSVersionInfo, 0, sizeof( OSVERSIONINFO ) ); + OSVersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + if ( GetVersionEx( &OSVersionInfo ) && + OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && + OSVersionInfo.dwMajorVersion == 5) + Con::setBoolVariable("$pref::Video::deleteContext", false); + else + Con::setBoolVariable("$pref::Video::deleteContext", true); +*/ + } + else + Con::setBoolVariable("$pref::Video::deleteContext", true); + Con::setBoolVariable("$pref::OpenGL::disableARBTextureCompression", !sCardProfiles[i].texCompress); + Con::setBoolVariable("$pref::Interior::lockArrays", sCardProfiles[i].interiorLock); + Con::setBoolVariable("$pref::TS::skipFirstFog", sCardProfiles[i].skipFirstFog); + Con::setBoolVariable("$pref::Video::only16", sCardProfiles[i].only16); + Con::setBoolVariable("$pref::OpenGL::noDrawArraysAlpha", sCardProfiles[i].noArraysAlpha); + + proFile = sCardProfiles[i].proFile; + + break; + } + + // defaults + U16 glProfile; + + if (!proFile) + { + // no driver GL profile -- make one via weighting GL extensions + glProfile = 25; + + glProfile += gGLState.suppARBMultitexture * 25; + glProfile += gGLState.suppLockedArrays * 15; + glProfile += gGLState.suppVertexArrayRange * 10; + glProfile += gGLState.suppTextureEnvCombine * 5; + glProfile += gGLState.suppPackedPixels * 5; + glProfile += gGLState.suppTextureCompression * 5; + glProfile += gGLState.suppS3TC * 5; + glProfile += gGLState.suppFXT1 * 5; + + Con::setBoolVariable("$pref::Video::safeModeOn", true); + Con::setBoolVariable("$pref::OpenGL::disableEXTCompiledVertexArray", false); + Con::setBoolVariable("$pref::OpenGL::disableSubImage", false); + Con::setBoolVariable("$pref::TS::fogTexture", false); + Con::setBoolVariable("$pref::OpenGL::noEnvColor", false); + Con::setBoolVariable("$pref::Video::clipHigh", false); + Con::setBoolVariable("$pref::Video::deleteContext", true); + Con::setBoolVariable("$pref::OpenGL::disableARBTextureCompression", false); + Con::setBoolVariable("$pref::Interior::lockArrays", true); + Con::setBoolVariable("$pref::TS::skipFirstFog", false); + Con::setBoolVariable("$pref::Video::only16", false); + Con::setBoolVariable("$pref::OpenGL::noDrawArraysAlpha", false); + } + + Con::setVariable("$pref::Video::profiledVendor", vendor); + Con::setVariable("$pref::Video::profiledRenderer", renderer); + + if (!Con::getBoolVariable("$DisableSystemProfiling") && + ( dStrcmp(vendor, Con::getVariable("$pref::Video::defaultsVendor")) || + dStrcmp(renderer, Con::getVariable("$pref::Video::defaultsRenderer")) )) + { + if (proFile) + { + char settings[64]; + + dSprintf(settings,64,"%s.cs",proFile); + //Con::executef(2, "exec", settings); + execScript(settings); + } + else + { + U16 adjust; + + // match clock with profile + for (i = 0; i < sNumProcessors; ++i) + { + adjust = sProcessorProfiles[i].adjust; + + if (Platform::SystemInfo.processor.mhz < sProcessorProfiles[i].clock) break; + } + + const char *settings; + + // match performance metric with profile + for (i = 0; i < sNumSettings; ++i) + { + settings = sSettingProfiles[i].settings; + + if (glProfile+adjust <= sSettingProfiles[i].performance) break; + } + + //Con::executef(2, "exec", settings); + execScript(settings); + } + + bool match = false; + + for (i = 0; i < sOSCardProfiles.size(); ++i) + if (dStrstr(vendor, sOSCardProfiles[i].vendor) && + (!dStrcmp(sOSCardProfiles[i].renderer, "*") || + dStrstr(renderer, sOSCardProfiles[i].renderer))) + { + +// Con::setBoolVariable("$pref::Video::allowOpenGL", sOSCardProfiles[i].allowOpenGL); +// Con::setBoolVariable("$pref::Video::allowD3D", sOSCardProfiles[i].allowD3D); +// Con::setBoolVariable("$pref::Video::preferOpenGL", sOSCardProfiles[i].preferOpenGL); + + match = true; + + break; + } + + if (!match) + { + Con::setBoolVariable("$pref::Video::allowOpenGL", true); +// Con::setBoolVariable("$pref::Video::allowD3D", true); + Con::setBoolVariable("$pref::Video::preferOpenGL", true); + } + + Con::setVariable("$pref::Video::defaultsVendor", vendor); + Con::setVariable("$pref::Video::defaultsRenderer", renderer); + } + + // write out prefs + gEvalState.globalVars.exportVariables("$pref::*", "prefs/ClientPrefs.cs", false); + + clearCardProfiles(); + clearOSCardProfiles(); +} + + +//------------------------------------------------------------------------------ +OpenGLDevice::OpenGLDevice() +{ + initDevice(); +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::initDevice() +{ + #pragma message("todo: enumerate available display modes"); + // Set the device name: + mDeviceName = "OpenGL"; + + // !!!!!TBD + // PRE-TEST THAT GL IS AVAILABLE, REALLY. + + + // Never unload a code module + aglConfigure(AGL_RETAIN_RENDERERS, GL_TRUE); + + // Set some initial conditions: + mResolutionList.clear(); + + Resolution newRes1( 1024, 768, 16 ); + Resolution newRes2( 800, 600, 16 ); + Resolution newRes3( 640, 480, 16 ); + mResolutionList.push_back( newRes1 ); + mResolutionList.push_back( newRes2 ); + mResolutionList.push_back( newRes3 ); + +// sCanDoFullscreen = FALSE; +// then we check DSp available. could also check AGL_FULLSCREEN supported. +// .... TBD.... + + +/* + // 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 >= 640 && devMode.dmPelsHeight >= 480 + && ( devMode.dmBitsPerPel == 16 || devMode.dmBitsPerPel == 32 ) ) + { + // Only add this resolution if it is not already in the list: + bool alreadyInList = false; + for ( U32 i = 0; i < mResolutionList.size(); i++ ) + { + if ( devMode.dmPelsWidth == mResolutionList[i].w + && devMode.dmPelsHeight == mResolutionList[i].h + && devMode.dmBitsPerPel == mResolutionList[i].bpp ) + { + alreadyInList = true; + break; + } + } + + if ( !alreadyInList ) + { + Resolution newRes( devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel ); + mResolutionList.push_back( newRes ); + } + } + } +*/ +} + + +//------------------------------------------------------------------------------ +bool OpenGLDevice::activate( U32 width, U32 height, U32 bpp, bool fullScreen ) +{ + Con::printf( "Activating the OpenGL display device..." ); + + bool needResurrect = false; + +#pragma message("todo: need to error check on return from agl funcs.") + + // If the rendering context exists, delete it: +/* + if (platState.ctx) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + + Con::printf( "Making the rendering context not current..." ); + aglSetCurrentContext(NULL); + aglSetDrawable(platState.ctx, NULL); + + Con::printf( "Deleting the rendering context..." ); + aglDestroyContext(platState.ctx); + platState.ctx = NULL; + } +*/ + +#pragma message("todo: figure out when we will need to get a new window") + + static bool onceAlready = false; + bool profiled = false; + + // Set the resolution: + if ( !setScreenMode( width, height, bpp, ( fullScreen || mFullScreenOnly ), true, false ) ) + return false; + + // Get original gamma ramp +// mRestoreGamma = GetDeviceGammaRamp(platState.appDC, mOriginalRamp); + + // 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 ); + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + + QGL_EXT_Init(); + + Con::setVariable( "$pref::Video::displayDevice", mDeviceName ); + +/* !!!!!!TBD LATER!!!!!!!! + // only do this for the first session + if (!profiled && + !Con::getBoolVariable("$DisableSystemProfiling") && + ( dStrcmp(vendorString, Con::getVariable("$pref::Video::profiledVendor")) || + dStrcmp(rendererString, Con::getVariable("$pref::Video::profiledRenderer")) )) + { + profileSystem(vendorString, rendererString); + profiled = true; + } + + if (profiled) + { + U32 width, height, bpp; + + if (Con::getBoolVariable("$pref::Video::clipHigh", false)) + for (S32 i = mResolutionList.size()-1; i >= 0; --i) + if (mResolutionList[i].w > 1152 || mResolutionList[i].h > 864) + mResolutionList.erase(i); + + if (Con::getBoolVariable("$pref::Video::only16", false)) + for (S32 i = mResolutionList.size()-1; i >= 0; --i) + if (mResolutionList[i].bpp == 32) + mResolutionList.erase(i); + + dSscanf(Con::getVariable("$pref::Video::resolution"), "%d %d %d", &width, &height, &bpp); + setScreenMode(width, height, bpp, + Con::getBoolVariable("$pref::Video::fullScreen", true), false, false); + } +*/ + + // 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() +{ + OSStatus err = noErr; + + Con::printf( "Shutting down the OpenGL display device..." ); + +// temporarily don't have shutdown do anything. setScreenMode internally does all this cleanup, so this is only +// useful for real shutdown. +// !!!!TBD!!!!!! +return; +// !!!!TBD!!!!!! + + // Delete the rendering context: + if (platState.ctx) + { +/* + if (!Video::smNeedResurrect) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + } +*/ + +// !!!!!TBD HANDLE GAMMA +// if (mRestoreGamma) +// SetDeviceGammaRamp(platState.appDC, mOriginalRamp); + + Con::printf( "Making darn sure the rendering context is not current..." ); + aglSetCurrentContext(NULL); + aglSetDrawable(platState.ctx, NULL); + + if (0) // !!!!TBD error check + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglMakeCurrent( NULL, NULL ) failed!" ); + return; + } + +// !!!!!TBD +#pragma message("todo: ask Rick why we check pref here but not in activate()!!!!") + if ( Con::getBoolVariable("$pref::Video::deleteContext", true) ) + { + Con::printf( "Deleting the rendering context..." ); + aglDestroyContext(platState.ctx); + } + + if (0) // !!!!TBD error check + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglDeleteContext failed!" ); + return; + } + + platState.ctx = NULL; + } + +/* not sure shutdown should do this... !!!!!!!TBD + // Destroy the window: + if ( platState.appWindow ) + { + Con::printf( "Destroying the window..." ); + DisposeWindow( platState.appWindow ); + platState.appWindow = NULL; + } +*/ + + if ( smIsFullScreen ) + { + Con::printf( "Restoring the desktop display settings (%dx%dx%d)...", platState.desktopWidth, platState.desktopHeight, platState.desktopBitsPixel ); + // shut down DSp. + if (gpDSpPort) + ShutdownDSp(gpDSpPort); + } +} + + +//------------------------------------------------------------------------------ +// This is the real workhorse function of the DisplayDevice... +// +bool OpenGLDevice::setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen, bool forceIt, bool repaint ) +{ + WindowPtr curtain = NULL; + char errorMessage[256]; + Resolution newRes( width, height, bpp ); + bool newFullScreen = fullScreen; + bool safeModeOn = Con::getBoolVariable( "$pref::Video::safeModeOn" ); + OSStatus err; + +//temp!!!!!TBD +// if not really changing, return immediately. +// this is sort-of an optimization, but also useful to prevent thrashing (which the +// current graphic startup would do otherwise...) +if (smCurrentRes.w == width && smCurrentRes.h == height && smCurrentRes.bpp == bpp && smIsFullScreen == newFullScreen) + return(true); + + if ( !newFullScreen && mFullScreenOnly ) + { + Con::warnf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode - device or desktop color depth does not allow windowed mode!" ); + newFullScreen = true; + } + +/* + if ( !newFullScreen && ( newRes.w >= platState.desktopWidth || newRes.h >= platState.desktopHeight ) ) + { + Con::warnf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode -- can't switch to resolution larger than desktop in windowed mode!" ); + // Find the largest standard resolution that will fit on the desktop: + U32 resIndex; + for ( resIndex = mResolutionList.size() - 1; resIndex > 0; resIndex-- ) + { + if ( mResolutionList[resIndex].w < platState.desktopWidth + && mResolutionList[resIndex].h < platState.desktopHeight ) + break; + } + newRes = mResolutionList[resIndex]; + } + + // DHC -- !!!!!TBD -- Why the heck are we disallowing <640x480!?!?! Consoles/other media devices could easily want! + // THIS SAME ISSUE APPLIES TO PC CODE -- REINFORCES THAT THERE NEEDS TO BE MORE CODEPATH SHARING AMONGST PLATFORMS!!!! + if ( newRes.w < 640 || newRes.h < 480 ) + { + Con::warnf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode -- can't go smaller than 640x480!" ); + return false; + } +*/ + + +#pragma message("todo: how to handle picking res?") +#if 0 //!!!!!TBD how do we want to deal with this on the Mac??? + if ( newFullScreen ) + { + if (newRes.bpp != 16 && mFullScreenOnly) + newRes.bpp = 16; + + // Match the new resolution to one in the list: + U32 resIndex = 0; + U32 bestScore = 0, thisScore = 0; + for ( int i = 0; i < mResolutionList.size(); i++ ) + { + if ( newRes == mResolutionList[i] ) + { + resIndex = i; + break; + } + else + { + #define myAbs(x) (((x)<0)?-(x):(x)) + S32 tw = S32( newRes.w ) - S32( mResolutionList[i].w ); + S32 th = S32( newRes.h ) - S32( mResolutionList[i].h ); + thisScore = myAbs(tw) + myAbs(th) + + ( newRes.bpp == mResolutionList[i].bpp ? 0 : 1 ); + + if ( !bestScore || ( thisScore < bestScore ) ) + { + bestScore = thisScore; + resIndex = i; + } + } + } + + newRes = mResolutionList[resIndex]; + } + else + { + // Basically ignore the bit depth parameter: + newRes.bpp = platState.desktopBitsPixel; + } + + // Return if already at this resolution: + if ( !forceIt && newRes == smCurrentRes && newFullScreen == smIsFullScreen ) + return true; +#endif + + Con::printf( "Setting screen mode to %dx%dx%d (%s)...", newRes.w, newRes.h, newRes.bpp, ( newFullScreen ? "fs" : "w" ) ); + + bool needResurrect = false; + + if (gpDSpPort) + { + Con::printf("Shutting down DrawSprocket..."); + ShutdownDSp(gpDSpPort); + } + +// !!!!!!!TBD NOT SURE THAT THIS WILL DO THE RIGHT THING FOR MAC IN ALL CASES! + if ( ( newRes.bpp != smCurrentRes.bpp ) || ( safeModeOn && ( ( smIsFullScreen != newFullScreen ) || newFullScreen ) ) ) + { + // !!!!!TBD + // NOTE THAT A LARGE CHUNK OF THIS CODE IS SHARED WITH shutdown(), AND THUS SHOULD BE >REALLY< SHARED CODE. + // Delete the rendering context: + if (platState.ctx) + { + if (!Video::smNeedResurrect) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + } + + Con::printf( "Making darn sure the rendering context is not current..." ); + aglSetCurrentContext(NULL); + aglSetDrawable(platState.ctx, NULL); + if (0) // !!!!TBD error check + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglMakeCurrent( NULL, NULL ) failed!" ); + return false; + } + + Con::printf( "Deleting the rendering context..." ); + // !!!! TBD WHY THE CHECK HERE & NOT IN ACTIVATE? + if ( Con::getBoolVariable("$pref::Video::deleteContext",true) ) + aglDestroyContext(platState.ctx); + if (0) // !!!!TBD error check + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglDeleteContext failed!" ); + return false; + } + + // make sure it is null... + platState.ctx = NULL; + } + } + + // on the mac, if the window is still around for any reason, dispose of it now. + // DestroyGL or ShutdownDSp should have nuked it already if they created it... + if ( platState.appWindow ) + { + Con::printf( "Destroying the window..." ); + DisposeWindow( platState.appWindow ); + platState.appWindow = NULL; + } + + Con::printf( "Changing the display settings to %s %dx%dx%d...", newFullScreen?"fullscreen":"windowed", newRes.w, newRes.h, newRes.bpp ); + + // MAC DOESN'T NEED A CURTAIN -- prefers to use gamma fade out/in... +// curtain = CreateCurtain( newRes.w, newRes.h ); + + if ( newFullScreen ) + { + smIsFullScreen = true; + } + else if ( smIsFullScreen ) + { + Con::printf( "Changing to the desktop display settings (%dx%dx%d)...", platState.desktopWidth, platState.desktopHeight, platState.desktopBitsPixel ); + // this is automatic with the DestroyGL call or the ShutdownDSp call or the switching off fullscreen context. + smIsFullScreen = false; + } + Con::setBoolVariable( "$pref::Video::fullScreen", smIsFullScreen ); + + + if (newFullScreen) + { + if (NULL == (gpDSpPort = SetupDSp (&hGD, &numDevices, newRes.w, newRes.h, newRes.bpp))) // Setup DSp for OpenGL + { + if (CheckMacOSX ()) // if setupDSp fails we can try SetaglFullScreen on Mac OS X + { + short display = 0; // need to define a GDHandle to display # mappinga + if (NULL == (platState.ctx = SetupAGLFullScreen (display, newRes.w, newRes.h, newRes.bpp))) // Setup the OpenGL context + { + Con::printf("Failed to set up GL support"); + return false; + } + SetRect (&gRectPort, 0, 0, newRes.w, newRes.h); // l, t, r, b + Con::printf("Successfully set up AGLFullscreen GL support [%d x %d x %d]", newRes.w, newRes.h, newRes.bpp); + } + } + else + { + if (NULL == (platState.ctx = SetupAGL(hGD, gpDSpPort))) // Setup the OpenGL context + return false; + GetPortBounds (gpDSpPort, &gRectPort); + if (hGD) + Con::printf("Successfully set up DSpFullscreen GL support [%d x %d x %d]", gRectPort.right - gRectPort.left, gRectPort.bottom - gRectPort.top, (**(**hGD).gdPMap).pixelSize); + else + Con::printf("Successfully set up GL support [%d x %d]", gRectPort.right - gRectPort.left, gRectPort.bottom - gRectPort.top); + } + } + else + { + //if we haven't yet created/assigned a plat window, need to do so now. + if ( !newFullScreen && !platState.appWindow ) + { + Con::printf( "Creating a new %swindow...", ( fullScreen ? "full-screen " : "" ) ); + platState.appWindow = CreateOpenGLWindow( newRes.w, newRes.h, newFullScreen ); + if ( !platState.appWindow ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nFailed to create a new window!" ); + return false; + } + } + + GLint attrib[32]; + int c = 0; + attrib[c++] = AGL_RGBA; + attrib[c++] = AGL_DOUBLEBUFFER; + attrib[c++] = AGL_NO_RECOVERY; + attrib[c++] = AGL_ACCELERATED; + attrib[c++] = AGL_DEPTH_SIZE; + attrib[c++] = 16; //!!!!TBD -- this should come from a pref! +// !!!!TBD +// attrib[c++] = AGL_PIXEL_SIZE; +// attrib[c++] = newRes.bpp; + attrib[c++] = AGL_NONE; + + platState.fmt = aglChoosePixelFormat(NULL, 0, (const GLint *)attrib); + if(platState.fmt == NULL) + { + GLenum glerr = aglGetError(); + Platform::AlertOK("OpenGLDevice::setScreenMode Failure", (char *)aglErrorString(glerr)); + AssertFatal( false, "OpenGLDevice::setScreenMode\nNo valid pixel formats found!" ); + return false; + } + +//PRINT BACK THE FORMAT WE GOT!!!! !!!!TBD +//qwglDescribePixelFormat( platState.appDC, chosenFormat, sizeof( pfd ), &pfd ); +//Con::printf( "Pixel format set:" ); +//Con::printf( " %d color bits, %d depth bits, %d stencil bits", platState.fmt.cColorBits, pfd.cDepthBits, pfd.cStencilBits ); + + if ( !platState.ctx ) + { + // Create a new rendering context: + Con::printf( "Creating a new rendering context..." ); + platState.ctx = aglCreateContext(platState.fmt, NULL); + if(platState.ctx == NULL) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\naglCreateContext failed to create an OpenGL rendering context!" ); + return false; + } + + // Attach the context to the window + Con::printf( "Attaching the rendering context to the window..." ); + if( !aglSetDrawable(platState.ctx, GetWindowPort(platState.appWindow)) ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\naglSetDrawable failed to attach rendering context to window!" ); + return false; + } + } + } + + // Make the new rendering context current: + Con::printf( "Making the new rendering context current..." ); + aglSetCurrentContext(platState.ctx); + if (0) // need to add error check!!!!!TBD + { + AssertFatal( false, "OpenGLDevice::setScreenMode\naglSetCurrentContext failed to make the rendering context current!" ); + return false; + } + + // Just for kicks. Seems a relatively central place to put this... + 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" ) ); + + smCurrentRes = newRes; + Platform::setWindowSize( newRes.w, newRes.h ); + char tempBuf[15]; + dSprintf( tempBuf, sizeof( tempBuf ), "%d %d %d", smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp ); + Con::setVariable( "$pref::Video::resolution", tempBuf ); + + if ( curtain ) + DisposeWindow( curtain ); + + if ( repaint ) + Con::evaluate( "resetCanvas();" ); + + return true; +} + +//------------------------------------------------------------------------------ +void OpenGLDevice::swapBuffers() +{ + // TBD!!!!! should this be setting to platState.ctx + // how to in future multiple contexts like RIVET?? + + //dhc - also adding in a sanity check here. + AssertISV(platState.ctx, "OpenGLDevice::swapBuffers -- No GL context in place!"); + + aglSwapBuffers(aglGetCurrentContext()); +} + + +//------------------------------------------------------------------------------ +const char* OpenGLDevice::getDriverInfo() +{ + // 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 ); + 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 ramp[256*3]; + +#pragma message("todo: gamma") +/* + if (!GetDeviceGammaRamp(platState.appDC, ramp)) + return false; +*/ + + F32 csum = 0.0; + U32 ccount = 0; + + for (U16 i = 0; i < 256; ++i) + { + if (i != 0 && ramp[i] != 0 && ramp[i] != 65535) + { + F64 b = (F64) i/256.0; + F64 a = (F64) ramp[i]/65535.0; + F32 c = (F32) (mLog(a)/mLog(b)); + + csum += c; + ++ccount; + } + } + g = csum/ccount; + + return true; +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setGammaCorrection(F32 g) +{ + U16 ramp[256*3]; + + for (U16 i = 0; i < 256; ++i) + ramp[i] = mPow((F32) i/256.0f, g) * 65535.0f; + dMemcpy(&ramp[256],ramp,256*sizeof(U16)); + dMemcpy(&ramp[512],ramp,256*sizeof(U16)); + + bool t = false; +#pragma message("todo: gamma") +// t = SetDeviceGammaRamp(platState.appDC, ramp); + return t; +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setVerticalSync( bool on ) +{ +#pragma message("todo: gl swap interval ext") +/* + if ( !gGLState.suppSwapInterval ) + return( false ); + + return( qwglSwapIntervalEXT( on ? 1 : 0 ) ); +*/ + return(false); +} + +//------------------------------------------------------------------------------ +DisplayDevice* OpenGLDevice::create() +{ + bool result = true; + bool fullScreenOnly = false; +#pragma message("todo: ::create full gl availability test") +/* + bool result = false; + bool fullScreenOnly = false; + + OSVERSIONINFO OSVersionInfo; + U32 switchedNT = false; + + dMemset( &OSVersionInfo, 0, sizeof( OSVERSIONINFO ) ); + OSVersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + if ( GetVersionEx( &OSVersionInfo ) && + OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) + { + DEVMODE devMode; + + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmBitsPerPel = platState.desktopBitsPixel == 16 ? 32 : 16; + devMode.dmFields = DM_BITSPERPEL; + + // attempt bit-depth change to test Windows 95B + if ( ChangeDisplaySettings( &devMode, CDS_TEST ) != DISP_CHANGE_SUCCESSFUL ) + smCanSwitchBitDepth = false; + } + // switching NT to 32-bit desktop to determine if we have a 16-bit card + else if (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && + OSVersionInfo.dwMajorVersion == 4 && + platState.desktopBitsPixel != 32) + { + DEVMODE devMode; + + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmBitsPerPel = 32; + devMode.dmFields = DM_BITSPERPEL; + + switchedNT = ChangeDisplaySettings( &devMode, 0 ) == DISP_CHANGE_SUCCESSFUL; + } + + // This shouldn't happen, but just to be safe... + if ( platState.hinstOpenGL ) + QGL_Shutdown(); + + if (!QGL_Init( "opengl32", "glu32" )) + { + if (switchedNT) + ChangeDisplaySettings( NULL, 0 ); + + return NULL; + } + + // Create a test window to see if OpenGL hardware acceleration is available: + WNDCLASS wc; + dMemset(&wc, 0, sizeof(wc)); + wc.style = CS_OWNDC; + wc.lpfnWndProc = DefWindowProc; + wc.hInstance = platState.appInstance; + wc.lpszClassName = "OGLTest"; + RegisterClass( &wc ); + + HWND testWindow = CreateWindow( + "OGLTest", + "", + WS_POPUP, + 0, 0, 640, 480, + NULL, + NULL, + platState.appInstance, + NULL ); + + if ( testWindow ) + { + HDC testDC = GetDC( testWindow ); + if ( testDC ) + { + PIXELFORMATDESCRIPTOR pfd; + CreatePixelFormat( &pfd, 16, 16, 8, false ); + U32 chosenFormat = ChooseBestPixelFormat( testDC, &pfd ); + if ( chosenFormat != 0 ) + { + qwglDescribePixelFormat( testDC, chosenFormat, sizeof( pfd ), &pfd ); + + result = !( pfd.dwFlags & PFD_GENERIC_FORMAT ); + + if ( result && (platState.desktopBitsPixel == 16 || pfd.cColorBits == 16) ) + D3DDevice::smStay16 = true; + + if ( result && platState.desktopBitsPixel != 16 && !smCanSwitchBitDepth) + { + // If Windows 95 cannot switch bit depth, it should only attempt 16-bit cards + // with a 16-bit desktop + + // See if we can get a 32-bit pixel format: + PIXELFORMATDESCRIPTOR pfd; + + CreatePixelFormat( &pfd, 32, 24, 8, false ); + S32 chosenFormat = ChooseBestPixelFormat( testDC, &pfd ); + if ( chosenFormat != 0 ) + { + qwglDescribePixelFormat( platState.appDC, chosenFormat, sizeof( pfd ), &pfd ); + + if (pfd.cColorBits == 16) + { + Platform::AlertOK("Requires 16-Bit Desktop", + "You must run in 16-bit color to play \"Tribes 2\".\nPlease quit the game, set your desktop color depth to 16-bit\nand then restart the application."); + + result = false; + } + } + } + // Don't allow 16-bit cards to do windowed mode on NT + else if (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && + OSVersionInfo.dwMajorVersion == 4 && + result && pfd.cColorBits == 16) + fullScreenOnly = true; + } + else if ( (platState.desktopBitsPixel != 16 || switchedNT) && smCanSwitchBitDepth ) + { + // Try again after changing the display to 16-bit: + ReleaseDC( testWindow, testDC ); + DestroyWindow( testWindow ); + + DEVMODE devMode; + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmBitsPerPel = 16; + devMode.dmFields = DM_BITSPERPEL; + + U32 test = ChangeDisplaySettings( &devMode, 0 ); + if ( test == DISP_CHANGE_SUCCESSFUL ) + { + testWindow = CreateWindow( + "OGLTest", + "", + WS_OVERLAPPED | WS_CAPTION, + 0, 0, 640, 480, + NULL, + NULL, + platState.appInstance, + NULL ); + + if ( testWindow ) + { + testDC = GetDC( testWindow ); + if ( testDC ) + { + CreatePixelFormat( &pfd, 16, 16, 8, false ); + chosenFormat = ChooseBestPixelFormat( testDC, &pfd ); + if ( chosenFormat != 0 ) + { + qwglDescribePixelFormat( testDC, chosenFormat, sizeof( pfd ), &pfd ); + + result = !( pfd.dwFlags & PFD_GENERIC_FORMAT ); + if ( result ) + fullScreenOnly = true; + } + } + } + } + ChangeDisplaySettings( NULL, 0 ); + switchedNT = false; + } + else if ( platState.desktopBitsPixel != 16 && !smCanSwitchBitDepth ) + Platform::AlertOK("Requires 16-Bit Desktop", + "You must run in 16-bit color to play \"Tribes 2\".\nPlease quit the game, set your desktop color depth to 16-bit\nand then restart the application."); + + ReleaseDC( testWindow, testDC ); + } + DestroyWindow( testWindow ); + } + + UnregisterClass( "OGLTest", platState.appInstance ); + + QGL_Shutdown(); + + // Return NT to previous desktop bit depth + if (switchedNT) + ChangeDisplaySettings( NULL, 0 ); +*/ + + if ( result ) + { + OpenGLDevice* newOGLDevice = new OpenGLDevice(); + if ( newOGLDevice ) + { + newOGLDevice->mFullScreenOnly = fullScreenOnly; + return (DisplayDevice*) newOGLDevice; + } + else + return NULL; + } + else + return NULL; +} diff --git a/platformMacCarb/macCarbOGLVideo.h b/platformMacCarb/macCarbOGLVideo.h new file mode 100644 index 0000000..6420496 --- /dev/null +++ b/platformMacCarb/macCarbOGLVideo.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MACCARBOGLVIDEO_H_ +#define _MACCARBOGLVIDEO_H_ + +#ifndef _PLATFORMVIDEO_H_ +#include "Platform/platformVideo.h" +#endif + +class OpenGLDevice : public DisplayDevice +{ + bool mCanChangeGamma; + bool mRestoreGamma; + U16 mOriginalRamp[256*3]; + + public: + 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 ); + + static DisplayDevice* create(); +}; + +#endif // _MACCARBOGLVIDEO_H_ diff --git a/platformMacCarb/macCarbProcessControl.cc b/platformMacCarb/macCarbProcessControl.cc new file mode 100644 index 0000000..c259c41 --- /dev/null +++ b/platformMacCarb/macCarbProcessControl.cc @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformMacCarb/platformMacCarb.h" + +void Platform::postQuitMessage(const U32 in_quitVal) +{ + platState.quit = true; +} + +void Platform::debugBreak() +{ +#pragma message("Platform::debugBreak NYI") + //DebugBreak(); +} + +void Platform::forceShutdown(S32 returnValue) +{ +#pragma message("Platform::forceShutdown NYI") + //exit(returnValue); +} diff --git a/platformMacCarb/macCarbStrings.cc b/platformMacCarb/macCarbStrings.cc new file mode 100644 index 0000000..f15da37 --- /dev/null +++ b/platformMacCarb/macCarbStrings.cc @@ -0,0 +1,370 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformMacCarb/platformMacCarb.h" +#include "platform/platform.h" +#include +#include +#include +#include +#include + + +char *dStrdup_r(const char *src, const char *file, U32 line) +{ + char *buffer = (char *) dMalloc_r(dStrlen(src) + 1, file, line); + dStrcpy(buffer, src); + return buffer; +} + +char *dStrnew(const char *src) +{ + char *buffer = new char[dStrlen(src) + 1]; + dStrcpy(buffer, src); + return buffer; +} + +char* dStrcat(char *dst, const char *src) +{ + return strcat(dst,src); +} + + +// 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, U32 dstSize, ...) +{ + const char* src; + 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, U32 dstSize, ...) +{ + const char* src; + 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; +} + + +S32 dStrcmp(const char *str1, const char *str2) +{ + return strcmp(str1, str2); +} + + +S32 dStricmp(const char *str1, const char *str2) +{ + char c1, c2; + while (1) + { + c1 = tolower(*str1++); + c2 = tolower(*str2++); + if (c1 < c2) return -1; + if (c1 > c2) return 1; + if (c1 == 0) return 0; + } +} + +S32 dStrncmp(const char *str1, const char *str2, U32 len) +{ + return strncmp(str1, str2, len); +} + +S32 dStrnicmp(const char *str1, const char *str2, U32 len) +{ + S32 i; + char c1, c2; + for (i=0; i c2) return 1; + if (!c1) return 0; + } + return 0; +} + + +char* dStrcpy(char *dst, const char *src) +{ + return strcpy(dst,src); +} + + +char* dStrncpy(char *dst, const char *src, U32 len) +{ + return strncpy(dst,src,len); +} + + +U32 dStrlen(const char *str) +{ + return strlen(str); +} + + +char* dStrupr(char *str) +{ + while (*str) + { + *str = toupper(*str); + str++; + } + return str; +} + + +char* dStrlwr(char *str) +{ + while (*str) + { + *str = tolower(*str); + str++; + } + return str; +} + + +char* dStrchr(char *str, S32 c) +{ + return strchr(str,c); +} + + +const char* dStrchr(const char *str, S32 c) +{ + return strchr(str,c); +} + +const char* dStrrchr(const char *str, S32 c) +{ + return strrchr(str,c); +} + + +char* dStrrchr(char *str, S32 c) +{ + return strrchr(str,c); +} + +U32 dStrspn(const char *str, const char *set) +{ + return(strspn(str, set)); +} + +U32 dStrcspn(const char *str, const char *set) +{ + return strcspn(str, set); +} + + +char* dStrstr(const char *str1, const char *str2) +{ + return strstr(str1,str2); +} + +char* dStrtok(char *str, const char *sep) +{ + return strtok(str, sep); +} + + +S32 dAtoi(const char *str) +{ + return atoi(str); +} + + +F32 dAtof(const char *str) +{ + return atof(str); +} + +bool dAtob(const char *str) +{ + return !dStricmp(str, "true") || dAtof(str); +} + +bool dIsalnum(const char c) +{ + return isalnum(c); +} + +bool dIsalpha(const char c) +{ + return(isalpha(c)); +} + +bool dIsspace(const char c) +{ + return(isspace(c)); +} + +bool dIsdigit(const char c) +{ + return(isdigit(c)); +} + +void dPrintf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vprintf(format, args); +} + +S32 dVprintf(const char *format, void *arglist) +{ + S32 len = vprintf(format, (char*)arglist); + + // to-do + // The intended behavior is to zero-terminate and not allow the overflow + return (len); +} + +S32 dSprintf(char *buffer, U32 /*bufferSize*/, const char *format, ...) +{ + va_list args; + va_start(args, format); + S32 len = vsprintf(buffer, format, args); + + // to-do + // The intended behavior is to zero-terminate and not allow the overflow + return (len); +} + + +S32 dVsprintf(char *buffer, U32 /*bufferSize*/, const char *format, void *arglist) +{ + S32 len = vsprintf(buffer, format, (char*)arglist); + + // to-do + // The intended behavior is to zero-terminate and not allow the overflow + return (len); +} + + +S32 dSscanf(const char *buffer, const char *format, ...) +{ + va_list args; + va_start(args, format); +// return __vsscanf(buffer, format, args); + return vsscanf(buffer, format, args); +} + +S32 dFflushStdout() +{ + return fflush(stdout); +} + +S32 dFflushStderr() +{ + return fflush(stderr); +} + +void dQsort(void *base, U32 nelem, U32 width, S32 (QSORT_CALLBACK *fcmp)(const void *, const void *)) +{ + qsort(base, nelem, width, fcmp); +} + + +//--------------------------------------------------------------------------- +// Mac Strinng conversion routines +U8* str2p(const char *str) +{ + static U8 buffer[256]; + str2p(str, buffer); + return buffer; +} + + +U8* str2p(const char *str, U8 *p) +{ + AssertFatal(dStrlen(str) <= 255, "str2p:: Max Pascal String length exceeded (max=255)."); + U8 *dst = p+1; + U8 *src = (U8*)str; + *p = 0; + while(*src != '\0') + { + *dst++ = *src++; + (*p) += 1; + } + return p; +} + + +char* p2str(U8 *p) +{ + static char buffer[256]; + p2str(p, buffer); + return buffer; +} + + +char* p2str(U8 *p, char *dst_str) +{ + U8 len = *p++; + char *src = (char*)p; + char *dst = dst_str; + while (len--) + { + *dst++ = *src++; + } + *dst = '\0'; + return dst_str; +} + diff --git a/platformMacCarb/macCarbTime.cc b/platformMacCarb/macCarbTime.cc new file mode 100644 index 0000000..85901ec --- /dev/null +++ b/platformMacCarb/macCarbTime.cc @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformMacCarb/platformMacCarb.h" +#include +#include + +#pragma message("time code needs eval -- may not be accurate in all cases, and might cause performance hit.") + +//-------------------------------------- +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; +} + +U32 Platform::getTime() +{ + time_t long_time; + time( &long_time ); + return long_time; +} + +U32 GetMilliseconds() +{ + UnsignedWide time; + Microseconds(&time); + return (time.hi*5000000) + (time.lo/1000); +} + +U32 Platform::getRealMilliseconds() +{ + UnsignedWide time; + Microseconds(&time); + return (time.hi*5000000) + (time.lo/1000); +} + +U32 Platform::getVirtualMilliseconds() +{ + return platState.currentTime; +} + +void Platform::advanceTime(U32 delta) +{ + platState.currentTime += delta; +} + diff --git a/platformMacCarb/macCarbWindow.cc b/platformMacCarb/macCarbWindow.cc new file mode 100644 index 0000000..10b278e --- /dev/null +++ b/platformMacCarb/macCarbWindow.cc @@ -0,0 +1,1281 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformMacCarb/platformMacCarb.h" +#include "platformMacCarb/platformGL.h" +#include "platform/platform.h" +#include "platform/platformVideo.h" +#include "platformMacCarb/maccarbOGLVideo.h" +#include "platform/event.h" +#include "console/console.h" +#include "platformMacCarb/maccarbConsole.h" +#include "platform/platformInput.h" +//#include "platformMacCarb/maccarbInput.h" +#include "platform/gameInterface.h" +#include "math/mRandom.h" +#include "core/fileStream.h" +#include "game/resource.h" +#include "platformMacCarb/maccarbFileio.h" + +//-------------------------------------- Mac System includes +#if defined(TARG_MACCARB) && (TARG_MACCARB>=0x0120) +#include +#else +#include +#include +#endif + +#include + +#if !__APPLE__ +#include +#endif + +//-------------------------------------- Resource Includes +#include "dgl/gBitmap.h" +#include "dgl/gFont.h" + +extern void createFontInit(); +extern void createFontShutdown(); + +bool gWindowCreated = false; + +#pragma message("todo: better timing abstraction") +#define GetTickCount() GetMilliseconds() + + +MacCarbPlatState platState; + +MacCarbPlatState::MacCarbPlatState() +{ + hDisplay = NULL; + appWindow = NULL; + drawable = NULL; + + quit = false; + + fmt = NULL; + ctx = NULL; + + // start with something reasonable. + desktopBitsPixel = 16; + desktopWidth = 1024; + desktopHeight = 768; + + volRefNum = 0; + dirID = 0; + + dStrcpy(appWindowTitle, "V12 Window"); +} + +static bool windowLocked = false; + +static U8 keyboardState[256]; +static bool mouseButtonState[3]; +static bool capsLockDown = false; +static S32 modifierKeys = 0; +static bool windowActive = true; + +static Point2I lastCursorPos(0,0); +static Point2I windowSize; +static bool sgDoubleByteEnabled = false; + +static const char *getKeyName(S32 vkCode); + +#define TICKS_IN_FRONT 0L +#define TICKS_IN_BACK 60L +static bool gBackgrounded = false; +static long gSleepTicks = TICKS_IN_FRONT; + +//-------------------------------------- +static const char *getMessageName(S32 msg) +{ + switch(msg) + { +// case WM_KEYDOWN: +// return "WM_KEYDOWN"; +// case WM_KEYUP: +// return "WM_KEYUP"; +// case WM_SYSKEYUP: +// return "WM_SYSKEYUP"; +// case WM_SYSKEYDOWN: +// return "WM_SYSKEYDOWN"; + default: + return "Unknown!!"; + } +} + + +void Platform::AlertOK(const char *windowTitle, const char *message) +{ + S16 hit; + Str255 title, desc; + str2p(windowTitle, title); + str2p(message, desc); + + Input::deactivate(); + + StandardAlert(0, title, desc, NULL, &hit); +} + +bool Platform::AlertOKCancel(const char *windowTitle, const char *message) +{ + S16 hit; + AlertStdAlertParamRec param; + Str255 title, desc; + str2p(windowTitle, title); + str2p(message, desc); + param.movable = false; + param.helpButton = false; + param.filterProc = NULL; + param.defaultText = (StringPtr)kAlertDefaultOKText; + param.cancelText = (StringPtr)kAlertDefaultCancelText; + param.otherText = NULL; + param.defaultButton = kAlertStdAlertOKButton; + param.cancelButton = kAlertStdAlertCancelButton; + param.position = kWindowDefaultPosition; + StandardAlert(0, title, desc, ¶m, &hit); + return (hit==kAlertStdAlertOKButton); +} + +bool Platform::AlertRetry(const char *windowTitle, const char *message) +{ + S16 hit; + AlertStdAlertParamRec param; + Str255 title, desc; + Str255 retryStr; + str2p(windowTitle, title); + str2p(message, desc); + str2p("Retry", retryStr); + param.movable = false; + param.helpButton = false; + param.filterProc = NULL; + param.defaultText = retryStr; + param.cancelText = (StringPtr)kAlertDefaultCancelText; + param.otherText = NULL; + param.defaultButton = kAlertStdAlertOKButton; + param.cancelButton = kAlertStdAlertCancelButton; + param.position = kWindowDefaultPosition; + StandardAlert(0, title, desc, ¶m, &hit); + return (hit==kAlertStdAlertOKButton); +} + +//-------------------------------------- +static void InitInput() +{ + dMemset( keyboardState, 0, 256 ); + dMemset( mouseButtonState, 0, sizeof( mouseButtonState ) ); +} + +/* +extern Point MTemp : 0x828; // RawMouse and MTemp both contain the current absolute mouse position +extern Point RawMouse : 0x82C; +extern Rect CrsrPin : 0x834; +extern Byte CrsrNew : 0x8CE; // CrsrNew is a flag that tells the system when the mouse has changed +extern Byte CrsrCouple : 0x8CF; // CrsrCouple is what CrsrNew should be set to when the mouse has changed +*/ + +static Point& ClientToScreen( WindowPtr wnd, Point pt ) +{ + static Point screen; + screen = pt; + + GrafPtr savePort; + GetPort( &savePort ); + SetPortWindowPort(platState.appWindow); + LocalToGlobal( &screen ); + SetPort( savePort ); + + return screen; +} + + +void GetWindowRect( WindowPtr wnd, RectI *pRect ) +{ + // note - I should probably add a check for title bar, and borders to simulate Windows completely. + if ( pRect && wnd ) + { + GrafPtr port; + Rect r; + GetPort( &port ); + SetPortWindowPort(wnd); + GetWindowPortBounds(wnd, &r); + SetPort( port ); + + pRect->point.x = r.left; + pRect->point.y = r.top; + pRect->extent.x = r.right - r.left; + pRect->extent.y = r.bottom - r.top; + } +} + + +static void GetMousePos(Point *pt) +{ + GrafPtr savePort; + GetPort( &savePort ); + SetPortWindowPort( platState.appWindow ); + GetMouse(pt); + SetPort( savePort ); +} + +/* +static void SetCursorPos(const Point2I &pos) +{ + Point pt; + Rect deviceRect = (*platState.hDisplay)->gdRect; + + // window to screen space + //RectI r; + //GetWindowRect(platState.appWindow, &r); + pt.h = pos.x; + pt.v = pos.y; + pt = ClientToScreen(platState.appWindow, pt); + + // display to desktop ? + //pt.h = deviceRect.left + pos.x; + //pt.v = deviceRect.top + pos.y; + + MTemp = RawMouse = pt; + CrsrNew = CrsrCouple; +} + + + +//-------------------------------------- +static void setMouseClipping() +{ +// ClipCursor(NULL); + if(windowActive) + { +// ShowCursor(false); + if(windowLocked) + { +// RECT r; +// GetWindowRect(winState.appWindow, &r); +// ClipCursor(&r); +// + RectI r; + GetWindowRect(platState.appWindow, &r); + //r.point += r.extent / 2; + r.extent /= 2; + //S32 centerX = (r.right + r.left) >> 1; + //S32 centerY = (r.bottom + r.top) >> 1; + SetCursorPos(r.extent); + } + } +// else +// ShowCursor(true); +} +*/ + +//-------------------------------------- +void Platform::enableKeyboardTranslation(void) +{ +} + +//-------------------------------------- +void Platform::disableKeyboardTranslation(void) +{ +} + + +//-------------------------------------- +void Platform::setWindowLocked(bool locked) +{ + windowLocked = locked; +// setMouseClipping(); +} + + +//-------------------------------------- +static bool HandleUpdate(WindowPtr w) +{ + if (w && w == platState.appWindow) + { + BeginUpdate(w); + Game->refreshWindow(); + EndUpdate(w); + return(true); + } + + return(false); +} + + +//-------------------------------------- +static void HandleActivate(WindowPtr w, bool winActive, bool appActive) +{ + if (w && w==platState.appWindow) + { + if (appActive) + { + Video::reactivate(); +/* + ShowCursor(false); + if ( Video::isFullScreen() ) + hideTheTaskbar(); +*/ + } + else + { + Video::deactivate(); +// restoreTheTaskbar(); + } + + if (winActive) + { + Input::activate(); + } + else + { + #pragma message("need to release input state on deactivate") + // if any input captured -- release all input states so we don't errantly keep them locked... + Input::deactivate(); + } + + HandleUpdate(w); + } +} + + +//-------------------------------------- +//static void processKeyMessage(UINT message, WPARAM wParam, LPARAM lParam) +//{ +// S32 nVirtkey = wParam; +// S32 scanCode = (lParam >> 16) & 0xff; +// bool extended = (lParam >> 24) & 1; +// bool repeat = (lParam >> 30) & 1; +// bool make = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN); +// +// S32 newVirtKey = nVirtkey; +// switch(nVirtkey) +// { +// case KEY_MENU: +// if(extended) +// newVirtKey = KEY_RMENU; +// else +// newVirtKey = KEY_LMENU; +// break; +// case KEY_CONTROL: +// if(extended) +// newVirtKey = KEY_RCONTROL; +// else +// newVirtKey = KEY_LCONTROL; +// break; +// case KEY_SHIFT: +// if(scanCode == 54) +// newVirtKey = KEY_RSHIFT; +// else +// newVirtKey = KEY_LSHIFT; +// break; +// } +// +// S32 modKey = modifierKeys; +// +// if(make) +// { +// switch (newVirtKey) +// { +// case KEY_LSHIFT: modifierKeys |= SI_LSHIFT; modKey = 0; break; +// case KEY_RSHIFT: modifierKeys |= SI_RSHIFT; modKey = 0; break; +// case KEY_LCONTROL: modifierKeys |= SI_LCTRL; modKey = 0; break; +// case KEY_RCONTROL: modifierKeys |= SI_RCTRL; modKey = 0; break; +// case KEY_LMENU: modifierKeys |= SI_LALT; modKey = 0; break; +// case KEY_RMENU: modifierKeys |= SI_RALT; modKey = 0; break; +// } +// if(nVirtkey == VK_CAPITAL) +// { +// capsLockDown = !capsLockDown; +// if(capsLockDown) +// keyboardState[nVirtkey] |= 0x01; +// else +// keyboardState[nVirtkey] &= 0xFE; +// } +// keyboardState[nVirtkey] |= 0x80; +// } +// else +// { +// switch (newVirtKey) +// { +// case KEY_LSHIFT: modifierKeys &= ~SI_LSHIFT; modKey = 0; break; +// case KEY_RSHIFT: modifierKeys &= ~SI_RSHIFT; modKey = 0; break; +// case KEY_LCONTROL: modifierKeys &= ~SI_LCTRL; modKey = 0; break; +// case KEY_RCONTROL: modifierKeys &= ~SI_RCTRL; modKey = 0; break; +// case KEY_LMENU: modifierKeys &= ~SI_LALT; modKey = 0; break; +// case KEY_RMENU: modifierKeys &= ~SI_RALT; modKey = 0; break; +// } +// keyboardState[nVirtkey] &= 0x7f; +// } +// +// WORD ascii = 0; +// S32 res = ToAscii(nVirtkey, (lParam >> 16) & 0xFF, keyboardState, &ascii, 0); +// if(res < 1) +// ascii = 0; +// +// InputEvent event; +// +// event.deviceInst = 0; +// event.deviceType = KeyboardDeviceType; +// event.objType = SI_KEY; +// event.objInst = newVirtKey; +// event.action = make ? (repeat ? SI_REPEAT : SI_MAKE ) : SI_BREAK; +// event.modifier = modKey; +// event.ascii = ascii; +// event.fValue = make ? 1.0 : 0.0; +// +// // printf("%s - %s (%d) count: %d ext: %s char: %c\n", +// // getMessageName(message), getKeyName(nVirtkey), scanCode, +// // repeat, extended ? "True" : "False", ascii); +// GamePostEvent(event); +//} + +//-------------------------------------- +static Point lastPos = {-11,-11}; // something unlikely... +static void CheckCursorPos() +{ + static bool reset = true; + + if (!windowActive || gBackgrounded) //!!!!TBD windowActive/Locked should go false if bg. + { + reset = true; + return; + } + + Point mousePos; + GetMousePos(&mousePos); + + if (!windowLocked) + { + // since we're in Poll mode, we need to post a mousemove event for the cursor to be updated. + MouseMoveEvent event; + event.xPos = mousePos.h; // horizontal position of cursor + event.yPos = mousePos.v; // vertical position of cursor + event.modifier = modifierKeys; + Game->postEvent(event); + } + else + { + if (reset) + reset = false; + else + if (mousePos.h!=lastPos.h + && mousePos.v!=lastPos.v) // mouse moved. + { + if(mousePos.h != lastPos.h) + { + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_XAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = F32(mousePos.h - lastPos.h); + Game->postEvent(event); +// Con::printf( "EVENT: Mouse move (%.1f, 0.0).\n", event.fValue ); + } + + if(mousePos.v != lastPos.v) + { + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_YAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = F32(mousePos.v - lastPos.v); + Game->postEvent(event); +// Con::printf( "EVENT: Mouse move (0.0, %.1f).\n", event.fValue ); + } + + } + + lastPos = mousePos; // copy over last position. + } +} + + +//-------------------------------------- +//static void mouseButtonEvent(S32 action, S32 objInst) +//{ +// action, objInst; +// CheckCursorPos(); +// if(!windowLocked) +// { +// if(action == SI_MAKE) +// SetCapture(winState.appWindow); +// else +// ReleaseCapture(); +// } +// +// InputEvent event; +// +// event.deviceInst = 0; +// event.deviceType = MouseDeviceType; +// event.objType = SI_BUTTON; +// event.objInst = objInst; +// event.action = action; +// event.modifier = modifierKeys; +// event.ascii = 0; +// event.fValue = action == SI_MAKE ? 1.0 : 0.0; +// +// Game->postEvent(event); +//} + + +#define WINDOW_RESTRICT_EVENTS 0 + +static void ProcessKeyboard( EventRecord &msg ) +{ +#if WINDOW_RESTRICT_EVENTS + WindowPtr which; + FindWindow( msg.where, &which ); + if (which != platState.appWindow) + return; +#endif + +#if ALLOW_MENU_PROCESSING + unsigned char c; + c = (msg.message & charCodeMask); + if ((msg.modifiers & cmdKey) != 0) + { + AdjustMenus(); + HandleMenuKey(c, msg.modifiers); + } +#endif + + InputEvent event; + event.deviceInst = 0; + event.deviceType = KeyboardDeviceType; + event.objType = SI_KEY; + event.objInst = TranslateOSKeyCode( (msg.message & keyCodeMask) >> 8 ); + event.ascii = msg.message & charCodeMask; + + switch(msg.what) + { + case keyDown: + event.action = SI_MAKE; + event.fValue = 1.0f; + //Con::printf("keyDN ( %02x )", (msg.message & keyCodeMask) >> 8); + break; + + case autoKey: + event.action = SI_REPEAT; + event.fValue = 1.0f; + break; + + case keyUp: + event.action = SI_BREAK; + event.fValue = 0.0f; + //Con::printf("keyUP ( %02x )", (msg.message & keyCodeMask) >> 8); + break; + } + + event.modifier = 0; + if (msg.modifiers & shiftKey) event.modifier |= SI_LSHIFT; + if (msg.modifiers & rightShiftKey) event.modifier |= SI_RSHIFT; + if (msg.modifiers & cmdKey) event.modifier |= SI_LALT; + if (msg.modifiers & optionKey) event.modifier |= SI_MAC_LOPT; + if (msg.modifiers & rightOptionKey) event.modifier |= SI_MAC_ROPT; + if (msg.modifiers & controlKey) event.modifier |= SI_LCTRL; + if (msg.modifiers & rightControlKey) event.modifier |= SI_RCTRL; + + // handle command-Q exit + if ((event.objInst == KEY_Q) && (msg.modifiers & cmdKey)) + Platform::postQuitMessage(0); + + Game->postEvent(event); +} + + +static void ProcessMouse( EventRecord &msg ) +{ + WindowPtr which; + U16 where = FindWindow( msg.where, &which ); + if (which != platState.appWindow) + return; + + SelectWindow(platState.appWindow); + + // handle a little window maintence + switch (where) + { +#if !TARGET_API_MAC_CARBON + case inSysWindow: + SystemClick ( &msg, platState.appWindow ); + return; +#endif + + case inDrag: + { + RgnHandle rgn = GetGrayRgn(); + Rect r; + GetRegionBounds(rgn, &r); + SetPortWindowPort(platState.appWindow); + DragWindow(platState.appWindow, msg.where, &r); + if (platState.ctx) + aglUpdateContext(platState.ctx); + HandleUpdate(platState.appWindow); + return; + } + + case inContent: + // will handle below. + break; + + default: + return; + } + + + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_BUTTON; + event.objInst = KEY_BUTTON0; // always button 0 for now + event.modifier = 0; + event.ascii = 0; + if (msg.what == mouseDown) + { + event.action = SI_MAKE; + event.fValue = 1.0; + } + else + { + event.action = SI_BREAK; + event.fValue = 1.0; + } + Game->postEvent(event); +} + + +//-------------------------------------- +static bool ProcessMessages() +{ + if (platState.quit) + return false; + + SetEventMask(everyEvent); + + EventRecord msg; + bool done = false; + while(!done && WaitNextEvent(everyEvent, &msg, 0, NULL)) + { + bool handled = (gConsole && gConsole->handleEvent(&msg)); + if (!handled) + switch(msg.what) + { + case nullEvent: + //AdjustCursor(msg.where, ((msg.modifiers&optionKey)!=0), cursorRgn); + done = true; + break; + + case keyDown: +// case autoKey: + case keyUp: + ProcessKeyboard(msg); + break; + + case mouseDown: + case mouseUp: + ProcessMouse(msg); + break; + + case activateEvt: + HandleActivate((WindowPtr)(msg.message), (msg.modifiers & activeFlag) != 0, !gBackgrounded); + break; + + case updateEvt: + HandleUpdate((WindowPtr)(msg.message)); + break; + + case osEvt: + { + switch ((unsigned long)(msg.message >> 24)) // osEvt msg is in the high byte. + { + case mouseMovedMessage: // this is it moved from the region passed to WNE + { +// AdjustCursor(msg.where, ((msg.modifiers&optionKey)!=0), cursorRgn); + break; + } + + case suspendResumeMessage: + { + Cursor arrow; + GetQDGlobalsArrow(&arrow); + + gBackgrounded = (msg.message & resumeFlag) == 0; + HandleActivate(platState.appWindow, !gBackgrounded, !gBackgrounded); + gSleepTicks = (gBackgrounded?TICKS_IN_BACK:TICKS_IN_FRONT); + + if (gBackgrounded) /* just suspended */ + SetCursor(&arrow); + else /* just resumed */ + SetCursor(&arrow); + break; + }/* end case suspend/resume evt */ + } + + break; + } + + case kHighLevelEvent: + { + // the type of message is stored in the where Point... so cast it + U32 hlWhat = *((U32*)(&msg.where)); + if ( hlWhat == kAEQuitApplication ) + { + Platform::postQuitMessage(0); + return false; + } + else + AEProcessAppleEvent(&msg); + break; + } + + default: + // Con::printf("%d %08x", msg.what, msg.what); + break; + } + } + return true; +} + + +//-------------------------------------- +void Platform::process() +{ + extern bool gMouseActive; + if (!Input::isActive() || !gMouseActive) + CheckCursorPos(); + + gConsole->process(); + + if(!ProcessMessages()) + { + // generate a quit event + Event quitEvent; + quitEvent.type = QuitEventType; + + Game->postEvent(quitEvent); + } + +/* + // if there's no window, we sleep 1, otherwise we sleep 0 + if(!Game->isJournalReading()) + Sleep(gWindowCreated ? 0 : 1); // give others some process time if necessary... + HWND window = GetForegroundWindow(); + if (window && gWindowCreated) + { + // check to see if we are in the foreground or not + // if not Sleep for 100ms or until a Win32 message/input is recieved + DWORD foregroundProcessId; + GetWindowThreadProcessId(window, &foregroundProcessId); + if (foregroundProcessId != winState.processId) + MsgWaitForMultipleObjects(0, NULL, false, 100, QS_ALLINPUT); + } + +*/ + + Input::process(); +} + +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 + +//-------------------------------------- +/* +static void InitWindowClass() +{ + WNDCLASS wc; + dMemset(&wc, 0, sizeof(wc)); + + wc.style = CS_OWNDC; + wc.lpfnWndProc = WindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = winState.appInstance; + wc.hIcon = LoadIcon(winState.appInstance, MAKEINTRESOURCE(IDI_ICON2)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); + wc.lpszMenuName = 0; + wc.lpszClassName = windowClassName; + RegisterClass( &wc ); + + // Curtain window class: + wc.lpfnWndProc = DefWindowProc; + wc.hCursor = NULL; + wc.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH); + wc.lpszClassName = "Curtain"; + RegisterClass( &wc ); +} +*/ + +//-------------------------------------- +static void GetDesktopState() +{ + platState.hDisplay = GetMainDevice(); + platState.desktopBitsPixel = (*(*(platState.hDisplay))->gdPMap)->pixelSize; + Rect r = (*(platState.hDisplay))->gdRect; + platState.desktopWidth = r.right - r.left; + platState.desktopHeight = r.bottom - r.top; +} + + + +//-------------------------------------- +WindowPtr CreateOpenGLWindow( U32 width, U32 height, bool fullScreen ) +{ + int offset = 144; // some nice starting offset. + + // for now just get first screen device. + //!!!!!tbd - this needs to be in sync with what setupgl picks!!!! + platState.hDisplay = GetMainDevice(); + + // this rect should really be based off the device coords... !!!!!TBD + Rect rect; + SetRect( &rect, offset+48, offset, width+offset+48, height+offset); + + CWindowPtr w; + w = NewCWindow(NULL, + &rect, // bounding rect + str2p(platState.appWindowTitle), // window title + false, // is visible + fullScreen?kWindowPlainDialogProc:kWindowDocumentProc, // window type + (WindowPtr) -1L, // top most window + false, // has a close box + 0L); // reference constant + + if (w != NULL) + ShowWindow(w); + + return(w); +} + +//-------------------------------------- +WindowPtr CreateCurtain( U32 width, U32 height ) +{ + WindowPtr w=NULL; +/* + w = CreateWindow( + "Curtain", + "", + ( WS_POPUP | WS_MAXIMIZE | WS_VISIBLE ), + 0, 0, + width, height, + NULL, NULL, + winState.appInstance, + NULL ); +*/ + return(w); +} + + +/* +//-------------------------------------- +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; +} + +//-------------------------------------- +enum { MAX_PFDS = 256 }; + +S32 ChooseBestPixelFormat(HDC hDC, PIXELFORMATDESCRIPTOR *pPFD) +{ + PIXELFORMATDESCRIPTOR pfds[MAX_PFDS+1]; + S32 i; + S32 bestMatch = 0; + + S32 maxPFD = qwglDescribePixelFormat(hDC, 1, sizeof(PIXELFORMATDESCRIPTOR), &pfds[0]); + if(maxPFD > MAX_PFDS) + maxPFD = MAX_PFDS; + + bool accelerated = false; + + for(i = 1; i <= maxPFD; i++) + { + qwglDescribePixelFormat(hDC, i, sizeof(PIXELFORMATDESCRIPTOR), &pfds[i]); + + // make sure this has hardware acceleration: + if ( ( pfds[i].dwFlags & PFD_GENERIC_FORMAT ) != 0 ) + continue; + + // verify pixel type + if ( pfds[i].iPixelType != PFD_TYPE_RGBA ) + continue; + + // verify proper flags + if ( ( ( pfds[i].dwFlags & pPFD->dwFlags ) & pPFD->dwFlags ) != pPFD->dwFlags ) + continue; + + accelerated = !(pfds[i].dwFlags & PFD_GENERIC_FORMAT); + + // + // selection criteria (in order of priority): + // + // PFD_STEREO + // colorBits + // depthBits + // stencilBits + // + if ( bestMatch ) + { + // check stereo + if ( ( pfds[i].dwFlags & PFD_STEREO ) && ( !( pfds[bestMatch].dwFlags & PFD_STEREO ) ) && ( pPFD->dwFlags & PFD_STEREO ) ) + { + bestMatch = i; + continue; + } + + if ( !( pfds[i].dwFlags & PFD_STEREO ) && ( pfds[bestMatch].dwFlags & PFD_STEREO ) && ( pPFD->dwFlags & PFD_STEREO ) ) + { + bestMatch = i; + continue; + } + + // check color + if ( pfds[bestMatch].cColorBits != pPFD->cColorBits ) + { + // prefer perfect match + if ( pfds[i].cColorBits == pPFD->cColorBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( pfds[i].cColorBits > pfds[bestMatch].cColorBits ) + { + bestMatch = i; + continue; + } + } + + // check depth + if ( pfds[bestMatch].cDepthBits != pPFD->cDepthBits ) + { + // prefer perfect match + if ( pfds[i].cDepthBits == pPFD->cDepthBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( pfds[i].cDepthBits > pfds[bestMatch].cDepthBits ) + { + bestMatch = i; + continue; + } + } + + // check stencil + if ( pfds[bestMatch].cStencilBits != pPFD->cStencilBits ) + { + // prefer perfect match + if ( pfds[i].cStencilBits == pPFD->cStencilBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( ( pfds[i].cStencilBits > pfds[bestMatch].cStencilBits ) && + ( pPFD->cStencilBits > 0 ) ) + { + bestMatch = i; + continue; + } + } + } + else + { + bestMatch = i; + } + } + + if ( !bestMatch ) + return 0; + + else if ( pfds[bestMatch].dwFlags & PFD_GENERIC_ACCELERATED ) + { + // MCD + } + else + { + // ICD + } + + *pPFD = pfds[bestMatch]; + + return bestMatch; +} +*/ + + +//-------------------------------------- +const Point2I &Platform::getWindowSize() +{ + return windowSize; +} + + +//-------------------------------------- +void Platform::setWindowSize( U32 newWidth, U32 newHeight ) +{ + windowSize.set( newWidth, newHeight ); +} + + +//-------------------------------------- +// !!!!!TBD!!!!! what should this do on the Mac. +void Platform::minimizeWindow() +{ +} + + +//-------------------------------------- +static void InitWindow(const Point2I &initialSize) +{ + windowSize = initialSize; +} + + +//-------------------------------------- +static void InitOpenGL() +{ + // Just for kicks. Seems a relatively central place to put this... +#if !__APPLE__ + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); +#endif + + 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" ); + + if ( !Video::setDevice( Con::getVariable( "$pref::Video::displayDevice" ), width, height, bpp, fullScreen ) ) + { + // Next, try the default OpenGL device: + if ( !Video::setDevice( "OpenGL", width, height, bpp, fullScreen ) ) + { + AssertFatal( false, "Could not find a compatible display device!" ); + return; + } + } +} + + +//-------------------------------------- +ConsoleFunction( getDesktopResolution, const char*, 1, 1, "getDesktopResolution()" ) +{ + argc; argv; + char buffer[256]; + dSprintf( buffer, sizeof( buffer ), "%d %d %d", platState.desktopWidth, platState.desktopHeight, platState.desktopBitsPixel ); + char* returnString = Con::getReturnBuffer( dStrlen( buffer ) + 1 ); + dStrcpy( returnString, buffer ); + return( returnString ); +} + + +//-------------------------------------- +void Platform::init() +{ + // Set the platform variable for the scripts + Con::setVariable( "$platform", "maccarb" ); + + MacConsole::create(); + if ( !MacConsole::isEnabled() ) + Input::init(); + InitInput(); // in case Input setup falls through. + + GetDesktopState(); +// installRedBookDevices(); + +// not sure why this is here on mac. + Video::init(); + Video::installDevice( OpenGLDevice::create() ); + + sgDoubleByteEnabled = true; // !!!!! this right? +// sgQueueEvents = true; +} + +//-------------------------------------- +void Platform::shutdown() +{ +// sgQueueEvents = false; + +// if(gMutexHandle) +// CloseHandle(gMutexHandle); + setWindowLocked( false ); + Video::destroy(); + Input::destroy(); + MacConsole::destroy(); +} + + +//-------------------------------------- + +static U32 lastTimeTick; + +//-------------------------------------- +static S32 run(S32 argc, const char **argv) +{ + createFontInit(); + windowSize.set(0,0); + + lastTimeTick = GetTickCount(); + +// TribesGame *tmpgame = new TribesGame; + int ret = Game->main(argc, argv); + createFontShutdown(); + return ret; +} + +void Platform::initWindow(const Point2I &initialSize, const char *name) +{ + Con::printf( "Video Init:" ); + Video::init(); + if ( Video::installDevice( OpenGLDevice::create() ) ) + Con::printf( " Accelerated OpenGL display device detected." ); + else + Con::printf( " Accelerated OpenGL display device not detected." ); + Con::printf( "" ); + + dSprintf(platState.appWindowTitle, sizeof(platState.appWindowTitle), name); + InitWindow(initialSize); + InitOpenGL(); + gWindowCreated = true; +} + +//-------------------------------------- +S32 main(S32 argc, const char **argv) +{ + // mac does not support command line arguments + // it may be possible to get the comment field from the file + // see DesktopManager and DTGetComment + +#if !TARGET_API_MAC_CARBON + InitGraf(&qd.thePort); // init QuickDraw -- 'qd' is a Mac Global + InitFonts(); // init the Font Manager + InitWindows(); // init the Window Manager + InitMenus(); + TEInit(); + InitDialogs( NULL ); +#endif + InitCursor(); + + FlushEvents( everyEvent, 0 ); + SetEventMask(everyEvent); + + // save away home directory info into platState. + macGetHomeDirectory(); + + return run(argc, argv); +} + +//-------------------------------------- +void TimeManager::process() +{ + U32 curTime = GetTickCount(); + TimeEvent event; + event.elapsedTime = curTime - lastTimeTick; + if(event.elapsedTime > 5) + { + lastTimeTick = curTime; + Game->postEvent(event); + } +} + +/* +GLimp_Init + GLW_LoadOpenGL + QGL_Init(driver); + GLW_StartDriverAndSetMode + GLW_SetMode + ChangeDisplaySettings + GLW_CreateWindow + GLW_InitDriver + GLW_CreatePFD + GLW_MakeContext + GLW_ChoosePFD + DescribePixelFormat + SetPixelFormat + + GLW_InitExtensions + WG_CheckHardwareGamma +*/ + +//-------------------------------------- +#pragma message("todo: implement random") +F32 Platform::getRandom() +{ + //return rand() / F32(RAND_MAX); + return 0.5; +} + + +//-------------------------------------- +// Web browser function: +//-------------------------------------- +bool Platform::openWebBrowser( const char* webAddress ) +{//!!!!!!! TBD + return(false); +} \ No newline at end of file diff --git a/platformMacCarb/macCarb_common_prefix.h b/platformMacCarb/macCarb_common_prefix.h new file mode 100644 index 0000000..e0f690b --- /dev/null +++ b/platformMacCarb/macCarb_common_prefix.h @@ -0,0 +1,20 @@ +//maccarb_common_prefix.h + + +// for Project Builder, not sure what the heck it sets. +//#ifndef __POWERPC__ +//#define __POWERPC__ 1 +//#endif + + +#define TARG_MACCARB 0x0104 // the carbon version # we are targeting. +#define TARG_MACANY 1 + +// activate custom features. +#define TRUFORM 1 +#define TERRAIN_NORMALS 1 // which will allow Truform on terrain. + + +// defines for the mac headers to activate proper Carbon codepaths. +#define TARGET_API_MAC_CARBON 1 +#define OTCARBONAPPLICATION 1 // means we can use the old-style funcnames diff --git a/platformMacCarb/macCarb_debug_prefix.h b/platformMacCarb/macCarb_debug_prefix.h new file mode 100644 index 0000000..ff234aa --- /dev/null +++ b/platformMacCarb/macCarb_debug_prefix.h @@ -0,0 +1,21 @@ +//maccarb_debug_prefix.h + +#include "maccarb_common_prefix.h" + +// our defines +#define V12_DEBUG 1 +#define BUILD_SUFFIX "_DEBUG" + +// turn these off for now. +//#define DEBUG 1 +//#define ENABLE_ASSERTS 1 + +#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/platformMacCarb/macCarb_release_prefix.h b/platformMacCarb/macCarb_release_prefix.h new file mode 100644 index 0000000..fe243c2 --- /dev/null +++ b/platformMacCarb/macCarb_release_prefix.h @@ -0,0 +1,19 @@ +//maccarb_release_prefix.h + +#include "maccarb_common_prefix.h" + +// our defines +#define V12_NO_ASSERTS 1 +#define BUILD_SUFFIX "" + +#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/platformMacCarb/platformAL.h b/platformMacCarb/platformAL.h new file mode 100644 index 0000000..c7a12ef --- /dev/null +++ b/platformMacCarb/platformAL.h @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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, // DEFINE IS IN NEWEST OPENAL + AL_CONE_OUTER_GAIN, + + // relative with pos(0,0,0) won't work for ambient sounds with miles + AL_SOURCE_AMBIENT, // DEFINE IS IN NEWEST OPENAL + 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 +//#include "OpenAL/win32/openALFn.h" +#endif + +namespace Audio +{ + +bool libraryInit(const char *library); +void libraryInitExtensions(); +void libraryShutdown(); + +inline bool doesSupportIASIG() +{ + return 0; //gDoesSupport_AL_EXT_IASIG; +} + +inline bool doesSupportDynamix() +{ + return 0; //gDoesSupport_AL_EXT_DYNAMIX; +} + +// helpers +F32 DBToLinear(F32 value); +F32 linearToDB(F32 value); + +} // end namespace Audio + + +#endif // _H_PLATFORMAL_ diff --git a/platformMacCarb/platformGL.h b/platformMacCarb/platformGL.h new file mode 100644 index 0000000..a2a0dcd --- /dev/null +++ b/platformMacCarb/platformGL.h @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMGL_H_ +#define _PLATFORMGL_H_ + + +// ON MAC, WE ARE USING THE STD APPLE OPENGL HDRS. +#include +#include +#include + + +// FROM HERE ON IS >NOT< IN APPLE OPENGL 1.2 SDK +#if defined(GL_GLEXT_VERSION) && (GL_GLEXT_VERSION<=6) && defined(GL_VERSION_1_2) + +#ifndef GL_EXT_vertex_buffer +#define GL_EXT_vertex_buffer 1 + +// these are the V12-custom extensions. really only useful in external 3d driver. +#define GL_V12MTVFMT_EXT 0x8702 +#define GL_V12MTNVFMT_EXT 0x8703 +#define GL_V12FTVFMT_EXT 0x8704 +#define GL_V12FMTVFMT_EXT 0x8705 + +#ifdef GL_GLEXT_PROTOTYPES +extern GLboolean APIENTRY glAvailableVertexBufferEXT(); +extern GLint APIENTRY glAllocateVertexBufferEXT(GLsizei size, GLint format, GLboolean preserve); +extern void* APIENTRY glLockVertexBufferEXT(GLint handle, GLsizei size); +extern void APIENTRY glUnlockVertexBufferEXT(GLint handle); +extern void APIENTRY glSetVertexBufferEXT(GLint handle); +extern void APIENTRY glOffsetVertexBufferEXT(GLint handle, GLuint offset); +extern void APIENTRY glFillVertexBufferEXT(GLint handle, GLint first, GLsizei count); +extern void APIENTRY glFreeVertexBufferEXT(GLint handle); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRY * PFNGLAVAILABLEVERTEXBUFFEREXTPROC) (); +typedef GLint (APIENTRY * PFNGLALLOCATEVERTEXBUFFEREXTPROC) (GLsizei size, GLint format, GLboolean preserve); +typedef void* (APIENTRY * PFNGLLOCKVERTEXBUFFEREXTPROC) (GLint handle, GLsizei size); +typedef void (APIENTRY * PFNGLUNLOCKVERTEXBUFFEREXTPROC) (GLint handle); +typedef void (APIENTRY * PFNGLSETVERTEXBUFFEREXTPROC) (GLint handle); +typedef void (APIENTRY * PFNGLOFFSETVERTEXBUFFEREXTPROC) (GLint handle, GLuint offset); +typedef void (APIENTRY * PFNGLFILLVERTEXBUFFEREXTPROC) (GLint handle, GLint first, GLsizei count); +typedef void (APIENTRY * PFNGLFREEVERTEXBUFFEREXTPROC) (GLint handle); +#endif + +/* A forgotten token. */ +#define GL_CLAMP_TO_EDGE_EXT 0x812F + +#define UNSIGNED_SHORT_5_6_5 0x8363 +#define UNSIGNED_SHORT_5_6_5_REV 0x8364 + +#endif // glexts < v6, gl v1.2 stuff needed additionally. + + +/* + * GL state information. + */ +struct GLState +{ + bool suppARBMultitexture; + bool suppPackedPixels; + bool suppTexEnvAdd; + bool suppLockedArrays; + bool suppTextureEnvCombine; + bool suppVertexArrayRange; + bool suppFogCoord; + bool suppEdgeClamp; + bool suppTextureCompression; + bool suppS3TC; + bool suppFXT1; + bool suppTexAnisotropic; + bool suppPalettedTexture; + bool suppVertexBuffer; + bool suppSwapInterval; + unsigned int triCount[4]; + unsigned int primCount[4]; + unsigned int primMode; // 0-3 + + GLfloat maxAnisotropy; + GLint maxTextureUnits; +}; + +extern GLState gGLState; + +extern bool gOpenGLDisablePT; +extern bool gOpenGLDisableCVA; +extern bool gOpenGLDisableTEC; +extern bool gOpenGLDisableARBMT; +extern bool gOpenGLDisableFC; +extern bool gOpenGLDisableTCompress; +extern bool gOpenGLNoEnvColor; +extern float gOpenGLGammaCorrection; +extern bool gOpenGLNoDrawArraysAlpha; + +/* + * Inline state helpers. + */ +inline void dglSetRenderPrimType(unsigned int type) +{ + gGLState.primMode = type; +} + +inline void dglClearPrimMetrics() +{ + for(int i = 0; i < 4; i++) + gGLState.triCount[i] = gGLState.primCount[i] = 0; +} + +inline bool dglDoesSupportPalettedTexture() +{ + return gGLState.suppPalettedTexture && (gOpenGLDisablePT == false); +} + +inline bool dglDoesSupportCompiledVertexArray() +{ + return gGLState.suppLockedArrays && (gOpenGLDisableCVA == false); +} + +inline bool dglDoesSupportTextureEnvCombine() +{ + return gGLState.suppTextureEnvCombine && (gOpenGLDisableTEC == false); +} + +inline bool dglDoesSupportARBMultitexture() +{ + return gGLState.suppARBMultitexture && (gOpenGLDisableARBMT == false); +} + +inline bool dglDoesSupportVertexArrayRange() +{ + return gGLState.suppVertexArrayRange; +} + +inline bool dglDoesSupportFogCoord() +{ + return gGLState.suppFogCoord && (gOpenGLDisableFC == false); +} + +inline bool dglDoesSupportEdgeClamp() +{ + return gGLState.suppEdgeClamp; +} + +inline bool dglDoesSupportTextureCompression() +{ + return gGLState.suppTextureCompression && (gOpenGLDisableTCompress == false); +} + +inline bool dglDoesSupportS3TC() +{ + return gGLState.suppS3TC; +} + +inline bool dglDoesSupportFXT1() +{ + return gGLState.suppFXT1; +} + +inline bool dglDoesSupportTexEnvAdd() +{ + return gGLState.suppTexEnvAdd; +} + +inline bool dglDoesSupportTexAnisotropy() +{ + return gGLState.suppTexAnisotropic; +} + +inline bool dglDoesSupportVertexBuffer() +{ + return false; +} + +inline GLfloat dglGetMaxAnisotropy() +{ + return gGLState.maxAnisotropy; +} + +inline GLint dglGetMaxTextureUnits() +{ + if (dglDoesSupportARBMultitexture()) + return gGLState.maxTextureUnits; + else + return 1; +} + +#endif diff --git a/platformMacCarb/platformMacCarb.h b/platformMacCarb/platformMacCarb.h new file mode 100644 index 0000000..1ca4ddd --- /dev/null +++ b/platformMacCarb/platformMacCarb.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMMACCARB_H_ +#define _PLATFORMMACCARB_H_ + +#if __APPLE__ +#include +#else +#include "platformMacCarb/macCarbHeaders.h" +#endif +#include + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +class MacCarbPlatState +{ +public: + GDHandle hDisplay; + WindowPtr appWindow; + AGLDrawable drawable; + + char appWindowTitle[256]; + bool quit; + + AGLPixelFormat fmt; + AGLContext ctx; + + short volRefNum; // application volume/drive reference number + long dirID; // application directory id + char absAppPath[2048]; // app path - make it big enough! + + S32 desktopBitsPixel; + S32 desktopWidth; + S32 desktopHeight; + U32 currentTime; + + MacCarbPlatState(); +}; + +extern MacCarbPlatState platState; + +extern bool QGL_EXT_Init(); + + +extern WindowPtr CreateOpenGLWindow( U32 width, U32 height, bool fullScreen ); +extern WindowPtr CreateCurtain( U32 width, U32 height ); + +U32 GetMilliseconds(); + +U8* str2p(const char *str); +U8* str2p(const char *str, U8 *dst_p); + +char* p2str(U8 *p); +char* p2str(U8 *p, char *dst_str); + +U8 TranslateOSKeyCode(U8 vcode); + +#endif //_PLATFORMMACCARB_H_ diff --git a/platformPPC/platformGL.h b/platformPPC/platformGL.h new file mode 100644 index 0000000..030f71c --- /dev/null +++ b/platformPPC/platformGL.h @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMGL_H_ +#define _PLATFORMGL_H_ + +#include +#include + + +bool QGL_EXT_Init(); + + +struct GLState +{ + bool suppARBMultitexture; + bool suppPackedPixels; + bool suppLockedArrays; + bool suppTextureEnvCombine; + bool suppVertexArrayRange; + bool suppFogCoord; + bool suppEdgeClamp; +}; + +extern GLState gGLState; +#define UNSIGNED_SHORT_5_6_5 0x8363 +#define UNSIGNED_SHORT_5_6_5_REV 0x8364 + +extern bool gOpenGLDisableCVA; +extern bool gOpenGLDisableTEC; +extern bool gOpenGLDisableARBMT; +extern bool gOpenGLDisableFC; + +inline bool dglDoesSupportCompiledVertexArray() +{ + return gGLState.suppLockedArrays && (gOpenGLDisableCVA == false); +} + +inline bool dglDoesSupportTextureEnvCombine() +{ + return gGLState.suppTextureEnvCombine && (gOpenGLDisableTEC == false); +} + +inline bool dglDoesSupportARBMultitexture() +{ + return gGLState.suppARBMultitexture && (gOpenGLDisableARBMT == false); +} + +inline bool dglDoesSupportVertexArrayRange() +{ + return gGLState.suppVertexArrayRange; +} + +inline bool dglDoesSupportFogCoord() +{ + return gGLState.suppFogCoord && (gOpenGLDisableFC == false); +} + +inline bool dglDoesSupportEdgeClamp() +{ + return gGLState.suppEdgeClamp; +} + + + + +// +// until Apple exposes some extensions we'll need to stub them here +// + +/* EXT_combine */ +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#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 +#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 + +/* EXT_fog_coord */ +#define GL_FOG_COORDINATE_SOURCE_EXT -1 +#define GL_FOG_COORDINATE_EXT -1 +#define GL_FRAGMENT_DEPTH_EXT -1 +#define GL_FOG_COORDINATE_ARRAY_EXT -1 + +/* EXT_texture_edge_clamp */ +#define GL_CLAMP_TO_EDGE_EXT 0x812F + +// stubbed unsupported extensions +inline void glFogCoordfEXT(GLfloat ) {} +inline void glFogCoordPointerEXT(GLenum, GLsizei, void *) {} + + + + + +#endif // _PLATFORMGL_H_ diff --git a/platformPPC/platformPPC.h b/platformPPC/platformPPC.h new file mode 100644 index 0000000..2ef81ee --- /dev/null +++ b/platformPPC/platformPPC.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMPPC_H_ +#define _PLATFORMPPC_H_ + + +//#if defined(__MWERKS__) +//# include +//#else +//# include +//# include +//#endif + +#include +#include +#include +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _PPCUTILS_H_ +#include "PlatformPPC/ppcUtils.h" +#endif + +struct PPCPlatState +{ + GDHandle hDisplay; + WindowPtr appWindow; + char appWindowTitle[255]; + bool quit; + + AGLPixelFormat fmt; + AGLContext ctx; + + S32 volRefNum; // current working volume/drive reference number + S32 dirID; // current working directory id + +// FILE *log_fp; +// HINSTANCE hinstOpenGL; +// HINSTANCE hinstGLU; +// HWND appWindow; +// HDC appDC; +// HINSTANCE appInstance; +// HGLRC hGLRC; + + S32 desktopBitsPixel; + S32 desktopWidth; + S32 desktopHeight; + U32 currentTime; + + PPCPlatState(); +}; + +U32 GetMilliseconds(); + +extern PPCPlatState ppcState; + +U8* str2p(const char *str); +U8* str2p(const char *str, U8 *dst_p); + +char* p2str(U8 *p); +char* p2str(U8 *p, char *dst_str); + + +#endif //_PLATFORMPPC_H_ diff --git a/platformPPC/ppcAudio.cc b/platformPPC/ppcAudio.cc new file mode 100644 index 0000000..4ac4aec --- /dev/null +++ b/platformPPC/ppcAudio.cc @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platformAudio.h" +//#include "winAudioDSound3.h" +//#include "winAudioDSound7.h" + +namespace Audio +{ + +//void installPlaybackDevice( PlaybackDevice *dev ); + + +//-------------------------------------- +void detect() +{ + // install devices in order of increasing audio quality + //installPlaybackDevice( DSound3SSDevice::create() ); + //installPlaybackDevice( DSound7HHDevice::create() ); +} + + +//-------------------------------------- +} // End namespace Audio diff --git a/platformPPC/ppcCPUInfo.cc b/platformPPC/ppcCPUInfo.cc new file mode 100644 index 0000000..4d7c4a1 --- /dev/null +++ b/platformPPC/ppcCPUInfo.cc @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include "console/console.h" +#include "Core/stringTable.h" +#include + +Platform::SystemInfo_struct Platform::SystemInfo; + + +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_PowerPC_G3; + Platform::SystemInfo.processor.name = StringTable->insert("Unknown PPC Compatible"); + Platform::SystemInfo.processor.mhz = 0; + Platform::SystemInfo.processor.properties = CPU_PROP_C; + + + 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"); + Con::printf(" "); +} diff --git a/platformPPC/ppcConsole.cc b/platformPPC/ppcConsole.cc new file mode 100644 index 0000000..5e84a59 --- /dev/null +++ b/platformPPC/ppcConsole.cc @@ -0,0 +1,231 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include "PlatformPPC/ppcConsole.h" +#include "Platform/event.h" + +WinConsole *WindowsConsole = NULL; + +static void consoleEnableCallback(const char *, const char *enabled) +{ + WindowsConsole->enable(dAtob(enabled)); +} + +void WinConsole::create() +{ + WindowsConsole = new WinConsole(); +} + +void WinConsole::destroy() +{ + delete WindowsConsole; + WindowsConsole = NULL; +} + +void WinConsole::enable(bool enabled) +{ + winConsoleEnabled = enabled; +// if(winConsoleEnabled) +// { +// AllocConsole(); +// const char *title = Con::getVariable("Con::WindowTitle"); +// if (title && *title) +// SetConsoleTitle(title); +// stdOut = GetStdHandle(STD_OUTPUT_HANDLE); +// stdIn = GetStdHandle(STD_INPUT_HANDLE); +// stdErr = GetStdHandle(STD_ERROR_HANDLE); +// +// printf("%s", Con::getVariable("Con::Prompt")); +// } +} + +WinConsole::WinConsole() +{ +// for (S32 iIndex = 0; iIndex < MAX_CMDS; iIndex ++) +// rgCmds[iIndex][0] = '\0'; +// +// iCmdIndex = 0; +// winConsoleEnabled = false; +// Con::addConsumer(this); +// inpos = 0; +// lineOutput = false; +// Con::addVariable("WinConsoleEnabled", consoleEnableCallback, "false"); +} + +void WinConsole::printf(const char *s, ...) +{ + s; +// static char buffer[512]; +// DWORD bytes; +// va_list args; +// va_start(args, s); +// vsprintf(buffer, s, args); +// WriteFile(stdOut, buffer, strlen(buffer), &bytes, NULL); +// FlushFileBuffers( stdOut ); +} + +void WinConsole::processConsoleLine(const char *consoleLine) +{ + 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[256]; +// 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 '\n': +// case '\r': +// outbuf[outpos++] = '\r'; +// outbuf[outpos++] = '\n'; +// +// inbuf[inpos] = 0; +// outbuf[outpos] = 0; +// printf("%s", outbuf); +// +// S32 eventSize; +// eventSize = ConsoleEventHeaderSize; +// +// dStrcpy(postEvent.data, inbuf); +// postEvent.size = eventSize + dStrlen(inbuf) + 1; +// GamePostEvent(postEvent); +// +// // 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/platformPPC/ppcConsole.h b/platformPPC/ppcConsole.h new file mode 100644 index 0000000..7729382 --- /dev/null +++ b/platformPPC/ppcConsole.h @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PCCCONSOLE_H_ +#define _PCCCONSOLE_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; + ConsoleEvent postEvent; + 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(); + void process(); + void enable(bool); + void processConsoleLine(const char *consoleLine); + static void create(); + static void destroy(); +}; + +extern WinConsole *WindowsConsole; + +#endif diff --git a/platformPPC/ppcFileio.cc b/platformPPC/ppcFileio.cc new file mode 100644 index 0000000..6f16830 --- /dev/null +++ b/platformPPC/ppcFileio.cc @@ -0,0 +1,636 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include "Core/fileio.h" +#include "Core/tVector.h" +#include "Core/stringTable.h" +#include "console/console.h" +#include +#include + +//-------------------------------------- Helper Functions +static void forwardslash(char *str) +{ + while(*str) + { + if(*str == '\\') + *str = ':'; + str++; + } +} + +static void toHostFilename(const char *str, char *dst) +{ + *dst++ = ':'; + while(*str) + { + if(*str == '\\' || *str == '/') + *dst++ = ':'; + else + *dst++ = *str; + str++; + } + *dst = 0; +} + + +//----------------------------------------------------------------------------- +// 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) +{ + char hostFilename[256]; + + AssertFatal(dStrlen(filename) <= 255, "File::open: Max Mac file length exceeded. MAX=255"); + AssertFatal(NULL != filename, "File::open: NULL filename"); + AssertWarn(NULL == handle, "File::open: handle already valid"); + + toHostFilename(filename, hostFilename); + + // Close the file if it was already open... + if (Closed != currentStatus) + close(); + + // create the appropriate type of file... + switch (openMode) + { + case Read: + handle = (void *)fopen(hostFilename, "rb"); + break; + case Write: + handle = (void *)fopen(hostFilename, "wb"); + break; + case ReadWrite: + handle = (void *)fopen(hostFilename, "ab+"); + break; + default: + AssertFatal(false, "File::open: bad access mode"); // impossible + } + + if (handle == NULL) // 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: + capability = U32(FileWrite); + break; + case ReadWrite: + capability = U32(FileRead) | U32(FileWrite); + break; + default: + AssertFatal(false, "File::open: bad access mode"); + } + + if (openMode == ReadWrite) + setPosition(0); + + 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 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 (Ok != currentStatus && EOS != currentStatus) + return currentStatus; + + U32 finalPos; + switch (absolutePos) + { + case true: // 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); + break; + case false: // relative position + AssertFatal((getPosition() >= (U32)abs(position) && 0 > position) || 0 <= position, "File::setPosition: negative relative position"); + // position beyond EOS is OK + fseek((FILE*)handle, position, SEEK_CUR); + finalPos = ftell((FILE*)handle); + } + + 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(handle != NULL, "File::getSize: invalid file handle"); + + if (Ok == currentStatus || EOS == currentStatus) + { + // this is not a very good way to do this + U32 pos = ftell((FILE*)handle); + fseek((FILE*)handle, 0, SEEK_END); + U32 size = ftell((FILE*)handle); + fseek((FILE*)handle, pos, SEEK_SET); + return size; + } + 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(handle != NULL, "File::flush: invalid file handle"); + AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file"); + + if (fflush((FILE*)handle) == EOF) + 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 (handle != NULL) + { + if (fclose((FILE*)handle) == EOF) + return setStatus(); // unsuccessful + } + 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() +{ +#pragma message("todo: File::setStatus") + + switch (errno) + { +/* + case EACCESS: // permission denied + return currentStatus = IOError; + case EBADF: // Bad File Pointer + errno++; + case EINVAL: // Invalid argument + errno++; + case ENOENT: // file not found + errno++; +*/ + default: + return currentStatus = UnknownError; + } + + 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(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; + else + { + U32 lastBytes; + U32 *bytes = (NULL == bytesRead) ? &lastBytes : bytesRead; + if (fread(dst, size, 1, (FILE*)handle) != 1) + { // fread onlu reports the number of chunks read not bytes + // so we don't know exactly how much was read + *bytes = getPosition(); + return currentStatus = EOS; // end of stream + } + else + { + *bytes = size; + return currentStatus = Ok; // 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(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; + else + { + U32 lastBytes; + U32 *bytes = (NULL == bytesWritten) ? &lastBytes : bytesWritten; + if (fwrite(src, size, 1, (FILE*)handle) != 1) + { // fwrite onlu reports the number of chunks written not bytes + // so we don't know exactly how much was written + *bytes = getPosition(); + return setStatus(); + } + else + { + *bytes = size; + return currentStatus = Ok; // success! + } + } +} + +//----------------------------------------------------------------------------- +// Self-explanatory. +//----------------------------------------------------------------------------- +bool File::hasCapability(Capability cap) const +{ + return (0 != (U32(cap) & capability)); +} + +S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b) +{ + if(a.time > b.time) + return 1; + if(a.time < b.time) + return -1; + return 0; +} + + +static bool _recurseDumpPath(const char* in_pBasePath, const char* in_pCurPath, S32 dirID, Vector& out_rFileVector) +{ + char curPath[1024]; + char basePath[1024]; + char scratchBuf[1024]; + + if(in_pCurPath) + dStrcpy(curPath, in_pCurPath); + else + curPath[0] = 0; + + dStrcpy(basePath, in_pBasePath); + in_pBasePath = basePath; + + CInfoPBRec cinfo; + Str63 nameField; + OSErr result; + S32 index = 1; + + do + { // setup a catalog information request structure + cinfo.hFileInfo.ioVRefNum = ppcState.volRefNum; // volume ID to search in + cinfo.hFileInfo.ioDirID = dirID; // directory ID to search in + cinfo.hFileInfo.ioFDirIndex = index++; // specify which entry you are interested in + cinfo.hFileInfo.ioNamePtr = nameField; // supply a buffer to store the name + + result = PBGetCatInfoSync(&cinfo); + if (result == noErr) + { + if (cinfo.dirInfo.ioFlAttrib & ioDirMask) + { // it's a directory + char *dirname = p2str(cinfo.hFileInfo.ioNamePtr); + scratchBuf[0] = '\0'; + if (curPath[0] != '\0') { + dStrcpy(scratchBuf, curPath); + dStrcat(scratchBuf, "/"); + } + dStrcat(scratchBuf, dirname); + + _recurseDumpPath(basePath, scratchBuf, cinfo.hFileInfo.ioDirID, out_rFileVector); + } + else + { // it's a file + char *filename = p2str(cinfo.hFileInfo.ioNamePtr); + + out_rFileVector.increment(); + Platform::FileInfo& rInfo = out_rFileVector.last(); + + if (curPath[0] != '\0') { + dSprintf(scratchBuf, sizeof(scratchBuf), "%s/%s", basePath, curPath); + rInfo.pFullPath = StringTable->insert(scratchBuf); + rInfo.pVirtPath = StringTable->insert(curPath); + } else { + rInfo.pFullPath = StringTable->insert(basePath); + rInfo.pVirtPath = NULL; + } + + rInfo.pFileName = StringTable->insert(filename); + rInfo.fileSize = cinfo.hFileInfo.ioFlLgLen; + //rInfo.createTime.time = cinfo.hFileInfo.ioFlCrDat; + //rInfo.modifyTime.time = cinfo.hFileInfo.ioFlMdDat; + } + } + }while (result == noErr); + + return true; +} + +//-------------------------------------- +bool Platform::getFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime) +{ + return false; +/* + WIN32_FIND_DATA findData; + HANDLE h = FindFirstFile(filePath, &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) +{ + char pathBuf[1024]; + char dirBuf[256]; + const char *dir; + U32 pathLen = 0; + S32 parentDirID = ppcState.dirID; + + pathBuf[0] = 0; + while((dir = dStrchr(file, '/')) != NULL) + { + U32 len = dir-file; + dStrncpy(dirBuf, file, len); + dirBuf[len] = 0; + + dStrncpy(pathBuf + pathLen, file, dir - file); + pathBuf[pathLen + dir-file] = 0; + + // does directory/name already exist? + CInfoPBRec cinfo; + Str63 nameField; + + cinfo.hFileInfo.ioVRefNum = ppcState.volRefNum; // volume ID to search in + cinfo.hFileInfo.ioDirID = parentDirID; // directory ID to search in + cinfo.hFileInfo.ioFDirIndex = 0; // get info on ioNamePtr + cinfo.hFileInfo.ioNamePtr = nameField; // supply a buffer with name + str2p(dirBuf, nameField); + OSErr err = PBGetCatInfoSync(&cinfo); + switch(err) + { + case noErr: + if (cinfo.dirInfo.ioFlAttrib & ioDirMask) + { // it's a directory + parentDirID = cinfo.hFileInfo.ioDirID; + } + else + { // the name existed and it was NOT a directory + Con::printf("CreateDirectory(%s) - failed", pathBuf); + return false; + } + break; + + case fnfErr: + { // the name did not exist so create the directory + long newId; + OSErr err = DirCreate(ppcState.volRefNum, parentDirID, str2p(dirBuf), &newId); + if (err != noErr) + { + Con::printf("CreateDirectory(%s) - failed", pathBuf); + return false; + } + parentDirID = newId; + } + break; + } + + file = dir + 1; + pathLen += len; + pathBuf[pathLen++] = '/'; + pathBuf[pathLen] = 0; + Con::printf("CreateDirectory(%s) - Succeeded", pathBuf); + + } + return true; +} + + +//-------------------------------------- +bool Platform::dumpPath(const char *in_pBasePath, Vector& out_rFileVector) +{ + // for now we can only search directories in the apps current workinng directory + // specifying another drive, path or sub path (base/art) will not work. + S32 dirID = ppcState.dirID; + + if (in_pBasePath) + { + CInfoPBRec cinfo; + Str63 nameField; + OSErr result; + + cinfo.hFileInfo.ioVRefNum = ppcState.volRefNum; // volume ID to search in + cinfo.hFileInfo.ioDirID = dirID; // directory ID to search in + cinfo.hFileInfo.ioFDirIndex = 0; // get info on ioNamePtr + cinfo.hFileInfo.ioNamePtr = nameField; // supply a buffer with name + str2p(in_pBasePath, nameField); + result = PBGetCatInfoSync(&cinfo); + if (result == noErr) + { + if (cinfo.dirInfo.ioFlAttrib & ioDirMask) + { // it's a directory + char *dirname = p2str(cinfo.hFileInfo.ioNamePtr); + return _recurseDumpPath(in_pBasePath, NULL, cinfo.hFileInfo.ioDirID, out_rFileVector); + } + return false; // not a directory + } + } + return _recurseDumpPath(in_pBasePath, NULL, dirID, out_rFileVector); +} + + +//-------------------------------------- +void Platform::getCurrentDirectory(char *dirBuf, const U32 in_bufferSize) +{ + dirBuf, in_bufferSize; + #pragma message("todo: Platform::getCurrentDirectory") +// GetCurrentDirectory(in_bufferSize - 1, dirBuf); +// forwardslash(dirBuf); + + *dirBuf = '\0'; + +/* +FUNCTION GetFullPath (DirID: LongInt; vRefnum: Integer): Str255; +VAR + myPB: CInfoPBRec; {parameter block for PBGetCatInfo} + dirName: Str255; {a directory name} + fullPath: Str255; {full pathname being constructed} + myErr: OSErr; +BEGIN + fullPath := ''; {initialize full pathname} + myPB.ioNamePtr := @dirName; + myPB.ioVRefNum := vRefNum; {indicate target volume} + myPB.ioDrParID := DirId; {initialize parent directory ID} + myPB.ioFDirIndex := -1; {get info about a directory} + {Get name of each parent directory, up to root directory.} + REPEAT + myPB.ioDrDirID := myPB.ioDrParID; + myErr := PBGetCatInfo(@myPB, FALSE); + IF gHaveAUX THEN + + BEGIN + IF dirName[1] <> '/' THEN + dirName := concat(dirName, '/'); + END + ELSE + dirName := concat(dirName, ':'); + fullPath := concat(dirName, fullPath); + UNTIL myPB.ioDrDirID = fsRtDirID; + GetFullPath := fullPath; {return full pathname} +END; + +Note that GetFullPath uses either a slash (/) or a colon (:) to separate names in the +full path, depending on whether A/UX is running or not. The GetFullPath function reads +the value of the global variable gHaveAUX to determine whether A/UX is running; your +application must initialize this variable (preferably by calling the Gestalt function) +before it calls GetFullPath. +The GetFullPath function defined in Listing 2-5 returns a result of type Str255, which +limits the full pathname to 255 characters. An actual full pathname, however, might +exceed 255 characters. A volume name can be up to 27 characters, and each directory name +can be up to 31 characters. If the average volume and directory name is about 20 +characters long, GetFullPath can handle files located only about 12 levels deep. If the +length of the average directory name is closer to the maximum, GetFullPath provides a +full pathname for files located only about 8 levels deep. If necessary, you can overcome +this limitation by rewriting GetFullPath to return a handle to the full pathname; the +algorithm for ascending the directory hierarchy using PBGetCatInfo will still work, however. +*/ + + + + +} + diff --git a/platformPPC/ppcFont.cc b/platformPPC/ppcFont.cc new file mode 100644 index 0000000..2ee149e --- /dev/null +++ b/platformPPC/ppcFont.cc @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include "dgl/gFont.h" +#include "dgl/gBitmap.h" +#include "Math/mRect.h" + +// static HDC fontHDC = NULL; +// static HBITMAP fontBMP = NULL; + +// void createFontInit(void); +// void createFontShutdown(void); +// void CopyCharToBitmap(GBitmap *pDstBMP, HDC hSrcHDC, const RectI &r); +// +void createFontInit() +{ +// 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; +// } +// } +// } + +GFont *createFont(const char *name, S32 size) +{ + return(NULL); + +// if(!name) +// return NULL; +// if(size < 1) +// return NULL; +// +// +// HFONT hNewFont = CreateFont(size,0,0,0,0,0,0,0,0,0,0,0,0,name); +// if(!hNewFont) +// return NULL; +// +// GFont *retFont = new GFont; +// GBitmap scratchPad(256, 256); +// +// TEXTMETRIC textMetric; +// COLORREF backgroundColorRef = RGB( 0, 0, 0); +// COLORREF foregroundColorRef = RGB(255, 255, 255); +// +// SelectObject(fontHDC, fontBMP); +// SelectObject(fontHDC, hNewFont); +// SetBkColor(fontHDC, backgroundColorRef); +// SetTextColor(fontHDC, foregroundColorRef); +// GetTextMetrics(fontHDC, &textMetric); +// +// RectI clip; +// for(S32 i = 32; i < 256; i++) +// { +// SIZE size; +// char buf[4]; +// buf[0] = ' '; +// buf[1] = (char)i; +// buf[2] = ' '; +// buf[3] = '\0'; +// +// TextOut(fontHDC, 0, 0, buf, 3); +// GetTextExtentPoint32(fontHDC, buf, 3, &size); +// RectI r(0, 0, size.cx + 1, size.cy + 1); +// CopyCharToBitmap(&scratchPad, fontHDC, r); +// +// // now clip the raw bitmap so we don't waste any space +// clip.point.x = 256; +// clip.point.y = 256; +// clip.extent.x = -257; +// clip.extent.y = -257; +// +// S32 row, col; +// bool found = FALSE; +// Point2I upperL(256, 256); +// Point2I lowerR(-1, -1); +// +// for (row = 0; row < size.cy; row++) +// for (col = 0; col < size.cx; col++) +// { +// if (*scratchPad.getAddress(col, row) == 255) +// { +// found = TRUE; +// if (upperL.x > col) +// upperL.x = col; +// if (upperL.y > row) +// upperL.y = row; +// if (lowerR.x < col) +// lowerR.x = col; +// if (lowerR.y < row) +// lowerR.y = row; +// } +// } +// +// if (!found) +// { +// // bitmap is blank, probably a space, leave the width alone +// // but truncate height to one line +// clip.point.x = 0; +// clip.point.y = 0x7fffffff; +// clip.extent.x = size.cx / 3 + 1; +// clip.extent.y = 0; +// } +// else +// { +// clip.point = upperL; +// clip.point.x -= 1; +// if (clip.point.x < 0) +// clip.point.x = 0; +// clip.point.y -= 1; +// if (clip.point.y < 0) +// clip.point.y = 0; +// clip.extent.set(lowerR.x - upperL.x + 2, lowerR.y - upperL.y + 2); +// } +// retFont->insertBitmap(i, &scratchPad, &clip); +// } +// retFont->pack(textMetric.tmHeight, textMetric.tmAscent); +// //clean up local vars +// DeleteObject(hNewFont); +// +// return retFont; +} diff --git a/platformPPC/ppcGL.cc b/platformPPC/ppcGL.cc new file mode 100644 index 0000000..87df332 --- /dev/null +++ b/platformPPC/ppcGL.cc @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformGL.h" +#include "PlatformPPC/platformPPC.h" +#include +#include "console/console.h" + +GLState gGLState; + +bool gOpenGLDisableCVA = false; +bool gOpenGLDisableTEC = false; +bool gOpenGLDisableARBMT = false; +bool gOpenGLDisableFC = true; + + +// #define GL_EXT_abgr 1 +// #define GL_EXT_blend_color 1 +// #define GL_EXT_blend_minmax 1 +// #define GL_EXT_blend_subtract 1 +// #define GL_EXT_compiled_vertex_array 1 +// #define GL_ARB_multitexture 1 +// #define GL_APPLE_specular_vector 1 +// #define GL_APPLE_transform_hint 1 + + + + +bool QGL_EXT_Init( ) +{ + // Load extensions... + // + const char* pExtString = reinterpret_cast(glGetString(GL_EXTENSIONS)); + + // EXT_compiled_vertex_array + if (GL_EXT_compiled_vertex_array) //pExtString && dStrstr(pExtString, (const char*)"GL_EXT_compiled_vertex_array") != NULL) + { + //glLockArraysEXT = dllLockArraysEXT = (glLockArrays_t) qwglGetProcAddress("glLockArraysEXT"); + //glUnlockArraysEXT = dllUnlockArraysEXT = (glUnlockArrays_t) qwglGetProcAddress("glUnlockArraysEXT"); + gGLState.suppLockedArrays = true; + } else { + //glLockArraysEXT = dllLockArraysEXT = NULL; + //glUnlockArraysEXT = dllUnlockArraysEXT = NULL; + gGLState.suppLockedArrays = false; + } + + // ARB_multitexture + if (GL_ARB_multitexture) //pExtString && dStrstr(pExtString, (const char*)"GL_ARB_multitexture") != NULL) + { + //glActiveTextureARB = dllActiveTextureARB = (glActiveTextureARB_t) qwglGetProcAddress("glActiveTextureARB"); + //glClientActiveTextureARB = dllClientActiveTextureARB = (glClientActiveTextureARB_t) qwglGetProcAddress("glClientActiveTextureARB"); + //glMultiTexCoord2fARB = dllMultiTexCoord2fARB = (glMultiTexCoord2fARB_t) qwglGetProcAddress("glMultiTexCoord2fARB"); + //glMultiTexCoord2fvARB = dllMultiTexCoord2fvARB = (glMultiTexCoord2fvARB_t) qwglGetProcAddress("glMultiTexCoord2fvARB"); + gGLState.suppARBMultitexture = true; + } else { + //glActiveTextureARB = dllActiveTextureARB = NULL; + //glClientActiveTextureARB = dllClientActiveTextureARB = NULL; + //glMultiTexCoord2fARB = dllMultiTexCoord2fARB = NULL; + //glMultiTexCoord2fvARB = dllMultiTexCoord2fvARB = NULL; + gGLState.suppARBMultitexture = false; + } + + // NV_vertex_array_range + if (false) //pExtString && dStrstr(pExtString, (const char*)"GL_NV_vertex_array_range") != NULL) + { + //glVertexArrayRangeNV = dllVertexArrayRangeNV = (glVertexArrayRange_t) qwglGetProcAddress("glVertexArrayRangeNV"); + //glFlushVertexArrayRangeNV = dllFlushVertexArrayRangeNV = (glFlushVertexArrayRange_t) qwglGetProcAddress("glFlushVertexArrayRangeNV"); + gGLState.suppVertexArrayRange = true; + } else { + //glVertexArrayRangeNV = dllVertexArrayRangeNV = NULL; + //glFlushVertexArrayRangeNV = dllFlushVertexArrayRangeNV = NULL; + gGLState.suppVertexArrayRange = false; + } + + // EXT_fog_coord + if (false) //pExtString && dStrstr(pExtString, (const char*)"GL_EXT_fog_coord") != NULL) + { + //glFogCoordfEXT = dllFogCoordfEXT = (glFogCoordf_t) qwglGetProcAddress("glFogCoordfEXT"); + //glFogCoordPointerEXT = dllFogCoordPointerEXT = (glFogCoordPointer_t) qwglGetProcAddress("glFogCoordPointerEXT"); + gGLState.suppFogCoord = true; + } else { + //glFogCoordfEXT = dllFogCoordfEXT = NULL; + //glFogCoordPointerEXT = dllFogCoordPointerEXT = NULL; + gGLState.suppFogCoord = false; + } + + // Binary states, i.e., no supporting functions + // EXT_packed_pixels + // EXT_texture_env_combine + // + gGLState.suppPackedPixels = false; //pExtString? (dStrstr(pExtString, (const char*)"GL_EXT_packed_pixels") != NULL) : false; + gGLState.suppTextureEnvCombine = false; //pExtString? (dStrstr(pExtString, (const char*)"GL_EXT_texture_env_combine") != NULL) : false; + + Con::printf("OpenGL Init: Enabled Extensions"); + if (gGLState.suppARBMultitexture) Con::printf(" ARB_multitexture"); + 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"); + + Con::warnf(ConsoleLogEntry::General, "OpenGL Init: Disabled Extensions"); + if (!gGLState.suppARBMultitexture) Con::warnf(ConsoleLogEntry::General, " ARB_multitexture"); + if (!gGLState.suppLockedArrays) Con::warnf(ConsoleLogEntry::General, " EXT_compiled_vertex_array"); + if (!gGLState.suppVertexArrayRange) Con::warnf(ConsoleLogEntry::General, " NV_vertex_array_range"); + if (!gGLState.suppTextureEnvCombine) Con::warnf(ConsoleLogEntry::General, " EXT_texture_env_combine"); + if (!gGLState.suppPackedPixels) Con::warnf(ConsoleLogEntry::General, " EXT_packed_pixels"); + if (!gGLState.suppFogCoord) Con::warnf(ConsoleLogEntry::General, " EXT_fog_coord"); + Con::printf(""); + + return true; +} diff --git a/platformPPC/ppcInput.cc b/platformPPC/ppcInput.cc new file mode 100644 index 0000000..38878df --- /dev/null +++ b/platformPPC/ppcInput.cc @@ -0,0 +1,305 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include "Platform/platformInput.h" +#include "ppcInput.h" +#include "Platform/event.h" +#include "console/console.h" + + +// Static class variables: +InputManager* Input::smManager; + + +//-------------------------------------------------------------------------- +void Input::init() +{ + Con::printf( "Input Init:" ); + smManager = NULL; +} + + +//-------------------------------------------------------------------------- +static U8 VcodeRemap[256] = +{ +KEY_A, // 0x00 +KEY_A, // 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 +KEY_Y, // 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 +0, // 0x18 +KEY_9, // 0x19 +KEY_7, // 0x1A +0, // 0x1B +KEY_8, // 0x1C +KEY_0, // 0x1D +0, // 0x1E +KEY_O, // 0x1F +KEY_U, // 0x20 +0, // 0x21 +KEY_I, // 0x22 +KEY_P, // 0x23 +KEY_RETURN, // 0x24 +KEY_L, // 0x25 +KEY_J, // 0x26 +0, // 0x27 +KEY_K, // 0x28 +0, // 0x29 +0, // 0x2A +0, // 0x2B +0, // 0x2C +KEY_N, // 0x2D +KEY_M, // 0x2E +0, // 0x2F + +KEY_TAB, // 0x30 +0, // 0x31 +0, // 0x32 +KEY_BACKSPACE, // 0x33 +0, // 0x34 +KEY_ESCAPE, // 0x35 +0, // 0x36 +KEY_ALT, // 0x37 +KEY_LSHIFT, // 0x38 +KEY_CAPSLOCK, // 0x39 +0, // 0x3A +KEY_CONTROL, // 0x3B +KEY_RSHIFT, // 0x3C +0, // 0x3D +0, // 0x3E +0, // 0x3F +0, // 0x40 + +KEY_MULTIPLY, // 0x41 +0, // 0x42 +0, // 0x43 +0, // 0x44 +KEY_ADD, // 0x45 +0, // 0x46 +KEY_NUMLOCK, // 0x47 +0, // 0x48 +0, // 0x49 +0, // 0x4A +0, // 0x4B +KEY_RETURN, // 0x4C +KEY_DIVIDE, // 0x4D +KEY_SUBTRACT, // 0x4E +0, // 0x4F +0, // 0x50 +0, // 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_PRINT, // 0x69 +0, // 0x6A +KEY_SCROLLLOCK, // 0x6B +0, // 0x6C +KEY_F10, // 0x6D +0, // 0x6E +KEY_F12, // 0x6F +0, // 0x70 +0, // 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 +0, // 0x7F +0, // 0x80 +0, // 0x81 +0, // 0x82 +0, // 0x83 +0, // 0x84 +0, // 0x85 +0, // 0x86 +0, // 0x87 +0, // 0x88 +0, // 0x89 +0, // 0x8A +0, // 0x8B +0, // 0x8C +0, // 0x8D +0, // 0x8E +0, // 0x8F + +0, // 0x90 +0, // 0x91 +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 + +0, // 0xA0 +0, // 0xA1 +0, // 0xA2 +0, // 0xA3 +0, // 0xA4 +0, // 0xA5 +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 +0, // 0xBA +0, // 0xBB +0, // 0xBC +0, // 0xBD +0, // 0xBE +0, // 0xBF +0, // 0xC0 +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 +0, // 0xDB +0, // 0xDC +0, // 0xDD +0, // 0xDE +0, // 0xDF +0, // 0xE0 +0, // 0xE1 +0, // 0xE2 +0, // 0xE3 +0, // 0xE4 + +0, // 0xE5 + +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 +0, // 0xF7 +0, // 0xF8 +0, // 0xF9 +0, // 0xFA +0, // 0xFB +0, // 0xFC +0, // 0xFD +0, // 0xFE +0 // 0xFF +}; + + +U8 TranslateOSKeyCode(U8 vcode) +{ + return VcodeRemap[vcode]; +} + + diff --git a/platformPPC/ppcMath.cc b/platformPPC/ppcMath.cc new file mode 100644 index 0000000..adf255a --- /dev/null +++ b/platformPPC/ppcMath.cc @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platform.h" +#include "console/console.h" +#include "Math/mMath.h" + + +extern void mInstallLibrary_C(); + + +//-------------------------------------- +static void cMathInit(SimObject *obj, S32 argc, const char **argv) +{ + obj; + argc; + argv; + 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; + } + Con::printf("Error: MathInit(): ignoring unknown math extension '%s'", *argv); + } + Math::init(properties); +} + + + +//------------------------------------------------------------------------------ +void Math::init(U32 properties) +{ + Con::addCommand( "MathInit", cMathInit, "MathInit(detect|C|FPU|MMX|3DNOW|SSE|...)", 1, 10); + + 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 (properties & CPU_PROP_FPU) + { + Con::printf(" Installing FPU extensions"); + } + Con::printf(" "); +} + + diff --git a/platformPPC/ppcMemory.cc b/platformPPC/ppcMemory.cc new file mode 100644 index 0000000..6d1d393 --- /dev/null +++ b/platformPPC/ppcMemory.cc @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include +#include + +//-------------------------------------- +#ifdef new +#undef new +#endif + +void* FN_CDECL operator new(dsize_t, void* ptr) +{ + return (ptr); +} + + +//-------------------------------------- +void* dRealMalloc(dsize_t in_size) +{ + return malloc(in_size); +} + + +//-------------------------------------- +void dRealFree(void* in_pFree) +{ + free(in_pFree); +} + + +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)); +} diff --git a/platformPPC/ppcNet.cc b/platformPPC/ppcNet.cc new file mode 100644 index 0000000..a77f792 --- /dev/null +++ b/platformPPC/ppcNet.cc @@ -0,0 +1,787 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include +#include + +#include "PlatformPPC/platformPPC.h" +#include "Platform/platform.h" +#include "Platform/event.h" +//#include +//#include +#include "console/console.h" + + +// stubbed stuff to get this to compile +typedef void* HANDLE; +typedef void* SOCKET; +#define INVALID_SOCKET NULL +typedef InetAddress SOCKADDR_IN; +typedef InetAddress SOCKADDR_IPX; + + +struct Connection +{ + NetSocket socket; + S32 state; + S32 prevState; + bool listenConnection; + Connection *nextConnection; + Connection *nextInTable; + HANDLE connectThreadHandle; + U32 tag; +}; + +struct Status +{ + bool mSignal; + OSErr mErr; + void *mData; + Status() + { + mSignal = false; + mErr = kOTNoError; + mData = NULL; + } +}; + + +static Net::Error getLastError(); +static S32 defaultPort = 28000; +static S32 netPort = 0; +//static SOCKET ipxSocket = INVALID_SOCKET; +//static SOCKET udpSocket = INVALID_SOCKET; +static bool sendPackets = true; +static U32 nextConnectionId = 1; +static U32 nextAcceptId = 1000000000; +static Connection *connectionList = NULL; +enum { + ConnectionTableSize = 256, + ConnectionTableMask = 0xFF +}; +static Connection *connectionTable[ConnectionTableSize] = { 0, }; + + +// kOTAnyInetAddress +static U32 OTReferenceCount = 0; +static TEndpoint *EndPoint = NULL; +static OSErr err = kOTNoError; + + +static pascal void EventProc( void *_pSession, OTEventCode event, OTResult result, void *cookie ) +{ + Status *status = (Status *) _pSession; + + switch ( event ) + { + case T_DATA: + //pSess->m_dataToRead = 1; + break; + + case T_BINDCOMPLETE: + status->mErr = (OSErr) result; + status->mSignal = true; + break; + + case T_OPENCOMPLETE: + status->mErr = (OSErr) result; + status->mData = cookie; // the EndPoint + status->mSignal = true; + break; + } +} + + +void Net::setJournaling(bool jrn) +{ + sendPackets = !jrn; +} + +bool Net::init() +{ +// WSADATA stWSAData; +// return !WSAStartup(0x0101, &stWSAData); + if ( OTReferenceCount == 0 ) + if (InitOpenTransport() != kOTNoError) + return false; + + // successfully opened Open Transport, inncrement the reference count + OTReferenceCount++; + return true; +} + +void Net::shutdown() +{ +// while(connectionList) +// Net::closeConnectTo(connectionList->tag); +// +// closePort(); +// WSACleanup(); + + while(connectionList) + Net::closeConnectTo(connectionList->tag); + + closePort(); + if ( OTReferenceCount <= 0 ) + return; + + // close Open Transport if there are no more references to it + OTReferenceCount--; + if ( OTReferenceCount == 0 ) + CloseOpenTransport(); +} + +static void netToIPSocketAddress(const NetAddress *address, SOCKADDR_IN *sockAddr) +{ + address; + dMemset(sockAddr, 0, sizeof(SOCKADDR_IN)); +// sockAddr->sin_family = AF_INET; +// sockAddr->sin_port = htons(address->port); +// sockAddr->sin_addr.s_net = address->netNum[0]; +// sockAddr->sin_addr.s_host = address->netNum[1]; +// sockAddr->sin_addr.s_lh = address->netNum[2]; +// sockAddr->sin_addr.s_impno = address->netNum[3]; +} + +static void IPSocketToNetAddress(const SOCKADDR_IN *sockAddr, NetAddress *address) +{ + sockAddr, address; +// address->type = NetAddress::IPAddress; +// address->port = htons(sockAddr->sin_port); +// 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; +} + +static void netToIPXSocketAddress(const NetAddress *address, SOCKADDR_IPX *sockAddr) +{ + sockAddr, address; +// dMemset(sockAddr, 0, sizeof(SOCKADDR_IPX)); +// sockAddr->sa_family = AF_INET; +// sockAddr->sa_socket = htons(address->port); +// sockAddr->sa_netnum[0] = address->netNum[0]; +// sockAddr->sa_netnum[1] = address->netNum[1]; +// sockAddr->sa_netnum[2] = address->netNum[2]; +// sockAddr->sa_netnum[3] = address->netNum[3]; +// sockAddr->sa_nodenum[0] = address->nodeNum[0]; +// sockAddr->sa_nodenum[1] = address->nodeNum[1]; +// sockAddr->sa_nodenum[2] = address->nodeNum[2]; +// sockAddr->sa_nodenum[3] = address->nodeNum[3]; +// sockAddr->sa_nodenum[4] = address->nodeNum[4]; +// sockAddr->sa_nodenum[5] = address->nodeNum[5]; +} + +static void IPXSocketToNetAddress(const SOCKADDR_IPX *sockAddr, NetAddress *address) +{ + sockAddr, address; +// address->type = NetAddress::IPXAddress; +// address->port = htons(sockAddr->sa_socket); +// address->netNum[0] = sockAddr->sa_netnum[0] ; +// address->netNum[1] = sockAddr->sa_netnum[1] ; +// address->netNum[2] = sockAddr->sa_netnum[2] ; +// address->netNum[3] = sockAddr->sa_netnum[3] ; +// address->nodeNum[0] = sockAddr->sa_nodenum[0]; +// address->nodeNum[1] = sockAddr->sa_nodenum[1]; +// address->nodeNum[2] = sockAddr->sa_nodenum[2]; +// address->nodeNum[3] = sockAddr->sa_nodenum[3]; +// address->nodeNum[4] = sockAddr->sa_nodenum[4]; +// address->nodeNum[5] = sockAddr->sa_nodenum[5]; +} + +//DWORD WINAPI connectThreadFunction(LPVOID param) +//{ +// Connection *con = (Connection *) param; +// con; +// return 0; +//} + +static Connection *newConnection(U32 id) +{ + Connection *conn = new Connection; + conn->nextConnection = connectionList; + conn->tag = id; + connectionList = conn; + conn->nextInTable = connectionTable[conn->tag & ConnectionTableMask]; + connectionTable[conn->tag & ConnectionTableMask] = conn; + return conn; +} + +U32 Net::openListenPort(U16 port) +{ + nextConnectionId++; + if(!sendPackets) + return nextConnectionId; + + Connection *conn = newConnection(nextConnectionId); + + conn->listenConnection = true; + + conn->socket = openSocket(); + bind(conn->socket, port); + listen(conn->socket, 4); + setBlocking(conn->socket, false); + return conn->tag; +} + +U32 Net::openConnectTo(U16 port, const NetAddress *address) +{ + port; + + nextConnectionId++; + if(!sendPackets) + return nextConnectionId; + + Connection *conn = newConnection(nextConnectionId); + + conn->listenConnection = false; + conn->prevState = ConnectedNotifyEvent::DNSResolve; + conn->state = ConnectedNotifyEvent::DNSResolve; + + conn->socket = openSocket(); + connect(conn->socket, address); + setBlocking(conn->socket, false); + +//threadHandle = CreateThread(NULL, 0, connectThreadFunction, (LPVOID) conn, 0, &threadId); + + +//CloseHandle(threadHandle); + conn->state = ConnectedNotifyEvent::Connected; + return conn->tag; +} + +void Net::processConnected() +{ + Connection **walk = &connectionList; + Connection *con; + while((con = *walk) != NULL) + { + bool del = false; + if(con->listenConnection) + { + NetSocket newSocket; + ConnectedAcceptEvent event; + newSocket = accept(con->socket, &event.address); + + if(newSocket != InvalidSocket) + { + Connection *nc = newConnection(nextAcceptId++); + nc->listenConnection = false; + nc->prevState = ConnectedNotifyEvent::Connected; + nc->state = ConnectedNotifyEvent::Connected; + nc->socket = newSocket; + setBlocking(nc->socket, false); + + event.portTag = con->tag; + event.connectionTag = nc->tag; + GamePostEvent(event); + } + walk = &con->nextConnection; + } + else + { + if(con->state != con->prevState) + { + ConnectedNotifyEvent event; + event.tag = con->tag; + event.state = con->state; + GamePostEvent(event); + con->prevState = con->state; + } + if(con->state == ConnectedNotifyEvent::Connected) + { + ConnectedReceiveEvent event; + Net::Error err; + S32 bytesRead; + event.tag = con->tag; + + do { +// err = recv(con->socket, event.data, MaxPacketDataSize, &bytesRead); + if(err == NoError && bytesRead != 0) + { + event.size = ConnectedReceiveEventHeaderSize + bytesRead; + GamePostEvent(event); + } + else if(err != WouldBlock) + { + // bad news... this disconnected + ConnectedNotifyEvent event; + event.tag = con->tag; + event.state = ConnectedNotifyEvent::Disconnected; + GamePostEvent(event); + del = true; + } + } + while(err == NoError && bytesRead != 0); + } + if(del) + { + *walk = con->nextConnection; + closeSocket(con->socket); + for(Connection **tbWalk = &connectionTable[con->tag & ConnectionTableMask]; *tbWalk != NULL; tbWalk = &(*tbWalk)->nextInTable) + { + Connection *dc = *tbWalk; + if(dc->tag == con->tag) + { + *tbWalk = dc->nextInTable; + break; + } + } + delete con; + } + else + walk = &con->nextConnection; + } + } +} + +void Net::sendTo(U32 tag, const U8 *buffer, S32 bufferSize) +{ + Connection *walk; + for(walk = connectionTable[tag & ConnectionTableMask]; walk != NULL; walk = walk->nextConnection) + if(walk->tag == tag) + break; + if(walk) + send(walk->socket, buffer, bufferSize); +} + +void Net::closeConnectTo(U32 tag) +{ + Connection **walk; + for(walk = &connectionList; *walk != NULL; walk = &(*walk)->nextConnection) + { + Connection *con = *walk; + if(con->tag == tag) + { + *walk = con->nextConnection; + closeSocket(con->socket); + break; + } + } + for(walk = &connectionTable[tag & ConnectionTableMask]; *walk != NULL; walk = &(*walk)->nextInTable) + { + Connection *con = *walk; + if(con->tag == tag) + { + *walk = con->nextInTable; + delete con; + return; + } + } +} + +bool Net::openPort(S32 port) +{ +// if(udpSocket != INVALID_SOCKET) +// closesocket(udpSocket); +// if(ipxSocket != INVALID_SOCKET) +// closesocket(ipxSocket); +// +// udpSocket = socket(AF_INET, SOCK_DGRAM, 0); +// ipxSocket = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX); +// +// if(udpSocket != INVALID_SOCKET) +// { +// 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 +// { +// closesocket(udpSocket); +// udpSocket = INVALID_SOCKET; +// Con::printf("Unable to initialize UDP - error %d", error); +// } +// } +// if(ipxSocket != INVALID_SOCKET) +// { +// Net::Error error = NoError; +// SOCKADDR_IPX ipxAddress; +// memset((char *)&ipxAddress, 0, sizeof(ipxAddress)); +// ipxAddress.sa_family = AF_IPX; +// ipxAddress.sa_socket = htons(port); +// S32 err = ::bind(ipxSocket, (PSOCKADDR) &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 +// { +// closesocket(ipxSocket); +// ipxSocket = INVALID_SOCKET; +// Con::printf("Unable to initialize IPX - error %d", error); +// } +// } + netPort = port; +// return ipxSocket != INVALID_SOCKET || udpSocket != INVALID_SOCKET; + return true; +} + +void Net::closePort() +{ +// if(ipxSocket != INVALID_SOCKET) +// closesocket(ipxSocket); +// if(udpSocket != INVALID_SOCKET) +// closesocket(udpSocket); +} + +Net::Error Net::sendto(const NetAddress *address, const U8 *buffer, S32 bufferSize) +{ + address, buffer, bufferSize; + return NoError; +// if(!sendPackets) +// return NoError; +// if(address->type == NetAddress::IPXAddress) +// { +// SOCKADDR_IPX ipxAddr; +// netToIPXSocketAddress(address, &ipxAddr); +// if(::sendto(ipxSocket, (const char*)buffer, bufferSize, 0, +// (PSOCKADDR) &ipxAddr, sizeof(SOCKADDR_IPX)) == 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() +{ +// SOCKADDR sa; +// +// PacketReceiveEvent receiveEvent; +// for(;;) +// { +// S32 addrLen = sizeof(sa); +// S32 bytesRead = SOCKET_ERROR; +// if(udpSocket != INVALID_SOCKET) +// bytesRead = recvfrom(udpSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); +// if(bytesRead == SOCKET_ERROR && ipxSocket != INVALID_SOCKET) +// { +// addrLen = sizeof(sa); +// bytesRead = recvfrom(ipxSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); +// } +// +// if(bytesRead == SOCKET_ERROR) +// 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; +// GamePostEvent(receiveEvent); +// } +} + +NetSocket Net::openSocket() +{ + return InvalidSocket; +// SOCKET retSocket; +// retSocket = socket(AF_INET, SOCK_STREAM, 0); +// +// if(retSocket == INVALID_SOCKET) +// 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) +{ + socket; + if(address->type != NetAddress::IPAddress) + return WrongProtocolType; +// SOCKADDR_IN socketAddress; +// netToIPSocketAddress(address, &socketAddress); +// if(!::connect(socket, (PSOCKADDR) &socketAddress, sizeof(socketAddress))) +// return NoError; + return getLastError(); +} + +Net::Error Net::listen(NetSocket socket, S32 backlog) +{ + socket, backlog; +// if(!::listen(socket, backlog)) +// return NoError; + return getLastError(); +} + +NetSocket Net::accept(NetSocket acceptSocket, NetAddress *remoteAddress) +{ + acceptSocket, remoteAddress; +// SOCKADDR_IN socketAddress; +// S32 addrLen = sizeof(socketAddress); +// +// SOCKET retVal = ::accept(acceptSocket, (PSOCKADDR) &socketAddress, &addrLen); +// if(retVal != INVALID_SOCKET) +// { +// IPSocketToNetAddress(&socketAddress, remoteAddress); +// return retVal; +// } + return InvalidSocket; +} + +Net::Error Net::bind(NetSocket socket, U16 port) +{ + socket, port; +// S32 error; +// +// SOCKADDR_IN socketAddress; +// dMemset((char *)&socketAddress, 0, sizeof(socketAddress)); +// socketAddress.sin_family = AF_INET; +// socketAddress.sin_addr.s_addr = INADDR_ANY; +// socketAddress.sin_port = htons(port); +// error = ::bind(socket, (PSOCKADDR) &socketAddress, sizeof(socketAddress)); +// +// if(!error) +// return NoError; + return getLastError(); +} + +Net::Error Net::setBufferSize(NetSocket socket, S32 bufferSize) +{ + socket, 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) +{ + socket, 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) +{ + socket, blockingIO; +// DWORD notblock = !blockingIO; +// S32 error = ioctlsocket(socket, FIONBIO, ¬block); +// if(!error) +// return NoError; + return getLastError(); +} + +Net::Error Net::send(NetSocket socket, const U8 *buffer, S32 bufferSize) +{ + socket, buffer, bufferSize; +// S32 error = ::send(socket, (const char*)buffer, bufferSize, 0); +// if(!error) +// return NoError; + return getLastError(); +} + +Net::Error Net::recv(NetSocket socket, U8 *buffer, S32 bufferSize, S32 *bytesRead) +{ + socket, buffer, bufferSize, bytesRead; +// *bytesRead = ::recv(socket, (char*)buffer, bufferSize, 0); +// if(*bytesRead == SOCKET_ERROR) +// return getLastError(); + return NoError; +} + +bool Net::compareAddresses(const NetAddress *a1, const NetAddress *a2) +{ + if(a1->type != a2->type) + return false; + if(*((U32 *)a1->netNum) != *((U32 *)a2->netNum)) + 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) +{ + addressString, 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 + { + ipAddr.sin_addr.s_addr = inet_addr(remoteAddr); + if(ipAddr.sin_addr.s_addr == INADDR_NONE) + { + if((hp = gethostbyname(remoteAddr)) == NULL) + 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; + } + } + */ + return false; +} + +void Net::addressToString(const NetAddress *address, char addressString[256]) +{ + address, addressString; +/* + 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:%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)); + } + else + { + 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() +{ + return Net::UnknownError; +// S32 err = WSAGetLastError(); +// switch(err) +// { +// case WSAEWOULDBLOCK: +// return Net::WouldBlock; +// default: +// return Net::UnknownError; +// } +} diff --git a/platformPPC/ppcOGLVideo.cc b/platformPPC/ppcOGLVideo.cc new file mode 100644 index 0000000..2ad0133 --- /dev/null +++ b/platformPPC/ppcOGLVideo.cc @@ -0,0 +1,422 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformGL.h" +#include "PlatformPPC/platformPPC.h" +#include "PlatformPPC/ppcOGLVideo.h" +#include "console/console.h" +#include + +//extern Win32PlatState winState; +extern PPCPlatState ppcState; + + +//------------------------------------------------------------------------------ +OpenGLDevice::OpenGLDevice() +{ + initDevice(); +} + + +OpenGLDevice::~OpenGLDevice() +{ +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::initDevice() +{ + #pragma message("todo: enumerate available display modes"); + // Set the device name: + mDeviceName = "OpenGL"; + + // Set some initial conditions: + mResolutionList.clear(); + + Resolution newRes( 640, 480, 32 ); + mResolutionList.push_back( newRes ); + return; + +/* + // 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 >= 640 && devMode.dmPelsHeight >= 480 + && ( devMode.dmBitsPerPel == 16 || devMode.dmBitsPerPel == 32 ) ) + { + // Only add this resolution if it is not already in the list: + bool alreadyInList = false; + for ( U32 i = 0; i < mResolutionList.size(); i++ ) + { + if ( devMode.dmPelsWidth == mResolutionList[i].w + && devMode.dmPelsHeight == mResolutionList[i].h + && devMode.dmBitsPerPel == mResolutionList[i].bpp ) + { + alreadyInList = true; + break; + } + } + + if ( !alreadyInList ) + { + Resolution newRes( devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel ); + mResolutionList.push_back( newRes ); + } + } + } +*/ +} + + +//------------------------------------------------------------------------------ +bool OpenGLDevice::activate() +{ + // If the rendering context exists, delete it: + if (ppcState.ctx) + { + Con::printf( "Making the rendering context not current..." ); + aglSetCurrentContext(NULL); + aglSetDrawable(ppcState.ctx, NULL); + Con::printf( "Deleting the rendering context..." ); + aglDestroyContext(ppcState.ctx); + ppcState.ctx = NULL; + } + + //if ( winState.hGLRC ) + //{ + // qwglMakeCurrent( NULL, NULL ); + // qwglDeleteContext( winState.hGLRC ); + // winState.hGLRC = NULL; + //} + + // If OpenGL library already loaded, shut it down: + //if ( winState.hinstOpenGL ) + // QGL_Shutdown(); + + //if ( 0 /* TODO -- figure out when we will need to get a new window */ ) + //{ + // // Release the device context and kill the window: + // ReleaseDC( winState.appWindow, winState.appDC ); + // winState.appDC = NULL; + // DestroyWindow( winState.appWindow ); + //} + + // If the app window does not exist, create it: +/* + if ( ppcState.appWindow == NULL ) + { + // Create the new window: + ppcState.appWindow = CreateOpenGLWindow( smIsFullScreen ); + if ( ppcState.appWindow == NULL ) + { + Platform::AlertOK( "OpenGLDevice::activate", "Failed to create a window!" ); + return false; + } + + setResolution( smCurrentRes, true ); + + // Set the new window to the foreground: + ShowWindow( ppcState.appWindow ); + + //ShowWindow( winState.appWindow, SW_SHOW ); + //SetForegroundWindow( winState.appWindow ); + //SetFocus( winState.appWindow ); + + } +*/ + GLint attrib[] = { AGL_RGBA, AGL_DOUBLEBUFFER, AGL_DEPTH_SIZE, 16, AGL_NONE }; + + // Never unload a code module + aglConfigure(AGL_RETAIN_RENDERERS, GL_TRUE); + + ppcState.fmt = aglChoosePixelFormat(NULL, 0, attrib); + if(ppcState.fmt == NULL) + { + Platform::AlertOK( "OpenGLDevice::activate", "Failed to set the pixel format of the device context!" ); + return false; + } + // Con::printf( ??? ); // Spit out pixel format details? + + //QGL_Init( "opengl32", "glu32" ); + + //PIXELFORMATDESCRIPTOR pfd; + //CreatePixelFormat( &pfd, smCurrentRes.bpp, 24, 8, false ); + + //S32 pixelFormat = ChooseBestPixelFormat( winState.appDC, &pfd ); + //qwglDescribePixelFormat( winState.appDC, pixelFormat, sizeof( pfd ), &pfd ); + //if ( !SetPixelFormat( winState.appDC, pixelFormat, &pfd ) ) + //{ + // Platform::AlertOK( "OpenGLDevice::activate", "Failed to set the pixel format of the device context!" ); + // return false; + //} + + // Create an AGL context + Con::printf( "Creating a new rendering context..." ); + ppcState.ctx = aglCreateContext(ppcState.fmt, NULL); + if(ppcState.ctx == NULL) + { + Platform::AlertOK( "OpenGLDevice::activate", "Failed to create a GL rendering context!" ); + return false; + } + + //winState.hGLRC = qwglCreateContext( winState.appDC ); + //if ( winState.hGLRC == NULL ) + //{ + // Platform::AlertOK( "OpenGLDevice::activate", "Failed to create a GL rendering context!" ); + // return false; + //} + + // Attach the context to the window + Con::printf( "Attaching the rendering context to the window..." ); + if( !aglSetDrawable(ppcState.ctx, (CGrafPtr) ppcState.appWindow) ) + { + Platform::AlertOK( "OpenGLDevice::activate", "Failed to make the rendering context current!" ); + return false; + } + // Make this context current + aglSetCurrentContext(ppcState.ctx); + + //if ( !qwglMakeCurrent( winState.appDC, winState.hGLRC ) ) + //{ + // Platform::AlertOK( "OpenGLDevice::activate", "Failed to make the rendering context current!" ); + // return false; + //} + + // Output some driver info to the console? + + QGL_EXT_Init(); + + return true; +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::shutdown() +{ + /*if (ppcState.ctx) + { + aglSetCurrentContext(NULL); + aglSetDrawable(ppcState.ctx, NULL); + aglDestroyContext(ppcState.ctx); + ppcState.ctx = NULL; + }*/ + +// if ( smIsFullScreen ) +// ChangeDisplaySettings( NULL, 0 ); +} + + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setResolution( Resolution &res, bool forceIt ) +{ + if ( !smIsFullScreen && ( res.w >= ppcState.desktopWidth || res.h >= ppcState.desktopHeight ) ) + { + Con::printf( "OpenGLDevice::setResolution -- can't switch to resolution larger than desktop in windowed mode!" ); + return false; + } + + if ( res.w < 640 || res.h < 480 ) + { + Con::printf( "OpenGLDevice::setResolution -- can't go smaller than 640x480!" ); + return false; + } + + // If full-screen, make the new resolution match one of the ones in the list: + if ( smIsFullScreen ) + { + U32 resIndex = 0; + U32 bestScore = 0, thisScore = 0; + for ( int i = 0; i < mResolutionList.size(); i++ ) + { + if ( res == mResolutionList[i] ) + { + resIndex = i; + break; + } + else + { + thisScore = (int) res.h - (int) mResolutionList[i].h; + if (thisScore < 0) thisScore = -thisScore; + thisScore = (int) res.w - (int) mResolutionList[i].w + thisScore; + if (thisScore < 0) thisScore = -thisScore; + //thisScore = abs( (int) res.w - (int) mResolutionList[i].w ) + abs( (int) res.h - (int) mResolutionList[i].h ); + if ( ( !bestScore || thisScore < bestScore ) && res.bpp == mResolutionList[i].bpp ) + { + bestScore = thisScore; + resIndex = i; + } + } + } + + res = mResolutionList[resIndex]; + } + + // Return if already at this resolution: + if ( !forceIt && res == smCurrentRes ) + return true; + + Con::printf( "Changing resolution to %dx%dx%d", res.w, res.h, res.bpp ); + U32 test; + if ( smIsFullScreen ) + { + // aglSetFullScreen( neds to know monitor freq... +#if 0 + // Change the display settings: + DEVMODE devMode; + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmPelsWidth = res.w; + devMode.dmPelsHeight = res.h; + // TODO: whatever it takes to make bit depth switching work. + //devMode.dmBitsPerPel = res.bpp + devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT/* | DM_BITSPERPEL*/; + + if ( ChangeDisplaySettings( &devMode, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL ) + { + ChangeDisplaySettings( NULL, 0 ); + Con::printf( "OpenGLDevice::setResolution -- ChangeDisplaySettings to %dx%dx%d failed.", res.w, res.h, res.bpp ); + return; + } + + test = SetWindowPos( ppcState.appWindow, 0, 0, 0, res.w, res.h, SWP_NOZORDER ); + if ( !test ) + { + Con::printf( "OpenGLDevice::setResolution -- SetWindowPos moving to ( 0, 0 ) and sizing to %dx%d failed.", res.w, res.h ); + return; + } +#endif + } + else + { + //SizeWindow(ppcState.appWindow, res.w, res.h, true); // change the window size + #pragma message("todo: center window on resize"); + + U32 xPos = 0; + U32 yPos = 0; + if (!aglSetDrawable(ppcState.ctx, (CGrafPtr) ppcState.appWindow)) + { + Con::printf( "OpenGLDevice::setResolution: SizeWindow moving to ( %d, %d ) and sizing to %dx%d failed.", xPos, yPos, res.w, res.h ); + return false; + } + if (!aglUpdateContext(ppcState.ctx)) // tell OpenGL the window changed + { + Con::printf("OpenGLDevice::setResolution: Unable to resize render buffer"); + return false; + } + +// // Adjust the window rect to compensate for the window style: +// +// RECT windowRect; +// windowRect.left = windowRect.top = 0; +// windowRect.right = res.w; +// windowRect.bottom = res.h; +// +// AdjustWindowRect( &windowRect, GetWindowLong( ppcState.appWindow, GWL_STYLE ), false ); +// U32 adjWidth = windowRect.right - windowRect.left; +// U32 adjHeight = windowRect.bottom - windowRect.top; +// +// // Center the window again: +// U32 xPos = ( ppcState.desktopWidth - adjWidth ) / 2; +// U32 yPos = ( ppcState.desktopHeight - adjHeight ) / 2; +// test = SetWindowPos( ppcState.appWindow, 0, xPos, yPos, adjWidth, adjHeight, SWP_NOZORDER ); +// if ( !test ) +// { +// Con::printf( "OpenGLDevice::setResolution -- SetWindowPos moving to ( %d, %d ) and sizing to %dx%d failed.", xPos, yPos, res.w, res.h ); +// return; +// } + } + + smCurrentRes = res; + Platform::setWindowSize( res.w, res.h ); + char tempBuf[15]; + dSprintf( tempBuf, sizeof( tempBuf ), "%dx%dx%d", smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp ); + Con::setVariable( "$pref::Video::resolution", tempBuf ); + + return true; +} + + +//------------------------------------------------------------------------------ +bool OpenGLDevice::toggleFullScreen() +{ + // Change the window style: +// U32 style = WS_VISIBLE; +// if ( smIsFullScreen ) +// style |= ( WS_OVERLAPPED | WS_BORDER | WS_CAPTION ); +// else +// style |= ( WS_POPUP | WS_MAXIMIZE ); +// SetWindowLong( ppcState.appWindow, GWL_STYLE, style ); +// +// U32 test = SetWindowPos( ppcState.appWindow, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED ); +// if ( !test ) +// { +// Con::printf( "OpenGLDevice::toggleFullScreen -- SetWindowPos changing window style failed." ); +// return false; +// } + + smIsFullScreen = !smIsFullScreen; + Con::setVariable( "$pref::Video::fullScreen", ( smIsFullScreen ? "true" : "false" ) ); + + if ( !smIsFullScreen ) + { + // Change to windowed mode: +// ChangeDisplaySettings( NULL, 0 ); + + // Make sure the window size is not too big for the desktop: + if ( smCurrentRes.w >= ppcState.desktopWidth || smCurrentRes.h >= ppcState.desktopHeight ) + { + S32 resIndex; + for ( resIndex = 0; resIndex < mResolutionList.size(); resIndex++ ) + { + if ( smCurrentRes == mResolutionList[resIndex] ) + break; + } + + if ( resIndex < mResolutionList.size() ) + { + // Walk back in the list until we get to a res smaller than the desktop: + while ( resIndex >= 0 && ( mResolutionList[resIndex].w >= ppcState.desktopWidth || mResolutionList[resIndex].h >= ppcState.desktopHeight ) ) + resIndex--; + + if ( resIndex >= 0 ) + { + setResolution( mResolutionList[resIndex], true ); + return true; + } + } + + // Something is really screwy if you ever get to here, but let it fall through anyways. + Con::printf( "OpenGLDevice::toggleFullScreen -- a suitable resolution could not be found!" ); + } + } + + // Force the resolution change: + if (setResolution( smCurrentRes, true )) + return true; + return false; +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::swapBuffers() +{ + aglSwapBuffers(aglGetCurrentContext()); +} + + +//------------------------------------------------------------------------------ +DisplayDevice* OpenGLDevice::create() +{ + return new OpenGLDevice(); +} diff --git a/platformPPC/ppcOGLVideo.h b/platformPPC/ppcOGLVideo.h new file mode 100644 index 0000000..be693bc --- /dev/null +++ b/platformPPC/ppcOGLVideo.h @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PCCOGLVIDEO_H_ +#define _PCCOGLVIDEO_H_ + +#ifndef _PLATFORMVIDEO_H_ +#include "Platform/platformVideo.h" +#endif + +class OpenGLDevice : public DisplayDevice +{ + public: + OpenGLDevice(); + ~OpenGLDevice(); + + void initDevice(); + bool activate(); + void shutdown(); + bool setResolution( Resolution &res, bool forceIt = false ); + bool toggleFullScreen(); + void swapBuffers(); + + static DisplayDevice* create(); +}; + +#endif // _H_WINOGLVIDEO diff --git a/platformPPC/ppcProcessControl.cc b/platformPPC/ppcProcessControl.cc new file mode 100644 index 0000000..8b2824d --- /dev/null +++ b/platformPPC/ppcProcessControl.cc @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" + +void Platform::postQuitMessage(const U32 in_quitVal) +{ + ppcState.quit = true; +} + +void Platform::debugBreak() +{ + //DebugBreak(); +} + +void Platform::forceShutdown(S32 returnValue) +{ + //exit(returnValue); +} diff --git a/platformPPC/ppcStrings.cc b/platformPPC/ppcStrings.cc new file mode 100644 index 0000000..0ce169f --- /dev/null +++ b/platformPPC/ppcStrings.cc @@ -0,0 +1,306 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include +#include +#include +#include + +char *dStrdup(const char *src) +{ + char *buffer = new char[dStrlen(src) + 1]; + dStrcpy(buffer, src); + return buffer; +} + +char *dStrnew(const char *src) +{ + char *buffer = new char[dStrlen(src) + 1]; + dStrcpy(buffer, src); + return buffer; +} + +char* dStrcat(char *dst, const char *src) +{ + return strcat(dst,src); +} + + +S32 dStrcmp(const char *str1, const char *str2) +{ + return strcmp(str1, str2); +} + + +S32 dStricmp(const char *str1, const char *str2) +{ + char c1, c2; + while (1) + { + c1 = tolower(*str1++); + c2 = tolower(*str2++); + if (c1 < c2) return -1; + if (c1 > c2) return 1; + if (c1 == 0) return 0; + } +} + +S32 dStrncmp(const char *str1, const char *str2, U32 len) +{ + return strncmp(str1, str2, len); +} + +S32 dStrnicmp(const char *str1, const char *str2, U32 len) +{ + S32 i; + char c1, c2; + for (i=0; i c2) return 1; + if (!c1) return 0; + } + return 0; +} + + +char* dStrcpy(char *dst, const char *src) +{ + return strcpy(dst,src); +} + + +char* dStrncpy(char *dst, const char *src, U32 len) +{ + return strncpy(dst,src,len); +} + + +U32 dStrlen(const char *str) +{ + return strlen(str); +} + + +char* dStrupr(char *str) +{ + while (*str) + { + *str = toupper(*str); + str++; + } + return str; +} + + +char* dStrlwr(char *str) +{ + while (*str) + { + *str = tolower(*str); + str++; + } + return str; +} + + +char* dStrchr(char *str, S32 c) +{ + return strchr(str,c); +} + + +const char* dStrchr(const char *str, S32 c) +{ + return strchr(str,c); +} + +const char* dStrrchr(const char *str, S32 c) +{ + return strrchr(str,c); +} + + +char* dStrrchr(char *str, S32 c) +{ + return strrchr(str,c); +} + +U32 dStrspn(const char *str, const char *set) +{ + return(strspn(str, set)); +} + +U32 dStrcspn(const char *str, const char *set) +{ + return strcspn(str, set); +} + + +char* dStrstr(char *str1, char *str2) +{ + return strstr(str1,str2); +} + + +const char* dStrstr(const char *str1, const char *str2) +{ + return strstr(str1,str2); +} + +char* dStrtok(char *str, const char *sep) +{ + return strtok(str, sep); +} + + +S32 dAtoi(const char *str) +{ + return atoi(str); +} + + +F32 dAtof(const char *str) +{ + return atof(str); +} + +bool dAtob(const char *str) +{ + return !dStricmp(str, "true") || dAtof(str); +} + +bool dIsalnum(const char c) +{ + return isalnum(c); +} + +bool dIsalpha(const char c) +{ + return(isalpha(c)); +} + +bool dIsspace(const char c) +{ + return(isspace(c)); +} + +bool dIsdigit(const char c) +{ + return(isdigit(c)); +} + +void dPrintf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vprintf(format, args); +} + +S32 dVprintf(const char *format, void *arglist) +{ + S32 len = vprintf(format, (char*)arglist); + + // to-do + // The intended behavior is to zero-terminate and not allow the overflow + return (len); +} + +S32 dSprintf(char *buffer, U32 /*bufferSize*/, const char *format, ...) +{ + va_list args; + va_start(args, format); + S32 len = vsprintf(buffer, format, args); + + // to-do + // The intended behavior is to zero-terminate and not allow the overflow + return (len); +} + + +S32 dVsprintf(char *buffer, U32 /*bufferSize*/, const char *format, void *arglist) +{ + S32 len = vsprintf(buffer, format, (char*)arglist); + + // to-do + // The intended behavior is to zero-terminate and not allow the overflow + return (len); +} + + +S32 dSscanf(const char *buffer, const char *format, ...) +{ + va_list args; + va_start(args, format); + return __vsscanf(buffer, format, args); +} + +S32 dFflushStdout() +{ + return fflush(stdout); +} + +S32 dFflushStderr() +{ + return fflush(stderr); +} + +void dQsort(void *base, U32 nelem, U32 width, S32 (QSORT_CALLBACK *fcmp)(const void *, const void *)) +{ + qsort(base, nelem, width, fcmp); +} + + +//--------------------------------------------------------------------------- +// Mac Strinng conversion routines +U8* str2p(const char *str) +{ + static U8 buffer[256]; + str2p(str, buffer); + return buffer; +} + + +U8* str2p(const char *str, U8 *p) +{ + AssertFatal(dStrlen(str) <= 255, "str2p:: Max Pascal String length exceeded (max=255)."); + U8 *dst = p+1; + U8 *src = (U8*)str; + *p = 0; + while(*src != '\0') + { + *dst++ = *src++; + (*p) += 1; + } + return p; +} + + +char* p2str(U8 *p) +{ + static char buffer[256]; + p2str(p, buffer); + return buffer; +} + + +char* p2str(U8 *p, char *dst_str) +{ + U8 len = *p++; + char *src = (char*)p; + char *dst = dst_str; + while (len--) + { + *dst++ = *src++; + } + *dst = '\0'; + return dst_str; +} + diff --git a/platformPPC/ppcTime.cc b/platformPPC/ppcTime.cc new file mode 100644 index 0000000..ea2eeee --- /dev/null +++ b/platformPPC/ppcTime.cc @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include +#include + +//-------------------------------------- +void Platform::getLocalTime(LocalTime <) +{ + #pragma message("todo: Platform::getLocalTime") +// 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; + + lt.sec = 0; + lt.min = 0; + lt.hour = 0; + lt.month = 1; + lt.monthday = 1; + lt.weekday = 0; + lt.year = 1999; + lt.yearday = 1; + lt.isdst = 0; +} + + +U32 GetMilliseconds() +{ + UnsignedWide time; + Microseconds(&time); + return (time.hi*5000000) + (time.lo/1000); +} + +U32 Platform::getRealMilliseconds() +{ + UnsignedWide time; + Microseconds(&time); + return (time.hi*5000000) + (time.lo/1000); +} + +U32 Platform::getVirtualMilliseconds() +{ + return ppcState.currentTime; +} + +void Platform::advanceTime(U32 delta) +{ + ppcState.currentTime += delta; +} + diff --git a/platformPPC/ppcUtils.cc b/platformPPC/ppcUtils.cc new file mode 100644 index 0000000..e6dd685 --- /dev/null +++ b/platformPPC/ppcUtils.cc @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include "PlatformPPC/ppcUtils.h" + +void ppcGetWorkingDirectory() +{ + WDPBRec rec; + rec.ioCompletion = NULL; + rec.ioNamePtr = NULL; + + // get the current vRefNum and dirID + PBHGetVolSync( &rec ); + + ppcState.volRefNum = rec.ioWDVRefNum; + ppcState.dirID = rec.ioWDDirID; +} diff --git a/platformPPC/ppcUtils.h b/platformPPC/ppcUtils.h new file mode 100644 index 0000000..3f5a336 --- /dev/null +++ b/platformPPC/ppcUtils.h @@ -0,0 +1,15 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PPCUTILLS_H_ +#define _PPCUTILLS_H_ + + +void ppcGetWorkingDirectory(); + + +#endif // _H_PPCUTILS_ diff --git a/platformPPC/ppcWindow.cc b/platformPPC/ppcWindow.cc new file mode 100644 index 0000000..aeb14d0 --- /dev/null +++ b/platformPPC/ppcWindow.cc @@ -0,0 +1,1046 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformPPC/platformPPC.h" +#include "PlatformWin32/platformGL.h" +#include "Platform/platform.h" +#include "Platform/platformVideo.h" +#include "PlatformPPC/ppcOGLVideo.h" +#include "Platform/event.h" +#include "console/console.h" +#include "PlatformPPC/ppcConsole.h" + +//-------------------------------------- Mac System includes +#include +#include + +//#include +#include + + +#include + +//-------------------------------------- Resource Includes +#include "dgl/gBitmap.h" +#include "dgl/gFont.h" + +extern void createFontInit(); +extern void createFontShutdown(); + +PPCPlatState ppcState; + +PPCPlatState::PPCPlatState() +{ + hDisplay = NULL; + appWindow = NULL; + quit = false; + + fmt = NULL; + ctx = NULL; + + volRefNum = 0; + dirID = 0; + + dStrcpy(appWindowTitle, "Darkstar Window"); + +// log_fp = NULL; +// hinstOpenGL = NULL; +// hinstGLU = NULL; +// appWindow = NULL; +// appDC = NULL; +// appInstance = NULL; +} + +static bool windowLocked = false; + +static U8 keyboardState[256]; +static bool capsLockDown = false; +static S32 modifierKeys = 0; +static bool windowActive = true; + +static const char *getKeyName(S32 vkCode); +static Point2I windowSize; + + +//-------------------------------------- +static const char *getMessageName(S32 msg) +{ + switch(msg) + { +// case WM_KEYDOWN: +// return "WM_KEYDOWN"; +// case WM_KEYUP: +// return "WM_KEYUP"; +// case WM_SYSKEYUP: +// return "WM_SYSKEYUP"; +// case WM_SYSKEYDOWN: +// return "WM_SYSKEYDOWN"; + default: + return "Unknown!!"; + } +} + + +void Platform::AlertOK(const char *windowTitle, const char *message) +{ + windowTitle, message; + //messageBox(message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OK); +} + +bool Platform::AlertOKCancel(const char *windowTitle, const char *message) +{ + windowTitle, message; + return true; + //return messageBox(message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OKCANCEL) == IDOK; +} + +bool Platform::AlertRetry(const char *windowTitle, const char *message) +{ + windowTitle, message; + return true; + //return messageBox(message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_RETRYCANCEL) == IDRETRY); +} + +//-------------------------------------- +static void InitInput() +{ + dMemset( keyboardState, 0, 256 ); +} + +extern Point MTemp : 0x828; // RawMouse and MTemp both contain the current absolute mouse position +extern Point RawMouse : 0x82C; +extern Rect CrsrPin : 0x834; +extern Byte CrsrNew : 0x8CE; // CrsrNew is a flag that tells the system when the mouse has changed +extern Byte CrsrCouple : 0x8CF; // CrsrCouple is what CrsrNew should be set to when the mouse has changed + +static Point& ClientToScreen( WindowPtr wnd, Point pt ) +{ + static Point screen; + screen = pt; + + GrafPort *savePort; + GetPort( &savePort ); + SetPort( ppcState.appWindow ); + LocalToGlobal( &screen ); + SetPort( savePort ); + + return screen; +} + + +void GetWindowRect( WindowPtr wnd, RectI *pRect ) +{ + // note - I should probably add a check for title bar, and borders to simulate Windows completely. + if ( pRect && wnd ) + { + GrafPort *port; + GetPort( &port ); + SetPort( wnd ); + Rect r = wnd->portRect; + SetPort( port ); + + pRect->point.x = r.left; + pRect->point.y = r.top; + pRect->extent.x = r.right - r.left; + pRect->extent.y = r.bottom - r.top; + + pRect->point.x = 0; + pRect->point.y = 0; + pRect->extent.x = 800; + pRect->extent.y = 600; + + + + } +} + + +static void GetMousePos(Point *pt) +{ + GrafPort *savePort; + GetPort( &savePort ); + SetPort( ppcState.appWindow ); + GetMouse(pt); + SetPort( savePort ); +} + +static void SetCursorPos(const Point2I &pos) +{ + Point pt; + Rect deviceRect = (*ppcState.hDisplay)->gdRect; + + // window to screen space + //RectI r; + //GetWindowRect(ppcState.appWindow, &r); + pt.h = pos.x; + pt.v = pos.y; + pt = ClientToScreen(ppcState.appWindow, pt); + + // display to desktop ? + //pt.h = deviceRect.left + pos.x; + //pt.v = deviceRect.top + pos.y; + + MTemp = RawMouse = pt; + CrsrNew = CrsrCouple; +} + + + +//-------------------------------------- +static void setMouseClipping() +{ +// ClipCursor(NULL); + if(windowActive) + { +// ShowCursor(false); + if(windowLocked) + { +// RECT r; +// GetWindowRect(winState.appWindow, &r); +// ClipCursor(&r); +// + RectI r; + GetWindowRect(ppcState.appWindow, &r); + //r.point += r.extent / 2; + r.extent /= 2; + //S32 centerX = (r.right + r.left) >> 1; + //S32 centerY = (r.bottom + r.top) >> 1; + SetCursorPos(r.extent); + } + } +// else +// ShowCursor(true); +} + +void Platform::setWindowLocked(bool locked) +{ + windowLocked = locked; +// setMouseClipping(); +} + +//-------------------------------------- +//static void processKeyMessage(UINT message, WPARAM wParam, LPARAM lParam) +//{ +// S32 nVirtkey = wParam; +// S32 scanCode = (lParam >> 16) & 0xff; +// bool extended = (lParam >> 24) & 1; +// bool repeat = (lParam >> 30) & 1; +// bool make = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN); +// +// S32 newVirtKey = nVirtkey; +// switch(nVirtkey) +// { +// case KEY_MENU: +// if(extended) +// newVirtKey = KEY_RMENU; +// else +// newVirtKey = KEY_LMENU; +// break; +// case KEY_CONTROL: +// if(extended) +// newVirtKey = KEY_RCONTROL; +// else +// newVirtKey = KEY_LCONTROL; +// break; +// case KEY_SHIFT: +// if(scanCode == 54) +// newVirtKey = KEY_RSHIFT; +// else +// newVirtKey = KEY_LSHIFT; +// break; +// } +// +// S32 modKey = modifierKeys; +// +// if(make) +// { +// switch (newVirtKey) +// { +// case KEY_LSHIFT: modifierKeys |= SI_LSHIFT; modKey = 0; break; +// case KEY_RSHIFT: modifierKeys |= SI_RSHIFT; modKey = 0; break; +// case KEY_LCONTROL: modifierKeys |= SI_LCTRL; modKey = 0; break; +// case KEY_RCONTROL: modifierKeys |= SI_RCTRL; modKey = 0; break; +// case KEY_LMENU: modifierKeys |= SI_LALT; modKey = 0; break; +// case KEY_RMENU: modifierKeys |= SI_RALT; modKey = 0; break; +// } +// if(nVirtkey == VK_CAPITAL) +// { +// capsLockDown = !capsLockDown; +// if(capsLockDown) +// keyboardState[nVirtkey] |= 0x01; +// else +// keyboardState[nVirtkey] &= 0xFE; +// } +// keyboardState[nVirtkey] |= 0x80; +// } +// else +// { +// switch (newVirtKey) +// { +// case KEY_LSHIFT: modifierKeys &= ~SI_LSHIFT; modKey = 0; break; +// case KEY_RSHIFT: modifierKeys &= ~SI_RSHIFT; modKey = 0; break; +// case KEY_LCONTROL: modifierKeys &= ~SI_LCTRL; modKey = 0; break; +// case KEY_RCONTROL: modifierKeys &= ~SI_RCTRL; modKey = 0; break; +// case KEY_LMENU: modifierKeys &= ~SI_LALT; modKey = 0; break; +// case KEY_RMENU: modifierKeys &= ~SI_RALT; modKey = 0; break; +// } +// keyboardState[nVirtkey] &= 0x7f; +// } +// +// WORD ascii = 0; +// S32 res = ToAscii(nVirtkey, (lParam >> 16) & 0xFF, keyboardState, &ascii, 0); +// if(res < 1) +// ascii = 0; +// +// InputEvent event; +// +// event.deviceInst = 0; +// event.deviceType = KeyboardDeviceType; +// event.objType = SI_KEY; +// event.objInst = newVirtKey; +// event.action = make ? (repeat ? SI_REPEAT : SI_MAKE ) : SI_BREAK; +// event.modifier = modKey; +// event.ascii = ascii; +// event.fValue = make ? 1.0 : 0.0; +// +// // printf("%s - %s (%d) count: %d ext: %s char: %c\n", +// // getMessageName(message), getKeyName(nVirtkey), scanCode, +// // repeat, extended ? "True" : "False", ascii); +// GamePostEvent(event); +//} + +static S32 mouseX = 0xFFFFFFFF; +static S32 mouseY = 0xFFFFFFFF; +//-------------------------------------- +static void CheckCursorPos() +{ + if(windowLocked && windowActive) + { + Point mousePos; + GetMousePos(&mousePos); + RectI r; + + GetWindowRect(ppcState.appWindow, &r); + r.extent /= 2; +// S32 centerX = (r.right + r.left) >> 1; +// S32 centerY = (r.bottom + r.top) >> 1; +// + if(mousePos.h != r.extent.x) + { + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_XAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = mousePos.h - r.extent.x; + GamePostEvent(event); + } + if(mousePos.v != r.extent.y) + { + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_YAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = mousePos.v - r.extent.y; + GamePostEvent(event); + } + SetCursorPos(r.extent); + } +} + + +//-------------------------------------- +//static void mouseButtonEvent(S32 action, S32 objInst) +//{ +// action, objInst; +// CheckCursorPos(); +// if(!windowLocked) +// { +// if(action == SI_MAKE) +// SetCapture(winState.appWindow); +// else +// ReleaseCapture(); +// } +// +// InputEvent event; +// +// event.deviceInst = 0; +// event.deviceType = MouseDeviceType; +// event.objType = SI_BUTTON; +// event.objInst = objInst; +// event.action = action; +// event.modifier = modifierKeys; +// event.ascii = 0; +// event.fValue = action == SI_MAKE ? 1.0 : 0.0; +// +// GamePostEvent(event); +//} + + +static void ProcessKeyboard( EventRecord &msg ) +{ + WindowPtr which; + FindWindow( msg.where, &which ); + if (which != ppcState.appWindow) + return; + + InputEvent event; + + event.deviceInst = 0; + event.deviceType = KeyboardDeviceType; + event.objType = SI_KEY; + event.objInst = TranslateOSKeyCode( (msg.message & keyCodeMask) >> 8 ); + event.ascii = msg.message & charCodeMask; + + switch(msg.what) + { + case keyDown: + event.action = SI_MAKE; + event.fValue = 1.0f; + //Con::printf("keyDN ( %02x )", (msg.message & keyCodeMask) >> 8); + break; + + case autoKey: + event.action = SI_REPEAT; + event.fValue = 1.0f; + break; + + case keyUp: + event.action = SI_BREAK; + event.fValue = 0.0f; + //Con::printf("keyUP ( %02x )", (msg.message & keyCodeMask) >> 8); + break; + } + + event.modifier = 0; + //if (msg.modifiers & cmdKey) event.modifier |= SI_COMMAND; + if (msg.modifiers & shiftKey) event.modifier |= SI_LSHIFT; + if (msg.modifiers & rightShiftKey) event.modifier |= SI_RSHIFT; + if (msg.modifiers & optionKey) event.modifier |= SI_LALT; + if (msg.modifiers & rightOptionKey) event.modifier |= SI_RALT; + if (msg.modifiers & controlKey) event.modifier |= SI_LCTRL; + if (msg.modifiers & rightControlKey) event.modifier |= SI_RCTRL; + + // handle command-period exit + if (event.ascii == '.' && (msg.modifiers & cmdKey)) + Platform::postQuitMessage(0); + + // fake right mose button with command-x for now + #pragma message("todo: remove") + if (event.ascii == '`') + { + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_BUTTON; + event.objInst = KEY_BUTTON1; + event.modifier = 0; + event.ascii = 0; + } + GamePostEvent(event); +} + + +static void ProcessMouse( EventRecord &msg ) +{ + WindowPtr which; + U16 where = FindWindow( msg.where, &which ); + if (which != ppcState.appWindow) + return; + + // handle a little window maintence + switch (where) + { + case inSysWindow: + SystemClick ( &msg, ppcState.appWindow ); + return; + + case inDrag: { + RgnHandle rgn = GetGrayRgn(); + DragWindow(ppcState.appWindow, ClientToScreen(ppcState.appWindow, msg.where), &((*rgn)->rgnBBox)); + return; + } + default: + return; + + case inContent: + break; + } + + + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_BUTTON; + event.objInst = KEY_BUTTON0; // always button 0 for now + event.modifier = 0; + event.ascii = 0; + if (msg.what == mouseDown) + { + event.action = SI_MAKE; + event.fValue = 1.0; + } + else + { + event.action = SI_BREAK; + event.fValue = 1.0; + } + GamePostEvent(event); +} + + +//-------------------------------------- +static bool ProcessMessages() +{ + if (ppcState.quit) + return false; + + // the mouse position is polled on the Mac + if(!windowLocked) + { + Point mouseLoc; + GetMousePos( &mouseLoc ); + + MouseMoveEvent event; + event.xPos = mouseLoc.h; // horizontal position of cursor + event.yPos = mouseLoc.v; // vertical position of cursor + event.modifier = modifierKeys; + GamePostEvent(event); + } + + SetEventMask(everyEvent); + + EventRecord msg; + bool done = false; + while(!done && WaitNextEvent(everyEvent, &msg, 0, NULL)) + { + switch(msg.what) + { + case nullEvent: + done = true; + break; + + case keyDown: + case autoKey: + case keyUp: + ProcessKeyboard(msg); + break; + + case mouseDown: + case mouseUp: + ProcessMouse(msg); + break; + + case updateEvt: + BeginUpdate(ppcState.appWindow); + EndUpdate(ppcState.appWindow); + break; + + case kHighLevelEvent:{ + // the type of message is stored in the where Point... so cast it + U32 hlWhat = *((U32*)(&msg.where)); + if ( hlWhat == kAEQuitApplication ) + { + return false; + } + else + AEProcessAppleEvent(&msg); + } + break; + + //case activateEvt: + // SelectWindow(ppcState.appWindow); + // break; + + //default: + // Con::printf("%d %08x", msg.what, msg.what); + } + } + return true; +} + + +//-------------------------------------- +void Platform::process() +{ + CheckCursorPos(); + WindowsConsole->process(); + + if(!ProcessMessages()) + { + // generate a quit event + Event quitEvent; + quitEvent.type = QuitEventType; + + GamePostEvent(quitEvent); + } +} + +//static void InitWindowClass() +//{ +// WNDCLASS wc; +// memset(&wc, 0, sizeof(wc)); +// +// wc.style = 0; +// wc.lpfnWndProc = WindowProc; +// wc.cbClsExtra = 0; +// wc.cbWndExtra = 0; +// wc.hInstance = winState.appInstance; +// wc.hIcon = 0; +// wc.hCursor = LoadCursor (NULL,IDC_ARROW); +// wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); +// wc.lpszMenuName = 0; +// wc.lpszClassName = windowClassName; +// RegisterClass( &wc ); +//} + +enum { MAX_PFDS = 256 }; + + +//-------------------------------------- +//static 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 +// 24, // 24-bit 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 +// 24, // 24-bit z-buffer +// 8, // 8-bit stencil buffer +// 0, // no auxiliary buffer +// PFD_MAIN_PLANE, // main layer +// 0, // reserved +// 0, 0, 0 // layer masks ignored +// }; +// +// src.cColorBits = colorbits; +// src.cDepthBits = depthbits; +// src.cStencilBits = stencilbits; +// +// if ( stereo ) +// { +// //ri.Printf( PRINT_ALL, "...attempting to use stereo\n" ); +// src.dwFlags |= PFD_STEREO; +// //glConfig.stereoEnabled = true; +// } +// else +// { +// //glConfig.stereoEnabled = qfalse; +// } +// *pPFD = src; +//} + + +//-------------------------------------- +//static S32 ChooseBestPixelFormat(HDC hDC, PIXELFORMATDESCRIPTOR *pPFD) +//{ +// PIXELFORMATDESCRIPTOR pfds[MAX_PFDS+1]; +// S32 i; +// S32 bestMatch = 0; +// +// S32 maxPFD = DescribePixelFormat(hDC, 1, sizeof(PIXELFORMATDESCRIPTOR), &pfds[0]); +// if(maxPFD > MAX_PFDS) +// maxPFD = MAX_PFDS; +// +// for(i = 1; i < maxPFD; i++) +// { +// DescribePixelFormat(hDC, i, sizeof(PIXELFORMATDESCRIPTOR), &pfds[i]); +// +// // make sure this has hardware acceleration: +// if ( ( pfds[i].dwFlags & PFD_GENERIC_FORMAT ) != 0 ) +// continue; +// +// // verify pixel type +// if ( pfds[i].iPixelType != PFD_TYPE_RGBA ) +// continue; +// +// // verify proper flags +// if ( ( ( pfds[i].dwFlags & pPFD->dwFlags ) & pPFD->dwFlags ) != pPFD->dwFlags ) +// continue; +// +// // +// // selection criteria (in order of priority): +// // +// // PFD_STEREO +// // colorBits +// // depthBits +// // stencilBits +// // +// if ( bestMatch ) +// { +// // check stereo +// if ( ( pfds[i].dwFlags & PFD_STEREO ) && ( !( pfds[bestMatch].dwFlags & PFD_STEREO ) ) && ( pPFD->dwFlags & PFD_STEREO ) ) +// { +// bestMatch = i; +// continue; +// } +// +// if ( !( pfds[i].dwFlags & PFD_STEREO ) && ( pfds[bestMatch].dwFlags & PFD_STEREO ) && ( pPFD->dwFlags & PFD_STEREO ) ) +// { +// bestMatch = i; +// continue; +// } +// +// // check color +// if ( pfds[bestMatch].cColorBits != pPFD->cColorBits ) +// { +// // prefer perfect match +// if ( pfds[i].cColorBits == pPFD->cColorBits ) +// { +// bestMatch = i; +// continue; +// } +// // otherwise if this PFD has more bits than our best, use it +// else if ( pfds[i].cColorBits > pfds[bestMatch].cColorBits ) +// { +// bestMatch = i; +// continue; +// } +// } +// +// // check depth +// if ( pfds[bestMatch].cDepthBits != pPFD->cDepthBits ) +// { +// // prefer perfect match +// if ( pfds[i].cDepthBits == pPFD->cDepthBits ) +// { +// bestMatch = i; +// continue; +// } +// // otherwise if this PFD has more bits than our best, use it +// else if ( pfds[i].cDepthBits > pfds[bestMatch].cDepthBits ) +// { +// bestMatch = i; +// continue; +// } +// } +// +// // check stencil +// if ( pfds[bestMatch].cStencilBits != pPFD->cStencilBits ) +// { +// // prefer perfect match +// if ( pfds[i].cStencilBits == pPFD->cStencilBits ) +// { +// bestMatch = i; +// continue; +// } +// // otherwise if this PFD has more bits than our best, use it +// else if ( ( pfds[i].cStencilBits > pfds[bestMatch].cStencilBits ) && +// ( pPFD->cStencilBits > 0 ) ) +// { +// bestMatch = i; +// continue; +// } +// } +// } +// else +// { +// bestMatch = i; +// } +// } +// +// if ( !bestMatch ) +// return 0; +// +// else if ( pfds[bestMatch].dwFlags & PFD_GENERIC_ACCELERATED ) +// { +// // MCD +// } +// else +// { +// // ICD +// } +// +// *pPFD = pfds[bestMatch]; +// +// return bestMatch; +//} + +const Point2I &Platform::getWindowSize() +{ + return windowSize; +} + +void Platform::setWindowSize( U32 newWidth, U32 newHeight ) +{ + windowSize.set( newWidth, newHeight ); +} + + +//-------------------------------------- +static void InitWindow(const Point2I &initialSize) +{ + const S32 offset = 50; // magic number that can be changed later + + // for now just get first screen device, later we should check them all + ppcState.hDisplay = DMGetFirstScreenDevice(true); + + Rect rect; + SetRect( &rect, offset, offset, initialSize.x+offset, initialSize.y+offset); + + ppcState.appWindow = NewCWindow(NULL, + &rect, // bounding rect + str2p(ppcState.appWindowTitle), // window title + false, // is visible + documentProc, // window type + (WindowPtr) -1L, // top most window + true, // has a close box + 0L); // reference constant + if (ppcState.appWindow == NULL) + return; + + ShowWindow( ppcState.appWindow ); + windowSize = initialSize; + +// RECT r; +// r.left = 0; +// r.top = 0; +// r.right = initialSize.x; +// r.bottom = initialSize.y; +// +// windowSize = initialSize; +// +// S32 windowStyle = (WS_OVERLAPPED|WS_BORDER|WS_CAPTION|WS_VISIBLE); +// S32 exWindowStyle = 0; +// +// AdjustWindowRect (&r, windowStyle, FALSE); +// +// S32 x = 50; +// S32 y = 50; +// S32 w = r.right - r.left; +// S32 h = r.bottom - r.top; +// +// winState.appWindow = CreateWindowEx( +// exWindowStyle, +// windowClassName, +// "Tribes II", +// windowStyle, +// x, y, w, h, +// NULL, NULL, +// winState.appInstance, +// NULL); +// +// +// ShowWindow( winState.appWindow, SW_SHOW ); +// UpdateWindow( winState.appWindow ); +// +// SetForegroundWindow( winState.appWindow ); +// SetFocus( winState.appWindow ); +// +// winState.appDC = GetDC(winState.appWindow); +// setMouseClipping(); +} + + +//-------------------------------------- +static void InitOpenGL() +{ + // Video::setDevice( Con::getVariable( "$pref::Video::displayDevice" ) ); + Video::setDevice( "OpenGL" ); + + // Just for kicks. Seems a relatively central place to put this... + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + +// GLint attrib[] = { AGL_RGBA, AGL_DOUBLEBUFFER, AGL_DEPTH_SIZE, 16, AGL_NONE }; + +// // Never unload a code module +// aglConfigure(AGL_RETAIN_RENDERERS, GL_TRUE); + +// ppcState.fmt = aglChoosePixelFormat(NULL, 0, attrib); +// if(ppcState.fmt == NULL) return; + +// /* Create an AGL context */ +// ppcState.ctx = aglCreateContext(ppcState.fmt, NULL); +// if(ppcState.ctx == NULL) return; + +// /* Attach the context to the window */ +// if(!aglSetDrawable(ppcState.ctx, (CGrafPtr) ppcState.appWindow)) return; + +// /* Make this context current */ +// aglSetCurrentContext(ppcState.ctx); + +} + +static U32 lastTimeTick; + +//-------------------------------------- + +void Platform::init() +{ + WinConsole::create(); + Input::init(); + InitInput(); // in case our other input falls through + Video::init(); + Video::installDevice( OpenGLDevice::create() ); +} + +void Platform::shutdown() +{ + Video::destroy(); + WinConsole::destroy(); + +// // cleanup openGl +// aglSetCurrentContext(NULL); +// if (ppcState.ctx) +// { +// aglSetDrawable(ppcState.ctx, NULL); +// aglDestroyContext(ppcState.ctx); +// } +// +// // clean up the app window +// if (ppcState.appWindow) +// DisposeWindow(ppcState.appWindow); +} + +static S32 run(S32 argc, const char **argv) +{ + createFontInit(); + windowSize.set(0,0); + + lastTimeTick = GetMilliseconds(); + + S32 ret = GameMain(argc, argv); + createFontShutdown(); + + return ret; +} + +void Platform::initWindow(const Point2I &initialSize, const char *name) +{ + dSprintf(ppcState.appWindowTitle, sizeof(ppcState.appWindowTitle), name); + InitWindow(initialSize); + InitOpenGL(); +} + +//-------------------------------------- +S32 main(S32 argc, const char **argv) +{ + // mac does not support command line arguments + // it may be possible to get the comment field from the file + // see DesktopManager and DTGetComment + + InitGraf(&qd.thePort); // init QuickDraw -- 'qd' is a Mac Global + InitFonts(); // init the Font Manager + InitWindows(); // init the Window Manager + InitMenus(); + TEInit(); + InitDialogs( NULL ); + InitCursor(); + + FlushEvents( everyEvent, 0 ); + SetEventMask(everyEvent); + + ppcGetWorkingDirectory(); + + return run(argc, argv); +} + + +//-------------------------------------- +//S32 PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, S32 nCmdShow ) +//{ +// hPrevInstance; +// const char *ptr = lpszCmdLine; +// nCmdShow; +// Vector argv; +// char moduleName[256]; +// GetModuleFileName(NULL, moduleName, sizeof(moduleName)); +// +// argv.push_back(moduleName); +// S32 i = 0; +// for(;;) +// { +// char c = ptr[i]; +// if(c == 0 || c == ' ') +// { +// if(i) +// { +// char *arg = (char *) dMalloc(i+1); +// dStrncpy(arg, ptr, i); +// arg[i] = 0; +// argv.push_back(arg); +// ptr += i + 1; +// i = 0; +// +// } +// if(!c) +// break; +// } +// else +// i++; +// } +// 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; +//} + + +//-------------------------------------- +//void Video::swapBuffers() +//{ +// aglSwapBuffers(aglGetCurrentContext()); +//} + + +//-------------------------------------- +void TimeManager::process() +{ + U32 curTime = GetMilliseconds(); + TimeEvent event; + event.elapsedTime = curTime - lastTimeTick; + lastTimeTick = curTime; + GamePostEvent(event); +} + +/* +GLimp_Init + GLW_LoadOpenGL + QGL_Init(driver); + GLW_StartDriverAndSetMode + GLW_SetMode + ChangeDisplaySettings + GLW_CreateWindow + GLW_InitDriver + GLW_CreatePFD + GLW_MakeContext + GLW_ChoosePFD + DescribePixelFormat + SetPixelFormat + + GLW_InitExtensions + WG_CheckHardwareGamma +*/ + +F32 Platform::getRandom() +{ + //return rand() / F32(RAND_MAX); + return 0.5; +} + diff --git a/platformWin32/d3dgl.cc b/platformWin32/d3dgl.cc new file mode 100644 index 0000000..ca78b62 --- /dev/null +++ b/platformWin32/d3dgl.cc @@ -0,0 +1,10381 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/D3DGL.h" +#include "PlatformWin32/platformWin32.h" +#include "console/console.h" + +#ifdef USEICECAP +#include "icapexp.h" +#endif + +static void APIENTRY d3dEnd (void); +static void APIENTRY d3dBegin (GLenum mode); +static void APIENTRY d3dVertex3fv (const GLfloat *v); + +static Globals g; + +static void QuakeUpdateViewport() +{ + D3DVIEWPORT7 vport; + g.m_d3ddev->GetViewport(&vport); + if(g.m_scissoring) + { + // Check whether viewport is completely within scissor rect + RECT scirect, vwprect, xrect; + scirect.left = g.m_scix; + scirect.top = g.m_sciy; + scirect.right = g.m_scix + g.m_sciw; + scirect.bottom = g.m_sciy + g.m_scih; + vwprect.left = g.m_vwx; + vwprect.top = g.m_vwy; + vwprect.right = g.m_vwx + g.m_vww; + vwprect.bottom = g.m_vwy + g.m_vwh; + if(IntersectRect(&xrect, &scirect, &vwprect)) + { + if(EqualRect(&xrect, &vwprect)) + { + goto updvwp; + } + } + else + { + goto updvwp; + } + // BUGBUG: if the viewport overlaps the scissor rect, then none of this works + vport.dwX = g.m_scix; + vport.dwY = g.m_winHeight - (g.m_sciy + g.m_scih); + vport.dwWidth = g.m_sciw; + vport.dwHeight = g.m_scih; + D3DVALUE dvClipX = (2.f * g.m_scix) / g.m_vww - 1.0f; + D3DVALUE dvClipY = (2.f * (g.m_sciy + g.m_scih)) / g.m_vwh - 1.0f; + D3DVALUE dvClipWidth = (2.f * g.m_sciw) / g.m_vww; + D3DVALUE dvClipHeight = (2.f * g.m_scih) / g.m_vwh; + D3DMATRIX c; + c._11 = 2.f / dvClipWidth; + c._21 = 0.f; + c._31 = 0.f; + c._41 = -1.f - 2.f * (dvClipX / dvClipWidth); + c._12 = 0.f; + c._22 = 2.f / dvClipHeight; + c._32 = 0.f; + c._42 = -1.f - 2.f * (dvClipY / dvClipHeight); + c._13 = 0.f; + c._23 = 0.f; + c._33 = 1.f; + c._43 = 0.f; + c._14 = 0.f; + c._24 = 0.f; + c._34 = 0.f; + c._44 = 1.f; + g.m_d3ddev->MultiplyTransform(D3DTRANSFORMSTATE_PROJECTION, &c); + } + else + { +updvwp: + vport.dwX = g.m_vwx; + vport.dwY = g.m_winHeight - (g.m_vwy + g.m_vwh); + vport.dwWidth = g.m_vww; + vport.dwHeight = g.m_vwh; + } + g.m_d3ddev->SetViewport(&vport); + g.m_updvwp = FALSE; +} + +static void QuakeSetTexturingState() +{ + if (g.m_updvwp) + QuakeUpdateViewport(); + if (g.m_texturing == TRUE) { + if (g.m_texHandleValid == FALSE) { + TexInfo &ti = g.m_tex[g.m_curstagebinding[0]]; + + if (ti.m_dwStage != 0) + { + g.m_d3ddev->DeleteStateBlock(ti.m_block); + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ADDRESSU,ti.m_addu); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ADDRESSV,ti.m_addv); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_MAGFILTER,ti.m_magmode); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_MINFILTER,ti.m_minmode); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_MIPFILTER,ti.m_mipmode); + g.m_d3ddev->SetTexture(0, ti.m_ddsurf); + g.m_d3ddev->EndStateBlock(&ti.m_block); + ti.m_dwStage = 0; + ti.m_capture = FALSE; + } + else if (ti.m_capture) + { + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ADDRESSU,ti.m_addu); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ADDRESSV,ti.m_addv); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_MAGFILTER,ti.m_magmode); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_MINFILTER,ti.m_minmode); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_MIPFILTER,ti.m_mipmode); + g.m_d3ddev->SetTexture(0, ti.m_ddsurf); + g.m_d3ddev->CaptureStateBlock(ti.m_block); + ti.m_capture = FALSE; + } + else + g.m_d3ddev->ApplyStateBlock(ti.m_block); + + switch (g.m_blendmode[0]) { + case GL_REPLACE: + switch (ti.m_internalformat) { + case GL_RGB5_A1: + case GL_RGB: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[0][0]); + break; + case GL_RGBA: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[0][1]); + break; + } + break; + case GL_MODULATE: + switch (ti.m_internalformat) { + case GL_ALPHA: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[0][8]); + break; + case GL_LUMINANCE: + case GL_RGB5_A1: + case GL_RGB: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[0][2]); + break; + case GL_RGBA: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[0][3]); + break; + } + break; + case GL_DECAL: + switch (ti.m_internalformat) { + case GL_RGB5_A1: + case GL_RGB: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[0][4]); + break; + case GL_RGBA: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[0][5]); + break; + } + break; + case GL_BLEND: + switch (ti.m_internalformat) { + case GL_RGB5_A1: + case GL_RGB: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[0][6]); + break; + case GL_RGBA: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[0][7]); + break; + } + break; + } + + if (g.m_mtex != FALSE) { + TexInfo &ti2 = g.m_tex[g.m_curstagebinding[1]]; + + if (ti2.m_dwStage != 1) + { + g.m_d3ddev->DeleteStateBlock(ti2.m_block); + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ADDRESSU,ti2.m_addu); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ADDRESSV,ti2.m_addv); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_MAGFILTER,ti2.m_magmode); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_MINFILTER,ti2.m_minmode); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_MIPFILTER,ti2.m_mipmode); + g.m_d3ddev->SetTexture(1, ti2.m_ddsurf); + g.m_d3ddev->EndStateBlock(&ti2.m_block); + ti2.m_dwStage = 1; + ti2.m_capture = FALSE; + } + else if (ti2.m_capture) + { + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ADDRESSU,ti2.m_addu); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ADDRESSV,ti2.m_addv); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_MAGFILTER,ti2.m_magmode); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_MINFILTER,ti2.m_minmode); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_MIPFILTER,ti2.m_mipmode); + g.m_d3ddev->SetTexture(1, ti2.m_ddsurf); + g.m_d3ddev->CaptureStateBlock(ti2.m_block); + ti2.m_capture = FALSE; + } + else + g.m_d3ddev->ApplyStateBlock(ti2.m_block); + + switch (g.m_blendmode[1]) { + case GL_REPLACE: + switch (ti2.m_internalformat) { + case GL_RGB5_A1: + case GL_RGB: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[1][0]); + break; + case GL_RGBA: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[1][1]); + break; + } + break; + case GL_MODULATE: + switch (ti2.m_internalformat) { + case GL_RGB5_A1: + case GL_RGB: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[1][2]); + break; + case GL_RGBA: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[1][3]); + break; + } + break; + case GL_DECAL: + switch (ti2.m_internalformat) { + case GL_RGB5_A1: + case GL_RGB: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[1][4]); + break; + case GL_RGBA: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[1][5]); + break; + } + break; + case GL_BLEND: + switch (ti2.m_internalformat) { + case GL_RGB5_A1: + case GL_RGB: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[1][6]); + break; + case GL_RGBA: + g.m_d3ddev->ApplyStateBlock(g.m_shaders[1][7]); + break; + } + break; + } + } + g.m_texHandleValid = TRUE; + } + + if ((g.m_texgen[0] && g.m_texgenmode[0] == GL_OBJECT_LINEAR) || + (g.m_texgen[1] && g.m_texgenmode[1] == GL_OBJECT_LINEAR)) + { + if (g.m_inversedirty) + { + D3DMATRIX world; + float det; + + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_WORLD, &world); + D3DXMatrixInverse((D3DXMATRIX *) &g.m_inverseworld,&det,(const D3DXMATRIX *) &world); + } + if ((g.m_inversedirty || g.m_objectdirty[0]) && + g.m_texgen[0] && g.m_texgenmode[0] == GL_OBJECT_LINEAR) + { + D3DMATRIX object; + GLfloat *u = g.m_texgenplane[0][0]; + GLfloat *v = g.m_texgenplane[0][1]; + + object._11 = u[0]; object._12 = v[0]; object._13 = 0; object._14 = 0; + object._21 = u[1]; object._22 = v[1]; object._23 = 0; object._24 = 0; + object._31 = u[2]; object._32 = v[2]; object._33 = 0; object._34 = 0; + object._41 = u[3]; object._42 = v[3]; object._43 = 0; object._44 = 0; + + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_TEXTURE0,&object); + g.m_d3ddev->MultiplyTransform(D3DTRANSFORMSTATE_TEXTURE0,&g.m_curtexmatrix); + g.m_d3ddev->MultiplyTransform(D3DTRANSFORMSTATE_TEXTURE0,&g.m_inverseworld); + } + if ((g.m_inversedirty || g.m_objectdirty[1]) && + g.m_texgen[1] && g.m_texgenmode[1] == GL_OBJECT_LINEAR) + { + D3DMATRIX object; + GLfloat *u = g.m_texgenplane[1][0]; + GLfloat *v = g.m_texgenplane[1][1]; + + object._11 = u[0]; object._12 = v[0]; object._13 = 0; object._14 = 0; + object._21 = u[1]; object._22 = v[1]; object._23 = 0; object._24 = 0; + object._31 = u[2]; object._32 = v[2]; object._33 = 0; object._34 = 0; + object._41 = u[3]; object._42 = v[3]; object._43 = 0; object._44 = 0; + + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_TEXTURE1,&object); + g.m_d3ddev->MultiplyTransform(D3DTRANSFORMSTATE_TEXTURE1,&g.m_inverseworld); + } + if (g.m_inversedirty) + g.m_inversedirty = FALSE; + } + } +} + +static void RawToCanon(DWORD dwFormat, DWORD dwWidth, DWORD dwHeight, const void *lpPixels, DWORD *lpdwCanon) +{ + switch (dwFormat) + { + case GL_ALPHA: + { + for (int i = 0, j = dwWidth*dwHeight; i < j; ++i) + lpdwCanon[i] = ((UCHAR *) lpPixels)[i] << 24; + } + break; + case GL_LUMINANCE: + { + for (int i = 0, j = dwWidth*dwHeight; i < j; ++i) + { + DWORD t = ((UCHAR *) lpPixels)[i]; + + lpdwCanon[i] = (t << 24) | (t << 16) | (t << 8) | t; + } + } + break; + case GL_RGB5_A1: + { + USHORT *rgba = (USHORT *) lpPixels; + + for (int i = 0, j = dwWidth * dwHeight; i < j; ++i) + lpdwCanon[i] = ((rgba[i] & 0xF800) >> 8) | 0x7 | + ((rgba[i] & 0x07C0) << 5) | 0x700 | + ((rgba[i] & 0x003E) << 18) | 0x70000; + } + break; + case GL_RGB: + { + UCHAR *rgb = (UCHAR *) lpPixels; + + for (int i = 0, j = dwWidth * dwHeight, k = 0; i < j; ++i, k += 3) + lpdwCanon[i] = rgb[k] | (rgb[k+1] << 8) | (rgb[k+2] << 16); + } + break; + case GL_RGBA: + memcpy(lpdwCanon, lpPixels, dwWidth * dwHeight * sizeof(DWORD)); + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unsupported texture format: %x\n", dwFormat); + OutputDebugString(buf); + } + } +} + +static void Resize(DWORD dwWidth, DWORD dwHeight, const DWORD *lpdwCanon, + DWORD dwNewWidth, DWORD dwNewHeight, DWORD *lpdwNewCanon) +{ + DWORD i, j; + double rx = (double) dwWidth / (double) dwNewWidth; + double ry = (double) dwHeight / (double) dwNewHeight; + + for (i = 0; i < dwNewHeight; ++i) + for (j = 0; j < dwNewWidth; ++j) + lpdwNewCanon[i * dwNewWidth + j] = lpdwCanon[((DWORD)(i * ry)) * dwWidth + (DWORD)(j * rx)]; +} + +static void CanonTo565(LPRECT lprect, const DWORD *lpdwCanon, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + USHORT *lpPixels = (USHORT*)lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (USHORT *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + lpPixels[l] = (USHORT) (((lpdwCanon[i] & 0xF8) << 8) | ((lpdwCanon[i] & 0xFC00) >> 5) | ((lpdwCanon[i] & 0xF80000) >> 19)); +} + +static void RGB5To565(LPRECT lprect, const USHORT *lpushort, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + USHORT *lpPixels = (USHORT*)lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (USHORT *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + lpPixels[l] = (lpushort[i] & 0xFFC0) | 0x20 | ((lpushort[i] & 0x3E) >> 1); +} + +static void CanonTo555(LPRECT lprect, const DWORD *lpdwCanon, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + USHORT *lpPixels = (USHORT *) lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (USHORT *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + lpPixels[l] = (USHORT) (((lpdwCanon[i] & 0xF8) << 7) | ((lpdwCanon[i] & 0xF800) >> 6) | ((lpdwCanon[i] & 0xF80000) >> 19)); +} + +static void RGB5To555(LPRECT lprect, const USHORT *lpushort, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + USHORT *lpPixels = (USHORT*)lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (USHORT *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + lpPixels[l] = lpushort[i] >> 1; +} + +static void CanonTo4444(LPRECT lprect, const DWORD *lpdwCanon, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + USHORT *lpPixels = (USHORT *) lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (USHORT *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + lpPixels[l] = (USHORT) (((lpdwCanon[i] & 0xF0) << 4) | ((lpdwCanon[i] & 0xF000) >> 8) | ((lpdwCanon[i] & 0xF00000) >> 20) | ((lpdwCanon[i] & 0xF0000000) >> 16)); +} + +static void CanonTo8888(LPRECT lprect, const DWORD *lpdwCanon, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + DWORD *lpPixels = (DWORD*)lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (DWORD *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + lpPixels[l] = ((lpdwCanon[i] & 0xFF00FF00) | ((lpdwCanon[i] & 0xFF) << 16) | ((lpdwCanon[i] & 0xFF0000) >> 16)); +} + +static void ALPHATo8888(LPRECT lprect, const UCHAR *lpuchar, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + DWORD *lpPixels = (DWORD *) lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (DWORD *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + lpPixels[l] = lpuchar[i] << 24; +} + +static void LUMINANCETo8888(LPRECT lprect, const UCHAR *lpuchar, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + DWORD *lpPixels = (DWORD *) lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (DWORD *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + { + DWORD t = lpuchar[i]; + + lpPixels[l] = t << 24 | t << 16 | t << 8 | t; + } +} + +static void RGBTo8888(LPRECT lprect, const UCHAR *lpuchar, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + DWORD *lpPixels = (DWORD *) lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (DWORD *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, i += 3, ++l) + lpPixels[l] = (lpuchar[i]) << 16 | (lpuchar[i+1] << 8) | lpuchar[i+2]; +} + +static void RGBATo8888(LPRECT lprect, const DWORD *lpdwCanon, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + DWORD *lpPixels = (DWORD *) lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (DWORD *) ((UCHAR *) lpPixels + lpddsd->lPitch)) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + lpPixels[l] = ((lpdwCanon[i] & 0xFF00FF00) | ((lpdwCanon[i] & 0xFF) << 16) | ((lpdwCanon[i] & 0xFF0000) >> 16)); +} + +static void CanonTo8(LPRECT lprect, const DWORD *lpdwCanon, LPDDSURFACEDESC2 lpddsd) +{ + LONG i, j, k, l; + UCHAR *lpPixels = (UCHAR *) lpddsd->lpSurface; + + for (k = lprect->top, i = 0; k < lprect->bottom; ++k, lpPixels = (UCHAR *) lpPixels + lpddsd->lPitch) + for (j = lprect->left, l = 0; j < lprect->right; ++j, ++i, ++l) + lpPixels[l] = (UCHAR)(lpdwCanon[i] & 0xFF); +} + +static void LoadSurface(LPDIRECTDRAWSURFACE7 lpDDS, DWORD dwFormat, + DWORD dwWidth, DWORD dwHeight, DWORD dwNewWidth, DWORD dwNewHeight, + const void *pixels) +{ + DDSURFACEDESC2 ddsd; + HRESULT ddrval; + DWORD *lpdwCanon, *lpdwNewCanon; + RECT rect; + + /* + * Lock the surface so it can be filled with the texture + */ + memset(&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + ddrval = lpDDS->Lock(NULL, &ddsd, DDLOCK_NOSYSLOCK | DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + if (ddrval != DD_OK) { + lpDDS->Release(); + OutputDebugString("Lock failed while loading surface\n"); + + return; + } + SetRect(&rect, 0, 0, ddsd.dwWidth, ddsd.dwHeight); + + if (dwFormat == GL_RGB5_A1 && ddsd.ddpfPixelFormat.dwRBitMask == 0xF800 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + RGB5To565(&rect,(const USHORT *) pixels,&ddsd); + else if (dwFormat == GL_RGB5_A1 && ddsd.ddpfPixelFormat.dwRBitMask == 0x7C00 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + RGB5To555(&rect,(const USHORT *) pixels,&ddsd); + else if (dwFormat == GL_ALPHA && ddsd.ddpfPixelFormat.dwRBitMask == 0xFF0000 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + ALPHATo8888(&rect,(const UCHAR *) pixels,&ddsd); + else if (dwFormat == GL_LUMINANCE && ddsd.ddpfPixelFormat.dwRBitMask == 0xFF0000 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + LUMINANCETo8888(&rect,(const UCHAR *) pixels,&ddsd); + else if (dwFormat == GL_RGB && ddsd.ddpfPixelFormat.dwRBitMask == 0xFF0000 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + RGBTo8888(&rect,(const UCHAR *) pixels,&ddsd); + else if (dwFormat == GL_RGBA && ddsd.ddpfPixelFormat.dwRBitMask == 0xFF0000 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + RGBATo8888(&rect,(const DWORD *) pixels,&ddsd); + else { + /* + * Convert the GL texture into a canonical format (8888), + * so that we can cleanly do image ops (such as resize) without + * having to worry about the bit format. + */ + lpdwCanon = (DWORD *) malloc(dwWidth * dwHeight * sizeof(DWORD)); + RawToCanon(dwFormat, dwWidth, dwHeight, pixels, lpdwCanon); + /* Now resize the canon image */ + if (dwWidth != dwNewWidth || dwHeight != dwNewHeight) { + lpdwNewCanon = (DWORD *) malloc(dwNewWidth * dwNewHeight * sizeof(DWORD)); + Resize(dwWidth, dwHeight, lpdwCanon, dwNewWidth, dwNewHeight, lpdwNewCanon); + free(lpdwCanon); + } + else + lpdwNewCanon = lpdwCanon; + + /* Copy the texture into the surface */ + if (ddsd.ddpfPixelFormat.dwLuminanceBitMask == 0xFF) + CanonTo8(&rect, lpdwNewCanon, &ddsd); + else if (ddsd.ddpfPixelFormat.dwRGBAlphaBitMask == 0xF000) + CanonTo4444(&rect, lpdwNewCanon, &ddsd); + else if (ddsd.ddpfPixelFormat.dwRBitMask == 0xF800) + CanonTo565(&rect, lpdwNewCanon, &ddsd); + else if (ddsd.ddpfPixelFormat.dwRBitMask == 0x7C00) + CanonTo555(&rect, lpdwNewCanon, &ddsd); + else + CanonTo8888(&rect, lpdwNewCanon, &ddsd); + free(lpdwNewCanon); + } + + /* + * unlock the surface + */ + lpDDS->Unlock(NULL); +} + +static HRESULT LoadSubSurface(LPDIRECTDRAWSURFACE7 lpDDS, DWORD dwFormat, + DWORD dwWidth, DWORD dwHeight, const void *pixels, + LPRECT lpsubimage) +{ + DDSURFACEDESC2 ddsd; + HRESULT ddrval; + DWORD *lpdwCanon, *lpdwNewCanon; + DWORD dwNewWidth=lpsubimage->right-lpsubimage->left; + DWORD dwNewHeight=lpsubimage->bottom-lpsubimage->top; + /* + * Lock the surface so it can be filled with the texture + */ + memset(&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + ddrval = lpDDS->Lock(lpsubimage, &ddsd, DDLOCK_NOSYSLOCK | DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + if (ddrval != DD_OK) { + lpDDS->Release(); + OutputDebugString("Lock failed while loading surface\n"); + + return ddrval; + } + + if (dwFormat == GL_RGB5_A1 && ddsd.ddpfPixelFormat.dwRBitMask == 0xF800 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + RGB5To565(lpsubimage,(const USHORT *) pixels,&ddsd); + else if (dwFormat == GL_RGB5_A1 && ddsd.ddpfPixelFormat.dwRBitMask == 0x7C00 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + RGB5To555(lpsubimage,(const USHORT *) pixels,&ddsd); + else if (dwFormat == GL_ALPHA && ddsd.ddpfPixelFormat.dwRBitMask == 0xFF0000 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + ALPHATo8888(lpsubimage,(const UCHAR *) pixels,&ddsd); + else if (dwFormat == GL_LUMINANCE && ddsd.ddpfPixelFormat.dwRBitMask == 0xFF0000 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + LUMINANCETo8888(lpsubimage,(const UCHAR *) pixels,&ddsd); + else if (dwFormat == GL_RGB && ddsd.ddpfPixelFormat.dwRBitMask == 0xFF0000 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + RGBTo8888(lpsubimage,(const UCHAR *) pixels,&ddsd); + else if (dwFormat == GL_RGBA && ddsd.ddpfPixelFormat.dwRBitMask == 0xFF0000 && + dwWidth == dwNewWidth && dwHeight == dwNewHeight) + RGBATo8888(lpsubimage,(const DWORD *) pixels,&ddsd); + else { + /* + * Convert the GL texture into a canonical format (8888), + * so that we can cleanly do image ops (such as resize) without + * having to worry about the bit format. + */ + lpdwCanon = (DWORD *) malloc(dwWidth * dwHeight * sizeof(DWORD)); + RawToCanon(dwFormat, dwWidth, dwHeight, pixels, lpdwCanon); + if (dwWidth != dwNewWidth || dwHeight != dwNewHeight) + { + /* Now resize the canon image */ + lpdwNewCanon = (DWORD *) malloc(dwNewWidth * dwNewHeight * sizeof(DWORD)); + Resize(dwWidth, dwHeight, lpdwCanon, dwNewWidth, dwNewHeight, lpdwNewCanon); + free(lpdwCanon); + } + else + lpdwNewCanon=lpdwCanon; + /* Copy the texture into the surface */ + if (ddsd.ddpfPixelFormat.dwLuminanceBitMask == 0xFF) + CanonTo8(lpsubimage,lpdwNewCanon,&ddsd); + else if (ddsd.ddpfPixelFormat.dwRGBAlphaBitMask == 0xF000) + CanonTo4444(lpsubimage,lpdwNewCanon,&ddsd); + else if (ddsd.ddpfPixelFormat.dwRBitMask == 0xF800) + CanonTo565(lpsubimage,lpdwNewCanon,&ddsd); + else if (ddsd.ddpfPixelFormat.dwRBitMask == 0x7C00) + CanonTo555(lpsubimage, lpdwNewCanon, &ddsd); + else + CanonTo8888(lpsubimage, lpdwNewCanon, &ddsd); + free(lpdwNewCanon); + } + /* + * unlock the surface + */ + lpDDS->Unlock(NULL); + + return DD_OK; +} + +///////////////////////////// BEGIN API ENTRIES /////////////////////////////////////////////////// + +static void APIENTRY d3dActiveTextureARB(GLenum texture) +{ + g.m_curtgt = texture == GL_TEXTURE0_ARB ? 0 : 1; +} + +static void APIENTRY d3dAlphaFunc (GLenum func, GLclampf ref) +{ + int funcvalue = -1; + switch(func) { + case GL_NEVER: + funcvalue=D3DCMP_NEVER; + break; + case GL_LESS: + funcvalue=D3DCMP_LESS; + break; + case GL_EQUAL: + funcvalue=D3DCMP_EQUAL; + break; + case GL_LEQUAL: + funcvalue=D3DCMP_LESSEQUAL; + break; + case GL_GREATER: + funcvalue=D3DCMP_GREATER; + break; + case GL_NOTEQUAL: + funcvalue=D3DCMP_NOTEQUAL; + break; + case GL_GEQUAL: + funcvalue=D3DCMP_GREATEREQUAL; + break; + case GL_ALWAYS: + funcvalue=D3DCMP_ALWAYS; + break; + } + if (funcvalue >= 0) { + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ALPHAFUNC, funcvalue); + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ALPHAREF, (D3DFIXED)(ref * 255.f)); + } +} + +static void APIENTRY d3dArrayElement (GLint i) +{ + if (g.m_usetexcoordary[0]) + { + GLfloat *start0 = (GLfloat *) &((unsigned char *) g.m_texcoordary[0])[i*g.m_texcoordstride[0]]; + + g.m_tu = start0[0]; + g.m_tv = start0[1]; + } + if (g.m_usetexcoordary[1]) + { + GLfloat *start1 = (GLfloat *) &((unsigned char *) g.m_texcoordary[1])[i*g.m_texcoordstride[1]]; + + g.m_tu2 = start1[0]; + g.m_tv2 = start1[1]; + } + if (g.m_usecolorary) + if (g.m_colortype == GL_UNSIGNED_BYTE) + { +#ifdef _X86_ + _asm + { + mov ebx, g.m_colorary; + mov eax, i; + mov edx, g.m_colorstride; + mul edx; + cld; + mov esi, eax; + mov edx, 0x00FF00FF; + mov eax, [ebx + esi]; + mov ecx, eax; + and eax, edx; + not edx; + rol eax, 16; + and ecx, edx; + or eax, ecx; + mov g.m_color, eax; + } +#else + GLubyte *start = &((GLubyte *) g.m_colorary)[i*g.m_colorstride]; + + g.m_color = RGBA_MAKE(start[0],start[1],start[2],start[3]); +#endif + } + else + { + GLfloat *start = (GLfloat *) &((unsigned char *) g.m_colorary)[i*g.m_colorstride]; + static float two55 = 255.f; + unsigned int R, G, B, A; + +#ifdef _X86_ + __asm { + mov ebx, start; + fld [ebx]; + fld [ebx + 4]; + fld [ebx + 8]; + fld [ebx + 12]; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; + pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; + pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; + pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; + pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov g.m_color, eax; + } +#else + R = (unsigned int)(start[0] * two55); + G = (unsigned int)(start[1] * two55); + B = (unsigned int)(start[2] * two55); + A = (unsigned int)(start[3] * two55); + if (R > 255) + R = 255; + if (G > 255) + G = 255; + if (B > 255) + B = 255; + if (A > 255) + A = 255; + g.m_color = RGBA_MAKE(R, G, B, A); +#endif + } + if (g.m_usenormalary) + { + unsigned char *start = &((unsigned char *) g.m_normalary)[i*g.m_normalstride]; + + memcpy(&g.m_nx,start,sizeof(D3DVALUE)*3); + } + if (g.m_nfv[g.m_comp] + g.m_vcnt[g.m_comp] >= (VBUFSIZE - MAXVERTSPERPRIM)) + { + if (g.m_prim == GL_TRIANGLES) + { + if (g.m_vcnt[g.m_comp] % 3 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + else if (g.m_prim == GL_QUADS) + { + if (g.m_vcnt[g.m_comp] % 4 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + else if(g.m_prim == GL_LINES) + { + if (g.m_vcnt[g.m_comp] % 2 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + } + +#ifdef _X86_ + _asm + { + mov eax, i; + mov edx, g.m_vertexstride; + mul edx; + cld; + mov esi, g.m_vertexary; + mov ecx, g.m_comp; + mov edi, g.m_verts; + add esi, eax; + mov eax, g.m_vcnt[ecx * 4]; + lea edx, [eax + 1]; + mov g.m_vcnt[ecx * 4], edx; + lea edx, [ecx * 8 + 28]; + mul edx; + cld; + add edi, eax; + movsd; + movsd; + movsd; + lea esi, g.m_nx; + lea ecx, [ecx * 2 + 4]; + rep movsd; + } +#else + D3DVALUE *d3dv = &(((D3DVALUE*)g.m_verts)[g.m_vcnt[g.m_comp]++ * (g.m_comp * 2 + 7)]); + GLfloat *v = (GLfloat *) &((unsigned char *) g.m_vertexary)[i * g.m_vertexstride]; + + *(d3dv++) = v[0]; + *(d3dv++) = v[1]; + *(d3dv++) = v[2]; + memcpy(d3dv, &g.m_nx, sizeof(D3DVALUE)*3 + sizeof(D3DCOLOR) + g.m_comp*sizeof(D3DVALUE)*2); +#endif +} + +static void APIENTRY d3dBegin (GLenum mode) +{ + g.m_prim = mode; + if(g.m_texturing) + { + if(g.m_mtex) + { + g.m_comp = 2; + g.m_vcnt[2] = 0; + if(g.m_nfv[2] > (VBUFSIZE - MAXVERTSPERPRIM)) // check if space available + { + g.m_mtvbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[2] = 0; + } + else + { + g.m_mtvbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeMTVertex*)g.m_verts)[g.m_nfv[2]]); + } + } + else + { + g.m_comp = 1; + g.m_vcnt[1] = 0; + if(g.m_nfv[1] > (VBUFSIZE - MAXVERTSPERPRIM)) // check if space available + { + g.m_tvbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[1] = 0; + } + else + { + g.m_tvbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeTVertex*)g.m_verts)[g.m_nfv[1]]); + } + } + } + else + { + g.m_comp = 0; + g.m_vcnt[0] = 0; + if(g.m_nfv[0] > (VBUFSIZE - MAXVERTSPERPRIM)) // check if space available + { + g.m_vbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[0] = 0; + } + else + { + g.m_vbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeVertex*)g.m_verts)[g.m_nfv[0]]); + } + } + QuakeSetTexturingState(); +} + +static void APIENTRY d3dBindTexture (GLenum target, GLuint texture) +{ + target; + + g.m_curstagebinding[g.m_curtgt] = texture; + g.m_texHandleValid = FALSE; +} + +static void APIENTRY d3dBlendFunc (GLenum sfactor, GLenum dfactor) +{ + int svalue = -1, dvalue = -1; + + switch(sfactor) { + case GL_ZERO: + svalue=D3DBLEND_ZERO; + break; + case GL_ONE: + svalue=D3DBLEND_ONE; + break; + case GL_DST_COLOR: + svalue=D3DBLEND_DESTCOLOR; + break; + case GL_ONE_MINUS_DST_COLOR: + svalue=D3DBLEND_INVDESTCOLOR; + break; + case GL_SRC_ALPHA: + svalue=D3DBLEND_SRCALPHA; + break; + case GL_ONE_MINUS_SRC_ALPHA: + svalue=D3DBLEND_INVSRCALPHA; + break; + case GL_DST_ALPHA: + svalue=D3DBLEND_DESTALPHA; + break; + case GL_ONE_MINUS_DST_ALPHA: + svalue=D3DBLEND_INVDESTALPHA; + break; + case GL_SRC_ALPHA_SATURATE: + svalue=D3DBLEND_SRCALPHASAT; + break; + } + switch(dfactor) { + case GL_ZERO: + dvalue=D3DBLEND_ZERO; + break; + case GL_ONE: + dvalue=D3DBLEND_ONE; + break; + case GL_SRC_COLOR: + dvalue=D3DBLEND_SRCCOLOR; + break; + case GL_ONE_MINUS_SRC_COLOR: + dvalue=D3DBLEND_INVSRCCOLOR; + break; + case GL_SRC_ALPHA: + dvalue=D3DBLEND_SRCALPHA; + break; + case GL_ONE_MINUS_SRC_ALPHA: + dvalue=D3DBLEND_INVSRCALPHA; + break; + case GL_DST_ALPHA: + dvalue=D3DBLEND_DESTALPHA; + break; + case GL_ONE_MINUS_DST_ALPHA: + dvalue=D3DBLEND_INVDESTALPHA; + break; + } + + if (svalue >= 0) g.m_d3ddev->SetRenderState(D3DRENDERSTATE_SRCBLEND, (DWORD)svalue); + if (dvalue >= 0) g.m_d3ddev->SetRenderState(D3DRENDERSTATE_DESTBLEND, (DWORD)dvalue); + +} + +static void APIENTRY d3dClear (GLbitfield mask) +{ + DWORD flags = 0; + + if (mask & GL_COLOR_BUFFER_BIT) + flags |= D3DCLEAR_TARGET; + if (mask & GL_DEPTH_BUFFER_BIT) + flags |= D3DCLEAR_ZBUFFER; + if (mask & GL_STENCIL_BUFFER_BIT) + flags |= D3DCLEAR_STENCIL; + + if (g.m_updvwp) + QuakeUpdateViewport(); + + g.m_d3ddev->Clear(0, NULL, flags, g.m_clearColor, (D3DVALUE) g.m_clearDepth, 0); +} + +static void APIENTRY d3dClearColor (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) +{ + static float two55 = 255.f; + unsigned int R, G, B, A; +#ifdef _X86_ + __asm { + fld red; + fld green; + fld blue; + fld alpha; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; +pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; +pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; +pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; +pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov g.m_clearColor, eax; + } +#else + R = (unsigned int)(red * two55); + G = (unsigned int)(green * two55); + B = (unsigned int)(blue * two55); + A = (unsigned int)(alpha * two55); + if(R > 255) + R = 255; + if(G > 255) + G = 255; + if(B > 255) + B = 255; + if(A > 255) + A = 255; + g.m_clearColor = RGBA_MAKE(R, G, B, A); +#endif +} + +static void APIENTRY d3dClearDepth (GLclampd depth) +{ + g.m_clearDepth = depth; +} + +static void APIENTRY d3dClientActiveTextureARB(GLenum texture) +{ + g.m_client_active_texture_arb = texture == GL_TEXTURE0_ARB ? 0 : 1; +} + +static void APIENTRY d3dClipPlane (GLenum plane, const GLdouble *equation) +{ + plane; + equation; +} + +static void APIENTRY d3dColor3f (GLfloat red, GLfloat green, GLfloat blue) +{ + static float two55 = 255.f; + unsigned int R, G, B; +#ifdef _X86_ + __asm { + fld red; + fld green; + fld blue; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmulp st(3), st(0); + fistp B; + fistp G; + fistp R; + mov eax, B; + cmp eax, 255; + jle pt1; + mov eax, 255; +pt1: mov ebx, G; + cmp ebx, 255; + jle pt2; + mov ebx, 255; +pt2: mov ecx, R; + cmp ecx, 255; + jle pt3; + mov ecx, 255; +pt3: shl ebx, 8; + shl ecx, 16; + or eax, ebx; + or ecx, 0xFF000000; + or eax, ecx; + mov g.m_color, eax; + } +#else + R = (unsigned int)(red * two55); + G = (unsigned int)(green * two55); + B = (unsigned int)(blue * two55); + if(R > 255) + R = 255; + if(G > 255) + G = 255; + if(B > 255) + B = 255; + g.m_color = RGBA_MAKE(R, G, B, 255); +#endif +} + +static void APIENTRY d3dColor3ub (GLubyte red, GLubyte green, GLubyte blue) +{ + g.m_color = RGBA_MAKE(red, green, blue, 255); +} + +static void APIENTRY d3dColor3ubv (const GLubyte *v) +{ + g.m_color = RGBA_MAKE(v[0], v[1], v[2], 255); +} + +static void APIENTRY d3dColor4ub (GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) +{ + g.m_color = RGBA_MAKE(red, green, blue, alpha); +} + +static void APIENTRY d3dColor4ubv (const GLubyte *v) +{ +#ifdef _X86_ + _asm + { + mov ebx, v; + mov edx, 0x00FF00FF; + mov eax, [ebx]; + mov ecx, eax; + and eax, edx; + not edx; + rol eax, 16; + and ecx, edx; + or eax, ecx; + mov g.m_color, eax; + } +#else + g.m_color = RGBA_MAKE(v[0], v[1], v[2], v[3]); +#endif +} + +static void APIENTRY d3dColor4f (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + static float two55 = 255.f; + unsigned int R, G, B, A; +#ifdef _X86_ + __asm { + fld red; + fld green; + fld blue; + fld alpha; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; +pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; +pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; +pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; +pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov g.m_color, eax; + } +#else + R = (unsigned int)(red * two55); + G = (unsigned int)(green * two55); + B = (unsigned int)(blue * two55); + A = (unsigned int)(alpha * two55); + if(R > 255) + R = 255; + if(G > 255) + G = 255; + if(B > 255) + B = 255; + if(A > 255) + A = 255; + g.m_color = RGBA_MAKE(R, G, B, A); +#endif +} + +static void APIENTRY d3dColor4fv (const GLfloat *v) +{ + static float two55 = 255.f; + unsigned int R, G, B, A; + +#ifdef _X86_ + __asm { + mov ebx, v; + fld [ebx]; + fld [ebx + 4]; + fld [ebx + 8]; + fld [ebx + 12]; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; +pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; +pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; +pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; +pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov g.m_color, eax; + } +#else + R = (unsigned int)(v[0] * two55); + G = (unsigned int)(v[1] * two55); + B = (unsigned int)(v[2] * two55); + A = (unsigned int)(v[3] * two55); + if(R > 255) + R = 255; + if(G > 255) + G = 255; + if(B > 255) + B = 255; + if(A > 255) + A = 255; + g.m_color = RGBA_MAKE(R, G, B, A); +#endif +} + +static void APIENTRY d3dColorPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) +{ + if (size == 4 && (type == GL_UNSIGNED_BYTE || type == GL_FLOAT)) + { + if (stride) + g.m_colorstride = stride; + else + g.m_colorstride = (type == GL_UNSIGNED_BYTE) ? 4 : 16; + g.m_colortype = type; + g.m_colorary = pointer; + } + else + OutputDebugString("Color array not supported\n"); +} + +static void APIENTRY d3dCullFace (GLenum mode) +{ + g.m_cullMode = mode; + if(g.m_cullEnabled == TRUE){ + DWORD statevalue; + if ((mode == GL_BACK && g.m_frontFace == GL_CCW) || + (mode == GL_FRONT && g.m_frontFace == GL_CW)) + statevalue=D3DCULL_CW; + else + statevalue=D3DCULL_CCW; + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_CULLMODE, statevalue); + } +} + +static void APIENTRY d3dGenTextures (GLsizei n, GLuint *textures) +{ + GLListManip freeTextures(&g.m_freeTextures); + + for (int i = 0; i < n; ++i) + { + textures[i] = freeTextures(); + freeTextures.remove(); + } +} + +static void APIENTRY d3dDeleteTextures (GLsizei n, const GLuint *textures) +{ + GLListManip freeTextures(&g.m_freeTextures); + + for(int i = 0; i < n; ++i) { + TexInfo &ti = g.m_tex[textures[i]]; + + if(ti.m_ddsurf != 0) { + ti.m_ddsurf->Release(); + ti.m_ddsurf = 0; + } + if(ti.m_block != 0) + { + g.m_d3ddev->DeleteStateBlock(ti.m_block); + ti.m_block = 0; + } + ti.m_capture = FALSE; + ti.m_dwStage = 0; + ti.m_minmode = D3DTFN_POINT; + ti.m_magmode = D3DTFG_LINEAR; + ti.m_mipmode = D3DTFP_LINEAR; + ti.m_addu = D3DTADDRESS_WRAP; + ti.m_addv = D3DTADDRESS_WRAP; + + freeTextures.insert(textures[i]); + } +} + +static void APIENTRY d3dDepthFunc (GLenum func) +{ + int state = -1; + switch(func) { + case GL_NEVER: + state=D3DCMP_NEVER; + break; + case GL_LESS: + state=D3DCMP_LESS; + break; + case GL_EQUAL: + state=D3DCMP_EQUAL; + break; + case GL_LEQUAL: + state=D3DCMP_LESSEQUAL; + break; + case GL_GREATER: + state=D3DCMP_GREATER; + break; + case GL_NOTEQUAL: + state=D3DCMP_NOTEQUAL; + break; + case GL_GEQUAL: + state=D3DCMP_GREATEREQUAL; + break; + case GL_ALWAYS: + state=D3DCMP_ALWAYS; + break; + } + if(state >= 0) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ZFUNC, state); +} + +static void APIENTRY d3dDepthMask (GLboolean flag) +{ + if(flag == 0) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ZWRITEENABLE, FALSE); + else + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ZWRITEENABLE, TRUE); +} + +static void APIENTRY d3dDepthRange (GLclampd zNear, GLclampd zFar) +{ + if(zFar == zNear) + { + zFar += .001; + zNear -= .001; + } + if(zNear < 0.) + zNear = 0.; + if(zNear > 1.) + zNear = 1.; + if(zFar < 0.) + zFar = 0.; + if(zFar > 1.) + zFar = 1.; + D3DVIEWPORT7 vport; + g.m_d3ddev->GetViewport(&vport); + vport.dvMinZ = (D3DVALUE)zNear; + vport.dvMaxZ = (D3DVALUE)zFar; + g.m_d3ddev->SetViewport(&vport); +} + +static void APIENTRY d3dEnd (void) +{ + if (g.m_texturing) + { + if (g.m_mtex) + { + if (g.m_vcnt[2] && g.m_prim == GL_LINE_LOOP) + { + D3DVALUE *d3dv = (D3DVALUE *) g.m_verts; + + memcpy(&g.m_nx,&d3dv[3],sizeof(D3DVALUE)*3 + sizeof(D3DCOLOR) + sizeof(D3DVALUE)*4); + d3dVertex3fv(d3dv); + } + g.m_mtvbuf->Unlock(); + if(g.m_vcnt[2] == 0) + return; + unsigned i; + switch(g.m_prim) + { + case GL_LINES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_LINELIST, g.m_mtvbuf, g.m_nfv[2], g.m_vcnt[2], 0); + g.m_nfv[2] += g.m_vcnt[2]; + break; + case GL_LINE_LOOP: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_LINESTRIP, g.m_mtvbuf, g.m_nfv[2], g.m_vcnt[2], 0); + g.m_nfv[2] += g.m_vcnt[2]; + break; + case GL_TRIANGLES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLELIST, g.m_mtvbuf, g.m_nfv[2], g.m_vcnt[2], 0); + g.m_nfv[2] += g.m_vcnt[2]; + break; + case GL_TRIANGLE_STRIP: + case GL_QUAD_STRIP: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLESTRIP, g.m_mtvbuf, g.m_nfv[2], g.m_vcnt[2], 0); + g.m_nfv[2] += g.m_vcnt[2]; + break; + case GL_POLYGON: + case GL_TRIANGLE_FAN: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_mtvbuf, g.m_nfv[2], g.m_vcnt[2], 0); + g.m_nfv[2] += g.m_vcnt[2]; + break; + case GL_QUADS: + for(i = 0; i < g.m_vcnt[2]; i += 4) + { + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_mtvbuf, g.m_nfv[2], 4, 0); + g.m_nfv[2] += 4; + } + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",g.m_prim); + OutputDebugString(buf); + } + } + } + else + { + if (g.m_vcnt[1] && g.m_prim == GL_LINE_LOOP) + { + D3DVALUE *d3dv = (D3DVALUE *) g.m_verts; + + memcpy(&g.m_nx,&d3dv[3],sizeof(D3DVALUE)*3 + sizeof(D3DCOLOR) + sizeof(D3DVALUE)*2); + d3dVertex3fv(d3dv); + } + g.m_tvbuf->Unlock(); + if(g.m_vcnt[1] == 0) + return; + unsigned i; + switch(g.m_prim) + { + case GL_LINES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_LINELIST, g.m_tvbuf, g.m_nfv[1], g.m_vcnt[1], 0); + g.m_nfv[1] += g.m_vcnt[1]; + break; + case GL_LINE_LOOP: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_LINESTRIP, g.m_tvbuf, g.m_nfv[1], g.m_vcnt[1], 0); + g.m_nfv[1] += g.m_vcnt[1]; + break; + case GL_TRIANGLES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLELIST, g.m_tvbuf, g.m_nfv[1], g.m_vcnt[1], 0); + g.m_nfv[1] += g.m_vcnt[1]; + break; + case GL_TRIANGLE_STRIP: + case GL_QUAD_STRIP: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLESTRIP, g.m_tvbuf, g.m_nfv[1], g.m_vcnt[1], 0); + g.m_nfv[1] += g.m_vcnt[1]; + break; + case GL_POLYGON: + case GL_TRIANGLE_FAN: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_tvbuf, g.m_nfv[1], g.m_vcnt[1], 0); + g.m_nfv[1] += g.m_vcnt[1]; + break; + case GL_QUADS: + for(i = 0; i < g.m_vcnt[1]; i += 4) + { + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_tvbuf, g.m_nfv[1], 4, 0); + g.m_nfv[1] += 4; + } + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",g.m_prim); + OutputDebugString(buf); + } + } + } + } + else + { + if (g.m_vcnt[0] && g.m_prim == GL_LINE_LOOP) + { + D3DVALUE *d3dv = (D3DVALUE *) g.m_verts; + + memcpy(&g.m_nx,&d3dv[3],sizeof(D3DVALUE)*3 + sizeof(D3DCOLOR)); + d3dVertex3fv(d3dv); + } + g.m_vbuf->Unlock(); + if(g.m_vcnt[0] == 0) + return; + unsigned i; + switch(g.m_prim) + { + case GL_LINES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_LINELIST, g.m_vbuf, g.m_nfv[0], g.m_vcnt[0], 0); + g.m_nfv[0] += g.m_vcnt[0]; + break; + case GL_LINE_LOOP: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_LINESTRIP, g.m_vbuf, g.m_nfv[0], g.m_vcnt[0], 0); + g.m_nfv[0] += g.m_vcnt[0]; + break; + case GL_TRIANGLES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLELIST, g.m_vbuf, g.m_nfv[0], g.m_vcnt[0], 0); + g.m_nfv[0] += g.m_vcnt[0]; + break; + case GL_TRIANGLE_STRIP: + case GL_QUAD_STRIP: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLESTRIP, g.m_vbuf, g.m_nfv[0], g.m_vcnt[0], 0); + g.m_nfv[0] += g.m_vcnt[0]; + break; + case GL_POLYGON: + case GL_TRIANGLE_FAN: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_vbuf, g.m_nfv[0], g.m_vcnt[0], 0); + g.m_nfv[0] += g.m_vcnt[0]; + break; + case GL_QUADS: + for(i = 0; i < g.m_vcnt[0]; i += 4) + { + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_vbuf, g.m_nfv[0], 4, 0); + g.m_nfv[0] += 4; + } + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",g.m_prim); + OutputDebugString(buf); + } + } + } +} + +static void APIENTRY d3dDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) +{ + if (count == 0) + return; + + if (count > g.m_numIndices) + { + g.m_numIndices = count; + delete [] g.m_wIndices; + g.m_wIndices = new WORD[g.m_numIndices]; + } + + unsigned min, max, vcount; + GLsizei i; + + if (g.m_lckcount != 0) + { + switch (type) + { + case GL_UNSIGNED_BYTE: + for (i = 0; i < count; ++i) + g.m_wIndices[i] = (WORD) ((unsigned char*) indices)[i]; + break; + case GL_UNSIGNED_SHORT: + for (i = 0; i < count; ++i) + g.m_wIndices[i] = (WORD) ((unsigned short*) indices)[i]; + break; + case GL_UNSIGNED_INT: + for (i = 0; i < count; ++i) + g.m_wIndices[i] = (WORD) ((unsigned *) indices)[i]; + break; + } + min = g.m_lckfirst; + max = g.m_lckfirst+g.m_lckcount - 1; + vcount = g.m_lckcount; + } + else + { + min = 65535; + max = 0; + switch (type) + { + case GL_UNSIGNED_BYTE: + for (i = 0; i < count; ++i) + { + unsigned t = ((unsigned char *) indices)[i]; + if (t < min) + min = t; + if (t > max) + max = t; + } + + for (i = 0; i < count; ++i) + g.m_wIndices[i] = (WORD) (((unsigned char*) indices)[i] - min); + break; + case GL_UNSIGNED_SHORT: + for (i = 0; i < count; ++i) + { + unsigned t = ((unsigned short *) indices)[i]; + + if (t < min) + min = t; + if (t > max) + max = t; + } + + for (i = 0; i < count; ++i) + g.m_wIndices[i] = (WORD) (((unsigned short *) indices)[i] - min); + break; + case GL_UNSIGNED_INT: + for (i = 0; i < count; ++i) + { + unsigned t = ((unsigned int *) indices)[i]; + + if (t < min) + min = t; + if (t > max) + max = t; + } + + for (i = 0; i < count; ++i) + g.m_wIndices[i] = (WORD) (((unsigned int *) indices)[i] - min); + break; + } + vcount = max-min + 1; + } + + if (g.m_texturing) + if (g.m_mtex) + { + g.m_comp = 2; + if (g.m_nfv[2] > (VBUFSIZE - vcount)) // check if space available + { + g.m_mtvbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[2] = 0; + } + else + { + g.m_mtvbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeMTVertex*)g.m_verts)[g.m_nfv[2]]); + } + } + else + { + g.m_comp = 1; + if (g.m_nfv[1] > (VBUFSIZE - vcount)) // check if space available + { + g.m_tvbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[1] = 0; + } + else + { + g.m_tvbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeTVertex*)g.m_verts)[g.m_nfv[1]]); + } + } + else + { + g.m_comp = 0; + if (g.m_nfv[0] > (VBUFSIZE - vcount)) // check if space available + { + g.m_vbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[0] = 0; + } + else + { + g.m_vbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeVertex*)g.m_verts)[g.m_nfv[0]]); + } + } + + unsigned dstride = 7 + g.m_comp*2; + + if (g.m_texturing) + { + if (g.m_usetexcoordary[0]) + { + unsigned char *tex0 = &((unsigned char *) g.m_texcoordary[0])[min*g.m_texcoordstride[0]]; + D3DVALUE *data = (D3DVALUE *) g.m_verts+7; + + for (i = 0; i < vcount; ++i) + { + memcpy(data,tex0,2*sizeof(GLfloat)); + tex0 += g.m_texcoordstride[0]; + data += dstride; + } + } + else if (g.m_texgen[0] && g.m_texgenmode[0] == GL_SPHERE_MAP) + { + unsigned char *vtx = &((unsigned char *) g.m_vertexary)[min*g.m_vertexstride]; + D3DVALUE *data = (D3DVALUE *) g.m_verts+7; + + // Get the current world-view matrix + D3DMATRIX world; + + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_WORLD, &world); + + // Extract world-view matrix elements for speed + FLOAT m11 = world._11, m21 = world._21, m31 = world._31; + FLOAT m12 = world._12, m22 = world._22, m32 = world._32; + FLOAT m13 = world._13, m23 = world._23, m33 = world._33; + + // Loop through the vertices, transforming each one and calculating + // the correct texture coordinates. + for (i = 0; i < vcount; ++i) + { + FLOAT nx = ((FLOAT *) vtx)[0]; + FLOAT ny = ((FLOAT *) vtx)[1]; + FLOAT nz = ((FLOAT *) vtx)[2]; + + // Check the z-component, to skip any vertices that face backwards + if (nx*m13 + ny*m23 + nz*m33 > 0.0f) + continue; + + // Assign the spheremap's texture coordinates + data[0] = 0.5f * (1.0f + (nx*m11 + ny*m21 + nz*m31)); + data[1] = 0.5f * (1.0f - (nx*m12 + ny*m22 + nz*m32)); + + vtx += g.m_vertexstride; + data += dstride; + } + } + else + { + D3DVALUE *data = (D3DVALUE *) g.m_verts+7; + + for (i = 0; i < vcount; ++i) + { + memcpy(data,&g.m_tu,2*sizeof(GLfloat)); + data += dstride; + } + } + + if (g.m_mtex) + if (g.m_usetexcoordary[1]) + { + unsigned char *tex1 = &((unsigned char *) g.m_texcoordary[1])[min*g.m_texcoordstride[1]]; + D3DVALUE *data = (D3DVALUE *) g.m_verts+9; + + for (i = 0; i < vcount; ++i) + { + memcpy(data,tex1,2*sizeof(GLfloat)); + tex1 += g.m_texcoordstride[1]; + data += dstride; + } + } + else if (g.m_texgen[1] && g.m_texgenmode[1] == GL_SPHERE_MAP) + { + unsigned char *vtx = &((unsigned char *) g.m_vertexary)[min*g.m_vertexstride]; + D3DVALUE *data = (D3DVALUE *) g.m_verts+9; + + // Get the current world-view matrix + D3DMATRIX world; + + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_WORLD, &world); + + // Extract world-view matrix elements for speed + FLOAT m11 = world._11, m21 = world._21, m31 = world._31; + FLOAT m12 = world._12, m22 = world._22, m32 = world._32; + FLOAT m13 = world._13, m23 = world._23, m33 = world._33; + + // Loop through the vertices, transforming each one and calculating + // the correct texture coordinates. + for (i = 0; i < vcount; ++i) + { + FLOAT nx = ((FLOAT *) vtx)[0]; + FLOAT ny = ((FLOAT *) vtx)[1]; + FLOAT nz = ((FLOAT *) vtx)[2]; + + // Check the z-component, to skip any vertices that face backwards + if (nx*m13 + ny*m23 + nz*m33 > 0.0f) + continue; + + // Assign the spheremap's texture coordinates + data[0] = 0.5f * (1.0f + (nx*m11 + ny*m21 + nz*m31)); + data[1] = 0.5f * (1.0f - (nx*m12 + ny*m22 + nz*m32)); + + vtx += g.m_vertexstride; + data += dstride; + } + } + else + { + D3DVALUE *data = (D3DVALUE *) g.m_verts+9; + + for (i = 0; i < vcount; ++i) + { + memcpy(data,&g.m_tu2,2*sizeof(GLfloat)); + data += dstride; + } + } + } + + if (g.m_usenormalary) + { + unsigned char *nml = &((unsigned char *) g.m_normalary)[min*g.m_normalstride]; + D3DVALUE *data = (D3DVALUE *) g.m_verts+3; + + for (i = 0; i < vcount; ++i) + { + memcpy(data,nml,3*sizeof(GLfloat)); + nml += g.m_normalstride; + data += dstride; + } + } + + if (g.m_usecolorary) + { + if (g.m_colortype == GL_UNSIGNED_BYTE) + { +#ifdef _X86_ + DWORD ustride = (dstride-1) * 4; + + _asm + { + mov esi, min; + mov ecx, max; + mov edx, 0x00FF00FF; + mov ebx, g.m_colorary; + mov edi, g.m_verts; + add edi, 24; + sub ecx, esi; + lea esi, [ebx + esi * 4]; + inc ecx; + cld; +lp1: lodsd; + mov ebx, eax; + and eax, edx; + not edx; + rol eax, 16; + and ebx, edx; + not edx; + or eax, ebx; + stosd; + add edi, ustride; + loop lp1; + } +#else + GLubyte *clr = &((GLubyte *) g.m_colorary)[min*g.m_colorstride]; + D3DCOLOR *data = (D3DCOLOR *) g.m_verts+6; + + for (i = 0; i < vcount; i++) + { + data[0] = RGBA_MAKE(clr[0],clr[1],clr[2],clr[3]); + clr += g.m_colorstride; + data += dstride; + } +#endif + } + else + { + GLubyte *clr = &((GLubyte *) g.m_colorary)[min*g.m_colorstride]; + D3DCOLOR *data = (D3DCOLOR *) g.m_verts+6; + + for (i = 0; i < vcount; ++i) + { + static float two55 = 255.f; + unsigned int R, G, B, A; + +//#ifdef _X86_ +#if 0 + __asm { + mov ebx, clr; + fld [ebx]; + fld [ebx + 4]; + fld [ebx + 8]; + fld [ebx + 12]; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; + pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; + pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; + pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; + pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov data, eax; + } +#else + GLfloat *fclr = (GLfloat *) clr; + + R = (unsigned int)(fclr[0] * two55); + G = (unsigned int)(fclr[1] * two55); + B = (unsigned int)(fclr[2] * two55); + A = (unsigned int)(fclr[3] * two55); + if (R > 255) + R = 255; + if (G > 255) + G = 255; + if (B > 255) + B = 255; + if (A > 255) + A = 255; + data[0] = RGBA_MAKE(R, G, B, A); +#endif + clr += g.m_colorstride; + data += dstride; + } + } + } + else + { + D3DCOLOR *data = (D3DCOLOR *) g.m_verts+6; + + for (i = 0; i < vcount; ++i) + { + memcpy(data,&g.m_color,sizeof(D3DCOLOR)); + data += dstride; + } + } + + AssertFatal(g.m_usevertexary, "We're always using a vertex array...right?"); + + { + unsigned char *vtx = &((unsigned char *) g.m_vertexary)[min*g.m_vertexstride]; + D3DVALUE *data = (D3DVALUE *) g.m_verts; + + for (i = 0; i < vcount; ++i) + { + memcpy(data,vtx,3*sizeof(GLfloat)); + vtx += g.m_vertexstride; + data += dstride; + } + } + + QuakeSetTexturingState(); + + if (g.m_texturing) + if (g.m_mtex) + { + g.m_mtvbuf->Unlock(); + switch (mode) + { + case GL_LINES: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_LINELIST, g.m_mtvbuf, g.m_nfv[2], vcount, g.m_wIndices, count, 0); + g.m_nfv[2] += vcount; + break; + case GL_TRIANGLES: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLELIST, g.m_mtvbuf, g.m_nfv[2], vcount, g.m_wIndices, count, 0); + g.m_nfv[2] += vcount; + break; + case GL_TRIANGLE_STRIP: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLESTRIP, g.m_mtvbuf, g.m_nfv[2], vcount, g.m_wIndices, count, 0); + g.m_nfv[2] += vcount; + break; + case GL_POLYGON: + case GL_TRIANGLE_FAN: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_mtvbuf, g.m_nfv[2], vcount, g.m_wIndices, count, 0); + g.m_nfv[2] += vcount; + break; + case GL_QUADS: + for (i = 0; i < count; i += 4) + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_mtvbuf, g.m_nfv[2], vcount, &g.m_wIndices[i], 4, 0); + g.m_nfv[2] += vcount; + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",mode); + OutputDebugString(buf); + } + } + } + else + { + g.m_tvbuf->Unlock(); + switch (mode) + { + case GL_LINES: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_LINELIST, g.m_tvbuf, g.m_nfv[1], vcount, g.m_wIndices, count, 0); + g.m_nfv[1] += vcount; + break; + case GL_TRIANGLES: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLELIST, g.m_tvbuf, g.m_nfv[1], vcount, g.m_wIndices, count, 0); + g.m_nfv[1] += vcount; + break; + case GL_TRIANGLE_STRIP: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLESTRIP, g.m_tvbuf, g.m_nfv[1], vcount, g.m_wIndices, count, 0); + g.m_nfv[1] += vcount; + break; + case GL_POLYGON: + case GL_TRIANGLE_FAN: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_tvbuf, g.m_nfv[1], vcount, g.m_wIndices, count, 0); + g.m_nfv[1] += vcount; + break; + case GL_QUADS: + for (i = 0; i < count; i += 4) + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_tvbuf, g.m_nfv[1], vcount, &g.m_wIndices[i], 4, 0); + g.m_nfv[1] += vcount; + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",mode); + OutputDebugString(buf); + } + } + } + else + { + g.m_vbuf->Unlock(); + switch (mode) + { + case GL_LINES: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_LINELIST, g.m_vbuf, g.m_nfv[0], vcount, g.m_wIndices, count, 0); + g.m_nfv[0] += vcount; + break; + case GL_TRIANGLES: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLELIST, g.m_vbuf, g.m_nfv[0], vcount, g.m_wIndices, count, 0); + g.m_nfv[0] += vcount; + break; + case GL_TRIANGLE_STRIP: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLESTRIP, g.m_vbuf, g.m_nfv[0], vcount, g.m_wIndices, count, 0); + g.m_nfv[0] += vcount; + break; + case GL_POLYGON: + case GL_TRIANGLE_FAN: + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_vbuf, g.m_nfv[0], vcount, g.m_wIndices, count, 0); + g.m_nfv[0] += vcount; + break; + case GL_QUADS: + for (i = 0; i < count; i += 4) + g.m_d3ddev->DrawIndexedPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_vbuf, g.m_nfv[0], vcount, &g.m_wIndices[i], 4, 0); + g.m_nfv[0] += vcount; + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",mode); + OutputDebugString(buf); + } + } + } +} + +static void APIENTRY d3dViewport (GLint x, GLint y, GLsizei width, GLsizei height) +{ + g.m_vwx = x; + g.m_vwy = y; + g.m_vww = width; + g.m_vwh = height; + g.m_updvwp = TRUE; +} + +static void APIENTRY d3dLineWidth (GLfloat width) +{ + width; +} + +static void APIENTRY d3dLoadIdentity (void) +{ + D3DMATRIX unity; + + unity._11 = 1.0f; unity._12 = 0.0f; unity._13 = 0.0f; unity._14 = 0.0f; + unity._21 = 0.0f; unity._22 = 1.0f; unity._23 = 0.0f; unity._24 = 0.0f; + unity._31 = 0.0f; unity._32 = 0.0f; unity._33 = 1.0f; unity._34 = 0.0f; + unity._41 = 0.0f; unity._42 = 0.0f; unity._43 = 0.0f; unity._44 = 1.0f; + g.m_d3ddev->SetTransform(g.m_matrixMode, &unity); + + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + g.m_inversedirty = TRUE; + else if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_TEXTURE0, &g.m_curtexmatrix); +} + +static void APIENTRY d3dMatrixMode (GLenum mode) +{ + if (mode == GL_MODELVIEW) + g.m_matrixMode = D3DTRANSFORMSTATE_WORLD; + else if (mode == GL_PROJECTION) + g.m_matrixMode = D3DTRANSFORMSTATE_PROJECTION; + else + g.m_matrixMode = D3DTRANSFORMSTATE_TEXTURE0; +} + +static void APIENTRY d3dDisable (GLenum cap) +{ + switch(cap) + { + case GL_DEPTH_TEST: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ZENABLE, FALSE); + break; + case GL_CULL_FACE: + g.m_cullEnabled = FALSE; + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_CULLMODE, D3DCULL_NONE); + break; + case GL_FOG: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_FOGENABLE, FALSE); + break; + case GL_BLEND: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, FALSE); + break; + case GL_CLIP_PLANE0: + case GL_CLIP_PLANE1: + case GL_CLIP_PLANE2: + case GL_CLIP_PLANE3: + case GL_CLIP_PLANE4: + case GL_CLIP_PLANE5: + break; + case GL_POLYGON_OFFSET_FILL: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ZBIAS, 0); + break; + case GL_STENCIL_TEST: + break; + case GL_SCISSOR_TEST: + g.m_scissoring = FALSE; + glViewport(g.m_vwx, g.m_vwy, g.m_vww, g.m_vwh); + break; + case GL_TEXTURE_2D: + if (g.m_curtgt == 0) + { + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_DISABLE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + g.m_texturing = FALSE; + } + else + { + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_DISABLE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + g.m_mtex = FALSE; + } + g.m_texHandleValid = FALSE; + break; + case GL_ALPHA_TEST: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ALPHATESTENABLE, FALSE); + break; + case GL_LIGHTING: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_LIGHTING, FALSE); + break; + case GL_TEXTURE_GEN_S: + case GL_TEXTURE_GEN_T: + g.m_texgen[g.m_curtgt] = FALSE; + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXCOORDINDEX, + g.m_curtgt); + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); + break; + case GL_LIGHT0: + g.m_d3ddev->LightEnable(0, FALSE); + break; + case GL_LIGHT1: + g.m_d3ddev->LightEnable(1, FALSE); + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: glDisable on this cap not supported: %x\n", cap); + OutputDebugString(buf); + } + } +} + +static void APIENTRY d3dDisableClientState (GLenum array) +{ + switch(array) + { + case GL_COLOR_ARRAY: + g.m_usecolorary = FALSE; + break; + case GL_TEXTURE_COORD_ARRAY: + g.m_usetexcoordary[g.m_client_active_texture_arb] = FALSE; + break; + case GL_VERTEX_ARRAY: + g.m_usevertexary = FALSE; + break; + case GL_NORMAL_ARRAY: + g.m_usenormalary = FALSE; + break; + case GL_FOG_COORDINATE_ARRAY_EXT: + g.m_usefogary = FALSE; + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Array not supported: %x\n", array); + OutputDebugString(buf); + } + } +} + +static void APIENTRY d3dDrawBuffer (GLenum mode) +{ + mode; +} + +static void APIENTRY d3dEnable (GLenum cap) +{ + switch (cap) { + case GL_DEPTH_TEST: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ZENABLE, TRUE); + break; + case GL_CULL_FACE: + g.m_cullEnabled = TRUE; + if ((g.m_cullMode == GL_BACK && g.m_frontFace == GL_CCW) || + (g.m_cullMode == GL_FRONT && g.m_frontFace == GL_CW)) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_CULLMODE, D3DCULL_CW); + else + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_CULLMODE, D3DCULL_CCW); + break; + case GL_FOG: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_FOGENABLE, TRUE); + break; + case GL_BLEND: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, TRUE); + break; + case GL_CLIP_PLANE0: + case GL_CLIP_PLANE1: + case GL_CLIP_PLANE2: + case GL_CLIP_PLANE3: + case GL_CLIP_PLANE4: + case GL_CLIP_PLANE5: + break; + case GL_POLYGON_OFFSET_FILL: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ZBIAS, g.m_zbias); + break; + case GL_SCISSOR_TEST: + g.m_scissoring = TRUE; + g.m_updvwp = TRUE; + break; + case GL_TEXTURE_2D: + if (g.m_curtgt == 0) + g.m_texturing = TRUE; + else + g.m_mtex = TRUE; + g.m_texHandleValid = FALSE; + break; + case GL_ALPHA_TEST: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ALPHATESTENABLE, TRUE); + break; + case GL_LIGHTING: + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_LIGHTING, TRUE); + break; + case GL_TEXTURE_GEN_S: + case GL_TEXTURE_GEN_T: + g.m_texgen[g.m_curtgt] = TRUE; + if (g.m_texgenmode[g.m_curtgt] == GL_OBJECT_LINEAR) + { + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXCOORDINDEX, + g.m_curtgt | D3DTSS_TCI_CAMERASPACEPOSITION); + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + } + else + { + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXCOORDINDEX, + g.m_curtgt); + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); + } + g.m_texcoordary[g.m_curtgt] = g.m_spherecoords; + g.m_texcoordstride[g.m_curtgt] = 8; + break; + case GL_LIGHT0: + g.m_d3ddev->SetLight(0, &g.m_lights[0]); + g.m_d3ddev->LightEnable(0, TRUE); + break; + case GL_LIGHT1: + g.m_d3ddev->SetLight(1, &g.m_lights[1]); + g.m_d3ddev->LightEnable(1, TRUE); + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: glEnable on this cap not supported: %x\n",cap); + OutputDebugString(buf); + } + } +} + +static void APIENTRY d3dEnableClientState (GLenum array) +{ + switch (array) + { + case GL_COLOR_ARRAY: + g.m_usecolorary = TRUE; + break; + case GL_TEXTURE_COORD_ARRAY: + g.m_usetexcoordary[g.m_client_active_texture_arb] = TRUE; + break; + case GL_VERTEX_ARRAY: + g.m_usevertexary = TRUE; + break; + case GL_NORMAL_ARRAY: + g.m_usenormalary = TRUE; + break; + case GL_FOG_COORDINATE_ARRAY_EXT: + g.m_usefogary = TRUE; + break; + default: + OutputDebugString("Wrapper: Array not supported\n"); + } +} + +static void APIENTRY d3dFogf (GLenum pname, GLfloat param) +{ + FLOAT start, end; + + switch (pname) + { + case GL_FOG_START: + start = param; + //g.m_d3ddev->SetRenderState(D3DRENDERSTATE_FOGTABLESTART, *(DWORD*)(&start)); + break; + case GL_FOG_END: + end = param; + //g.m_d3ddev->SetRenderState(D3DRENDERSTATE_FOGTABLEEND, *(DWORD*)(&end)); + break; + default: + OutputDebugString("Wrapper: Fog pname not supported\n"); + } +} + +static void APIENTRY d3dFogfv (GLenum pname, const GLfloat *params) +{ + if (pname == GL_FOG_COLOR) + { + static float two55 = 255.f; + unsigned int R, G, B, A; + +#ifdef _X86_ + __asm { + mov ebx, params; + fld [ebx]; + fld [ebx + 4]; + fld [ebx + 8]; + fld [ebx + 12]; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; + pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; + pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; + pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; + pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov g.m_fogcolor, eax; + } +#else + R = (unsigned int) (params[0] * two55); + G = (unsigned int) (params[1] * two55); + B = (unsigned int) (params[2] * two55); + A = (unsigned int) (params[3] * two55); + if (R > 255) + R = 255; + if (G > 255) + G = 255; + if (B > 255) + B = 255; + if (A > 255) + A = 255; + g.m_fogcolor = RGBA_MAKE(R, G, B, A); +#endif + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_FOGCOLOR, g.m_fogcolor); + } + else + OutputDebugString("Wrapper: Fog pname not supported\n"); +} + +static void APIENTRY d3dFogi (GLenum pname, GLint param) +{ + switch(pname) + { + case GL_FOG_COORDINATE_SOURCE_EXT: + break; + case GL_FOG_MODE: + switch (param) + { + case GL_LINEAR: + //g.m_d3ddev->SetRenderState(D3DRENDERSTATE_FOGTABLEMODE, D3DFOG_LINEAR); + break; + case GL_EXP: + //g.m_d3ddev->SetRenderState(D3DRENDERSTATE_FOGTABLEMODE, D3DFOG_EXP); + break; + case GL_EXP2: + //g.m_d3ddev->SetRenderState(D3DRENDERSTATE_FOGTABLEMODE, D3DFOG_EXP2); + break; + } + break; + default: + OutputDebugString("Wrapper: Fog pname not supported\n"); + } +} + +static void APIENTRY d3dFrustum (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + D3DMATRIX f; + + f._11 = (D3DVALUE)((2.0 * zNear) / (right - left)); + f._21 = 0.f; + f._31 = (D3DVALUE)((right + left) / (right - left)); + f._41 = 0.f; + f._12 = 0.f; + f._22 = (D3DVALUE)((2.0 * zNear) / (top - bottom)); + f._32 = (D3DVALUE)((top + bottom) / (top - bottom)); + f._42 = 0.f; + f._13 = 0.f; + f._23 = 0.f; + f._33 = (D3DVALUE)(-zFar / (zFar - zNear)); + //f._33 = (D3DVALUE)(-(zFar+zNear)/(zFar-zNear)); + f._43 = (D3DVALUE)(-(zFar * zNear) / (zFar - zNear)); + //f._43 = (D3DVALUE)(-(2.0 * zFar * zNear)/(zFar - zNear)); + f._14 = 0.f; + f._24 = 0.f; + f._34 = -1.f; + f._44 = 0.f; + g.m_d3ddev->MultiplyTransform(g.m_matrixMode, &f); + + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + g.m_inversedirty = TRUE; +} + +static GLenum APIENTRY d3dGetError (void) +{ + return GL_NO_ERROR; +} + +static void APIENTRY d3dGetDoublev (GLenum pname, GLdouble *params) +{ + switch (pname) { + case GL_MODELVIEW_MATRIX: + { + D3DMATRIX tmp; + + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_WORLD, &tmp); + for (U8 i = 0; i < 16; ++i) + params[0] = ((GLfloat *) &tmp)[i]; + } + break; + case GL_PROJECTION_MATRIX: + { + D3DMATRIX tmp; + + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_PROJECTION, &tmp); + for (U8 i = 0; i < 16; ++i) + params[0] = ((GLfloat *) &tmp)[i]; + } + break; + default: + OutputDebugString("Wrapper: Unimplemented GetDoublev query\n"); + } +} + +static void APIENTRY d3dGetFloatv (GLenum pname, GLfloat *params) +{ + switch (pname) { + case GL_MODELVIEW_MATRIX: + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_WORLD, (LPD3DMATRIX)params); + break; + case GL_PROJECTION_MATRIX: + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_PROJECTION, (LPD3DMATRIX)params); + break; + default: + OutputDebugString("Wrapper: Unimplemented GetFloatv query\n"); + } +} + +static void APIENTRY d3dGetIntegerv (GLenum pname, GLint *params) +{ + switch(pname) + { + case GL_MAX_TEXTURE_SIZE: + *params = (g.m_dd.dwMaxTextureWidth >= g.m_dd.dwMaxTextureHeight) ? g.m_dd.dwMaxTextureWidth : g.m_dd.dwMaxTextureHeight; + break; + case GL_MAX_TEXTURE_UNITS_ARB: + *params = g.m_usemtex ? 2 : 1; + break; + case GL_MODELVIEW_STACK_DEPTH: + *params = g.m_matrixStack[0].length() + 1; + break; + case GL_PROJECTION_STACK_DEPTH: + *params = g.m_matrixStack[1].length() + 1; + break; + case GL_MAX_LIGHTS: + *params = 8; + break; + default: + OutputDebugString("Wrapper: Unimplemented GetIntegerv query\n"); + } +} + +static const GLubyte* APIENTRY d3dGetString (GLenum name) +{ + switch(name) { + case GL_VENDOR: + return (const GLubyte*)"Microsoft Corp."; + case GL_RENDERER: + return (const GLubyte*)"Direct3D"; + case GL_VERSION: + return (const GLubyte*)"1.1"; + case GL_EXTENSIONS: + if(g.m_usemtex != FALSE) + return (const GLubyte*)"GL_ARB_multitexture GL_EXT_compiled_vertex_array GL_SGIS_multitexture GL_EXT_fog_coord"; + else + return (const GLubyte*)"GL_EXT_compiled_vertex_array GL_EXT_fog_coord"; + default: + OutputDebugString("Wrapper: Unimplemented GetString query\n"); + } + return (const GLubyte*)""; +} + +static void APIENTRY d3dLoadMatrixf (const GLfloat *m) +{ + if (g.m_matrixMode == D3DTRANSFORMSTATE_PROJECTION) + { + D3DMATRIX f = *((LPD3DMATRIX) m); + + f._13 = (((LPD3DMATRIX) m)->_13 + ((LPD3DMATRIX) m)->_14) * 0.5f; + f._23 = (((LPD3DMATRIX) m)->_23 + ((LPD3DMATRIX) m)->_24) * 0.5f; + f._33 = (((LPD3DMATRIX) m)->_33 + ((LPD3DMATRIX) m)->_34) * 0.5f; + f._43 = (((LPD3DMATRIX) m)->_43 + ((LPD3DMATRIX) m)->_44) * 0.5f; + g.m_d3ddev->SetTransform(g.m_matrixMode, &f); + } + else + { + g.m_d3ddev->SetTransform(g.m_matrixMode, (LPD3DMATRIX) m); + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + g.m_inversedirty = TRUE; + else if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_TEXTURE0, &g.m_curtexmatrix); + } +} + +static void APIENTRY d3dLockArraysEXT(GLint first, GLsizei count) +{ + g.m_lckfirst = first; + g.m_lckcount = count; +} + +static void APIENTRY d3dMTexCoord2fSGIS(GLenum target, GLfloat s, GLfloat t) +{ + if(target == GL_TEXTURE0_SGIS) { + g.m_tu = s; + g.m_tv = t; + } + else { + g.m_tu2 = s; + g.m_tv2 = t; + } +} + +static void APIENTRY d3dMultiTexCoord2fARB (GLenum texture, GLfloat s, GLfloat t) +{ + if(texture == GL_TEXTURE0_ARB) + { + g.m_tu = s; + g.m_tv = t; + } + else + { + g.m_tu2 = s; + g.m_tv2 = t; + } +} + +static void APIENTRY d3dMultiTexCoord2fvARB (GLenum texture, GLfloat *v) +{ + texture; + v; + + #ifdef DODPFS + DPF("glMultiTexCoord2fvARB"); + #endif //DODPFS +} + +static void APIENTRY d3dOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + D3DMATRIX f; + + left += 0.5; + right += 0.5; + top += 0.5; + bottom += 0.5; + + f._11 = (D3DVALUE) (2.0 / (right - left)); + f._21 = 0.f; + f._31 = 0.f; + f._41 = (D3DVALUE) (-(right + left) / (right - left)); + f._12 = 0.f; + f._22 = (D3DVALUE) (2.0 / (top - bottom)); + f._32 = 0.f; + f._42 = (D3DVALUE) (-(top + bottom) / (top - bottom)); + f._13 = 0.f; + f._23 = 0.f; + f._33 = (D3DVALUE) (-1.0 / (zFar - zNear)); + f._43 = (D3DVALUE) (-zNear / (zFar - zNear)); + f._14 = 0.f; + f._24 = 0.f; + f._34 = 0.f; + f._44 = 1.f; + g.m_d3ddev->MultiplyTransform(g.m_matrixMode, &f); + + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + g.m_inversedirty = TRUE; +} + +static void APIENTRY d3dPolygonMode (GLenum face, GLenum mode) +{ + face; + + int statevalue=-1; + switch(mode) { + case GL_POINT: + statevalue=D3DFILL_POINT; + break; + case GL_LINE: + statevalue=D3DFILL_WIREFRAME; + break; + case GL_FILL: + statevalue=D3DFILL_SOLID; + break; + } + if(statevalue >= 0) { + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_FILLMODE, (DWORD)statevalue); + } +} + +static void APIENTRY d3dPolygonOffset (GLfloat factor, GLfloat units) +{ + factor; + + g.m_zbias = (DWORD) -units; +} + +static void APIENTRY d3dPopMatrix (void) +{ + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + { + GLListManip m(&g.m_matrixStack[0]); + + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_WORLD, &(m())); + m.remove(); + g.m_inversedirty = TRUE; + } + else if (g.m_matrixMode == D3DTRANSFORMSTATE_PROJECTION) + { + GLListManip m(&g.m_matrixStack[1]); + + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_PROJECTION, &(m())); + m.remove(); + } + else + { + GLListManip m(&g.m_matrixStack[2]); + + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_TEXTURE0, &(m())); + g.m_curtexmatrix = m(); + m.remove(); + + //if (g.m_matrixStack[2].length() == 0 && !g.m_texgen[0]) + //g.m_d3ddev->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); + } +} + +static void APIENTRY d3dPushMatrix (void) +{ + if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + { + //if (g.m_matrixStack[2].length() == 0 && !g.m_texgen[0]) + //g.m_d3ddev->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + g.m_matrixStack[2].prepend(g.m_curtexmatrix); + } + else + { + D3DMATRIX m; + + g.m_d3ddev->GetTransform(g.m_matrixMode, &m); + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + g.m_matrixStack[0].prepend(m); + else + g.m_matrixStack[1].prepend(m); + } +} + +static void APIENTRY d3dRotatef (GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + static double PI = 4. * atan(1.); + D3DMATRIX f; + double ra = angle * PI / 180.f; + float ca = (float) cos(ra); + float sa = (float) sin(ra); + + if (x == 1.f) + { + f._11 = 1.f; f._21 = 0.f; f._31 = 0.f; f._41 = 0.f; + f._12 = 0.f; f._22 = ca; f._32 = -sa; f._42 = 0.f; + f._13 = 0.f; f._23 = sa; f._33 = ca; f._43 = 0.f; + f._14 = 0.f; f._24 = 0.f; f._34 = 0.f; f._44 = 1.f; + } + else if (y == 1.f) + { + f._11 = ca; f._21 = 0.f; f._31 = sa; f._41 = 0.f; + f._12 = 0.f; f._22 = 1.f; f._32 = 0.f; f._42 = 0.f; + f._13 = -sa; f._23 = 0.f; f._33 = ca; f._43 = 0.f; + f._14 = 0.f; f._24 = 0.f; f._34 = 0.f; f._44 = 1.f; + } + else if (z == 1.f) + { + f._11 = ca; f._21 = -sa; f._31 = 0.f; f._41 = 0.f; + f._12 = sa; f._22 = ca; f._32 = 0.f; f._42 = 0.f; + f._13 = 0.f; f._23 = 0.f; f._33 = 1.f; f._43 = 0.f; + f._14 = 0.f; f._24 = 0.f; f._34 = 0.f; f._44 = 1.f; + } + else + return; + + if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_TEXTURE0,&g.m_curtexmatrix); + + g.m_d3ddev->MultiplyTransform(g.m_matrixMode, &f); + + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + g.m_inversedirty = TRUE; + else if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_TEXTURE0, &g.m_curtexmatrix); +} + +static void APIENTRY d3dScalef (GLfloat x, GLfloat y, GLfloat z) +{ + D3DMATRIX f; + + f._11 = x; f._21 = 0.f; f._31 = 0.f; f._41 = 0.f; + f._12 = 0.f; f._22 = y; f._32 = 0.f; f._42 = 0.f; + f._13 = 0.f; f._23 = 0.f; f._33 = z; f._43 = 0.f; + f._14 = 0.f; f._24 = 0.f; f._34 = 0.f; f._44 = 1.f; + + if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_TEXTURE0,&g.m_curtexmatrix); + + g.m_d3ddev->MultiplyTransform(g.m_matrixMode, &f); + + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + g.m_inversedirty = TRUE; + else if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_TEXTURE0, &g.m_curtexmatrix); +} + +static void APIENTRY d3dScissor (GLint x, GLint y, GLsizei width, GLsizei height) +{ + g.m_scix = x; + g.m_sciy = y; + g.m_sciw = width; + g.m_scih = height; + g.m_updvwp = TRUE; +} + +static void APIENTRY d3dSelectTextureSGIS(GLenum target) +{ + g.m_curtgt = target == GL_TEXTURE0_SGIS ? 0 : 1; +} + +static void APIENTRY d3dShadeModel (GLenum mode) +{ + if(mode == GL_SMOOTH) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_SHADEMODE, D3DSHADE_GOURAUD); + else + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_SHADEMODE, D3DSHADE_FLAT); +} + +static void APIENTRY d3dTexCoord2f (GLfloat s, GLfloat t) +{ + g.m_tu = s; + g.m_tv = t; +} + +static void APIENTRY d3dTexCoordPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) +{ + if (size == 2 && type == GL_FLOAT) + { + g.m_texcoordary[g.m_client_active_texture_arb] = (GLfloat *) pointer; + if (stride) + g.m_texcoordstride[g.m_client_active_texture_arb] = stride; + else + g.m_texcoordstride[g.m_client_active_texture_arb] = 8; + } + else + { + char buf[128]; + + dSprintf(buf,128,"Wrapper: TexCoord array not supported (size: %d type: %x stride: %d)\n", + size, type, stride); + OutputDebugString(buf); + } +} + +static void APIENTRY d3dTexEnvf (GLenum target, GLenum pname, GLfloat param) +{ + target; + + if(pname == GL_TEXTURE_ENV_MODE) { + g.m_blendmode[g.m_curtgt] = (int)param; + g.m_texHandleValid = FALSE; + } + else + OutputDebugString("Wrapper: GL_TEXTURE_ENV_COLOR not implemented\n"); +} + +static void APIENTRY d3dTexEnvi (GLenum target, GLenum pname, GLint param) +{ + d3dTexEnvf(target,pname,(GLfloat) param); +} + +static void APIENTRY d3dTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei glwidth, GLsizei glheight, GLint border, GLenum format, GLenum type, const GLvoid *pixels) +{ + target; + border; + format; + type; + + DWORD width, height; + TexInfo &ti = g.m_tex[g.m_curstagebinding[g.m_curtgt]]; + + /* See if texture needs to be subsampled */ + if(g.m_subsample) { + if(glwidth > 256 || glheight > 256) { + if(glwidth > glheight) { + width = 256; + height = (glheight * 256) / glwidth; + } + else { + height = 256; + width = (glwidth * 256) / glheight; + } + } + else { + width = glwidth; + height = glheight; + } + } + else { + width = glwidth; + height = glheight; + } + + /* See if texture needs to be square */ + if(g.m_makeSquare) { + if(height > width) { + width = height; + } + else { + height = width; + } + } + + if(level == 0) { + LPDIRECTDRAWSURFACE7 ddsurf; + D3DX_SURFACEFORMAT fmt; + switch(internalformat) { + case 1: + case GL_LUMINANCE: + fmt = g.m_ddLuminanceSurfFormat; + break; + case GL_RGB5: + case GL_RGB5_A1: + fmt = g.m_ddFiveBitSurfFormat; + break; + case 4: + case GL_RGBA4: + fmt = g.m_ddFourBitAlphaSurfFormat; + break; + case GL_RGB: + fmt = g.m_ddEightBitSurfFormat; + break; + case GL_RGBA: + fmt = g.m_ddEightBitAlphaSurfFormat; + break; + case GL_ALPHA: + fmt = g.m_ddAlphaSurfFormat; + break; + default: + OutputDebugString("Wrapper: Unimplemented internalformat\n"); + break; + } + DWORD flags = D3DX_TEXTURE_NOMIPMAP; + HRESULT ddrval = D3DXCreateTexture(g.m_d3ddev, + &flags, + (DWORD*)&width, (DWORD*)&height, &fmt, + NULL, // Palette + &ddsurf, + NULL); + if (ddrval != DD_OK) + { + OutputDebugString("Wrapper: CreateTexture failed\n"); + return; + } + LoadSurface(ddsurf, internalformat, glwidth, glheight, width, height, (const DWORD*)pixels); + if(ti.m_ddsurf != 0) { + ti.m_ddsurf->Release(); + } + ti.m_dwStage = g.m_curtgt; + ti.m_fmt = fmt; + ti.m_internalformat = internalformat; + ti.m_width = width; + ti.m_height = height; + ti.m_ddsurf = ddsurf; + ti.m_oldwidth = glwidth; + ti.m_oldheight = glheight; + + if(ti.m_block == 0) + { + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (g.m_curtgt, D3DTSS_ADDRESSU,ti.m_addu); + g.m_d3ddev->SetTextureStageState (g.m_curtgt, D3DTSS_ADDRESSV,ti.m_addv); + g.m_d3ddev->SetTextureStageState (g.m_curtgt, D3DTSS_MAGFILTER,ti.m_magmode); + g.m_d3ddev->SetTextureStageState (g.m_curtgt, D3DTSS_MINFILTER,ti.m_minmode); + g.m_d3ddev->SetTextureStageState (g.m_curtgt, D3DTSS_MIPFILTER,ti.m_mipmode); + g.m_d3ddev->SetTexture(g.m_curtgt, ti.m_ddsurf); + g.m_d3ddev->EndStateBlock(&ti.m_block); + ti.m_capture = FALSE; + } + else + { + ti.m_capture = TRUE; + } + } + else if(level == 1 && g.m_usemipmap) { // oops, a mipmap + LPDIRECTDRAWSURFACE7 ddsurf; + DWORD flags = 0; + HRESULT ddrval = D3DXCreateTexture(g.m_d3ddev, + &flags, + (DWORD*)&ti.m_width, (DWORD*)&ti.m_height, &ti.m_fmt, + NULL, // Palette + &ddsurf, + NULL); + if (ddrval != DD_OK) { + OutputDebugString("Wrapper: CreateSurface for texture failed\n"); + return; + } + ddsurf->Blt(NULL, ti.m_ddsurf, NULL, DDBLT_WAIT, NULL); + ti.m_ddsurf->Release(); + ti.m_ddsurf = ddsurf; + DDSURFACEDESC2 ddsd; + memset(&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP; + LPDIRECTDRAWSURFACE7 lpDDSTmp; + ti.m_ddsurf->GetAttachedSurface(&ddsd.ddsCaps, &lpDDSTmp); + LoadSurface(lpDDSTmp, internalformat, glwidth, glheight, width, height, (const DWORD*)pixels); + lpDDSTmp->Release(); + ti.m_capture = TRUE; + } + else if(g.m_usemipmap) { + LPDIRECTDRAWSURFACE7 ddsurf = ti.m_ddsurf; + ddsurf->AddRef(); + for(int i = 0; i < level; ++i) { + DDSURFACEDESC2 ddsd; + memset(&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP; + LPDIRECTDRAWSURFACE7 lpDDSTmp; + HRESULT ddrval = ddsurf->GetAttachedSurface(&ddsd.ddsCaps, &lpDDSTmp); + ddsurf->Release(); + if (ddrval == DDERR_NOTFOUND) { + return; + } + ddsurf = lpDDSTmp; + } + LoadSurface(ddsurf, internalformat, glwidth, glheight, width, height, (const DWORD*)pixels); + ddsurf->Release(); + } + g.m_texHandleValid = FALSE; +} + +static void APIENTRY d3dTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) +{ + target; + format; + type; + + TexInfo &ti = g.m_tex[g.m_curstagebinding[g.m_curtgt]]; + LPDIRECTDRAWSURFACE7 ddsurf = ti.m_ddsurf; + RECT subimage; + + if (!ddsurf) + { + // We haven't got a surface -- so just do a glTexImage2D + // NOTE: this won't work for a mip-mapped texture + d3dTexImage2D(target,level,format,width,height,0,format,type,pixels); + + return; + } + + for(int i = 0; i < level; ++i) { + DDSURFACEDESC2 ddsd; + HRESULT ddrval; + memset(&ddsd.ddsCaps, 0, sizeof(ddsd.ddsCaps)); + ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP; + LPDIRECTDRAWSURFACE7 lpDDSTmp; + ddrval = ddsurf->GetAttachedSurface(&ddsd.ddsCaps, &lpDDSTmp); + if (ddrval == DDERR_NOTFOUND) + return; + ddsurf = lpDDSTmp; + ddsurf->Release(); + } + xoffset = (xoffset * ti.m_width) / ti.m_oldwidth; + yoffset = (yoffset * ti.m_height) / ti.m_oldheight; + SetRect(&subimage, xoffset, yoffset, + (width * ti.m_width) / ti.m_oldwidth + xoffset, + (height * ti.m_height) / ti.m_oldheight + yoffset); + if (DD_OK != LoadSubSurface(ddsurf, ti.m_internalformat, width, height, (const DWORD*)pixels, &subimage)) { + OutputDebugString("Wrapper: LoadSubSurface Failure.\n"); + return; + } +} + +static void APIENTRY d3dTexParameterf (GLenum target, GLenum pname, GLfloat param) +{ + target; + + switch(pname) { + case GL_TEXTURE_MIN_FILTER: + switch((int)param) { + case GL_NEAREST: + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_minmode = D3DTFN_POINT; + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_mipmode = D3DTFP_NONE; + break; + case GL_LINEAR: + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_minmode = D3DTFN_LINEAR; + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_mipmode = D3DTFP_NONE; + break; + case GL_NEAREST_MIPMAP_NEAREST: + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_minmode = D3DTFN_POINT; + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_mipmode = D3DTFP_POINT; + break; + case GL_NEAREST_MIPMAP_LINEAR: + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_minmode = D3DTFN_POINT; + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_mipmode = D3DTFP_LINEAR; + break; + case GL_LINEAR_MIPMAP_NEAREST: + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_minmode = D3DTFN_LINEAR; + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_mipmode = D3DTFP_POINT; + break; + case GL_LINEAR_MIPMAP_LINEAR: + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_minmode = D3DTFN_LINEAR; + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_mipmode = D3DTFP_LINEAR; + break; + } + break; + case GL_TEXTURE_MAG_FILTER: + if((int)param == GL_NEAREST) + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_magmode = D3DTFG_POINT; + else + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_magmode = D3DTFG_LINEAR; + break; + case GL_TEXTURE_WRAP_S: + if((int)param == GL_CLAMP) + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_addu = D3DTADDRESS_CLAMP; + else + //GL_REPEAT falls here + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_addu = D3DTADDRESS_WRAP; + break; + case GL_TEXTURE_WRAP_T: + if((int)param == GL_CLAMP) + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_addv = D3DTADDRESS_CLAMP; + else + //GL_REPEAT falls here + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_addv = D3DTADDRESS_WRAP; + break; + } + g.m_tex[g.m_curstagebinding[g.m_curtgt]].m_capture = TRUE; + g.m_texHandleValid = FALSE; +} + +static void APIENTRY d3dTexParameteri (GLenum target, GLenum pname, GLint param) +{ + d3dTexParameterf(target,pname,(GLfloat) param); +} + +static void APIENTRY d3dTranslatef (GLfloat x, GLfloat y, GLfloat z) +{ + D3DMATRIX f; + + f._11 = 1.f; f._21 = 0.f; f._31 = 0.f; f._41 = x; + f._12 = 0.f; f._22 = 1.f; f._32 = 0.f; f._42 = y; + f._13 = 0.f; f._23 = 0.f; f._33 = 1.f; f._43 = z; + f._14 = 0.f; f._24 = 0.f; f._34 = 0.f; f._44 = 1.f; + + if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_TEXTURE0,&g.m_curtexmatrix); + + g.m_d3ddev->MultiplyTransform(g.m_matrixMode, &f); + + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + g.m_inversedirty = TRUE; + else if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_TEXTURE0, &g.m_curtexmatrix); +} + +static void APIENTRY d3dUnlockArraysEXT() +{ + g.m_lckfirst = 0; + g.m_lckcount = 0; +} + +static void APIENTRY d3dVertex2f (GLfloat x, GLfloat y) +{ + y; + + if (g.m_nfv[g.m_comp] + g.m_vcnt[g.m_comp] >= (VBUFSIZE - MAXVERTSPERPRIM)) + { + if (g.m_prim == GL_TRIANGLES) + { + if (g.m_vcnt[g.m_comp] % 3 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + else if (g.m_prim == GL_QUADS) + { + if (g.m_vcnt[g.m_comp] % 4 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + else if (g.m_prim == GL_LINES) + { + if (g.m_vcnt[g.m_comp] % 2 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + } +#ifdef _X86_ + __asm + { + mov ecx, g.m_comp; + mov edi, g.m_verts; + lea esi, x; + mov eax, g.m_vcnt[ecx * 4]; + lea edx, [eax + 1]; + mov g.m_vcnt[ecx * 4], edx; + lea edx, [ecx * 8 + 28]; + mul edx; + cld; + add edi, eax; + xor eax, eax; + movsd; + movsd; + stosd; + lea esi, g.m_nx; + lea ecx, [ecx * 2 + 4]; + rep movsd; + } +#else + D3DVALUE *d3dv = &(((D3DVALUE*)g.m_verts)[g.m_vcnt[g.m_comp]++ * (g.m_comp * 2 + 7)]); + + *(d3dv++) = x; + *(d3dv++) = y; + *(d3dv++) = 0.f; + memcpy(d3dv, &g.m_nx, (sizeof(D3DVALUE)*3 + sizeof(D3DCOLOR) + g.m_comp*sizeof(D3DVALUE)*2)); +#endif +} + +static void APIENTRY d3dVertex2i (GLint x, GLint y) +{ + d3dVertex2f((GLfloat) x, (GLfloat) y); +} + +static void APIENTRY d3dVertex3f (GLfloat x, GLfloat y, GLfloat z) +{ + y; + z; + + if (g.m_nfv[g.m_comp] + g.m_vcnt[g.m_comp] >= (VBUFSIZE - MAXVERTSPERPRIM)) + { + if (g.m_prim == GL_TRIANGLES) + { + if (g.m_vcnt[g.m_comp] % 3 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + else if(g.m_prim == GL_QUADS) + { + if (g.m_vcnt[g.m_comp] % 4 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + else if (g.m_prim == GL_LINES) + { + if (g.m_vcnt[g.m_comp] % 2 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + } +#ifdef _X86_ + __asm + { + mov ecx, g.m_comp; + mov edi, g.m_verts; + lea esi, x; + mov eax, g.m_vcnt[ecx * 4]; + lea edx, [eax + 1]; + mov g.m_vcnt[ecx * 4], edx; + lea edx, [ecx * 8 + 28]; + mul edx; + cld; + add edi, eax; + movsd; + movsd; + movsd; + lea esi, g.m_nx; + lea ecx, [ecx * 2 + 4]; + rep movsd; + } +#else + D3DVALUE *d3dv = &(((D3DVALUE*)g.m_verts)[g.m_vcnt[g.m_comp]++ * (g.m_comp * 2 + 7)]); + + *(d3dv++) = x; + *(d3dv++) = y; + *(d3dv++) = z; + memcpy(d3dv, &g.m_nx, (sizeof(D3DVALUE)*3 + sizeof(D3DCOLOR) + g.m_comp*sizeof(D3DVALUE)*2)); +#endif +} + +static void APIENTRY d3dVertex3fv (const GLfloat *v) +{ + if(g.m_nfv[g.m_comp] + g.m_vcnt[g.m_comp] >= (VBUFSIZE - MAXVERTSPERPRIM)) + { + if(g.m_prim == GL_TRIANGLES) + { + if(g.m_vcnt[g.m_comp] % 3 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + else if(g.m_prim == GL_QUADS) + { + if(g.m_vcnt[g.m_comp] % 4 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + else if(g.m_prim == GL_LINES) + { + if(g.m_vcnt[g.m_comp] % 2 == 0) + { + d3dEnd(); + d3dBegin(g.m_prim); + } + } + } +#ifdef _X86_ + __asm + { + mov ecx, g.m_comp; + mov edi, g.m_verts; + mov esi, v; + mov eax, g.m_vcnt[ecx * 4]; + lea edx, [eax + 1]; + mov g.m_vcnt[ecx * 4], edx; + lea edx, [ecx * 8 + 28]; + mul edx; + cld; + add edi, eax; + movsd; + movsd; + movsd; + lea esi, g.m_nx; + lea ecx, [ecx * 2 + 4]; + rep movsd; + } +#else + D3DVALUE *d3dv = &(((D3DVALUE*)g.m_verts)[g.m_vcnt[g.m_comp]++ * (g.m_comp * 2 + 7)]); + *(d3dv++) = *(v++); + *(d3dv++) = *(v++); + *(d3dv++) = *(v++); + memcpy(d3dv, &g.m_nx, (sizeof(D3DVALUE)*3 + sizeof(D3DCOLOR) + g.m_comp*sizeof(D3DVALUE)*2)); +#endif +} + +static void APIENTRY d3dVertexPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) +{ + if (size == 3 && type == GL_FLOAT) + { + g.m_vertexary = (GLfloat *) pointer; + if (stride) + g.m_vertexstride = stride; + else + g.m_vertexstride = 12; + } + else + { + char buf[128]; + + dSprintf(buf,128,"Wrapper: Vertex array not supported (size: %d type: %x stride: %d)\n", + size, type, stride); + OutputDebugString(buf); + } +} + +static void APIENTRY d3dRecti (GLint x1, GLint y1, GLint x2, GLint y2) +{ + d3dBegin(GL_POLYGON); + + d3dVertex2f((GLfloat) x1, (GLfloat) y1); + d3dVertex2f((GLfloat) x2, (GLfloat) y1); + d3dVertex2f((GLfloat) x2, (GLfloat) y2); + d3dVertex2f((GLfloat) x1, (GLfloat) y2); + + d3dEnd(); +} + +static void APIENTRY d3dMultMatrixf (const GLfloat *m) +{ + if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_TEXTURE0,&g.m_curtexmatrix); + + g.m_d3ddev->MultiplyTransform(g.m_matrixMode, (LPD3DMATRIX) m); + + if (g.m_matrixMode == D3DTRANSFORMSTATE_WORLD) + g.m_inversedirty = TRUE; + else if (g.m_matrixMode == D3DTRANSFORMSTATE_TEXTURE0) + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_TEXTURE0, &g.m_curtexmatrix); +} + +static GLboolean APIENTRY d3dIsEnabled (GLenum cap) +{ + DWORD enabled; + + switch (cap) + { + case GL_BLEND: + g.m_d3ddev->GetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, &enabled); + break; + case GL_TEXTURE_2D: + if (g.m_curtgt == 0) + enabled = g.m_texturing; + else + enabled = g.m_mtex; + break; + case GL_TEXTURE_COORD_ARRAY: + enabled = g.m_usetexcoordary[g.m_client_active_texture_arb]; + break; + case GL_COLOR_ARRAY: + enabled = g.m_usecolorary; + break; + case GL_VERTEX_ARRAY: + enabled = g.m_usevertexary; + break; + case GL_NORMAL_ARRAY: + enabled = g.m_usenormalary; + break; + case GL_FOG_COORDINATE_ARRAY_EXT: + enabled = g.m_usefogary; + break; + case GL_LIGHTING: + enabled = false; + break; + case GL_LIGHT0: + enabled = false; + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: unsupported glIsEnabled query: %x", cap); + OutputDebugString(buf); + + enabled = false; + } + break; + } + + return enabled; +} + +static void APIENTRY d3dGetTexEnviv (GLenum target, GLenum pname, GLint *params) +{ + target; + + if (pname == GL_TEXTURE_ENV_MODE) + *params = g.m_blendmode[g.m_curtgt]; + else + OutputDebugString("Wrapper: GL_TEXTURE_ENV_COLOR not implemented\n"); +} + +static void APIENTRY d3dFrontFace (GLenum mode) +{ + g.m_frontFace = mode; + if (g.m_cullEnabled == TRUE) + { + DWORD statevalue; + + if ((g.m_cullMode == GL_BACK && mode == GL_CCW) || + (g.m_cullMode == GL_FRONT && mode == GL_CW)) + statevalue=D3DCULL_CW; + else + statevalue=D3DCULL_CCW; + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_CULLMODE, statevalue); + } +} + +static void APIENTRY d3dTexGeni (GLenum coord, GLenum pname, GLint param) +{ + coord; + pname; + + g.m_texgenmode[g.m_curtgt] = param; + if (g.m_texgen[g.m_curtgt] && param == GL_OBJECT_LINEAR) + { + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXCOORDINDEX, + g.m_curtgt | D3DTSS_TCI_CAMERASPACEPOSITION); + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + } + else + { + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXCOORDINDEX, + g.m_curtgt); + g.m_d3ddev->SetTextureStageState(g.m_curtgt, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); + } +} + +static void APIENTRY d3dTexGenfv (GLenum coord, GLenum pname, const GLfloat *params) +{ + pname; + + switch (coord) + { + case GL_S: + dMemcpy(g.m_texgenplane[g.m_curtgt][0],params,sizeof(GLfloat)*4); + g.m_objectdirty[g.m_curtgt] = TRUE; + break; + case GL_T: + dMemcpy(g.m_texgenplane[g.m_curtgt][1],params,sizeof(GLfloat)*4); + g.m_objectdirty[g.m_curtgt] = TRUE; + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: TexGen coordinate unsupported: %x\n", coord); + OutputDebugString(buf); + } + break; + } +} + +static void APIENTRY d3dDrawArrays (GLenum mode, GLint first, GLsizei count) +{ + if (count == 0) + return; + + if (g.m_usefogary) + { + g.m_comp = 2; + if (g.m_nfv[3] > (VBUFSIZE - count)) // check if space available + { + g.m_fmtvbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[3] = 0; + } + else + { + g.m_fmtvbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeFMTVertex*)g.m_verts)[g.m_nfv[3]]); + } + } + else if (g.m_texturing) + if (g.m_mtex) + { + g.m_comp = 2; + if (g.m_nfv[2] > (VBUFSIZE - count)) // check if space available + { + g.m_mtvbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[2] = 0; + } + else + { + g.m_mtvbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeMTVertex*)g.m_verts)[g.m_nfv[2]]); + } + } + else + { + g.m_comp = 1; + if (g.m_nfv[1] > (VBUFSIZE - count)) // check if space available + { + g.m_tvbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[1] = 0; + } + else + { + g.m_tvbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeTVertex*)g.m_verts)[g.m_nfv[1]]); + } + } + else + { + g.m_comp = 0; + if (g.m_nfv[0] > (VBUFSIZE - count)) // check if space available + { + g.m_vbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[0] = 0; + } + else + { + g.m_vbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeVertex*)g.m_verts)[g.m_nfv[0]]); + } + } + + GLsizei i; + unsigned last = first+count - 1; + unsigned dstride = 7 + g.m_usefogary + g.m_comp*2; + + if (g.m_texturing) + { + if (g.m_usetexcoordary[0]) + { + unsigned char *tex0 = &((unsigned char *) g.m_texcoordary[0])[first*g.m_texcoordstride[0]]; + D3DVALUE *data = (D3DVALUE *) g.m_verts+7 + g.m_usefogary; + + for (i = 0; i < count; ++i) + { + memcpy(data,tex0,2*sizeof(GLfloat)); + tex0 += g.m_texcoordstride[0]; + data += dstride; + } + } + else + { + D3DVALUE *data = (D3DVALUE *) g.m_verts+7 + g.m_usefogary; + + for (i = 0; i < count; ++i) + { + memcpy(data,&g.m_tu,2*sizeof(GLfloat)); + data += dstride; + } + } + + if (g.m_mtex) + if (g.m_usetexcoordary[1]) + { + unsigned char *tex1 = &((unsigned char *) g.m_texcoordary[1])[first*g.m_texcoordstride[1]]; + D3DVALUE *data = (D3DVALUE *) g.m_verts+9 + g.m_usefogary; + + for (i = 0; i < count; ++i) + { + memcpy(data,tex1,2*sizeof(GLfloat)); + tex1 += g.m_texcoordstride[1]; + data += dstride; + } + } + else + { + D3DVALUE *data = (D3DVALUE *) g.m_verts+9 + g.m_usefogary; + + for (i = 0; i < count; ++i) + { + memcpy(data,&g.m_tu2,2*sizeof(GLfloat)); + data += dstride; + } + } + } + + if (g.m_usenormalary) + { + unsigned char *nml = &((unsigned char *) g.m_normalary)[first*g.m_normalstride]; + D3DVALUE *data = (D3DVALUE *) g.m_verts+3; + + for (i = 0; i < count; ++i) + { + memcpy(data,nml,3*sizeof(GLfloat)); + nml += g.m_normalstride; + data += dstride; + } + } + + if (g.m_usecolorary) + { + if (g.m_colortype == GL_UNSIGNED_BYTE) + { +#ifdef _X86_ + DWORD ustride = (dstride-1) * 4; + + _asm + { + mov esi, first; + mov ecx, last; + mov edx, 0x00FF00FF; + mov ebx, g.m_colorary; + mov edi, g.m_verts; + add edi, 24; + sub ecx, esi; + lea esi, [ebx + esi * 4]; + inc ecx; + cld; +lp1: lodsd; + mov ebx, eax; + and eax, edx; + not edx; + rol eax, 16; + and ebx, edx; + not edx; + or eax, ebx; + stosd; + add edi, ustride; + loop lp1; + } +#else + GLubyte *clr = &((GLubyte *) g.m_colorary)[first*g.m_colorstride]; + D3DCOLOR *data = (D3DCOLOR *) g.m_verts+6; + + for (i = 0; i < count; i++) + { + data[0] = RGBA_MAKE(clr[0],clr[1],clr[2],clr[3]); + clr += g.m_colorstride; + data += dstride; + } +#endif + } + else + { + GLubyte *clr = &((GLubyte *) g.m_colorary)[first*g.m_colorstride]; + D3DCOLOR *data = (D3DCOLOR *) g.m_verts+6; + + for (i = 0; i < count; ++i) + { + static float two55 = 255.f; + unsigned int R, G, B, A; + +#ifdef _X86_ + __asm { + mov ebx, clr; + fld [ebx]; + fld [ebx + 4]; + fld [ebx + 8]; + fld [ebx + 12]; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; + pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; + pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; + pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; + pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov data, eax; + } +#else + GLfloat *fclr = (GLfloat *) clr; + + R = (unsigned int)(fclr[0] * two55); + G = (unsigned int)(fclr[1] * two55); + B = (unsigned int)(fclr[2] * two55); + A = (unsigned int)(fclr[3] * two55); + if (R > 255) + R = 255; + if (G > 255) + G = 255; + if (B > 255) + B = 255; + if (A > 255) + A = 255; + data[0] = RGBA_MAKE(R, G, B, A); +#endif + clr += g.m_colorstride; + data += dstride; + } + } + } + else + { + D3DCOLOR *data = (D3DCOLOR *) g.m_verts+6; + + for (i = 0; i < count; ++i) + { + memcpy(data,&g.m_color,sizeof(D3DCOLOR)); + data += dstride; + } + } + + if (g.m_usefogary) + { + unsigned char *fog = &((unsigned char *) g.m_fogary)[first*g.m_fogstride]; + D3DCOLOR *data = (D3DCOLOR *) g.m_verts+7; + + for (i = 0; i < count; ++i) + { + static float two55 = 255.f; + unsigned int A; + + A = (unsigned int) (((GLfloat *) fog)[0] * two55); + if (A > 255) + A = 255; + data[0] = (255-A) << 24; + + fog += g.m_fogstride; + data += dstride; + } + } + + AssertFatal(g.m_usevertexary, "We're always using a vertex array...right?"); + + { + unsigned char *vtx = &((unsigned char *) g.m_vertexary)[first*g.m_vertexstride]; + D3DVALUE *data = (D3DVALUE *) g.m_verts; + + for (i = 0; i < count; ++i) + { + memcpy(data,vtx,3*sizeof(GLfloat)); + vtx += g.m_vertexstride; + data += dstride; + } + } + + QuakeSetTexturingState(); + + if (g.m_usefogary) + { + g.m_fmtvbuf->Unlock(); + if (mode == GL_TRIANGLES) + { + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLELIST, g.m_fmtvbuf, g.m_nfv[3], count, 0); + g.m_nfv[3] += count; + } + else + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",mode); + OutputDebugString(buf); + } + } + else if (g.m_texturing) + if (g.m_mtex) + { + g.m_mtvbuf->Unlock(); + switch (mode) + { + case GL_LINES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_LINELIST, g.m_mtvbuf, g.m_nfv[2], count, 0); + g.m_nfv[2] += count; + break; + case GL_TRIANGLES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLELIST, g.m_mtvbuf, g.m_nfv[2], count, 0); + g.m_nfv[2] += count; + break; + case GL_TRIANGLE_STRIP: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLESTRIP, g.m_mtvbuf, g.m_nfv[2], count, 0); + g.m_nfv[2] += count; + break; + case GL_POLYGON: + case GL_TRIANGLE_FAN: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_mtvbuf, g.m_nfv[2], count, 0); + g.m_nfv[2] += count; + break; + case GL_QUADS: + for (i = 0; i < count; i += 4) + { + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_mtvbuf, g.m_nfv[2], 4, 0); + g.m_nfv[2] += 4; + } + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",mode); + OutputDebugString(buf); + } + } + } + else + { + g.m_tvbuf->Unlock(); + switch (mode) + { + case GL_LINES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_LINELIST, g.m_tvbuf, g.m_nfv[1], count, 0); + g.m_nfv[1] += count; + break; + case GL_TRIANGLES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLELIST, g.m_tvbuf, g.m_nfv[1], count, 0); + g.m_nfv[1] += count; + break; + case GL_TRIANGLE_STRIP: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLESTRIP, g.m_tvbuf, g.m_nfv[1], count, 0); + g.m_nfv[1] += count; + break; + case GL_POLYGON: + case GL_TRIANGLE_FAN: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_tvbuf, g.m_nfv[1], count, 0); + g.m_nfv[1] += count; + break; + case GL_QUADS: + for (i = 0; i < count; i += 4) + { + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_tvbuf, g.m_nfv[1], 4, 0); + g.m_nfv[1] += 4; + } + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",mode); + OutputDebugString(buf); + } + } + } + else + { + g.m_vbuf->Unlock(); + switch (mode) + { + case GL_LINES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_LINELIST, g.m_vbuf, g.m_nfv[0], count, 0); + g.m_nfv[0] += count; + break; + case GL_TRIANGLES: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLELIST, g.m_vbuf, g.m_nfv[0], count, 0); + g.m_nfv[0] += count; + break; + case GL_TRIANGLE_STRIP: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLESTRIP, g.m_vbuf, g.m_nfv[0], count, 0); + g.m_nfv[0] += count; + break; + case GL_POLYGON: + case GL_TRIANGLE_FAN: + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_vbuf, g.m_nfv[0], count, 0); + g.m_nfv[0] += count; + break; + case GL_QUADS: + for (i = 0; i < count; i += 4) + { + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLEFAN, g.m_vbuf, g.m_nfv[0], 4, 0); + g.m_nfv[0] += 4; + } + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unimplemented primitive type: %x\n",mode); + OutputDebugString(buf); + } + } + } + +#if 0 + if (g.m_usefogary) + { + if (g.m_nfv[0] > (VBUFSIZE - count)) // check if space available + { + g.m_vbuf->Lock(DDLOCK_WAIT | DDLOCK_OKTOSWAP, &g.m_verts, 0); + g.m_nfv[0] = 0; + } + else + { + g.m_vbuf->Lock(DDLOCK_WAIT | DDLOCK_NOOVERWRITE, &g.m_verts, 0); + g.m_verts = &(((QuakeVertex*)g.m_verts)[g.m_nfv[0]]); + } + + { + GLubyte *fog = &((GLubyte *) g.m_fogary)[first*g.m_fogstride]; + D3DCOLOR *data = (D3DCOLOR *) g.m_verts+6; + + for (i = 0; i < count; ++i) + { + static float two55 = 255.f; + unsigned int A; + +//#ifdef _X86_ +//#if 0 + DWORD maskedfog = g.m_fogcolor & 0xFFFFFF; + + __asm { + mov ebx, fog; + fld [ebx]; + fld two55; + fmulp st(1), st(0); + fistp A; + mov edx, A; + cmp edx, 255; + jle pt5; + mov edx, 255; + pt5: shl edx, 24; + or edx, maskedfog; + mov data, eax; + } +//#else + GLfloat *fclr = (GLfloat *) fog; + + A = (unsigned int) (fclr[0] * two55); + if (A > 255) + A = 255; + data[0] = (A << 24) | (g.m_fogcolor & 0xFFFFFF); +//#endif + fog += g.m_fogstride; + data += 7; + } + } + + { + unsigned char *vtx = &((unsigned char *) g.m_vertexary)[first*g.m_vertexstride]; + D3DVALUE *data = (D3DVALUE *) g.m_verts; + + for (i = 0; i < count; ++i) + { + memcpy(data,vtx,3*sizeof(GLfloat)); + vtx += g.m_vertexstride; + data += 7; + } + } + + DWORD blend; + DWORD src; + DWORD dst; + + g.m_d3ddev->GetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, &blend); + g.m_d3ddev->GetRenderState(D3DRENDERSTATE_SRCBLEND, &src); + g.m_d3ddev->GetRenderState(D3DRENDERSTATE_DESTBLEND, &dst); + + if (!blend) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, TRUE); + if (src != D3DBLEND_SRCALPHA) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_SRCBLEND, D3DBLEND_SRCALPHA); + if (dst != D3DBLEND_INVSRCALPHA) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_DESTBLEND, D3DBLEND_INVSRCALPHA); + + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_DISABLE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_DISABLE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + + g.m_vbuf->Unlock(); + if (mode == GL_TRIANGLES) + { + g.m_d3ddev->DrawPrimitiveVB(D3DPT_TRIANGLELIST, g.m_vbuf, g.m_nfv[0], count, 0); + g.m_nfv[0] += count; + } + else + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unsupported fog coordinate primitive: %x\n", mode); + OutputDebugString(buf); + } + + if (!blend) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, FALSE); + if (src != D3DBLEND_SRCALPHA) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_SRCBLEND, src); + if (dst != D3DBLEND_INVSRCALPHA) + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_DESTBLEND, dst); + } +#endif +} + +static void APIENTRY d3dTexEnvfv (GLenum target, GLenum pname, const GLfloat *params) +{ + target; + pname; + params; + + static float two55 = 255.f; + unsigned int R, G, B, A; +#ifdef _X86_ + __asm { + mov ebx, params; + fld [ebx]; + fld [ebx + 4]; + fld [ebx + 8]; + fld [ebx + 12]; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; +pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; +pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; +pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; +pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov g.m_envcolor, eax; + } +#else + R = (unsigned int) (params[0] * two55); + G = (unsigned int) (params[1] * two55); + B = (unsigned int) (params[2] * two55); + A = (unsigned int) (params[3] * two55); + if (R > 255) + R = 255; + if (G > 255) + G = 255; + if (B > 255) + B = 255; + if (A > 255) + A = 255; + g.m_envcolor = RGBA_MAKE(R, G, B, A); +#endif +} + +static void APIENTRY d3dTexCoord2fv (const GLfloat *v) +{ + g.m_tu = v[0]; + g.m_tv = v[1]; +} + +static void APIENTRY d3dNormalPointer (GLenum type, GLsizei stride, const GLvoid *pointer) +{ + type; + + if (stride) + g.m_normalstride = stride; + else + g.m_normalstride = 12; + g.m_normalary = (const GLfloat *) pointer; +} + +static void APIENTRY d3dMaterialfv (GLenum face, GLenum pname, const GLfloat *params) +{ + face; + pname; + params; + + switch (pname) + { + case GL_AMBIENT: + break; + case GL_DIFFUSE: + case GL_AMBIENT_AND_DIFFUSE: + { + static float two55 = 255.f; + unsigned int R, G, B, A; + +#ifdef _X86_ + __asm { + mov ebx, params; + fld [ebx]; + fld [ebx + 4]; + fld [ebx + 8]; + fld [ebx + 12]; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; + pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; + pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; + pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; + pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov g.m_color, eax; + } +#else + R = (unsigned int)(params[0] * two55); + G = (unsigned int)(params[1] * two55); + B = (unsigned int)(params[2] * two55); + A = (unsigned int)(params[3] * two55); + if (R > 255) + R = 255; + if (G > 255) + G = 255; + if (B > 255) + B = 255; + if (A > 255) + A = 255; + g.m_color = RGBA_MAKE(R, G, B, A); +#endif + } + break; + default: + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Materialfv pname not supported: %x\n", pname); + OutputDebugString(buf); + } + break; + } +} + +static void APIENTRY d3dLightf (GLenum light, GLenum pname, GLfloat param) +{ + GLenum i = light - GL_LIGHT0; + + switch (pname) + { + case GL_SPOT_CUTOFF: + if (param >= 180.0) + { + g.m_lights[i].dltType = (g.m_lights[i].dltType == D3DLIGHT_DIRECTIONAL) ? D3DLIGHT_DIRECTIONAL : D3DLIGHT_POINT; + g.m_lights[i].dvTheta = g.m_lights[i].dvPhi = M_PI/2.0; + } + else + { + g.m_lights[i].dltType = (g.m_lights[i].dltType == D3DLIGHT_DIRECTIONAL) ? D3DLIGHT_DIRECTIONAL : D3DLIGHT_SPOT; + g.m_lights[i].dvTheta = g.m_lights[i].dvPhi = mDegToRad(param*2.0); + } + break; + case GL_CONSTANT_ATTENUATION: + g.m_lights[i].dvAttenuation0 = param; + break; + case GL_LINEAR_ATTENUATION: + g.m_lights[i].dvAttenuation1 = param; + break; + case GL_QUADRATIC_ATTENUATION: + g.m_lights[i].dvAttenuation2 = param; + break; + } + + D3DLIGHT7 &peak = g.m_lights[i]; + + g.m_d3ddev->SetLight(i,&g.m_lights[i]); +} + +static void APIENTRY d3dLightfv (GLenum light, GLenum pname, const GLfloat *params) +{ + GLenum i = light - GL_LIGHT0; + + switch (pname) + { + case GL_POSITION: + { + D3DXMATRIX world; + + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_WORLD, (D3DMATRIX *) &world); + if (params[3] == 0.0) + { + D3DXVECTOR4 mdir(params), wdir, tdir(0,0,1,0); + + g.m_lights[i].dltType = D3DLIGHT_DIRECTIONAL; + D3DXVec4Transform(&wdir,&mdir,&world); + dMemcpy(&g.m_lights[i].dvDirection,wdir,sizeof(GLfloat)*3); + } + else + { + D3DXVECTOR4 mpos(params), wpos; + + g.m_lights[i].dltType = (g.m_lights[i].dvPhi == M_2PI) ? D3DLIGHT_POINT : D3DLIGHT_SPOT; + D3DXVec4Transform(&wpos,&mpos,&world); + dMemcpy(&g.m_lights[i].dvPosition,wpos,sizeof(GLfloat)*3); + } + } + break; + case GL_SPOT_DIRECTION: + { + D3DXMATRIX world; + D3DXVECTOR4 mdir(params), wdir; + + g.m_d3ddev->GetTransform(D3DTRANSFORMSTATE_WORLD, (D3DMATRIX *) &world); + D3DXVec4Transform(&wdir,&mdir,&world); + dMemcpy(&g.m_lights[i].dvDirection,wdir,sizeof(GLfloat)*3); + } + break; + case GL_DIFFUSE: + dMemcpy(&g.m_lights[i].dcvDiffuse,params,sizeof(GLfloat)*3); + break; + case GL_AMBIENT: + dMemcpy(&g.m_lights[i].dcvAmbient,params,sizeof(GLfloat)*3); + break; + case GL_SPECULAR: + dMemcpy(&g.m_lights[i].dcvSpecular,params,sizeof(GLfloat)*3); + break; + } + + D3DLIGHT7 &peak = g.m_lights[i]; + + g.m_d3ddev->SetLight(i,&g.m_lights[i]); +} + +static void APIENTRY d3dLightModelfv (GLenum pname, const GLfloat *params) +{ + if (pname == GL_LIGHT_MODEL_AMBIENT) + { + DWORD ambient; + + static float two55 = 255.f; + unsigned int R, G, B, A; + +#ifdef _X86_ + __asm { + mov ebx, params; + fld [ebx]; + fld [ebx + 4]; + fld [ebx + 8]; + fld [ebx + 12]; + fld two55; + fmul st(1), st(0); + fmul st(2), st(0); + fmul st(3), st(0); + fmulp st(4), st(0); + fistp A; + fistp B; + fistp G; + fistp R; + mov edx, A; + cmp edx, 255; + jle pt1; + mov edx, 255; + pt1: mov eax, B; + cmp eax, 255; + jle pt2; + mov eax, 255; + pt2: mov ebx, G; + cmp ebx, 255; + jle pt3; + mov ebx, 255; + pt3: mov ecx, R; + cmp ecx, 255; + jle pt4; + mov ecx, 255; + pt4: shl ebx, 8; + shl ecx, 16; + shl edx, 24; + or eax, ebx; + or ecx, edx; + or eax, ecx; + mov ambient, eax; + } +#else + R = (unsigned int) (params[0] * two55); + G = (unsigned int) (params[1] * two55); + B = (unsigned int) (params[2] * two55); + A = (unsigned int) (params[3] * two55); + if (R > 255) + R = 255; + if (G > 255) + G = 255; + if (B > 255) + B = 255; + if (A > 255) + A = 255; + ambient = RGBA_MAKE(R, G, B, A); +#endif + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_AMBIENT, ambient); + } + else + { + char buf[64]; + + dSprintf(buf,64,"Wrapper: Unsupported LightModelfv pname: %x\n", pname); + OutputDebugString(buf); + } +} + +static void APIENTRY d3dFogCoordPointerEXT(GLenum type, GLsizei stride, void *pointer) +{ + if (type == GL_FLOAT) + { + if (stride) + g.m_fogstride = stride; + else + g.m_fogstride = 4; + g.m_fogary = (GLfloat *) pointer; + } + else + OutputDebugString("Wrapper: Unsupported FogCoordPointerEXT array type\n"); +} + +static void APIENTRY d3dPointSize (GLfloat size) +{ + size; +} + +static int WINAPI wd3dChoosePixelFormat(HDC hdc, CONST PIXELFORMATDESCRIPTOR *ppfd) +{ + hdc; + ppfd; + + return 1; +} + +static LRESULT CALLBACK MyMsgHandler( + HWND hwnd, // handle to window + UINT uMsg, // message identifier + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter +) +{ + if(uMsg == WM_CHAR) + { + if(wParam == 0x2F) + { + ++g.m_lod; + if(g.m_lod == 8) + g.m_lod = 0; + int changedLOD = 0; + static char str[256]; + for(int i = 0; i < MAXGLTEXHANDLES; ++i) + { + if(g.m_tex[i].m_ddsurf != 0) + { + DDSURFACEDESC2 surfdesc; + surfdesc.dwSize = sizeof(DDSURFACEDESC2); + g.m_tex[i].m_ddsurf->GetSurfaceDesc(&surfdesc); + if(surfdesc.dwMipMapCount != 0) + { + if(g.m_lod >= surfdesc.dwMipMapCount) + g.m_tex[i].m_ddsurf->SetLOD(surfdesc.dwMipMapCount - 1); + else + { + g.m_tex[i].m_ddsurf->SetLOD(g.m_lod); + ++changedLOD; + } + } + } + } + _itoa(changedLOD, str, 10); + OutputDebugString(str); + OutputDebugString("\n"); + return 0; + } + } + return CallWindowProc(g.m_wndproc, hwnd, uMsg, wParam, lParam); +} + +static HGLRC WINAPI wd3dCreateContext(HDC hdc) +{ + g.m_hdc = hdc; + g.m_hwnd = WindowFromDC(g.m_hdc); + RECT rect; + GetClientRect(g.m_hwnd, &rect); + g.m_winWidth = (USHORT)rect.right; + g.m_winHeight = (USHORT)rect.bottom; + g.m_vwx = rect.left; + g.m_vwy = rect.top; + g.m_vww = rect.right - rect.left; + g.m_vwh = rect.bottom - rect.top; + + HRESULT hr = D3DXInitialize(); + if(FAILED(hr)) + { + return 0; + } + + DWORD DeviceType = D3DX_DEFAULT; + // Check registry key to see if we need to do software emulation + HKEY hKey; + if(ERROR_SUCCESS == RegOpenKey(HKEY_LOCAL_MACHINE, RESPATH_QUAKE, &hKey)) { + DWORD dwType; + DWORD dwValue; + DWORD dwSize = 4; + if (ERROR_SUCCESS == RegQueryValueEx( hKey, "Emulation", NULL, &dwType, (LPBYTE) &dwValue, &dwSize) && + dwType == REG_DWORD && + dwValue != 0) { + DeviceType = D3DX_HWLEVEL_2D; + } + RegCloseKey( hKey ); + } + + D3DX_VIDMODEDESC VidMode; + hr = D3DXGetCurrentVideoMode(DeviceType, &VidMode); + if(FAILED(hr)) + { + D3DXUninitialize(); + return 0; + } + + // See if the window is full screen + if( (DWORD)rect.right == VidMode.width && (DWORD)rect.bottom == VidMode.height ) + { + // We are full screen + hr = D3DXCreateContextEx(DeviceType, D3DX_CONTEXT_FULLSCREEN, g.m_hwnd, g.m_hwnd, + VidMode.bpp, D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, + VidMode.width, VidMode.height, VidMode.refreshRate, &g.m_pD3DX); + } + else + { + hr = D3DXCreateContext(DeviceType, 0, g.m_hwnd, D3DX_DEFAULT, D3DX_DEFAULT, &g.m_pD3DX); + } + if(FAILED(hr)) + { + D3DXUninitialize(); + return 0; + } + + // Keep a copy of the device + g.m_d3ddev = g.m_pD3DX->GetD3DDevice(); + if(g.m_d3ddev == NULL) + { + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + + g.m_d3ddev->GetCaps(&g.m_dd); + if(FAILED(hr)) + { + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + + // Check registry key to see if we need to turn off mipmapping + if(ERROR_SUCCESS == RegOpenKey(HKEY_LOCAL_MACHINE, RESPATH_QUAKE, &hKey)) { + DWORD dwType; + DWORD dwValue; + DWORD dwSize = 4; + if (ERROR_SUCCESS == RegQueryValueEx( hKey, "DisableMipMap", NULL, &dwType, (LPBYTE) &dwValue, &dwSize) && + dwType == REG_DWORD && + dwValue != 0) { + g.m_usemipmap = FALSE; + OutputDebugString("Wrapper: Mipmapping disabled\n"); + } + else { + g.m_usemipmap = TRUE; + } + RegCloseKey( hKey ); + } + else { + g.m_usemipmap = TRUE; + } + + // Enumerate texture formats and find the right ones to use + DWORD texfmts = D3DXGetMaxSurfaceFormats(DeviceType, NULL, D3DX_SC_COLORTEXTURE); + // Look for a four bit alpha surface + BOOLEAN found = FALSE; + DWORD i; + for(i = 0; i < texfmts; ++i) + { + hr = D3DXGetSurfaceFormat(DeviceType, NULL, D3DX_SC_COLORTEXTURE, i, &g.m_ddFourBitAlphaSurfFormat); + if( FAILED(hr) ) + { + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + if(g.m_ddFourBitAlphaSurfFormat == D3DX_SF_A4R4G4B4) + { + found = TRUE; + break; + } + } + if ( found == FALSE ) + { + OutputDebugString("Wrapper: Unable to find 4444 texture.\n"); + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + // Look for a eight bit alpha surface + found = FALSE; + for(i = 0; i < texfmts; ++i) + { + hr = D3DXGetSurfaceFormat(DeviceType, NULL, D3DX_SC_COLORTEXTURE, i, &g.m_ddEightBitAlphaSurfFormat); + if( FAILED(hr) ) + { + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + if(g.m_ddEightBitAlphaSurfFormat == D3DX_SF_A8R8G8B8) + { + found = TRUE; + break; + } + } + if ( found == FALSE ) + { + OutputDebugString("Wrapper: Not using 8888 texture\n"); + g.m_ddEightBitAlphaSurfFormat = g.m_ddFourBitAlphaSurfFormat; + } + // Look for a surface + found = FALSE; + for(i = 0; i < texfmts; ++i) + { + hr = D3DXGetSurfaceFormat(DeviceType, NULL, D3DX_SC_COLORTEXTURE, i, &g.m_ddFiveBitSurfFormat); + if( FAILED(hr) ) + { + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + if(g.m_ddFiveBitSurfFormat == D3DX_SF_R5G5B5 || g.m_ddFiveBitSurfFormat == D3DX_SF_R5G6B5) + { + found = TRUE; + break; + } + } + if ( found == FALSE ) + { + OutputDebugString("Wrapper: Unable to find 555 or 565 texture.\n"); + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + // Look for a 8-bit surface + found = FALSE; + for(i = 0; i < texfmts; ++i) + { + hr = D3DXGetSurfaceFormat(DeviceType, NULL, D3DX_SC_COLORTEXTURE, i, &g.m_ddEightBitSurfFormat); + if( FAILED(hr) ) + { + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + if(g.m_ddEightBitSurfFormat == D3DX_SF_X8R8G8B8) + { + found = TRUE; + break; + } + } + if ( found == FALSE ) + { + OutputDebugString("Wrapper: Not using 888 texture\n"); + g.m_ddEightBitSurfFormat = g.m_ddEightBitAlphaSurfFormat; + } + // Look for a luminance surface + found = FALSE; + for(i = 0; i < texfmts; ++i) + { + hr = D3DXGetSurfaceFormat(DeviceType, NULL, D3DX_SC_COLORTEXTURE, i, &g.m_ddLuminanceSurfFormat); + if( FAILED(hr) ) + { + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + if(g.m_ddLuminanceSurfFormat == D3DX_SF_L8) + { + found = TRUE; + break; + } + } + if ( found == FALSE ) + { + OutputDebugString("Wrapper: Not using luminance texture\n"); + g.m_ddLuminanceSurfFormat = g.m_ddEightBitSurfFormat; + } + // Look for an alpha surface + found = FALSE; + for(i = 0; i < texfmts; ++i) + { + hr = D3DXGetSurfaceFormat(DeviceType, NULL, D3DX_SC_COLORTEXTURE, i, &g.m_ddAlphaSurfFormat); + if( FAILED(hr) ) + { + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + if(g.m_ddAlphaSurfFormat == D3DX_SF_A8) + { + found = TRUE; + break; + } + } + if ( found == FALSE ) + { + OutputDebugString("Wrapper: Not using alpha texture\n"); + g.m_ddAlphaSurfFormat = g.m_ddEightBitSurfFormat; + } + + // Do misc init stuff + if(g.m_dd.dwMaxTextureWidth < 512 || g.m_dd.dwMaxTextureHeight < 512) { + g.m_subsample = TRUE; + OutputDebugString("Wrapper: Subsampling textures to 256 x 256\n"); + } + else + g.m_subsample = FALSE; + if(g.m_dd.dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_SQUAREONLY) { + g.m_makeSquare = TRUE; + OutputDebugString("Wrapper: Forcing all textures to be square\n"); + } + else + g.m_makeSquare = FALSE; + if(g.m_dd.wMaxSimultaneousTextures > 1) { + g.m_usemtex = TRUE; + OutputDebugString("Wrapper: Multitexturing enabled\n"); + } + else { + g.m_usemtex = FALSE; + OutputDebugString("Wrapper: Multitexturing not available with this driver\n"); + } + if(!(g.m_dd.dpcTriCaps.dwTextureFilterCaps & D3DPTFILTERCAPS_LINEARMIPLINEAR) && + !(g.m_dd.dpcTriCaps.dwTextureFilterCaps & D3DPTFILTERCAPS_LINEARMIPNEAREST) && + !(g.m_dd.dpcTriCaps.dwTextureFilterCaps & D3DPTFILTERCAPS_MIPLINEAR) && + !(g.m_dd.dpcTriCaps.dwTextureFilterCaps & D3DPTFILTERCAPS_MIPNEAREST)) { + g.m_usemipmap = FALSE; + OutputDebugString("Wrapper: Mipmapping disabled\n"); + } + if (!(g.m_dd.dwVertexProcessingCaps & D3DVTXPCAPS_DIRECTIONALLIGHTS)) + { + g.m_usedirectional = FALSE; + OutputDebugString("Wrapper: Directional lights disabled\n"); + } + else + g.m_usedirectional = TRUE; + if(ERROR_SUCCESS == RegOpenKey(HKEY_LOCAL_MACHINE, RESPATH_QUAKE, &hKey)) { + DWORD dwType; + DWORD dwValue; + DWORD dwSize = 4; + if (ERROR_SUCCESS == RegQueryValueEx( hKey, "DoFlip", NULL, &dwType, (LPBYTE) &dwValue, &dwSize) && + dwType == REG_DWORD && + dwValue != 0) { + g.m_doFlip = TRUE; + } + else { + g.m_doFlip = FALSE; + } + RegCloseKey( hKey ); + } + else { + g.m_doFlip = FALSE; + } + + // Create vertex buffers + // Get a D3D ptr to create the vertex buffer. + LPDIRECT3D7 pD3D = g.m_pD3DX->GetD3D(); + if( pD3D == NULL ) + { + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + D3DVERTEXBUFFERDESC vbufdesc; + vbufdesc.dwSize = sizeof(D3DVERTEXBUFFERDESC); + vbufdesc.dwCaps = D3DVBCAPS_WRITEONLY; + if (!(g.m_dd.dwDevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)) + vbufdesc.dwCaps = D3DVBCAPS_SYSTEMMEMORY; + vbufdesc.dwFVF = QUAKEVFMT; + vbufdesc.dwNumVertices = VBUFSIZE; + hr = pD3D->CreateVertexBuffer(&vbufdesc, &g.m_vbuf, 0); + if( FAILED(hr) ) + { + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + vbufdesc.dwFVF = QUAKETVFMT; + hr = pD3D->CreateVertexBuffer(&vbufdesc, &g.m_tvbuf, 0); + if( FAILED(hr) ) + { + g.m_vbuf->Release(); + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + vbufdesc.dwFVF = QUAKEMTVFMT; + hr = pD3D->CreateVertexBuffer(&vbufdesc, &g.m_mtvbuf, 0); + if( FAILED(hr) ) + { + g.m_tvbuf->Release(); + g.m_vbuf->Release(); + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + vbufdesc.dwFVF = QUAKEFMTVFMT; + hr = pD3D->CreateVertexBuffer(&vbufdesc, &g.m_fmtvbuf, 0); + if( FAILED(hr) ) + { + g.m_mtvbuf->Release(); + g.m_tvbuf->Release(); + g.m_vbuf->Release(); + g.m_d3ddev->Release(); + g.m_pD3DX->Release(); + D3DXUninitialize(); + return 0; + } + // Done with D3D + pD3D->Release(); + + // Some more init stuff + g.m_cullMode = GL_BACK; + g.m_cullEnabled = FALSE; + + g.m_texHandleValid = FALSE; + g.m_texturing = FALSE; + g.m_updvwp = TRUE; + g.m_blendmode[0] = GL_MODULATE; + g.m_blendmode[1] = GL_MODULATE; + g.m_nfv[0] = g.m_nfv[1] = g.m_nfv[2] = g.m_nfv[3] = 0; + g.m_curtgt = 0; + g.m_client_active_texture_arb = 0; + g.m_nx = 0.0; + g.m_ny = 0.0; + g.m_nz = 1.0; + g.m_color = 0xFFFFFFFF; + g.m_tu = g.m_tv = g.m_tu2 = g.m_tv2 = 0.f; + g.m_clearColor = 0; + g.m_clearDepth = 1.f; + g.m_usecolorary = FALSE; + g.m_usetexcoordary[0] = FALSE; + g.m_usetexcoordary[1] = FALSE; + g.m_usevertexary = FALSE; + g.m_scix = 0; + g.m_sciy = 0; + g.m_sciw = g.m_winWidth; + g.m_scih = g.m_winHeight; + g.m_lckfirst = 0; + g.m_lckcount = 0; + g.m_numIndices = 1024; + g.m_wIndices = new WORD[g.m_numIndices]; + g.m_frontFace = GL_CCW; + g.m_usenormalary = FALSE; + g.m_normalstride = 0; + g.m_texcoordstride[0] = 0; + g.m_texcoordstride[1] = 0; + g.m_texgen[0] = FALSE; + g.m_texgen[1] = FALSE; + g.m_texgenmode[0] = GL_EYE_LINEAR; + g.m_texgenmode[0] = GL_EYE_LINEAR; + g.m_texgenplane[0][0][0] = 1.0; + g.m_texgenplane[0][0][1] = 0.0; + g.m_texgenplane[0][0][2] = 0.0; + g.m_texgenplane[0][0][3] = 0.0; + g.m_texgenplane[0][1][0] = 0.0; + g.m_texgenplane[0][1][1] = 1.0; + g.m_texgenplane[0][1][2] = 0.0; + g.m_texgenplane[0][1][3] = 0.0; + g.m_texgenplane[1][0][0] = 1.0; + g.m_texgenplane[1][0][1] = 0.0; + g.m_texgenplane[1][0][2] = 0.0; + g.m_texgenplane[1][0][3] = 0.0; + g.m_texgenplane[1][1][0] = 0.0; + g.m_texgenplane[1][1][1] = 1.0; + g.m_texgenplane[1][1][2] = 0.0; + g.m_texgenplane[1][1][3] = 0.0; + g.m_inversedirty = TRUE; + g.m_objectdirty[0] = TRUE; + g.m_objectdirty[1] = TRUE; + g.m_envcolor = 0; + g.m_colorstride = 0; + g.m_colortype = GL_FLOAT; + g.m_spherecoords = new GLfloat[100]; + g.m_zbias = 0; + g.m_fogcolor = 0; + g.m_usefogary = FALSE; + g.m_fogstride = 0; + + // Initialize the structure. + ZeroMemory(g.m_lights, sizeof(D3DLIGHT7)); + + // Set up for a white point light. + g.m_lights[0].dltType = D3DLIGHT_DIRECTIONAL; + g.m_lights[0].dcvDiffuse.r = 1.0f; + g.m_lights[0].dcvDiffuse.g = 1.0f; + g.m_lights[0].dcvDiffuse.b = 1.0f; + g.m_lights[0].dcvAmbient.r = 0.0f; + g.m_lights[0].dcvAmbient.g = 0.0f; + g.m_lights[0].dcvAmbient.b = 0.0f; + g.m_lights[0].dcvSpecular.r = 1.0f; + g.m_lights[0].dcvSpecular.g = 1.0f; + g.m_lights[0].dcvSpecular.b = 1.0f; + + // Position it high in the scene, and behind the viewer. + // (Remember, these coordinates are in world space, so + // the "viewer" could be anywhere in world space, too. + // For the purposes of this example, assume the viewer + // is at the origin of world space.) + g.m_lights[0].dvPosition.x = 0.0f; + g.m_lights[0].dvPosition.y = 0.0f; + g.m_lights[0].dvPosition.z = 1.0f; + g.m_lights[0].dvDirection.x = 0.0f; + g.m_lights[0].dvDirection.y = 0.0f; + g.m_lights[0].dvDirection.z = -1.0f; + + // Don't attenuate. + g.m_lights[0].dvAttenuation0 = 1.0f; + g.m_lights[0].dvRange = D3DLIGHT_RANGE_MAX; + + g.m_lights[0].dvTheta = g.m_lights[0].dvPhi = M_PI/4.0; + + for (i = 1; i < 8; ++i) + { + D3DLIGHT7 &d3dLight = g.m_lights[i]; + + // Initialize the structure. + ZeroMemory(&d3dLight, sizeof(D3DLIGHT7)); + + // Set up for a white point light. + d3dLight.dltType = D3DLIGHT_DIRECTIONAL; + d3dLight.dcvDiffuse.r = 0.0f; + d3dLight.dcvDiffuse.g = 0.0f; + d3dLight.dcvDiffuse.b = 0.0f; + d3dLight.dcvAmbient.r = 0.0f; + d3dLight.dcvAmbient.g = 0.0f; + d3dLight.dcvAmbient.b = 0.0f; + d3dLight.dcvSpecular.r = 0.0f; + d3dLight.dcvSpecular.g = 0.0f; + d3dLight.dcvSpecular.b = 0.0f; + + // Position it high in the scene, and behind the viewer. + // (Remember, these coordinates are in world space, so + // the "viewer" could be anywhere in world space, too. + // For the purposes of this example, assume the viewer + // is at the origin of world space.) + d3dLight.dvPosition.x = 0.0f; + d3dLight.dvPosition.y = 0.0f; + d3dLight.dvPosition.z = 1.0f; + d3dLight.dvDirection.x = 0.0f; + d3dLight.dvDirection.y = 0.0f; + d3dLight.dvDirection.z = -1.0f; + + // Don't attenuate. + d3dLight.dvAttenuation0 = 1.0f; + d3dLight.dvRange = D3DLIGHT_RANGE_MAX; + + d3dLight.dvTheta = d3dLight.dvPhi = M_PI/2.0; + } + + g.m_curtexmatrix._11 = 1.0f; g.m_curtexmatrix._12 = 0.0f; g.m_curtexmatrix._13 = 0.0f; g.m_curtexmatrix._14 = 0.0f; + g.m_curtexmatrix._21 = 0.0f; g.m_curtexmatrix._22 = 1.0f; g.m_curtexmatrix._23 = 0.0f; g.m_curtexmatrix._24 = 0.0f; + g.m_curtexmatrix._31 = 0.0f; g.m_curtexmatrix._32 = 0.0f; g.m_curtexmatrix._33 = 1.0f; g.m_curtexmatrix._34 = 0.0f; + g.m_curtexmatrix._41 = 0.0f; g.m_curtexmatrix._42 = 0.0f; g.m_curtexmatrix._43 = 0.0f; g.m_curtexmatrix._44 = 1.0f; + + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_TEXTURE0,&g.m_curtexmatrix); + + GLListManip freeTextures(&g.m_freeTextures); + + for(i = 0; i < MAXGLTEXHANDLES; ++i) { + g.m_tex[i].m_ddsurf = 0; + g.m_tex[i].m_block = 0; + g.m_tex[i].m_capture = FALSE; + g.m_tex[i].m_dwStage = 0; + g.m_tex[i].m_minmode = D3DTFN_POINT; + g.m_tex[i].m_magmode = D3DTFG_LINEAR; + g.m_tex[i].m_mipmode = D3DTFP_LINEAR; + g.m_tex[i].m_addu = D3DTADDRESS_WRAP; + g.m_tex[i].m_addv = D3DTADDRESS_WRAP; + + if (i) + freeTextures.insert(i); + } + + D3DMATRIX unity; + unity._11 = 1.0f; unity._12 = 0.0f; unity._13 = 0.0f; unity._14 = 0.0f; + unity._21 = 0.0f; unity._22 = 1.0f; unity._23 = 0.0f; unity._24 = 0.0f; + unity._31 = 0.0f; unity._32 = 0.0f; unity._33 = 1.0f; unity._34 = 0.0f; + unity._41 = 0.0f; unity._42 = 0.0f; unity._43 = 0.0f; unity._44 = 1.0f; + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_VIEW, &unity); + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_WORLD, &unity); + g.m_d3ddev->SetTransform(D3DTRANSFORMSTATE_PROJECTION, &unity); + g.m_curtex[0] = NULL; + g.m_curtex[1] = NULL; + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_TEXTUREPERSPECTIVE, TRUE); + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_SPECULARENABLE, FALSE); + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_DITHERENABLE, TRUE); + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_CLIPPING, TRUE); + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_LIGHTING, FALSE); + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_AMBIENT, 0x32323219); + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_AMBIENTMATERIALSOURCE, D3DMCS_COLOR1); + g.m_d3ddev->SetRenderState(D3DRENDERSTATE_EXTENTS, FALSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_TEXCOORDINDEX,0); + if(g.m_usemtex == TRUE) + g.m_d3ddev->SetTextureStageState (1, D3DTSS_TEXCOORDINDEX,1); + + // Create shaders + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[0][0]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + g.m_d3ddev->EndStateBlock(&g.m_shaders[0][1]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_MODULATE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[0][2]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_MODULATE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + g.m_d3ddev->EndStateBlock(&g.m_shaders[0][3]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[0][4]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_BLENDTEXTUREALPHA); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[0][5]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE | D3DTA_COMPLEMENT); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_MODULATE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[0][6]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE | D3DTA_COMPLEMENT); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_MODULATE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + g.m_d3ddev->EndStateBlock(&g.m_shaders[0][7]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + g.m_d3ddev->SetTextureStageState (0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + g.m_d3ddev->EndStateBlock(&g.m_shaders[0][8]); + + if(g.m_usemtex) + { + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[1][0]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + g.m_d3ddev->EndStateBlock(&g.m_shaders[1][1]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_MODULATE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[1][2]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_MODULATE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + g.m_d3ddev->EndStateBlock(&g.m_shaders[1][3]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[1][4]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_BLENDTEXTUREALPHA); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[1][5]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE | D3DTA_COMPLEMENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_MODULATE); + // following stage state to speedup software rasterizer + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + g.m_d3ddev->EndStateBlock(&g.m_shaders[1][6]); + + g.m_d3ddev->BeginStateBlock(); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE | D3DTA_COMPLEMENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_COLOROP, D3DTOP_MODULATE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + g.m_d3ddev->SetTextureStageState (1, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + g.m_d3ddev->EndStateBlock(&g.m_shaders[1][7]); + } + + // Hook into message loop + // g.m_wndproc = (WNDPROC)SetWindowLong(g.m_hwnd, GWL_WNDPROC, (LONG)MyMsgHandler); + // g.m_lod = 0; + + // Start a scene + g.m_d3ddev->BeginScene(); + + return (HGLRC)1; +} + +static BOOL WINAPI wd3dDeleteContext(HGLRC hglrc) +{ + hglrc; + + g.m_d3ddev->EndScene(); + SetWindowLong(g.m_hwnd, GWL_WNDPROC, (LONG)g.m_wndproc); + int i; + for(i = 0; i < MAXGLTEXHANDLES; ++i){ + if(g.m_tex[i].m_ddsurf != 0) { + g.m_tex[i].m_ddsurf->Release(); + g.m_tex[i].m_ddsurf = 0; + if(g.m_tex[i].m_block != 0) + { + g.m_d3ddev->DeleteStateBlock(g.m_tex[i].m_block); + g.m_tex[i].m_block = 0; + } + g.m_tex[i].m_capture = FALSE; + } + } + + GLListManip freeTextures(&g.m_freeTextures); + + while (g.m_freeTextures.length()) + freeTextures.remove(); + + for(i = 0; i < 8; ++i) + g.m_d3ddev->DeleteStateBlock(g.m_shaders[0][i]); + if(g.m_usemtex) + { + for(i = 0; i < 8; ++i) + g.m_d3ddev->DeleteStateBlock(g.m_shaders[1][i]); + } + g.m_vbuf->Release(); + g.m_vbuf = 0; + g.m_mtvbuf->Release(); + g.m_mtvbuf = 0; + g.m_tvbuf->Release(); + g.m_tvbuf = 0; + g.m_d3ddev->Release(); + g.m_d3ddev = 0; + g.m_pD3DX->Release(); + g.m_pD3DX = 0; + D3DXUninitialize(); + delete[] g.m_wIndices; + g.m_wIndices = 0; + + return TRUE; +} + +static int WINAPI wd3dDescribePixelFormat(HDC hdc, INT iPixelFormat, UINT nBytes, PIXELFORMATDESCRIPTOR *ppfd) +{ + hdc; + iPixelFormat; + nBytes; + + HRESULT hr = D3DXInitialize(); + if (FAILED(hr)) + return 0; + + D3DX_VIDMODEDESC VidMode; + hr = D3DXGetCurrentVideoMode(D3DX_DEFAULT, &VidMode); + if (FAILED(hr)) + { + D3DXUninitialize(); + return 0; + } + + D3DXUninitialize(); + + ppfd->nSize = sizeof(PIXELFORMATDESCRIPTOR); + ppfd->nVersion = 1; + ppfd->dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_GENERIC_ACCELERATED; + ppfd->iPixelType = PFD_TYPE_RGBA; + ppfd->cColorBits = (unsigned char)VidMode.bpp; + ppfd->cAccumBits = 0; + ppfd->cAccumRedBits = 0; + ppfd->cAccumGreenBits = 0; + ppfd->cAccumBlueBits = 0; + ppfd->cAccumAlphaBits = 0; + ppfd->cStencilBits = 0; + ppfd->cAuxBuffers = 0; + ppfd->iLayerType = 0; + ppfd->bReserved = 0; + ppfd->dwLayerMask = 0; + ppfd->dwVisibleMask = 0; + ppfd->dwDamageMask = 0; + if(VidMode.bpp == 16) + { + ppfd->cRedBits = 5; + ppfd->cRedShift = 11; + ppfd->cGreenBits = 6; + ppfd->cGreenShift = 5; + ppfd->cBlueBits = 5; + ppfd->cBlueShift = 0; + ppfd->cAlphaBits = 0; + ppfd->cAlphaShift = 0; + ppfd->cDepthBits = 16; + } + else if(VidMode.bpp == 24 || VidMode.bpp == 32) + { + ppfd->cRedBits = 8; + ppfd->cRedShift = 16; + ppfd->cGreenBits = 8; + ppfd->cGreenShift = 8; + ppfd->cBlueBits = 8; + ppfd->cBlueShift = 0; + ppfd->cAlphaBits = 0; + ppfd->cAlphaShift = 0; + ppfd->cDepthBits = VidMode.bpp; + } + else + { + return 0; + } + + return 1; +} + +static HGLRC WINAPI wd3dGetCurrentContext(VOID) +{ + return (HGLRC)1; +} + +static HDC WINAPI wd3dGetCurrentDC(VOID) +{ + return g.m_hdc; +} + +static int WINAPI wd3dGetPixelFormat(HDC hdc) +{ + hdc; + + return 1; +} + +static PROC WINAPI wd3dGetProcAddress(LPCSTR str) +{ + if(strcmp(str, "glMTexCoord2fSGIS") == 0) + return (PROC)d3dMTexCoord2fSGIS; + else if(strcmp(str, "glSelectTextureSGIS") == 0) + return (PROC)d3dSelectTextureSGIS; + else if(strcmp(str, "glActiveTextureARB") == 0) + return (PROC)d3dActiveTextureARB; + else if(strcmp(str, "glClientActiveTextureARB") == 0) + return (PROC)d3dClientActiveTextureARB; + else if(strcmp(str, "glMultiTexCoord2fARB") == 0) + return (PROC)d3dMultiTexCoord2fARB; + else if(strcmp(str, "glMultiTexCoord2fvARB") == 0) + return (PROC)d3dMultiTexCoord2fvARB; + else if(strcmp(str, "glLockArraysEXT") == 0) + return (PROC)d3dLockArraysEXT; + else if(strcmp(str, "glUnlockArraysEXT") == 0) + return (PROC)d3dUnlockArraysEXT; + else + { + OutputDebugString("Wrapper: Unimplemented function "); + OutputDebugString(str); + OutputDebugString("\n"); + } + return NULL; +} + +static BOOL WINAPI wd3dMakeCurrent(HDC hdc, HGLRC hglrc) +{ + hdc; + hglrc; + + return TRUE; +} + +static BOOL WINAPI wd3dSetPixelFormat(HDC hdc, int iPixelFormat, CONST PIXELFORMATDESCRIPTOR *ppfd) +{ + hdc; + iPixelFormat; + ppfd; + + return TRUE; +} + +static BOOL WINAPI wd3dSwapBuffers(HDC hdc) +{ + hdc; + +#ifdef USEICECAP + static unsigned nframes = 0; + ++nframes; + if(nframes == 1000) + { + OutputDebugString("Started profiling\n"); + StartCAP(); + } + if(nframes == 1500) + { + StopCAP(); + OutputDebugString("stopped profiling\n"); + } +#endif + + g.m_d3ddev->EndScene(); + g.m_pD3DX->UpdateFrame(g.m_doFlip ? 0 : D3DX_UPDATE_NOVSYNC); + //g.m_pD3DX->UpdateFrame(D3DX_UPDATE_NOVSYNC); + +//LPDIRECTDRAW7 pDD = g.m_pD3DX->GetDD(); + + //pDD->GetSurfaceFromDC(hdc,&g.m_ddsurface); + + //g.m_ddsurface->Flip(NULL,0); + g.m_d3ddev->BeginScene(); + return TRUE; +} + + +//////////////////////////////////////////// NOT USED by QuakeGL /////////////////////////////////////////////////////// + +#define DODPFS + +static void DPF(char *str) +{ + OutputDebugString(str); + OutputDebugString("\n"); +} + +static void APIENTRY d3dAccum (GLenum op, GLfloat value) +{ + op; + value; + + #ifdef DODPFS + DPF("glAccum"); + #endif //DODPFS +} + +static GLboolean APIENTRY d3dAreTexturesResident (GLsizei n, const GLuint *textures, GLboolean *residences) +{ + n; + textures; + residences; + + GLboolean dummy = FALSE; + + #ifdef DODPFS + DPF("glAreTexturesResident"); + #endif //DODPFS + + return dummy; +} + +static void APIENTRY d3dBitmap (GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap) +{ + width; + height; + xorig; + yorig; + xmove; + ymove; + bitmap; + + #ifdef DODPFS + DPF("glBitmap"); + #endif //DODPFS +} + +static void APIENTRY d3dCallList (GLuint list) +{ + list; + + #ifdef DODPFS + DPF("glCallList"); + #endif //DODPFS +} + +static void APIENTRY d3dCallLists (GLsizei n, GLenum type, const GLvoid *lists) +{ + n; + type; + lists; + + #ifdef DODPFS + DPF("glCallLists"); + #endif //DODPFS +} + +static void APIENTRY d3dClearAccum (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + red; + green; + blue; + alpha; + + #ifdef DODPFS + DPF("glClearAccum"); + #endif //DODPFS +} + +static void APIENTRY d3dClearIndex (GLfloat c) +{ + c; + + #ifdef DODPFS + DPF("glClearIndex"); + #endif //DODPFS +} + +static void APIENTRY d3dClearStencil (GLint s) +{ + s; + + #ifdef DODPFS + DPF("glClearStencil"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3b (GLbyte red, GLbyte green, GLbyte blue) +{ + red; + green; + blue; + + #ifdef DODPFS + DPF("glColor3b"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3bv (const GLbyte *v) +{ + v; + + #ifdef DODPFS + DPF("glColor3bv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3d (GLdouble red, GLdouble green, GLdouble blue) +{ + red; + green; + blue; + + #ifdef DODPFS + DPF("glColor3d"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glColor3dv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glColor3fv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3i (GLint red, GLint green, GLint blue) +{ + red; + green; + blue; + + #ifdef DODPFS + DPF("glColor3i"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glColor3iv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3s (GLshort red, GLshort green, GLshort blue) +{ + red; + green; + blue; + + #ifdef DODPFS + DPF("glColor3s"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glColor3sv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3ui (GLuint red, GLuint green, GLuint blue) +{ + red; + green; + blue; + + #ifdef DODPFS + DPF("glColor3ui"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3uiv (const GLuint *v) +{ + v; + + #ifdef DODPFS + DPF("glColor3uiv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3us (GLushort red, GLushort green, GLushort blue) +{ + red; + green; + blue; + + #ifdef DODPFS + DPF("glColor3us"); + #endif //DODPFS +} + +static void APIENTRY d3dColor3usv (const GLushort *v) +{ + v; + + #ifdef DODPFS + DPF("glColor3usv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4b (GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha) +{ + red; + green; + blue; + alpha; + + #ifdef DODPFS + DPF("glColor4b"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4bv (const GLbyte *v) +{ + v; + + #ifdef DODPFS + DPF("glColor4bv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4d (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha) +{ + red; + green; + blue; + alpha; + + #ifdef DODPFS + DPF("glColor4d"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glColor4dv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4i (GLint red, GLint green, GLint blue, GLint alpha) +{ + red; + green; + blue; + alpha; + + #ifdef DODPFS + DPF("glColor4i"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glColor4iv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4s (GLshort red, GLshort green, GLshort blue, GLshort alpha) +{ + red; + green; + blue; + alpha; + + #ifdef DODPFS + DPF("glColor4s"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glColor4sv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4ui (GLuint red, GLuint green, GLuint blue, GLuint alpha) +{ + red; + green; + blue; + alpha; + + #ifdef DODPFS + DPF("glColor4ui"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4uiv (const GLuint *v) +{ + v; + + #ifdef DODPFS + DPF("glColor4uiv"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4us (GLushort red, GLushort green, GLushort blue, GLushort alpha) +{ + red; + green; + blue; + alpha; + + #ifdef DODPFS + DPF("glColor4us"); + #endif //DODPFS +} + +static void APIENTRY d3dColor4usv (const GLushort *v) +{ + v; + + #ifdef DODPFS + DPF("glColor4usv"); + #endif //DODPFS +} + +static void APIENTRY d3dColorMask (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) +{ + red; + green; + blue; + alpha; + + #ifdef DODPFS + DPF("glColorMask"); + #endif //DODPFS +} + +static void APIENTRY d3dColorMaterial (GLenum face, GLenum mode) +{ + face; + mode; + + #ifdef DODPFS + DPF("glColorMaterial"); + #endif //DODPFS +} + +static void APIENTRY d3dCopyPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type) +{ + x; + y; + width; + height; + type; + + #ifdef DODPFS + DPF("glCopyPixels"); + #endif //DODPFS +} + +static void APIENTRY d3dCopyTexImage1D (GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border) +{ + target; + level; + internalFormat; + x; + y; + width; + border; + + #ifdef DODPFS + DPF("glCopyTexImage1D"); + #endif //DODPFS +} + +static void APIENTRY d3dCopyTexImage2D (GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) +{ + target; + level; + internalFormat; + x; + y; + width; + height; + border; + + #ifdef DODPFS + DPF("glCopyTexImage2D"); + #endif //DODPFS +} + +static void APIENTRY d3dCopyTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) +{ + target; + level; + xoffset; + x; + y; + width; + + #ifdef DODPFS + DPF("glCopyTexSubImage1D"); + #endif //DODPFS +} + +static void APIENTRY d3dCopyTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) +{ + target; + level; + xoffset; + yoffset; + x; + y; + width; + height; + + #ifdef DODPFS + DPF("glCopyTexSubImage2D"); + #endif //DODPFS +} + +static void APIENTRY d3dDeleteLists (GLuint list, GLsizei range) +{ + list; + range; + + #ifdef DODPFS + DPF("glDeleteLists"); + #endif //DODPFS +} + +static void APIENTRY d3dDrawPixels (GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) +{ + width; + height; + format; + type; + pixels; + + #ifdef DODPFS + DPF("glDrawPixels"); + #endif //DODPFS +} + +static void APIENTRY d3dEdgeFlag (GLboolean flag) +{ + flag; + + #ifdef DODPFS + DPF("glEdgeFlag"); + #endif //DODPFS +} + +static void APIENTRY d3dEdgeFlagPointer (GLsizei stride, const GLvoid *pointer) +{ + stride; + pointer; + + #ifdef DODPFS + DPF("glEdgeFlagPointer"); + #endif //DODPFS +} + +static void APIENTRY d3dEdgeFlagv (const GLboolean *flag) +{ + flag; + + #ifdef DODPFS + DPF("glEdgeFlagv"); + #endif //DODPFS +} + +static void APIENTRY d3dEndList (void) +{ + #ifdef DODPFS + DPF("d3dEndList"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalCoord1d (GLdouble u) +{ + u; + + #ifdef DODPFS + DPF("glEvalCoord1d"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalCoord1dv (const GLdouble *u) +{ + u; + + #ifdef DODPFS + DPF("glEvalCoord1dv"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalCoord1f (GLfloat u) +{ + u; + + #ifdef DODPFS + DPF("glEvalCoord1f"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalCoord1fv (const GLfloat *u) +{ + u; + + #ifdef DODPFS + DPF("glEvalCoord1fv"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalCoord2d (GLdouble u, GLdouble v) +{ + u; + v; + + #ifdef DODPFS + DPF("glEvalCoord2d"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalCoord2dv (const GLdouble *u) +{ + u; + + #ifdef DODPFS + DPF("glEvalCoord2dv"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalCoord2f (GLfloat u, GLfloat v) +{ + u; + v; + + #ifdef DODPFS + DPF("glEvalCoord2f"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalCoord2fv (const GLfloat *u) +{ + u; + + #ifdef DODPFS + DPF("glEvalCoord2fv"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalMesh1 (GLenum mode, GLint i1, GLint i2) +{ + mode; + i1; + i2; + + #ifdef DODPFS + DPF("glEvalMesh1"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalMesh2 (GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2) +{ + mode; + i1; + i2; + j1; + j2; + + #ifdef DODPFS + DPF("glEvalMesh2"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalPoint1 (GLint i) +{ + i; + + #ifdef DODPFS + DPF("glEvalPoint1"); + #endif //DODPFS +} + +static void APIENTRY d3dEvalPoint2 (GLint i, GLint j) +{ + i; + j; + + #ifdef DODPFS + DPF("glEvalPoint2"); + #endif //DODPFS +} + +static void APIENTRY d3dFeedbackBuffer (GLsizei size, GLenum type, GLfloat *buffer) +{ + size; + type; + buffer; + + #ifdef DODPFS + DPF("glFeedbackBuffer"); + #endif //DODPFS +} + +static void APIENTRY d3dFinish (void) +{ + #ifdef DODPFS + //DPF("glFinish()"); + #endif //DODPFS +} + +static void APIENTRY d3dFlush (void) +{ + #ifdef DODPFS + DPF("glFlush"); + #endif //DODPFS +} + +static void APIENTRY d3dFogiv (GLenum pname, const GLint *params) +{ + pname; + params; + + #ifdef DODPFS + DPF("glFogiv"); + #endif //DODPFS +} + +static GLuint APIENTRY d3dGenLists (GLsizei range) +{ + range; + + GLuint dummy = 0; + #ifdef DODPFS + DPF("glGenLists"); + #endif //DODPFS + return dummy; +} + +static void APIENTRY d3dGetBooleanv (GLenum pname, GLboolean *params) +{ + pname; + params; + + #ifdef DODPFS + DPF("glGetBooleanv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetClipPlane (GLenum plane, GLdouble *equation) +{ + plane; + equation; + + #ifdef DODPFS + DPF("glGetClipPlane"); + #endif //DODPFS +} + +static void APIENTRY d3dGetLightfv (GLenum light, GLenum pname, GLfloat *params) +{ + light; + pname; + params; + + #ifdef DODPFS + DPF("glGetLightfv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetLightiv (GLenum light, GLenum pname, GLint *params) +{ + light; + pname; + params; + + #ifdef DODPFS + DPF("glGetLightiv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetMapdv (GLenum target, GLenum query, GLdouble *v) +{ + target; + query; + v; + + #ifdef DODPFS + DPF("glGetMapdv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetMapfv (GLenum target, GLenum query, GLfloat *v) +{ + target; + query; + v; + + #ifdef DODPFS + DPF("glGetMapfv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetMapiv (GLenum target, GLenum query, GLint *v) +{ + target; + query; + v; + + #ifdef DODPFS + DPF("glGetMapiv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetMaterialfv (GLenum face, GLenum pname, GLfloat *params) +{ + face; + pname; + params; + + #ifdef DODPFS + DPF("glGetMaterialfv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetMaterialiv (GLenum face, GLenum pname, GLint *params) +{ + face; + pname; + params; + + #ifdef DODPFS + DPF("glGetMaterialiv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetPixelMapfv (GLenum map, GLfloat *values) +{ + map; + values; + + #ifdef DODPFS + DPF("glGetPixelMapfv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetPixelMapuiv (GLenum map, GLuint *values) +{ + map; + values; + + #ifdef DODPFS + DPF("glGetPixelMapuiv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetPixelMapusv (GLenum map, GLushort *values) +{ + map; + values; + + #ifdef DODPFS + DPF("glGetPixelMapusv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetPointerv (GLenum pname, GLvoid* *params) +{ + pname; + params; + + #ifdef DODPFS + DPF("glGetPointerv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetPolygonStipple (GLubyte *mask) +{ + mask; + + #ifdef DODPFS + DPF("glGetPolygonStipple"); + #endif //DODPFS +} + +static void APIENTRY d3dGetTexEnvfv (GLenum target, GLenum pname, GLfloat *params) +{ + target; + pname; + params; + + #ifdef DODPFS + DPF("glGetTexEnvfv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetTexGendv (GLenum coord, GLenum pname, GLdouble *params) +{ + coord; + pname; + params; + + #ifdef DODPFS + DPF("glGetTexGendv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetTexGenfv (GLenum coord, GLenum pname, GLfloat *params) +{ + coord; + pname; + params; + + #ifdef DODPFS + DPF("glGetTexGenfv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetTexGeniv (GLenum coord, GLenum pname, GLint *params) +{ + coord; + pname; + params; + + #ifdef DODPFS + DPF("glGetTexGeniv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetTexImage (GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels) +{ + target; + level; + format; + type; + pixels; + + #ifdef DODPFS + DPF("glGetTexImage"); + #endif //DODPFS +} + +static void APIENTRY d3dGetTexLevelParameterfv (GLenum target, GLint level, GLenum pname, GLfloat *params) +{ + target; + level; + pname; + params; + + #ifdef DODPFS + DPF("glGetTexLevelParameterfv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetTexLevelParameteriv (GLenum target, GLint level, GLenum pname, GLint *params) +{ + target; + level; + pname; + params; + + #ifdef DODPFS + DPF("glGetTexLevelParameteriv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetTexParameterfv (GLenum target, GLenum pname, GLfloat *params) +{ + target; + pname; + params; + + #ifdef DODPFS + DPF("glGetTexParameterfv"); + #endif //DODPFS +} + +static void APIENTRY d3dGetTexParameteriv (GLenum target, GLenum pname, GLint *params) +{ + target; + pname; + params; + + #ifdef DODPFS + DPF("glGetTexParameteriv"); + #endif //DODPFS +} + +static void APIENTRY d3dHint (GLenum target, GLenum mode) +{ + target; + mode; + + #ifdef DODPFS + //DPF("glHint(%X, %X)",target,mode); + DPF("glHint"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexMask (GLuint mask) +{ + mask; + + #ifdef DODPFS + DPF("glIndexMask"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexPointer (GLenum type, GLsizei stride, const GLvoid *pointer) +{ + type; + stride; + pointer; + + #ifdef DODPFS + DPF("glIndexPointer"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexd (GLdouble c) +{ + c; + + #ifdef DODPFS + DPF("glIndexd"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexdv (const GLdouble *c) +{ + c; + + #ifdef DODPFS + DPF("glIndexdv"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexf (GLfloat c) +{ + c; + + #ifdef DODPFS + DPF("glIndexf"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexfv (const GLfloat *c) +{ + c; + + #ifdef DODPFS + DPF("glIndexfv"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexi (GLint c) +{ + c; + + #ifdef DODPFS + DPF("glIndexi"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexiv (const GLint *c) +{ + c; + + #ifdef DODPFS + DPF("glIndexiv"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexs (GLshort c) +{ + c; + + #ifdef DODPFS + DPF("glIndexs"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexsv (const GLshort *c) +{ + c; + + #ifdef DODPFS + DPF("glIndexsv"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexub (GLubyte c) +{ + c; + + #ifdef DODPFS + DPF("glIndexub"); + #endif //DODPFS +} + +static void APIENTRY d3dIndexubv (const GLubyte *c) +{ + c; + + #ifdef DODPFS + DPF("glIndexubv"); + #endif //DODPFS +} + +static void APIENTRY d3dInitNames (void) +{ + #ifdef DODPFS + DPF("glInitNames"); + #endif //DODPFS +} + +static void APIENTRY d3dInterleavedArrays (GLenum format, GLsizei stride, const GLvoid *pointer) +{ + format; + stride; + pointer; + + #ifdef DODPFS + DPF("glInterleavedArrays"); + #endif //DODPFS +} + +static GLboolean APIENTRY d3dIsList (GLuint list) +{ + list; + + GLboolean dummy = FALSE; + #ifdef DODPFS + DPF("glIsList"); + #endif //DODPFS + return dummy; +} + +static GLboolean APIENTRY d3dIsTexture (GLuint texture) +{ + texture; + + GLboolean dummy = FALSE; + #ifdef DODPFS + DPF("glIsTexture"); + #endif //DODPFS + return dummy; +} + +static void APIENTRY d3dLightModelf (GLenum pname, GLfloat param) +{ + pname; + param; + + #ifdef DODPFS + DPF("glLightModelf"); + #endif //DODPFS +} + +static void APIENTRY d3dLightModeli (GLenum pname, GLint param) +{ + pname; + param; + + #ifdef DODPFS + DPF("glLightModeli"); + #endif //DODPFS +} + +static void APIENTRY d3dLightModeliv (GLenum pname, const GLint *params) +{ + pname; + params; + + #ifdef DODPFS + DPF("glLightModeliv"); + #endif //DODPFS +} + +static void APIENTRY d3dLighti (GLenum light, GLenum pname, GLint param) +{ + light; + pname; + param; + + #ifdef DODPFS + DPF("glLighti"); + #endif //DODPFS +} + +static void APIENTRY d3dLightiv (GLenum light, GLenum pname, const GLint *params) +{ + light; + pname; + params; + + #ifdef DODPFS + DPF("glLightiv"); + #endif //DODPFS +} + +static void APIENTRY d3dLineStipple (GLint factor, GLushort pattern) +{ + factor; + pattern; + + #ifdef DODPFS + DPF("glLineStipple"); + #endif //DODPFS +} + +static void APIENTRY d3dListBase (GLuint base) +{ + base; + + #ifdef DODPFS + DPF("glListBase"); + #endif //DODPFS +} + +static void APIENTRY d3dLoadMatrixd (const GLdouble *m) +{ + m; + + #ifdef DODPFS + DPF("glLoadMatrixd"); + #endif //DODPFS +} + +static void APIENTRY d3dLoadName (GLuint name) +{ + name; + + #ifdef DODPFS + DPF("glLoadName"); + #endif //DODPFS +} + +static void APIENTRY d3dLogicOp (GLenum opcode) +{ + opcode; + + #ifdef DODPFS + DPF("glLogicOp"); + #endif //DODPFS +} + +static void APIENTRY d3dMap1d (GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points) +{ + target; + u1; + u2; + stride; + order; + points; + + #ifdef DODPFS + DPF("glMap1d"); + #endif //DODPFS +} + +static void APIENTRY d3dMap1f (GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points) +{ + target; + u1; + u2; + stride; + order; + points; + + #ifdef DODPFS + DPF("glMap1f"); + #endif //DODPFS +} + +static void APIENTRY d3dMap2d (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points) +{ + target; + u1; + u2; + ustride; + uorder; + v1; + v2; + vstride; + vorder; + points; + + #ifdef DODPFS + DPF("glMap2d"); + #endif //DODPFS +} + +static void APIENTRY d3dMap2f (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points) +{ + target; + u1; + u2; + ustride; + uorder; + v1; + v2; + vstride; + vorder; + points; + + #ifdef DODPFS + DPF("glMap2f"); + #endif //DODPFS +} + +static void APIENTRY d3dMapGrid1d (GLint un, GLdouble u1, GLdouble u2) +{ + un; + u1; + u2; + + #ifdef DODPFS + DPF("glMapGrid1d"); + #endif //DODPFS +} + +static void APIENTRY d3dMapGrid1f (GLint un, GLfloat u1, GLfloat u2) +{ + un; + u1; + u2; + + #ifdef DODPFS + DPF("glMapGrid1f"); + #endif //DODPFS +} + +static void APIENTRY d3dMapGrid2d (GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2) +{ + un; + u1; + u2; + vn; + v1; + v2; + + #ifdef DODPFS + DPF("glMapGrid2d"); + #endif //DODPFS +} + +static void APIENTRY d3dMapGrid2f (GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2) +{ + un; + u1; + u2; + vn; + v1; + v2; + + #ifdef DODPFS + DPF("glMapGrid2f"); + #endif //DODPFS +} + +static void APIENTRY d3dMaterialf (GLenum face, GLenum pname, GLfloat param) +{ + face; + pname; + param; + + #ifdef DODPFS + DPF("glMaterialf"); + #endif //DODPFS +} + +static void APIENTRY d3dMateriali (GLenum face, GLenum pname, GLint param) +{ + face; + pname; + param; + + #ifdef DODPFS + DPF("glMateriali"); + #endif //DODPFS +} + +static void APIENTRY d3dMaterialiv (GLenum face, GLenum pname, const GLint *params) +{ + face; + pname; + params; + + #ifdef DODPFS + DPF("glMaterialiv"); + #endif //DODPFS +} + +static void APIENTRY d3dMultMatrixd (const GLdouble *m) +{ + m; + + #ifdef DODPFS + DPF("glMultMatrixd"); + #endif //DODPFS +} + +static void APIENTRY d3dNewList (GLuint list, GLenum mode) +{ + list; + mode; + + #ifdef DODPFS + DPF("glNewList"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3b (GLbyte nx, GLbyte ny, GLbyte nz) +{ + nx; + ny; + nz; + + #ifdef DODPFS + DPF("glNormal3b"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3bv (const GLbyte *v) +{ + v; + + #ifdef DODPFS + DPF("glNormal3bv"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3d (GLdouble nx, GLdouble ny, GLdouble nz) +{ + nx; + ny; + nz; + + #ifdef DODPFS + DPF("glNormal3d"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glNormal3dv"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3f (GLfloat nx, GLfloat ny, GLfloat nz) +{ + nx; + ny; + nz; + + #ifdef DODPFS + DPF("glNormal3f"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glNormal3fv"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3i (GLint nx, GLint ny, GLint nz) +{ + nx; + ny; + nz; + + #ifdef DODPFS + DPF("glNormal3i"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glNormal3iv"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3s (GLshort nx, GLshort ny, GLshort nz) +{ + nx; + ny; + nz; + + #ifdef DODPFS + DPF("glNormal3s"); + #endif //DODPFS +} + +static void APIENTRY d3dNormal3sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glNormal3sv"); + #endif //DODPFS +} + +static void APIENTRY d3dPassThrough (GLfloat token) +{ + token; + + #ifdef DODPFS + DPF("glPassThrough"); + #endif //DODPFS +} + +static void APIENTRY d3dPixelMapfv (GLenum map, GLsizei mapsize, const GLfloat *values) +{ + map; + mapsize; + values; + + #ifdef DODPFS + DPF("glPixelMapfv"); + #endif //DODPFS +} + +static void APIENTRY d3dPixelMapuiv (GLenum map, GLsizei mapsize, const GLuint *values) +{ + map; + mapsize; + values; + + #ifdef DODPFS + DPF("glPixelMapuiv"); + #endif //DODPFS +} + +static void APIENTRY d3dPixelMapusv (GLenum map, GLsizei mapsize, const GLushort *values) +{ + map; + mapsize; + values; + + #ifdef DODPFS + DPF("glPixelMapusv"); + #endif //DODPFS +} + +static void APIENTRY d3dPixelStoref (GLenum pname, GLfloat param) +{ + pname; + param; + + #ifdef DODPFS + DPF("glPixelStoref"); + #endif //DODPFS +} + +static void APIENTRY d3dPixelStorei (GLenum pname, GLint param) +{ + pname; + param; + + #ifdef DODPFS + DPF("glPixelStorei"); + #endif //DODPFS +} + +static void APIENTRY d3dPixelTransferf (GLenum pname, GLfloat param) +{ + pname; + param; + + #ifdef DODPFS + DPF("glPixelTransferf"); + #endif //DODPFS +} + +static void APIENTRY d3dPixelTransferi (GLenum pname, GLint param) +{ + pname; + param; + + #ifdef DODPFS + DPF("glPixelTransferi"); + #endif //DODPFS +} + +static void APIENTRY d3dPixelZoom (GLfloat xfactor, GLfloat yfactor) +{ + xfactor; + yfactor; + + #ifdef DODPFS + DPF("glPixelZoom"); + #endif //DODPFS +} + +static void APIENTRY d3dPolygonStipple (const GLubyte *mask) +{ + mask; + + #ifdef DODPFS + DPF("glPolygonStipple"); + #endif //DODPFS +} + +static void APIENTRY d3dPopAttrib (void) +{ + #ifdef DODPFS + DPF("glPopAttrib"); + #endif //DODPFS +} + +static void APIENTRY d3dPopClientAttrib (void) +{ + #ifdef DODPFS + DPF("glPopClientAttrib"); + #endif //DODPFS +} + +static void APIENTRY d3dPopName (void) +{ + #ifdef DODPFS + DPF("glPopName"); + #endif //DODPFS +} + +static void APIENTRY d3dPrioritizeTextures (GLsizei n, const GLuint *textures, const GLclampf *priorities) +{ + n; + textures; + priorities; + + #ifdef DODPFS + DPF("glPrioritizeTextures"); + #endif //DODPFS +} + +static void APIENTRY d3dPushAttrib (GLbitfield mask) +{ + mask; + + #ifdef DODPFS + DPF("glPushAttrib"); + #endif //DODPFS +} + +static void APIENTRY d3dPushClientAttrib (GLbitfield mask) +{ + mask; + + #ifdef DODPFS + DPF("glPushClientAttrib"); + #endif //DODPFS +} + +static void APIENTRY d3dPushName (GLuint name) +{ + name; + + #ifdef DODPFS + DPF("glPushName"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos2d (GLdouble x, GLdouble y) +{ + x; + y; + + #ifdef DODPFS + DPF("glRasterPos2d"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos2dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos2dv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos2f (GLfloat x, GLfloat y) +{ + x; + y; + + #ifdef DODPFS + DPF("glRasterPos2f"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos2fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos2fv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos2i (GLint x, GLint y) +{ + x; + y; + + #ifdef DODPFS + DPF("glRasterPos2i"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos2iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos2iv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos2s (GLshort x, GLshort y) +{ + x; + y; + + #ifdef DODPFS + DPF("glRasterPos2s"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos2sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos2sv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos3d (GLdouble x, GLdouble y, GLdouble z) +{ + x; + y; + z; + + #ifdef DODPFS + DPF("glRasterPos3d"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos3dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos3dv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos3f (GLfloat x, GLfloat y, GLfloat z) +{ + x; + y; + z; + + #ifdef DODPFS + DPF("glRasterPos3f"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos3fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos3fv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos3i (GLint x, GLint y, GLint z) +{ + x; + y; + z; + + #ifdef DODPFS + DPF("glRasterPos3i"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos3iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos3iv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos3s (GLshort x, GLshort y, GLshort z) +{ + x; + y; + z; + + #ifdef DODPFS + DPF("glRasterPos3s"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos3sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos3sv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + x; + y; + z; + w; + + #ifdef DODPFS + DPF("glRasterPos4d"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos4dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos4dv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + x; + y; + z; + w; + + #ifdef DODPFS + DPF("glRasterPos4f"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos4fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos4fv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos4i (GLint x, GLint y, GLint z, GLint w) +{ + x; + y; + z; + w; + + #ifdef DODPFS + DPF("glRasterPos4i"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos4iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos4iv"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos4s (GLshort x, GLshort y, GLshort z, GLshort w) +{ + x; + y; + z; + w; + + #ifdef DODPFS + DPF("glRasterPos4s"); + #endif //DODPFS +} + +static void APIENTRY d3dRasterPos4sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glRasterPos4sv"); + #endif //DODPFS +} + +static void APIENTRY d3dReadBuffer (GLenum mode) +{ + mode; + + #ifdef DODPFS + //DPF("glReadBuffer(%X)",mode); + DPF("glReadBuffer"); + #endif //DODPFS +} + +static void APIENTRY d3dReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) +{ + x; + y; + width; + height; + format; + type; + pixels; + + #ifdef DODPFS + DPF("glReadPixels"); + #endif //DODPFS +} + +static void APIENTRY d3dRectd (GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2) +{ + x1; + y1; + x2; + y2; + + #ifdef DODPFS + DPF("glRectd"); + #endif //DODPFS +} + +static void APIENTRY d3dRectdv (const GLdouble *v1, const GLdouble *v2) +{ + v1; + v2; + + #ifdef DODPFS + DPF("glRectdv"); + #endif //DODPFS +} + +static void APIENTRY d3dRectf (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) +{ + x1; + y1; + x2; + y2; + + #ifdef DODPFS + DPF("glRectf"); + #endif //DODPFS +} + +static void APIENTRY d3dRectfv (const GLfloat *v1, const GLfloat *v2) +{ + v1; + v2; + + #ifdef DODPFS + DPF("glRectfv"); + #endif //DODPFS +} + +static void APIENTRY d3dRectiv (const GLint *v1, const GLint *v2) +{ + v1; + v2; + + #ifdef DODPFS + DPF("glRectiv"); + #endif //DODPFS +} + +static void APIENTRY d3dRects (GLshort x1, GLshort y1, GLshort x2, GLshort y2) +{ + x1; + y1; + x2; + y2; + + #ifdef DODPFS + DPF("glRects"); + #endif //DODPFS +} + +static void APIENTRY d3dRectsv (const GLshort *v1, const GLshort *v2) +{ + v1; + v2; + + #ifdef DODPFS + DPF("glRectsv"); + #endif //DODPFS +} + +static GLint APIENTRY d3dRenderMode (GLenum mode) +{ + mode; + + GLint dummy = 0; + + #ifdef DODPFS + DPF("glRenderMode"); + #endif //DODPFS + + return dummy; +} + +static void APIENTRY d3dRotated (GLdouble angle, GLdouble x, GLdouble y, GLdouble z) +{ + angle; + x; + y; + z; + + #ifdef DODPFS + DPF("glRotated"); + #endif //DODPFS +} + +static void APIENTRY d3dScaled (GLdouble x, GLdouble y, GLdouble z) +{ + x; + y; + z; + + #ifdef DODPFS + DPF("glScaled"); + #endif //DODPFS +} + +static void APIENTRY d3dSelectBuffer (GLsizei size, GLuint *buffer) +{ + size; + buffer; + + #ifdef DODPFS + DPF("glSelectBuffer"); + #endif //DODPFS +} + +static void APIENTRY d3dStencilFunc (GLenum func, GLint ref, GLuint mask) +{ + func; + ref; + mask; + + #ifdef DODPFS + DPF("glStencilFunc"); + #endif //DODPFS +} + +static void APIENTRY d3dStencilMask (GLuint mask) +{ + mask; + + #ifdef DODPFS + DPF("glStencilMask"); + #endif //DODPFS +} + +static void APIENTRY d3dStencilOp (GLenum fail, GLenum zfail, GLenum zpass) +{ + fail; + zfail; + zpass; + + #ifdef DODPFS + DPF("glStencilOp"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord1d (GLdouble s) +{ + s; + + #ifdef DODPFS + DPF("glTexCoord1d"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord1dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord1dv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord1f (GLfloat s) +{ + s; + + #ifdef DODPFS + DPF("glTexCoord1f"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord1fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord1fv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord1i (GLint s) +{ + s; + + #ifdef DODPFS + DPF("glTexCoord1i"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord1iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord1iv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord1s (GLshort s) +{ + s; + + #ifdef DODPFS + DPF("glTexCoord1s"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord1sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord1sv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord2d (GLdouble s, GLdouble t) +{ + s; + t; + + #ifdef DODPFS + DPF("glTexCoord2d"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord2dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord2dv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord2i (GLint s, GLint t) +{ + s; + t; + + #ifdef DODPFS + DPF("glTexCoord2i"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord2iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord2iv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord2s (GLshort s, GLshort t) +{ + s; + t; + + #ifdef DODPFS + DPF("glTexCoord2s"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord2sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord2sv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord3d (GLdouble s, GLdouble t, GLdouble r) +{ + s; + t; + r; + + #ifdef DODPFS + DPF("glTexCoord3d"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord3dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord3dv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord3f (GLfloat s, GLfloat t, GLfloat r) +{ + s; + t; + r; + + #ifdef DODPFS + DPF("glTexCoord3f"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord3fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord3fv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord3i (GLint s, GLint t, GLint r) +{ + s; + t; + r; + + #ifdef DODPFS + DPF("glTexCoord3i"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord3iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord3iv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord3s (GLshort s, GLshort t, GLshort r) +{ + s; + t; + r; + + #ifdef DODPFS + DPF("glTexCoord3s"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord3sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord3sv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord4d (GLdouble s, GLdouble t, GLdouble r, GLdouble q) +{ + s; + t; + r; + q; + + #ifdef DODPFS + DPF("glTexCoord4d"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord4dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord4dv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord4f (GLfloat s, GLfloat t, GLfloat r, GLfloat q) +{ + s; + t; + r; + q; + + #ifdef DODPFS + DPF("glTexCoord4f"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord4fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord4fv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord4i (GLint s, GLint t, GLint r, GLint q) +{ + s; + t; + r; + q; + + #ifdef DODPFS + DPF("glTexCoord4i"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord4iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord4iv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord4s (GLshort s, GLshort t, GLshort r, GLshort q) +{ + s; + t; + r; + q; + + #ifdef DODPFS + DPF("glTexCoord4s"); + #endif //DODPFS +} + +static void APIENTRY d3dTexCoord4sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glTexCoord4sv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexEnviv (GLenum target, GLenum pname, const GLint *params) +{ + target; + pname; + params; + + #ifdef DODPFS + DPF("glTexEnviv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexGend (GLenum coord, GLenum pname, GLdouble param) +{ + coord; + pname; + param; + + #ifdef DODPFS + DPF("glTexGend"); + #endif //DODPFS +} + +static void APIENTRY d3dTexGendv (GLenum coord, GLenum pname, const GLdouble *params) +{ + coord; + pname; + params; + + #ifdef DODPFS + DPF("glTexGendv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexGenf (GLenum coord, GLenum pname, GLfloat param) +{ + coord; + pname; + param; + + #ifdef DODPFS + DPF("glTexGenf"); + #endif //DODPFS +} + +static void APIENTRY d3dTexGeniv (GLenum coord, GLenum pname, const GLint *params) +{ + coord; + pname; + params; + + #ifdef DODPFS + DPF("glTexGeniv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexImage1D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels) +{ + target; + level; + internalformat; + width; + border; + format; + type; + pixels; + + #ifdef DODPFS + DPF("glTexImage1D"); + #endif //DODPFS +} + +static void APIENTRY d3dTexParameterfv (GLenum target, GLenum pname, const GLfloat *params) +{ + target; + pname; + params; + + #ifdef DODPFS + DPF("glTexParameterfv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexParameteriv (GLenum target, GLenum pname, const GLint *params) +{ + target; + pname; + params; + + #ifdef DODPFS + DPF("glTexParameteriv"); + #endif //DODPFS +} + +static void APIENTRY d3dTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels) +{ + target; + level; + xoffset; + width; + format; + type; + pixels; + + #ifdef DODPFS + DPF("glTexSubImage1D"); + #endif //DODPFS +} + +static void APIENTRY d3dTranslated (GLdouble x, GLdouble y, GLdouble z) +{ + x; + y; + z; + + #ifdef DODPFS + DPF("glTranslated"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex2d (GLdouble x, GLdouble y) +{ + x; + y; + + #ifdef DODPFS + DPF("glVertex2d"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex2dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex2dv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex2fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex2fv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex2iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex2iv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex2s (GLshort x, GLshort y) +{ + x; + y; + + #ifdef DODPFS + DPF("glVertex2s"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex2sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex2sv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex3d (GLdouble x, GLdouble y, GLdouble z) +{ + x; + y; + z; + + #ifdef DODPFS + DPF("glVertex3d"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex3dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex3dv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex3i (GLint x, GLint y, GLint z) +{ + x; + y; + z; + + #ifdef DODPFS + DPF("glVertex3i"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex3iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex3iv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex3s (GLshort x, GLshort y, GLshort z) +{ + x; + y; + z; + + #ifdef DODPFS + DPF("glVertex3s"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex3sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex3sv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + x; + y; + z; + w; + + #ifdef DODPFS + DPF("glVertex4d"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex4dv (const GLdouble *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex4dv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + x; + y; + z; + w; + + #ifdef DODPFS + DPF("glVertex4f"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex4fv (const GLfloat *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex4fv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex4i (GLint x, GLint y, GLint z, GLint w) +{ + x; + y; + z; + w; + + #ifdef DODPFS + DPF("glVertex4i"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex4iv (const GLint *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex4iv"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex4s (GLshort x, GLshort y, GLshort z, GLshort w) +{ + x; + y; + z; + w; + + #ifdef DODPFS + DPF("glVertex4s"); + #endif //DODPFS +} + +static void APIENTRY d3dVertex4sv (const GLshort *v) +{ + v; + + #ifdef DODPFS + DPF("glVertex4sv"); + #endif //DODPFS +} + +static void APIENTRY d3dFogCoordfEXT (GLfloat f) +{ + f; + + #ifdef DODPFS + DPF("glFogCoordfEXT"); + #endif //DODPFS +} + +static BOOL WINAPI wd3dCopyContext(HGLRC src, HGLRC dst, UINT mask) +{ + src; + dst; + mask; + + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglCopyContext"); + #endif //DODPFS + return dummy; +} + +static HGLRC WINAPI wd3dCreateLayerContext(HDC hdc, int iLayerPlane) +{ + hdc; + iLayerPlane; + + HGLRC dummy = NULL; + #ifdef DODPFS + DPF("wglCreateLayerContext"); + #endif //DODPFS + return dummy; +} + +static BOOL WINAPI wd3dDescribeLayerPlane(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nBytes, LPLAYERPLANEDESCRIPTOR plpd) +{ + hdc; + iPixelFormat; + iLayerPlane; + nBytes; + plpd; + + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglDescribeLayerPlane"); + #endif //DODPFS + return dummy; +} + +static BOOL WINAPI wd3dGetDefaultProcAddress() +{ + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglGetDefaultProcAddress"); + #endif //DODPFS + return dummy; +} + +static int WINAPI wd3dGetLayerPaletteEntries(HDC hdc, int iLayerPlane, int iStart, int cEntries, COLORREF *pcr) +{ + hdc; + iLayerPlane; + iStart; + cEntries; + pcr; + + int dummy = 0; + #ifdef DODPFS + DPF("wglGetLayerPaletteEntries"); + #endif //DODPFS + return dummy; +} +static BOOL WINAPI wd3dRealizeLayerPalette(HDC hdc, int iLayerPlane, BOOL bRealize) +{ + hdc; + iLayerPlane; + bRealize; + + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglRealizeLayerPalette"); + #endif //DODPFS + return dummy; +} +static int WINAPI wd3dSetLayerPaletteEntries(HDC hdc, int iLayerPlane, int iStart, int cEntries, CONST COLORREF *pcr) +{ + hdc; + iLayerPlane; + iStart; + cEntries; + pcr; + + int dummy = 0; + #ifdef DODPFS + DPF("wglSetLayerPaletteEntries"); + #endif //DODPFS + return dummy; +} +static BOOL WINAPI wd3dShareLists(HGLRC hglrc1, HGLRC hglrc2) +{ + hglrc1; + hglrc2; + + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglShareLists"); + #endif //DODPFS + return dummy; +} +static BOOL WINAPI wd3dSwapLayerBuffers(HDC hdc, UINT fuPlanes) +{ + hdc; + fuPlanes; + + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglSwapLayerBuffers"); + #endif //DODPFS + return dummy; +} +static BOOL WINAPI wd3dUseFontBitmapsA(HDC hdc, DWORD first, DWORD count, DWORD listbase) +{ + hdc; + first; + count; + listbase; + + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglUseFontBitmapsA"); + #endif //DODPFS + return dummy; +} +static BOOL WINAPI wd3dUseFontBitmapsW() +{ + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglUseFontBitmapsW"); + #endif //DODPFS + return dummy; +} +static BOOL WINAPI wd3dUseFontOutlinesA(HDC hdc, DWORD first, DWORD count, DWORD listBase, float deviation, float extrusion, int format, LPGLYPHMETRICSFLOAT lpgmf) +{ + hdc; + first; + count; + listBase; + deviation; + extrusion; + format; + lpgmf; + + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglUseFontOutlinesA"); + #endif //DODPFS + return dummy; +} +static BOOL WINAPI wd3dUseFontOutlinesW() +{ + BOOL dummy = FALSE; + #ifdef DODPFS + DPF("wglUseFontOutlinesW"); + #endif //DODPFS + return dummy; +} + + +//////////////////////////////////////////// GLU Implementation /////////////////////////////////////////////////////// + +static const GLubyte *APIENTRY +d3duErrorString(GLenum errorCode) +{ + /* GLU Errors */ + if (errorCode == GLU_NO_ERROR) { + return (GLubyte *) "no error"; + } + else if (errorCode == GLU_INVALID_ENUM) { + return (GLubyte *) "invalid enum"; + } + else if (errorCode == GLU_INVALID_VALUE) { + return (GLubyte *) "invalid value"; + } + else if (errorCode == GLU_OUT_OF_MEMORY) { + return (GLubyte *) "out of memory"; + } + else { + return NULL; + } +} + +static const GLubyte *APIENTRY +d3duGetString(GLenum name) +{ + static char *extensions = ""; + static char *version = "1.0 Dynamix"; + + switch (name) { + case GLU_EXTENSIONS: + return (GLubyte *) extensions; + case GLU_VERSION: + return (GLubyte *) version; + default: + return NULL; + } +} + +static void APIENTRY +d3duOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top) +{ + d3dOrtho(left, right, bottom, top, -1.0, 1.0); +} + +static void APIENTRY +d3duPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar) +{ + GLdouble xmin, xmax, ymin, ymax; + + ymax = zNear * tan(fovy * M_PI / 360.0); + ymin = -ymax; + + xmin = ymin * aspect; + xmax = ymax * aspect; + + d3dFrustum(xmin, xmax, ymin, ymax, zNear, zFar); +} + +static void APIENTRY +d3duPickMatrix(GLdouble x, GLdouble y, + GLdouble width, GLdouble height, GLint viewport[4]) +{ + GLfloat m[16]; + GLfloat sx, sy; + GLfloat tx, ty; + + sx = (GLfloat) (viewport[2] / width); + sy = (GLfloat) (viewport[3] / height); + tx = (GLfloat) ((viewport[2] + 2.0 * (viewport[0] - x)) / width); + ty = (GLfloat) ((viewport[3] + 2.0 * (viewport[1] - y)) / height); + +#define M(row,col) m[col*4+row] + M(0, 0) = sx; + M(0, 1) = 0.0; + M(0, 2) = 0.0; + M(0, 3) = tx; + M(1, 0) = 0.0; + M(1, 1) = sy; + M(1, 2) = 0.0; + M(1, 3) = ty; + M(2, 0) = 0.0; + M(2, 1) = 0.0; + M(2, 2) = 1.0; + M(2, 3) = 0.0; + M(3, 0) = 0.0; + M(3, 1) = 0.0; + M(3, 2) = 0.0; + M(3, 3) = 1.0; +#undef M + + d3dMultMatrixf(m); +} + +static void APIENTRY +d3duLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, + GLdouble centerx, GLdouble centery, GLdouble centerz, + GLdouble upx, GLdouble upy, GLdouble upz) +{ + GLdouble m[16]; + GLdouble x[3], y[3], z[3]; + GLdouble mag; + + /* Make rotation matrix */ + + /* Z vector */ + z[0] = eyex - centerx; + z[1] = eyey - centery; + z[2] = eyez - centerz; + mag = sqrt(z[0] * z[0] + z[1] * z[1] + z[2] * z[2]); + if (mag) { /* mpichler, 19950515 */ + z[0] /= mag; + z[1] /= mag; + z[2] /= mag; + } + + /* Y vector */ + y[0] = upx; + y[1] = upy; + y[2] = upz; + + /* X vector = Y cross Z */ + x[0] = y[1] * z[2] - y[2] * z[1]; + x[1] = -y[0] * z[2] + y[2] * z[0]; + x[2] = y[0] * z[1] - y[1] * z[0]; + + /* Recompute Y = Z cross X */ + y[0] = z[1] * x[2] - z[2] * x[1]; + y[1] = -z[0] * x[2] + z[2] * x[0]; + y[2] = z[0] * x[1] - z[1] * x[0]; + + /* mpichler, 19950515 */ + /* cross product gives area of parallelogram, which is < 1.0 for + * non-perpendicular unit-length vectors; so normalize x, y here + */ + + mag = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]); + if (mag) { + x[0] /= mag; + x[1] /= mag; + x[2] /= mag; + } + + mag = sqrt(y[0] * y[0] + y[1] * y[1] + y[2] * y[2]); + if (mag) { + y[0] /= mag; + y[1] /= mag; + y[2] /= mag; + } + +#define M(row,col) m[col*4+row] + M(0, 0) = x[0]; + M(0, 1) = x[1]; + M(0, 2) = x[2]; + M(0, 3) = 0.0; + M(1, 0) = y[0]; + M(1, 1) = y[1]; + M(1, 2) = y[2]; + M(1, 3) = 0.0; + M(2, 0) = z[0]; + M(2, 1) = z[1]; + M(2, 2) = z[2]; + M(2, 3) = 0.0; + M(3, 0) = 0.0; + M(3, 1) = 0.0; + M(3, 2) = 0.0; + M(3, 3) = 1.0; +#undef M + d3dMultMatrixd(m); + + /* Translate Eye to Origin */ + d3dTranslated(-eyex, -eyey, -eyez); +} + +/* + * Transform a point (column vector) by a 4x4 matrix. I.e. out = m * in + * Input: m - the 4x4 matrix + * in - the 4x1 vector + * Output: out - the resulting 4x1 vector. + */ +static void +transform_point(GLdouble out[4], const GLdouble m[16], const GLdouble in[4]) +{ +#define M(row,col) m[col*4+row] + out[0] = + M(0, 0) * in[0] + M(0, 1) * in[1] + M(0, 2) * in[2] + M(0, 3) * in[3]; + out[1] = + M(1, 0) * in[0] + M(1, 1) * in[1] + M(1, 2) * in[2] + M(1, 3) * in[3]; + out[2] = + M(2, 0) * in[0] + M(2, 1) * in[1] + M(2, 2) * in[2] + M(2, 3) * in[3]; + out[3] = + M(3, 0) * in[0] + M(3, 1) * in[1] + M(3, 2) * in[2] + M(3, 3) * in[3]; +#undef M +} + +/* + * Perform a 4x4 matrix multiplication (product = a x b). + * Input: a, b - matrices to multiply + * Output: product - product of a and b + */ +static void +matmul(GLdouble * product, const GLdouble * a, const GLdouble * b) +{ + /* This matmul was contributed by Thomas Malik */ + GLdouble temp[16]; + GLint i; + +#define A(row,col) a[(col<<2)+row] +#define B(row,col) b[(col<<2)+row] +#define T(row,col) temp[(col<<2)+row] + + /* i-te Zeile */ + for (i = 0; i < 4; i++) { + T(i, 0) = + A(i, 0) * B(0, 0) + A(i, 1) * B(1, 0) + A(i, 2) * B(2, 0) + A(i, + 3) * + B(3, 0); + T(i, 1) = + A(i, 0) * B(0, 1) + A(i, 1) * B(1, 1) + A(i, 2) * B(2, 1) + A(i, + 3) * + B(3, 1); + T(i, 2) = + A(i, 0) * B(0, 2) + A(i, 1) * B(1, 2) + A(i, 2) * B(2, 2) + A(i, + 3) * + B(3, 2); + T(i, 3) = + A(i, 0) * B(0, 3) + A(i, 1) * B(1, 3) + A(i, 2) * B(2, 3) + A(i, + 3) * + B(3, 3); + } + +#undef A +#undef B +#undef T + memcpy(product, temp, 16 * sizeof(GLdouble)); +} + +/* + * Compute inverse of 4x4 transformation matrix. + * Code contributed by Jacques Leroy jle@star.be + * Return GL_TRUE for success, GL_FALSE for failure (singular matrix) + */ +static GLboolean +invert_matrix(const GLdouble * m, GLdouble * out) +{ +/* NB. OpenGL Matrices are COLUMN major. */ +#define SWAP_ROWS(a, b) { GLdouble *_tmp = a; (a)=(b); (b)=_tmp; } +#define MAT(m,r,c) (m)[(c)*4+(r)] + + GLdouble wtmp[4][8]; + GLdouble m0, m1, m2, m3, s; + GLdouble *r0, *r1, *r2, *r3; + + r0 = wtmp[0], r1 = wtmp[1], r2 = wtmp[2], r3 = wtmp[3]; + + r0[0] = MAT(m, 0, 0), r0[1] = MAT(m, 0, 1), + r0[2] = MAT(m, 0, 2), r0[3] = MAT(m, 0, 3), + r0[4] = 1.0, r0[5] = r0[6] = r0[7] = 0.0, + r1[0] = MAT(m, 1, 0), r1[1] = MAT(m, 1, 1), + r1[2] = MAT(m, 1, 2), r1[3] = MAT(m, 1, 3), + r1[5] = 1.0, r1[4] = r1[6] = r1[7] = 0.0, + r2[0] = MAT(m, 2, 0), r2[1] = MAT(m, 2, 1), + r2[2] = MAT(m, 2, 2), r2[3] = MAT(m, 2, 3), + r2[6] = 1.0, r2[4] = r2[5] = r2[7] = 0.0, + r3[0] = MAT(m, 3, 0), r3[1] = MAT(m, 3, 1), + r3[2] = MAT(m, 3, 2), r3[3] = MAT(m, 3, 3), + r3[7] = 1.0, r3[4] = r3[5] = r3[6] = 0.0; + + /* choose pivot - or die */ + if (fabs(r3[0]) > fabs(r2[0])) + SWAP_ROWS(r3, r2); + if (fabs(r2[0]) > fabs(r1[0])) + SWAP_ROWS(r2, r1); + if (fabs(r1[0]) > fabs(r0[0])) + SWAP_ROWS(r1, r0); + if (0.0 == r0[0]) + return GL_FALSE; + + /* eliminate first variable */ + m1 = r1[0] / r0[0]; + m2 = r2[0] / r0[0]; + m3 = r3[0] / r0[0]; + s = r0[1]; + r1[1] -= m1 * s; + r2[1] -= m2 * s; + r3[1] -= m3 * s; + s = r0[2]; + r1[2] -= m1 * s; + r2[2] -= m2 * s; + r3[2] -= m3 * s; + s = r0[3]; + r1[3] -= m1 * s; + r2[3] -= m2 * s; + r3[3] -= m3 * s; + s = r0[4]; + if (s != 0.0) { + r1[4] -= m1 * s; + r2[4] -= m2 * s; + r3[4] -= m3 * s; + } + s = r0[5]; + if (s != 0.0) { + r1[5] -= m1 * s; + r2[5] -= m2 * s; + r3[5] -= m3 * s; + } + s = r0[6]; + if (s != 0.0) { + r1[6] -= m1 * s; + r2[6] -= m2 * s; + r3[6] -= m3 * s; + } + s = r0[7]; + if (s != 0.0) { + r1[7] -= m1 * s; + r2[7] -= m2 * s; + r3[7] -= m3 * s; + } + + /* choose pivot - or die */ + if (fabs(r3[1]) > fabs(r2[1])) + SWAP_ROWS(r3, r2); + if (fabs(r2[1]) > fabs(r1[1])) + SWAP_ROWS(r2, r1); + if (0.0 == r1[1]) + return GL_FALSE; + + /* eliminate second variable */ + m2 = r2[1] / r1[1]; + m3 = r3[1] / r1[1]; + r2[2] -= m2 * r1[2]; + r3[2] -= m3 * r1[2]; + r2[3] -= m2 * r1[3]; + r3[3] -= m3 * r1[3]; + s = r1[4]; + if (0.0 != s) { + r2[4] -= m2 * s; + r3[4] -= m3 * s; + } + s = r1[5]; + if (0.0 != s) { + r2[5] -= m2 * s; + r3[5] -= m3 * s; + } + s = r1[6]; + if (0.0 != s) { + r2[6] -= m2 * s; + r3[6] -= m3 * s; + } + s = r1[7]; + if (0.0 != s) { + r2[7] -= m2 * s; + r3[7] -= m3 * s; + } + + /* choose pivot - or die */ + if (fabs(r3[2]) > fabs(r2[2])) + SWAP_ROWS(r3, r2); + if (0.0 == r2[2]) + return GL_FALSE; + + /* eliminate third variable */ + m3 = r3[2] / r2[2]; + r3[3] -= m3 * r2[3], r3[4] -= m3 * r2[4], + r3[5] -= m3 * r2[5], r3[6] -= m3 * r2[6], r3[7] -= m3 * r2[7]; + + /* last check */ + if (0.0 == r3[3]) + return GL_FALSE; + + s = 1.0 / r3[3]; /* now back substitute row 3 */ + r3[4] *= s; + r3[5] *= s; + r3[6] *= s; + r3[7] *= s; + + m2 = r2[3]; /* now back substitute row 2 */ + s = 1.0 / r2[2]; + r2[4] = s * (r2[4] - r3[4] * m2), r2[5] = s * (r2[5] - r3[5] * m2), + r2[6] = s * (r2[6] - r3[6] * m2), r2[7] = s * (r2[7] - r3[7] * m2); + m1 = r1[3]; + r1[4] -= r3[4] * m1, r1[5] -= r3[5] * m1, + r1[6] -= r3[6] * m1, r1[7] -= r3[7] * m1; + m0 = r0[3]; + r0[4] -= r3[4] * m0, r0[5] -= r3[5] * m0, + r0[6] -= r3[6] * m0, r0[7] -= r3[7] * m0; + + m1 = r1[2]; /* now back substitute row 1 */ + s = 1.0 / r1[1]; + r1[4] = s * (r1[4] - r2[4] * m1), r1[5] = s * (r1[5] - r2[5] * m1), + r1[6] = s * (r1[6] - r2[6] * m1), r1[7] = s * (r1[7] - r2[7] * m1); + m0 = r0[2]; + r0[4] -= r2[4] * m0, r0[5] -= r2[5] * m0, + r0[6] -= r2[6] * m0, r0[7] -= r2[7] * m0; + + m0 = r0[1]; /* now back substitute row 0 */ + s = 1.0 / r0[0]; + r0[4] = s * (r0[4] - r1[4] * m0), r0[5] = s * (r0[5] - r1[5] * m0), + r0[6] = s * (r0[6] - r1[6] * m0), r0[7] = s * (r0[7] - r1[7] * m0); + + MAT(out, 0, 0) = r0[4]; + MAT(out, 0, 1) = r0[5], MAT(out, 0, 2) = r0[6]; + MAT(out, 0, 3) = r0[7], MAT(out, 1, 0) = r1[4]; + MAT(out, 1, 1) = r1[5], MAT(out, 1, 2) = r1[6]; + MAT(out, 1, 3) = r1[7], MAT(out, 2, 0) = r2[4]; + MAT(out, 2, 1) = r2[5], MAT(out, 2, 2) = r2[6]; + MAT(out, 2, 3) = r2[7], MAT(out, 3, 0) = r3[4]; + MAT(out, 3, 1) = r3[5], MAT(out, 3, 2) = r3[6]; + MAT(out, 3, 3) = r3[7]; + + return GL_TRUE; + +#undef MAT +#undef SWAP_ROWS +} + +/* projection du point (objx,objy,obz) sur l'ecran (winx,winy,winz) */ +static GLint APIENTRY +d3duProject(GLdouble objx, GLdouble objy, GLdouble objz, + const GLdouble model[16], const GLdouble proj[16], + const GLint viewport[4], + GLdouble * winx, GLdouble * winy, GLdouble * winz) +{ + /* matrice de transformation */ + GLdouble in[4], out[4]; + + /* initilise la matrice et le vecteur a transformer */ + in[0] = objx; + in[1] = objy; + in[2] = objz; + in[3] = 1.0; + transform_point(out, model, in); + transform_point(in, proj, out); + + /* d'ou le resultat normalise entre -1 et 1 */ + if (in[3] == 0.0) + return GL_FALSE; + + in[0] /= in[3]; + in[1] /= in[3]; + in[2] /= in[3]; + + /* en coordonnees ecran */ + *winx = viewport[0] + (1 + in[0]) * viewport[2] / 2; + *winy = viewport[1] + (1 + in[1]) * viewport[3] / 2; + /* entre 0 et 1 suivant z */ + *winz = (1 + in[2]) / 2; + return GL_TRUE; +} + +/* transformation du point ecran (winx,winy,winz) en point objet */ +static GLint APIENTRY +d3duUnProject(GLdouble winx, GLdouble winy, GLdouble winz, + const GLdouble model[16], const GLdouble proj[16], + const GLint viewport[4], + GLdouble * objx, GLdouble * objy, GLdouble * objz) +{ + /* matrice de transformation */ + GLdouble m[16], A[16]; + GLdouble in[4], out[4]; + + /* transformation coordonnees normalisees entre -1 et 1 */ + in[0] = (winx - viewport[0]) * 2 / viewport[2] - 1.0; + in[1] = (winy - viewport[1]) * 2 / viewport[3] - 1.0; + in[2] = 2 * winz - 1.0; + in[3] = 1.0; + + /* calcul transformation inverse */ + matmul(A, proj, model); + invert_matrix(A, m); + + /* d'ou les coordonnees objets */ + transform_point(out, m, in); + if (out[3] == 0.0) + return GL_FALSE; + *objx = out[0] / out[3]; + *objy = out[1] / out[3]; + *objz = out[2] / out[3]; + return GL_TRUE; +} + +static GLint APIENTRY +d3duScaleImage(GLenum format, + GLsizei widthin, GLsizei heightin, + GLenum typein, const void *datain, + GLsizei widthout, GLsizei heightout, + GLenum typeout, void *dataout) +{ + GLint components, i, j, k; + GLfloat *tempin, *tempout; + GLfloat sx, sy; + GLint unpackrowlength, unpackalignment, unpackskiprows, unpackskippixels; + GLint packrowlength, packalignment, packskiprows, packskippixels; + GLint sizein, sizeout; + GLint rowstride, rowlen; + + + /* Determine number of components per pixel */ + switch (format) { + case GL_COLOR_INDEX: + case GL_STENCIL_INDEX: + case GL_DEPTH_COMPONENT: + case GL_RED: + case GL_GREEN: + case GL_BLUE: + case GL_ALPHA: + case GL_LUMINANCE: + components = 1; + break; + case GL_LUMINANCE_ALPHA: + components = 2; + break; + case GL_RGB: + case GL_BGR: + components = 3; + break; + case GL_RGBA: + case GL_BGRA: +#ifdef GL_EXT_abgr + case GL_ABGR_EXT: +#endif + components = 4; + break; + default: + return GLU_INVALID_ENUM; + } + + /* Determine bytes per input datum */ + switch (typein) { + case GL_UNSIGNED_BYTE: + sizein = sizeof(GLubyte); + break; + case GL_BYTE: + sizein = sizeof(GLbyte); + break; + case GL_UNSIGNED_SHORT: + sizein = sizeof(GLushort); + break; + case GL_SHORT: + sizein = sizeof(GLshort); + break; + case GL_UNSIGNED_INT: + sizein = sizeof(GLuint); + break; + case GL_INT: + sizein = sizeof(GLint); + break; + case GL_FLOAT: + sizein = sizeof(GLfloat); + break; + case GL_BITMAP: + /* not implemented yet */ + default: + return GL_INVALID_ENUM; + } + + /* Determine bytes per output datum */ + switch (typeout) { + case GL_UNSIGNED_BYTE: + sizeout = sizeof(GLubyte); + break; + case GL_BYTE: + sizeout = sizeof(GLbyte); + break; + case GL_UNSIGNED_SHORT: + sizeout = sizeof(GLushort); + break; + case GL_SHORT: + sizeout = sizeof(GLshort); + break; + case GL_UNSIGNED_INT: + sizeout = sizeof(GLuint); + break; + case GL_INT: + sizeout = sizeof(GLint); + break; + case GL_FLOAT: + sizeout = sizeof(GLfloat); + break; + case GL_BITMAP: + /* not implemented yet */ + default: + return GL_INVALID_ENUM; + } + + /* Get glPixelStore state */ + d3dGetIntegerv(GL_UNPACK_ROW_LENGTH, &unpackrowlength); + d3dGetIntegerv(GL_UNPACK_ALIGNMENT, &unpackalignment); + d3dGetIntegerv(GL_UNPACK_SKIP_ROWS, &unpackskiprows); + d3dGetIntegerv(GL_UNPACK_SKIP_PIXELS, &unpackskippixels); + d3dGetIntegerv(GL_PACK_ROW_LENGTH, &packrowlength); + d3dGetIntegerv(GL_PACK_ALIGNMENT, &packalignment); + d3dGetIntegerv(GL_PACK_SKIP_ROWS, &packskiprows); + d3dGetIntegerv(GL_PACK_SKIP_PIXELS, &packskippixels); + + /* Allocate storage for intermediate images */ + tempin = (GLfloat *) malloc(widthin * heightin + * components * sizeof(GLfloat)); + if (!tempin) { + return GLU_OUT_OF_MEMORY; + } + tempout = (GLfloat *) malloc(widthout * heightout + * components * sizeof(GLfloat)); + if (!tempout) { + free(tempin); + return GLU_OUT_OF_MEMORY; + } + + + /* + * Unpack the pixel data and convert to floating point + */ + + if (unpackrowlength > 0) { + rowlen = unpackrowlength; + } + else { + rowlen = widthin; + } + if (sizein >= unpackalignment) { + rowstride = components * rowlen; + } + else { + rowstride = unpackalignment / sizein + * CEILING(components * rowlen * sizein, unpackalignment); + } + + switch (typein) { + case GL_UNSIGNED_BYTE: + k = 0; + for (i = 0; i < heightin; i++) { + GLubyte *ubptr = (GLubyte *) datain + + i * rowstride + + unpackskiprows * rowstride + unpackskippixels * components; + for (j = 0; j < widthin * components; j++) { + dummy(j, k); + tempin[k++] = (GLfloat) * ubptr++; + } + } + break; + case GL_BYTE: + k = 0; + for (i = 0; i < heightin; i++) { + GLbyte *bptr = (GLbyte *) datain + + i * rowstride + + unpackskiprows * rowstride + unpackskippixels * components; + for (j = 0; j < widthin * components; j++) { + dummy(j, k); + tempin[k++] = (GLfloat) * bptr++; + } + } + break; + case GL_UNSIGNED_SHORT: + k = 0; + for (i = 0; i < heightin; i++) { + GLushort *usptr = (GLushort *) datain + + i * rowstride + + unpackskiprows * rowstride + unpackskippixels * components; + for (j = 0; j < widthin * components; j++) { + dummy(j, k); + tempin[k++] = (GLfloat) * usptr++; + } + } + break; + case GL_SHORT: + k = 0; + for (i = 0; i < heightin; i++) { + GLshort *sptr = (GLshort *) datain + + i * rowstride + + unpackskiprows * rowstride + unpackskippixels * components; + for (j = 0; j < widthin * components; j++) { + dummy(j, k); + tempin[k++] = (GLfloat) * sptr++; + } + } + break; + case GL_UNSIGNED_INT: + k = 0; + for (i = 0; i < heightin; i++) { + GLuint *uiptr = (GLuint *) datain + + i * rowstride + + unpackskiprows * rowstride + unpackskippixels * components; + for (j = 0; j < widthin * components; j++) { + dummy(j, k); + tempin[k++] = (GLfloat) * uiptr++; + } + } + break; + case GL_INT: + k = 0; + for (i = 0; i < heightin; i++) { + GLint *iptr = (GLint *) datain + + i * rowstride + + unpackskiprows * rowstride + unpackskippixels * components; + for (j = 0; j < widthin * components; j++) { + dummy(j, k); + tempin[k++] = (GLfloat) * iptr++; + } + } + break; + case GL_FLOAT: + k = 0; + for (i = 0; i < heightin; i++) { + GLfloat *fptr = (GLfloat *) datain + + i * rowstride + + unpackskiprows * rowstride + unpackskippixels * components; + for (j = 0; j < widthin * components; j++) { + dummy(j, k); + tempin[k++] = *fptr++; + } + } + break; + default: + return GLU_INVALID_ENUM; + } + + + /* + * Scale the image! + */ + + if (widthout > 1) + sx = (GLfloat) (widthin - 1) / (GLfloat) (widthout - 1); + else + sx = (GLfloat) (widthin - 1); + if (heightout > 1) + sy = (GLfloat) (heightin - 1) / (GLfloat) (heightout - 1); + else + sy = (GLfloat) (heightin - 1); + +/*#define POINT_SAMPLE*/ +#ifdef POINT_SAMPLE + for (i = 0; i < heightout; i++) { + GLint ii = i * sy; + for (j = 0; j < widthout; j++) { + GLint jj = j * sx; + + GLfloat *src = tempin + (ii * widthin + jj) * components; + GLfloat *dst = tempout + (i * widthout + j) * components; + + for (k = 0; k < components; k++) { + *dst++ = *src++; + } + } + } +#else + if (sx < 1.0 && sy < 1.0) { + /* magnify both width and height: use weighted sample of 4 pixels */ + GLint i0, i1, j0, j1; + GLfloat alpha, beta; + GLfloat *src00, *src01, *src10, *src11; + GLfloat s1, s2; + GLfloat *dst; + + for (i = 0; i < heightout; i++) { + i0 = (GLint) (i * sy); + i1 = i0 + 1; + if (i1 >= heightin) + i1 = heightin - 1; +/* i1 = (i+1) * sy - EPSILON;*/ + alpha = i * sy - i0; + for (j = 0; j < widthout; j++) { + j0 = (GLint) (j * sx); + j1 = j0 + 1; + if (j1 >= widthin) + j1 = widthin - 1; +/* j1 = (j+1) * sx - EPSILON; */ + beta = j * sx - j0; + + /* compute weighted average of pixels in rect (i0,j0)-(i1,j1) */ + src00 = tempin + (i0 * widthin + j0) * components; + src01 = tempin + (i0 * widthin + j1) * components; + src10 = tempin + (i1 * widthin + j0) * components; + src11 = tempin + (i1 * widthin + j1) * components; + + dst = tempout + (i * widthout + j) * components; + + for (k = 0; k < components; k++) { + s1 = (GLfloat) (*src00++ * (1.0 - beta) + *src01++ * beta); + s2 = (GLfloat) (*src10++ * (1.0 - beta) + *src11++ * beta); + *dst++ = (GLfloat) (s1 * (1.0 - alpha) + s2 * alpha); + } + } + } + } + else { + /* shrink width and/or height: use an unweighted box filter */ + GLint i0, i1; + GLint j0, j1; + GLint ii, jj; + GLfloat sum, *dst; + + for (i = 0; i < heightout; i++) { + i0 = (GLint) (i * sy); + i1 = i0 + 1; + if (i1 >= heightin) + i1 = heightin - 1; +/* i1 = (i+1) * sy - EPSILON; */ + for (j = 0; j < widthout; j++) { + j0 = (GLint) (j * sx); + j1 = j0 + 1; + if (j1 >= widthin) + j1 = widthin - 1; +/* j1 = (j+1) * sx - EPSILON; */ + + dst = tempout + (i * widthout + j) * components; + + /* compute average of pixels in the rectangle (i0,j0)-(i1,j1) */ + for (k = 0; k < components; k++) { + sum = 0.0; + for (ii = i0; ii <= i1; ii++) { + for (jj = j0; jj <= j1; jj++) { + sum += *(tempin + (ii * widthin + jj) * components + k); + } + } + sum /= (j1 - j0 + 1) * (i1 - i0 + 1); + *dst++ = sum; + } + } + } + } +#endif + + + /* + * Return output image + */ + + if (packrowlength > 0) { + rowlen = packrowlength; + } + else { + rowlen = widthout; + } + if (sizeout >= packalignment) { + rowstride = components * rowlen; + } + else { + rowstride = packalignment / sizeout + * CEILING(components * rowlen * sizeout, packalignment); + } + + switch (typeout) { + case GL_UNSIGNED_BYTE: + k = 0; + for (i = 0; i < heightout; i++) { + GLubyte *ubptr = (GLubyte *) dataout + + i * rowstride + + packskiprows * rowstride + packskippixels * components; + for (j = 0; j < widthout * components; j++) { + dummy(j, k + i); + *ubptr++ = (GLubyte) tempout[k++]; + } + } + break; + case GL_BYTE: + k = 0; + for (i = 0; i < heightout; i++) { + GLbyte *bptr = (GLbyte *) dataout + + i * rowstride + + packskiprows * rowstride + packskippixels * components; + for (j = 0; j < widthout * components; j++) { + dummy(j, k + i); + *bptr++ = (GLbyte) tempout[k++]; + } + } + break; + case GL_UNSIGNED_SHORT: + k = 0; + for (i = 0; i < heightout; i++) { + GLushort *usptr = (GLushort *) dataout + + i * rowstride + + packskiprows * rowstride + packskippixels * components; + for (j = 0; j < widthout * components; j++) { + dummy(j, k + i); + *usptr++ = (GLushort) tempout[k++]; + } + } + break; + case GL_SHORT: + k = 0; + for (i = 0; i < heightout; i++) { + GLshort *sptr = (GLshort *) dataout + + i * rowstride + + packskiprows * rowstride + packskippixels * components; + for (j = 0; j < widthout * components; j++) { + dummy(j, k + i); + *sptr++ = (GLshort) tempout[k++]; + } + } + break; + case GL_UNSIGNED_INT: + k = 0; + for (i = 0; i < heightout; i++) { + GLuint *uiptr = (GLuint *) dataout + + i * rowstride + + packskiprows * rowstride + packskippixels * components; + for (j = 0; j < widthout * components; j++) { + dummy(j, k + i); + *uiptr++ = (GLuint) tempout[k++]; + } + } + break; + case GL_INT: + k = 0; + for (i = 0; i < heightout; i++) { + GLint *iptr = (GLint *) dataout + + i * rowstride + + packskiprows * rowstride + packskippixels * components; + for (j = 0; j < widthout * components; j++) { + dummy(j, k + i); + *iptr++ = (GLint) tempout[k++]; + } + } + break; + case GL_FLOAT: + k = 0; + for (i = 0; i < heightout; i++) { + GLfloat *fptr = (GLfloat *) dataout + + i * rowstride + + packskiprows * rowstride + packskippixels * components; + for (j = 0; j < widthout * components; j++) { + dummy(j, k + i); + *fptr++ = tempout[k++]; + } + } + break; + default: + return GLU_INVALID_ENUM; + } + + + /* free temporary image storage */ + free(tempin); + free(tempout); + + return 0; +} + +/* + * Return the largest k such that 2^k <= n. + */ +static GLint +ilog2(GLint n) +{ + GLint k; + + if (n <= 0) + return 0; + for (k = 0; n >>= 1; k++) {} + return k; +} + +/* + * Find the value nearest to n which is also a power of two. + */ +static GLint +round2(GLint n) +{ + GLint m; + + for (m = 1; m < n; m *= 2) {} + + /* m>=n */ + if (m - n <= n - m / 2) { + return m; + } + else { + return m / 2; + } +} + +/* + * WARNING: This function isn't finished and has never been tested!!!! + */ +static GLint APIENTRY +d3duBuild1DMipmaps(GLenum target, GLint components, + GLsizei width, GLenum format, GLenum type, const void *data) +{ + target; + + GLubyte *texture; + GLint levels, max_levels; + GLint new_width, max_width; + GLint i, j, k, l; + + if (width < 1) + return GLU_INVALID_VALUE; + + d3dGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_width); + max_levels = ilog2(max_width) + 1; + + /* Compute how many mipmap images to make */ + levels = ilog2(width) + 1; + if (levels > max_levels) { + levels = max_levels; + } + + new_width = 1 << (levels - 1); + + texture = (GLubyte *) malloc(new_width * components); + if (!texture) { + return GLU_OUT_OF_MEMORY; + } + + if (width != new_width) { + /* initial rescaling */ + switch (type) { + case GL_UNSIGNED_BYTE: + { + GLubyte *ub_data = (GLubyte *) data; + for (i = 0; i < new_width; i++) { + j = i * width / new_width; + for (k = 0; k < components; k++) { + texture[i * components + k] = ub_data[j * components + k]; + } + } + } + break; + default: + /* Not implemented */ + return GLU_ERROR; + } + } + + /* generate and load mipmap images */ + for (l = 0; l < levels; l++) { + d3dTexImage1D(GL_TEXTURE_1D, l, components, new_width, 0, + format, GL_UNSIGNED_BYTE, texture); + + /* Scale image down to 1/2 size */ + new_width = new_width / 2; + for (i = 0; i < new_width; i++) { + for (k = 0; k < components; k++) { + GLint sample1, sample2; + sample1 = (GLint) texture[i * 2 * components + k]; + sample2 = (GLint) texture[(i * 2 + 1) * components + k]; + texture[i * components + k] = (GLubyte) ((sample1 + sample2) / 2); + } + } + } + + free(texture); + + return 0; +} + +/* + * Given an pixel format and datatype, return the number of bytes to + * store one pixel. + */ +static GLint +bytes_per_pixel(GLenum format, GLenum type) +{ + GLint n, m; + + switch (format) { + case GL_COLOR_INDEX: + case GL_STENCIL_INDEX: + case GL_DEPTH_COMPONENT: + case GL_RED: + case GL_GREEN: + case GL_BLUE: + case GL_ALPHA: + case GL_LUMINANCE: + n = 1; + break; + case GL_LUMINANCE_ALPHA: + n = 2; + break; + case GL_RGB: + case GL_BGR: + n = 3; + break; + case GL_RGBA: + case GL_BGRA: +#ifdef GL_EXT_abgr + case GL_ABGR_EXT: +#endif + n = 4; + break; + default: + n = 0; + } + + switch (type) { + case GL_UNSIGNED_BYTE: + m = sizeof(GLubyte); + break; + case GL_BYTE: + m = sizeof(GLbyte); + break; + case GL_BITMAP: + m = 1; + break; + case GL_UNSIGNED_SHORT: + m = sizeof(GLushort); + break; + case GL_SHORT: + m = sizeof(GLshort); + break; + case GL_UNSIGNED_INT: + m = sizeof(GLuint); + break; + case GL_INT: + m = sizeof(GLint); + break; + case GL_FLOAT: + m = sizeof(GLfloat); + break; + default: + m = 0; + } + + return n * m; +} + +static GLint APIENTRY +d3duBuild2DMipmaps(GLenum target, GLint components, + GLsizei width, GLsizei height, GLenum format, + GLenum type, const void *data) +{ + GLint w, h, maxsize; + void *image, *newimage; + GLint neww, newh, level, bpp; + int error; + GLboolean done; + GLint retval = 0; + GLint unpackrowlength, unpackalignment, unpackskiprows, unpackskippixels; + GLint packrowlength, packalignment, packskiprows, packskippixels; + + if (width < 1 || height < 1) + return GLU_INVALID_VALUE; + + d3dGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxsize); + + w = round2(width); + if (w > maxsize) { + w = maxsize; + } + h = round2(height); + if (h > maxsize) { + h = maxsize; + } + + bpp = bytes_per_pixel(format, type); + if (bpp == 0) { + /* probably a bad format or type enum */ + return GLU_INVALID_ENUM; + } + + /* Get current glPixelStore values */ + d3dGetIntegerv(GL_UNPACK_ROW_LENGTH, &unpackrowlength); + d3dGetIntegerv(GL_UNPACK_ALIGNMENT, &unpackalignment); + d3dGetIntegerv(GL_UNPACK_SKIP_ROWS, &unpackskiprows); + d3dGetIntegerv(GL_UNPACK_SKIP_PIXELS, &unpackskippixels); + d3dGetIntegerv(GL_PACK_ROW_LENGTH, &packrowlength); + d3dGetIntegerv(GL_PACK_ALIGNMENT, &packalignment); + d3dGetIntegerv(GL_PACK_SKIP_ROWS, &packskiprows); + d3dGetIntegerv(GL_PACK_SKIP_PIXELS, &packskippixels); + + /* set pixel packing */ + d3dPixelStorei(GL_PACK_ROW_LENGTH, 0); + d3dPixelStorei(GL_PACK_ALIGNMENT, 1); + d3dPixelStorei(GL_PACK_SKIP_ROWS, 0); + d3dPixelStorei(GL_PACK_SKIP_PIXELS, 0); + + done = GL_FALSE; + + if (w != width || h != height) { + /* must rescale image to get "top" mipmap texture image */ + image = malloc((w + 4) * h * bpp); + if (!image) { + return GLU_OUT_OF_MEMORY; + } + error = d3duScaleImage(format, width, height, type, data, + w, h, type, image); + if (error) { + retval = error; + done = GL_TRUE; + } + } + else { + image = (void *) data; + } + + level = 0; + while (!done) { + if (image != data) { + /* set pixel unpacking */ + d3dPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + d3dPixelStorei(GL_UNPACK_ALIGNMENT, 1); + d3dPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + d3dPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + } + + d3dTexImage2D(target, level, components, w, h, 0, format, type, image); + + if (w == 1 && h == 1) + break; + + neww = (w < 2) ? 1 : w / 2; + newh = (h < 2) ? 1 : h / 2; + newimage = malloc((neww + 4) * newh * bpp); + if (!newimage) { + return GLU_OUT_OF_MEMORY; + } + + error = d3duScaleImage(format, w, h, type, image, + neww, newh, type, newimage); + if (error) { + retval = error; + done = GL_TRUE; + } + + if (image != data) { + free(image); + } + image = newimage; + + w = neww; + h = newh; + level++; + } + + if (image != data) { + free(image); + } + + /* Restore original glPixelStore state */ + d3dPixelStorei(GL_UNPACK_ROW_LENGTH, unpackrowlength); + d3dPixelStorei(GL_UNPACK_ALIGNMENT, unpackalignment); + d3dPixelStorei(GL_UNPACK_SKIP_ROWS, unpackskiprows); + d3dPixelStorei(GL_UNPACK_SKIP_PIXELS, unpackskippixels); + d3dPixelStorei(GL_PACK_ROW_LENGTH, packrowlength); + d3dPixelStorei(GL_PACK_ALIGNMENT, packalignment); + d3dPixelStorei(GL_PACK_SKIP_ROWS, packskiprows); + d3dPixelStorei(GL_PACK_SKIP_PIXELS, packskippixels); + + return retval; +} + + +/* +** D3DGL_Init +** +** This is responsible for binding our gl function pointers to +** the appropriate D3DGL stuff. +** +*/ + +#define GPA_D3D( a ) GetProcAddress( winState.hinstD3D, a ) + +//-------------------------------------- +bool D3DGL_Init() +{ + // GLU Functions + gluErrorString = d3duErrorString; + gluGetString = d3duGetString; + gluOrtho2D = d3duOrtho2D; + gluPerspective = d3duPerspective; + gluPickMatrix = d3duPickMatrix; + gluLookAt = d3duLookAt; + gluProject = d3duProject; + gluUnProject = d3duUnProject; + gluScaleImage = d3duScaleImage; + gluBuild1DMipmaps = d3duBuild1DMipmaps; + gluBuild2DMipmaps = d3duBuild2DMipmaps; + + // GL Functions + glAccum = d3dAccum; + glAlphaFunc = d3dAlphaFunc; + glAreTexturesResident = d3dAreTexturesResident; + glArrayElement = d3dArrayElement; + glBegin = d3dBegin; + glBindTexture = d3dBindTexture; + glBitmap = d3dBitmap; + glBlendFunc = d3dBlendFunc; + glCallList = d3dCallList; + glCallLists = d3dCallLists; + glClear = d3dClear; + glClearAccum = d3dClearAccum; + glClearColor = d3dClearColor; + glClearDepth = d3dClearDepth; + glClearIndex = d3dClearIndex; + glClearStencil = d3dClearStencil; + glClipPlane = d3dClipPlane; + glColor3b = d3dColor3b; + glColor3bv = d3dColor3bv; + glColor3d = d3dColor3d; + glColor3dv = d3dColor3dv; + glColor3f = d3dColor3f; + glColor3fv = d3dColor3fv; + glColor3i = d3dColor3i; + glColor3iv = d3dColor3iv; + glColor3s = d3dColor3s; + glColor3sv = d3dColor3sv; + glColor3ub = d3dColor3ub; + glColor3ubv = d3dColor3ubv; + glColor3ui = d3dColor3ui; + glColor3uiv = d3dColor3uiv; + glColor3us = d3dColor3us; + glColor3usv = d3dColor3usv; + glColor4b = d3dColor4b; + glColor4bv = d3dColor4bv; + glColor4d = d3dColor4d; + glColor4dv = d3dColor4dv; + glColor4f = d3dColor4f; + glColor4fv = d3dColor4fv; + glColor4i = d3dColor4i; + glColor4iv = d3dColor4iv; + glColor4s = d3dColor4s; + glColor4sv = d3dColor4sv; + glColor4ub = d3dColor4ub; + glColor4ubv = d3dColor4ubv; + glColor4ui = d3dColor4ui; + glColor4uiv = d3dColor4uiv; + glColor4us = d3dColor4us; + glColor4usv = d3dColor4usv; + glColorMask = d3dColorMask; + glColorMaterial = d3dColorMaterial; + glColorPointer = d3dColorPointer; + glCopyPixels = d3dCopyPixels; + glCopyTexImage1D = d3dCopyTexImage1D; + glCopyTexImage2D = d3dCopyTexImage2D; + glCopyTexSubImage1D = d3dCopyTexSubImage1D; + glCopyTexSubImage2D = d3dCopyTexSubImage2D; + glCullFace = d3dCullFace; + glDeleteLists = d3dDeleteLists; + glDeleteTextures = d3dDeleteTextures; + glDepthFunc = d3dDepthFunc; + glDepthMask = d3dDepthMask; + glDepthRange = d3dDepthRange; + glDisable = d3dDisable; + glDisableClientState = d3dDisableClientState; + glDrawArrays = d3dDrawArrays; + glDrawBuffer = d3dDrawBuffer; + glDrawElements = d3dDrawElements; + glDrawPixels = d3dDrawPixels; + glEdgeFlag = d3dEdgeFlag; + glEdgeFlagPointer = d3dEdgeFlagPointer; + glEdgeFlagv = d3dEdgeFlagv; + glEnable = d3dEnable; + glEnableClientState = d3dEnableClientState; + glEnd = d3dEnd; + glEndList = d3dEndList; + glEvalCoord1d = d3dEvalCoord1d; + glEvalCoord1dv = d3dEvalCoord1dv; + glEvalCoord1f = d3dEvalCoord1f; + glEvalCoord1fv = d3dEvalCoord1fv; + glEvalCoord2d = d3dEvalCoord2d; + glEvalCoord2dv = d3dEvalCoord2dv; + glEvalCoord2f = d3dEvalCoord2f; + glEvalCoord2fv = d3dEvalCoord2fv; + glEvalMesh1 = d3dEvalMesh1; + glEvalMesh2 = d3dEvalMesh2; + glEvalPoint1 = d3dEvalPoint1; + glEvalPoint2 = d3dEvalPoint2; + glFeedbackBuffer = d3dFeedbackBuffer; + glFinish = d3dFinish; + glFlush = d3dFlush; + glFogf = d3dFogf; + glFogfv = d3dFogfv; + glFogi = d3dFogi; + glFogiv = d3dFogiv; + glFrontFace = d3dFrontFace; + glFrustum = d3dFrustum; + glGenLists = d3dGenLists; + glGenTextures = d3dGenTextures; + glGetBooleanv = d3dGetBooleanv; + glGetClipPlane = d3dGetClipPlane; + glGetDoublev = d3dGetDoublev; + glGetError = d3dGetError; + glGetFloatv = d3dGetFloatv; + glGetIntegerv = d3dGetIntegerv; + glGetLightfv = d3dGetLightfv; + glGetLightiv = d3dGetLightiv; + glGetMapdv = d3dGetMapdv; + glGetMapfv = d3dGetMapfv; + glGetMapiv = d3dGetMapiv; + glGetMaterialfv = d3dGetMaterialfv; + glGetMaterialiv = d3dGetMaterialiv; + glGetPixelMapfv = d3dGetPixelMapfv; + glGetPixelMapuiv = d3dGetPixelMapuiv; + glGetPixelMapusv = d3dGetPixelMapusv; + glGetPointerv = d3dGetPointerv; + glGetPolygonStipple = d3dGetPolygonStipple; + glGetString = d3dGetString; + glGetTexEnvfv = d3dGetTexEnvfv; + glGetTexEnviv = d3dGetTexEnviv; + glGetTexGendv = d3dGetTexGendv; + glGetTexGenfv = d3dGetTexGenfv; + glGetTexGeniv = d3dGetTexGeniv; + glGetTexImage = d3dGetTexImage; + glGetTexLevelParameterfv = d3dGetTexLevelParameterfv; + glGetTexLevelParameteriv = d3dGetTexLevelParameteriv; + glGetTexParameterfv = d3dGetTexParameterfv; + glGetTexParameteriv = d3dGetTexParameteriv; + glHint = d3dHint; + glIndexMask = d3dIndexMask; + glIndexPointer = d3dIndexPointer; + glIndexd = d3dIndexd; + glIndexdv = d3dIndexdv; + glIndexf = d3dIndexf; + glIndexfv = d3dIndexfv; + glIndexi = d3dIndexi; + glIndexiv = d3dIndexiv; + glIndexs = d3dIndexs; + glIndexsv = d3dIndexsv; + glIndexub = d3dIndexub; + glIndexubv = d3dIndexubv; + glInitNames = d3dInitNames; + glInterleavedArrays = d3dInterleavedArrays; + glIsEnabled = d3dIsEnabled; + glIsList = d3dIsList; + glIsTexture = d3dIsTexture; + glLightModelf = d3dLightModelf; + glLightModelfv = d3dLightModelfv; + glLightModeli = d3dLightModeli; + glLightModeliv = d3dLightModeliv; + glLightf = d3dLightf; + glLightfv = d3dLightfv; + glLighti = d3dLighti; + glLightiv = d3dLightiv; + glLineStipple = d3dLineStipple; + glLineWidth = d3dLineWidth; + glListBase = d3dListBase; + glLoadIdentity = d3dLoadIdentity; + glLoadMatrixd = d3dLoadMatrixd; + glLoadMatrixf = d3dLoadMatrixf; + glLoadName = d3dLoadName; + glLogicOp = d3dLogicOp; + glMap1d = d3dMap1d; + glMap1f = d3dMap1f; + glMap2d = d3dMap2d; + glMap2f = d3dMap2f; + glMapGrid1d = d3dMapGrid1d; + glMapGrid1f = d3dMapGrid1f; + glMapGrid2d = d3dMapGrid2d; + glMapGrid2f = d3dMapGrid2f; + glMaterialf = d3dMaterialf; + glMaterialfv = d3dMaterialfv; + glMateriali = d3dMateriali; + glMaterialiv = d3dMaterialiv; + glMatrixMode = d3dMatrixMode; + glMultMatrixd = d3dMultMatrixd; + glMultMatrixf = d3dMultMatrixf; + glNewList = d3dNewList; + glNormal3b = d3dNormal3b; + glNormal3bv = d3dNormal3bv; + glNormal3d = d3dNormal3d; + glNormal3dv = d3dNormal3dv; + glNormal3f = d3dNormal3f; + glNormal3fv = d3dNormal3fv; + glNormal3i = d3dNormal3i; + glNormal3iv = d3dNormal3iv; + glNormal3s = d3dNormal3s; + glNormal3sv = d3dNormal3sv; + glNormalPointer = d3dNormalPointer; + glOrtho = d3dOrtho; + glPassThrough = d3dPassThrough; + glPixelMapfv = d3dPixelMapfv; + glPixelMapuiv = d3dPixelMapuiv; + glPixelMapusv = d3dPixelMapusv; + glPixelStoref = d3dPixelStoref; + glPixelStorei = d3dPixelStorei; + glPixelTransferf = d3dPixelTransferf; + glPixelTransferi = d3dPixelTransferi; + glPixelZoom = d3dPixelZoom; + glPointSize = d3dPointSize; + glPolygonMode = d3dPolygonMode; + glPolygonOffset = d3dPolygonOffset; + glPolygonStipple = d3dPolygonStipple; + glPopAttrib = d3dPopAttrib; + glPopClientAttrib = d3dPopClientAttrib; + glPopMatrix = d3dPopMatrix; + glPopName = d3dPopName; + glPrioritizeTextures = d3dPrioritizeTextures; + glPushAttrib = d3dPushAttrib; + glPushClientAttrib = d3dPushClientAttrib; + glPushMatrix = d3dPushMatrix; + glPushName = d3dPushName; + glRasterPos2d = d3dRasterPos2d; + glRasterPos2dv = d3dRasterPos2dv; + glRasterPos2f = d3dRasterPos2f; + glRasterPos2fv = d3dRasterPos2fv; + glRasterPos2i = d3dRasterPos2i; + glRasterPos2iv = d3dRasterPos2iv; + glRasterPos2s = d3dRasterPos2s; + glRasterPos2sv = d3dRasterPos2sv; + glRasterPos3d = d3dRasterPos3d; + glRasterPos3dv = d3dRasterPos3dv; + glRasterPos3f = d3dRasterPos3f; + glRasterPos3fv = d3dRasterPos3fv; + glRasterPos3i = d3dRasterPos3i; + glRasterPos3iv = d3dRasterPos3iv; + glRasterPos3s = d3dRasterPos3s; + glRasterPos3sv = d3dRasterPos3sv; + glRasterPos4d = d3dRasterPos4d; + glRasterPos4dv = d3dRasterPos4dv; + glRasterPos4f = d3dRasterPos4f; + glRasterPos4fv = d3dRasterPos4fv; + glRasterPos4i = d3dRasterPos4i; + glRasterPos4iv = d3dRasterPos4iv; + glRasterPos4s = d3dRasterPos4s; + glRasterPos4sv = d3dRasterPos4sv; + glReadBuffer = d3dReadBuffer; + glReadPixels = d3dReadPixels; + glRectd = d3dRectd; + glRectdv = d3dRectdv; + glRectf = d3dRectf; + glRectfv = d3dRectfv; + glRecti = d3dRecti; + glRectiv = d3dRectiv; + glRects = d3dRects; + glRectsv = d3dRectsv; + glRenderMode = d3dRenderMode; + glRotated = d3dRotated; + glRotatef = d3dRotatef; + glScaled = d3dScaled; + glScalef = d3dScalef; + glScissor = d3dScissor; + glSelectBuffer = d3dSelectBuffer; + glShadeModel = d3dShadeModel; + glStencilFunc = d3dStencilFunc; + glStencilMask = d3dStencilMask; + glStencilOp = d3dStencilOp; + glTexCoord1d = d3dTexCoord1d; + glTexCoord1dv = d3dTexCoord1dv; + glTexCoord1f = d3dTexCoord1f; + glTexCoord1fv = d3dTexCoord1fv; + glTexCoord1i = d3dTexCoord1i; + glTexCoord1iv = d3dTexCoord1iv; + glTexCoord1s = d3dTexCoord1s; + glTexCoord1sv = d3dTexCoord1sv; + glTexCoord2d = d3dTexCoord2d; + glTexCoord2dv = d3dTexCoord2dv; + glTexCoord2f = d3dTexCoord2f; + glTexCoord2fv = d3dTexCoord2fv; + glTexCoord2i = d3dTexCoord2i; + glTexCoord2iv = d3dTexCoord2iv; + glTexCoord2s = d3dTexCoord2s; + glTexCoord2sv = d3dTexCoord2sv; + glTexCoord3d = d3dTexCoord3d; + glTexCoord3dv = d3dTexCoord3dv; + glTexCoord3f = d3dTexCoord3f; + glTexCoord3fv = d3dTexCoord3fv; + glTexCoord3i = d3dTexCoord3i; + glTexCoord3iv = d3dTexCoord3iv; + glTexCoord3s = d3dTexCoord3s; + glTexCoord3sv = d3dTexCoord3sv; + glTexCoord4d = d3dTexCoord4d; + glTexCoord4dv = d3dTexCoord4dv; + glTexCoord4f = d3dTexCoord4f; + glTexCoord4fv = d3dTexCoord4fv; + glTexCoord4i = d3dTexCoord4i; + glTexCoord4iv = d3dTexCoord4iv; + glTexCoord4s = d3dTexCoord4s; + glTexCoord4sv = d3dTexCoord4sv; + glTexCoordPointer = d3dTexCoordPointer; + glTexEnvf = d3dTexEnvf; + glTexEnvfv = d3dTexEnvfv; + glTexEnvi = d3dTexEnvi; + glTexEnviv = d3dTexEnviv; + glTexGend = d3dTexGend; + glTexGendv = d3dTexGendv; + glTexGenf = d3dTexGenf; + glTexGenfv = d3dTexGenfv; + glTexGeni = d3dTexGeni; + glTexGeniv = d3dTexGeniv; + glTexImage1D = d3dTexImage1D; + glTexImage2D = d3dTexImage2D; + glTexParameterf = d3dTexParameterf; + glTexParameterfv = d3dTexParameterfv; + glTexParameteri = d3dTexParameteri; + glTexParameteriv = d3dTexParameteriv; + glTexSubImage1D = d3dTexSubImage1D; + glTexSubImage2D = d3dTexSubImage2D; + glTranslated = d3dTranslated; + glTranslatef = d3dTranslatef; + glVertex2d = d3dVertex2d; + glVertex2dv = d3dVertex2dv; + glVertex2f = d3dVertex2f; + glVertex2fv = d3dVertex2fv; + glVertex2i = d3dVertex2i; + glVertex2iv = d3dVertex2iv; + + glVertex2s = d3dVertex2s; + glVertex2sv = d3dVertex2sv; + glVertex3d = d3dVertex3d; + glVertex3dv = d3dVertex3dv; + glVertex3f = d3dVertex3f; + glVertex3fv = d3dVertex3fv; + glVertex3i = d3dVertex3i; + glVertex3iv = d3dVertex3iv; + glVertex3s = d3dVertex3s; + glVertex3sv = d3dVertex3sv; + glVertex4d = d3dVertex4d; + glVertex4dv = d3dVertex4dv; + glVertex4f = d3dVertex4f; + glVertex4fv = d3dVertex4fv; + glVertex4i = d3dVertex4i; + glVertex4iv = d3dVertex4iv; + glVertex4s = d3dVertex4s; + glVertex4sv = d3dVertex4sv; + glVertexPointer = d3dVertexPointer; + glViewport = d3dViewport; + + qwglCopyContext = wd3dCopyContext; + qwglCreateContext = wd3dCreateContext; + qwglCreateLayerContext = wd3dCreateLayerContext; + qwglDeleteContext = wd3dDeleteContext; + qwglDescribeLayerPlane = wd3dDescribeLayerPlane; + qwglGetCurrentContext = wd3dGetCurrentContext; + qwglGetCurrentDC = wd3dGetCurrentDC; + qwglGetLayerPaletteEntries = wd3dGetLayerPaletteEntries; + qwglGetProcAddress = wd3dGetProcAddress; + qwglMakeCurrent = wd3dMakeCurrent; + qwglRealizeLayerPalette = wd3dRealizeLayerPalette; + qwglSetLayerPaletteEntries = wd3dSetLayerPaletteEntries; + qwglShareLists = wd3dShareLists; + qwglSwapLayerBuffers = wd3dSwapLayerBuffers; + qwglUseFontBitmaps = wd3dUseFontBitmapsA; + qwglUseFontOutlines = wd3dUseFontOutlinesA; + + qwglChoosePixelFormat = wd3dChoosePixelFormat; + qwglDescribePixelFormat = wd3dDescribePixelFormat; + qwglGetPixelFormat = wd3dGetPixelFormat; + qwglSetPixelFormat = wd3dSetPixelFormat; + qwglSwapBuffers = wd3dSwapBuffers; + + qwglSwapIntervalEXT = 0; + + return true; +} + +bool D3DGL_EXT_Init() +{ + // Load extensions... + // + const char* pExtString = reinterpret_cast(glGetString(GL_EXTENSIONS)); + + gGLState.primMode = 0; + + // EXT_compiled_vertex_array + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_compiled_vertex_array") != NULL) { + glLockArraysEXT = d3dLockArraysEXT; + glUnlockArraysEXT = d3dUnlockArraysEXT; + gGLState.suppLockedArrays = true; + } else { + glLockArraysEXT = NULL; + glUnlockArraysEXT = NULL; + gGLState.suppLockedArrays = false; + } + + // ARB_multitexture + if (pExtString && dStrstr(pExtString, (const char*)"GL_ARB_multitexture") != NULL) { + glActiveTextureARB = d3dActiveTextureARB; + glClientActiveTextureARB = d3dClientActiveTextureARB; + glMultiTexCoord2fARB = d3dMultiTexCoord2fARB; + glMultiTexCoord2fvARB = d3dMultiTexCoord2fvARB; + gGLState.suppARBMultitexture = true; + } else { + glActiveTextureARB = NULL; + glClientActiveTextureARB = NULL; + glMultiTexCoord2fARB = NULL; + glMultiTexCoord2fvARB = NULL; + gGLState.suppARBMultitexture = false; + } + + // NV_vertex_array_range + glVertexArrayRangeNV = NULL; + glFlushVertexArrayRangeNV = NULL; + wglAllocateMemoryNV = NULL; + wglFreeMemoryNV = NULL; + gGLState.suppVertexArrayRange = false; + + // EXT_fog_coord + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_fog_coord") != NULL) { + glFogCoordfEXT = d3dFogCoordfEXT; + glFogCoordPointerEXT = d3dFogCoordPointerEXT; + gGLState.suppFogCoord = true; + } else { + glFogCoordfEXT = NULL; + glFogCoordPointerEXT = NULL; + gGLState.suppFogCoord = false; + } + + // ARB_texture_compression + glCompressedTexImage3DARB = NULL; + glCompressedTexImage2DARB = NULL; + glCompressedTexImage1DARB = NULL; + glCompressedTexSubImage3DARB = NULL; + glCompressedTexSubImage2DARB = NULL; + glCompressedTexSubImage1DARB = NULL; + glGetCompressedTexImageARB = NULL; + + gGLState.suppTextureCompression = false; + + // 3DFX_texture_compression_FXT1 + gGLState.suppFXT1 = false; + + // EXT_texture_compression_S3TC + gGLState.suppS3TC = false; + + // WGL_3DFS_gamma_control + qwglGetDeviceGammaRamp3DFX = NULL; + qwglSetDeviceGammaRamp3DFX = NULL; + + // Binary states, i.e., no supporting functions + // EXT_packed_pixels + // EXT_texture_env_combine + // + gGLState.suppPackedPixels = false; + gGLState.suppTextureEnvCombine = false; + gGLState.suppEdgeClamp = false; + gGLState.suppTexEnvAdd = false; + + // Anisotropic filtering + gGLState.suppTexAnisotropic = false; + + Con::printf("OpenGL Init: Enabled Extensions"); + if (gGLState.suppARBMultitexture) Con::printf(" ARB_multitexture"); + 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); + + Con::warnf(ConsoleLogEntry::General, "OpenGL Init: Disabled Extensions"); + if (!gGLState.suppARBMultitexture) Con::warnf(ConsoleLogEntry::General, " ARB_multitexture"); + if (!gGLState.suppLockedArrays) Con::warnf(ConsoleLogEntry::General, " EXT_compiled_vertex_array"); + if (!gGLState.suppVertexArrayRange) Con::warnf(ConsoleLogEntry::General, " NV_vertex_array_range"); + if (!gGLState.suppTextureEnvCombine) Con::warnf(ConsoleLogEntry::General, " EXT_texture_env_combine"); + if (!gGLState.suppPackedPixels) Con::warnf(ConsoleLogEntry::General, " EXT_packed_pixels"); + if (!gGLState.suppFogCoord) Con::warnf(ConsoleLogEntry::General, " EXT_fog_coord"); + if (!gGLState.suppTextureCompression) Con::warnf(ConsoleLogEntry::General, " ARB_texture_compression"); + if (!gGLState.suppS3TC) Con::warnf(ConsoleLogEntry::General, " EXT_texture_compression_s3tc"); + if (!gGLState.suppFXT1) Con::warnf(ConsoleLogEntry::General, " 3DFX_texture_compression_FXT1"); + if (!gGLState.suppTexEnvAdd) Con::warnf(ConsoleLogEntry::General, " (ARB|EXT)_texture_env_add"); + if (!gGLState.suppTexAnisotropic) Con::warnf(ConsoleLogEntry::General, " EXT_texture_filter_anisotropic"); + Con::printf(""); + + // Set some console variables: + Con::setBoolVariable( "$FogCoordSupported", gGLState.suppFogCoord ); + Con::setBoolVariable( "$TextureCompressionSupported", gGLState.suppTextureCompression ); + Con::setBoolVariable( "$AnisotropySupported", gGLState.suppTexAnisotropic ); + + return true; +} + +/* +** D3DGL_Shutdown +** +** This is only called during a hard shutdown of the OGL subsystem (e.g. vid_restart). +*/ +void D3DGL_Shutdown() +{ + // GLU Functions + gluErrorString = NULL; + gluGetString = NULL; + gluOrtho2D = NULL; + gluPerspective = NULL; + gluPickMatrix = NULL; + gluLookAt = NULL; + gluProject = NULL; + gluUnProject = NULL; + gluScaleImage = NULL; + gluBuild1DMipmaps = NULL; + gluBuild2DMipmaps = NULL; + + // GL Functions + glAccum = NULL; + glAlphaFunc = NULL; + glAreTexturesResident = NULL; + glArrayElement = NULL; + glBegin = NULL; + glBindTexture = NULL; + glBitmap = NULL; + glBlendFunc = NULL; + glCallList = NULL; + glCallLists = NULL; + glClear = NULL; + glClearAccum = NULL; + glClearColor = NULL; + glClearDepth = NULL; + glClearIndex = NULL; + glClearStencil = NULL; + glClipPlane = NULL; + glColor3b = NULL; + glColor3bv = NULL; + glColor3d = NULL; + glColor3dv = NULL; + glColor3f = NULL; + glColor3fv = NULL; + glColor3i = NULL; + glColor3iv = NULL; + glColor3s = NULL; + glColor3sv = NULL; + glColor3ub = NULL; + glColor3ubv = NULL; + glColor3ui = NULL; + glColor3uiv = NULL; + glColor3us = NULL; + glColor3usv = NULL; + glColor4b = NULL; + glColor4bv = NULL; + glColor4d = NULL; + glColor4dv = NULL; + glColor4f = NULL; + glColor4fv = NULL; + glColor4i = NULL; + glColor4iv = NULL; + glColor4s = NULL; + glColor4sv = NULL; + glColor4ub = NULL; + glColor4ubv = NULL; + glColor4ui = NULL; + glColor4uiv = NULL; + glColor4us = NULL; + glColor4usv = NULL; + glColorMask = NULL; + glColorMaterial = NULL; + glColorPointer = NULL; + glCopyPixels = NULL; + glCopyTexImage1D = NULL; + glCopyTexImage2D = NULL; + glCopyTexSubImage1D = NULL; + glCopyTexSubImage2D = NULL; + glCullFace = NULL; + glDeleteLists = NULL; + glDeleteTextures = NULL; + glDepthFunc = NULL; + glDepthMask = NULL; + glDepthRange = NULL; + glDisable = NULL; + glDisableClientState = NULL; + glDrawArrays = NULL; + glDrawBuffer = NULL; + glDrawElements = NULL; + glDrawPixels = NULL; + glEdgeFlag = NULL; + glEdgeFlagPointer = NULL; + glEdgeFlagv = NULL; + glEnable = NULL; + glEnableClientState = NULL; + glEnd = NULL; + glEndList = NULL; + glEvalCoord1d = NULL; + glEvalCoord1dv = NULL; + glEvalCoord1f = NULL; + glEvalCoord1fv = NULL; + glEvalCoord2d = NULL; + glEvalCoord2dv = NULL; + glEvalCoord2f = NULL; + glEvalCoord2fv = NULL; + glEvalMesh1 = NULL; + glEvalMesh2 = NULL; + glEvalPoint1 = NULL; + glEvalPoint2 = NULL; + glFeedbackBuffer = NULL; + glFinish = NULL; + glFlush = NULL; + glFogf = NULL; + glFogfv = NULL; + glFogi = NULL; + glFogiv = NULL; + glFrontFace = NULL; + glFrustum = NULL; + glGenLists = NULL; + glGenTextures = NULL; + glGetBooleanv = NULL; + glGetClipPlane = NULL; + glGetDoublev = NULL; + glGetError = NULL; + glGetFloatv = NULL; + glGetIntegerv = NULL; + glGetLightfv = NULL; + glGetLightiv = NULL; + glGetMapdv = NULL; + glGetMapfv = NULL; + glGetMapiv = NULL; + glGetMaterialfv = NULL; + glGetMaterialiv = NULL; + glGetPixelMapfv = NULL; + glGetPixelMapuiv = NULL; + glGetPixelMapusv = NULL; + glGetPointerv = NULL; + glGetPolygonStipple = NULL; + glGetString = NULL; + glGetTexEnvfv = NULL; + glGetTexEnviv = NULL; + glGetTexGendv = NULL; + glGetTexGenfv = NULL; + glGetTexGeniv = NULL; + glGetTexImage = NULL; + glGetTexLevelParameterfv = NULL; + glGetTexLevelParameteriv = NULL; + glGetTexParameterfv = NULL; + glGetTexParameteriv = NULL; + glHint = NULL; + glIndexMask = NULL; + glIndexPointer = NULL; + glIndexd = NULL; + glIndexdv = NULL; + glIndexf = NULL; + glIndexfv = NULL; + glIndexi = NULL; + glIndexiv = NULL; + glIndexs = NULL; + glIndexsv = NULL; + glIndexub = NULL; + glIndexubv = NULL; + glInitNames = NULL; + glInterleavedArrays = NULL; + glIsEnabled = NULL; + glIsList = NULL; + glIsTexture = NULL; + glLightModelf = NULL; + glLightModelfv = NULL; + glLightModeli = NULL; + glLightModeliv = NULL; + glLightf = NULL; + glLightfv = NULL; + glLighti = NULL; + glLightiv = NULL; + glLineStipple = NULL; + glLineWidth = NULL; + glListBase = NULL; + glLoadIdentity = NULL; + glLoadMatrixd = NULL; + glLoadMatrixf = NULL; + glLoadName = NULL; + glLogicOp = NULL; + glMap1d = NULL; + glMap1f = NULL; + glMap2d = NULL; + glMap2f = NULL; + glMapGrid1d = NULL; + glMapGrid1f = NULL; + glMapGrid2d = NULL; + glMapGrid2f = NULL; + glMaterialf = NULL; + glMaterialfv = NULL; + glMateriali = NULL; + glMaterialiv = NULL; + glMatrixMode = NULL; + glMultMatrixd = NULL; + glMultMatrixf = NULL; + glNewList = NULL; + glNormal3b = NULL; + glNormal3bv = NULL; + glNormal3d = NULL; + glNormal3dv = NULL; + glNormal3f = NULL; + glNormal3fv = NULL; + glNormal3i = NULL; + glNormal3iv = NULL; + glNormal3s = NULL; + glNormal3sv = NULL; + glNormalPointer = NULL; + glOrtho = NULL; + glPassThrough = NULL; + glPixelMapfv = NULL; + glPixelMapuiv = NULL; + glPixelMapusv = NULL; + glPixelStoref = NULL; + glPixelStorei = NULL; + glPixelTransferf = NULL; + glPixelTransferi = NULL; + glPixelZoom = NULL; + glPointSize = NULL; + glPolygonMode = NULL; + glPolygonOffset = NULL; + glPolygonStipple = NULL; + glPopAttrib = NULL; + glPopClientAttrib = NULL; + glPopMatrix = NULL; + glPopName = NULL; + glPrioritizeTextures = NULL; + glPushAttrib = NULL; + glPushClientAttrib = NULL; + glPushMatrix = NULL; + glPushName = NULL; + glRasterPos2d = NULL; + glRasterPos2dv = NULL; + glRasterPos2f = NULL; + glRasterPos2fv = NULL; + glRasterPos2i = NULL; + glRasterPos2iv = NULL; + glRasterPos2s = NULL; + glRasterPos2sv = NULL; + glRasterPos3d = NULL; + glRasterPos3dv = NULL; + glRasterPos3f = NULL; + glRasterPos3fv = NULL; + glRasterPos3i = NULL; + glRasterPos3iv = NULL; + glRasterPos3s = NULL; + glRasterPos3sv = NULL; + glRasterPos4d = NULL; + glRasterPos4dv = NULL; + glRasterPos4f = NULL; + glRasterPos4fv = NULL; + glRasterPos4i = NULL; + glRasterPos4iv = NULL; + glRasterPos4s = NULL; + glRasterPos4sv = NULL; + glReadBuffer = NULL; + glReadPixels = NULL; + glRectd = NULL; + glRectdv = NULL; + glRectf = NULL; + glRectfv = NULL; + glRecti = NULL; + glRectiv = NULL; + glRects = NULL; + glRectsv = NULL; + glRenderMode = NULL; + glRotated = NULL; + glRotatef = NULL; + glScaled = NULL; + glScalef = NULL; + glScissor = NULL; + glSelectBuffer = NULL; + glShadeModel = NULL; + glStencilFunc = NULL; + glStencilMask = NULL; + glStencilOp = NULL; + glTexCoord1d = NULL; + glTexCoord1dv = NULL; + glTexCoord1f = NULL; + glTexCoord1fv = NULL; + glTexCoord1i = NULL; + glTexCoord1iv = NULL; + glTexCoord1s = NULL; + glTexCoord1sv = NULL; + glTexCoord2d = NULL; + glTexCoord2dv = NULL; + glTexCoord2f = NULL; + glTexCoord2fv = NULL; + glTexCoord2i = NULL; + glTexCoord2iv = NULL; + glTexCoord2s = NULL; + glTexCoord2sv = NULL; + glTexCoord3d = NULL; + glTexCoord3dv = NULL; + glTexCoord3f = NULL; + glTexCoord3fv = NULL; + glTexCoord3i = NULL; + glTexCoord3iv = NULL; + glTexCoord3s = NULL; + glTexCoord3sv = NULL; + glTexCoord4d = NULL; + glTexCoord4dv = NULL; + glTexCoord4f = NULL; + glTexCoord4fv = NULL; + glTexCoord4i = NULL; + glTexCoord4iv = NULL; + glTexCoord4s = NULL; + glTexCoord4sv = NULL; + glTexCoordPointer = NULL; + glTexEnvf = NULL; + glTexEnvfv = NULL; + glTexEnvi = NULL; + glTexEnviv = NULL; + glTexGend = NULL; + glTexGendv = NULL; + glTexGenf = NULL; + glTexGenfv = NULL; + glTexGeni = NULL; + glTexGeniv = NULL; + glTexImage1D = NULL; + glTexImage2D = NULL; + glTexParameterf = NULL; + glTexParameterfv = NULL; + glTexParameteri = NULL; + glTexParameteriv = NULL; + glTexSubImage1D = NULL; + glTexSubImage2D = NULL; + glTranslated = NULL; + glTranslatef = NULL; + glVertex2d = NULL; + glVertex2dv = NULL; + glVertex2f = NULL; + glVertex2fv = NULL; + glVertex2i = NULL; + glVertex2iv = NULL; + glVertex2s = NULL; + glVertex2sv = NULL; + glVertex3d = NULL; + glVertex3dv = NULL; + glVertex3f = NULL; + glVertex3fv = NULL; + glVertex3i = NULL; + glVertex3iv = NULL; + glVertex3s = NULL; + glVertex3sv = NULL; + glVertex4d = NULL; + glVertex4dv = NULL; + glVertex4f = NULL; + glVertex4fv = NULL; + glVertex4i = NULL; + glVertex4iv = NULL; + glVertex4s = NULL; + glVertex4sv = NULL; + glVertexPointer = NULL; + glViewport = NULL; + + // EXT_compiled_vertex_array + glLockArraysEXT = NULL; + glUnlockArraysEXT = NULL; + + // ARB_multitexture + glActiveTextureARB = NULL; + glClientActiveTextureARB = NULL; + glMultiTexCoord2fARB = NULL; + glMultiTexCoord2fvARB = NULL; + + // NV_vertex_array_range + glVertexArrayRangeNV = NULL; + glFlushVertexArrayRangeNV = NULL; + wglAllocateMemoryNV = NULL; + wglFreeMemoryNV = NULL; + + // EXT_fog_coord + glFogCoordfEXT = NULL; + glFogCoordPointerEXT = NULL; + + /* ARB_texture_compression */ + glCompressedTexImage3DARB = NULL; + glCompressedTexImage2DARB = NULL; + glCompressedTexImage1DARB = NULL; + glCompressedTexSubImage3DARB = NULL; + glCompressedTexSubImage2DARB = NULL; + glCompressedTexSubImage1DARB = NULL; + glGetCompressedTexImageARB = NULL; + + qwglCopyContext = NULL; + qwglCreateContext = NULL; + qwglCreateLayerContext = NULL; + qwglDeleteContext = NULL; + qwglDescribeLayerPlane = NULL; + qwglGetCurrentContext = NULL; + qwglGetCurrentDC = NULL; + qwglGetLayerPaletteEntries = NULL; + qwglGetProcAddress = NULL; + qwglMakeCurrent = NULL; + qwglRealizeLayerPalette = NULL; + qwglSetLayerPaletteEntries = NULL; + qwglShareLists = NULL; + qwglSwapLayerBuffers = NULL; + qwglUseFontBitmaps = NULL; + qwglUseFontOutlines = NULL; + + qwglChoosePixelFormat = NULL; + qwglDescribePixelFormat = NULL; + qwglGetPixelFormat = NULL; + qwglSetPixelFormat = NULL; + qwglSwapBuffers = NULL; +} diff --git a/platformWin32/d3dgl.h b/platformWin32/d3dgl.h new file mode 100644 index 0000000..6b9b69b --- /dev/null +++ b/platformWin32/d3dgl.h @@ -0,0 +1,222 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _D3DGL_H_ +#define _D3DGL_H_ + +#define D3D_OVERLOADS + +#ifndef __D3DX_H__ +#include "d3dx.h" +#endif +#ifndef _GLLIST_H_ +#include "PlatformWin32/gllist.h" +#endif +#ifndef _PLATFORMGL_H_ +#include "PlatformWin32/platformGL.h" +#endif + +#define M_2PI (3.1415926535897932384626433 * 2.0) +#define mDegToRad(d) ((d*M_PI) / 180.0) + +/* Multitexture extensions */ +#define GL_TEXTURE0_SGIS 0x835E +#define GL_TEXTURE1_SGIS 0x835F +#define GL_TEXTURE2_SGIS 0x8360 +#define GL_TEXTURE3_SGIS 0x8361 + +#define MAXGLTEXHANDLES 4096 + +struct QuakeVertex { + D3DVALUE x, y, z; + D3DVALUE nx, ny, nz; + D3DCOLOR color; +}; + +struct QuakeTVertex { + D3DVALUE x, y, z; + D3DVALUE nx, ny, nz; + D3DCOLOR color; + D3DVALUE tu, tv; +}; + +struct QuakeMTVertex { + D3DVALUE x, y, z; + D3DVALUE nx, ny, nz; + D3DCOLOR color; + D3DVALUE tu, tv, tu2, tv2; +}; + +struct QuakeFMTVertex { + D3DVALUE x, y, z; + D3DVALUE nx, ny, nz; + D3DCOLOR diffuse; + D3DCOLOR specular; + D3DVALUE tu, tv, tu2, tv2; +}; + +struct TransformedQuakeVertex { + D3DVALUE x, y, z, rhw; + D3DCOLOR color; + D3DVALUE tu, tv, tu2, tv2; +}; + +#define QUAKEVFMT (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE) +#define QUAKETVFMT (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1) +#define QUAKEMTVFMT (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX2) +#define QUAKEFMTVFMT (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX2 | D3DFVF_SPECULAR) +#define QUAKETRVFMT (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX2) + +#define VBUFSIZE 2048 +#define MAXVERTSPERPRIM 128 + +#define RESPATH_QUAKE "Software\\Microsoft\\Quake" + +struct TexInfo { + DWORD m_block; + DWORD m_dwStage; + BOOL m_capture; + GLint m_internalformat; + D3DX_SURFACEFORMAT m_fmt; + GLsizei m_width; + GLsizei m_height; + GLsizei m_oldwidth; + GLsizei m_oldheight; + LPDIRECTDRAWSURFACE7 m_ddsurf; + D3DTEXTUREMINFILTER m_minmode; + D3DTEXTUREMAGFILTER m_magmode; + D3DTEXTUREMIPFILTER m_mipmode; + D3DTEXTUREADDRESS m_addu, m_addv; +}; + +struct Globals { + // Cache Line 1 + GLenum m_prim; + unsigned m_comp; + unsigned m_nfv[4]; + unsigned m_vcnt[4]; + + // Cache Line 2 + void *m_verts; + // Following always needs to be together + /********************************/ + D3DVALUE m_nx, m_ny, m_nz; + D3DCOLOR m_color; + D3DVALUE m_tu, m_tv, m_tu2, m_tv2; + /********************************/ + BOOL m_texturing; + BOOL m_mtex; + + // Cache Line 3 + LPDIRECT3DVERTEXBUFFER7 m_vbuf, m_tvbuf, m_mtvbuf, m_fmtvbuf; + LPDIRECT3DDEVICE7 m_d3ddev; + + DWORD m_shaders[2][9]; + int m_winWidth; + int m_winHeight; + GLint m_scix, m_sciy; + GLsizei m_sciw, m_scih; + GLint m_vwx, m_vwy; + GLsizei m_vww, m_vwh; + GLint m_lckfirst; + GLsizei m_lckcount; + HWND m_hwnd; + HDC m_hdc; + ID3DXContext *m_pD3DX; + D3DX_SURFACEFORMAT m_ddFourBitAlphaSurfFormat; + D3DX_SURFACEFORMAT m_ddEightBitAlphaSurfFormat; + D3DX_SURFACEFORMAT m_ddFiveBitSurfFormat; + D3DX_SURFACEFORMAT m_ddEightBitSurfFormat; + D3DX_SURFACEFORMAT m_ddLuminanceSurfFormat; + D3DX_SURFACEFORMAT m_ddAlphaSurfFormat; + D3DDEVICEDESC7 m_dd; + GLenum m_cullMode; + D3DTRANSFORMSTATETYPE m_matrixMode; + BOOL m_cullEnabled; + BOOL m_texHandleValid; + BOOL m_subsample; + BOOL m_usemtex; + BOOL m_usemipmap; + BOOL m_doFlip; + BOOL m_makeSquare; + BOOL m_scissoring; + BOOL m_updvwp; + BOOL m_usecolorary, m_usetexcoordary[2], m_usevertexary; + GLuint m_curstagebinding[2]; + GLenum m_curtgt, m_client_active_texture_arb; + int m_blendmode[2]; + D3DCOLOR m_clearColor; + GLclampd m_clearDepth; + const GLfloat *m_vertexary; + const void *m_colorary; + GLsizei m_numIndices; + WORD *m_wIndices; + const GLfloat *m_texcoordary[2]; + LPDIRECTDRAWSURFACE7 m_curtex[2]; + GLList m_matrixStack[3]; + WNDPROC m_wndproc; + DWORD m_lod; + TexInfo m_tex[MAXGLTEXHANDLES]; // support upto MAXGLTEXHANDLES for the time being; + GLList m_freeTextures; + GLenum m_frontFace; + BOOL m_usenormalary; + const GLfloat *m_normalary; + DWORD m_normalstride; + DWORD m_texcoordstride[2]; + DWORD m_vertexstride; + BOOL m_texgen[2]; + GLint m_texgenmode[2]; + GLfloat m_texgenplane[2][2][4]; + D3DMATRIX m_inverseworld; + BOOL m_inversedirty; + BOOL m_objectdirty[2]; + D3DCOLOR m_envcolor; + DWORD m_colorstride; + GLenum m_colortype; + D3DLIGHT7 m_lights[8]; + GLfloat *m_spherecoords; + DWORD m_zbias; + BOOL m_usedirectional; + D3DMATRIX m_curtexmatrix; + BOOL m_usefogary; + D3DCOLOR m_fogcolor; + const GLfloat *m_fogary; + DWORD m_fogstride; +}; + +#ifndef M_PI +#define M_PI 3.1415926536 +#endif + +#define CEILING( A, B ) ( (A) % (B) == 0 ? (A)/(B) : (A)/(B)+1 ) + +/* To work around optimizer bug in MSVC4.1 */ +#if defined(__WIN32__) && !defined(OPENSTEP) +void +dummy(GLuint j, GLuint k) +{ +} +#else +#define dummy(J, K) +#endif + +/* EXT_bgra */ +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 + +/* Errors */ +#define GLU_NO_ERROR 0 +#define GLU_ERROR 100103 +#define GLU_INVALID_ENUM 100900 +#define GLU_INVALID_VALUE 100901 +#define GLU_OUT_OF_MEMORY 100902 + +/* GLU 1.1 and later */ +#define GLU_VERSION 100800 +#define GLU_EXTENSIONS 100801 + +#endif diff --git a/platformWin32/gllist.h b/platformWin32/gllist.h new file mode 100644 index 0000000..37256aa --- /dev/null +++ b/platformWin32/gllist.h @@ -0,0 +1,346 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GLLIST_H_ +#define _GLLIST_H_ + +#include + +// forward class declarations + +template class GLListIter; +template class GLListManip; + +template class GLLink { + + // Private members + + Type d_data; + GLLink *d_next_p; + + // Links cannot be copied or assigned + + GLLink(const GLLink&); + GLLink& operator=(const GLLink&); + + // Public members + +public: + + // Construct & Destroy + + inline GLLink(GLLink **addLinkPtr, const Type &data); + ~GLLink() {} + + // Modifiers + + inline void setData(const Type &data); + inline void setNext(GLLink *link); + inline GLLink*& getNextRef(); // generally a bad practice + + // Accessors + + inline Type& getData(); + inline GLLink* getNext() const; + +}; + +template class GLList { + + // Private members + + int d_length; + GLLink *d_head_p; + + // Friends + + friend class GLListIter; + friend class GLListManip; + + // Public members + +public: + + // Construct & Destroy + + GLList(); + GLList(const GLList&); + ~GLList(); + + // Modifiers + + GLList& operator=(const GLList &list); + GLList& operator+=(const Type &i); + GLList& operator+=(const GLList &list); + GLList& prepend(const Type &i); + GLList& prepend(const GLList &list); + + // Accessors + + int length() const; + +}; + +template ostream& operator<<(ostream& o, const GLList& list); + +template class GLListIter { + + // Private Data + + GLLink *d_current_p; + + // Public Members + +public: + + // Construct and Destroy + + inline GLListIter(const GLList &list); + inline GLListIter(const GLListIter &iter); + ~GLListIter() {} + + // Modifiers + inline GLListIter& operator=(const GLListIter &iter); + inline void operator++(); + + // Accessors + inline operator const void* () const; + inline Type& operator()() const; + +}; + +template class GLListManip { + + // Private Data + + GLList *d_list_p; + GLLink **d_current_p; + + // Links cannot be copied or assigned + + GLListManip(const GLListManip &manip); + GLListManip& operator=(const GLListManip &manip); + + // Public Members + +public: + // Construct and Destroy + inline GLListManip(GLList *list); + ~GLListManip() {} + + // Modifiers + inline void operator++(); + inline void insert (const Type &data); + inline void remove (); + + // Accessors + inline operator const void *() const; + inline Type& operator()() const; + +}; + +template GLLink::GLLink(GLLink **addLinkPtr, const Type &data) : d_next_p(*addLinkPtr), d_data(data) +{ + *addLinkPtr = this; +} + +template void GLLink::setData(const Type &data) +{ + d_data = data; +} + +template void GLLink::setNext(GLLink *link) +{ + d_next_p = link; +} + +template GLLink*& GLLink::getNextRef() +{ + return d_next_p; +} + +template Type& GLLink::getData() +{ + return d_data; +} + +template GLLink* GLLink::getNext() const +{ + return d_next_p; +} + +template GLList::GLList() : d_head_p(0), d_length(0) +{ +} + +template GLList::GLList(const GLList& list) : d_head_p(0) +{ + GLListManip m(this); + GLListIter l(list); + + while(l) { + m.insert(l()); + ++l; + ++m; + } +} + +template GLList::~GLList() +{ + GLListManip m(this); + + while(m != 0) + m.remove(); +} + +template GLList& GLList::operator=(const GLList& list) +{ + GLListManip m(this); + GLListIter l(list); + + if(this != &list) { + while(m) + m.remove(); + while(l) { + m.insert(l()); + ++l; + ++m; + } + } + return *this; +} + +template GLList& GLList::operator+=(const Type &i) +{ + GLListManip m(this); + + while(m) + ++m; + m.insert(i); + return *this; +} + +template GLList& GLList::operator+=(const GLList& list) +{ + unsigned i, s; + GLListIter l(list); + GLListManip m(this); + + while(m) + ++m; + s = list.d_length; + for(i = 0; i < s; ++i) { + m.insert(l()); + ++m; + ++l; + } + return *this; +} + +template GLList& GLList::prepend(const Type &i) +{ + GLListManip m(this); + + m.insert(i); + return *this; +} + +template GLList& GLList::prepend(const GLList &list) +{ + GLListIter l(list); + GLListManip m(this); + + while(l) { + m.insert(l()); + ++m; + ++l; + } + return *this; +} + +template int GLList::length() const +{ + return d_length; +} + +template ostream& operator<<(ostream &o, const GLList& list) +{ + GLListIter l(list); + + o << "[ "; + while(l != 0) { + o << l(); + o << " "; + ++l; + } + return o << "]"; +} + +template GLListIter::GLListIter(const GLList &list) : d_current_p(list.d_head_p) +{ +} + +template GLListIter::GLListIter(const GLListIter &iter) : d_current_p(iter.d_current_p) +{ +} + +template GLListIter& GLListIter::operator=(const GLListIter &iter) +{ + d_current_p = iter.d_current_p; + return *this; +} + +template void GLListIter::operator++() +{ + d_current_p = d_current_p -> getNext(); +} + +template Type& GLListIter::operator()() const +{ + return d_current_p -> getData(); +} + +template GLListIter::operator const void* () const +{ + return d_current_p; +} + +template GLListManip::GLListManip(GLList *list) : d_current_p(&(list -> d_head_p)), d_list_p(list) +{ +} + +template void GLListManip::operator++() +{ + d_current_p = &((*d_current_p) -> getNextRef()); +} + +template void GLListManip::insert(const Type &data) +{ + new GLLink(d_current_p, data); + ++(d_list_p -> d_length); +} + +template void GLListManip::remove() +{ + GLLink *t = *d_current_p; + + *d_current_p = (*d_current_p) -> getNext(); + delete t; + --(d_list_p -> d_length); +} + +template GLListManip::operator const void* () const +{ + return *d_current_p; +} + +template Type& GLListManip::operator()() const +{ + return (*d_current_p) -> getData(); +} + +#endif diff --git a/platformWin32/platformAL.h b/platformWin32/platformAL.h new file mode 100644 index 0000000..2b4d2a6 --- /dev/null +++ b/platformWin32/platformAL.h @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 +//#include "OpenAL/win32/openALFn.h" +#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/platformWin32/platformGL.h b/platformWin32/platformGL.h new file mode 100644 index 0000000..39fb811 --- /dev/null +++ b/platformWin32/platformGL.h @@ -0,0 +1,1809 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/*++ BUILD Version: 0004 // Increment this if a change has global effects + +Copyright (c) 1985-96, Microsoft Corporation + +Module Name: + + gl.h + +Abstract: + + Procedure declarations, constant definitions and macros for the OpenGL + component. + +--*/ + +#ifndef _PLATFORMGL_H_ +#define _PLATFORMGL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright 1996 Silicon Graphics, Inc. +** All Rights Reserved. +** +** This is UNPUBLISHED PROPRIETARY SOURCE CODE of Silicon Graphics, Inc.; +** the contents of this file may not be disclosed to third parties, copied or +** duplicated in any form, in whole or in part, without the prior written +** permission of Silicon Graphics, Inc. +** +** RESTRICTED RIGHTS LEGEND: +** Use, duplication or disclosure by the Government is subject to restrictions +** as set forth in subdivision (c)(1)(ii) of the Rights in Technical Data +** and Computer Software clause at DFARS 252.227-7013, and/or in similar or +** successor clauses in the FAR, DOD or NASA FAR Supplement. Unpublished - +** rights reserved under the Copyright Laws of the United States. +*/ + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef int GLsizei; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void GLvoid; + +/*************************************************************/ + +/* Version */ +#define GL_VERSION_1_1 1 + +/* AccumOp */ +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 + +/* AlphaFunction */ +#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 + +/* AttribMask */ +#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 + +/* BeginMode */ +#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 + +/* BlendingFactorDest */ +#define GL_ZERO 0 +#define GL_ONE 1 +#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 + +/* BlendingFactorSrc */ +/* GL_ZERO */ +/* GL_ONE */ +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +/* GL_SRC_ALPHA */ +/* GL_ONE_MINUS_SRC_ALPHA */ +/* GL_DST_ALPHA */ +/* GL_ONE_MINUS_DST_ALPHA */ + +/* Boolean */ +#define GL_TRUE 1 +#define GL_FALSE 0 + +/* ClearBufferMask */ +/* GL_COLOR_BUFFER_BIT */ +/* GL_ACCUM_BUFFER_BIT */ +/* GL_STENCIL_BUFFER_BIT */ +/* GL_DEPTH_BUFFER_BIT */ + +/* ClientArrayType */ +/* GL_VERTEX_ARRAY */ +/* GL_NORMAL_ARRAY */ +/* GL_COLOR_ARRAY */ +/* GL_INDEX_ARRAY */ +/* GL_TEXTURE_COORD_ARRAY */ +/* GL_EDGE_FLAG_ARRAY */ + +/* ClipPlaneName */ +#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 + +/* ColorMaterialFace */ +/* GL_FRONT */ +/* GL_BACK */ +/* GL_FRONT_AND_BACK */ + +/* ColorMaterialParameter */ +/* GL_AMBIENT */ +/* GL_DIFFUSE */ +/* GL_SPECULAR */ +/* GL_EMISSION */ +/* GL_AMBIENT_AND_DIFFUSE */ + +/* ColorPointerType */ +/* GL_BYTE */ +/* GL_UNSIGNED_BYTE */ +/* GL_SHORT */ +/* GL_UNSIGNED_SHORT */ +/* GL_INT */ +/* GL_UNSIGNED_INT */ +/* GL_FLOAT */ +/* GL_DOUBLE */ + +/* CullFaceMode */ +/* GL_FRONT */ +/* GL_BACK */ +/* GL_FRONT_AND_BACK */ + +/* DataType */ +#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_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 +#define GL_DOUBLE 0x140A + +/* DepthFunction */ +/* GL_NEVER */ +/* GL_LESS */ +/* GL_EQUAL */ +/* GL_LEQUAL */ +/* GL_GREATER */ +/* GL_NOTEQUAL */ +/* GL_GEQUAL */ +/* GL_ALWAYS */ + +/* DrawBufferMode */ +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C + +/* Enable */ +/* GL_FOG */ +/* GL_LIGHTING */ +/* GL_TEXTURE_1D */ +/* GL_TEXTURE_2D */ +/* GL_LINE_STIPPLE */ +/* GL_POLYGON_STIPPLE */ +/* GL_CULL_FACE */ +/* GL_ALPHA_TEST */ +/* GL_BLEND */ +/* GL_INDEX_LOGIC_OP */ +/* GL_COLOR_LOGIC_OP */ +/* GL_DITHER */ +/* GL_STENCIL_TEST */ +/* GL_DEPTH_TEST */ +/* GL_CLIP_PLANE0 */ +/* GL_CLIP_PLANE1 */ +/* GL_CLIP_PLANE2 */ +/* GL_CLIP_PLANE3 */ +/* GL_CLIP_PLANE4 */ +/* GL_CLIP_PLANE5 */ +/* GL_LIGHT0 */ +/* GL_LIGHT1 */ +/* GL_LIGHT2 */ +/* GL_LIGHT3 */ +/* GL_LIGHT4 */ +/* GL_LIGHT5 */ +/* GL_LIGHT6 */ +/* GL_LIGHT7 */ +/* GL_TEXTURE_GEN_S */ +/* GL_TEXTURE_GEN_T */ +/* GL_TEXTURE_GEN_R */ +/* GL_TEXTURE_GEN_Q */ +/* GL_MAP1_VERTEX_3 */ +/* GL_MAP1_VERTEX_4 */ +/* GL_MAP1_COLOR_4 */ +/* GL_MAP1_INDEX */ +/* GL_MAP1_NORMAL */ +/* GL_MAP1_TEXTURE_COORD_1 */ +/* GL_MAP1_TEXTURE_COORD_2 */ +/* GL_MAP1_TEXTURE_COORD_3 */ +/* GL_MAP1_TEXTURE_COORD_4 */ +/* GL_MAP2_VERTEX_3 */ +/* GL_MAP2_VERTEX_4 */ +/* GL_MAP2_COLOR_4 */ +/* GL_MAP2_INDEX */ +/* GL_MAP2_NORMAL */ +/* GL_MAP2_TEXTURE_COORD_1 */ +/* GL_MAP2_TEXTURE_COORD_2 */ +/* GL_MAP2_TEXTURE_COORD_3 */ +/* GL_MAP2_TEXTURE_COORD_4 */ +/* GL_POINT_SMOOTH */ +/* GL_LINE_SMOOTH */ +/* GL_POLYGON_SMOOTH */ +/* GL_SCISSOR_TEST */ +/* GL_COLOR_MATERIAL */ +/* GL_NORMALIZE */ +/* GL_AUTO_NORMAL */ +/* GL_VERTEX_ARRAY */ +/* GL_NORMAL_ARRAY */ +/* GL_COLOR_ARRAY */ +/* GL_INDEX_ARRAY */ +/* GL_TEXTURE_COORD_ARRAY */ +/* GL_EDGE_FLAG_ARRAY */ +/* GL_POLYGON_OFFSET_POINT */ +/* GL_POLYGON_OFFSET_LINE */ +/* GL_POLYGON_OFFSET_FILL */ + +/* ErrorCode */ +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +/* FeedBackMode */ +#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 + +/* FeedBackToken */ +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_LINE_RESET_TOKEN 0x0707 + +/* FogMode */ +/* GL_LINEAR */ +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 + +/* FogParameter */ +/* GL_FOG_COLOR */ +/* GL_FOG_DENSITY */ +/* GL_FOG_END */ +/* GL_FOG_INDEX */ +/* GL_FOG_MODE */ +/* GL_FOG_START */ + +/* FrontFaceDirection */ +#define GL_CW 0x0900 +#define GL_CCW 0x0901 + +/* GetMapTarget */ +#define GL_COEFF 0x0A00 +#define GL_ORDER 0x0A01 +#define GL_DOMAIN 0x0A02 + +/* GetPixelMap */ +/* GL_PIXEL_MAP_I_TO_I */ +/* GL_PIXEL_MAP_S_TO_S */ +/* GL_PIXEL_MAP_I_TO_R */ +/* GL_PIXEL_MAP_I_TO_G */ +/* GL_PIXEL_MAP_I_TO_B */ +/* GL_PIXEL_MAP_I_TO_A */ +/* GL_PIXEL_MAP_R_TO_R */ +/* GL_PIXEL_MAP_G_TO_G */ +/* GL_PIXEL_MAP_B_TO_B */ +/* GL_PIXEL_MAP_A_TO_A */ + +/* GetPointerTarget */ +/* GL_VERTEX_ARRAY_POINTER */ +/* GL_NORMAL_ARRAY_POINTER */ +/* GL_COLOR_ARRAY_POINTER */ +/* GL_INDEX_ARRAY_POINTER */ +/* GL_TEXTURE_COORD_ARRAY_POINTER */ +/* GL_EDGE_FLAG_ARRAY_POINTER */ + +/* GetTarget */ +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_POINT_SMOOTH 0x0B10 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LIST_MODE 0x0B30 +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#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_LIGHTING 0x0B50 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_SHADE_MODEL 0x0B54 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_NORMALIZE 0x0BA1 +#define GL_VIEWPORT 0x0BA2 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_INDEX_MODE 0x0C30 +#define GL_RGBA_MODE 0x0C31 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_RENDER_MODE 0x0C40 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#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_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#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_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#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_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 +#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_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_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_VIEWPORT_DIMS 0x0D3A +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_INDEX_BITS 0x0D51 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#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_NAME_STACK_DEPTH 0x0D70 +#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_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 +/* GL_TEXTURE_BINDING_1D */ +/* GL_TEXTURE_BINDING_2D */ +/* GL_VERTEX_ARRAY */ +/* GL_NORMAL_ARRAY */ +/* GL_COLOR_ARRAY */ +/* GL_INDEX_ARRAY */ +/* GL_TEXTURE_COORD_ARRAY */ +/* GL_EDGE_FLAG_ARRAY */ +/* GL_VERTEX_ARRAY_SIZE */ +/* GL_VERTEX_ARRAY_TYPE */ +/* GL_VERTEX_ARRAY_STRIDE */ +/* GL_NORMAL_ARRAY_TYPE */ +/* GL_NORMAL_ARRAY_STRIDE */ +/* GL_COLOR_ARRAY_SIZE */ +/* GL_COLOR_ARRAY_TYPE */ +/* GL_COLOR_ARRAY_STRIDE */ +/* GL_INDEX_ARRAY_TYPE */ +/* GL_INDEX_ARRAY_STRIDE */ +/* GL_TEXTURE_COORD_ARRAY_SIZE */ +/* GL_TEXTURE_COORD_ARRAY_TYPE */ +/* GL_TEXTURE_COORD_ARRAY_STRIDE */ +/* GL_EDGE_FLAG_ARRAY_STRIDE */ +/* GL_POLYGON_OFFSET_FACTOR */ +/* GL_POLYGON_OFFSET_UNITS */ + +/* GetTextureParameter */ +/* GL_TEXTURE_MAG_FILTER */ +/* GL_TEXTURE_MIN_FILTER */ +/* GL_TEXTURE_WRAP_S */ +/* GL_TEXTURE_WRAP_T */ +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_BORDER 0x1005 +/* GL_TEXTURE_RED_SIZE */ +/* GL_TEXTURE_GREEN_SIZE */ +/* GL_TEXTURE_BLUE_SIZE */ +/* GL_TEXTURE_ALPHA_SIZE */ +/* GL_TEXTURE_LUMINANCE_SIZE */ +/* GL_TEXTURE_INTENSITY_SIZE */ +/* GL_TEXTURE_PRIORITY */ +/* GL_TEXTURE_RESIDENT */ + +/* HintMode */ +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +/* HintTarget */ +/* GL_PERSPECTIVE_CORRECTION_HINT */ +/* GL_POINT_SMOOTH_HINT */ +/* GL_LINE_SMOOTH_HINT */ +/* GL_POLYGON_SMOOTH_HINT */ +/* GL_FOG_HINT */ + +/* IndexPointerType */ +/* GL_SHORT */ +/* GL_INT */ +/* GL_FLOAT */ +/* GL_DOUBLE */ + +/* LightModelParameter */ +/* GL_LIGHT_MODEL_AMBIENT */ +/* GL_LIGHT_MODEL_LOCAL_VIEWER */ +/* GL_LIGHT_MODEL_TWO_SIDE */ + +/* LightName */ +#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 + +/* LightParameter */ +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#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 + +/* InterleavedArrays */ +/* GL_V2F */ +/* GL_V3F */ +/* GL_C4UB_V2F */ +/* GL_C4UB_V3F */ +/* GL_C3F_V3F */ +/* GL_N3F_V3F */ +/* GL_C4F_N3F_V3F */ +/* GL_T2F_V3F */ +/* GL_T4F_V4F */ +/* GL_T2F_C4UB_V3F */ +/* GL_T2F_C3F_V3F */ +/* GL_T2F_N3F_V3F */ +/* GL_T2F_C4F_N3F_V3F */ +/* GL_T4F_C4F_N3F_V4F */ + +/* ListMode */ +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 + +/* ListNameType */ +/* GL_BYTE */ +/* GL_UNSIGNED_BYTE */ +/* GL_SHORT */ +/* GL_UNSIGNED_SHORT */ +/* GL_INT */ +/* GL_UNSIGNED_INT */ +/* GL_FLOAT */ +/* GL_2_BYTES */ +/* GL_3_BYTES */ +/* GL_4_BYTES */ + +/* LogicOp */ +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F + +/* MapTarget */ +/* GL_MAP1_COLOR_4 */ +/* GL_MAP1_INDEX */ +/* GL_MAP1_NORMAL */ +/* GL_MAP1_TEXTURE_COORD_1 */ +/* GL_MAP1_TEXTURE_COORD_2 */ +/* GL_MAP1_TEXTURE_COORD_3 */ +/* GL_MAP1_TEXTURE_COORD_4 */ +/* GL_MAP1_VERTEX_3 */ +/* GL_MAP1_VERTEX_4 */ +/* GL_MAP2_COLOR_4 */ +/* GL_MAP2_INDEX */ +/* GL_MAP2_NORMAL */ +/* GL_MAP2_TEXTURE_COORD_1 */ +/* GL_MAP2_TEXTURE_COORD_2 */ +/* GL_MAP2_TEXTURE_COORD_3 */ +/* GL_MAP2_TEXTURE_COORD_4 */ +/* GL_MAP2_VERTEX_3 */ +/* GL_MAP2_VERTEX_4 */ + +/* MaterialFace */ +/* GL_FRONT */ +/* GL_BACK */ +/* GL_FRONT_AND_BACK */ + +/* MaterialParameter */ +#define GL_EMISSION 0x1600 +#define GL_SHININESS 0x1601 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 +/* GL_AMBIENT */ +/* GL_DIFFUSE */ +/* GL_SPECULAR */ + +/* MatrixMode */ +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 + +/* MeshMode1 */ +/* GL_POINT */ +/* GL_LINE */ + +/* MeshMode2 */ +/* GL_POINT */ +/* GL_LINE */ +/* GL_FILL */ + +/* NormalPointerType */ +/* GL_BYTE */ +/* GL_SHORT */ +/* GL_INT */ +/* GL_FLOAT */ +/* GL_DOUBLE */ + +/* PixelCopyType */ +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 + +/* PixelFormat */ +#define GL_COLOR_INDEX 0x1900 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A + +/* PixelMap */ +/* GL_PIXEL_MAP_I_TO_I */ +/* GL_PIXEL_MAP_S_TO_S */ +/* GL_PIXEL_MAP_I_TO_R */ +/* GL_PIXEL_MAP_I_TO_G */ +/* GL_PIXEL_MAP_I_TO_B */ +/* GL_PIXEL_MAP_I_TO_A */ +/* GL_PIXEL_MAP_R_TO_R */ +/* GL_PIXEL_MAP_G_TO_G */ +/* GL_PIXEL_MAP_B_TO_B */ +/* GL_PIXEL_MAP_A_TO_A */ + +/* PixelStore */ +/* GL_UNPACK_SWAP_BYTES */ +/* GL_UNPACK_LSB_FIRST */ +/* GL_UNPACK_ROW_LENGTH */ +/* GL_UNPACK_SKIP_ROWS */ +/* GL_UNPACK_SKIP_PIXELS */ +/* GL_UNPACK_ALIGNMENT */ +/* GL_PACK_SWAP_BYTES */ +/* GL_PACK_LSB_FIRST */ +/* GL_PACK_ROW_LENGTH */ +/* GL_PACK_SKIP_ROWS */ +/* GL_PACK_SKIP_PIXELS */ +/* GL_PACK_ALIGNMENT */ + +/* PixelTransfer */ +/* GL_MAP_COLOR */ +/* GL_MAP_STENCIL */ +/* GL_INDEX_SHIFT */ +/* GL_INDEX_OFFSET */ +/* GL_RED_SCALE */ +/* GL_RED_BIAS */ +/* GL_GREEN_SCALE */ +/* GL_GREEN_BIAS */ +/* GL_BLUE_SCALE */ +/* GL_BLUE_BIAS */ +/* GL_ALPHA_SCALE */ +/* GL_ALPHA_BIAS */ +/* GL_DEPTH_SCALE */ +/* GL_DEPTH_BIAS */ + +/* PixelType */ +#define GL_BITMAP 0x1A00 +/* GL_BYTE */ +/* GL_UNSIGNED_BYTE */ +/* GL_SHORT */ +/* GL_UNSIGNED_SHORT */ +/* GL_INT */ +/* GL_UNSIGNED_INT */ +/* GL_FLOAT */ + +/* PolygonMode */ +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 + +/* ReadBufferMode */ +/* GL_FRONT_LEFT */ +/* GL_FRONT_RIGHT */ +/* GL_BACK_LEFT */ +/* GL_BACK_RIGHT */ +/* GL_FRONT */ +/* GL_BACK */ +/* GL_LEFT */ +/* GL_RIGHT */ +/* GL_AUX0 */ +/* GL_AUX1 */ +/* GL_AUX2 */ +/* GL_AUX3 */ + +/* RenderingMode */ +#define GL_RENDER 0x1C00 +#define GL_FEEDBACK 0x1C01 +#define GL_SELECT 0x1C02 + +/* ShadingModel */ +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 + +/* StencilFunction */ +/* GL_NEVER */ +/* GL_LESS */ +/* GL_EQUAL */ +/* GL_LEQUAL */ +/* GL_GREATER */ +/* GL_NOTEQUAL */ +/* GL_GEQUAL */ +/* GL_ALWAYS */ + +/* StencilOp */ +/* GL_ZERO */ +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +/* GL_INVERT */ + +/* StringName */ +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +/* TextureCoordName */ +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 + +/* TexCoordPointerType */ +/* GL_SHORT */ +/* GL_INT */ +/* GL_FLOAT */ +/* GL_DOUBLE */ + +/* TextureEnvMode */ +#define GL_MODULATE 0x2100 +#define GL_DECAL 0x2101 +/* GL_BLEND */ +/* GL_REPLACE */ + +/* TextureEnvParameter */ +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 + +/* TextureEnvTarget */ +#define GL_TEXTURE_ENV 0x2300 + +/* TextureGenMode */ +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 + +/* TextureGenParameter */ +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 + +/* TextureMagFilter */ +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 + +/* TextureMinFilter */ +/* GL_NEAREST */ +/* GL_LINEAR */ +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +/* TextureParameterName */ +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +/* GL_TEXTURE_BORDER_COLOR */ +/* GL_TEXTURE_PRIORITY */ + +/* TextureTarget */ +/* GL_TEXTURE_1D */ +/* GL_TEXTURE_2D */ +/* GL_PROXY_TEXTURE_1D */ +/* GL_PROXY_TEXTURE_2D */ + +/* TextureWrapMode */ +#define GL_CLAMP 0x2900 +#define GL_REPEAT 0x2901 + +/* VertexPointerType */ +/* GL_SHORT */ +/* GL_INT */ +/* GL_FLOAT */ +/* GL_DOUBLE */ + +/* ClientAttribMask */ +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_CLIENT_ALL_ATTRIB_BITS 0xffffffff + +/* polygon_offset */ +#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 + +/* texture */ +#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 +#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_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 + +/* texture_object */ +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 + +/* vertex_array */ +#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 + +/* Extensions */ +#define GL_EXT_vertex_array 1 +#define GL_WIN_swap_hint 1 +#define GL_EXT_bgra 1 +#define GL_EXT_paletted_texture 1 +#define GL_EXT_combine 1 + +/* EXT_vertex_array */ +#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 GL_DOUBLE_EXT GL_DOUBLE + +/* EXT_bgra */ +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 + +/* EXT_paletted_texture */ + +/* These must match the GL_COLOR_TABLE_*_SGI enumerants */ +#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 + +/* EXT_combine */ +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#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 +#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 + +/* ARB_multitexture */ +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#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 + +/* 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_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 + +/* 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 + +/* EXT_texture_edge_clamp */ +#define GL_CLAMP_TO_EDGE_EXT 0x812F + +/* 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 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 + +/* 3DFX_texture_compression_FXT1 */ +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 + +/* 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 + +/* (ARB|EXT)_texture_filter_anisotropic */ +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84fe +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84ff + +/* EXT_vertex_buffer */ +#define GL_TRIBESMTVFMT_EXT 0x8702 +#define GL_TRIBESMTNVFMT_EXT 0x8703 +#define GL_TRIBESFTVFMT_EXT 0x8704 +#define GL_TRIBESFMTVFMT_EXT 0x8705 + +/* For compatibility with OpenGL v1.0 */ +#define GL_LOGIC_OP GL_INDEX_LOGIC_OP +#define GL_TEXTURE_COMPONENTS GL_TEXTURE_INTERNAL_FORMAT + +/*************************************************************/ +#define GLAPIENTRY __stdcall + +extern "C" { +extern void ( GLAPIENTRY * glAccum )(GLenum op, GLfloat value); +extern void ( GLAPIENTRY * glAlphaFunc )(GLenum func, GLclampf ref); +extern GLboolean ( GLAPIENTRY * glAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +extern void ( GLAPIENTRY * glArrayElement )(GLint i); +extern void ( GLAPIENTRY * glBegin )(GLenum mode); +extern void ( GLAPIENTRY * glBindTexture )(GLenum target, GLuint texture); +extern void ( GLAPIENTRY * glBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +extern void ( GLAPIENTRY * glBlendFunc )(GLenum sfactor, GLenum dfactor); +extern void ( GLAPIENTRY * glCallList )(GLuint list); +extern void ( GLAPIENTRY * glCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +extern void ( GLAPIENTRY * glClear )(GLbitfield mask); +extern void ( GLAPIENTRY * glClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( GLAPIENTRY * glClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +extern void ( GLAPIENTRY * glClearDepth )(GLclampd depth); +extern void ( GLAPIENTRY * glClearIndex )(GLfloat c); +extern void ( GLAPIENTRY * glClearStencil )(GLint s); +extern void ( GLAPIENTRY * glClipPlane )(GLenum plane, const GLdouble *equation); +extern void ( GLAPIENTRY * glColor3b )(GLbyte red, GLbyte green, GLbyte blue); +extern void ( GLAPIENTRY * glColor3bv )(const GLbyte *v); +extern void ( GLAPIENTRY * glColor3d )(GLdouble red, GLdouble green, GLdouble blue); +extern void ( GLAPIENTRY * glColor3dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glColor3f )(GLfloat red, GLfloat green, GLfloat blue); +extern void ( GLAPIENTRY * glColor3fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glColor3i )(GLint red, GLint green, GLint blue); +extern void ( GLAPIENTRY * glColor3iv )(const GLint *v); +extern void ( GLAPIENTRY * glColor3s )(GLshort red, GLshort green, GLshort blue); +extern void ( GLAPIENTRY * glColor3sv )(const GLshort *v); +extern void ( GLAPIENTRY * glColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +extern void ( GLAPIENTRY * glColor3ubv )(const GLubyte *v); +extern void ( GLAPIENTRY * glColor3ui )(GLuint red, GLuint green, GLuint blue); +extern void ( GLAPIENTRY * glColor3uiv )(const GLuint *v); +extern void ( GLAPIENTRY * glColor3us )(GLushort red, GLushort green, GLushort blue); +extern void ( GLAPIENTRY * glColor3usv )(const GLushort *v); +extern void ( GLAPIENTRY * glColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +extern void ( GLAPIENTRY * glColor4bv )(const GLbyte *v); +extern void ( GLAPIENTRY * glColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +extern void ( GLAPIENTRY * glColor4dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( GLAPIENTRY * glColor4fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +extern void ( GLAPIENTRY * glColor4iv )(const GLint *v); +extern void ( GLAPIENTRY * glColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +extern void ( GLAPIENTRY * glColor4sv )(const GLshort *v); +extern void ( GLAPIENTRY * glColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void ( GLAPIENTRY * glColor4ubv )(const GLubyte *v); +extern void ( GLAPIENTRY * glColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +extern void ( GLAPIENTRY * glColor4uiv )(const GLuint *v); +extern void ( GLAPIENTRY * glColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +extern void ( GLAPIENTRY * glColor4usv )(const GLushort *v); +extern void ( GLAPIENTRY * glColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +extern void ( GLAPIENTRY * glColorMaterial )(GLenum face, GLenum mode); +extern void ( GLAPIENTRY * glColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( GLAPIENTRY * glCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +extern void ( GLAPIENTRY * glCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +extern void ( GLAPIENTRY * glCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +extern void ( GLAPIENTRY * glCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void ( GLAPIENTRY * glCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( GLAPIENTRY * glCullFace )(GLenum mode); +extern void ( GLAPIENTRY * glDeleteLists )(GLuint list, GLsizei range); +extern void ( GLAPIENTRY * glDeleteTextures )(GLsizei n, const GLuint *textures); +extern void ( GLAPIENTRY * glDepthFunc )(GLenum func); +extern void ( GLAPIENTRY * glDepthMask )(GLboolean flag); +extern void ( GLAPIENTRY * glDepthRange )(GLclampd zNear, GLclampd zFar); +extern void ( GLAPIENTRY * glDisable )(GLenum cap); +extern void ( GLAPIENTRY * glDisableClientState )(GLenum array); +extern void ( GLAPIENTRY * glDrawArrays )(GLenum mode, GLint first, GLsizei count); +extern void ( GLAPIENTRY * glDrawBuffer )(GLenum mode); +extern void ( GLAPIENTRY * glDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +extern void ( GLAPIENTRY * glDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( GLAPIENTRY * glEdgeFlag )(GLboolean flag); +extern void ( GLAPIENTRY * glEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +extern void ( GLAPIENTRY * glEdgeFlagv )(const GLboolean *flag); +extern void ( GLAPIENTRY * glEnable )(GLenum cap); +extern void ( GLAPIENTRY * glEnableClientState )(GLenum array); +extern void ( GLAPIENTRY * glEnd )(void); +extern void ( GLAPIENTRY * glEndList )(void); +extern void ( GLAPIENTRY * glEvalCoord1d )(GLdouble u); +extern void ( GLAPIENTRY * glEvalCoord1dv )(const GLdouble *u); +extern void ( GLAPIENTRY * glEvalCoord1f )(GLfloat u); +extern void ( GLAPIENTRY * glEvalCoord1fv )(const GLfloat *u); +extern void ( GLAPIENTRY * glEvalCoord2d )(GLdouble u, GLdouble v); +extern void ( GLAPIENTRY * glEvalCoord2dv )(const GLdouble *u); +extern void ( GLAPIENTRY * glEvalCoord2f )(GLfloat u, GLfloat v); +extern void ( GLAPIENTRY * glEvalCoord2fv )(const GLfloat *u); +extern void ( GLAPIENTRY * glEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +extern void ( GLAPIENTRY * glEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +extern void ( GLAPIENTRY * glEvalPoint1 )(GLint i); +extern void ( GLAPIENTRY * glEvalPoint2 )(GLint i, GLint j); +extern void ( GLAPIENTRY * glFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +extern void ( GLAPIENTRY * glFinish )(void); +extern void ( GLAPIENTRY * glFlush )(void); +extern void ( GLAPIENTRY * glFogf )(GLenum pname, GLfloat param); +extern void ( GLAPIENTRY * glFogfv )(GLenum pname, const GLfloat *params); +extern void ( GLAPIENTRY * glFogi )(GLenum pname, GLint param); +extern void ( GLAPIENTRY * glFogiv )(GLenum pname, const GLint *params); +extern void ( GLAPIENTRY * glFrontFace )(GLenum mode); +extern void ( GLAPIENTRY * glFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern GLuint ( GLAPIENTRY * glGenLists )(GLsizei range); +extern void ( GLAPIENTRY * glGenTextures )(GLsizei n, GLuint *textures); +extern void ( GLAPIENTRY * glGetBooleanv )(GLenum pname, GLboolean *params); +extern void ( GLAPIENTRY * glGetClipPlane )(GLenum plane, GLdouble *equation); +extern void ( GLAPIENTRY * glGetDoublev )(GLenum pname, GLdouble *params); +extern GLenum ( GLAPIENTRY * glGetError )(void); +extern void ( GLAPIENTRY * glGetFloatv )(GLenum pname, GLfloat *params); +extern void ( GLAPIENTRY * glGetIntegerv )(GLenum pname, GLint *params); +extern void ( GLAPIENTRY * glGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +extern void ( GLAPIENTRY * glGetLightiv )(GLenum light, GLenum pname, GLint *params); +extern void ( GLAPIENTRY * glGetMapdv )(GLenum target, GLenum query, GLdouble *v); +extern void ( GLAPIENTRY * glGetMapfv )(GLenum target, GLenum query, GLfloat *v); +extern void ( GLAPIENTRY * glGetMapiv )(GLenum target, GLenum query, GLint *v); +extern void ( GLAPIENTRY * glGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +extern void ( GLAPIENTRY * glGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +extern void ( GLAPIENTRY * glGetPixelMapfv )(GLenum map, GLfloat *values); +extern void ( GLAPIENTRY * glGetPixelMapuiv )(GLenum map, GLuint *values); +extern void ( GLAPIENTRY * glGetPixelMapusv )(GLenum map, GLushort *values); +extern void ( GLAPIENTRY * glGetPointerv )(GLenum pname, GLvoid* *params); +extern void ( GLAPIENTRY * glGetPolygonStipple )(GLubyte *mask); +extern const GLubyte * ( GLAPIENTRY * glGetString )(GLenum name); +extern void ( GLAPIENTRY * glGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( GLAPIENTRY * glGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +extern void ( GLAPIENTRY * glGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +extern void ( GLAPIENTRY * glGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +extern void ( GLAPIENTRY * glGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +extern void ( GLAPIENTRY * glGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +extern void ( GLAPIENTRY * glGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +extern void ( GLAPIENTRY * glGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +extern void ( GLAPIENTRY * glGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( GLAPIENTRY * glGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +extern void ( GLAPIENTRY * glHint )(GLenum target, GLenum mode); +extern void ( GLAPIENTRY * glIndexMask )(GLuint mask); +extern void ( GLAPIENTRY * glIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( GLAPIENTRY * glIndexd )(GLdouble c); +extern void ( GLAPIENTRY * glIndexdv )(const GLdouble *c); +extern void ( GLAPIENTRY * glIndexf )(GLfloat c); +extern void ( GLAPIENTRY * glIndexfv )(const GLfloat *c); +extern void ( GLAPIENTRY * glIndexi )(GLint c); +extern void ( GLAPIENTRY * glIndexiv )(const GLint *c); +extern void ( GLAPIENTRY * glIndexs )(GLshort c); +extern void ( GLAPIENTRY * glIndexsv )(const GLshort *c); +extern void ( GLAPIENTRY * glIndexub )(GLubyte c); +extern void ( GLAPIENTRY * glIndexubv )(const GLubyte *c); +extern void ( GLAPIENTRY * glInitNames )(void); +extern void ( GLAPIENTRY * glInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +extern GLboolean ( GLAPIENTRY * glIsEnabled )(GLenum cap); +extern GLboolean ( GLAPIENTRY * glIsList )(GLuint list); +extern GLboolean ( GLAPIENTRY * glIsTexture )(GLuint texture); +extern void ( GLAPIENTRY * glLightModelf )(GLenum pname, GLfloat param); +extern void ( GLAPIENTRY * glLightModelfv )(GLenum pname, const GLfloat *params); +extern void ( GLAPIENTRY * glLightModeli )(GLenum pname, GLint param); +extern void ( GLAPIENTRY * glLightModeliv )(GLenum pname, const GLint *params); +extern void ( GLAPIENTRY * glLightf )(GLenum light, GLenum pname, GLfloat param); +extern void ( GLAPIENTRY * glLightfv )(GLenum light, GLenum pname, const GLfloat *params); +extern void ( GLAPIENTRY * glLighti )(GLenum light, GLenum pname, GLint param); +extern void ( GLAPIENTRY * glLightiv )(GLenum light, GLenum pname, const GLint *params); +extern void ( GLAPIENTRY * glLineStipple )(GLint factor, GLushort pattern); +extern void ( GLAPIENTRY * glLineWidth )(GLfloat width); +extern void ( GLAPIENTRY * glListBase )(GLuint base); +extern void ( GLAPIENTRY * glLoadIdentity )(void); +extern void ( GLAPIENTRY * glLoadMatrixd )(const GLdouble *m); +extern void ( GLAPIENTRY * glLoadMatrixf )(const GLfloat *m); +extern void ( GLAPIENTRY * glLoadName )(GLuint name); +extern void ( GLAPIENTRY * glLogicOp )(GLenum opcode); +extern void ( GLAPIENTRY * glMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +extern void ( GLAPIENTRY * glMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +extern void ( GLAPIENTRY * glMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +extern void ( GLAPIENTRY * glMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +extern void ( GLAPIENTRY * glMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +extern void ( GLAPIENTRY * glMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +extern void ( GLAPIENTRY * glMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +extern void ( GLAPIENTRY * glMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +extern void ( GLAPIENTRY * glMaterialf )(GLenum face, GLenum pname, GLfloat param); +extern void ( GLAPIENTRY * glMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +extern void ( GLAPIENTRY * glMateriali )(GLenum face, GLenum pname, GLint param); +extern void ( GLAPIENTRY * glMaterialiv )(GLenum face, GLenum pname, const GLint *params); +extern void ( GLAPIENTRY * glMatrixMode )(GLenum mode); +extern void ( GLAPIENTRY * glMultMatrixd )(const GLdouble *m); +extern void ( GLAPIENTRY * glMultMatrixf )(const GLfloat *m); +extern void ( GLAPIENTRY * glNewList )(GLuint list, GLenum mode); +extern void ( GLAPIENTRY * glNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +extern void ( GLAPIENTRY * glNormal3bv )(const GLbyte *v); +extern void ( GLAPIENTRY * glNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +extern void ( GLAPIENTRY * glNormal3dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +extern void ( GLAPIENTRY * glNormal3fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glNormal3i )(GLint nx, GLint ny, GLint nz); +extern void ( GLAPIENTRY * glNormal3iv )(const GLint *v); +extern void ( GLAPIENTRY * glNormal3s )(GLshort nx, GLshort ny, GLshort nz); +extern void ( GLAPIENTRY * glNormal3sv )(const GLshort *v); +extern void ( GLAPIENTRY * glNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( GLAPIENTRY * glOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern void ( GLAPIENTRY * glPassThrough )(GLfloat token); +extern void ( GLAPIENTRY * glPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +extern void ( GLAPIENTRY * glPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +extern void ( GLAPIENTRY * glPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +extern void ( GLAPIENTRY * glPixelStoref )(GLenum pname, GLfloat param); +extern void ( GLAPIENTRY * glPixelStorei )(GLenum pname, GLint param); +extern void ( GLAPIENTRY * glPixelTransferf )(GLenum pname, GLfloat param); +extern void ( GLAPIENTRY * glPixelTransferi )(GLenum pname, GLint param); +extern void ( GLAPIENTRY * glPixelZoom )(GLfloat xfactor, GLfloat yfactor); +extern void ( GLAPIENTRY * glPointSize )(GLfloat size); +extern void ( GLAPIENTRY * glPolygonMode )(GLenum face, GLenum mode); +extern void ( GLAPIENTRY * glPolygonOffset )(GLfloat factor, GLfloat units); +extern void ( GLAPIENTRY * glPolygonStipple )(const GLubyte *mask); +extern void ( GLAPIENTRY * glPopAttrib )(void); +extern void ( GLAPIENTRY * glPopClientAttrib )(void); +extern void ( GLAPIENTRY * glPopMatrix )(void); +extern void ( GLAPIENTRY * glPopName )(void); +extern void ( GLAPIENTRY * glPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +extern void ( GLAPIENTRY * glPushAttrib )(GLbitfield mask); +extern void ( GLAPIENTRY * glPushClientAttrib )(GLbitfield mask); +extern void ( GLAPIENTRY * glPushMatrix )(void); +extern void ( GLAPIENTRY * glPushName )(GLuint name); +extern void ( GLAPIENTRY * glRasterPos2d )(GLdouble x, GLdouble y); +extern void ( GLAPIENTRY * glRasterPos2dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glRasterPos2f )(GLfloat x, GLfloat y); +extern void ( GLAPIENTRY * glRasterPos2fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glRasterPos2i )(GLint x, GLint y); +extern void ( GLAPIENTRY * glRasterPos2iv )(const GLint *v); +extern void ( GLAPIENTRY * glRasterPos2s )(GLshort x, GLshort y); +extern void ( GLAPIENTRY * glRasterPos2sv )(const GLshort *v); +extern void ( GLAPIENTRY * glRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( GLAPIENTRY * glRasterPos3dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( GLAPIENTRY * glRasterPos3fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glRasterPos3i )(GLint x, GLint y, GLint z); +extern void ( GLAPIENTRY * glRasterPos3iv )(const GLint *v); +extern void ( GLAPIENTRY * glRasterPos3s )(GLshort x, GLshort y, GLshort z); +extern void ( GLAPIENTRY * glRasterPos3sv )(const GLshort *v); +extern void ( GLAPIENTRY * glRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( GLAPIENTRY * glRasterPos4dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( GLAPIENTRY * glRasterPos4fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( GLAPIENTRY * glRasterPos4iv )(const GLint *v); +extern void ( GLAPIENTRY * glRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( GLAPIENTRY * glRasterPos4sv )(const GLshort *v); +extern void ( GLAPIENTRY * glReadBuffer )(GLenum mode); +extern void ( GLAPIENTRY * glReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +extern void ( GLAPIENTRY * glRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +extern void ( GLAPIENTRY * glRectdv )(const GLdouble *v1, const GLdouble *v2); +extern void ( GLAPIENTRY * glRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +extern void ( GLAPIENTRY * glRectfv )(const GLfloat *v1, const GLfloat *v2); +extern void ( GLAPIENTRY * glRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +extern void ( GLAPIENTRY * glRectiv )(const GLint *v1, const GLint *v2); +extern void ( GLAPIENTRY * glRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +extern void ( GLAPIENTRY * glRectsv )(const GLshort *v1, const GLshort *v2); +extern GLint ( GLAPIENTRY * glRenderMode )(GLenum mode); +extern void ( GLAPIENTRY * glRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +extern void ( GLAPIENTRY * glRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +extern void ( GLAPIENTRY * glScaled )(GLdouble x, GLdouble y, GLdouble z); +extern void ( GLAPIENTRY * glScalef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( GLAPIENTRY * glScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( GLAPIENTRY * glSelectBuffer )(GLsizei size, GLuint *buffer); +extern void ( GLAPIENTRY * glShadeModel )(GLenum mode); +extern void ( GLAPIENTRY * glStencilFunc )(GLenum func, GLint ref, GLuint mask); +extern void ( GLAPIENTRY * glStencilMask )(GLuint mask); +extern void ( GLAPIENTRY * glStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +extern void ( GLAPIENTRY * glTexCoord1d )(GLdouble s); +extern void ( GLAPIENTRY * glTexCoord1dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glTexCoord1f )(GLfloat s); +extern void ( GLAPIENTRY * glTexCoord1fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glTexCoord1i )(GLint s); +extern void ( GLAPIENTRY * glTexCoord1iv )(const GLint *v); +extern void ( GLAPIENTRY * glTexCoord1s )(GLshort s); +extern void ( GLAPIENTRY * glTexCoord1sv )(const GLshort *v); +extern void ( GLAPIENTRY * glTexCoord2d )(GLdouble s, GLdouble t); +extern void ( GLAPIENTRY * glTexCoord2dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glTexCoord2f )(GLfloat s, GLfloat t); +extern void ( GLAPIENTRY * glTexCoord2fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glTexCoord2i )(GLint s, GLint t); +extern void ( GLAPIENTRY * glTexCoord2iv )(const GLint *v); +extern void ( GLAPIENTRY * glTexCoord2s )(GLshort s, GLshort t); +extern void ( GLAPIENTRY * glTexCoord2sv )(const GLshort *v); +extern void ( GLAPIENTRY * glTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +extern void ( GLAPIENTRY * glTexCoord3dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +extern void ( GLAPIENTRY * glTexCoord3fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glTexCoord3i )(GLint s, GLint t, GLint r); +extern void ( GLAPIENTRY * glTexCoord3iv )(const GLint *v); +extern void ( GLAPIENTRY * glTexCoord3s )(GLshort s, GLshort t, GLshort r); +extern void ( GLAPIENTRY * glTexCoord3sv )(const GLshort *v); +extern void ( GLAPIENTRY * glTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern void ( GLAPIENTRY * glTexCoord4dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void ( GLAPIENTRY * glTexCoord4fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +extern void ( GLAPIENTRY * glTexCoord4iv )(const GLint *v); +extern void ( GLAPIENTRY * glTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +extern void ( GLAPIENTRY * glTexCoord4sv )(const GLshort *v); +extern void ( GLAPIENTRY * glTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( GLAPIENTRY * glTexEnvf )(GLenum target, GLenum pname, GLfloat param); +extern void ( GLAPIENTRY * glTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( GLAPIENTRY * glTexEnvi )(GLenum target, GLenum pname, GLint param); +extern void ( GLAPIENTRY * glTexEnviv )(GLenum target, GLenum pname, const GLint *params); +extern void ( GLAPIENTRY * glTexGend )(GLenum coord, GLenum pname, GLdouble param); +extern void ( GLAPIENTRY * glTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +extern void ( GLAPIENTRY * glTexGenf )(GLenum coord, GLenum pname, GLfloat param); +extern void ( GLAPIENTRY * glTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +extern void ( GLAPIENTRY * glTexGeni )(GLenum coord, GLenum pname, GLint param); +extern void ( GLAPIENTRY * glTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +extern void ( GLAPIENTRY * glTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( GLAPIENTRY * glTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( GLAPIENTRY * glTexParameterf )(GLenum target, GLenum pname, GLfloat param); +extern void ( GLAPIENTRY * glTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( GLAPIENTRY * glTexParameteri )(GLenum target, GLenum pname, GLint param); +extern void ( GLAPIENTRY * glTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +extern void ( GLAPIENTRY * glTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( GLAPIENTRY * glTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( GLAPIENTRY * glTranslated )(GLdouble x, GLdouble y, GLdouble z); +extern void ( GLAPIENTRY * glTranslatef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( GLAPIENTRY * glVertex2d )(GLdouble x, GLdouble y); +extern void ( GLAPIENTRY * glVertex2dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glVertex2f )(GLfloat x, GLfloat y); +extern void ( GLAPIENTRY * glVertex2fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glVertex2i )(GLint x, GLint y); +extern void ( GLAPIENTRY * glVertex2iv )(const GLint *v); +extern void ( GLAPIENTRY * glVertex2s )(GLshort x, GLshort y); +extern void ( GLAPIENTRY * glVertex2sv )(const GLshort *v); +extern void ( GLAPIENTRY * glVertex3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( GLAPIENTRY * glVertex3dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glVertex3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( GLAPIENTRY * glVertex3fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glVertex3i )(GLint x, GLint y, GLint z); +extern void ( GLAPIENTRY * glVertex3iv )(const GLint *v); +extern void ( GLAPIENTRY * glVertex3s )(GLshort x, GLshort y, GLshort z); +extern void ( GLAPIENTRY * glVertex3sv )(const GLshort *v); +extern void ( GLAPIENTRY * glVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( GLAPIENTRY * glVertex4dv )(const GLdouble *v); +extern void ( GLAPIENTRY * glVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( GLAPIENTRY * glVertex4fv )(const GLfloat *v); +extern void ( GLAPIENTRY * glVertex4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( GLAPIENTRY * glVertex4iv )(const GLint *v); +extern void ( GLAPIENTRY * glVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( GLAPIENTRY * glVertex4sv )(const GLshort *v); +extern void ( GLAPIENTRY * glVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( GLAPIENTRY * glViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +struct GLState +{ + bool suppARBMultitexture; + bool suppPackedPixels; + bool suppTexEnvAdd; + bool suppLockedArrays; + bool suppTextureEnvCombine; + bool suppVertexArrayRange; + bool suppFogCoord; + bool suppEdgeClamp; + bool suppTextureCompression; + bool suppS3TC; + bool suppFXT1; + bool suppTexAnisotropic; + bool suppPalettedTexture; + bool suppVertexBuffer; + bool suppSwapInterval; + unsigned int triCount[4]; + unsigned int primCount[4]; + unsigned int primMode; // 0-3 + + GLfloat maxAnisotropy; + GLint maxTextureUnits; +}; + +extern GLState gGLState; +#define UNSIGNED_SHORT_5_6_5 0x8363 +#define UNSIGNED_SHORT_5_6_5_REV 0x8364 + +extern bool gOpenGLDisablePT; +extern bool gOpenGLDisableCVA; +extern bool gOpenGLDisableTEC; +extern bool gOpenGLDisableARBMT; +extern bool gOpenGLDisableFC; +extern bool gOpenGLDisableTCompress; +extern bool gOpenGLNoEnvColor; +extern float gOpenGLGammaCorrection; +extern bool gOpenGLNoDrawArraysAlpha; + +inline void dglSetRenderPrimType(unsigned int type) +{ + gGLState.primMode = type; +} + +inline void dglClearPrimMetrics() +{ + for(int i = 0; i < 4; i++) + gGLState.triCount[i] = gGLState.primCount[i] = 0; +} + +inline bool dglDoesSupportPalettedTexture() +{ + return gGLState.suppPalettedTexture && (gOpenGLDisablePT == false); +} + +inline bool dglDoesSupportCompiledVertexArray() +{ + return gGLState.suppLockedArrays && (gOpenGLDisableCVA == false); +} + +inline bool dglDoesSupportTextureEnvCombine() +{ + return gGLState.suppTextureEnvCombine && (gOpenGLDisableTEC == false); +} + +inline bool dglDoesSupportARBMultitexture() +{ + return gGLState.suppARBMultitexture && (gOpenGLDisableARBMT == false); +} + +inline bool dglDoesSupportVertexArrayRange() +{ + return gGLState.suppVertexArrayRange; +} + +inline bool dglDoesSupportFogCoord() +{ + return gGLState.suppFogCoord && (gOpenGLDisableFC == false); +} + +inline bool dglDoesSupportEdgeClamp() +{ + return gGLState.suppEdgeClamp; +} + +inline bool dglDoesSupportTextureCompression() +{ + return gGLState.suppTextureCompression && (gOpenGLDisableTCompress == false); +} + +inline bool dglDoesSupportS3TC() +{ + return gGLState.suppS3TC; +} + +inline bool dglDoesSupportFXT1() +{ + return gGLState.suppFXT1; +} + +inline bool dglDoesSupportTexEnvAdd() +{ + return gGLState.suppTexEnvAdd; +} + +inline bool dglDoesSupportTexAnisotropy() +{ + return gGLState.suppTexAnisotropic; +} + +inline bool dglDoesSupportVertexBuffer() +{ + return gGLState.suppVertexBuffer; +} + +inline GLfloat dglGetMaxAnisotropy() +{ + return gGLState.maxAnisotropy; +} + +inline GLint dglGetMaxTextureUnits() +{ + if (dglDoesSupportARBMultitexture()) + return gGLState.maxTextureUnits; + else + return 1; +} + +/* EXT_paletted_texture */ +extern void ( GLAPIENTRY* glColorTableEXT)(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data); + + +/* EXT_compiled_vertex_array */ +extern void ( GLAPIENTRY* glLockArraysEXT)(GLint first, GLsizei count); +extern void ( GLAPIENTRY* glUnlockArraysEXT)(); + +/* ARB_multitexture */ +extern void ( GLAPIENTRY * glActiveTextureARB)(GLenum target); +extern void ( GLAPIENTRY * glClientActiveTextureARB)(GLenum target); +extern void ( GLAPIENTRY * glMultiTexCoord2fARB)(GLenum texture, GLfloat, GLfloat); +extern void ( GLAPIENTRY * glMultiTexCoord2fvARB)(GLenum texture, GLfloat*); + +/* NV_vertex_array_range */ +extern void (GLAPIENTRY* glVertexArrayRangeNV)(GLsizei length, void* pointer); +extern void (GLAPIENTRY* glFlushVertexArrayRangeNV)(); +extern void* (GLAPIENTRY* wglAllocateMemoryNV)(GLsizei, GLfloat, GLfloat, GLfloat); +extern void (GLAPIENTRY* wglFreeMemoryNV)(void*); + +/* EXT_fog_coord */ +extern void (GLAPIENTRY* glFogCoordfEXT)(GLfloat coord); +extern void (GLAPIENTRY* glFogCoordPointerEXT)(GLenum type, GLsizei stride, void *pointer); + +/* ARB_texture_compression */ +extern void (GLAPIENTRY* glCompressedTexImage3DARB)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void*); +extern void (GLAPIENTRY* glCompressedTexImage2DARB)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void*); +extern void (GLAPIENTRY* glCompressedTexImage1DARB)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLsizei imageSize, const void*); +extern void (GLAPIENTRY* glCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void*); +extern void (GLAPIENTRY* glCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void*); +extern void (GLAPIENTRY* glCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void*); +extern void (GLAPIENTRY* glGetCompressedTexImageARB)(GLenum target, GLint lod, void* img); + +/* EXT_vertex_buffer */ +extern GLboolean (GLAPIENTRY* glAvailableVertexBufferEXT)(); +extern GLint (GLAPIENTRY* glAllocateVertexBufferEXT)(GLsizei size, GLint format, GLboolean preserve); +extern void* (GLAPIENTRY* glLockVertexBufferEXT)(GLint handle, GLsizei size); +extern void (GLAPIENTRY* glUnlockVertexBufferEXT)(GLint handle); +extern void (GLAPIENTRY* glSetVertexBufferEXT)(GLint handle); +extern void (GLAPIENTRY* glOffsetVertexBufferEXT)(GLint handle, GLuint offset); +extern void (GLAPIENTRY* glFillVertexBufferEXT)(GLint handle, GLint first, GLsizei count); +extern void (GLAPIENTRY* glFreeVertexBufferEXT)(GLint handle); +} // extern "C" + +/* EXT_vertex_array */ +typedef void (GLAPIENTRY * PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (GLAPIENTRY * PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (GLAPIENTRY * PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (GLAPIENTRY * PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (GLAPIENTRY * PFNGLGETPOINTERVEXTPROC) (GLenum pname, GLvoid* *params); +typedef void (GLAPIENTRY * PFNGLARRAYELEMENTARRAYEXTPROC)(GLenum mode, GLsizei count, const GLvoid* pi); + +/* WIN_swap_hint */ +typedef void (GLAPIENTRY * PFNGLADDSWAPHINTRECTWINPROC) (GLint x, GLint y, GLsizei width, GLsizei height); + +/* EXT_paletted_texture */ +typedef void (GLAPIENTRY * PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *data); +typedef void (GLAPIENTRY * PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *data); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (GLAPIENTRY * PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); + + +//------------------------------------------------------------------------------ +// GLU functions + +extern const GLubyte* (GLAPIENTRY * gluErrorString) (GLenum errCode); +extern const GLubyte* (GLAPIENTRY * gluGetString) (GLenum name); +extern void (GLAPIENTRY * gluOrtho2D) (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top); +extern void (GLAPIENTRY * gluPerspective) (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar); +extern void (GLAPIENTRY * gluPickMatrix) (GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4]); +extern void (GLAPIENTRY * gluLookAt) (GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz); +extern int (GLAPIENTRY * gluProject) (GLdouble objx, GLdouble objy, GLdouble objz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *winx, GLdouble *winy, GLdouble *winz); +extern int (GLAPIENTRY * gluUnProject) (GLdouble winx, GLdouble winy, GLdouble winz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *objx, GLdouble *objy, GLdouble *objz); +extern int (GLAPIENTRY * gluScaleImage) (GLenum format, GLint widthin, GLint heightin, GLenum typein, const void *datain, GLint widthout, GLint heightout, GLenum typeout, void *dataout); +extern int (GLAPIENTRY * gluBuild1DMipmaps) (GLenum target, GLint components, GLint width, GLenum format, GLenum type, const void *data); +extern int (GLAPIENTRY * gluBuild2DMipmaps) (GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data); + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/platformWin32/platformWin32.h b/platformWin32/platformWin32.h new file mode 100644 index 0000000..59049c0 --- /dev/null +++ b/platformWin32/platformWin32.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMWIN32_H_ +#define _PLATFORMWIN32_H_ + + +#if defined(__MWERKS__) +# include +# include +# include +#else +# include +# include +#endif +#define NOMINMAX + +// define this so that we can use WM_MOUSEWHEEL messages... +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0400 +#endif + +#include +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +struct Win32PlatState +{ + FILE *log_fp; + HINSTANCE hinstOpenGL; + HINSTANCE hinstGLU; + HINSTANCE hinstOpenAL; + HWND appWindow; + HDC appDC; + HINSTANCE appInstance; + HGLRC hGLRC; + DWORD processId; + + S32 desktopBitsPixel; + S32 desktopWidth; + S32 desktopHeight; + U32 currentTime; + + Win32PlatState(); +}; + +extern Win32PlatState winState; + +extern bool QGL_Init( const char *dllname_gl, const char *dllname_glu ); +extern bool QGL_EXT_Init(); +extern void QGL_Shutdown(); + +extern HWND CreateOpenGLWindow( U32 width, U32 height, bool fullScreen ); +extern HWND CreateCurtain( U32 width, U32 height ); +extern void CreatePixelFormat( PIXELFORMATDESCRIPTOR *pPFD, S32 colorBits, S32 depthBits, S32 stencilBits, bool stereo ); +extern S32 ChooseBestPixelFormat( HDC hDC, PIXELFORMATDESCRIPTOR *pPFD ); +extern void setModifierKeys( S32 modKeys ); + +extern S32 ( WINAPI * qwglSwapIntervalEXT )(S32 interval ); +extern BOOL ( WINAPI * qwglGetDeviceGammaRamp3DFX )(HDC, LPVOID ); +extern BOOL ( WINAPI * qwglSetDeviceGammaRamp3DFX )(HDC, LPVOID ); +extern S32 ( WINAPI * qwglChoosePixelFormat )(HDC, CONST PIXELFORMATDESCRIPTOR *); +extern S32 ( WINAPI * qwglDescribePixelFormat) (HDC, S32, UINT, LPPIXELFORMATDESCRIPTOR); +extern S32 ( WINAPI * qwglGetPixelFormat )(HDC); +extern BOOL ( WINAPI * qwglSetPixelFormat )(HDC, S32, CONST PIXELFORMATDESCRIPTOR *); +extern BOOL ( WINAPI * qwglSwapBuffers )(HDC); +extern BOOL ( WINAPI * qwglCopyContext )(HGLRC, HGLRC, UINT); +extern HGLRC ( WINAPI * qwglCreateContext )(HDC); +extern HGLRC ( WINAPI * qwglCreateLayerContext )(HDC, S32); +extern BOOL ( WINAPI * qwglDeleteContext )(HGLRC); +extern HGLRC ( WINAPI * qwglGetCurrentContext )(VOID); +extern HDC ( WINAPI * qwglGetCurrentDC )(VOID); +extern PROC ( WINAPI * qwglGetProcAddress )(LPCSTR); +extern BOOL ( WINAPI * qwglMakeCurrent )(HDC, HGLRC); +extern BOOL ( WINAPI * qwglShareLists )(HGLRC, HGLRC); +extern BOOL ( WINAPI * qwglUseFontBitmaps )(HDC, DWORD, DWORD, DWORD); +extern BOOL ( WINAPI * qwglUseFontOutlines )(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, S32, LPGLYPHMETRICSFLOAT); +extern BOOL ( WINAPI * qwglDescribeLayerPlane )(HDC, S32, S32, UINT, LPLAYERPLANEDESCRIPTOR); +extern S32 ( WINAPI * qwglSetLayerPaletteEntries )(HDC, S32, S32, S32, CONST COLORREF *); +extern S32 ( WINAPI * qwglGetLayerPaletteEntries )(HDC, S32, S32, S32, COLORREF *); +extern BOOL ( WINAPI * qwglRealizeLayerPalette )(HDC, S32, BOOL); +extern BOOL ( WINAPI * qwglSwapLayerBuffers )(HDC, UINT); + + +#endif //_PLATFORMWIN32_H_ diff --git a/platformWin32/win32NPatch.h b/platformWin32/win32NPatch.h new file mode 100644 index 0000000..3fde081 --- /dev/null +++ b/platformWin32/win32NPatch.h @@ -0,0 +1,37 @@ +// for the moment, this seems to be the best roundup of +// the npatch extensions on the PC. + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 + +#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 + +typedef void (APIENTRY *PFNGLPNTRIANGLESIATIPROC)(GLenum pname, GLint param); +typedef void (APIENTRY *PFNGLPNTRIANGLESFATIPROC)(GLenum pname, GLfloat param); + +#endif + +#define GL_NPATCH_EXT_STRING "GL_ATI_pn_triangles" +#define GL_NPATCH_SETINT_STRING "glPNTrianglesiATI" +typedef PFNGLPNTRIANGLESIATIPROC PFNNPatchSetInt; + +#define GETINT_NPATCH_MAX_LEVEL GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI +#define GL_NPATCH_FLAG GL_PN_TRIANGLES_ATI +#define GL_NPATCH_LOD GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI + +#define SETINT_NPATCH_POINTINTERP GL_PN_TRIANGLES_POINT_MODE_ATI +#define SETINT_NPATCH_NORMALINTERP GL_PN_TRIANGLES_NORMAL_MODE_ATI + +#define NPATCH_POINTINTERP_MIN GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI +#define NPATCH_POINTINTERP_MAX GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI + +#define NPATCH_NORMALINTERP_MIN GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI +#define NPATCH_NORMALINTERP_MAX GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI diff --git a/platformWin32/winAsmBlit.cc b/platformWin32/winAsmBlit.cc new file mode 100644 index 0000000..731eb1e --- /dev/null +++ b/platformWin32/winAsmBlit.cc @@ -0,0 +1,338 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/terrData.h" +#include "Math/mMath.h" +#include "dgl/dgl.h" +#include "dgl/gBitmap.h" +#include "terrain/terrRender.h" + +#if !defined(__MWERKS__) && defined(_MSC_VER) +#define asm _asm +#endif + +//-------------------------------------------------------------------------- +//void terrMipBlit_asm(U16 *dest, U32 destStride, U32 squareSize, const U8 *sourcePtr, U32 sourceStep, U32 sourceRowAdd) +//{ +// //for(U32 k = 0; k < squareSize; k++) +// //{ +// // for(U32 l = 0; l < squareSize; l++) +// // { +// // dest[l] = *((U16 *) sourcePtr); +// // sourcePtr += sourceStep; +// // } +// // dest -= destStride; +// // sourcePtr += sourceRowAdd; +// //} +// if(sourceStep == 2) +// { +// destStride <<= 1; +// sourceRowAdd += squareSize << 1; +// asm +// { +// push eax +// push ebx +// push ecx +// push edx +// push edi +// push esi +// +// mov edx, squareSize +// mov edi, dest +// mov esi, sourcePtr +// shr edx, 1 +// mov ecx, 0 +// mov ebx, 0 +// pixelLoop2: +// mov eax, [esi+ebx*4] +// mov [edi+ebx*4], eax +// inc ebx +// cmp ebx, edx +// jnz pixelLoop2 +// +// mov ebx, 0 +// inc ecx +// sub edi, destStride +// add esi, sourceRowAdd +// cmp ecx, squareSize +// jl pixelLoop2 +// +// pop esi +// pop edi +// pop edx +// pop ecx +// pop ebx +// pop eax +// } +// } +// else if(sourceStep == -2) +// { +// destStride <<= 1; +// asm +// { +// push eax +// push ebx +// push ecx +// push edx +// push edi +// push esi +// +// mov edx, squareSize +// mov edi, dest +// mov esi, sourcePtr +// shr edx, 1 +// mov ecx, 0 +// mov ebx, 0 +// pixelLoopNeg2: +// mov eax, [esi-2] +// sub esi, 4 +// ror eax, 16 +// mov [edi+ebx*4], eax +// inc ebx +// cmp ebx, edx +// jnz pixelLoopNeg2 +// +// mov ebx, 0 +// inc ecx +// sub edi, destStride +// add esi, sourceRowAdd +// cmp ecx, squareSize +// jl pixelLoopNeg2 +// +// pop esi +// pop edi +// pop edx +// pop ecx +// pop ebx +// pop eax +// } +// } +// else +// { +// destStride = (destStride + squareSize) << 1; +// asm +// { +// push eax +// push ebx +// push ecx +// push edx +// push edi +// push esi +// +// mov eax, squareSize +// mov edi, dest +// mov esi, sourcePtr +// lea edx, [edi + eax * 2] +// mov ecx, 0 // row index +// mov ebx, sourceStep +// pixelLoop: +// mov ax, [esi+ebx] +// shl eax, 16 +// add edi, 4 +// mov ax, [esi] +// lea esi, [esi+ebx*2] +// mov [edi-4], eax +// cmp edi, edx +// jnz pixelLoop +// +// inc ecx +// sub edi, destStride +// mov eax, squareSize +// add esi, sourceRowAdd +// lea edx, [edi + eax * 2] +// cmp ecx, squareSize +// jl pixelLoop +// +// pop esi +// pop edi +// pop edx +// pop ecx +// pop ebx +// pop eax +// } +// } +//} + + +//-------------------------------------------------------------------------- +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; + } +} + + +#ifndef __BORLANDC__ + +//-------------------------------------------------------------------------- +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) + { +#ifndef __BORLANDC__ + bitmapExtrudeRGB = bitmapExtrudeRGB_mmx; + bitmapConvertRGB_to_5551 = bitmapConvertRGB_to_5551_mmx; +#endif + } +// terrMipBlit = terrMipBlit_asm; +} diff --git a/platformWin32/winCPUInfo.cc b/platformWin32/winCPUInfo.cc new file mode 100644 index 0000000..31ebcfe --- /dev/null +++ b/platformWin32/winCPUInfo.cc @@ -0,0 +1,280 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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(); + +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; + + +#ifndef __BORLANDC__ + char vendor[13] = {0,}; + U32 properties = 0; + U32 processor = 0; + + enum + { + BIT_FPU = (1<<0), + BIT_RDTSC = (1<<4), + BIT_MMX = (1<<23), + BIT_SSE = (1<<25), + BIT_3DNOW = (1<<31), + }; + + __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, 0x0ff0 + 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 + } + + 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) + { + Platform::SystemInfo.processor.properties |= (properties & BIT_SSE) ? CPU_PROP_SSE : 0; + switch (processor & 0xf00) + { + case 0x600: + switch(processor & 0xf0) + { + case 0x10: + Platform::SystemInfo.processor.type = CPU_Intel_PentiumII; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel Pentium Pro" ); + break; + case 0x30: + case 0x50: + case 0x60: + Platform::SystemInfo.processor.type = CPU_Intel_PentiumII; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel Pentium II" ); + break; + case 0x70: + case 0x80: + case 0xa0: + Platform::SystemInfo.processor.type = CPU_Intel_PentiumIII; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel Pentium III" ); + break; + default: + Platform::SystemInfo.processor.type = CPU_Intel_PentiumII; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel (unknown, PPro family)" ); + break; + } + break; + case 0x500: + Platform::SystemInfo.processor.type = CPU_Intel_Pentium; + Platform::SystemInfo.processor.name = StringTable->insert("Intel Pentium"); + break; + default: + Platform::SystemInfo.processor.type = CPU_Intel_Unknown; + Platform::SystemInfo.processor.name = StringTable->insert("Intel (unknown)"); + break; + } + } + //-------------------------------------- + else + if (dStricmp(vendor, "AuthenticAMD") == 0) + { + Platform::SystemInfo.processor.properties |= (properties & BIT_3DNOW) ? CPU_PROP_3DNOW : 0; + switch (processor) + { + case 0x700: + case 0x710: + Platform::SystemInfo.processor.type = CPU_AMD_K7; + Platform::SystemInfo.processor.name = StringTable->insert("AMD K7"); + break; + case 0x660: + case 0x670: + Platform::SystemInfo.processor.type = CPU_AMD_K6; + Platform::SystemInfo.processor.name = StringTable->insert("AMD K6"); + break; + case 0x680: + Platform::SystemInfo.processor.type = CPU_AMD_K6_2; + Platform::SystemInfo.processor.name = StringTable->insert("AMD K6-2"); + break; + case 0x690: + Platform::SystemInfo.processor.type = CPU_AMD_K6_3; + Platform::SystemInfo.processor.name = StringTable->insert("AMD K6-3"); + break; + case 0x510: + case 0x520: + case 0x530: + Platform::SystemInfo.processor.type = CPU_AMD_K6_3; + Platform::SystemInfo.processor.name = StringTable->insert("AMD K5"); + break; + default: + Platform::SystemInfo.processor.type = CPU_AMD_Unknown; + Platform::SystemInfo.processor.name = StringTable->insert("AMD (unknown)"); + break; + } + } + //-------------------------------------- + else + if (dStricmp(vendor, "CyrixInstead") == 0) + { + switch (processor) + { + case 0x520: + Platform::SystemInfo.processor.type = CPU_Cyrix_6x86; + Platform::SystemInfo.processor.name = StringTable->insert("Cyrix 6x86"); + break; + case 0x440: + Platform::SystemInfo.processor.type = CPU_Cyrix_MediaGX; + Platform::SystemInfo.processor.name = StringTable->insert("Cyrix Media GX"); + break; + case 0x600: + Platform::SystemInfo.processor.type = CPU_Cyrix_6x86MX; + Platform::SystemInfo.processor.name = StringTable->insert("Cyrix 6x86mx/MII"); + break; + case 0x540: + Platform::SystemInfo.processor.type = CPU_Cyrix_GXm; + Platform::SystemInfo.processor.name = StringTable->insert("Cyrix GXm"); + break; + default: + Platform::SystemInfo.processor.type = CPU_Cyrix_Unknown; + Platform::SystemInfo.processor.name = StringTable->insert("Cyrix (unknown)"); + break; + } + } + + //-------------------------------------- + // 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; + U32 time[2]; + U32 ticks = 0; + __asm + { + push eax + push edx + //db 0fh, 31h + rdtsc + mov DWORD PTR time, eax + mov DWORD PTR time+4, edx + pop edx + pop eax + } + U32 ms = GetTickCount(); + while ( GetTickCount() < ms+MS_INTERVAL ) + { /* empty */ } + ms = GetTickCount()-ms; + __asm + { + push eax + push edx + //db 0fh, 31h + rdtsc + sub edx, DWORD PTR time+4 + sbb eax, DWORD PTR time + mov DWORD PTR ticks, eax + pop edx + pop eax + } + U32 mhz = F32(ticks) / 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); + } +#endif + + 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/platformWin32/winConsole.cc b/platformWin32/winConsole.cc new file mode 100644 index 0000000..3fb50cf --- /dev/null +++ b/platformWin32/winConsole.cc @@ -0,0 +1,244 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" +#include "PlatformWin32/winConsole.h" +//#include +#include "Platform/event.h" +#include "Platform/gameInterface.h" + +WinConsole *WindowsConsole = NULL; + +ConsoleFunction(enableWinConsole, void, 2, 2, "enableWinConsole(bool);") +{ + argc; + WindowsConsole->enable(dAtob(argv[1])); +} + +void WinConsole::create() +{ + WindowsConsole = new WinConsole(); +} + +void WinConsole::destroy() +{ + delete WindowsConsole; + WindowsConsole = NULL; +} + +void WinConsole::enable(bool enabled) +{ + winConsoleEnabled = enabled; + if(winConsoleEnabled) + { + AllocConsole(); + const char *title = Con::getVariable("Con::WindowTitle"); + if (title && *title) + SetConsoleTitle(title); + 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, const char *line) +{ + WindowsConsole->processConsoleLine(line); +} + +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; +} + +void WinConsole::printf(const char *s, ...) +{ + static char buffer[512]; + DWORD bytes; + va_list args; + va_start(args, s); + vsprintf(buffer, s, args); + 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[256]; + 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 '\n': + case '\r': + outbuf[outpos++] = '\r'; + outbuf[outpos++] = '\n'; + + inbuf[inpos] = 0; + outbuf[outpos] = 0; + printf("%s", outbuf); + + S32 eventSize; + eventSize = ConsoleEventHeaderSize; + + dStrcpy(postEvent.data, inbuf); + postEvent.size = eventSize + dStrlen(inbuf) + 1; + Game->postEvent(postEvent); + + // 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..1882b8a --- /dev/null +++ b/platformWin32/winConsole.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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; + ConsoleEvent postEvent; + 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(); + 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/winD3DVideo.cc b/platformWin32/winD3DVideo.cc new file mode 100644 index 0000000..7fba554 --- /dev/null +++ b/platformWin32/winD3DVideo.cc @@ -0,0 +1,720 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformWIN32/platformGL.h" +#include "platformWIN32/platformWin32.h" +#include "platform/platformAudio.h" +#include "platformWIN32/winD3DVideo.h" +#include "console/console.h" +#include "math/mPoint.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "console/consoleInternal.h" +#include "console/ast.h" + +//------------------------------------------------------------------------------ + +bool D3DDevice::smCanSwitchBitDepth = true; +bool D3DDevice::smStay16 = false; + +static void profileSystem(const char *vendor, const char *renderer) +{ + Con::setBoolVariable("$pref::Video::safeModeOn", true); + Con::setBoolVariable("$pref::OpenGL::disableEXTCompiledVertexArray", false); + Con::setBoolVariable("$pref::OpenGL::disableSubImage", false); + Con::setBoolVariable("$pref::OpenGL::noEnvColor", true); + Con::setBoolVariable("$pref::OpenGL::disableARBTextureCompression", false); + Con::setBoolVariable("$pref::Interior::lockArrays", true); + Con::setBoolVariable("$pref::TS::skipFirstFog", false); + Con::setBoolVariable("$pref::OpenGL::disableEXTFogCoord", false); + + Con::setVariable("$pref::Video::profiledVendor", vendor); + Con::setVariable("$pref::Video::profiledRenderer", renderer); + + // write out prefs + gEvalState.globalVars.exportVariables("$pref::*", "prefs/ClientPrefs.cs", false); +} + + +//------------------------------------------------------------------------------ +D3DDevice::D3DDevice() +{ + initDevice(); +} + + +//------------------------------------------------------------------------------ +void D3DDevice::initDevice() +{ + // Set the device name: + mDeviceName = "D3D"; + + // Set some initial conditions: + mResolutionList.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 >= 640 && devMode.dmPelsHeight >= 480 + && ( devMode.dmBitsPerPel == 16 || (devMode.dmBitsPerPel == 32 && !smStay16) ) && + ( smCanSwitchBitDepth || devMode.dmBitsPerPel == winState.desktopBitsPixel ) ) + { + // Only add this resolution if it is not already in the list: + bool alreadyInList = false; + for ( U32 i = 0; i < mResolutionList.size(); i++ ) + { + if ( devMode.dmPelsWidth == mResolutionList[i].w + && devMode.dmPelsHeight == mResolutionList[i].h + && devMode.dmBitsPerPel == mResolutionList[i].bpp ) + { + alreadyInList = true; + break; + } + } + + if ( !alreadyInList ) + { + Resolution newRes( devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel ); + mResolutionList.push_back( newRes ); + } + } + } +} + + +//------------------------------------------------------------------------------ +bool D3DDevice::activate( U32 width, U32 height, U32 bpp, bool fullScreen ) +{ + bool needResurrect = false; + + // If the rendering context exists, delete it: + if ( winState.hGLRC ) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + + Con::printf( "Making the rendering context not current..." ); + if ( !qwglMakeCurrent( NULL, NULL ) ) + { + AssertFatal( false, "D3DDevice::activate\nqwglMakeCurrent( NULL, NULL ) failed!" ); + return false; + } + + Con::printf( "Deleting the rendering context ..." ); + if ( !qwglDeleteContext( winState.hGLRC ) ) + { + AssertFatal( false, "D3DDevice::activate\nqwglDeleteContext failed!" ); + return false; + } + winState.hGLRC = NULL; + } + + // If the window already exists, kill it so we can start fresh: + if ( winState.appWindow ) + { + Con::printf( "Releasing the device context..." ); + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + + Con::printf( "Destroying the window..." ); + DestroyWindow( winState.appWindow ); + winState.appWindow = NULL; + } + + // If OpenGL library already loaded, shut it down and reload it: + if ( winState.hinstOpenGL ) + QGL_Shutdown(); + + QGL_Init(OPENGL2D3D,GLU2D3D); + + // Set the resolution: + if ( !setScreenMode( width, height, bpp, ( fullScreen || mFullScreenOnly ), true, false ) ) + return false; + + // Get original gamma ramp + mRestoreGamma = GetDeviceGammaRamp(winState.appDC, mOriginalRamp); + + // 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 ); + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + + QGL_EXT_Init(); + + Con::setVariable( "$pref::Video::displayDevice", mDeviceName ); + Con::setBoolVariable( "$SwapIntervalSupported", false ); + Con::setBoolVariable( "$pref::OpenGL::allowTexGen", false ); + Con::setBoolVariable( "$pref::environmentMaps", false ); + + // only do this for the first session + if (!Con::getBoolVariable("$DisableSystemProfiling") && (dStrcmp(vendorString, Con::getVariable("$pref::Video::profiledVendor")) || + dStrcmp(rendererString, Con::getVariable("$pref::Video::profiledRenderer")))) + profileSystem(vendorString, rendererString); + + return true; +} + + +//------------------------------------------------------------------------------ +void D3DDevice::shutdown() +{ + if ( winState.hGLRC ) + { + if (mRestoreGamma) + SetDeviceGammaRamp(winState.appDC, mOriginalRamp); + + qwglMakeCurrent( NULL, NULL ); + qwglDeleteContext( winState.hGLRC ); + winState.hGLRC = NULL; + } + + if ( winState.appDC ) + { + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + } + + if ( smIsFullScreen || (smStay16 && winState.desktopBitsPixel != 16)) + ChangeDisplaySettings( NULL, 0 ); + + QGL_Shutdown(); +} + + +//------------------------------------------------------------------------------ +// This is the real workhorse function of the DisplayDevice... +// +bool D3DDevice::setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen, bool forceIt, bool repaint ) +{ + HWND curtain = NULL; + char errorMessage[256]; + Resolution newRes( width, height, bpp ); + bool newFullScreen = fullScreen; + bool safeModeOn = Con::getBoolVariable( "$pref::Video::safeModeOn" ); + + if ( !newFullScreen && mFullScreenOnly ) + { + Con::warnf( ConsoleLogEntry::General, "D3DDevice::setScreenMode - device or desktop color depth does not allow windowed mode!" ); + newFullScreen = true; + } + + if ( !newFullScreen && ( newRes.w >= winState.desktopWidth || newRes.h >= winState.desktopHeight ) ) + { + Con::warnf( ConsoleLogEntry::General, "D3DDevice::setScreenMode -- can't switch to resolution larger than desktop in windowed mode!" ); + // Find the largest standard resolution that will fit on the desktop: + U32 resIndex; + for ( resIndex = mResolutionList.size() - 1; resIndex > 0; resIndex-- ) + { + if ( mResolutionList[resIndex].w < winState.desktopWidth + && mResolutionList[resIndex].h < winState.desktopHeight ) + break; + } + newRes = mResolutionList[resIndex]; + } + + if ( newRes.w < 640 || newRes.h < 480 ) + { + Con::warnf( ConsoleLogEntry::General, "D3DDevice::setScreenMode -- can't go smaller than 640x480!" ); + return false; + } + + if ( newFullScreen ) + { + if (newRes.bpp != 16 && mFullScreenOnly) + newRes.bpp = 16; + + // Match the new resolution to one in the list: + U32 resIndex = 0; + U32 bestScore = 0, thisScore = 0; + for ( int i = 0; i < mResolutionList.size(); i++ ) + { + if ( newRes == mResolutionList[i] ) + { + resIndex = i; + break; + } + else + { + thisScore = abs( S32( newRes.w ) - S32( mResolutionList[i].w ) ) + + abs( S32( newRes.h ) - S32( mResolutionList[i].h ) ) + + ( newRes.bpp == mResolutionList[i].bpp ? 0 : 1 ); + + if ( !bestScore || ( thisScore < bestScore ) ) + { + bestScore = thisScore; + resIndex = i; + } + } + } + + newRes = mResolutionList[resIndex]; + } + else + // Basically ignore the bit depth parameter: + if (!smStay16) + newRes.bpp = winState.desktopBitsPixel; + else + newRes.bpp = 16; + + // Return if already at this resolution: + if ( !forceIt && newRes == smCurrentRes && newFullScreen == smIsFullScreen ) + return true; + + Con::printf( "Setting screen mode to %dx%dx%d (%s)...", newRes.w, newRes.h, newRes.bpp, ( newFullScreen ? "fs" : "w" ) ); + + bool needResurrect = false; + + // oh just always do it + //if ( ( newRes.bpp != smCurrentRes.bpp ) || ( safeModeOn && ( ( smIsFullScreen != newFullScreen ) || newFullScreen ) ) ) + if (true) + { + // Delete the rendering context: + if ( winState.hGLRC ) + { + if (!Video::smNeedResurrect) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + } + + Con::printf( "Making the rendering context not current..." ); + if ( !qwglMakeCurrent( NULL, NULL ) ) + { + AssertFatal( false, "D3DDevice::setScreenMode\nqwglMakeCurrent( NULL, NULL ) failed!" ); + return false; + } + + Con::printf( "Deleting the rendering context..." ); + if ( !qwglDeleteContext( winState.hGLRC ) ) + { + AssertFatal( false, "D3DDevice::setScreenMode\nqwglDeleteContext failed!" ); + return false; + } + winState.hGLRC = NULL; + } + + // Release the device context: + if ( winState.appDC ) + { + Con::printf( "Releasing the device context..." ); + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + } + + // Destroy the window: + if ( winState.appWindow ) + { + Con::printf( "Destroying the window..." ); + DestroyWindow( winState.appWindow ); + winState.appWindow = NULL; + } + } + else if ( smIsFullScreen != newFullScreen ) + { + // Change the window style: + Con::printf( "Changing the window style..." ); + S32 windowStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + if ( newFullScreen ) + windowStyle |= ( WS_POPUP | WS_MAXIMIZE ); + else + windowStyle |= ( WS_OVERLAPPED | WS_CAPTION ); + + if ( !SetWindowLong( winState.appWindow, GWL_STYLE, windowStyle ) ) + Platform::AlertOK( "Error", "Failed to change the window style!" ); + } + + U32 test; + if ( newFullScreen ) + { + // Change the display settings: + DEVMODE devMode; + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmPelsWidth = newRes.w; + devMode.dmPelsHeight = newRes.h; + devMode.dmBitsPerPel = newRes.bpp; + devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; + + Con::printf( "Changing the display settings to %dx%dx%d...", newRes.w, newRes.h, newRes.bpp ); + curtain = CreateCurtain( newRes.w, newRes.h ); + test = ChangeDisplaySettings( &devMode, CDS_FULLSCREEN ); + if ( test != DISP_CHANGE_SUCCESSFUL ) + { + smIsFullScreen = false; + Con::setBoolVariable( "$pref::Video::fullScreen", false ); + ChangeDisplaySettings( NULL, 0 ); + Con::errorf( ConsoleLogEntry::General, "D3DDevice::setScreenMode - ChangeDisplaySettings failed." ); + switch( test ) + { + case DISP_CHANGE_RESTART: + Platform::AlertOK( "Display Change Failed", "You must restart your machine to get the specified mode." ); + break; + + case DISP_CHANGE_BADMODE: + Platform::AlertOK( "Display Change Failed", "The specified mode is not supported by this device." ); + break; + + default: + Platform::AlertOK( "Display Change Failed", "Hardware failed to switch to the specified mode." ); + break; + }; + DestroyWindow( curtain ); + return false; + } + else + smIsFullScreen = true; + } + else if ( smIsFullScreen || (smStay16 && winState.desktopBitsPixel != 16)) + { + if (!smStay16 || winState.desktopBitsPixel == 16) + { + Con::printf( "Changing to the desktop display settings (%dx%dx%d)...", winState.desktopWidth, winState.desktopHeight, winState.desktopBitsPixel ); + ChangeDisplaySettings( NULL, 0 ); + } + else + { + // Change the display settings: + DEVMODE devMode; + + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmBitsPerPel = newRes.bpp; + devMode.dmFields = DM_BITSPERPEL; + + Con::printf( "Changing the display settings to %dx%dx%d...", winState.desktopWidth, winState.desktopHeight, newRes.bpp ); + test = ChangeDisplaySettings( &devMode, 0 ); + if ( test != DISP_CHANGE_SUCCESSFUL ) + { + smIsFullScreen = true; + Con::setBoolVariable( "$pref::Video::fullScreen", true ); + ChangeDisplaySettings( NULL, 0 ); + Con::errorf( ConsoleLogEntry::General, "D3DDevice::setScreenMode - ChangeDisplaySettings failed." ); + switch ( test ) + { + case DISP_CHANGE_RESTART: + Platform::AlertOK( "Display Change Failed", "You must restart your machine to get the specified mode." ); + break; + + case DISP_CHANGE_BADMODE: + Platform::AlertOK( "Display Change Failed", "The specified mode is not supported by this device." ); + break; + + default: + Platform::AlertOK( "Display Change Failed", "Hardware failed to switch to the specified mode." ); + break; + }; + + return false; + } + } + smIsFullScreen = false; + } + Con::setBoolVariable( "$pref::Video::fullScreen", smIsFullScreen ); + + bool newWindow = false; + if ( !winState.appWindow ) + { + Con::printf( "Creating a new %swindow...", ( fullScreen ? "full-screen " : "" ) ); + winState.appWindow = CreateOpenGLWindow( newRes.w, newRes.h, newFullScreen ); + if ( !winState.appWindow ) + { + AssertFatal( false, "D3DDevice::setScreenMode\nFailed to create a new window!" ); + return false; + } + newWindow = true; + } + + // Move the window: + if ( !newFullScreen ) + { + // Adjust the window rect to compensate for the window style: + RECT windowRect; + windowRect.left = windowRect.top = 0; + windowRect.right = newRes.w; + windowRect.bottom = newRes.h; + + AdjustWindowRect( &windowRect, GetWindowLong( winState.appWindow, GWL_STYLE ), false ); + U32 adjWidth = windowRect.right - windowRect.left; + U32 adjHeight = windowRect.bottom - windowRect.top; + + // Center the window on the desktop: + U32 xPos = ( winState.desktopWidth - adjWidth ) / 2; + U32 yPos = ( winState.desktopHeight - adjHeight ) / 2; + test = SetWindowPos( winState.appWindow, 0, xPos, yPos, adjWidth, adjHeight, SWP_NOZORDER ); + if ( !test ) + { + dSprintf( errorMessage, 255, "D3DDevice::setScreenMode\nSetWindowPos failed trying to move the window to (%d,%d) and size it to %dx%d.", xPos, yPos, newRes.w, newRes.h ); + AssertFatal( false, errorMessage ); + return false; + } + } + else if ( !newWindow ) + { + // Move and size the window to take up the whole screen: + if ( !SetWindowPos( winState.appWindow, 0, 0, 0, newRes.w, newRes.h, SWP_NOZORDER ) ) + { + dSprintf( errorMessage, 255, "D3DDevice::setScreenMode\nSetWindowPos failed to move the window to (0,0) and size it to %dx%d.", newRes.w, newRes.h ); + AssertFatal( false, errorMessage ); + return false; + } + } + + if ( !winState.appDC ) + { + // Get a new device context: + Con::printf( "Acquiring a new device context..." ); + winState.appDC = GetDC( winState.appWindow ); + if ( !winState.appDC ) + { + AssertFatal( false, "D3DDevice::setScreenMode\nGetDC failed to get a valid device context!" ); + return false; + } + } + + if ( newWindow ) + { + // Set the pixel format of the new window: + PIXELFORMATDESCRIPTOR pfd; + qwglDescribePixelFormat( winState.appDC, 1, sizeof( pfd ), &pfd ); + if ( !SetPixelFormat( winState.appDC, 1, &pfd ) ) + { + //AssertFatal( false, "D3DDevice::setScreenMode\nFailed to set the pixel format!" ); + //return false; + } + Con::printf( "Pixel format set:" ); + Con::printf( " %d color bits, %d depth bits, %d stencil bits", pfd.cColorBits, pfd.cDepthBits, pfd.cStencilBits ); + } + + if ( !winState.hGLRC ) + { + // Create a new rendering context: + Con::printf( "Creating a new rendering context..." ); + winState.hGLRC = qwglCreateContext( winState.appDC ); + if ( !winState.hGLRC ) + { + AssertFatal( false, "D3DDevice::setScreenMode\nqwglCreateContext failed to create an OpenGL rendering context!" ); + return false; + } + + // Make the new rendering context current: + Con::printf( "Making the new rendering context current..." ); + if ( !qwglMakeCurrent( winState.appDC, winState.hGLRC ) ) + { + AssertFatal( false, "D3DDevice::setScreenMode\nqwglMakeCurrent failed to make the rendering context current!" ); + return false; + } + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + } + + smCurrentRes = newRes; + Platform::setWindowSize( newRes.w, newRes.h ); + char tempBuf[15]; + dSprintf( tempBuf, sizeof( tempBuf ), "%d %d %d", smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp ); + Con::setVariable( "$pref::Video::resolution", tempBuf ); + + if ( curtain ) + DestroyWindow( curtain ); + + // Doesn't hurt to do this even it isn't necessary: + ShowWindow( winState.appWindow, SW_SHOW ); + SetForegroundWindow( winState.appWindow ); + SetFocus( winState.appWindow ); + + if ( repaint ) + Con::evaluate( "resetCanvas();" ); + + return true; +} + + +//------------------------------------------------------------------------------ +void D3DDevice::swapBuffers() +{ + qwglSwapBuffers( winState.appDC ); +} + + +//------------------------------------------------------------------------------ +const char* D3DDevice::getDriverInfo() +{ + // 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 ); + 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 D3DDevice::getGammaCorrection(F32 &g) +{ + U16 ramp[256*3]; + + if (!GetDeviceGammaRamp(winState.appDC, ramp)) + return false; + + F32 csum = 0.0; + U32 ccount = 0; + + for (U16 i = 0; i < 256; ++i) + { + if (i != 0 && ramp[i] != 0 && ramp[i] != 65535) + { + F64 b = (F64) i/256.0; + F64 a = (F64) ramp[i]/65535.0; + F32 c = (F32) (mLog(a)/mLog(b)); + + csum += c; + ++ccount; + } + } + g = csum/ccount; + + return true; +} + +//------------------------------------------------------------------------------ +bool D3DDevice::setGammaCorrection(F32 g) +{ + U16 ramp[256*3]; + + for (U16 i = 0; i < 256; ++i) + ramp[i] = mPow((F32) i/256.0f, g) * 65535.0f; + dMemcpy(&ramp[256],ramp,256*sizeof(U16)); + dMemcpy(&ramp[512],ramp,256*sizeof(U16)); + + return SetDeviceGammaRamp(winState.appDC, ramp); +} + +//------------------------------------------------------------------------------ +bool D3DDevice::setVerticalSync( bool on ) +{ + on; + return( false ); +} + +//------------------------------------------------------------------------------ +DisplayDevice* D3DDevice::create() +{ + bool result = false; + + OSVERSIONINFO OSVersionInfo; + + dMemset( &OSVersionInfo, 0, sizeof( OSVERSIONINFO ) ); + OSVersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + if ( GetVersionEx( &OSVersionInfo ) && + (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && + OSVersionInfo.dwMajorVersion <= 4) ) + return NULL; + + // If we can't do OpenGL, don't attempt D3D either (probably Voodoo2 only) + if (Video::getDevice("OpenGL") == NULL) + return NULL; + + // This shouldn't happen, but just to be safe... + if ( winState.hinstOpenGL ) + QGL_Shutdown(); + + if (!QGL_Init(OPENGL2D3D,GLU2D3D)) + return NULL; + + // Create a test window to see if OpenGL hardware acceleration is available: + WNDCLASS wc; + dMemset(&wc, 0, sizeof(wc)); + wc.style = CS_OWNDC; + wc.lpfnWndProc = DefWindowProc; + wc.hInstance = winState.appInstance; + wc.lpszClassName = "D3DTest"; + RegisterClass( &wc ); + + HWND testWindow = CreateWindow( + "D3DTest", + "", + WS_POPUP, + 0, 0, 640, 480, + NULL, + NULL, + winState.appInstance, + NULL ); + + if ( testWindow ) + { + HDC testDC = GetDC( testWindow ); + + if ( testDC ) + { + PIXELFORMATDESCRIPTOR pfd; + + result = qwglDescribePixelFormat( testDC, 1, sizeof( pfd ), &pfd ); + ReleaseDC( testWindow, testDC ); + } + DestroyWindow( testWindow ); + } + + UnregisterClass( "D3DTest", winState.appInstance ); + + QGL_Shutdown(); + + if ( result ) + { + D3DDevice* newOGLDevice = new D3DDevice(); + if ( newOGLDevice ) + { + newOGLDevice->mFullScreenOnly = Video::getDevice("OpenGL")->isFullScreenOnly(); + return (DisplayDevice*) newOGLDevice; + } + else + return NULL; + } + else + return NULL; +} diff --git a/platformWin32/winD3DVideo.h b/platformWin32/winD3DVideo.h new file mode 100644 index 0000000..df6baff --- /dev/null +++ b/platformWin32/winD3DVideo.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WIND3DVIDEO_H_ +#define _WIND3DVIDEO_H_ + +#ifndef _PLATFORMVIDEO_H_ +#include "Platform/platformVideo.h" +#endif + +class D3DDevice : public DisplayDevice +{ + static bool smCanSwitchBitDepth; + + bool mRestoreGamma; + U16 mOriginalRamp[256*3]; + + public: + D3DDevice(); + + 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 ); + + static DisplayDevice* create(); + + static bool smStay16; +}; + +#endif // _H_WIND3DVIDEO diff --git a/platformWin32/winDInputDevice.cc b/platformWin32/winDInputDevice.cc new file mode 100644 index 0000000..2a86053 --- /dev/null +++ b/platformWin32/winDInputDevice.cc @@ -0,0 +1,1629 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef INITGUID +#define INITGUID +#endif + +#include "math/mMath.h" +#include "platformWIN32/winDInputDevice.h" +#include "console/console.h" +#include "platform/gameInterface.h" + +// Static class data: +LPDIRECTINPUT DInputDevice::smDInputInterface; +bool DInputDevice::smIsDX5; +U8 DInputDevice::smKeyboardCount; +U8 DInputDevice::smMouseCount; +U8 DInputDevice::smJoystickCount; +U8 DInputDevice::smUnknownCount; +U8 DInputDevice::smModifierKeys; +bool DInputDevice::smKeyStates[256]; +bool DInputDevice::smInitialized = false; + +#ifdef LOG_INPUT +const char* getKeyName( U16 key ); +#endif + +//------------------------------------------------------------------------------ +DInputDevice::DInputDevice( const DIDEVICEINSTANCE* dii ) +{ + mDeviceInstance = *dii; + mDevice = NULL; + mDevice2 = NULL; + mAcquired = false; + mNeedSync = false; + mObjInstance = NULL; + mObjFormat = NULL; + mObjInfo = NULL; + mObjBuffer1 = NULL; + mObjBuffer2 = NULL; + mPrevObjBuffer = NULL; + + switch ( GET_DIDEVICE_TYPE( mDeviceInstance.dwDevType ) ) + { + case DIDEVTYPE_KEYBOARD: + mDeviceType = KeyboardDeviceType; + mDeviceID = smKeyboardCount++; + dSprintf( mName, 29, "keyboard%d", mDeviceID ); + break; + + case DIDEVTYPE_MOUSE: + mDeviceType = MouseDeviceType; + mDeviceID = smMouseCount++; + dSprintf( mName, 29, "mouse%d", mDeviceID ); + break; + + case DIDEVTYPE_JOYSTICK: + mDeviceType = JoystickDeviceType; + mDeviceID = smJoystickCount++; + dSprintf( mName, 29, "joystick%d", mDeviceID ); + break; + + default: + mDeviceType = UnknownDeviceType; + mDeviceID = smUnknownCount++; + dSprintf( mName, 29, "unknown%d", mDeviceID ); + break; + } +} + +//------------------------------------------------------------------------------ +DInputDevice::~DInputDevice() +{ + destroy(); +} + +//------------------------------------------------------------------------------ +void DInputDevice::init() +{ + if ( !smInitialized ) + { + dMemset( smKeyStates, 0, sizeof( smKeyStates ) ); + smInitialized = true; + } + + // Reset all of the static variables: + smDInputInterface = NULL; + smKeyboardCount = 0; + smMouseCount = 0; + smJoystickCount = 0; + smUnknownCount = 0; + smModifierKeys = 0; +} + +//------------------------------------------------------------------------------ +void DInputDevice::resetModifierKeys() +{ + smModifierKeys = 0; + setModifierKeys( 0 ); +} + +//------------------------------------------------------------------------------ +bool DInputDevice::create() +{ + HRESULT result; + + if ( smDInputInterface ) + { + result = smDInputInterface->CreateDevice( mDeviceInstance.guidInstance, &mDevice, NULL ); + if ( result == DI_OK ) + { + mDevice->QueryInterface( IID_IDirectInputDevice2, (void**) &mDevice2 ); + mDeviceCaps.dwSize = ( smIsDX5 ? sizeof( DIDEVCAPS ) : sizeof( DIDEVCAPS_DX3 ) ); + 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(); + + if ( mDevice2 ) + { + mDevice2->Release(); + mDevice2 = NULL; + } + + 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 + ) + coopLevel |= DISCL_EXCLUSIVE; + else + coopLevel |= DISCL_NONEXCLUSIVE; + + result = mDevice->SetCooperativeLevel( winState.appWindow, 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; + } + + 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; + + // 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; + + // Enumerate all of the 'objects' detected on the device: + if ( FAILED( mDevice->EnumObjects( EnumObjectsProc, this, DIDFT_ALL ) ) ) + return false; + + mObjBufferSize = ( mObjBufferSize + 3 ) & ~3; // Fill in the actual size to nearest DWORD + + U32 buttonCount = 0; + U32 povCount = 0; + U32 xAxisCount = 0; + U32 yAxisCount = 0; + U32 zAxisCount = 0; + U32 rAxisCount = 0; + U32 uAxisCount = 0; + U32 vAxisCount = 0; + U32 sliderCount = 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 = KEY_BUTTON0 + buttonCount++; + } + else if ( mObjInstance[i].guidType == GUID_POV ) + { + mObjInfo[i].mType = SI_POV; + mObjInfo[i].mInst = povCount++; + } + else if ( mObjInstance[i].guidType == GUID_XAxis ) + { + mObjInfo[i].mType = SI_XAXIS; + mObjInfo[i].mInst = xAxisCount++; + } + else if ( mObjInstance[i].guidType == GUID_YAxis ) + { + mObjInfo[i].mType = SI_YAXIS; + mObjInfo[i].mInst = yAxisCount++; + } + else if ( mObjInstance[i].guidType == GUID_ZAxis ) + { + mObjInfo[i].mType = SI_ZAXIS; + mObjInfo[i].mInst = zAxisCount++; + } + else if ( mObjInstance[i].guidType == GUID_RxAxis ) + { + mObjInfo[i].mType = SI_RXAXIS; + mObjInfo[i].mInst = rAxisCount++; + } + else if ( mObjInstance[i].guidType == GUID_RyAxis ) + { + mObjInfo[i].mType = SI_RYAXIS; + mObjInfo[i].mInst = uAxisCount++; + } + else if ( mObjInstance[i].guidType == GUID_RzAxis ) + { + mObjInfo[i].mType = SI_RZAXIS; + mObjInfo[i].mInst = vAxisCount++; + } + else if ( mObjInstance[i].guidType == GUID_Slider ) + { + mObjInfo[i].mType = SI_SLIDER; + mObjInfo[i].mInst = sliderCount++; + } + 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 = 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() +{ + return mDeviceInstance.tszInstanceName; +} + +//------------------------------------------------------------------------------ +const char* DInputDevice::getProductName() +{ + return mDeviceInstance.tszProductName; +} + +//------------------------------------------------------------------------------ +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; + + // Test for the "need sync" flag: + if ( mNeedSync ) + { + // For now, only sync the keyboard: + if ( mDeviceType == KeyboardDeviceType ) + syncKeyboardState(); + + mNeedSync = 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; + + if ( mDevice2 ) + mDevice2->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: + S32 newData, oldData; + 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; +} + +//------------------------------------------------------------------------------ +bool DInputDevice::processKeyEvent( InputEvent &event ) +{ + if ( event.deviceType != KeyboardDeviceType || event.objType != SI_KEY ) + return false; + + bool modKey = false; + U8 DIKeyCode = Key_to_DIK( event.objInst ); + + if ( event.action == SI_MAKE ) + { + // Maintain the key structure: + smKeyStates[DIKeyCode] = true; + + switch ( event.objInst ) + { + case KEY_LSHIFT: + smModifierKeys |= SI_LSHIFT; + modKey = true; + break; + + case KEY_RSHIFT: + smModifierKeys |= SI_RSHIFT; + modKey = true; + break; + + case KEY_LCONTROL: + smModifierKeys |= SI_LCTRL; + modKey = true; + break; + + case KEY_RCONTROL: + smModifierKeys |= SI_RCTRL; + modKey = true; + break; + + case KEY_LALT: + smModifierKeys |= SI_LALT; + modKey = true; + break; + + case KEY_RALT: + smModifierKeys |= SI_RALT; + modKey = true; + break; + } + } + else + { + // Maintain the keys structure: + smKeyStates[DIKeyCode] = false; + + switch ( event.objInst ) + { + case KEY_LSHIFT: + smModifierKeys &= ~SI_LSHIFT; + modKey = true; + break; + + case KEY_RSHIFT: + smModifierKeys &= ~SI_RSHIFT; + modKey = true; + break; + + case KEY_LCONTROL: + smModifierKeys &= ~SI_LCTRL; + modKey = true; + break; + + case KEY_RCONTROL: + smModifierKeys &= ~SI_RCTRL; + modKey = true; + break; + + case KEY_LALT: + smModifierKeys &= ~SI_LALT; + modKey = true; + break; + + case KEY_RALT: + smModifierKeys &= ~SI_RALT; + modKey = true; + break; + } + } + + if ( modKey ) + { + setModifierKeys( smModifierKeys ); + event.modifier = 0; + } + else + event.modifier = smModifierKeys; + + // TODO: alter this getAscii call + KEY_STATE state = STATE_LOWER; + if (event.modifier & (SI_CTRL|SI_ALT) ) + { + state = STATE_GOOFY; + } +// else if ( event.modifier & SI_SHIFT ) + if ( event.modifier & SI_SHIFT ) + { + state = STATE_UPPER; + } + + + event.ascii = Input::getAscii( event.objInst, state ); + + return modKey; +} + +//------------------------------------------------------------------------------ +void DInputDevice::syncKeyboardState() +{ + AssertFatal( mDeviceType == KeyboardDeviceType, "DInputDevice::syncKeyboardState - device is not a keyboard!" ); + +#ifdef LOG_INPUT + Input::log( "Resynching key states for %s!\n", mName ); +#endif + + U8* keyBuffer = new U8[mObjBufferSize]; + dMemset( keyBuffer, 0, sizeof( keyBuffer ) ); + HRESULT result = mDevice->GetDeviceState( mObjBufferSize, keyBuffer ); + if ( SUCCEEDED( result ) ) + { + S32 keyState; + bool keyIsDown, keyWasDown; + for ( DWORD i = 1; i < mObjCount; i++ ) // Valid key codes start at 1 + { + keyState = *( (U8*) ( keyBuffer + mObjFormat[i].dwOfs ) ); + keyWasDown = smKeyStates[i]; + keyIsDown = bool( keyState & 0x80 ); + if ( keyWasDown != keyIsDown ) + buildEvent( i - 1, ( keyState & 0x80 ), ( keyWasDown ? 0x80 : 0 ) ); + } + +#ifdef LOG_INPUT + Input::log( "Resync done.\n" ); +#endif + } + else + { + 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; + + case E_PENDING: + errorString = "E_PENDING"; + break; + + default: + errorString = "Unknown Error"; + } + +#ifdef LOG_INPUT + Input::log( "Resync GetDeviceState on %s failed! %s\n", mName, errorString ); +#endif + Con::errorf( "DInputDevice::syncKeyboardState - %s", errorString ); + } + + delete [] keyBuffer; +} + +//------------------------------------------------------------------------------ +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; +} + +//------------------------------------------------------------------------------ +bool DInputDevice::buildEvent( DWORD offset, S32 newData, S32 oldData ) +{ + DIDEVICEOBJECTINSTANCE &objInstance = mObjInstance[offset]; + ObjInfo &objInfo = mObjInfo[offset]; + + if ( objInfo.mType == SI_UNKNOWN ) + return false; + + InputEvent newEvent; + newEvent.deviceType = mDeviceType; + newEvent.deviceInst = mDeviceID; + newEvent.objType = objInfo.mType; + newEvent.objInst = objInfo.mInst; + newEvent.modifier = smModifierKeys; + + switch ( newEvent.objType ) + { + case SI_XAXIS: + case SI_YAXIS: + case SI_ZAXIS: + case SI_RXAXIS: + case SI_RYAXIS: + case SI_RZAXIS: + case SI_SLIDER: + newEvent.action = SI_MOVE; + if ( newEvent.deviceType == MouseDeviceType ) + { + newEvent.fValue = float( newData ); + +#ifdef LOG_INPUT +#ifdef LOG_MOUSEMOVE + if ( newEvent.objType == SI_XAXIS ) + Input::log( "EVENT (DInput): %s move (%.1f, 0.0).\n", mName, newEvent.fValue ); + else if ( newEvent.objType == SI_YAXIS ) + Input::log( "EVENT (DInput): %s move (0.0, %.1f).\n", mName, newEvent.fValue ); + else +#endif + if ( newEvent.objType == 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( newData - objInfo.mMin ) / range; + newEvent.fValue = float( ( 2 * newData ) - objInfo.mMax - objInfo.mMin ) / range; + } + else + newEvent.fValue = 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 + } + + Game->postEvent( newEvent ); + 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 + + Game->postEvent( newEvent ); + break; + + case SI_KEY: + newEvent.action = ( newData & 0x80 ) ? SI_MAKE : SI_BREAK; + newEvent.fValue = ( newEvent.action == SI_MAKE ) ? 1.0f : 0.0f; + processKeyEvent( newEvent ); + +#ifdef LOG_INPUT + if ( newEvent.action == SI_MAKE ) + Input::log( "EVENT (DInput): %s key pressed. MODS:%c%c%c\n", getKeyName( newEvent.objInst ), ( smModifierKeys & SI_SHIFT ? 'S' : '.' ), ( smModifierKeys & SI_CTRL ? 'C' : '.' ), ( smModifierKeys & SI_ALT ? 'A' : '.' ) ); + else + Input::log( "EVENT (DInput): %s key released.\n", getKeyName( newEvent.objInst ) ); +#endif + + Game->postEvent( newEvent ); + break; + + case SI_POV: +// newEvent.action = SI_MOVE; +// if ( LOWORD( newData ) == 0xffff ) +// { +// newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_XPOV : SI_XPOV2; +// newEvent.fValue = 0.5f; +// Game->postEvent( newEvent ); +// +// newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_YPOV : SI_YPOV2; +// Game->postEvent( newEvent ); +// } +// else +// { +// // Map 0-35999 data into 0.0-M_2PI fdata: +// float fdata = float( newData ) * ( 1.0 / 36000.0f ) * M_2PI; +// float x, y; +// mSinCos( fdata, x, y ); +// x = ( x + 1.0f ) * 0.5f; +// y = ( -y + 1.0f ) * 0.5f; +// newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_XPOV : SI_XPOV2; +// newEvent.fValue = x; +// #ifdef LOG_INPUT +// Input::log( "EVENT (DInput): POV X-axis %d at %.1f.\n", +// ( newEvent.objInst == SI_XPOV ) ? 1 : 2, newEvent.fValue ); +// #endif +// Game->postEvent( newEvent ); +// +// newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_YPOV : SI_YPOV2; +// newEvent.fValue = y; +// #ifdef LOG_INPUT +// Input::log( "EVENT (DInput): POV Y-axis %d at %.1f.\n", +// ( newEvent.objInst == SI_YPOV ) ? 1 : 2, newEvent.fValue ); +// #endif +// Game->postEvent( newEvent ); +// } + + // Handle artificial POV up/down/left/right buttons + newData = LOWORD( newData ); + oldData = LOWORD( oldData ); + newData = ( newData == 0xffff ) ? 5 : ( ( ( newData + 31500 ) / 9000 ) -3 ) & 0x03; + oldData = ( oldData == 0xffff ) ? 5 : ( ( ( oldData + 31500 ) / 9000 ) -3 ) & 0x03; + + if ( newData != oldData ) + { + if ( oldData != 5 ) + { + newEvent.action = SI_BREAK; + newEvent.fValue = 0.0f; + switch ( oldData ) + { + case 0: + newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_UPOV : SI_UPOV2; +#ifdef LOG_INPUT + Input::log( "EVENT (DInput): Up POV %d released.\n", + ( newEvent.objInst == SI_UPOV ) ? 1 : 2 ); +#endif + break; + + case 1: + newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_RPOV : SI_RPOV2; +#ifdef LOG_INPUT + Input::log( "EVENT (DInput): Right POV %d released.\n", + ( newEvent.objInst == SI_RPOV ) ? 1 : 2 ); +#endif + break; + + case 2: + newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_DPOV : SI_DPOV2; +#ifdef LOG_INPUT + Input::log( "EVENT (DInput): Down POV %d released.\n", + ( newEvent.objInst == SI_DPOV ) ? 1 : 2 ); +#endif + break; + + case 3: + newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_LPOV : SI_LPOV2; +#ifdef LOG_INPUT + Input::log( "EVENT (DInput): Left POV %d released.\n", + ( newEvent.objInst == SI_LPOV ) ? 1 : 2 ); +#endif + break; + } + Game->postEvent( newEvent ); + } + + if ( newData != 5 ) + { + newEvent.action = SI_MAKE; + newEvent.fValue = 1.0f; + switch( newData ) + { + case 0: + newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_UPOV : SI_UPOV2; +#ifdef LOG_INPUT + Input::log( "EVENT (DInput): Up POV %d pressed.\n", + ( newEvent.objInst == SI_UPOV ) ? 1 : 2 ); +#endif + break; + + case 1: + newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_RPOV : SI_RPOV2; +#ifdef LOG_INPUT + Input::log( "EVENT (DInput): Right POV %d pressed.\n", + ( newEvent.objInst == SI_RPOV ) ? 1 : 2 ); +#endif + break; + + case 2: + newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_DPOV : SI_DPOV2; +#ifdef LOG_INPUT + Input::log( "EVENT (DInput): Down POV %d pressed.\n", + ( newEvent.objInst == SI_DPOV ) ? 1 : 2 ); +#endif + break; + + case 3: + newEvent.objInst = ( newEvent.objInst == 0 ) ? SI_LPOV : SI_LPOV2; +#ifdef LOG_INPUT + Input::log( "EVENT (DInput): Left POV %d pressed.\n", + ( newEvent.objInst == SI_LPOV ) ? 1 : 2 ); +#endif + break; + } + Game->postEvent( newEvent ); + } + } + break; + } + + return true; +} + +//------------------------------------------------------------------------------ +// +// This function translates the DirectInput scan code to the associated +// internal key code (as defined in event.h). +// +//------------------------------------------------------------------------------ +U16 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 0; + case DIK_CONVERT: return 0; + case DIK_NOCONVERT: return 0; + case DIK_YEN: return 0; + case DIK_NUMPADEQUALS: return 0; + case DIK_CIRCUMFLEX: return 0; + case DIK_AT: return 0; + case DIK_COLON: return 0; + case DIK_UNDERLINE: return 0; + case DIK_KANJI: return 0; + case DIK_STOP: return 0; + case DIK_AX: return 0; + case DIK_UNLABELED: return 0; + + 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].mType ) + { + 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( smJoystickCount > 0 ); +} + + + diff --git a/platformWin32/winDInputDevice.h b/platformWin32/winDInputDevice.h new file mode 100644 index 0000000..2d72922 --- /dev/null +++ b/platformWin32/winDInputDevice.h @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 +#include + + +class DInputDevice : public InputDevice +{ + public: + static LPDIRECTINPUT smDInputInterface; + static bool smIsDX5; + + 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 smKeyboardCount; + static U8 smMouseCount; + static U8 smJoystickCount; + static U8 smUnknownCount; + + static U8 smModifierKeys; + static bool smKeyStates[256]; + static bool smInitialized; + + //-------------------------------------- + LPDIRECTINPUTDEVICE mDevice; + LPDIRECTINPUTDEVICE2 mDevice2; + DIDEVICEINSTANCE mDeviceInstance; + DIDEVCAPS mDeviceCaps; + U8 mDeviceType; + U8 mDeviceID; + + bool mAcquired; + bool mNeedSync; + + //-------------------------------------- + DIDEVICEOBJECTINSTANCE* mObjInstance; + DIOBJECTDATAFORMAT* mObjFormat; + ObjInfo* mObjInfo; + U8* mObjBuffer1; // polled device input buffers + U8* mObjBuffer2; + U8* mPrevObjBuffer; // points to buffer 1 or 2 + + 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(); + bool processKeyEvent( InputEvent &event ); + + void syncKeyboardState(); + + DWORD findObjInstance( DWORD offset ); + bool buildEvent( DWORD offset, S32 newData, S32 oldData ); + + public: + DInputDevice( const DIDEVICEINSTANCE* deviceInst ); + ~DInputDevice(); + + static void init(); + static void resetModifierKeys(); + + bool create(); + void destroy(); + + bool acquire(); + bool unacquire(); + + bool isAcquired(); + bool isPolled(); + + U8 getDeviceType(); + U8 getDeviceID(); + + const char* getName(); + const char* getProductName(); + + // 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 ); +} + +//------------------------------------------------------------------------------ +inline U8 DInputDevice::getDeviceType() +{ + return mDeviceType; +} + +//------------------------------------------------------------------------------ +inline U8 DInputDevice::getDeviceID() +{ + return mDeviceID; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +U16 DIK_to_Key( U8 dikCode ); +U8 Key_to_DIK( U16 keyCode ); +#endif // _H_WINDINPUTDEVICE_ diff --git a/platformWin32/winDirectInput.cc b/platformWin32/winDirectInput.cc new file mode 100644 index 0000000..e67d8f8 --- /dev/null +++ b/platformWin32/winDirectInput.cc @@ -0,0 +1,553 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformWIN32/platformWin32.h" +#include "platform/platformVideo.h" +#include "platformWIN32/winDirectInput.h" +#include "platformWIN32/winDInputDevice.h" +#include "platform/event.h" +#include "console/console.h" +#include "console/consoleTypes.h" + +//------------------------------------------------------------------------------ +// Static class variables: +bool DInputManager::smKeyboardEnabled = true; +bool DInputManager::smMouseEnabled = false; +bool DInputManager::smJoystickEnabled = false; + +// Type definitions: +typedef HRESULT (WINAPI* FN_DirectInputCreate)( HINSTANCE hInst, DWORD dwVersion, LPDIRECTINPUT * ppDI, IUnknown FAR *pUnkOuter ); + +//------------------------------------------------------------------------------ +DInputManager::DInputManager() +{ + mEnabled = false; + mDInputLib = NULL; + mDInputInterface = NULL; + mKeyboardActive = mMouseActive = mJoystickActive = false; +} + +//------------------------------------------------------------------------------ +void DInputManager::init() +{ + Con::addVariable( "pref::Input::KeyboardEnabled", TypeBool, &smKeyboardEnabled ); + Con::addVariable( "pref::Input::MouseEnabled", TypeBool, &smMouseEnabled ); + Con::addVariable( "pref::Input::JoystickEnabled", TypeBool, &smJoystickEnabled ); +} + +//------------------------------------------------------------------------------ +bool DInputManager::enable() +{ + FN_DirectInputCreate fnDInputCreate; + + disable(); +#ifdef LOG_INPUT + Input::log( "Enabling DirectInput...\n" ); +#endif + mDInputLib = LoadLibrary( "DInput.dll" ); + if ( mDInputLib ) + { + fnDInputCreate = (FN_DirectInputCreate) GetProcAddress( mDInputLib, "DirectInputCreateA" ); + if ( fnDInputCreate ) + { + bool result = SUCCEEDED( fnDInputCreate( winState.appInstance, 0x500, &mDInputInterface, NULL ) ); + if ( result ) + { + DInputDevice::smIsDX5 = true; +#ifdef LOG_INPUT + Input::log( "DirectX 5 or greater detected.\n" ); +#endif + } + else + { + result = SUCCEEDED( fnDInputCreate( winState.appInstance, 0x300, &mDInputInterface, NULL ) ); + if ( result ) + { + DInputDevice::smIsDX5 = false; +#ifdef LOG_INPUT + Input::log( "DirectX 3 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; + } + + 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() +{ + 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( DIDEVTYPE_KEYBOARD, EnumDevicesProc, this, DIEDFL_ATTACHEDONLY ); + mDInputInterface->EnumDevices( DIDEVTYPE_MOUSE, EnumDevicesProc, this, DIEDFL_ATTACHEDONLY ); + mDInputInterface->EnumDevices( DIDEVTYPE_JOYSTICK, 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::enableKeyboard() +{ + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isEnabled() ) + return( false ); + + if ( smKeyboardEnabled && mgr->isKeyboardActive() ) + return( true ); + + smKeyboardEnabled = true; + if ( Input::isActive() ) + smKeyboardEnabled = mgr->activateKeyboard(); + + if ( smKeyboardEnabled ) + { + Con::printf( "DirectInput keyboard enabled." ); +#ifdef LOG_INPUT + Input::log( "Keyboard enabled.\n" ); +#endif + } + else + { + Con::warnf( "DirectInput keyboard failed to enable!" ); +#ifdef LOG_INPUT + Input::log( "Keyboard failed to enable!\n" ); +#endif + } + + return( smKeyboardEnabled ); +} + +//------------------------------------------------------------------------------ +void DInputManager::disableKeyboard() +{ + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isEnabled() || !smKeyboardEnabled ) + return; + + mgr->deactivateKeyboard(); + smKeyboardEnabled = false; + Con::printf( "DirectInput keyboard disabled." ); +#ifdef LOG_INPUT + Input::log( "Keyboard disabled.\n" ); +#endif +} + +//------------------------------------------------------------------------------ +bool DInputManager::isKeyboardEnabled() +{ + return( smKeyboardEnabled ); +} + +//------------------------------------------------------------------------------ +bool DInputManager::activateKeyboard() +{ + if ( !mEnabled || !Input::isActive() || !smKeyboardEnabled ) + return( false ); + + // Acquire only one keyboard: + mKeyboardActive = acquire( KeyboardDeviceType, 0 ); +#ifdef LOG_INPUT + Input::log( mKeyboardActive ? "Keyboard activated.\n" : "Keyboard failed to activate!\n" ); +#endif + return( mKeyboardActive ); +} + +//------------------------------------------------------------------------------ +void DInputManager::deactivateKeyboard() +{ + if ( mEnabled && mKeyboardActive ) + { + unacquire( KeyboardDeviceType, SI_ANY ); + mKeyboardActive = false; +#ifdef LOG_INPUT + Input::log( "Keyboard deactivated.\n" ); +#endif + } +} + +//------------------------------------------------------------------------------ +bool DInputManager::enableMouse() +{ + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isEnabled() ) + return( false ); + + if ( smMouseEnabled && mgr->isMouseActive() ) + return( true ); + + smMouseEnabled = true; + if ( Input::isActive() ) + smMouseEnabled = mgr->activateMouse(); + + if ( smMouseEnabled ) + { + Con::printf( "DirectInput mouse enabled." ); +#ifdef LOG_INPUT + Input::log( "Mouse enabled.\n" ); +#endif + } + else + { + Con::warnf( "DirectInput mouse failed to enable!" ); +#ifdef LOG_INPUT + Input::log( "Mouse failed to enable!\n" ); +#endif + } + + return( smMouseEnabled ); +} + +//------------------------------------------------------------------------------ +void DInputManager::disableMouse() +{ + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isEnabled() || !smMouseEnabled ) + return; + + mgr->deactivateMouse(); + smMouseEnabled = false; + Con::printf( "DirectInput mouse disabled." ); +#ifdef LOG_INPUT + Input::log( "Mouse disabled.\n" ); +#endif +} + +//------------------------------------------------------------------------------ +bool DInputManager::isMouseEnabled() +{ + return( smMouseEnabled ); +} + +//------------------------------------------------------------------------------ +bool DInputManager::activateMouse() +{ + if ( !mEnabled || !Input::isActive() || !smMouseEnabled ) + return( false ); + + mMouseActive = acquire( MouseDeviceType, SI_ANY ); +#ifdef LOG_INPUT + Input::log( mMouseActive ? "Mouse activated.\n" : "Mouse failed to activate!\n" ); +#endif + return( mMouseActive ); +} + +//------------------------------------------------------------------------------ +void DInputManager::deactivateMouse() +{ + if ( mEnabled && mMouseActive ) + { + unacquire( MouseDeviceType, SI_ANY ); + mMouseActive = false; +#ifdef LOG_INPUT + Input::log( "Mouse deactivated.\n" ); +#endif + } +} + +//------------------------------------------------------------------------------ +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( "" ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( activateKeyboard, bool, 1, 1, "activateKeyboard()" ) +{ + argc; argv; + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + return( mgr->activateKeyboard() ); + + return( false ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( deactivateKeyboard, void, 1, 1, "deactivateKeyboard()" ) +{ + argc; argv; + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + mgr->deactivateKeyboard(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( enableMouse, bool, 1, 1, "enableMouse()" ) +{ + argc; argv; + return( DInputManager::enableMouse() ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( disableMouse, void, 1, 1, "disableMouse()" ) +{ + argc; argv; + DInputManager::disableMouse(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( enableJoystick, bool, 1, 1, "enableJoystick()" ) +{ + argc; argv; + return( DInputManager::enableJoystick() ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( disableJoystick, void, 1, 1, "disableJoystick()" ) +{ + argc; argv; + DInputManager::disableJoystick(); +} + +//------------------------------------------------------------------------------ +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( "- 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( "DirectInput is not enabled." ); +} diff --git a/platformWin32/winDirectInput.h b/platformWin32/winDirectInput.h new file mode 100644 index 0000000..56c9b42 --- /dev/null +++ b/platformWin32/winDirectInput.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 +#include + +struct InputEvent; + +//------------------------------------------------------------------------------ +class DInputManager : public InputManager +{ + private: + typedef SimGroup Parent; + + HMODULE mDInputLib; + LPDIRECTINPUT mDInputInterface; + + static bool smKeyboardEnabled; + static bool smMouseEnabled; + static bool smJoystickEnabled; + + bool mKeyboardActive; + bool mMouseActive; + bool mJoystickActive; + + void enumerateDevices(); + + static BOOL CALLBACK EnumDevicesProc( const DIDEVICEINSTANCE *pddi, LPVOID pvRef ); + + bool acquire( U8 deviceType, U8 deviceID ); + void unacquire( U8 deviceType, U8 deviceID ); + + public: + DInputManager(); + + bool enable(); + void disable(); + + void onDeleteNotify( SimObject* object ); + bool onAdd(); + void onRemove(); + + void process(); + + // DirectInput functions: + static void init(); + + static bool enableKeyboard(); + static void disableKeyboard(); + static bool isKeyboardEnabled(); + bool activateKeyboard(); + void deactivateKeyboard(); + bool isKeyboardActive() { return( mKeyboardActive ); } + + static bool enableMouse(); + static void disableMouse(); + static bool isMouseEnabled(); + bool activateMouse(); + void deactivateMouse(); + bool isMouseActive() { return( mMouseActive ); } + + static bool enableJoystick(); + static void disableJoystick(); + static bool isJoystickEnabled(); + bool activateJoystick(); + void deactivateJoystick(); + bool isJoystickActive() { return( mJoystickActive ); } + + // Console interface: + const char* getJoystickAxesString( U32 deviceID ); +}; + +#endif // _H_WINDIRECTINPUT_ diff --git a/platformWin32/winFileio.cc b/platformWin32/winFileio.cc new file mode 100644 index 0000000..dcbabd4 --- /dev/null +++ b/platformWin32/winFileio.cc @@ -0,0 +1,589 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformWIN32/platformWin32.h" +#include "core/fileio.h" +#include "core/tVector.h" +#include "core/stringTable.h" +#include "console/console.h" + +// Microsoft VC++ has this POSIX header in the wrong directory +#ifdef _MSC_VER +#include +#else +#include +#endif + +//-------------------------------------- Helper Functions +static void forwardslash(char *str) +{ + while(*str) + { + if(*str == '\\') + *str = '/'; + str++; + } +} + +static void backslash(char *str) +{ + while(*str) + { + if(*str == '/') + *str = '\\'; + str++; + } +} + +//----------------------------------------------------------------------------- +bool dFileDelete(const char * name) +{ + if(!name || (dStrlen(name) >= MAX_PATH)) + return(false); + return(::DeleteFile(name)); +} + +bool dFileTouch(const char * name) +{ + // change the modified time to the current time (0byte WriteFile fails!) + return(utime(name, 0) != -1); +} + +//----------------------------------------------------------------------------- +// 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) +{ + static char filebuf[2048]; + dStrcpy(filebuf, filename); + backslash(filebuf); + filename = filebuf; + + AssertFatal(NULL != filename, "File::open: NULL filename"); + AssertWarn(INVALID_HANDLE_VALUE == (HANDLE)handle, "File::open: handle already valid"); + + // 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(filename, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + break; + case Write: + handle = (void *)CreateFile(filename, + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + break; + case ReadWrite: + handle = (void *)CreateFile(filename, + GENERIC_WRITE | GENERIC_READ, + 0, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + break; + case WriteAppend: + handle = (void *)CreateFile(filename, + 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; + switch (absolutePos) + { + case true: // absolute position + AssertFatal(0 <= position, "File::setPosition: negative absolute position"); + + // position beyond EOS is OK + finalPos = SetFilePointer((HANDLE)handle, + position, + NULL, + FILE_BEGIN); + 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 = 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* in_pBasePath, + const char* in_pCurPath, + Vector& out_rFileVector) +{ + char buf[1024]; + char curPath[1024]; + char basePath[1024]; + char scratchBuf[1024]; + + if(in_pCurPath) + dStrcpy(curPath, in_pCurPath); + else + curPath[0] = 0; + + dStrcpy(basePath, in_pBasePath); + in_pBasePath = basePath; + + + if (curPath[0] != '\0') + dSprintf(buf, sizeof(buf), "%s/%s/*", basePath, curPath); + else + dSprintf(buf, sizeof(buf), "%s/*", basePath); + + + WIN32_FIND_DATA findData; + backslash(buf); + HANDLE hFind = FindFirstFile(buf, &findData); + if (hFind == INVALID_HANDLE_VALUE) + return false; + + while (hFind != INVALID_HANDLE_VALUE) { + if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + // Directory + if (findData.cFileName[0] != '.') { + scratchBuf[0] = '\0'; + if (curPath[0] != '\0') { + dStrcpy(scratchBuf, curPath); + dStrcat(scratchBuf, "/"); + } + dStrcat(scratchBuf, findData.cFileName); + + _recurseDumpPath(basePath, scratchBuf, out_rFileVector); + } + } else { + // File + out_rFileVector.increment(); + Platform::FileInfo& rInfo = out_rFileVector.last(); + + if (curPath[0] != '\0') { + dSprintf(scratchBuf, sizeof(scratchBuf), "%s/%s", basePath, curPath); + rInfo.pFullPath = StringTable->insert(scratchBuf); + rInfo.pVirtPath = StringTable->insert(curPath); + } else { + rInfo.pFullPath = StringTable->insert(basePath); + rInfo.pVirtPath = NULL; + } + rInfo.pFileName = StringTable->insert(findData.cFileName); + rInfo.fileSize = findData.nFileSizeLow; + } + + if(FindNextFile(hFind, &findData) == FALSE) { + FindClose(hFind); + hFind = INVALID_HANDLE_VALUE; + } + } + + return true; +} + +//-------------------------------------- + +bool Platform::getFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime) +{ + WIN32_FIND_DATA findData; + HANDLE h = FindFirstFile(filePath, &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) +{ + char pathbuf[1024]; + const char *dir; + pathbuf[0] = 0; + U32 pathLen = 0; + + while((dir = dStrchr(file, '/')) != NULL) + { + dStrncpy(pathbuf + pathLen, file, dir - file); + pathbuf[pathLen + dir-file] = 0; + bool ret = CreateDirectory(pathbuf, NULL); + pathLen += dir - file; + pathbuf[pathLen++] = '/'; + file = dir + 1; + } + return true; +} + +bool Platform::cdFileExists(const char *filePath, const char *volumeName, S32 serialNum) +{ + if (!filePath || !filePath[0]) + return true; + + //first find the CD device... + char fileBuf[256]; + char drivesBuf[256]; + S32 length = GetLogicalDriveStrings(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) && + GetVolumeInformation((const char*)drivePtr, &driveVolume[0], (unsigned long)255, + (unsigned long*)&driveSerial, (unsigned long*)&driveFNLength, + (unsigned long*)&driveFlags, &driveFileSystem[0], (unsigned long)255)) + { +#if defined (DEBUG) || defined (INTERNAL_RELEASE) + 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 (DEBUG) || defined (INTERNAL_RELEASE) + Con::printf("Looking for file: %s on %s", fileBuf, driveVolume); +#endif + WIN32_FIND_DATA findData; + HANDLE h = FindFirstFile(fileBuf, &findData); + if(h != INVALID_HANDLE_VALUE) + { + FindClose(h); + return true; + } + FindClose(h); + } + } + + //check the next drive + drivePtr += dStrlen(drivePtr) + 1; + } + + return false; +} + +//-------------------------------------- +bool Platform::dumpPath(const char *in_pBasePath, Vector& out_rFileVector) +{ + return _recurseDumpPath(in_pBasePath, NULL, out_rFileVector); +} + + +//-------------------------------------- +void Platform::getCurrentDirectory(char *dirBuf, const U32 in_bufferSize) +{ + GetCurrentDirectory(in_bufferSize - 1, dirBuf); + forwardslash(dirBuf); +} + diff --git a/platformWin32/winFont.cc b/platformWin32/winFont.cc new file mode 100644 index 0000000..ad32e17 --- /dev/null +++ b/platformWin32/winFont.cc @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" +#include "dgl/gFont.h" +#include "dgl/gBitmap.h" +#include "Math/mRect.h" +#include "console/console.h" + +static HDC fontHDC = NULL; +static HBITMAP fontBMP = NULL; + +void createFontInit(void); +void createFontShutdown(void); +void CopyCharToBitmap(GBitmap *pDstBMP, HDC hSrcHDC, const RectI &r); + +void createFontInit() +{ + 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; + } + } +} + +GFont *createFont(const char *name, S32 size) +{ + if(!name) + return NULL; + if(size < 1) + return NULL; + + + HFONT hNewFont = CreateFont(size,0,0,0,0,0,0,0,0,0,0,0,0,name); + if(!hNewFont) + return NULL; + + GFont *retFont = new GFont; + static U8 scratchPad[65536]; + + TEXTMETRIC textMetric; + COLORREF backgroundColorRef = RGB( 0, 0, 0); + COLORREF foregroundColorRef = RGB(255, 255, 255); + + SelectObject(fontHDC, fontBMP); + SelectObject(fontHDC, hNewFont); + SetBkColor(fontHDC, backgroundColorRef); + SetTextColor(fontHDC, foregroundColorRef); + GetTextMetrics(fontHDC, &textMetric); + 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; + S32 glyphCount = 0; + + for(S32 i = 32; i < 256; i++) + { + if(GetGlyphOutline( + fontHDC, // handle of device context + i, // 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) + { + glyphCount++; + U32 rowStride = (metrics.gmBlackBoxX + 3) & ~3; // DWORD aligned + U32 size = rowStride * metrics.gmBlackBoxY; + for(U32 j = 0; j < size; j++) + { + U32 pad = U32(scratchPad[j]) << 2; + if(pad > 255) + pad = 255; + scratchPad[j] = pad; + } + S32 inc = metrics.gmCellIncX; + if(inc < 0) + inc = -inc; + retFont->insertBitmap(i, scratchPad, rowStride, metrics.gmBlackBoxX, metrics.gmBlackBoxY, metrics.gmptGlyphOrigin.x, metrics.gmptGlyphOrigin.y, metrics.gmCellIncX); + } + else + { + char b = i; + SIZE size; + GetTextExtentPoint32(fontHDC, &b, 1, &size); + if(size.cx) + retFont->insertBitmap(i, scratchPad, 0, 0, 0, 0, 0, size.cx); + } + } + retFont->pack(textMetric.tmHeight, textMetric.tmAscent); + //clean up local vars + DeleteObject(hNewFont); + + if (!glyphCount) + Con::errorf(ConsoleLogEntry::General,"Error creating font: %s %d",name, size); + + return retFont; +} diff --git a/platformWin32/winGL.cc b/platformWin32/winGL.cc new file mode 100644 index 0000000..8f49501 --- /dev/null +++ b/platformWin32/winGL.cc @@ -0,0 +1,5948 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/* +** QGL_WIN.C +** +** This file implements the operating system binding of GL to QGL function +** pointers. When doing a port of Quake3 you must implement the following +** two functions: +** +** QGL_Init() - loads libraries, assigns function pointers, etc. +** QGL_Shutdown() - unloads libraries, NULLs function pointers +*/ +#include "platformWIN32/platformGL.h" +#include "platformWIN32/platformWin32.h" +#include +#include "console/console.h" +#include "console/consoleTypes.h" +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; + +typedef int ( WINAPI * qwglSwapIntervalEXT_t )(int interval ); +qwglSwapIntervalEXT_t qwglSwapIntervalEXT; +typedef BOOL ( WINAPI * qwglGetDeviceGammaRamp3DFX_t )(HDC, LPVOID ); +qwglGetDeviceGammaRamp3DFX_t qwglGetDeviceGammaRamp3DFX; +typedef BOOL ( WINAPI * qwglSetDeviceGammaRamp3DFX_t )(HDC, LPVOID ); +qwglSetDeviceGammaRamp3DFX_t qwglSetDeviceGammaRamp3DFX; +typedef int ( WINAPI * qwglChoosePixelFormat_t )(HDC, CONST PIXELFORMATDESCRIPTOR *); +qwglChoosePixelFormat_t qwglChoosePixelFormat; +typedef int ( WINAPI * qwglDescribePixelFormat_t) (HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); +qwglDescribePixelFormat_t qwglDescribePixelFormat; +typedef int ( WINAPI * qwglGetPixelFormat_t )(HDC); +qwglGetPixelFormat_t qwglGetPixelFormat; +typedef BOOL ( WINAPI * qwglSetPixelFormat_t )(HDC, int, CONST PIXELFORMATDESCRIPTOR *); +qwglSetPixelFormat_t qwglSetPixelFormat; +typedef BOOL ( WINAPI * qwglSwapBuffers_t )(HDC); +qwglSwapBuffers_t qwglSwapBuffers; +qwglSwapBuffers_t dllSwapBuffers; +typedef BOOL ( WINAPI * qwglCopyContext_t )(HGLRC, HGLRC, UINT); +qwglCopyContext_t qwglCopyContext; +typedef HGLRC ( WINAPI * qwglCreateContext_t )(HDC); +qwglCreateContext_t qwglCreateContext; +typedef HGLRC ( WINAPI * qwglCreateLayerContext_t )(HDC, int); +qwglCreateLayerContext_t qwglCreateLayerContext; +typedef BOOL ( WINAPI * qwglDeleteContext_t )(HGLRC); +qwglDeleteContext_t qwglDeleteContext; +typedef HGLRC ( WINAPI * qwglGetCurrentContext_t )(VOID); +qwglGetCurrentContext_t qwglGetCurrentContext; +typedef HDC ( WINAPI * qwglGetCurrentDC_t )(VOID); +qwglGetCurrentDC_t qwglGetCurrentDC; +typedef PROC ( WINAPI * qwglGetProcAddress_t )(LPCSTR); +qwglGetProcAddress_t qwglGetProcAddress; +typedef BOOL ( WINAPI * qwglMakeCurrent_t )(HDC, HGLRC); +qwglMakeCurrent_t qwglMakeCurrent; +typedef BOOL ( WINAPI * qwglShareLists_t )(HGLRC, HGLRC); +qwglShareLists_t qwglShareLists; +typedef BOOL ( WINAPI * qwglUseFontBitmaps_t )(HDC, DWORD, DWORD, DWORD); +qwglUseFontBitmaps_t qwglUseFontBitmaps; +typedef BOOL ( WINAPI * qwglUseFontOutlines_t )(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); +qwglUseFontOutlines_t qwglUseFontOutlines; +typedef BOOL ( WINAPI * qwglDescribeLayerPlane_t )(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); +qwglDescribeLayerPlane_t qwglDescribeLayerPlane; +typedef int ( WINAPI * qwglSetLayerPaletteEntries_t )(HDC, int, int, int, CONST COLORREF *); +qwglSetLayerPaletteEntries_t qwglSetLayerPaletteEntries; +typedef int ( WINAPI * qwglGetLayerPaletteEntries_t )(HDC, int, int, int, COLORREF *); +qwglGetLayerPaletteEntries_t qwglGetLayerPaletteEntries; +typedef BOOL ( WINAPI * qwglRealizeLayerPalette_t )(HDC, int, BOOL); +qwglRealizeLayerPalette_t qwglRealizeLayerPalette; +typedef BOOL ( WINAPI * qwglSwapLayerBuffers_t )(HDC, UINT); +qwglSwapLayerBuffers_t qwglSwapLayerBuffers; + + +//------------------------------------------------------------------------------ +// GLU Functions +typedef const GLubyte* (APIENTRY * gluErrorString_t) (GLenum errCode); +gluErrorString_t gluErrorString; +typedef const GLubyte* (APIENTRY * gluGetString_t) (GLenum name); +gluGetString_t gluGetString; +typedef void (APIENTRY * gluOrtho2D_t) (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top); +gluOrtho2D_t gluOrtho2D; +typedef void (APIENTRY * gluPerspective_t) (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar); +gluPerspective_t gluPerspective; +typedef void (APIENTRY * gluPickMatrix_t) (GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4]); +gluPickMatrix_t gluPickMatrix; +typedef void (APIENTRY * gluLookAt_t) (GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz); +gluLookAt_t gluLookAt; +typedef int (APIENTRY * gluProject_t) (GLdouble objx, GLdouble objy, GLdouble objz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *winx, GLdouble *winy, GLdouble *winz); +gluProject_t gluProject; +typedef int (APIENTRY * gluUnProject_t) (GLdouble winx, GLdouble winy, GLdouble winz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *objx, GLdouble *objy, GLdouble *objz); +gluUnProject_t gluUnProject; +typedef int (APIENTRY * gluScaleImage_t) (GLenum format, GLint widthin, GLint heightin, GLenum typein, const void *datain, GLint widthout, GLint heightout, GLenum typeout, void *dataout); +gluScaleImage_t gluScaleImage; +typedef int (APIENTRY * gluBuild1DMipmaps_t) (GLenum target, GLint components, GLint width, GLenum format, GLenum type, const void *data); +gluBuild1DMipmaps_t gluBuild1DMipmaps; +typedef int (APIENTRY * gluBuild2DMipmaps_t) (GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data); +gluBuild2DMipmaps_t gluBuild2DMipmaps; + +//------------------------------------------------------------------------------ +// GL Functions +typedef void ( APIENTRY * glAccum_t )(GLenum op, GLfloat value); +glAccum_t glAccum; +typedef void ( APIENTRY * glAlphaFunc_t )(GLenum func, GLclampf ref); +glAlphaFunc_t glAlphaFunc; +typedef GLboolean ( APIENTRY * glAreTexturesResident_t )(GLsizei n, const GLuint *textures, GLboolean *residences); +glAreTexturesResident_t glAreTexturesResident; +typedef void ( APIENTRY * glArrayElement_t )(GLint i); +glArrayElement_t glArrayElement; +typedef void ( APIENTRY * glBegin_t )(GLenum mode); +glBegin_t glBegin; +typedef void ( APIENTRY * glBindTexture_t )(GLenum target, GLuint texture); +glBindTexture_t glBindTexture; +typedef void ( APIENTRY * glBitmap_t )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +glBitmap_t glBitmap; +typedef void ( APIENTRY * glBlendFunc_t )(GLenum sfactor, GLenum dfactor); +glBlendFunc_t glBlendFunc; +typedef void ( APIENTRY * glCallList_t )(GLuint list); +glCallList_t glCallList; +typedef void ( APIENTRY * glCallLists_t )(GLsizei n, GLenum type, const GLvoid *lists); +glCallLists_t glCallLists; +typedef void ( APIENTRY * glClear_t )(GLbitfield mask); +glClear_t glClear; +typedef void ( APIENTRY * glClearAccum_t )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +glClearAccum_t glClearAccum; +typedef void ( APIENTRY * glClearColor_t )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +glClearColor_t glClearColor; +typedef void ( APIENTRY * glClearDepth_t )(GLclampd depth); +glClearDepth_t glClearDepth; +typedef void ( APIENTRY * glClearIndex_t )(GLfloat c); +glClearIndex_t glClearIndex; +typedef void ( APIENTRY * glClearStencil_t )(GLint s); +glClearStencil_t glClearStencil; +typedef void ( APIENTRY * glClipPlane_t )(GLenum plane, const GLdouble *equation); +glClipPlane_t glClipPlane; +typedef void ( APIENTRY * glColor3b_t )(GLbyte red, GLbyte green, GLbyte blue); +glColor3b_t glColor3b; +typedef void ( APIENTRY * glColor3bv_t )(const GLbyte *v); +glColor3bv_t glColor3bv; +typedef void ( APIENTRY * glColor3d_t )(GLdouble red, GLdouble green, GLdouble blue); +glColor3d_t glColor3d; +typedef void ( APIENTRY * glColor3dv_t )(const GLdouble *v); +glColor3dv_t glColor3dv; +typedef void ( APIENTRY * glColor3f_t )(GLfloat red, GLfloat green, GLfloat blue); +glColor3f_t glColor3f; +typedef void ( APIENTRY * glColor3fv_t )(const GLfloat *v); +glColor3fv_t glColor3fv; +typedef void ( APIENTRY * glColor3i_t )(GLint red, GLint green, GLint blue); +glColor3i_t glColor3i; +typedef void ( APIENTRY * glColor3iv_t )(const GLint *v); +glColor3iv_t glColor3iv; +typedef void ( APIENTRY * glColor3s_t )(GLshort red, GLshort green, GLshort blue); +glColor3s_t glColor3s; +typedef void ( APIENTRY * glColor3sv_t )(const GLshort *v); +glColor3sv_t glColor3sv; +typedef void ( APIENTRY * glColor3ub_t )(GLubyte red, GLubyte green, GLubyte blue); +glColor3ub_t glColor3ub; +typedef void ( APIENTRY * glColor3ubv_t )(const GLubyte *v); +glColor3ubv_t glColor3ubv; +typedef void ( APIENTRY * glColor3ui_t )(GLuint red, GLuint green, GLuint blue); +glColor3ui_t glColor3ui; +typedef void ( APIENTRY * glColor3uiv_t )(const GLuint *v); +glColor3uiv_t glColor3uiv; +typedef void ( APIENTRY * glColor3us_t )(GLushort red, GLushort green, GLushort blue); +glColor3us_t glColor3us; +typedef void ( APIENTRY * glColor3usv_t )(const GLushort *v); +glColor3usv_t glColor3usv; +typedef void ( APIENTRY * glColor4b_t )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +glColor4b_t glColor4b; +typedef void ( APIENTRY * glColor4bv_t )(const GLbyte *v); +glColor4bv_t glColor4bv; +typedef void ( APIENTRY * glColor4d_t )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +glColor4d_t glColor4d; +typedef void ( APIENTRY * glColor4dv_t )(const GLdouble *v); +glColor4dv_t glColor4dv; +typedef void ( APIENTRY * glColor4f_t )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +glColor4f_t glColor4f; +typedef void ( APIENTRY * glColor4fv_t )(const GLfloat *v); +glColor4fv_t glColor4fv; +typedef void ( APIENTRY * glColor4i_t )(GLint red, GLint green, GLint blue, GLint alpha); +glColor4i_t glColor4i; +typedef void ( APIENTRY * glColor4iv_t )(const GLint *v); +glColor4iv_t glColor4iv; +typedef void ( APIENTRY * glColor4s_t )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +glColor4s_t glColor4s; +typedef void ( APIENTRY * glColor4sv_t )(const GLshort *v); +glColor4sv_t glColor4sv; +typedef void ( APIENTRY * glColor4ub_t )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +glColor4ub_t glColor4ub; +typedef void ( APIENTRY * glColor4ubv_t )(const GLubyte *v); +glColor4ubv_t glColor4ubv; +typedef void ( APIENTRY * glColor4ui_t )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +glColor4ui_t glColor4ui; +typedef void ( APIENTRY * glColor4uiv_t )(const GLuint *v); +glColor4uiv_t glColor4uiv; +typedef void ( APIENTRY * glColor4us_t )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +glColor4us_t glColor4us; +typedef void ( APIENTRY * glColor4usv_t )(const GLushort *v); +glColor4usv_t glColor4usv; +typedef void ( APIENTRY * glColorMask_t )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +glColorMask_t glColorMask; +typedef void ( APIENTRY * glColorMaterial_t )(GLenum face, GLenum mode); +glColorMaterial_t glColorMaterial; +typedef void ( APIENTRY * glColorPointer_t )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +glColorPointer_t glColorPointer; +typedef void ( APIENTRY * glCopyPixels_t )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +glCopyPixels_t glCopyPixels; +typedef void ( APIENTRY * glCopyTexImage1D_t )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +glCopyTexImage1D_t glCopyTexImage1D; +typedef void ( APIENTRY * glCopyTexImage2D_t )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +glCopyTexImage2D_t glCopyTexImage2D; +typedef void ( APIENTRY * glCopyTexSubImage1D_t )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +glCopyTexSubImage1D_t glCopyTexSubImage1D; +typedef void ( APIENTRY * glCopyTexSubImage2D_t )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +glCopyTexSubImage2D_t glCopyTexSubImage2D; +typedef void ( APIENTRY * glCullFace_t )(GLenum mode); +glCullFace_t glCullFace; +typedef void ( APIENTRY * glDeleteLists_t )(GLuint list, GLsizei range); +glDeleteLists_t glDeleteLists; +typedef void ( APIENTRY * glDeleteTextures_t )(GLsizei n, const GLuint *textures); +glDeleteTextures_t glDeleteTextures; +typedef void ( APIENTRY * glDepthFunc_t )(GLenum func); +glDepthFunc_t glDepthFunc; +typedef void ( APIENTRY * glDepthMask_t )(GLboolean flag); +glDepthMask_t glDepthMask; +typedef void ( APIENTRY * glDepthRange_t )(GLclampd zNear, GLclampd zFar); +glDepthRange_t glDepthRange; +typedef void ( APIENTRY * glDisable_t )(GLenum cap); +glDisable_t glDisable; +typedef void ( APIENTRY * glDisableClientState_t )(GLenum array); +glDisableClientState_t glDisableClientState; +typedef void ( APIENTRY * glDrawArrays_t )(GLenum mode, GLint first, GLsizei count); +glDrawArrays_t glDrawArrays; +typedef void ( APIENTRY * glDrawBuffer_t )(GLenum mode); +glDrawBuffer_t glDrawBuffer; +typedef void ( APIENTRY * glDrawElements_t )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +glDrawElements_t glDrawElements; +typedef void ( APIENTRY * glDrawPixels_t )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +glDrawPixels_t glDrawPixels; +typedef void ( APIENTRY * glEdgeFlag_t )(GLboolean flag); +glEdgeFlag_t glEdgeFlag; +typedef void ( APIENTRY * glEdgeFlagPointer_t )(GLsizei stride, const GLvoid *pointer); +glEdgeFlagPointer_t glEdgeFlagPointer; +typedef void ( APIENTRY * glEdgeFlagv_t )(const GLboolean *flag); +glEdgeFlagv_t glEdgeFlagv; +typedef void ( APIENTRY * glEnable_t )(GLenum cap); +glEnable_t glEnable; +typedef void ( APIENTRY * glEnableClientState_t )(GLenum array); +glEnableClientState_t glEnableClientState; +typedef void ( APIENTRY * glEnd_t )(void); +glEnd_t glEnd; +typedef void ( APIENTRY * glEndList_t )(void); +glEndList_t glEndList; +typedef void ( APIENTRY * glEvalCoord1d_t )(GLdouble u); +glEvalCoord1d_t glEvalCoord1d; +typedef void ( APIENTRY * glEvalCoord1dv_t )(const GLdouble *u); +glEvalCoord1dv_t glEvalCoord1dv; +typedef void ( APIENTRY * glEvalCoord1f_t )(GLfloat u); +glEvalCoord1f_t glEvalCoord1f; +typedef void ( APIENTRY * glEvalCoord1fv_t )(const GLfloat *u); +glEvalCoord1fv_t glEvalCoord1fv; +typedef void ( APIENTRY * glEvalCoord2d_t )(GLdouble u, GLdouble v); +glEvalCoord2d_t glEvalCoord2d; +typedef void ( APIENTRY * glEvalCoord2dv_t )(const GLdouble *u); +glEvalCoord2dv_t glEvalCoord2dv; +typedef void ( APIENTRY * glEvalCoord2f_t )(GLfloat u, GLfloat v); +glEvalCoord2f_t glEvalCoord2f; +typedef void ( APIENTRY * glEvalCoord2fv_t )(const GLfloat *u); +glEvalCoord2fv_t glEvalCoord2fv; +typedef void ( APIENTRY * glEvalMesh1_t )(GLenum mode, GLint i1, GLint i2); +glEvalMesh1_t glEvalMesh1; +typedef void ( APIENTRY * glEvalMesh2_t )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +glEvalMesh2_t glEvalMesh2; +typedef void ( APIENTRY * glEvalPoint1_t )(GLint i); +glEvalPoint1_t glEvalPoint1; +typedef void ( APIENTRY * glEvalPoint2_t )(GLint i, GLint j); +glEvalPoint2_t glEvalPoint2; +typedef void ( APIENTRY * glFeedbackBuffer_t )(GLsizei size, GLenum type, GLfloat *buffer); +glFeedbackBuffer_t glFeedbackBuffer; +typedef void ( APIENTRY * glFinish_t )(void); +glFinish_t glFinish; +typedef void ( APIENTRY * glFlush_t )(void); +glFlush_t glFlush; +typedef void ( APIENTRY * glFogf_t )(GLenum pname, GLfloat param); +glFogf_t glFogf; +typedef void ( APIENTRY * glFogfv_t )(GLenum pname, const GLfloat *params); +glFogfv_t glFogfv; +typedef void ( APIENTRY * glFogi_t )(GLenum pname, GLint param); +glFogi_t glFogi; +typedef void ( APIENTRY * glFogiv_t )(GLenum pname, const GLint *params); +glFogiv_t glFogiv; +typedef void ( APIENTRY * glFrontFace_t )(GLenum mode); +glFrontFace_t glFrontFace; +typedef void ( APIENTRY * glFrustum_t )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +glFrustum_t glFrustum; +typedef GLuint ( APIENTRY * glGenLists_t )(GLsizei range); +glGenLists_t glGenLists; +typedef void ( APIENTRY * glGenTextures_t )(GLsizei n, GLuint *textures); +glGenTextures_t glGenTextures; +typedef void ( APIENTRY * glGetBooleanv_t )(GLenum pname, GLboolean *params); +glGetBooleanv_t glGetBooleanv; +typedef void ( APIENTRY * glGetClipPlane_t )(GLenum plane, GLdouble *equation); +glGetClipPlane_t glGetClipPlane; +typedef void ( APIENTRY * glGetDoublev_t )(GLenum pname, GLdouble *params); +glGetDoublev_t glGetDoublev; +typedef GLenum ( APIENTRY * glGetError_t )(void); +glGetError_t glGetError; +typedef void ( APIENTRY * glGetFloatv_t )(GLenum pname, GLfloat *params); +glGetFloatv_t glGetFloatv; +typedef void ( APIENTRY * glGetIntegerv_t )(GLenum pname, GLint *params); +glGetIntegerv_t glGetIntegerv; +typedef void ( APIENTRY * glGetLightfv_t )(GLenum light, GLenum pname, GLfloat *params); +glGetLightfv_t glGetLightfv; +typedef void ( APIENTRY * glGetLightiv_t )(GLenum light, GLenum pname, GLint *params); +glGetLightiv_t glGetLightiv; +typedef void ( APIENTRY * glGetMapdv_t )(GLenum target, GLenum query, GLdouble *v); +glGetMapdv_t glGetMapdv; +typedef void ( APIENTRY * glGetMapfv_t )(GLenum target, GLenum query, GLfloat *v); +glGetMapfv_t glGetMapfv; +typedef void ( APIENTRY * glGetMapiv_t )(GLenum target, GLenum query, GLint *v); +glGetMapiv_t glGetMapiv; +typedef void ( APIENTRY * glGetMaterialfv_t )(GLenum face, GLenum pname, GLfloat *params); +glGetMaterialfv_t glGetMaterialfv; +typedef void ( APIENTRY * glGetMaterialiv_t )(GLenum face, GLenum pname, GLint *params); +glGetMaterialiv_t glGetMaterialiv; +typedef void ( APIENTRY * glGetPixelMapfv_t )(GLenum map, GLfloat *values); +glGetPixelMapfv_t glGetPixelMapfv; +typedef void ( APIENTRY * glGetPixelMapuiv_t )(GLenum map, GLuint *values); +glGetPixelMapuiv_t glGetPixelMapuiv; +typedef void ( APIENTRY * glGetPixelMapusv_t )(GLenum map, GLushort *values); +glGetPixelMapusv_t glGetPixelMapusv; +typedef void ( APIENTRY * glGetPointerv_t )(GLenum pname, GLvoid* *params); +glGetPointerv_t glGetPointerv; +typedef void ( APIENTRY * glGetPolygonStipple_t )(GLubyte *mask); +glGetPolygonStipple_t glGetPolygonStipple; +typedef const GLubyte * ( APIENTRY * glGetString_t )(GLenum name); +glGetString_t glGetString; +typedef void ( APIENTRY * glGetTexEnvfv_t )(GLenum target, GLenum pname, GLfloat *params); +glGetTexEnvfv_t glGetTexEnvfv; +typedef void ( APIENTRY * glGetTexEnviv_t )(GLenum target, GLenum pname, GLint *params); +glGetTexEnviv_t glGetTexEnviv; +typedef void ( APIENTRY * glGetTexGendv_t )(GLenum coord, GLenum pname, GLdouble *params); +glGetTexGendv_t glGetTexGendv; +typedef void ( APIENTRY * glGetTexGenfv_t )(GLenum coord, GLenum pname, GLfloat *params); +glGetTexGenfv_t glGetTexGenfv; +typedef void ( APIENTRY * glGetTexGeniv_t )(GLenum coord, GLenum pname, GLint *params); +glGetTexGeniv_t glGetTexGeniv; +typedef void ( APIENTRY * glGetTexImage_t )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +glGetTexImage_t glGetTexImage; +typedef void ( APIENTRY * glGetTexLevelParameterfv_t )(GLenum target, GLint level, GLenum pname, GLfloat *params); +glGetTexLevelParameterfv_t glGetTexLevelParameterfv; +typedef void ( APIENTRY * glGetTexLevelParameteriv_t )(GLenum target, GLint level, GLenum pname, GLint *params); +glGetTexLevelParameteriv_t glGetTexLevelParameteriv; +typedef void ( APIENTRY * glGetTexParameterfv_t )(GLenum target, GLenum pname, GLfloat *params); +glGetTexParameterfv_t glGetTexParameterfv; +typedef void ( APIENTRY * glGetTexParameteriv_t )(GLenum target, GLenum pname, GLint *params); +glGetTexParameteriv_t glGetTexParameteriv; +typedef void ( APIENTRY * glHint_t )(GLenum target, GLenum mode); +glHint_t glHint; +typedef void ( APIENTRY * glIndexMask_t )(GLuint mask); +glIndexMask_t glIndexMask; +typedef void ( APIENTRY * glIndexPointer_t )(GLenum type, GLsizei stride, const GLvoid *pointer); +glIndexPointer_t glIndexPointer; +typedef void ( APIENTRY * glIndexd_t )(GLdouble c); +glIndexd_t glIndexd; +typedef void ( APIENTRY * glIndexdv_t )(const GLdouble *c); +glIndexdv_t glIndexdv; +typedef void ( APIENTRY * glIndexf_t )(GLfloat c); +glIndexf_t glIndexf; +typedef void ( APIENTRY * glIndexfv_t )(const GLfloat *c); +glIndexfv_t glIndexfv; +typedef void ( APIENTRY * glIndexi_t )(GLint c); +glIndexi_t glIndexi; +typedef void ( APIENTRY * glIndexiv_t )(const GLint *c); +glIndexiv_t glIndexiv; +typedef void ( APIENTRY * glIndexs_t )(GLshort c); +glIndexs_t glIndexs; +typedef void ( APIENTRY * glIndexsv_t )(const GLshort *c); +glIndexsv_t glIndexsv; +typedef void ( APIENTRY * glIndexub_t )(GLubyte c); +glIndexub_t glIndexub; +typedef void ( APIENTRY * glIndexubv_t )(const GLubyte *c); +glIndexubv_t glIndexubv; +typedef void ( APIENTRY * glInitNames_t )(void); +glInitNames_t glInitNames; +typedef void ( APIENTRY * glInterleavedArrays_t )(GLenum format, GLsizei stride, const GLvoid *pointer); +glInterleavedArrays_t glInterleavedArrays; +typedef GLboolean ( APIENTRY * glIsEnabled_t )(GLenum cap); +glIsEnabled_t glIsEnabled; +typedef GLboolean ( APIENTRY * glIsList_t )(GLuint list); +glIsList_t glIsList; +typedef GLboolean ( APIENTRY * glIsTexture_t )(GLuint texture); +glIsTexture_t glIsTexture; +typedef void ( APIENTRY * glLightModelf_t )(GLenum pname, GLfloat param); +glLightModelf_t glLightModelf; +typedef void ( APIENTRY * glLightModelfv_t )(GLenum pname, const GLfloat *params); +glLightModelfv_t glLightModelfv; +typedef void ( APIENTRY * glLightModeli_t )(GLenum pname, GLint param); +glLightModeli_t glLightModeli; +typedef void ( APIENTRY * glLightModeliv_t )(GLenum pname, const GLint *params); +glLightModeliv_t glLightModeliv; +typedef void ( APIENTRY * glLightf_t )(GLenum light, GLenum pname, GLfloat param); +glLightf_t glLightf; +typedef void ( APIENTRY * glLightfv_t )(GLenum light, GLenum pname, const GLfloat *params); +glLightfv_t glLightfv; +typedef void ( APIENTRY * glLighti_t )(GLenum light, GLenum pname, GLint param); +glLighti_t glLighti; +typedef void ( APIENTRY * glLightiv_t )(GLenum light, GLenum pname, const GLint *params); +glLightiv_t glLightiv; +typedef void ( APIENTRY * glLineStipple_t )(GLint factor, GLushort pattern); +glLineStipple_t glLineStipple; +typedef void ( APIENTRY * glLineWidth_t )(GLfloat width); +glLineWidth_t glLineWidth; +typedef void ( APIENTRY * glListBase_t )(GLuint base); +glListBase_t glListBase; +typedef void ( APIENTRY * glLoadIdentity_t )(void); +glLoadIdentity_t glLoadIdentity; +typedef void ( APIENTRY * glLoadMatrixd_t )(const GLdouble *m); +glLoadMatrixd_t glLoadMatrixd; +typedef void ( APIENTRY * glLoadMatrixf_t )(const GLfloat *m); +glLoadMatrixf_t glLoadMatrixf; +typedef void ( APIENTRY * glLoadName_t )(GLuint name); +glLoadName_t glLoadName; +typedef void ( APIENTRY * glLogicOp_t )(GLenum opcode); +glLogicOp_t glLogicOp; +typedef void ( APIENTRY * glMap1d_t )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +glMap1d_t glMap1d; +typedef void ( APIENTRY * glMap1f_t )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +glMap1f_t glMap1f; +typedef void ( APIENTRY * glMap2d_t )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +glMap2d_t glMap2d; +typedef void ( APIENTRY * glMap2f_t )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +glMap2f_t glMap2f; +typedef void ( APIENTRY * glMapGrid1d_t )(GLint un, GLdouble u1, GLdouble u2); +glMapGrid1d_t glMapGrid1d; +typedef void ( APIENTRY * glMapGrid1f_t )(GLint un, GLfloat u1, GLfloat u2); +glMapGrid1f_t glMapGrid1f; +typedef void ( APIENTRY * glMapGrid2d_t )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +glMapGrid2d_t glMapGrid2d; +typedef void ( APIENTRY * glMapGrid2f_t )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +glMapGrid2f_t glMapGrid2f; +typedef void ( APIENTRY * glMaterialf_t )(GLenum face, GLenum pname, GLfloat param); +glMaterialf_t glMaterialf; +typedef void ( APIENTRY * glMaterialfv_t )(GLenum face, GLenum pname, const GLfloat *params); +glMaterialfv_t glMaterialfv; +typedef void ( APIENTRY * glMateriali_t )(GLenum face, GLenum pname, GLint param); +glMateriali_t glMateriali; +typedef void ( APIENTRY * glMaterialiv_t )(GLenum face, GLenum pname, const GLint *params); +glMaterialiv_t glMaterialiv; +typedef void ( APIENTRY * glMatrixMode_t )(GLenum mode); +glMatrixMode_t glMatrixMode; +typedef void ( APIENTRY * glMultMatrixd_t )(const GLdouble *m); +glMultMatrixd_t glMultMatrixd; +typedef void ( APIENTRY * glMultMatrixf_t )(const GLfloat *m); +glMultMatrixf_t glMultMatrixf; +typedef void ( APIENTRY * glNewList_t )(GLuint list, GLenum mode); +glNewList_t glNewList; +typedef void ( APIENTRY * glNormal3b_t )(GLbyte nx, GLbyte ny, GLbyte nz); +glNormal3b_t glNormal3b; +typedef void ( APIENTRY * glNormal3bv_t )(const GLbyte *v); +glNormal3bv_t glNormal3bv; +typedef void ( APIENTRY * glNormal3d_t )(GLdouble nx, GLdouble ny, GLdouble nz); +glNormal3d_t glNormal3d; +typedef void ( APIENTRY * glNormal3dv_t )(const GLdouble *v); +glNormal3dv_t glNormal3dv; +typedef void ( APIENTRY * glNormal3f_t )(GLfloat nx, GLfloat ny, GLfloat nz); +glNormal3f_t glNormal3f; +typedef void ( APIENTRY * glNormal3fv_t )(const GLfloat *v); +glNormal3fv_t glNormal3fv; +typedef void ( APIENTRY * glNormal3i_t )(GLint nx, GLint ny, GLint nz); +glNormal3i_t glNormal3i; +typedef void ( APIENTRY * glNormal3iv_t )(const GLint *v); +glNormal3iv_t glNormal3iv; +typedef void ( APIENTRY * glNormal3s_t )(GLshort nx, GLshort ny, GLshort nz); +glNormal3s_t glNormal3s; +typedef void ( APIENTRY * glNormal3sv_t )(const GLshort *v); +glNormal3sv_t glNormal3sv; +typedef void ( APIENTRY * glNormalPointer_t )(GLenum type, GLsizei stride, const GLvoid *pointer); +glNormalPointer_t glNormalPointer; +typedef void ( APIENTRY * glOrtho_t )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +glOrtho_t glOrtho; +typedef void ( APIENTRY * glPassThrough_t )(GLfloat token); +glPassThrough_t glPassThrough; +typedef void ( APIENTRY * glPixelMapfv_t )(GLenum map, GLsizei mapsize, const GLfloat *values); +glPixelMapfv_t glPixelMapfv; +typedef void ( APIENTRY * glPixelMapuiv_t )(GLenum map, GLsizei mapsize, const GLuint *values); +glPixelMapuiv_t glPixelMapuiv; +typedef void ( APIENTRY * glPixelMapusv_t )(GLenum map, GLsizei mapsize, const GLushort *values); +glPixelMapusv_t glPixelMapusv; +typedef void ( APIENTRY * glPixelStoref_t )(GLenum pname, GLfloat param); +glPixelStoref_t glPixelStoref; +typedef void ( APIENTRY * glPixelStorei_t )(GLenum pname, GLint param); +glPixelStorei_t glPixelStorei; +typedef void ( APIENTRY * glPixelTransferf_t )(GLenum pname, GLfloat param); +glPixelTransferf_t glPixelTransferf; +typedef void ( APIENTRY * glPixelTransferi_t )(GLenum pname, GLint param); +glPixelTransferi_t glPixelTransferi; +typedef void ( APIENTRY * glPixelZoom_t )(GLfloat xfactor, GLfloat yfactor); +glPixelZoom_t glPixelZoom; +typedef void ( APIENTRY * glPointSize_t )(GLfloat size); +glPointSize_t glPointSize; +typedef void ( APIENTRY * glPolygonMode_t )(GLenum face, GLenum mode); +glPolygonMode_t glPolygonMode; +typedef void ( APIENTRY * glPolygonOffset_t )(GLfloat factor, GLfloat units); +glPolygonOffset_t glPolygonOffset; +typedef void ( APIENTRY * glPolygonStipple_t )(const GLubyte *mask); +glPolygonStipple_t glPolygonStipple; +typedef void ( APIENTRY * glPopAttrib_t )(void); +glPopAttrib_t glPopAttrib; +typedef void ( APIENTRY * glPopClientAttrib_t )(void); +glPopClientAttrib_t glPopClientAttrib; +typedef void ( APIENTRY * glPopMatrix_t )(void); +glPopMatrix_t glPopMatrix; +typedef void ( APIENTRY * glPopName_t )(void); +glPopName_t glPopName; +typedef void ( APIENTRY * glPrioritizeTextures_t )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +glPrioritizeTextures_t glPrioritizeTextures; +typedef void ( APIENTRY * glPushAttrib_t )(GLbitfield mask); +glPushAttrib_t glPushAttrib; +typedef void ( APIENTRY * glPushClientAttrib_t )(GLbitfield mask); +glPushClientAttrib_t glPushClientAttrib; +typedef void ( APIENTRY * glPushMatrix_t )(void); +glPushMatrix_t glPushMatrix; +typedef void ( APIENTRY * glPushName_t )(GLuint name); +glPushName_t glPushName; +typedef void ( APIENTRY * glRasterPos2d_t )(GLdouble x, GLdouble y); +glRasterPos2d_t glRasterPos2d; +typedef void ( APIENTRY * glRasterPos2dv_t )(const GLdouble *v); +glRasterPos2dv_t glRasterPos2dv; +typedef void ( APIENTRY * glRasterPos2f_t )(GLfloat x, GLfloat y); +glRasterPos2f_t glRasterPos2f; +typedef void ( APIENTRY * glRasterPos2fv_t )(const GLfloat *v); +glRasterPos2fv_t glRasterPos2fv; +typedef void ( APIENTRY * glRasterPos2i_t )(GLint x, GLint y); +glRasterPos2i_t glRasterPos2i; +typedef void ( APIENTRY * glRasterPos2iv_t )(const GLint *v); +glRasterPos2iv_t glRasterPos2iv; +typedef void ( APIENTRY * glRasterPos2s_t )(GLshort x, GLshort y); +glRasterPos2s_t glRasterPos2s; +typedef void ( APIENTRY * glRasterPos2sv_t )(const GLshort *v); +glRasterPos2sv_t glRasterPos2sv; +typedef void ( APIENTRY * glRasterPos3d_t )(GLdouble x, GLdouble y, GLdouble z); +glRasterPos3d_t glRasterPos3d; +typedef void ( APIENTRY * glRasterPos3dv_t )(const GLdouble *v); +glRasterPos3dv_t glRasterPos3dv; +typedef void ( APIENTRY * glRasterPos3f_t )(GLfloat x, GLfloat y, GLfloat z); +glRasterPos3f_t glRasterPos3f; +typedef void ( APIENTRY * glRasterPos3fv_t )(const GLfloat *v); +glRasterPos3fv_t glRasterPos3fv; +typedef void ( APIENTRY * glRasterPos3i_t )(GLint x, GLint y, GLint z); +glRasterPos3i_t glRasterPos3i; +typedef void ( APIENTRY * glRasterPos3iv_t )(const GLint *v); +glRasterPos3iv_t glRasterPos3iv; +typedef void ( APIENTRY * glRasterPos3s_t )(GLshort x, GLshort y, GLshort z); +glRasterPos3s_t glRasterPos3s; +typedef void ( APIENTRY * glRasterPos3sv_t )(const GLshort *v); +glRasterPos3sv_t glRasterPos3sv; +typedef void ( APIENTRY * glRasterPos4d_t )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +glRasterPos4d_t glRasterPos4d; +typedef void ( APIENTRY * glRasterPos4dv_t )(const GLdouble *v); +glRasterPos4dv_t glRasterPos4dv; +typedef void ( APIENTRY * glRasterPos4f_t )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +glRasterPos4f_t glRasterPos4f; +typedef void ( APIENTRY * glRasterPos4fv_t )(const GLfloat *v); +glRasterPos4fv_t glRasterPos4fv; +typedef void ( APIENTRY * glRasterPos4i_t )(GLint x, GLint y, GLint z, GLint w); +glRasterPos4i_t glRasterPos4i; +typedef void ( APIENTRY * glRasterPos4iv_t )(const GLint *v); +glRasterPos4iv_t glRasterPos4iv; +typedef void ( APIENTRY * glRasterPos4s_t )(GLshort x, GLshort y, GLshort z, GLshort w); +glRasterPos4s_t glRasterPos4s; +typedef void ( APIENTRY * glRasterPos4sv_t )(const GLshort *v); +glRasterPos4sv_t glRasterPos4sv; +typedef void ( APIENTRY * glReadBuffer_t )(GLenum mode); +glReadBuffer_t glReadBuffer; +typedef void ( APIENTRY * glReadPixels_t )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +glReadPixels_t glReadPixels; +typedef void ( APIENTRY * glRectd_t )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +glRectd_t glRectd; +typedef void ( APIENTRY * glRectdv_t )(const GLdouble *v1, const GLdouble *v2); +glRectdv_t glRectdv; +typedef void ( APIENTRY * glRectf_t )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +glRectf_t glRectf; +typedef void ( APIENTRY * glRectfv_t )(const GLfloat *v1, const GLfloat *v2); +glRectfv_t glRectfv; +typedef void ( APIENTRY * glRecti_t )(GLint x1, GLint y1, GLint x2, GLint y2); +glRecti_t glRecti; +typedef void ( APIENTRY * glRectiv_t )(const GLint *v1, const GLint *v2); +glRectiv_t glRectiv; +typedef void ( APIENTRY * glRects_t )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +glRects_t glRects; +typedef void ( APIENTRY * glRectsv_t )(const GLshort *v1, const GLshort *v2); +glRectsv_t glRectsv; +typedef GLint ( APIENTRY * glRenderMode_t )(GLenum mode); +glRenderMode_t glRenderMode; +typedef void ( APIENTRY * glRotated_t )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +glRotated_t glRotated; +typedef void ( APIENTRY * glRotatef_t )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +glRotatef_t glRotatef; +typedef void ( APIENTRY * glScaled_t )(GLdouble x, GLdouble y, GLdouble z); +glScaled_t glScaled; +typedef void ( APIENTRY * glScalef_t )(GLfloat x, GLfloat y, GLfloat z); +glScalef_t glScalef; +typedef void ( APIENTRY * glScissor_t )(GLint x, GLint y, GLsizei width, GLsizei height); +glScissor_t glScissor; +typedef void ( APIENTRY * glSelectBuffer_t )(GLsizei size, GLuint *buffer); +glSelectBuffer_t glSelectBuffer; +typedef void ( APIENTRY * glShadeModel_t )(GLenum mode); +glShadeModel_t glShadeModel; +typedef void ( APIENTRY * glStencilFunc_t )(GLenum func, GLint ref, GLuint mask); +glStencilFunc_t glStencilFunc; +typedef void ( APIENTRY * glStencilMask_t )(GLuint mask); +glStencilMask_t glStencilMask; +typedef void ( APIENTRY * glStencilOp_t )(GLenum fail, GLenum zfail, GLenum zpass); +glStencilOp_t glStencilOp; +typedef void ( APIENTRY * glTexCoord1d_t )(GLdouble s); +glTexCoord1d_t glTexCoord1d; +typedef void ( APIENTRY * glTexCoord1dv_t )(const GLdouble *v); +glTexCoord1dv_t glTexCoord1dv; +typedef void ( APIENTRY * glTexCoord1f_t )(GLfloat s); +glTexCoord1f_t glTexCoord1f; +typedef void ( APIENTRY * glTexCoord1fv_t )(const GLfloat *v); +glTexCoord1fv_t glTexCoord1fv; +typedef void ( APIENTRY * glTexCoord1i_t )(GLint s); +glTexCoord1i_t glTexCoord1i; +typedef void ( APIENTRY * glTexCoord1iv_t )(const GLint *v); +glTexCoord1iv_t glTexCoord1iv; +typedef void ( APIENTRY * glTexCoord1s_t )(GLshort s); +glTexCoord1s_t glTexCoord1s; +typedef void ( APIENTRY * glTexCoord1sv_t )(const GLshort *v); +glTexCoord1sv_t glTexCoord1sv; +typedef void ( APIENTRY * glTexCoord2d_t )(GLdouble s, GLdouble t); +glTexCoord2d_t glTexCoord2d; +typedef void ( APIENTRY * glTexCoord2dv_t )(const GLdouble *v); +glTexCoord2dv_t glTexCoord2dv; +typedef void ( APIENTRY * glTexCoord2f_t )(GLfloat s, GLfloat t); +glTexCoord2f_t glTexCoord2f; +typedef void ( APIENTRY * glTexCoord2fv_t )(const GLfloat *v); +glTexCoord2fv_t glTexCoord2fv; +typedef void ( APIENTRY * glTexCoord2i_t )(GLint s, GLint t); +glTexCoord2i_t glTexCoord2i; +typedef void ( APIENTRY * glTexCoord2iv_t )(const GLint *v); +glTexCoord2iv_t glTexCoord2iv; +typedef void ( APIENTRY * glTexCoord2s_t )(GLshort s, GLshort t); +glTexCoord2s_t glTexCoord2s; +typedef void ( APIENTRY * glTexCoord2sv_t )(const GLshort *v); +glTexCoord2sv_t glTexCoord2sv; +typedef void ( APIENTRY * glTexCoord3d_t )(GLdouble s, GLdouble t, GLdouble r); +glTexCoord3d_t glTexCoord3d; +typedef void ( APIENTRY * glTexCoord3dv_t )(const GLdouble *v); +glTexCoord3dv_t glTexCoord3dv; +typedef void ( APIENTRY * glTexCoord3f_t )(GLfloat s, GLfloat t, GLfloat r); +glTexCoord3f_t glTexCoord3f; +typedef void ( APIENTRY * glTexCoord3fv_t )(const GLfloat *v); +glTexCoord3fv_t glTexCoord3fv; +typedef void ( APIENTRY * glTexCoord3i_t )(GLint s, GLint t, GLint r); +glTexCoord3i_t glTexCoord3i; +typedef void ( APIENTRY * glTexCoord3iv_t )(const GLint *v); +glTexCoord3iv_t glTexCoord3iv; +typedef void ( APIENTRY * glTexCoord3s_t )(GLshort s, GLshort t, GLshort r); +glTexCoord3s_t glTexCoord3s; +typedef void ( APIENTRY * glTexCoord3sv_t )(const GLshort *v); +glTexCoord3sv_t glTexCoord3sv; +typedef void ( APIENTRY * glTexCoord4d_t )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +glTexCoord4d_t glTexCoord4d; +typedef void ( APIENTRY * glTexCoord4dv_t )(const GLdouble *v); +glTexCoord4dv_t glTexCoord4dv; +typedef void ( APIENTRY * glTexCoord4f_t )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +glTexCoord4f_t glTexCoord4f; +typedef void ( APIENTRY * glTexCoord4fv_t )(const GLfloat *v); +glTexCoord4fv_t glTexCoord4fv; +typedef void ( APIENTRY * glTexCoord4i_t )(GLint s, GLint t, GLint r, GLint q); +glTexCoord4i_t glTexCoord4i; +typedef void ( APIENTRY * glTexCoord4iv_t )(const GLint *v); +glTexCoord4iv_t glTexCoord4iv; +typedef void ( APIENTRY * glTexCoord4s_t )(GLshort s, GLshort t, GLshort r, GLshort q); +glTexCoord4s_t glTexCoord4s; +typedef void ( APIENTRY * glTexCoord4sv_t )(const GLshort *v); +glTexCoord4sv_t glTexCoord4sv; +typedef void ( APIENTRY * glTexCoordPointer_t )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +glTexCoordPointer_t glTexCoordPointer; +typedef void ( APIENTRY * glTexEnvf_t )(GLenum target, GLenum pname, GLfloat param); +glTexEnvf_t glTexEnvf; +typedef void ( APIENTRY * glTexEnvfv_t )(GLenum target, GLenum pname, const GLfloat *params); +glTexEnvfv_t glTexEnvfv; +typedef void ( APIENTRY * glTexEnvi_t )(GLenum target, GLenum pname, GLint param); +glTexEnvi_t glTexEnvi; +typedef void ( APIENTRY * glTexEnviv_t )(GLenum target, GLenum pname, const GLint *params); +glTexEnviv_t glTexEnviv; +typedef void ( APIENTRY * glTexGend_t )(GLenum coord, GLenum pname, GLdouble param); +glTexGend_t glTexGend; +typedef void ( APIENTRY * glTexGendv_t )(GLenum coord, GLenum pname, const GLdouble *params); +glTexGendv_t glTexGendv; +typedef void ( APIENTRY * glTexGenf_t )(GLenum coord, GLenum pname, GLfloat param); +glTexGenf_t glTexGenf; +typedef void ( APIENTRY * glTexGenfv_t )(GLenum coord, GLenum pname, const GLfloat *params); +glTexGenfv_t glTexGenfv; +typedef void ( APIENTRY * glTexGeni_t )(GLenum coord, GLenum pname, GLint param); +glTexGeni_t glTexGeni; +typedef void ( APIENTRY * glTexGeniv_t )(GLenum coord, GLenum pname, const GLint *params); +glTexGeniv_t glTexGeniv; +typedef void ( APIENTRY * glTexImage1D_t )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +glTexImage1D_t glTexImage1D; +typedef void ( APIENTRY * glTexImage2D_t )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +glTexImage2D_t glTexImage2D; +typedef void ( APIENTRY * glTexParameterf_t )(GLenum target, GLenum pname, GLfloat param); +glTexParameterf_t glTexParameterf; +typedef void ( APIENTRY * glTexParameterfv_t )(GLenum target, GLenum pname, const GLfloat *params); +glTexParameterfv_t glTexParameterfv; +typedef void ( APIENTRY * glTexParameteri_t )(GLenum target, GLenum pname, GLint param); +glTexParameteri_t glTexParameteri; +typedef void ( APIENTRY * glTexParameteriv_t )(GLenum target, GLenum pname, const GLint *params); +glTexParameteriv_t glTexParameteriv; +typedef void ( APIENTRY * glTexSubImage1D_t )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +glTexSubImage1D_t glTexSubImage1D; +typedef void ( APIENTRY * glTexSubImage2D_t )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +glTexSubImage2D_t glTexSubImage2D; +typedef void ( APIENTRY * glTranslated_t )(GLdouble x, GLdouble y, GLdouble z); +glTranslated_t glTranslated; +typedef void ( APIENTRY * glTranslatef_t )(GLfloat x, GLfloat y, GLfloat z); +glTranslatef_t glTranslatef; +typedef void ( APIENTRY * glVertex2d_t )(GLdouble x, GLdouble y); +glVertex2d_t glVertex2d; +typedef void ( APIENTRY * glVertex2dv_t )(const GLdouble *v); +glVertex2dv_t glVertex2dv; +typedef void ( APIENTRY * glVertex2f_t )(GLfloat x, GLfloat y); +glVertex2f_t glVertex2f; +typedef void ( APIENTRY * glVertex2fv_t )(const GLfloat *v); +glVertex2fv_t glVertex2fv; +typedef void ( APIENTRY * glVertex2i_t )(GLint x, GLint y); +glVertex2i_t glVertex2i; +typedef void ( APIENTRY * glVertex2iv_t )(const GLint *v); +glVertex2iv_t glVertex2iv; +typedef void ( APIENTRY * glVertex2s_t )(GLshort x, GLshort y); +glVertex2s_t glVertex2s; +typedef void ( APIENTRY * glVertex2sv_t )(const GLshort *v); +glVertex2sv_t glVertex2sv; +typedef void ( APIENTRY * glVertex3d_t )(GLdouble x, GLdouble y, GLdouble z); +glVertex3d_t glVertex3d; +typedef void ( APIENTRY * glVertex3dv_t )(const GLdouble *v); +glVertex3dv_t glVertex3dv; +typedef void ( APIENTRY * glVertex3f_t )(GLfloat x, GLfloat y, GLfloat z); +glVertex3f_t glVertex3f; +typedef void ( APIENTRY * glVertex3fv_t )(const GLfloat *v); +glVertex3fv_t glVertex3fv; +typedef void ( APIENTRY * glVertex3i_t )(GLint x, GLint y, GLint z); +glVertex3i_t glVertex3i; +typedef void ( APIENTRY * glVertex3iv_t )(const GLint *v); +glVertex3iv_t glVertex3iv; +typedef void ( APIENTRY * glVertex3s_t )(GLshort x, GLshort y, GLshort z); +glVertex3s_t glVertex3s; +typedef void ( APIENTRY * glVertex3sv_t )(const GLshort *v); +glVertex3sv_t glVertex3sv; +typedef void ( APIENTRY * glVertex4d_t )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +glVertex4d_t glVertex4d; +typedef void ( APIENTRY * glVertex4dv_t )(const GLdouble *v); +glVertex4dv_t glVertex4dv; +typedef void ( APIENTRY * glVertex4f_t )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +glVertex4f_t glVertex4f; +typedef void ( APIENTRY * glVertex4fv_t )(const GLfloat *v); +glVertex4fv_t glVertex4fv; +typedef void ( APIENTRY * glVertex4i_t )(GLint x, GLint y, GLint z, GLint w); +glVertex4i_t glVertex4i; +typedef void ( APIENTRY * glVertex4iv_t )(const GLint *v); +glVertex4iv_t glVertex4iv; +typedef void ( APIENTRY * glVertex4s_t )(GLshort x, GLshort y, GLshort z, GLshort w); +glVertex4s_t glVertex4s; +typedef void ( APIENTRY * glVertex4sv_t )(const GLshort *v); +glVertex4sv_t glVertex4sv; +typedef void ( APIENTRY * glVertexPointer_t )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +glVertexPointer_t glVertexPointer; +typedef void ( APIENTRY * glViewport_t )(GLint x, GLint y, GLsizei width, GLsizei height); +glViewport_t glViewport; + + +/* EXT_paletted_texture */ +typedef void ( GLAPIENTRY* glColorTable_t)(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data); +glColorTable_t glColorTableEXT; + +/* EXT_compiled_vertex_array */ +typedef void ( GLAPIENTRY * glLockArrays_t)(GLint first, GLsizei count); +typedef void ( GLAPIENTRY * glUnlockArrays_t)(); +glLockArrays_t glLockArraysEXT; +glUnlockArrays_t glUnlockArraysEXT; + +/* ARB_multitexture */ +typedef void ( GLAPIENTRY * glActiveTextureARB_t)(GLenum target); +typedef void ( GLAPIENTRY * glClientActiveTextureARB_t)(GLenum target); +typedef void ( GLAPIENTRY * glMultiTexCoord2fARB_t)(GLenum texture, GLfloat, GLfloat); +typedef void ( GLAPIENTRY * glMultiTexCoord2fvARB_t)(GLenum texture, GLfloat*); +glActiveTextureARB_t glActiveTextureARB; +glClientActiveTextureARB_t glClientActiveTextureARB; +glMultiTexCoord2fARB_t glMultiTexCoord2fARB; +glMultiTexCoord2fvARB_t glMultiTexCoord2fvARB; + +/* NV_vertex_array_range */ + +typedef void (GLAPIENTRY* glVertexArrayRange_t)(GLsizei length, void* pointer); +typedef void (GLAPIENTRY* glFlushVertexArrayRange_t)(); +typedef void* (GLAPIENTRY* wglAllocateMemory_t)(GLsizei, GLfloat, GLfloat, GLfloat); +typedef void (GLAPIENTRY* wglFreeMemory_t)(void*); +glVertexArrayRange_t glVertexArrayRangeNV; +glFlushVertexArrayRange_t glFlushVertexArrayRangeNV; +wglAllocateMemory_t wglAllocateMemoryNV; +wglFreeMemory_t wglFreeMemoryNV; + +/* EXT_fog_coord */ +typedef void (GLAPIENTRY* glFogCoordf_t)(GLfloat); +typedef void (GLAPIENTRY* glFogCoordPointer_t)(GLenum, GLsizei, void*); +glFogCoordf_t glFogCoordfEXT; +glFogCoordPointer_t glFogCoordPointerEXT; + +/* ARB_texture_compression */ +typedef void (GLAPIENTRY* glCompressedTexImage3DARB_t)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void*); +typedef void (GLAPIENTRY* glCompressedTexImage2DARB_t)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void*); +typedef void (GLAPIENTRY* glCompressedTexImage1DARB_t)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLsizei imageSize, const void*); +typedef void (GLAPIENTRY* glCompressedTexSubImage3DARB_t)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void*); +typedef void (GLAPIENTRY* glCompressedTexSubImage2DARB_t)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void*); +typedef void (GLAPIENTRY* glCompressedTexSubImage1DARB_t)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void*); +typedef void (GLAPIENTRY* glGetCompressedTexImageARB_t)(GLenum target, GLint lod, void* img); +glCompressedTexImage3DARB_t glCompressedTexImage3DARB; +glCompressedTexImage2DARB_t glCompressedTexImage2DARB; +glCompressedTexImage1DARB_t glCompressedTexImage1DARB; +glCompressedTexSubImage3DARB_t glCompressedTexSubImage3DARB; +glCompressedTexSubImage2DARB_t glCompressedTexSubImage2DARB; +glCompressedTexSubImage1DARB_t glCompressedTexSubImage1DARB; +glGetCompressedTexImageARB_t glGetCompressedTexImageARB; + +/* EXT_vertex_buffer */ +typedef GLboolean (GLAPIENTRY* glAvailableVertexBufferEXT_t)(); +typedef GLint (GLAPIENTRY* glAllocateVertexBufferEXT_t)(GLsizei size, GLint format, GLboolean preserve); +typedef void* (GLAPIENTRY* glLockVertexBufferEXT_t)(GLint handle, GLsizei size); +typedef void (GLAPIENTRY* glUnlockVertexBufferEXT_t)(GLint handle); +typedef void (GLAPIENTRY* glSetVertexBufferEXT_t)(GLint handle); +typedef void (GLAPIENTRY* glOffsetVertexBufferEXT_t)(GLint handle, GLuint offset); +typedef void (GLAPIENTRY* glFillVertexBufferEXT_t)(GLint handle, GLint first, GLsizei count); +typedef void (GLAPIENTRY* glFreeVertexBufferEXT_t)(GLint handle); +glAvailableVertexBufferEXT_t glAvailableVertexBufferEXT; +glAllocateVertexBufferEXT_t glAllocateVertexBufferEXT; +glLockVertexBufferEXT_t glLockVertexBufferEXT; +glUnlockVertexBufferEXT_t glUnlockVertexBufferEXT; +glSetVertexBufferEXT_t glSetVertexBufferEXT; +glOffsetVertexBufferEXT_t glOffsetVertexBufferEXT; +glFillVertexBufferEXT_t glFillVertexBufferEXT; +glFreeVertexBufferEXT_t glFreeVertexBufferEXT; + +//------------------------------------------------------------------------------ +// GLU DLL Functions +static const GLubyte* (APIENTRY * dllgluErrorString) (GLenum errCode); +static const GLubyte* (APIENTRY * dllgluGetString) (GLenum name); +static void (APIENTRY * dllgluOrtho2D) (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top); +static void (APIENTRY * dllgluPerspective) (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar); +static void (APIENTRY * dllgluPickMatrix) (GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4]); +static void (APIENTRY * dllgluLookAt) (GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz); +static int (APIENTRY * dllgluProject) (GLdouble objx, GLdouble objy, GLdouble objz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *winx, GLdouble *winy, GLdouble *winz); +static int (APIENTRY * dllgluUnProject) (GLdouble winx, GLdouble winy, GLdouble winz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *objx, GLdouble *objy, GLdouble *objz); +static int (APIENTRY * dllgluScaleImage) (GLenum format, GLint widthin, GLint heightin, GLenum typein, const void *datain, GLint widthout, GLint heightout, GLenum typeout, void *dataout); +static int (APIENTRY * dllgluBuild1DMipmaps) (GLenum target, GLint components, GLint width, GLenum format, GLenum type, const void *data); +static int (APIENTRY * dllgluBuild2DMipmaps) (GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data); + +//------------------------------------------------------------------------------ +// GL DLL Functions +static void ( APIENTRY * dllAccum )(GLenum op, GLfloat value); +static void ( APIENTRY * dllAlphaFunc )(GLenum func, GLclampf ref); +GLboolean ( APIENTRY * dllAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +static void ( APIENTRY * dllArrayElement )(GLint i); +static void ( APIENTRY * dllBegin )(GLenum mode); +static void ( APIENTRY * dllBindTexture )(GLenum target, GLuint texture); +static void ( APIENTRY * dllBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +static void ( APIENTRY * dllBlendFunc )(GLenum sfactor, GLenum dfactor); +static void ( APIENTRY * dllCallList )(GLuint list); +static void ( APIENTRY * dllCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +static void ( APIENTRY * dllClear )(GLbitfield mask); +static void ( APIENTRY * dllClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static void ( APIENTRY * dllClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +static void ( APIENTRY * dllClearDepth )(GLclampd depth); +static void ( APIENTRY * dllClearIndex )(GLfloat c); +static void ( APIENTRY * dllClearStencil )(GLint s); +static void ( APIENTRY * dllClipPlane )(GLenum plane, const GLdouble *equation); +static void ( APIENTRY * dllColor3b )(GLbyte red, GLbyte green, GLbyte blue); +static void ( APIENTRY * dllColor3bv )(const GLbyte *v); +static void ( APIENTRY * dllColor3d )(GLdouble red, GLdouble green, GLdouble blue); +static void ( APIENTRY * dllColor3dv )(const GLdouble *v); +static void ( APIENTRY * dllColor3f )(GLfloat red, GLfloat green, GLfloat blue); +static void ( APIENTRY * dllColor3fv )(const GLfloat *v); +static void ( APIENTRY * dllColor3i )(GLint red, GLint green, GLint blue); +static void ( APIENTRY * dllColor3iv )(const GLint *v); +static void ( APIENTRY * dllColor3s )(GLshort red, GLshort green, GLshort blue); +static void ( APIENTRY * dllColor3sv )(const GLshort *v); +static void ( APIENTRY * dllColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +static void ( APIENTRY * dllColor3ubv )(const GLubyte *v); +static void ( APIENTRY * dllColor3ui )(GLuint red, GLuint green, GLuint blue); +static void ( APIENTRY * dllColor3uiv )(const GLuint *v); +static void ( APIENTRY * dllColor3us )(GLushort red, GLushort green, GLushort blue); +static void ( APIENTRY * dllColor3usv )(const GLushort *v); +static void ( APIENTRY * dllColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +static void ( APIENTRY * dllColor4bv )(const GLbyte *v); +static void ( APIENTRY * dllColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +static void ( APIENTRY * dllColor4dv )(const GLdouble *v); +static void ( APIENTRY * dllColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static void ( APIENTRY * dllColor4fv )(const GLfloat *v); +static void ( APIENTRY * dllColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +static void ( APIENTRY * dllColor4iv )(const GLint *v); +static void ( APIENTRY * dllColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +static void ( APIENTRY * dllColor4sv )(const GLshort *v); +static void ( APIENTRY * dllColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +static void ( APIENTRY * dllColor4ubv )(const GLubyte *v); +static void ( APIENTRY * dllColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +static void ( APIENTRY * dllColor4uiv )(const GLuint *v); +static void ( APIENTRY * dllColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +static void ( APIENTRY * dllColor4usv )(const GLushort *v); +static void ( APIENTRY * dllColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +static void ( APIENTRY * dllColorMaterial )(GLenum face, GLenum mode); +static void ( APIENTRY * dllColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +static void ( APIENTRY * dllCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +static void ( APIENTRY * dllCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +static void ( APIENTRY * dllCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +static void ( APIENTRY * dllCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +static void ( APIENTRY * dllCullFace )(GLenum mode); +static void ( APIENTRY * dllDeleteLists )(GLuint list, GLsizei range); +static void ( APIENTRY * dllDeleteTextures )(GLsizei n, const GLuint *textures); +static void ( APIENTRY * dllDepthFunc )(GLenum func); +static void ( APIENTRY * dllDepthMask )(GLboolean flag); +static void ( APIENTRY * dllDepthRange )(GLclampd zNear, GLclampd zFar); +static void ( APIENTRY * dllDisable )(GLenum cap); +static void ( APIENTRY * dllDisableClientState )(GLenum array); +static void ( APIENTRY * dllDrawArrays )(GLenum mode, GLint first, GLsizei count); +static void ( APIENTRY * dllDrawBuffer )(GLenum mode); +static void ( APIENTRY * dllDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +static void ( APIENTRY * dllDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllEdgeFlag )(GLboolean flag); +static void ( APIENTRY * dllEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllEdgeFlagv )(const GLboolean *flag); +static void ( APIENTRY * dllEnable )(GLenum cap); +static void ( APIENTRY * dllEnableClientState )(GLenum array); +static void ( APIENTRY * dllEnd )(void); +static void ( APIENTRY * dllEndList )(void); +static void ( APIENTRY * dllEvalCoord1d )(GLdouble u); +static void ( APIENTRY * dllEvalCoord1dv )(const GLdouble *u); +static void ( APIENTRY * dllEvalCoord1f )(GLfloat u); +static void ( APIENTRY * dllEvalCoord1fv )(const GLfloat *u); +static void ( APIENTRY * dllEvalCoord2d )(GLdouble u, GLdouble v); +static void ( APIENTRY * dllEvalCoord2dv )(const GLdouble *u); +static void ( APIENTRY * dllEvalCoord2f )(GLfloat u, GLfloat v); +static void ( APIENTRY * dllEvalCoord2fv )(const GLfloat *u); +static void ( APIENTRY * dllEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +static void ( APIENTRY * dllEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +static void ( APIENTRY * dllEvalPoint1 )(GLint i); +static void ( APIENTRY * dllEvalPoint2 )(GLint i, GLint j); +static void ( APIENTRY * dllFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +static void ( APIENTRY * dllFinish )(void); +static void ( APIENTRY * dllFlush )(void); +static void ( APIENTRY * dllFogf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllFogfv )(GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllFogi )(GLenum pname, GLint param); +static void ( APIENTRY * dllFogiv )(GLenum pname, const GLint *params); +static void ( APIENTRY * dllFrontFace )(GLenum mode); +static void ( APIENTRY * dllFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLuint ( APIENTRY * dllGenLists )(GLsizei range); +static void ( APIENTRY * dllGenTextures )(GLsizei n, GLuint *textures); +static void ( APIENTRY * dllGetBooleanv )(GLenum pname, GLboolean *params); +static void ( APIENTRY * dllGetClipPlane )(GLenum plane, GLdouble *equation); +static void ( APIENTRY * dllGetDoublev )(GLenum pname, GLdouble *params); +GLenum ( APIENTRY * dllGetError )(void); +static void ( APIENTRY * dllGetFloatv )(GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetIntegerv )(GLenum pname, GLint *params); +static void ( APIENTRY * dllGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetLightiv )(GLenum light, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetMapdv )(GLenum target, GLenum query, GLdouble *v); +static void ( APIENTRY * dllGetMapfv )(GLenum target, GLenum query, GLfloat *v); +static void ( APIENTRY * dllGetMapiv )(GLenum target, GLenum query, GLint *v); +static void ( APIENTRY * dllGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetPixelMapfv )(GLenum map, GLfloat *values); +static void ( APIENTRY * dllGetPixelMapuiv )(GLenum map, GLuint *values); +static void ( APIENTRY * dllGetPixelMapusv )(GLenum map, GLushort *values); +static void ( APIENTRY * dllGetPointerv )(GLenum pname, GLvoid* *params); +static void ( APIENTRY * dllGetPolygonStipple )(GLubyte *mask); +const GLubyte * ( APIENTRY * dllGetString )(GLenum name); +static void ( APIENTRY * dllGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +static void ( APIENTRY * dllGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +static void ( APIENTRY * dllGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +static void ( APIENTRY * dllHint )(GLenum target, GLenum mode); +static void ( APIENTRY * dllIndexMask )(GLuint mask); +static void ( APIENTRY * dllIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllIndexd )(GLdouble c); +static void ( APIENTRY * dllIndexdv )(const GLdouble *c); +static void ( APIENTRY * dllIndexf )(GLfloat c); +static void ( APIENTRY * dllIndexfv )(const GLfloat *c); +static void ( APIENTRY * dllIndexi )(GLint c); +static void ( APIENTRY * dllIndexiv )(const GLint *c); +static void ( APIENTRY * dllIndexs )(GLshort c); +static void ( APIENTRY * dllIndexsv )(const GLshort *c); +static void ( APIENTRY * dllIndexub )(GLubyte c); +static void ( APIENTRY * dllIndexubv )(const GLubyte *c); +static void ( APIENTRY * dllInitNames )(void); +static void ( APIENTRY * dllInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +GLboolean ( APIENTRY * dllIsEnabled )(GLenum cap); +GLboolean ( APIENTRY * dllIsList )(GLuint list); +GLboolean ( APIENTRY * dllIsTexture )(GLuint texture); +static void ( APIENTRY * dllLightModelf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllLightModelfv )(GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllLightModeli )(GLenum pname, GLint param); +static void ( APIENTRY * dllLightModeliv )(GLenum pname, const GLint *params); +static void ( APIENTRY * dllLightf )(GLenum light, GLenum pname, GLfloat param); +static void ( APIENTRY * dllLightfv )(GLenum light, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllLighti )(GLenum light, GLenum pname, GLint param); +static void ( APIENTRY * dllLightiv )(GLenum light, GLenum pname, const GLint *params); +static void ( APIENTRY * dllLineStipple )(GLint factor, GLushort pattern); +static void ( APIENTRY * dllLineWidth )(GLfloat width); +static void ( APIENTRY * dllListBase )(GLuint base); +static void ( APIENTRY * dllLoadIdentity )(void); +static void ( APIENTRY * dllLoadMatrixd )(const GLdouble *m); +static void ( APIENTRY * dllLoadMatrixf )(const GLfloat *m); +static void ( APIENTRY * dllLoadName )(GLuint name); +static void ( APIENTRY * dllLogicOp )(GLenum opcode); +static void ( APIENTRY * dllMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +static void ( APIENTRY * dllMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +static void ( APIENTRY * dllMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +static void ( APIENTRY * dllMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +static void ( APIENTRY * dllMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +static void ( APIENTRY * dllMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +static void ( APIENTRY * dllMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +static void ( APIENTRY * dllMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +static void ( APIENTRY * dllMaterialf )(GLenum face, GLenum pname, GLfloat param); +static void ( APIENTRY * dllMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllMateriali )(GLenum face, GLenum pname, GLint param); +static void ( APIENTRY * dllMaterialiv )(GLenum face, GLenum pname, const GLint *params); +static void ( APIENTRY * dllMatrixMode )(GLenum mode); +static void ( APIENTRY * dllMultMatrixd )(const GLdouble *m); +static void ( APIENTRY * dllMultMatrixf )(const GLfloat *m); +static void ( APIENTRY * dllNewList )(GLuint list, GLenum mode); +static void ( APIENTRY * dllNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +static void ( APIENTRY * dllNormal3bv )(const GLbyte *v); +static void ( APIENTRY * dllNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +static void ( APIENTRY * dllNormal3dv )(const GLdouble *v); +static void ( APIENTRY * dllNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +static void ( APIENTRY * dllNormal3fv )(const GLfloat *v); +static void ( APIENTRY * dllNormal3i )(GLint nx, GLint ny, GLint nz); +static void ( APIENTRY * dllNormal3iv )(const GLint *v); +static void ( APIENTRY * dllNormal3s )(GLshort nx, GLshort ny, GLshort nz); +static void ( APIENTRY * dllNormal3sv )(const GLshort *v); +static void ( APIENTRY * dllNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +static void ( APIENTRY * dllPassThrough )(GLfloat token); +static void ( APIENTRY * dllPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +static void ( APIENTRY * dllPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +static void ( APIENTRY * dllPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +static void ( APIENTRY * dllPixelStoref )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllPixelStorei )(GLenum pname, GLint param); +static void ( APIENTRY * dllPixelTransferf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllPixelTransferi )(GLenum pname, GLint param); +static void ( APIENTRY * dllPixelZoom )(GLfloat xfactor, GLfloat yfactor); +static void ( APIENTRY * dllPointSize )(GLfloat size); +static void ( APIENTRY * dllPolygonMode )(GLenum face, GLenum mode); +static void ( APIENTRY * dllPolygonOffset )(GLfloat factor, GLfloat units); +static void ( APIENTRY * dllPolygonStipple )(const GLubyte *mask); +static void ( APIENTRY * dllPopAttrib )(void); +static void ( APIENTRY * dllPopClientAttrib )(void); +static void ( APIENTRY * dllPopMatrix )(void); +static void ( APIENTRY * dllPopName )(void); +static void ( APIENTRY * dllPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +static void ( APIENTRY * dllPushAttrib )(GLbitfield mask); +static void ( APIENTRY * dllPushClientAttrib )(GLbitfield mask); +static void ( APIENTRY * dllPushMatrix )(void); +static void ( APIENTRY * dllPushName )(GLuint name); +static void ( APIENTRY * dllRasterPos2d )(GLdouble x, GLdouble y); +static void ( APIENTRY * dllRasterPos2dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos2f )(GLfloat x, GLfloat y); +static void ( APIENTRY * dllRasterPos2fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos2i )(GLint x, GLint y); +static void ( APIENTRY * dllRasterPos2iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos2s )(GLshort x, GLshort y); +static void ( APIENTRY * dllRasterPos2sv )(const GLshort *v); +static void ( APIENTRY * dllRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllRasterPos3dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllRasterPos3fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos3i )(GLint x, GLint y, GLint z); +static void ( APIENTRY * dllRasterPos3iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos3s )(GLshort x, GLshort y, GLshort z); +static void ( APIENTRY * dllRasterPos3sv )(const GLshort *v); +static void ( APIENTRY * dllRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static void ( APIENTRY * dllRasterPos4dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static void ( APIENTRY * dllRasterPos4fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +static void ( APIENTRY * dllRasterPos4iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +static void ( APIENTRY * dllRasterPos4sv )(const GLshort *v); +static void ( APIENTRY * dllReadBuffer )(GLenum mode); +static void ( APIENTRY * dllReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +static void ( APIENTRY * dllRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +static void ( APIENTRY * dllRectdv )(const GLdouble *v1, const GLdouble *v2); +static void ( APIENTRY * dllRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +static void ( APIENTRY * dllRectfv )(const GLfloat *v1, const GLfloat *v2); +static void ( APIENTRY * dllRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +static void ( APIENTRY * dllRectiv )(const GLint *v1, const GLint *v2); +static void ( APIENTRY * dllRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +static void ( APIENTRY * dllRectsv )(const GLshort *v1, const GLshort *v2); +GLint ( APIENTRY * dllRenderMode )(GLenum mode); +static void ( APIENTRY * dllRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllScaled )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllScalef )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +static void ( APIENTRY * dllSelectBuffer )(GLsizei size, GLuint *buffer); +static void ( APIENTRY * dllShadeModel )(GLenum mode); +static void ( APIENTRY * dllStencilFunc )(GLenum func, GLint ref, GLuint mask); +static void ( APIENTRY * dllStencilMask )(GLuint mask); +static void ( APIENTRY * dllStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +static void ( APIENTRY * dllTexCoord1d )(GLdouble s); +static void ( APIENTRY * dllTexCoord1dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord1f )(GLfloat s); +static void ( APIENTRY * dllTexCoord1fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord1i )(GLint s); +static void ( APIENTRY * dllTexCoord1iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord1s )(GLshort s); +static void ( APIENTRY * dllTexCoord1sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord2d )(GLdouble s, GLdouble t); +static void ( APIENTRY * dllTexCoord2dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord2f )(GLfloat s, GLfloat t); +static void ( APIENTRY * dllTexCoord2fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord2i )(GLint s, GLint t); +static void ( APIENTRY * dllTexCoord2iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord2s )(GLshort s, GLshort t); +static void ( APIENTRY * dllTexCoord2sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +static void ( APIENTRY * dllTexCoord3dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +static void ( APIENTRY * dllTexCoord3fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord3i )(GLint s, GLint t, GLint r); +static void ( APIENTRY * dllTexCoord3iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord3s )(GLshort s, GLshort t, GLshort r); +static void ( APIENTRY * dllTexCoord3sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +static void ( APIENTRY * dllTexCoord4dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +static void ( APIENTRY * dllTexCoord4fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +static void ( APIENTRY * dllTexCoord4iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +static void ( APIENTRY * dllTexCoord4sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllTexEnvf )(GLenum target, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexEnvi )(GLenum target, GLenum pname, GLint param); +static void ( APIENTRY * dllTexEnviv )(GLenum target, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexGend )(GLenum coord, GLenum pname, GLdouble param); +static void ( APIENTRY * dllTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +static void ( APIENTRY * dllTexGenf )(GLenum coord, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexGeni )(GLenum coord, GLenum pname, GLint param); +static void ( APIENTRY * dllTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexParameterf )(GLenum target, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexParameteri )(GLenum target, GLenum pname, GLint param); +static void ( APIENTRY * dllTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTranslated )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllTranslatef )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllVertex2d )(GLdouble x, GLdouble y); +static void ( APIENTRY * dllVertex2dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex2f )(GLfloat x, GLfloat y); +static void ( APIENTRY * dllVertex2fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex2i )(GLint x, GLint y); +static void ( APIENTRY * dllVertex2iv )(const GLint *v); +static void ( APIENTRY * dllVertex2s )(GLshort x, GLshort y); +static void ( APIENTRY * dllVertex2sv )(const GLshort *v); +static void ( APIENTRY * dllVertex3d )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllVertex3dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex3f )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllVertex3fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex3i )(GLint x, GLint y, GLint z); +static void ( APIENTRY * dllVertex3iv )(const GLint *v); +static void ( APIENTRY * dllVertex3s )(GLshort x, GLshort y, GLshort z); +static void ( APIENTRY * dllVertex3sv )(const GLshort *v); +static void ( APIENTRY * dllVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static void ( APIENTRY * dllVertex4dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static void ( APIENTRY * dllVertex4fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex4i )(GLint x, GLint y, GLint z, GLint w); +static void ( APIENTRY * dllVertex4iv )(const GLint *v); +static void ( APIENTRY * dllVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +static void ( APIENTRY * dllVertex4sv )(const GLshort *v); +static void ( APIENTRY * dllVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +/* EXT_paletted_texture */ +static void ( APIENTRY * dllColorTableEXT)(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data); + +/* EXT_compiled_vertex_array */ +static void ( APIENTRY * dllLockArraysEXT)(GLint, GLsizei); +static void ( APIENTRY * dllUnlockArraysEXT)(); + +/* ARB_multitexture */ +static void ( APIENTRY * dllActiveTextureARB)(GLenum target); +static void ( APIENTRY * dllClientActiveTextureARB)(GLenum target); +static void ( APIENTRY * dllMultiTexCoord2fARB)(GLenum texture, GLfloat, GLfloat); +static void ( APIENTRY * dllMultiTexCoord2fvARB)(GLenum texture, GLfloat*); + +/* NV_vertex_array_range */ +static void (APIENTRY* dllVertexArrayRangeNV)(GLsizei length, void* pointer); +static void (APIENTRY* dllFlushVertexArrayRangeNV)(); +static void* (APIENTRY* dllAllocateMemoryNV)(GLsizei length, GLfloat, GLfloat, GLfloat); +static void (APIENTRY* dllFreeMemoryNV)(void*); + +/* EXT_fog_coord */ +static void (APIENTRY* dllFogCoordfEXT)(GLfloat coord); +static void (APIENTRY* dllFogCoordPointerEXT)(GLenum, GLsizei, void*); + +/* ARB_texture_compression */ +static void (APIENTRY* dllCompressedTexImage3DARB)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void*); +static void (APIENTRY* dllCompressedTexImage2DARB)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void*); +static void (APIENTRY* dllCompressedTexImage1DARB)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLsizei imageSize, const void*); +static void (APIENTRY* dllCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void*); +static void (APIENTRY* dllCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void*); +static void (APIENTRY* dllCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void*); +static void (APIENTRY* dllGetCompressedTexImageARB)(GLenum target, GLint lod, void* img); + +/* EXT_vertex_buffer */ +static GLboolean (GLAPIENTRY* dllAvailableVertexBufferEXT)(); +static GLint (GLAPIENTRY* dllAllocateVertexBufferEXT)(GLsizei size, GLint format, GLboolean preserve); +static void* (GLAPIENTRY* dllLockVertexBufferEXT)(GLint handle, GLsizei size); +static void (GLAPIENTRY* dllUnlockVertexBufferEXT)(GLint handle); +static void (GLAPIENTRY* dllSetVertexBufferEXT)(GLint handle); +static void (GLAPIENTRY* dllOffsetVertexBufferEXT)(GLint handle, GLuint offset); +static void (GLAPIENTRY* dllFillVertexBufferEXT)(GLint handle, GLint first, GLsizei count); +static void (GLAPIENTRY* dllFreeVertexBufferEXT)(GLint handle); + + +static const char * BooleanToString( GLboolean b ) +{ + if ( b == GL_FALSE ) + return "GL_FALSE"; + else if ( b == GL_TRUE ) + return "GL_TRUE"; + else + return "OUT OF RANGE FOR BOOLEAN"; +} + +static const char * FuncToString( GLenum f ) +{ + switch ( f ) + { + case GL_ALWAYS: + return "GL_ALWAYS"; + case GL_NEVER: + return "GL_NEVER"; + case GL_LEQUAL: + return "GL_LEQUAL"; + case GL_LESS: + return "GL_LESS"; + case GL_EQUAL: + return "GL_EQUAL"; + case GL_GREATER: + return "GL_GREATER"; + case GL_GEQUAL: + return "GL_GEQUAL"; + case GL_NOTEQUAL: + return "GL_NOTEQUAL"; + default: + return "!!! UNKNOWN !!!"; + } +} + +static const char* CoordToString( GLenum coord) +{ + if (coord == GL_S) + return "GL_S"; + else if (coord == GL_T) + return "GL_T"; + else + return "INVALID_COORD"; +} + +static const char * PrimToString( GLenum mode ) +{ + static char prim[1024]; + + if ( mode == GL_TRIANGLES ) + strcpy( prim, "GL_TRIANGLES" ); + else if ( mode == GL_TRIANGLE_STRIP ) + strcpy( prim, "GL_TRIANGLE_STRIP" ); + else if ( mode == GL_TRIANGLE_FAN ) + strcpy( prim, "GL_TRIANGLE_FAN" ); + else if ( mode == GL_QUADS ) + strcpy( prim, "GL_QUADS" ); + else if ( mode == GL_QUAD_STRIP ) + strcpy( prim, "GL_QUAD_STRIP" ); + else if ( mode == GL_POLYGON ) + strcpy( prim, "GL_POLYGON" ); + else if ( mode == GL_POINTS ) + strcpy( prim, "GL_POINTS" ); + else if ( mode == GL_LINES ) + strcpy( prim, "GL_LINES" ); + else if ( mode == GL_LINE_STRIP ) + strcpy( prim, "GL_LINE_STRIP" ); + else if ( mode == GL_LINE_LOOP ) + strcpy( prim, "GL_LINE_LOOP" ); + else + sprintf( prim, "0x%x", mode ); + + return prim; +} + +static const char * CapToString( GLenum cap ) +{ + static char buffer[1024]; + + switch ( cap ) + { + case GL_TEXTURE_2D: + return "GL_TEXTURE_2D"; + case GL_BLEND: + return "GL_BLEND"; + case GL_DEPTH_TEST: + return "GL_DEPTH_TEST"; + case GL_CULL_FACE: + return "GL_CULL_FACE"; + case GL_CLIP_PLANE0: + return "GL_CLIP_PLANE0"; + case GL_COLOR_ARRAY: + return "GL_COLOR_ARRAY"; + case GL_TEXTURE_COORD_ARRAY: + return "GL_TEXTURE_COORD_ARRAY"; + case GL_VERTEX_ARRAY: + return "GL_VERTEX_ARRAY"; + case GL_ALPHA_TEST: + return "GL_ALPHA_TEST"; + case GL_STENCIL_TEST: + return "GL_STENCIL_TEST"; + case GL_TEXTURE_GEN_S: + return "GL_TEXTURE_GEN_S"; + case GL_TEXTURE_GEN_T: + return "GL_TEXTURE_GEN_T"; + default: + sprintf( buffer, "0x%x", cap ); + } + + return buffer; +} + +static const char * TypeToString( GLenum t ) +{ + switch ( t ) + { + case GL_BYTE: + return "GL_BYTE"; + case GL_UNSIGNED_BYTE: + return "GL_UNSIGNED_BYTE"; + case GL_SHORT: + return "GL_SHORT"; + case GL_UNSIGNED_SHORT: + return "GL_UNSIGNED_SHORT"; + case GL_INT: + return "GL_INT"; + case GL_UNSIGNED_INT: + return "GL_UNSIGNED_INT"; + case GL_FLOAT: + return "GL_FLOAT"; + case GL_DOUBLE: + return "GL_DOUBLE"; + default: + return "!!! UNKNOWN !!!"; + } +} + +//------------------------------------------------------------------------------ +// GLU Log Functions +static void APIENTRY loggluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top) +{ + fprintf(winState.log_fp, "gluOrtho2D( %d, %d, %d, %d )\n", left, right, bottom, top); + fflush(winState.log_fp); + dllgluOrtho2D(left, right, bottom, top); +} + +static void APIENTRY loggluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar) +{ + fprintf(winState.log_fp, "gluPerspective( %d, %d, %d, %d )\n", fovy, aspect, zNear, zFar); + fflush(winState.log_fp); + dllgluPerspective(fovy, aspect, zNear, zFar); +} + +static void APIENTRY loggluPickMatrix(GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4]) +{ + fprintf(winState.log_fp, "gluPickMatrix(%d, %d, %d, %d, VIEW)\n", x, y, width, height); + fflush(winState.log_fp); + dllgluPickMatrix(x, y, width, height, viewport); +} + +static void APIENTRY loggluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz) +{ + fprintf(winState.log_fp, "gluLookAt(%d, %d, %d, %d, %d, %d, %d, %d, %d)\n",eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz); + fflush(winState.log_fp); + dllgluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz); +} + +static int APIENTRY loggluProject(GLdouble objx, GLdouble objy, GLdouble objz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *winx, GLdouble *winy, GLdouble *winz) +{ + fprintf(winState.log_fp, "gluProject\n"); + fflush(winState.log_fp); + return dllgluProject(objx, objy, objz, modelMatrix, projMatrix, viewport, winx, winy, winz); +} + +static int APIENTRY loggluUnProject(GLdouble winx, GLdouble winy, GLdouble winz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *objx, GLdouble *objy, GLdouble *objz) +{ + fprintf(winState.log_fp, "gluUnProject\n"); + fflush(winState.log_fp); + return dllgluUnProject(winx, winy, winz, modelMatrix, projMatrix, viewport, objx, objy, objz); +} + +static int APIENTRY loggluScaleImage(GLenum format, GLint widthin, GLint heightin, GLenum typein, const void *datain, GLint widthout, GLint heightout, GLenum typeout, void *dataout) +{ + fprintf(winState.log_fp, "gluScaleImage\n"); + fflush(winState.log_fp); + return dllgluScaleImage(format, widthin, heightin, typein, datain, widthout, heightout, typeout, dataout); +} + +static int APIENTRY loggluBuild1DMipmaps(GLenum target, GLint components, GLint width, GLenum format, GLenum type, const void *data) +{ + fprintf(winState.log_fp, "gluBuild1DMipmaps\n"); + fflush(winState.log_fp); + return dllgluBuild1DMipmaps(target, components, width, format, type, data); +} + +static int APIENTRY loggluBuild2DMipmaps(GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data) +{ + fprintf(winState.log_fp, "gluBuild2DMipmaps\n"); + fflush(winState.log_fp); + return dllgluBuild2DMipmaps(target, components, width, height, format, type, data); +} + + +//------------------------------------------------------------------------------ +// GL LOG Functions +static void APIENTRY logAccum(GLenum op, GLfloat value) +{ + fprintf( winState.log_fp, "glAccum\n" ); + fflush(winState.log_fp); + dllAccum( op, value ); +} + +static void APIENTRY logAlphaFunc(GLenum func, GLclampf ref) +{ + fprintf( winState.log_fp, "glAlphaFunc( 0x%x, %f )\n", func, ref ); + fflush(winState.log_fp); + dllAlphaFunc( func, ref ); +} + +static GLboolean APIENTRY logAreTexturesResident(GLsizei n, const GLuint *textures, GLboolean *residences) +{ + fprintf( winState.log_fp, "glAreTexturesResident\n" ); + fflush(winState.log_fp); + return dllAreTexturesResident( n, textures, residences ); +} + +static void APIENTRY logArrayElement(GLint i) +{ + fprintf( winState.log_fp, "glArrayElement\n" ); + fflush(winState.log_fp); + dllArrayElement( i ); +} + +static void APIENTRY logBegin(GLenum mode) +{ + fprintf( winState.log_fp, "glBegin( %s )\n", PrimToString( mode )); + fflush(winState.log_fp); + dllBegin( mode ); +} + +static void APIENTRY logBindTexture(GLenum target, GLuint texture) +{ + fprintf( winState.log_fp, "glBindTexture( 0x%x, %u )\n", target, texture ); + fflush(winState.log_fp); + dllBindTexture( target, texture ); +} + +static void APIENTRY logBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap) +{ + fprintf( winState.log_fp, "glBitmap\n" ); + fflush(winState.log_fp); + dllBitmap( width, height, xorig, yorig, xmove, ymove, bitmap ); +} + +static void BlendToName( char *n, GLenum f ) +{ + switch ( f ) + { + case GL_ONE: + strcpy( n, "GL_ONE" ); + break; + case GL_ZERO: + strcpy( n, "GL_ZERO" ); + break; + case GL_SRC_ALPHA: + strcpy( n, "GL_SRC_ALPHA" ); + break; + case GL_ONE_MINUS_SRC_ALPHA: + strcpy( n, "GL_ONE_MINUS_SRC_ALPHA" ); + break; + case GL_SRC_COLOR: + strcpy( n, "GL_SRC_COLOR" ); + break; + case GL_ONE_MINUS_SRC_COLOR: + strcpy( n, "GL_ONE_MINUS_SRC_COLOR" ); + break; + case GL_DST_COLOR: + strcpy( n, "GL_DST_COLOR" ); + break; + case GL_ONE_MINUS_DST_COLOR: + strcpy( n, "GL_ONE_MINUS_DST_COLOR" ); + break; + case GL_DST_ALPHA: + strcpy( n, "GL_DST_ALPHA" ); + break; + default: + sprintf( n, "0x%x", f ); + } +} +static void APIENTRY logBlendFunc(GLenum sfactor, GLenum dfactor) +{ + char sf[128], df[128]; + + BlendToName( sf, sfactor ); + BlendToName( df, dfactor ); + + fprintf( winState.log_fp, "glBlendFunc( %s, %s )\n", sf, df ); + fflush(winState.log_fp); + dllBlendFunc( sfactor, dfactor ); +} + +static void APIENTRY logCallList(GLuint list) +{ + fprintf( winState.log_fp, "glCallList( %u )\n", list ); + fflush(winState.log_fp); + dllCallList( list ); +} + +static void APIENTRY logCallLists(GLsizei n, GLenum type, const void *lists) +{ + fprintf( winState.log_fp, "glCallLists\n" ); + fflush(winState.log_fp); + dllCallLists( n, type, lists ); +} + +static void APIENTRY logClear(GLbitfield mask) +{ + fprintf( winState.log_fp, "glClear( 0x%x = ", mask ); + + if ( mask & GL_COLOR_BUFFER_BIT ) + fprintf( winState.log_fp, "GL_COLOR_BUFFER_BIT " ); + if ( mask & GL_DEPTH_BUFFER_BIT ) + fprintf( winState.log_fp, "GL_DEPTH_BUFFER_BIT " ); + if ( mask & GL_STENCIL_BUFFER_BIT ) + fprintf( winState.log_fp, "GL_STENCIL_BUFFER_BIT " ); + if ( mask & GL_ACCUM_BUFFER_BIT ) + fprintf( winState.log_fp, "GL_ACCUM_BUFFER_BIT " ); + + fprintf( winState.log_fp, ")\n" ); + fflush(winState.log_fp); + dllClear( mask ); +} + +static void APIENTRY logClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + fprintf( winState.log_fp, "glClearAccum\n" ); + fflush(winState.log_fp); + dllClearAccum( red, green, blue, alpha ); +} + +static void APIENTRY logClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) +{ + fprintf( winState.log_fp, "glClearColor\n" ); + fflush(winState.log_fp); + dllClearColor( red, green, blue, alpha ); +} + +static void APIENTRY logClearDepth(GLclampd depth) +{ + fprintf( winState.log_fp, "glClearDepth( %f )\n", ( F32 ) depth ); + fflush(winState.log_fp); + dllClearDepth( depth ); +} + +static void APIENTRY logClearIndex(GLfloat c) +{ + fprintf( winState.log_fp, "glClearIndex\n" ); + fflush(winState.log_fp); + dllClearIndex( c ); +} + +static void APIENTRY logClearStencil(GLint s) +{ + fprintf( winState.log_fp, "glClearStencil( %d )\n", s ); + fflush(winState.log_fp); + dllClearStencil( s ); +} + +static void APIENTRY logClipPlane(GLenum plane, const GLdouble *equation) +{ + fprintf( winState.log_fp, "glClipPlane\n" ); + fflush(winState.log_fp); + dllClipPlane( plane, equation ); +} + +static void APIENTRY logColor3b(GLbyte red, GLbyte green, GLbyte blue) +{ + fprintf( winState.log_fp, "glColor3b\n" ); + fflush(winState.log_fp); + dllColor3b( red, green, blue ); +} + +static void APIENTRY logColor3bv(const GLbyte *v) +{ + fprintf( winState.log_fp, "glColor3bv\n" ); + fflush(winState.log_fp); + dllColor3bv( v ); +} + +static void APIENTRY logColor3d(GLdouble red, GLdouble green, GLdouble blue) +{ + fprintf( winState.log_fp, "glColor3d\n" ); + fflush(winState.log_fp); + dllColor3d( red, green, blue ); +} + +static void APIENTRY logColor3dv(const GLdouble *v) +{ + fprintf( winState.log_fp, "glColor3dv\n" ); + fflush(winState.log_fp); + dllColor3dv( v ); +} + +static void APIENTRY logColor3f(GLfloat red, GLfloat green, GLfloat blue) +{ + fprintf( winState.log_fp, "glColor3f\n" ); + fflush(winState.log_fp); + dllColor3f( red, green, blue ); +} + +static void APIENTRY logColor3fv(const GLfloat *v) +{ + fprintf( winState.log_fp, "glColor3fv\n" ); + fflush(winState.log_fp); + dllColor3fv( v ); +} + +static void APIENTRY logColor3i(GLint red, GLint green, GLint blue) +{ + fprintf( winState.log_fp, "glColor3i\n" ); + fflush(winState.log_fp); + dllColor3i( red, green, blue ); +} + +static void APIENTRY logColor3iv(const GLint *v) +{ + fprintf( winState.log_fp, "glColor3iv\n" ); + fflush(winState.log_fp); + dllColor3iv( v ); +} + +static void APIENTRY logColor3s(GLshort red, GLshort green, GLshort blue) +{ + fprintf( winState.log_fp, "glColor3s\n" ); + fflush(winState.log_fp); + dllColor3s( red, green, blue ); +} + +static void APIENTRY logColor3sv(const GLshort *v) +{ + fprintf( winState.log_fp, "glColor3sv\n" ); + fflush(winState.log_fp); + dllColor3sv( v ); +} + +static void APIENTRY logColor3ub(GLubyte red, GLubyte green, GLubyte blue) +{ + fprintf( winState.log_fp, "glColor3ub\n" ); + fflush(winState.log_fp); + dllColor3ub( red, green, blue ); +} + +static void APIENTRY logColor3ubv(const GLubyte *v) +{ + fprintf( winState.log_fp, "glColor3ubv\n" ); + fflush(winState.log_fp); + dllColor3ubv( v ); +} + +#define SIG( x ) fprintf( winState.log_fp, x "\n" ); fflush(winState.log_fp) + +static void APIENTRY logColor3ui(GLuint red, GLuint green, GLuint blue) +{ + SIG( "glColor3ui" ); + dllColor3ui( red, green, blue ); +} + +static void APIENTRY logColor3uiv(const GLuint *v) +{ + SIG( "glColor3uiv" ); + dllColor3uiv( v ); +} + +static void APIENTRY logColor3us(GLushort red, GLushort green, GLushort blue) +{ + SIG( "glColor3us" ); + dllColor3us( red, green, blue ); +} + +static void APIENTRY logColor3usv(const GLushort *v) +{ + SIG( "glColor3usv" ); + dllColor3usv( v ); +} + +static void APIENTRY logColor4b(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha) +{ + SIG( "glColor4b" ); + dllColor4b( red, green, blue, alpha ); +} + +static void APIENTRY logColor4bv(const GLbyte *v) +{ + SIG( "glColor4bv" ); + dllColor4bv( v ); +} + +static void APIENTRY logColor4d(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha) +{ + SIG( "glColor4d" ); + dllColor4d( red, green, blue, alpha ); +} +static void APIENTRY logColor4dv(const GLdouble *v) +{ + SIG( "glColor4dv" ); + dllColor4dv( v ); +} +static void APIENTRY logColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + fprintf( winState.log_fp, "glColor4f( %f,%f,%f,%f )\n", red, green, blue, alpha ); + fflush(winState.log_fp); + dllColor4f( red, green, blue, alpha ); +} +static void APIENTRY logColor4fv(const GLfloat *v) +{ + fprintf( winState.log_fp, "glColor4fv( %f,%f,%f,%f )\n", v[0], v[1], v[2], v[3] ); + fflush(winState.log_fp); + dllColor4fv( v ); +} +static void APIENTRY logColor4i(GLint red, GLint green, GLint blue, GLint alpha) +{ + SIG( "glColor4i" ); + dllColor4i( red, green, blue, alpha ); +} +static void APIENTRY logColor4iv(const GLint *v) +{ + SIG( "glColor4iv" ); + dllColor4iv( v ); +} +static void APIENTRY logColor4s(GLshort red, GLshort green, GLshort blue, GLshort alpha) +{ + SIG( "glColor4s" ); + dllColor4s( red, green, blue, alpha ); +} +static void APIENTRY logColor4sv(const GLshort *v) +{ + SIG( "glColor4sv" ); + dllColor4sv( v ); +} +static void APIENTRY logColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) +{ + SIG( "glColor4ub" ); + dllColor4b( red, green, blue, alpha ); +} +static void APIENTRY logColor4ubv(const GLubyte *v) +{ + SIG( "glColor4ubv" ); + dllColor4ubv( v ); +} +static void APIENTRY logColor4ui(GLuint red, GLuint green, GLuint blue, GLuint alpha) +{ + SIG( "glColor4ui" ); + dllColor4ui( red, green, blue, alpha ); +} +static void APIENTRY logColor4uiv(const GLuint *v) +{ + SIG( "glColor4uiv" ); + dllColor4uiv( v ); +} +static void APIENTRY logColor4us(GLushort red, GLushort green, GLushort blue, GLushort alpha) +{ + SIG( "glColor4us" ); + dllColor4us( red, green, blue, alpha ); +} +static void APIENTRY logColor4usv(const GLushort *v) +{ + SIG( "glColor4usv" ); + dllColor4usv( v ); +} +static void APIENTRY logColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) +{ + SIG( "glColorMask" ); + dllColorMask( red, green, blue, alpha ); +} +static void APIENTRY logColorMaterial(GLenum face, GLenum mode) +{ + SIG( "glColorMaterial" ); + dllColorMaterial( face, mode ); +} + +static void APIENTRY logColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) +{ + fprintf( winState.log_fp, "glColorPointer( %d, %s, %d, MEM )\n", size, TypeToString( type ), stride ); + fflush(winState.log_fp); + dllColorPointer( size, type, stride, pointer ); +} + +static void APIENTRY logCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type) +{ + SIG( "glCopyPixels" ); + dllCopyPixels( x, y, width, height, type ); +} + +static void APIENTRY logCopyTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border) +{ + SIG( "glCopyTexImage1D" ); + dllCopyTexImage1D( target, level, internalFormat, x, y, width, border ); +} + +static void APIENTRY logCopyTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) +{ + SIG( "glCopyTexImage2D" ); + dllCopyTexImage2D( target, level, internalFormat, x, y, width, height, border ); +} + +static void APIENTRY logCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) +{ + SIG( "glCopyTexSubImage1D" ); + dllCopyTexSubImage1D( target, level, xoffset, x, y, width ); +} + +static void APIENTRY logCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) +{ + SIG( "glCopyTexSubImage2D" ); + dllCopyTexSubImage2D( target, level, xoffset, yoffset, x, y, width, height ); +} + +static void APIENTRY logCullFace(GLenum mode) +{ + fprintf( winState.log_fp, "glCullFace( %s )\n", ( mode == GL_FRONT ) ? "GL_FRONT" : "GL_BACK" ); + fflush(winState.log_fp); + dllCullFace( mode ); +} + +static void APIENTRY logDeleteLists(GLuint list, GLsizei range) +{ + SIG( "glDeleteLists" ); + dllDeleteLists( list, range ); +} + +static void APIENTRY logDeleteTextures(GLsizei n, const GLuint *textures) +{ + SIG( "glDeleteTextures" ); + dllDeleteTextures( n, textures ); +} + +static void APIENTRY logDepthFunc(GLenum func) +{ + fprintf( winState.log_fp, "glDepthFunc( %s )\n", FuncToString( func ) ); + fflush(winState.log_fp); + dllDepthFunc( func ); +} + +static void APIENTRY logDepthMask(GLboolean flag) +{ + fprintf( winState.log_fp, "glDepthMask( %s )\n", BooleanToString( flag ) ); + fflush(winState.log_fp); + dllDepthMask( flag ); +} + +static void APIENTRY logDepthRange(GLclampd zNear, GLclampd zFar) +{ + fprintf( winState.log_fp, "glDepthRange( %f, %f )\n", ( F32 ) zNear, ( F32 ) zFar ); + fflush(winState.log_fp); + dllDepthRange( zNear, zFar ); +} + +static void APIENTRY logDisable(GLenum cap) +{ + fprintf( winState.log_fp, "glDisable( %s )\n", CapToString( cap ) ); + fflush(winState.log_fp); + dllDisable( cap ); +} + +static void APIENTRY logDisableClientState(GLenum array) +{ + fprintf( winState.log_fp, "glDisableClientState( %s )\n", CapToString( array ) ); + fflush(winState.log_fp); + dllDisableClientState( array ); +} + +static void APIENTRY logDrawArrays(GLenum mode, GLint first, GLsizei count) +{ + SIG( "glDrawArrays" ); + dllDrawArrays( mode, first, count ); +} + +static void APIENTRY logDrawBuffer(GLenum mode) +{ + SIG( "glDrawBuffer" ); + dllDrawBuffer( mode ); +} + +static void APIENTRY logDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices) +{ + fprintf( winState.log_fp, "glDrawElements( %s, %d, %s, MEM )\n", PrimToString( mode ), count, TypeToString( type ) ); + fflush(winState.log_fp); + dllDrawElements( mode, count, type, indices ); +} + +static void APIENTRY logDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glDrawPixels" ); + dllDrawPixels( width, height, format, type, pixels ); +} + +static void APIENTRY logEdgeFlag(GLboolean flag) +{ + SIG( "glEdgeFlag" ); + dllEdgeFlag( flag ); +} + +static void APIENTRY logEdgeFlagPointer(GLsizei stride, const void *pointer) +{ + SIG( "glEdgeFlagPointer" ); + dllEdgeFlagPointer( stride, pointer ); +} + +static void APIENTRY logEdgeFlagv(const GLboolean *flag) +{ + SIG( "glEdgeFlagv" ); + dllEdgeFlagv( flag ); +} + +static void APIENTRY logEnable(GLenum cap) +{ + fprintf( winState.log_fp, "glEnable( %s )\n", CapToString( cap ) ); + fflush(winState.log_fp); + dllEnable( cap ); +} + +static void APIENTRY logEnableClientState(GLenum array) +{ + fprintf( winState.log_fp, "glEnableClientState( %s )\n", CapToString( array ) ); + fflush(winState.log_fp); + dllEnableClientState( array ); +} + +static void APIENTRY logEnd(void) +{ + SIG( "glEnd" ); + dllEnd(); +} + +static void APIENTRY logEndList(void) +{ + SIG( "glEndList" ); + dllEndList(); +} + +static void APIENTRY logEvalCoord1d(GLdouble u) +{ + SIG( "glEvalCoord1d" ); + dllEvalCoord1d( u ); +} + +static void APIENTRY logEvalCoord1dv(const GLdouble *u) +{ + SIG( "glEvalCoord1dv" ); + dllEvalCoord1dv( u ); +} + +static void APIENTRY logEvalCoord1f(GLfloat u) +{ + SIG( "glEvalCoord1f" ); + dllEvalCoord1f( u ); +} + +static void APIENTRY logEvalCoord1fv(const GLfloat *u) +{ + SIG( "glEvalCoord1fv" ); + dllEvalCoord1fv( u ); +} +static void APIENTRY logEvalCoord2d(GLdouble u, GLdouble v) +{ + SIG( "glEvalCoord2d" ); + dllEvalCoord2d( u, v ); +} +static void APIENTRY logEvalCoord2dv(const GLdouble *u) +{ + SIG( "glEvalCoord2dv" ); + dllEvalCoord2dv( u ); +} +static void APIENTRY logEvalCoord2f(GLfloat u, GLfloat v) +{ + SIG( "glEvalCoord2f" ); + dllEvalCoord2f( u, v ); +} +static void APIENTRY logEvalCoord2fv(const GLfloat *u) +{ + SIG( "glEvalCoord2fv" ); + dllEvalCoord2fv( u ); +} + +static void APIENTRY logEvalMesh1(GLenum mode, GLint i1, GLint i2) +{ + SIG( "glEvalMesh1" ); + dllEvalMesh1( mode, i1, i2 ); +} +static void APIENTRY logEvalMesh2(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2) +{ + SIG( "glEvalMesh2" ); + dllEvalMesh2( mode, i1, i2, j1, j2 ); +} +static void APIENTRY logEvalPoint1(GLint i) +{ + SIG( "glEvalPoint1" ); + dllEvalPoint1( i ); +} +static void APIENTRY logEvalPoint2(GLint i, GLint j) +{ + SIG( "glEvalPoint2" ); + dllEvalPoint2( i, j ); +} + +static void APIENTRY logFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer) +{ + SIG( "glFeedbackBuffer" ); + dllFeedbackBuffer( size, type, buffer ); +} + +static void APIENTRY logFinish(void) +{ + SIG( "glFinish" ); + dllFinish(); +} + +static void APIENTRY logFlush(void) +{ + SIG( "glFlush" ); + dllFlush(); +} + +static void APIENTRY logFogf(GLenum pname, GLfloat param) +{ + SIG( "glFogf" ); + dllFogf( pname, param ); +} + +static void APIENTRY logFogfv(GLenum pname, const GLfloat *params) +{ + SIG( "glFogfv" ); + dllFogfv( pname, params ); +} + +static void APIENTRY logFogi(GLenum pname, GLint param) +{ + SIG( "glFogi" ); + dllFogi( pname, param ); +} + +static void APIENTRY logFogiv(GLenum pname, const GLint *params) +{ + SIG( "glFogiv" ); + dllFogiv( pname, params ); +} + +static void APIENTRY logFrontFace(GLenum mode) +{ + SIG( "glFrontFace" ); + dllFrontFace( mode ); +} + +static void APIENTRY logFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + SIG( "glFrustum" ); + dllFrustum( left, right, bottom, top, zNear, zFar ); +} + +static GLuint APIENTRY logGenLists(GLsizei range) +{ + SIG( "glGenLists" ); + return dllGenLists( range ); +} + +static void APIENTRY logGenTextures(GLsizei n, GLuint *textures) +{ + SIG( "glGenTextures" ); + dllGenTextures( n, textures ); +} + +static void APIENTRY logGetBooleanv(GLenum pname, GLboolean *params) +{ + SIG( "glGetBooleanv" ); + dllGetBooleanv( pname, params ); +} + +static void APIENTRY logGetClipPlane(GLenum plane, GLdouble *equation) +{ + SIG( "glGetClipPlane" ); + dllGetClipPlane( plane, equation ); +} + +static void APIENTRY logGetDoublev(GLenum pname, GLdouble *params) +{ + SIG( "glGetDoublev" ); + dllGetDoublev( pname, params ); +} + +static GLenum APIENTRY logGetError(void) +{ + SIG( "glGetError" ); + return dllGetError(); +} + +static void APIENTRY logGetFloatv(GLenum pname, GLfloat *params) +{ + SIG( "glGetFloatv" ); + dllGetFloatv( pname, params ); +} + +static void APIENTRY logGetIntegerv(GLenum pname, GLint *params) +{ + SIG( "glGetIntegerv" ); + dllGetIntegerv( pname, params ); +} + +static void APIENTRY logGetLightfv(GLenum light, GLenum pname, GLfloat *params) +{ + SIG( "glGetLightfv" ); + dllGetLightfv( light, pname, params ); +} + +static void APIENTRY logGetLightiv(GLenum light, GLenum pname, GLint *params) +{ + SIG( "glGetLightiv" ); + dllGetLightiv( light, pname, params ); +} + +static void APIENTRY logGetMapdv(GLenum target, GLenum query, GLdouble *v) +{ + SIG( "glGetMapdv" ); + dllGetMapdv( target, query, v ); +} + +static void APIENTRY logGetMapfv(GLenum target, GLenum query, GLfloat *v) +{ + SIG( "glGetMapfv" ); + dllGetMapfv( target, query, v ); +} + +static void APIENTRY logGetMapiv(GLenum target, GLenum query, GLint *v) +{ + SIG( "glGetMapiv" ); + dllGetMapiv( target, query, v ); +} + +static void APIENTRY logGetMaterialfv(GLenum face, GLenum pname, GLfloat *params) +{ + SIG( "glGetMaterialfv" ); + dllGetMaterialfv( face, pname, params ); +} + +static void APIENTRY logGetMaterialiv(GLenum face, GLenum pname, GLint *params) +{ + SIG( "glGetMaterialiv" ); + dllGetMaterialiv( face, pname, params ); +} + +static void APIENTRY logGetPixelMapfv(GLenum map, GLfloat *values) +{ + SIG( "glGetPixelMapfv" ); + dllGetPixelMapfv( map, values ); +} + +static void APIENTRY logGetPixelMapuiv(GLenum map, GLuint *values) +{ + SIG( "glGetPixelMapuiv" ); + dllGetPixelMapuiv( map, values ); +} + +static void APIENTRY logGetPixelMapusv(GLenum map, GLushort *values) +{ + SIG( "glGetPixelMapusv" ); + dllGetPixelMapusv( map, values ); +} + +static void APIENTRY logGetPointerv(GLenum pname, GLvoid* *params) +{ + SIG( "glGetPointerv" ); + dllGetPointerv( pname, params ); +} + +static void APIENTRY logGetPolygonStipple(GLubyte *mask) +{ + SIG( "glGetPolygonStipple" ); + dllGetPolygonStipple( mask ); +} + +static const GLubyte * APIENTRY logGetString(GLenum name) +{ + SIG( "glGetString" ); + return dllGetString( name ); +} + +static void APIENTRY logGetTexEnvfv(GLenum target, GLenum pname, GLfloat *params) +{ + SIG( "glGetTexEnvfv" ); + dllGetTexEnvfv( target, pname, params ); +} + +static void APIENTRY logGetTexEnviv(GLenum target, GLenum pname, GLint *params) +{ + SIG( "glGetTexEnviv" ); + dllGetTexEnviv( target, pname, params ); +} + +static void APIENTRY logGetTexGendv(GLenum coord, GLenum pname, GLdouble *params) +{ + SIG( "glGetTexGendv" ); + dllGetTexGendv( coord, pname, params ); +} + +static void APIENTRY logGetTexGenfv(GLenum coord, GLenum pname, GLfloat *params) +{ + SIG( "glGetTexGenfv" ); + dllGetTexGenfv( coord, pname, params ); +} + +static void APIENTRY logGetTexGeniv(GLenum coord, GLenum pname, GLint *params) +{ + SIG( "glGetTexGeniv" ); + dllGetTexGeniv( coord, pname, params ); +} + +static void APIENTRY logGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void *pixels) +{ + SIG( "glGetTexImage" ); + dllGetTexImage( target, level, format, type, pixels ); +} +static void APIENTRY logGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params ) +{ + SIG( "glGetTexLevelParameterfv" ); + dllGetTexLevelParameterfv( target, level, pname, params ); +} + +static void APIENTRY logGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) +{ + SIG( "glGetTexLevelParameteriv" ); + dllGetTexLevelParameteriv( target, level, pname, params ); +} + +static void APIENTRY logGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params) +{ + SIG( "glGetTexParameterfv" ); + dllGetTexParameterfv( target, pname, params ); +} + +static void APIENTRY logGetTexParameteriv(GLenum target, GLenum pname, GLint *params) +{ + SIG( "glGetTexParameteriv" ); + dllGetTexParameteriv( target, pname, params ); +} + +static void APIENTRY logHint(GLenum target, GLenum mode) +{ + fprintf( winState.log_fp, "glHint( 0x%x, 0x%x )\n", target, mode ); + fflush(winState.log_fp); + dllHint( target, mode ); +} + +static void APIENTRY logIndexMask(GLuint mask) +{ + SIG( "glIndexMask" ); + dllIndexMask( mask ); +} + +static void APIENTRY logIndexPointer(GLenum type, GLsizei stride, const void *pointer) +{ + SIG( "glIndexPointer" ); + dllIndexPointer( type, stride, pointer ); +} + +static void APIENTRY logIndexd(GLdouble c) +{ + SIG( "glIndexd" ); + dllIndexd( c ); +} + +static void APIENTRY logIndexdv(const GLdouble *c) +{ + SIG( "glIndexdv" ); + dllIndexdv( c ); +} + +static void APIENTRY logIndexf(GLfloat c) +{ + SIG( "glIndexf" ); + dllIndexf( c ); +} + +static void APIENTRY logIndexfv(const GLfloat *c) +{ + SIG( "glIndexfv" ); + dllIndexfv( c ); +} + +static void APIENTRY logIndexi(GLint c) +{ + SIG( "glIndexi" ); + dllIndexi( c ); +} + +static void APIENTRY logIndexiv(const GLint *c) +{ + SIG( "glIndexiv" ); + dllIndexiv( c ); +} + +static void APIENTRY logIndexs(GLshort c) +{ + SIG( "glIndexs" ); + dllIndexs( c ); +} + +static void APIENTRY logIndexsv(const GLshort *c) +{ + SIG( "glIndexsv" ); + dllIndexsv( c ); +} + +static void APIENTRY logIndexub(GLubyte c) +{ + SIG( "glIndexub" ); + dllIndexub( c ); +} + +static void APIENTRY logIndexubv(const GLubyte *c) +{ + SIG( "glIndexubv" ); + dllIndexubv( c ); +} + +static void APIENTRY logInitNames(void) +{ + SIG( "glInitNames" ); + dllInitNames(); +} + +static void APIENTRY logInterleavedArrays(GLenum format, GLsizei stride, const void *pointer) +{ + SIG( "glInterleavedArrays" ); + dllInterleavedArrays( format, stride, pointer ); +} + +static GLboolean APIENTRY logIsEnabled(GLenum cap) +{ + SIG( "glIsEnabled" ); + return dllIsEnabled( cap ); +} +static GLboolean APIENTRY logIsList(GLuint list) +{ + SIG( "glIsList" ); + return dllIsList( list ); +} +static GLboolean APIENTRY logIsTexture(GLuint texture) +{ + SIG( "glIsTexture" ); + return dllIsTexture( texture ); +} + +static void APIENTRY logLightModelf(GLenum pname, GLfloat param) +{ + SIG( "glLightModelf" ); + dllLightModelf( pname, param ); +} + +static void APIENTRY logLightModelfv(GLenum pname, const GLfloat *params) +{ + SIG( "glLightModelfv" ); + dllLightModelfv( pname, params ); +} + +static void APIENTRY logLightModeli(GLenum pname, GLint param) +{ + SIG( "glLightModeli" ); + dllLightModeli( pname, param ); + +} + +static void APIENTRY logLightModeliv(GLenum pname, const GLint *params) +{ + SIG( "glLightModeliv" ); + dllLightModeliv( pname, params ); +} + +static void APIENTRY logLightf(GLenum light, GLenum pname, GLfloat param) +{ + SIG( "glLightf" ); + dllLightf( light, pname, param ); +} + +static void APIENTRY logLightfv(GLenum light, GLenum pname, const GLfloat *params) +{ + SIG( "glLightfv" ); + dllLightfv( light, pname, params ); +} + +static void APIENTRY logLighti(GLenum light, GLenum pname, GLint param) +{ + SIG( "glLighti" ); + dllLighti( light, pname, param ); +} + +static void APIENTRY logLightiv(GLenum light, GLenum pname, const GLint *params) +{ + SIG( "glLightiv" ); + dllLightiv( light, pname, params ); +} + +static void APIENTRY logLineStipple(GLint factor, GLushort pattern) +{ + SIG( "glLineStipple" ); + dllLineStipple( factor, pattern ); +} + +static void APIENTRY logLineWidth(GLfloat width) +{ + SIG( "glLineWidth" ); + dllLineWidth( width ); +} + +static void APIENTRY logListBase(GLuint base) +{ + SIG( "glListBase" ); + dllListBase( base ); +} + +static void APIENTRY logLoadIdentity(void) +{ + SIG( "glLoadIdentity" ); + dllLoadIdentity(); +} + +static void APIENTRY logLoadMatrixd(const GLdouble *m) +{ + SIG( "glLoadMatrixd" ); + dllLoadMatrixd( m ); +} + +static void APIENTRY logLoadMatrixf(const GLfloat *m) +{ + SIG( "glLoadMatrixf" ); + dllLoadMatrixf( m ); +} + +static void APIENTRY logLoadName(GLuint name) +{ + SIG( "glLoadName" ); + dllLoadName( name ); +} + +static void APIENTRY logLogicOp(GLenum opcode) +{ + SIG( "glLogicOp" ); + dllLogicOp( opcode ); +} + +static void APIENTRY logMap1d(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points) +{ + SIG( "glMap1d" ); + dllMap1d( target, u1, u2, stride, order, points ); +} + +static void APIENTRY logMap1f(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points) +{ + SIG( "glMap1f" ); + dllMap1f( target, u1, u2, stride, order, points ); +} + +static void APIENTRY logMap2d(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points) +{ + SIG( "glMap2d" ); + dllMap2d( target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); +} + +static void APIENTRY logMap2f(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points) +{ + SIG( "glMap2f" ); + dllMap2f( target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); +} + +static void APIENTRY logMapGrid1d(GLint un, GLdouble u1, GLdouble u2) +{ + SIG( "glMapGrid1d" ); + dllMapGrid1d( un, u1, u2 ); +} + +static void APIENTRY logMapGrid1f(GLint un, GLfloat u1, GLfloat u2) +{ + SIG( "glMapGrid1f" ); + dllMapGrid1f( un, u1, u2 ); +} + +static void APIENTRY logMapGrid2d(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2) +{ + SIG( "glMapGrid2d" ); + dllMapGrid2d( un, u1, u2, vn, v1, v2 ); +} +static void APIENTRY logMapGrid2f(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2) +{ + SIG( "glMapGrid2f" ); + dllMapGrid2f( un, u1, u2, vn, v1, v2 ); +} +static void APIENTRY logMaterialf(GLenum face, GLenum pname, GLfloat param) +{ + SIG( "glMaterialf" ); + dllMaterialf( face, pname, param ); +} +static void APIENTRY logMaterialfv(GLenum face, GLenum pname, const GLfloat *params) +{ + SIG( "glMaterialfv" ); + dllMaterialfv( face, pname, params ); +} + +static void APIENTRY logMateriali(GLenum face, GLenum pname, GLint param) +{ + SIG( "glMateriali" ); + dllMateriali( face, pname, param ); +} + +static void APIENTRY logMaterialiv(GLenum face, GLenum pname, const GLint *params) +{ + SIG( "glMaterialiv" ); + dllMaterialiv( face, pname, params ); +} + +static void APIENTRY logMatrixMode(GLenum mode) +{ + SIG( "glMatrixMode" ); + dllMatrixMode( mode ); +} + +static void APIENTRY logMultMatrixd(const GLdouble *m) +{ + SIG( "glMultMatrixd" ); + dllMultMatrixd( m ); +} + +static void APIENTRY logMultMatrixf(const GLfloat *m) +{ + SIG( "glMultMatrixf" ); + dllMultMatrixf( m ); +} + +static void APIENTRY logNewList(GLuint list, GLenum mode) +{ + SIG( "glNewList" ); + dllNewList( list, mode ); +} + +static void APIENTRY logNormal3b(GLbyte nx, GLbyte ny, GLbyte nz) +{ + SIG ("glNormal3b" ); + dllNormal3b( nx, ny, nz ); +} + +static void APIENTRY logNormal3bv(const GLbyte *v) +{ + SIG( "glNormal3bv" ); + dllNormal3bv( v ); +} + +static void APIENTRY logNormal3d(GLdouble nx, GLdouble ny, GLdouble nz) +{ + SIG( "glNormal3d" ); + dllNormal3d( nx, ny, nz ); +} + +static void APIENTRY logNormal3dv(const GLdouble *v) +{ + SIG( "glNormal3dv" ); + dllNormal3dv( v ); +} + +static void APIENTRY logNormal3f(GLfloat nx, GLfloat ny, GLfloat nz) +{ + SIG( "glNormal3f" ); + dllNormal3f( nx, ny, nz ); +} + +static void APIENTRY logNormal3fv(const GLfloat *v) +{ + SIG( "glNormal3fv" ); + dllNormal3fv( v ); +} +static void APIENTRY logNormal3i(GLint nx, GLint ny, GLint nz) +{ + SIG( "glNormal3i" ); + dllNormal3i( nx, ny, nz ); +} +static void APIENTRY logNormal3iv(const GLint *v) +{ + SIG( "glNormal3iv" ); + dllNormal3iv( v ); +} +static void APIENTRY logNormal3s(GLshort nx, GLshort ny, GLshort nz) +{ + SIG( "glNormal3s" ); + dllNormal3s( nx, ny, nz ); +} +static void APIENTRY logNormal3sv(const GLshort *v) +{ + SIG( "glNormal3sv" ); + dllNormal3sv( v ); +} +static void APIENTRY logNormalPointer(GLenum type, GLsizei stride, const void *pointer) +{ + SIG( "glNormalPointer" ); + dllNormalPointer( type, stride, pointer ); +} +static void APIENTRY logOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + SIG( "glOrtho" ); + dllOrtho( left, right, bottom, top, zNear, zFar ); +} + +static void APIENTRY logPassThrough(GLfloat token) +{ + SIG( "glPassThrough" ); + dllPassThrough( token ); +} + +static void APIENTRY logPixelMapfv(GLenum map, GLsizei mapsize, const GLfloat *values) +{ + SIG( "glPixelMapfv" ); + dllPixelMapfv( map, mapsize, values ); +} + +static void APIENTRY logPixelMapuiv(GLenum map, GLsizei mapsize, const GLuint *values) +{ + SIG( "glPixelMapuiv" ); + dllPixelMapuiv( map, mapsize, values ); +} + +static void APIENTRY logPixelMapusv(GLenum map, GLsizei mapsize, const GLushort *values) +{ + SIG( "glPixelMapusv" ); + dllPixelMapusv( map, mapsize, values ); +} +static void APIENTRY logPixelStoref(GLenum pname, GLfloat param) +{ + SIG( "glPixelStoref" ); + dllPixelStoref( pname, param ); +} +static void APIENTRY logPixelStorei(GLenum pname, GLint param) +{ + SIG( "glPixelStorei" ); + dllPixelStorei( pname, param ); +} +static void APIENTRY logPixelTransferf(GLenum pname, GLfloat param) +{ + SIG( "glPixelTransferf" ); + dllPixelTransferf( pname, param ); +} + +static void APIENTRY logPixelTransferi(GLenum pname, GLint param) +{ + SIG( "glPixelTransferi" ); + dllPixelTransferi( pname, param ); +} + +static void APIENTRY logPixelZoom(GLfloat xfactor, GLfloat yfactor) +{ + SIG( "glPixelZoom" ); + dllPixelZoom( xfactor, yfactor ); +} + +static void APIENTRY logPointSize(GLfloat size) +{ + SIG( "glPointSize" ); + dllPointSize( size ); +} + +static void APIENTRY logPolygonMode(GLenum face, GLenum mode) +{ + fprintf( winState.log_fp, "glPolygonMode( 0x%x, 0x%x )\n", face, mode ); + fflush(winState.log_fp); + dllPolygonMode( face, mode ); +} + +static void APIENTRY logPolygonOffset(GLfloat factor, GLfloat units) +{ + SIG( "glPolygonOffset" ); + dllPolygonOffset( factor, units ); +} +static void APIENTRY logPolygonStipple(const GLubyte *mask ) +{ + SIG( "glPolygonStipple" ); + dllPolygonStipple( mask ); +} +static void APIENTRY logPopAttrib(void) +{ + SIG( "glPopAttrib" ); + dllPopAttrib(); +} + +static void APIENTRY logPopClientAttrib(void) +{ + SIG( "glPopClientAttrib" ); + dllPopClientAttrib(); +} + +static void APIENTRY logPopMatrix(void) +{ + SIG( "glPopMatrix" ); + dllPopMatrix(); +} + +static void APIENTRY logPopName(void) +{ + SIG( "glPopName" ); + dllPopName(); +} + +static void APIENTRY logPrioritizeTextures(GLsizei n, const GLuint *textures, const GLclampf *priorities) +{ + SIG( "glPrioritizeTextures" ); + dllPrioritizeTextures( n, textures, priorities ); +} + +static void APIENTRY logPushAttrib(GLbitfield mask) +{ + SIG( "glPushAttrib" ); + dllPushAttrib( mask ); +} + +static void APIENTRY logPushClientAttrib(GLbitfield mask) +{ + SIG( "glPushClientAttrib" ); + dllPushClientAttrib( mask ); +} + +static void APIENTRY logPushMatrix(void) +{ + SIG( "glPushMatrix" ); + dllPushMatrix(); +} + +static void APIENTRY logPushName(GLuint name) +{ + SIG( "glPushName" ); + dllPushName( name ); +} + +static void APIENTRY logRasterPos2d(GLdouble x, GLdouble y) +{ + SIG ("glRasterPot2d" ); + dllRasterPos2d( x, y ); +} + +static void APIENTRY logRasterPos2dv(const GLdouble *v) +{ + SIG( "glRasterPos2dv" ); + dllRasterPos2dv( v ); +} + +static void APIENTRY logRasterPos2f(GLfloat x, GLfloat y) +{ + SIG( "glRasterPos2f" ); + dllRasterPos2f( x, y ); +} +static void APIENTRY logRasterPos2fv(const GLfloat *v) +{ + SIG( "glRasterPos2dv" ); + dllRasterPos2fv( v ); +} +static void APIENTRY logRasterPos2i(GLint x, GLint y) +{ + SIG( "glRasterPos2if" ); + dllRasterPos2i( x, y ); +} +static void APIENTRY logRasterPos2iv(const GLint *v) +{ + SIG( "glRasterPos2iv" ); + dllRasterPos2iv( v ); +} +static void APIENTRY logRasterPos2s(GLshort x, GLshort y) +{ + SIG( "glRasterPos2s" ); + dllRasterPos2s( x, y ); +} +static void APIENTRY logRasterPos2sv(const GLshort *v) +{ + SIG( "glRasterPos2sv" ); + dllRasterPos2sv( v ); +} +static void APIENTRY logRasterPos3d(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glRasterPos3d" ); + dllRasterPos3d( x, y, z ); +} +static void APIENTRY logRasterPos3dv(const GLdouble *v) +{ + SIG( "glRasterPos3dv" ); + dllRasterPos3dv( v ); +} +static void APIENTRY logRasterPos3f(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glRasterPos3f" ); + dllRasterPos3f( x, y, z ); +} +static void APIENTRY logRasterPos3fv(const GLfloat *v) +{ + SIG( "glRasterPos3fv" ); + dllRasterPos3fv( v ); +} +static void APIENTRY logRasterPos3i(GLint x, GLint y, GLint z) +{ + SIG( "glRasterPos3i" ); + dllRasterPos3i( x, y, z ); +} +static void APIENTRY logRasterPos3iv(const GLint *v) +{ + SIG( "glRasterPos3iv" ); + dllRasterPos3iv( v ); +} +static void APIENTRY logRasterPos3s(GLshort x, GLshort y, GLshort z) +{ + SIG( "glRasterPos3s" ); + dllRasterPos3s( x, y, z ); +} +static void APIENTRY logRasterPos3sv(const GLshort *v) +{ + SIG( "glRasterPos3sv" ); + dllRasterPos3sv( v ); +} +static void APIENTRY logRasterPos4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + SIG( "glRasterPos4d" ); + dllRasterPos4d( x, y, z, w ); +} +static void APIENTRY logRasterPos4dv(const GLdouble *v) +{ + SIG( "glRasterPos4dv" ); + dllRasterPos4dv( v ); +} +static void APIENTRY logRasterPos4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + SIG( "glRasterPos4f" ); + dllRasterPos4f( x, y, z, w ); +} +static void APIENTRY logRasterPos4fv(const GLfloat *v) +{ + SIG( "glRasterPos4fv" ); + dllRasterPos4fv( v ); +} +static void APIENTRY logRasterPos4i(GLint x, GLint y, GLint z, GLint w) +{ + SIG( "glRasterPos4i" ); + dllRasterPos4i( x, y, z, w ); +} +static void APIENTRY logRasterPos4iv(const GLint *v) +{ + SIG( "glRasterPos4iv" ); + dllRasterPos4iv( v ); +} +static void APIENTRY logRasterPos4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + SIG( "glRasterPos4s" ); + dllRasterPos4s( x, y, z, w ); +} +static void APIENTRY logRasterPos4sv(const GLshort *v) +{ + SIG( "glRasterPos4sv" ); + dllRasterPos4sv( v ); +} +static void APIENTRY logReadBuffer(GLenum mode) +{ + SIG( "glReadBuffer" ); + dllReadBuffer( mode ); +} +static void APIENTRY logReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels) +{ + SIG( "glReadPixels" ); + dllReadPixels( x, y, width, height, format, type, pixels ); +} + +static void APIENTRY logRectd(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2) +{ + SIG( "glRectd" ); + dllRectd( x1, y1, x2, y2 ); +} + +static void APIENTRY logRectdv(const GLdouble *v1, const GLdouble *v2) +{ + SIG( "glRectdv" ); + dllRectdv( v1, v2 ); +} + +static void APIENTRY logRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) +{ + SIG( "glRectf" ); + dllRectf( x1, y1, x2, y2 ); +} + +static void APIENTRY logRectfv(const GLfloat *v1, const GLfloat *v2) +{ + SIG( "glRectfv" ); + dllRectfv( v1, v2 ); +} +static void APIENTRY logRecti(GLint x1, GLint y1, GLint x2, GLint y2) +{ + SIG( "glRecti" ); + dllRecti( x1, y1, x2, y2 ); +} +static void APIENTRY logRectiv(const GLint *v1, const GLint *v2) +{ + SIG( "glRectiv" ); + dllRectiv( v1, v2 ); +} +static void APIENTRY logRects(GLshort x1, GLshort y1, GLshort x2, GLshort y2) +{ + SIG( "glRects" ); + dllRects( x1, y1, x2, y2 ); +} +static void APIENTRY logRectsv(const GLshort *v1, const GLshort *v2) +{ + SIG( "glRectsv" ); + dllRectsv( v1, v2 ); +} +static GLint APIENTRY logRenderMode(GLenum mode) +{ + SIG( "glRenderMode" ); + return dllRenderMode( mode ); +} +static void APIENTRY logRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glRotated" ); + dllRotated( angle, x, y, z ); +} + +static void APIENTRY logRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glRotatef" ); + dllRotatef( angle, x, y, z ); +} + +static void APIENTRY logScaled(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glScaled" ); + dllScaled( x, y, z ); +} + +static void APIENTRY logScalef(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glScalef" ); + dllScalef( x, y, z ); +} + +static void APIENTRY logScissor(GLint x, GLint y, GLsizei width, GLsizei height) +{ + fprintf( winState.log_fp, "glScissor( %d, %d, %d, %d )\n", x, y, width, height ); + fflush(winState.log_fp); + dllScissor( x, y, width, height ); +} + +static void APIENTRY logSelectBuffer(GLsizei size, GLuint *buffer) +{ + SIG( "glSelectBuffer" ); + dllSelectBuffer( size, buffer ); +} + +static void APIENTRY logShadeModel(GLenum mode) +{ + SIG( "glShadeModel" ); + dllShadeModel( mode ); +} + +static void APIENTRY logStencilFunc(GLenum func, GLint ref, GLuint mask) +{ + SIG( "glStencilFunc" ); + dllStencilFunc( func, ref, mask ); +} + +static void APIENTRY logStencilMask(GLuint mask) +{ + SIG( "glStencilMask" ); + dllStencilMask( mask ); +} + +static void APIENTRY logStencilOp(GLenum fail, GLenum zfail, GLenum zpass) +{ + SIG( "glStencilOp" ); + dllStencilOp( fail, zfail, zpass ); +} + +static void APIENTRY logTexCoord1d(GLdouble s) +{ + SIG( "glTexCoord1d" ); + dllTexCoord1d( s ); +} + +static void APIENTRY logTexCoord1dv(const GLdouble *v) +{ + SIG( "glTexCoord1dv" ); + dllTexCoord1dv( v ); +} + +static void APIENTRY logTexCoord1f(GLfloat s) +{ + SIG( "glTexCoord1f" ); + dllTexCoord1f( s ); +} +static void APIENTRY logTexCoord1fv(const GLfloat *v) +{ + SIG( "glTexCoord1fv" ); + dllTexCoord1fv( v ); +} +static void APIENTRY logTexCoord1i(GLint s) +{ + SIG( "glTexCoord1i" ); + dllTexCoord1i( s ); +} +static void APIENTRY logTexCoord1iv(const GLint *v) +{ + SIG( "glTexCoord1iv" ); + dllTexCoord1iv( v ); +} +static void APIENTRY logTexCoord1s(GLshort s) +{ + SIG( "glTexCoord1s" ); + dllTexCoord1s( s ); +} +static void APIENTRY logTexCoord1sv(const GLshort *v) +{ + SIG( "glTexCoord1sv" ); + dllTexCoord1sv( v ); +} +static void APIENTRY logTexCoord2d(GLdouble s, GLdouble t) +{ + SIG( "glTexCoord2d" ); + dllTexCoord2d( s, t ); +} + +static void APIENTRY logTexCoord2dv(const GLdouble *v) +{ + SIG( "glTexCoord2dv" ); + dllTexCoord2dv( v ); +} +static void APIENTRY logTexCoord2f(GLfloat s, GLfloat t) +{ + SIG( "glTexCoord2f" ); + dllTexCoord2f( s, t ); +} +static void APIENTRY logTexCoord2fv(const GLfloat *v) +{ + SIG( "glTexCoord2fv" ); + dllTexCoord2fv( v ); +} +static void APIENTRY logTexCoord2i(GLint s, GLint t) +{ + SIG( "glTexCoord2i" ); + dllTexCoord2i( s, t ); +} +static void APIENTRY logTexCoord2iv(const GLint *v) +{ + SIG( "glTexCoord2iv" ); + dllTexCoord2iv( v ); +} +static void APIENTRY logTexCoord2s(GLshort s, GLshort t) +{ + SIG( "glTexCoord2s" ); + dllTexCoord2s( s, t ); +} +static void APIENTRY logTexCoord2sv(const GLshort *v) +{ + SIG( "glTexCoord2sv" ); + dllTexCoord2sv( v ); +} +static void APIENTRY logTexCoord3d(GLdouble s, GLdouble t, GLdouble r) +{ + SIG( "glTexCoord3d" ); + dllTexCoord3d( s, t, r ); +} +static void APIENTRY logTexCoord3dv(const GLdouble *v) +{ + SIG( "glTexCoord3dv" ); + dllTexCoord3dv( v ); +} +static void APIENTRY logTexCoord3f(GLfloat s, GLfloat t, GLfloat r) +{ + SIG( "glTexCoord3f" ); + dllTexCoord3f( s, t, r ); +} +static void APIENTRY logTexCoord3fv(const GLfloat *v) +{ + SIG( "glTexCoord3fv" ); + dllTexCoord3fv( v ); +} +static void APIENTRY logTexCoord3i(GLint s, GLint t, GLint r) +{ + SIG( "glTexCoord3i" ); + dllTexCoord3i( s, t, r ); +} +static void APIENTRY logTexCoord3iv(const GLint *v) +{ + SIG( "glTexCoord3iv" ); + dllTexCoord3iv( v ); +} +static void APIENTRY logTexCoord3s(GLshort s, GLshort t, GLshort r) +{ + SIG( "glTexCoord3s" ); + dllTexCoord3s( s, t, r ); +} +static void APIENTRY logTexCoord3sv(const GLshort *v) +{ + SIG( "glTexCoord3sv" ); + dllTexCoord3sv( v ); +} +static void APIENTRY logTexCoord4d(GLdouble s, GLdouble t, GLdouble r, GLdouble q) +{ + SIG( "glTexCoord4d" ); + dllTexCoord4d( s, t, r, q ); +} +static void APIENTRY logTexCoord4dv(const GLdouble *v) +{ + SIG( "glTexCoord4dv" ); + dllTexCoord4dv( v ); +} +static void APIENTRY logTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q) +{ + SIG( "glTexCoord4f" ); + dllTexCoord4f( s, t, r, q ); +} +static void APIENTRY logTexCoord4fv(const GLfloat *v) +{ + SIG( "glTexCoord4fv" ); + dllTexCoord4fv( v ); +} +static void APIENTRY logTexCoord4i(GLint s, GLint t, GLint r, GLint q) +{ + SIG( "glTexCoord4i" ); + dllTexCoord4i( s, t, r, q ); +} +static void APIENTRY logTexCoord4iv(const GLint *v) +{ + SIG( "glTexCoord4iv" ); + dllTexCoord4iv( v ); +} +static void APIENTRY logTexCoord4s(GLshort s, GLshort t, GLshort r, GLshort q) +{ + SIG( "glTexCoord4s" ); + dllTexCoord4s( s, t, r, q ); +} +static void APIENTRY logTexCoord4sv(const GLshort *v) +{ + SIG( "glTexCoord4sv" ); + dllTexCoord4sv( v ); +} +static void APIENTRY logTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) +{ + fprintf( winState.log_fp, "glTexCoordPointer( %d, %s, %d, MEM )\n", size, TypeToString( type ), stride ); + fflush(winState.log_fp); + dllTexCoordPointer( size, type, stride, pointer ); +} + +static void APIENTRY logTexEnvf(GLenum target, GLenum pname, GLfloat param) +{ + fprintf( winState.log_fp, "glTexEnvf( 0x%x, 0x%x, %f )\n", target, pname, param ); + fflush(winState.log_fp); + dllTexEnvf( target, pname, param ); +} + +static void APIENTRY logTexEnvfv(GLenum target, GLenum pname, const GLfloat *params) +{ + SIG( "glTexEnvfv" ); + dllTexEnvfv( target, pname, params ); +} + +static void APIENTRY logTexEnvi(GLenum target, GLenum pname, GLint param) +{ + fprintf( winState.log_fp, "glTexEnvi( 0x%x, 0x%x, 0x%x )\n", target, pname, param ); + fflush(winState.log_fp); + dllTexEnvi( target, pname, param ); +} +static void APIENTRY logTexEnviv(GLenum target, GLenum pname, const GLint *params) +{ + SIG( "glTexEnviv" ); + dllTexEnviv( target, pname, params ); +} + +static void APIENTRY logTexGend(GLenum coord, GLenum pname, GLdouble param) +{ + SIG( "glTexGend" ); + dllTexGend( coord, pname, param ); +} + +static void APIENTRY logTexGendv(GLenum coord, GLenum pname, const GLdouble *params) +{ + SIG( "glTexGendv" ); + dllTexGendv( coord, pname, params ); +} + +static void APIENTRY logTexGenf(GLenum coord, GLenum pname, GLfloat param) +{ + SIG( "glTexGenf" ); + dllTexGenf( coord, pname, param ); +} +static void APIENTRY logTexGenfv(GLenum coord, GLenum pname, const GLfloat *params) +{ +// fprintf( winState.log_fp, "glTexGenfv( %s, (%f, %f, %f, %f) )\n", CoordToString( coord ), params[0], params[1], params[2], params[3]); + fflush(winState.log_fp); + fprintf( winState.log_fp, "glTexGenfv( %s, MEM )\n", CoordToString( coord )); + fflush(winState.log_fp); + dllTexGenfv( coord, pname, params ); +} +static void APIENTRY logTexGeni(GLenum coord, GLenum pname, GLint param) +{ + SIG( "glTexGeni" ); + dllTexGeni( coord, pname, param ); +} +static void APIENTRY logTexGeniv(GLenum coord, GLenum pname, const GLint *params) +{ + SIG( "glTexGeniv" ); + dllTexGeniv( coord, pname, params ); +} +static void APIENTRY logTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexImage1D" ); + dllTexImage1D( target, level, internalformat, width, border, format, type, pixels ); +} +static void APIENTRY logTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexImage2D" ); + dllTexImage2D( target, level, internalformat, width, height, border, format, type, pixels ); +} + +static void APIENTRY logTexParameterf(GLenum target, GLenum pname, GLfloat param) +{ + fprintf( winState.log_fp, "glTexParameterf( 0x%x, 0x%x, %f )\n", target, pname, param ); + fflush(winState.log_fp); + dllTexParameterf( target, pname, param ); +} + +static void APIENTRY logTexParameterfv(GLenum target, GLenum pname, const GLfloat *params) +{ + SIG( "glTexParameterfv" ); + dllTexParameterfv( target, pname, params ); +} +static void APIENTRY logTexParameteri(GLenum target, GLenum pname, GLint param) +{ + fprintf( winState.log_fp, "glTexParameteri( 0x%x, 0x%x, 0x%x )\n", target, pname, param ); + fflush(winState.log_fp); + dllTexParameteri( target, pname, param ); +} +static void APIENTRY logTexParameteriv(GLenum target, GLenum pname, const GLint *params) +{ + SIG( "glTexParameteriv" ); + dllTexParameteriv( target, pname, params ); +} +static void APIENTRY logTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexSubImage1D" ); + dllTexSubImage1D( target, level, xoffset, width, format, type, pixels ); +} +static void APIENTRY logTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexSubImage2D" ); + dllTexSubImage2D( target, level, xoffset, yoffset, width, height, format, type, pixels ); +} +static void APIENTRY logTranslated(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glTranslated" ); + dllTranslated( x, y, z ); +} + +static void APIENTRY logTranslatef(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glTranslatef" ); + dllTranslatef( x, y, z ); +} + +static void APIENTRY logVertex2d(GLdouble x, GLdouble y) +{ + SIG( "glVertex2d" ); + dllVertex2d( x, y ); +} + +static void APIENTRY logVertex2dv(const GLdouble *v) +{ + SIG( "glVertex2dv" ); + dllVertex2dv( v ); +} +static void APIENTRY logVertex2f(GLfloat x, GLfloat y) +{ + SIG( "glVertex2f" ); + dllVertex2f( x, y ); +} +static void APIENTRY logVertex2fv(const GLfloat *v) +{ + SIG( "glVertex2fv" ); + dllVertex2fv( v ); +} +static void APIENTRY logVertex2i(GLint x, GLint y) +{ + SIG( "glVertex2i" ); + dllVertex2i( x, y ); +} +static void APIENTRY logVertex2iv(const GLint *v) +{ + SIG( "glVertex2iv" ); + dllVertex2iv( v ); +} +static void APIENTRY logVertex2s(GLshort x, GLshort y) +{ + SIG( "glVertex2s" ); + dllVertex2s( x, y ); +} +static void APIENTRY logVertex2sv(const GLshort *v) +{ + SIG( "glVertex2sv" ); + dllVertex2sv( v ); +} +static void APIENTRY logVertex3d(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glVertex3d" ); + dllVertex3d( x, y, z ); +} +static void APIENTRY logVertex3dv(const GLdouble *v) +{ + SIG( "glVertex3dv" ); + dllVertex3dv( v ); +} +static void APIENTRY logVertex3f(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glVertex3f" ); + dllVertex3f( x, y, z ); +} +static void APIENTRY logVertex3fv(const GLfloat *v) +{ + SIG( "glVertex3fv" ); + dllVertex3fv( v ); +} +static void APIENTRY logVertex3i(GLint x, GLint y, GLint z) +{ + SIG( "glVertex3i" ); + dllVertex3i( x, y, z ); +} +static void APIENTRY logVertex3iv(const GLint *v) +{ + SIG( "glVertex3iv" ); + dllVertex3iv( v ); +} +static void APIENTRY logVertex3s(GLshort x, GLshort y, GLshort z) +{ + SIG( "glVertex3s" ); + dllVertex3s( x, y, z ); +} +static void APIENTRY logVertex3sv(const GLshort *v) +{ + SIG( "glVertex3sv" ); + dllVertex3sv( v ); +} +static void APIENTRY logVertex4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + SIG( "glVertex4d" ); + dllVertex4d( x, y, z, w ); +} +static void APIENTRY logVertex4dv(const GLdouble *v) +{ + SIG( "glVertex4dv" ); + dllVertex4dv( v ); +} +static void APIENTRY logVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + SIG( "glVertex4f" ); + dllVertex4f( x, y, z, w ); +} +static void APIENTRY logVertex4fv(const GLfloat *v) +{ + SIG( "glVertex4fv" ); + dllVertex4fv( v ); +} +static void APIENTRY logVertex4i(GLint x, GLint y, GLint z, GLint w) +{ + SIG( "glVertex4i" ); + dllVertex4i( x, y, z, w ); +} +static void APIENTRY logVertex4iv(const GLint *v) +{ + SIG( "glVertex4iv" ); + dllVertex4iv( v ); +} +static void APIENTRY logVertex4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + SIG( "glVertex4s" ); + dllVertex4s( x, y, z, w ); +} +static void APIENTRY logVertex4sv(const GLshort *v) +{ + SIG( "glVertex4sv" ); + dllVertex4sv( v ); +} +static void APIENTRY logVertexPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) +{ + fprintf( winState.log_fp, "glVertexPointer( %d, %s, %d, MEM )\n", size, TypeToString( type ), stride ); + fflush(winState.log_fp); + dllVertexPointer( size, type, stride, pointer ); +} +static void APIENTRY logViewport(GLint x, GLint y, GLsizei width, GLsizei height) +{ + fprintf( winState.log_fp, "glViewport( %d, %d, %d, %d )\n", x, y, width, height ); + fflush(winState.log_fp); + dllViewport( x, y, width, height ); +} + +static void APIENTRY logColorTableEXT(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data) +{ + AssertFatal(dllColorTableEXT != NULL, "Error, shouldn't have called unsupported paletted_texture extension"); + + fprintf(winState.log_fp, "glColorTableEXT(%d, %d, %d, %d, %d, )\n", + target, + internalFormat, + width, + format, + type); + fflush(winState.log_fp); + dllColorTableEXT(target, internalFormat, width, format, type, data); +} + +static void APIENTRY logLockArraysEXT(GLint first, GLsizei count) +{ + AssertFatal(dllLockArraysEXT != NULL, "Error, shouldn't have called unsupported compiled_vertex_array extension"); + + fprintf( winState.log_fp, "glLockArraysEXT( %d, %d )\n", first, count); + fflush(winState.log_fp); + dllLockArraysEXT(first, count); +} + +static void APIENTRY logUnlockArraysEXT() +{ + AssertFatal(dllLockArraysEXT != NULL, "Error, shouldn't have called unsupported compiled_vertex_array extension"); + + SIG("glUnlockArraysEXT"); + dllUnlockArraysEXT(); +} + +/* ARB_multitexture */ + +static const char* gARBMTenums[] = { + "GL_TEXTURE0_ARB", "GL_TEXTURE1_ARB", "GL_TEXTURE2_ARB", + "GL_TEXTURE3_ARB", "GL_TEXTURE4_ARB", "GL_TEXTURE5_ARB", + "GL_TEXTURE6_ARB", "GL_TEXTURE7_ARB", "GL_TEXTURE8_ARB", + "GL_TEXTURE9_ARB", "GL_TEXTURE10_ARB", "GL_TEXTURE11_ARB", + "GL_TEXTURE12_ARB", "GL_TEXTURE13_ARB", "GL_TEXTURE14_ARB", + "GL_TEXTURE15_ARB", "GL_TEXTURE16_ARB", "GL_TEXTURE17_ARB", + "GL_TEXTURE18_ARB", "GL_TEXTURE19_ARB", "GL_TEXTURE20_ARB", + "GL_TEXTURE21_ARB", "GL_TEXTURE22_ARB", "GL_TEXTURE23_ARB", + "GL_TEXTURE24_ARB", "GL_TEXTURE25_ARB", "GL_TEXTURE26_ARB", + "GL_TEXTURE27_ARB", "GL_TEXTURE28_ARB", "GL_TEXTURE29_ARB", + "GL_TEXTURE30_ARB", "GL_TEXTURE31_ARB" +}; + +static void APIENTRY logActiveTextureARB(GLenum target) +{ + U32 index = target - GL_TEXTURE0_ARB; + + fprintf( winState.log_fp, "glActiveTexturesARB( %s )\n", gARBMTenums[index]); + fflush(winState.log_fp); + dllActiveTextureARB(target); +} + +static void APIENTRY logClientActiveTextureARB(GLenum target) +{ + U32 index = target - GL_TEXTURE0_ARB; + + fprintf( winState.log_fp, "glClientActiveTexturesARB( %s )\n", gARBMTenums[index]); + fflush(winState.log_fp); + dllClientActiveTextureARB(target); +} + +static void APIENTRY logMultiTexCoord2fARB(GLenum texture, GLfloat x, GLfloat y) +{ + U32 index = texture - GL_TEXTURE0_ARB; + + fprintf( winState.log_fp, "glMultiTexCoord2fARB( %s, %f, %f )\n", gARBMTenums[index], x, y); + fflush(winState.log_fp); + dllMultiTexCoord2fARB(texture, x, y); +} + +static void APIENTRY logMultiTexCoord2fvARB(GLenum texture, GLfloat* p) +{ + U32 index = texture - GL_TEXTURE0_ARB; + + fprintf( winState.log_fp, "glMultiTexCoord2fARB( %s, [%f, %f] )\n", gARBMTenums[index], p[0], p[1]); + fflush(winState.log_fp); + dllMultiTexCoord2fvARB(texture, p); +} + +/* NV_vertex_array_range */ + +static void APIENTRY logVertexArrayRangeNV(GLsizei length, void* pointer) +{ + fprintf(winState.log_fp, "glVertexArrayRangeNV( %d, MEMORY )", length); + fflush(winState.log_fp); + dllVertexArrayRangeNV(length, pointer); +} + +static void APIENTRY logFlushVertexArrayRangeNV() +{ + SIG("glFlushVertexArrayRangeNV"); + dllFlushVertexArrayRangeNV(); +} + +static void* APIENTRY logAllocateMemoryNV(GLsizei length, GLfloat read, GLfloat write, GLfloat priority) +{ + fprintf(winState.log_fp, "wglAllocateMemoryNV( %d, %g, %g, %g)", length, read, write, priority); + fflush(winState.log_fp); + return dllAllocateMemoryNV(length, read, write, priority); +} + +static void APIENTRY logFreeMemoryNV(void* pointer) +{ + SIG("glFreeMemoryNV(MEM)"); + dllFreeMemoryNV(pointer); +} + +/* EXT_fog_coord */ + +static void APIENTRY logFogCoordfEXT(GLfloat coord) +{ + fprintf( winState.log_fp, "glFogCoordEXT(%f)\n", coord); + fflush(winState.log_fp); + dllFogCoordfEXT(coord); +} + +static void APIENTRY logFogCoordPointerEXT(GLenum type, GLsizei stride, void *pointer) +{ + fprintf( winState.log_fp, "glFogCoordPointerEXT(%s, %d, MEMORY)\n", TypeToString(type), stride); + fflush(winState.log_fp); + dllFogCoordPointerEXT(type, stride, pointer); +} + +/* ARB_texture_compression */ + +static void APIENTRY logCompressedTexImage3DARB(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data) +{ + fprintf( winState.log_fp, "glCompressedTexImage3DARB(...)\n"); + fflush(winState.log_fp); + dllCompressedTexImage3DARB(target, level, internalformat, width, height, depth, border, imageSize, data); +} + +static void APIENTRY logCompressedTexImage2DARB(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* data) +{ + fprintf( winState.log_fp, "glCompressedTexImage3DARB(...)\n"); + fflush(winState.log_fp); + dllCompressedTexImage2DARB(target, level, internalformat, width, height, border, imageSize, data); +} + +static void APIENTRY logCompressedTexImage1DARB(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* data) +{ + fprintf( winState.log_fp, "glCompressedTexImage3DARB(...)\n"); + fflush(winState.log_fp); + dllCompressedTexImage1DARB(target, level, internalformat, width, border, imageSize, data); +} + +static void APIENTRY logCompressedTexSubImage3DARB(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) +{ + fprintf( winState.log_fp, "glCompressedTexSubImage3DARB(...)\n"); + fflush(winState.log_fp); + dllCompressedTexSubImage3DARB(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data); +} + +static void APIENTRY logCompressedTexSubImage2DARB(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) +{ + fprintf( winState.log_fp, "glCompressedTexSubImage2DARB(...)\n"); + fflush(winState.log_fp); + dllCompressedTexSubImage2DARB(target, level, xoffset, yoffset, width, height, format, imageSize, data); +} + +static void APIENTRY logCompressedTexSubImage1DARB(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) +{ + fprintf( winState.log_fp, "glCompressedTexSubImage1DARB(...)\n"); + fflush(winState.log_fp); + dllCompressedTexSubImage1DARB(target, level, xoffset, width, format, imageSize, data); +} + +static void APIENTRY logGetCompressedTexImageARB(GLenum target, GLint lod, void* img) +{ + fprintf( winState.log_fp, "glGetCompressedTexImage3DARB(...)\n"); + fflush(winState.log_fp); + dllGetCompressedTexImageARB(target, lod, img); +} + +/* EXT_vertex_buffer */ + +static GLboolean APIENTRY logAvailableVertexBufferEXT() +{ + fprintf( winState.log_fp, "glAvailableVertexBufferEXT(...)\n"); + fflush(winState.log_fp); + return dllAvailableVertexBufferEXT(); +} + +static GLint APIENTRY logAllocateVertexBufferEXT(GLsizei size, GLint format, GLboolean preserve) +{ + fprintf( winState.log_fp, "glAllocateVertexBufferEXT(...)\n"); + fflush(winState.log_fp); + return dllAllocateVertexBufferEXT(size, format, preserve); +} + +static void * APIENTRY logLockVertexBufferEXT(GLint handle, GLsizei size) +{ + fprintf( winState.log_fp, "glLockVertexBufferEXT(...)\n"); + fflush(winState.log_fp); + return dllLockVertexBufferEXT(handle, size); +} + +static void APIENTRY logUnlockVertexBufferEXT(GLint handle) +{ + fprintf( winState.log_fp, "glUnlockVertexBufferEXT(...)\n"); + fflush(winState.log_fp); + dllUnlockVertexBufferEXT(handle); +} + +static void APIENTRY logSetVertexBufferEXT(GLint handle) +{ + fprintf( winState.log_fp, "glSetVertexBufferEXT(...)\n"); + fflush(winState.log_fp); + dllSetVertexBufferEXT(handle); +} + +static void APIENTRY logOffsetVertexBufferEXT(GLint handle, GLuint offset) +{ + fprintf( winState.log_fp, "glOffsetVertexBufferEXT(...)\n"); + fflush(winState.log_fp); + dllOffsetVertexBufferEXT(handle, offset); +} + +static void APIENTRY logFillVertexBufferEXT(GLint handle, GLint first, GLsizei count) +{ + fprintf( winState.log_fp, "glFillVertexBufferEXT(...)\n"); + fflush(winState.log_fp); + dllFillVertexBufferEXT(handle, first, count); +} + +static void APIENTRY logFreeVertexBufferEXT(GLint handle) +{ + fprintf( winState.log_fp, "glFreeVertexBufferEXT(...)\n"); + fflush(winState.log_fp); + dllFreeVertexBufferEXT(handle); +} + + +/* +** QGL_Shutdown +** +** Unloads the specified DLL then nulls out all the proc pointers. This +** is only called during a hard shutdown of the OGL subsystem (e.g. vid_restart). +*/ +void QGL_Shutdown( void ) +{ + if ( winState.hinstOpenGL ) + FreeLibrary( winState.hinstOpenGL ); + winState.hinstOpenGL = NULL; + + if ( winState.hinstGLU ) + FreeLibrary( winState.hinstGLU ); + winState.hinstGLU = NULL; + + gGLState.suppSwapInterval = false; + + // GLU Functions + gluErrorString = NULL; + gluGetString = NULL; + gluOrtho2D = NULL; + gluPerspective = NULL; + gluPickMatrix = NULL; + gluLookAt = NULL; + gluProject = NULL; + gluUnProject = NULL; + gluScaleImage = NULL; + gluBuild1DMipmaps = NULL; + gluBuild2DMipmaps = NULL; + + // GL Functions + glAccum = NULL; + glAlphaFunc = NULL; + glAreTexturesResident = NULL; + glArrayElement = NULL; + glBegin = NULL; + glBindTexture = NULL; + glBitmap = NULL; + glBlendFunc = NULL; + glCallList = NULL; + glCallLists = NULL; + glClear = NULL; + glClearAccum = NULL; + glClearColor = NULL; + glClearDepth = NULL; + glClearIndex = NULL; + glClearStencil = NULL; + glClipPlane = NULL; + glColor3b = NULL; + glColor3bv = NULL; + glColor3d = NULL; + glColor3dv = NULL; + glColor3f = NULL; + glColor3fv = NULL; + glColor3i = NULL; + glColor3iv = NULL; + glColor3s = NULL; + glColor3sv = NULL; + glColor3ub = NULL; + glColor3ubv = NULL; + glColor3ui = NULL; + glColor3uiv = NULL; + glColor3us = NULL; + glColor3usv = NULL; + glColor4b = NULL; + glColor4bv = NULL; + glColor4d = NULL; + glColor4dv = NULL; + glColor4f = NULL; + glColor4fv = NULL; + glColor4i = NULL; + glColor4iv = NULL; + glColor4s = NULL; + glColor4sv = NULL; + glColor4ub = NULL; + glColor4ubv = NULL; + glColor4ui = NULL; + glColor4uiv = NULL; + glColor4us = NULL; + glColor4usv = NULL; + glColorMask = NULL; + glColorMaterial = NULL; + glColorPointer = NULL; + glCopyPixels = NULL; + glCopyTexImage1D = NULL; + glCopyTexImage2D = NULL; + glCopyTexSubImage1D = NULL; + glCopyTexSubImage2D = NULL; + glCullFace = NULL; + glDeleteLists = NULL; + glDeleteTextures = NULL; + glDepthFunc = NULL; + glDepthMask = NULL; + glDepthRange = NULL; + glDisable = NULL; + glDisableClientState = NULL; + glDrawArrays = NULL; + glDrawBuffer = NULL; + glDrawElements = NULL; + glDrawPixels = NULL; + glEdgeFlag = NULL; + glEdgeFlagPointer = NULL; + glEdgeFlagv = NULL; + glEnable = NULL; + glEnableClientState = NULL; + glEnd = NULL; + glEndList = NULL; + glEvalCoord1d = NULL; + glEvalCoord1dv = NULL; + glEvalCoord1f = NULL; + glEvalCoord1fv = NULL; + glEvalCoord2d = NULL; + glEvalCoord2dv = NULL; + glEvalCoord2f = NULL; + glEvalCoord2fv = NULL; + glEvalMesh1 = NULL; + glEvalMesh2 = NULL; + glEvalPoint1 = NULL; + glEvalPoint2 = NULL; + glFeedbackBuffer = NULL; + glFinish = NULL; + glFlush = NULL; + glFogf = NULL; + glFogfv = NULL; + glFogi = NULL; + glFogiv = NULL; + glFrontFace = NULL; + glFrustum = NULL; + glGenLists = NULL; + glGenTextures = NULL; + glGetBooleanv = NULL; + glGetClipPlane = NULL; + glGetDoublev = NULL; + glGetError = NULL; + glGetFloatv = NULL; + glGetIntegerv = NULL; + glGetLightfv = NULL; + glGetLightiv = NULL; + glGetMapdv = NULL; + glGetMapfv = NULL; + glGetMapiv = NULL; + glGetMaterialfv = NULL; + glGetMaterialiv = NULL; + glGetPixelMapfv = NULL; + glGetPixelMapuiv = NULL; + glGetPixelMapusv = NULL; + glGetPointerv = NULL; + glGetPolygonStipple = NULL; + glGetString = NULL; + glGetTexEnvfv = NULL; + glGetTexEnviv = NULL; + glGetTexGendv = NULL; + glGetTexGenfv = NULL; + glGetTexGeniv = NULL; + glGetTexImage = NULL; + glGetTexLevelParameterfv = NULL; + glGetTexLevelParameteriv = NULL; + glGetTexParameterfv = NULL; + glGetTexParameteriv = NULL; + glHint = NULL; + glIndexMask = NULL; + glIndexPointer = NULL; + glIndexd = NULL; + glIndexdv = NULL; + glIndexf = NULL; + glIndexfv = NULL; + glIndexi = NULL; + glIndexiv = NULL; + glIndexs = NULL; + glIndexsv = NULL; + glIndexub = NULL; + glIndexubv = NULL; + glInitNames = NULL; + glInterleavedArrays = NULL; + glIsEnabled = NULL; + glIsList = NULL; + glIsTexture = NULL; + glLightModelf = NULL; + glLightModelfv = NULL; + glLightModeli = NULL; + glLightModeliv = NULL; + glLightf = NULL; + glLightfv = NULL; + glLighti = NULL; + glLightiv = NULL; + glLineStipple = NULL; + glLineWidth = NULL; + glListBase = NULL; + glLoadIdentity = NULL; + glLoadMatrixd = NULL; + glLoadMatrixf = NULL; + glLoadName = NULL; + glLogicOp = NULL; + glMap1d = NULL; + glMap1f = NULL; + glMap2d = NULL; + glMap2f = NULL; + glMapGrid1d = NULL; + glMapGrid1f = NULL; + glMapGrid2d = NULL; + glMapGrid2f = NULL; + glMaterialf = NULL; + glMaterialfv = NULL; + glMateriali = NULL; + glMaterialiv = NULL; + glMatrixMode = NULL; + glMultMatrixd = NULL; + glMultMatrixf = NULL; + glNewList = NULL; + glNormal3b = NULL; + glNormal3bv = NULL; + glNormal3d = NULL; + glNormal3dv = NULL; + glNormal3f = NULL; + glNormal3fv = NULL; + glNormal3i = NULL; + glNormal3iv = NULL; + glNormal3s = NULL; + glNormal3sv = NULL; + glNormalPointer = NULL; + glOrtho = NULL; + glPassThrough = NULL; + glPixelMapfv = NULL; + glPixelMapuiv = NULL; + glPixelMapusv = NULL; + glPixelStoref = NULL; + glPixelStorei = NULL; + glPixelTransferf = NULL; + glPixelTransferi = NULL; + glPixelZoom = NULL; + glPointSize = NULL; + glPolygonMode = NULL; + glPolygonOffset = NULL; + glPolygonStipple = NULL; + glPopAttrib = NULL; + glPopClientAttrib = NULL; + glPopMatrix = NULL; + glPopName = NULL; + glPrioritizeTextures = NULL; + glPushAttrib = NULL; + glPushClientAttrib = NULL; + glPushMatrix = NULL; + glPushName = NULL; + glRasterPos2d = NULL; + glRasterPos2dv = NULL; + glRasterPos2f = NULL; + glRasterPos2fv = NULL; + glRasterPos2i = NULL; + glRasterPos2iv = NULL; + glRasterPos2s = NULL; + glRasterPos2sv = NULL; + glRasterPos3d = NULL; + glRasterPos3dv = NULL; + glRasterPos3f = NULL; + glRasterPos3fv = NULL; + glRasterPos3i = NULL; + glRasterPos3iv = NULL; + glRasterPos3s = NULL; + glRasterPos3sv = NULL; + glRasterPos4d = NULL; + glRasterPos4dv = NULL; + glRasterPos4f = NULL; + glRasterPos4fv = NULL; + glRasterPos4i = NULL; + glRasterPos4iv = NULL; + glRasterPos4s = NULL; + glRasterPos4sv = NULL; + glReadBuffer = NULL; + glReadPixels = NULL; + glRectd = NULL; + glRectdv = NULL; + glRectf = NULL; + glRectfv = NULL; + glRecti = NULL; + glRectiv = NULL; + glRects = NULL; + glRectsv = NULL; + glRenderMode = NULL; + glRotated = NULL; + glRotatef = NULL; + glScaled = NULL; + glScalef = NULL; + glScissor = NULL; + glSelectBuffer = NULL; + glShadeModel = NULL; + glStencilFunc = NULL; + glStencilMask = NULL; + glStencilOp = NULL; + glTexCoord1d = NULL; + glTexCoord1dv = NULL; + glTexCoord1f = NULL; + glTexCoord1fv = NULL; + glTexCoord1i = NULL; + glTexCoord1iv = NULL; + glTexCoord1s = NULL; + glTexCoord1sv = NULL; + glTexCoord2d = NULL; + glTexCoord2dv = NULL; + glTexCoord2f = NULL; + glTexCoord2fv = NULL; + glTexCoord2i = NULL; + glTexCoord2iv = NULL; + glTexCoord2s = NULL; + glTexCoord2sv = NULL; + glTexCoord3d = NULL; + glTexCoord3dv = NULL; + glTexCoord3f = NULL; + glTexCoord3fv = NULL; + glTexCoord3i = NULL; + glTexCoord3iv = NULL; + glTexCoord3s = NULL; + glTexCoord3sv = NULL; + glTexCoord4d = NULL; + glTexCoord4dv = NULL; + glTexCoord4f = NULL; + glTexCoord4fv = NULL; + glTexCoord4i = NULL; + glTexCoord4iv = NULL; + glTexCoord4s = NULL; + glTexCoord4sv = NULL; + glTexCoordPointer = NULL; + glTexEnvf = NULL; + glTexEnvfv = NULL; + glTexEnvi = NULL; + glTexEnviv = NULL; + glTexGend = NULL; + glTexGendv = NULL; + glTexGenf = NULL; + glTexGenfv = NULL; + glTexGeni = NULL; + glTexGeniv = NULL; + glTexImage1D = NULL; + glTexImage2D = NULL; + glTexParameterf = NULL; + glTexParameterfv = NULL; + glTexParameteri = NULL; + glTexParameteriv = NULL; + glTexSubImage1D = NULL; + glTexSubImage2D = NULL; + glTranslated = NULL; + glTranslatef = NULL; + glVertex2d = NULL; + glVertex2dv = NULL; + glVertex2f = NULL; + glVertex2fv = NULL; + glVertex2i = NULL; + glVertex2iv = NULL; + glVertex2s = NULL; + glVertex2sv = NULL; + glVertex3d = NULL; + glVertex3dv = NULL; + glVertex3f = NULL; + glVertex3fv = NULL; + glVertex3i = NULL; + glVertex3iv = NULL; + glVertex3s = NULL; + glVertex3sv = NULL; + glVertex4d = NULL; + glVertex4dv = NULL; + glVertex4f = NULL; + glVertex4fv = NULL; + glVertex4i = NULL; + glVertex4iv = NULL; + glVertex4s = NULL; + glVertex4sv = NULL; + glVertexPointer = NULL; + glViewport = NULL; + + // EXT_compiled_vertex_array + glLockArraysEXT = NULL; + glUnlockArraysEXT = NULL; + + // ARB_multitexture + glActiveTextureARB = NULL; + glClientActiveTextureARB = NULL; + glMultiTexCoord2fARB = NULL; + glMultiTexCoord2fvARB = NULL; + + // NV_vertex_array_range + glVertexArrayRangeNV = NULL; + glFlushVertexArrayRangeNV = NULL; + wglAllocateMemoryNV = NULL; + wglFreeMemoryNV = NULL; + + // EXT_fog_coord + glFogCoordfEXT = NULL; + glFogCoordPointerEXT = NULL; + + /* ARB_texture_compression */ + glCompressedTexImage3DARB = NULL; + glCompressedTexImage2DARB = NULL; + glCompressedTexImage1DARB = NULL; + glCompressedTexSubImage3DARB = NULL; + glCompressedTexSubImage2DARB = NULL; + glCompressedTexSubImage1DARB = NULL; + glGetCompressedTexImageARB = NULL; + + glAvailableVertexBufferEXT = NULL; + glAllocateVertexBufferEXT = NULL; + glLockVertexBufferEXT = NULL; + glUnlockVertexBufferEXT = NULL; + glSetVertexBufferEXT = NULL; + glOffsetVertexBufferEXT = NULL; + glFillVertexBufferEXT = NULL; + glFreeVertexBufferEXT = NULL; + + qwglCopyContext = NULL; + qwglCreateContext = NULL; + qwglCreateLayerContext = NULL; + qwglDeleteContext = NULL; + qwglDescribeLayerPlane = NULL; + qwglGetCurrentContext = NULL; + qwglGetCurrentDC = NULL; + qwglGetLayerPaletteEntries = NULL; + qwglGetProcAddress = NULL; + qwglMakeCurrent = NULL; + qwglRealizeLayerPalette = NULL; + qwglSetLayerPaletteEntries = NULL; + qwglShareLists = NULL; + qwglSwapLayerBuffers = NULL; + qwglUseFontBitmaps = NULL; + qwglUseFontOutlines = NULL; + + qwglChoosePixelFormat = NULL; + qwglDescribePixelFormat = NULL; + qwglGetPixelFormat = NULL; + qwglSetPixelFormat = NULL; + qwglSwapBuffers = NULL; +} + + +#define GR_NUM_BOARDS 0x0f + +static bool GlideIsValid( void ) +{ +// int numBoards; +// void (__stdcall *grGet)(unsigned int, unsigned int, int*); + + if ( LoadLibrary("Glide3X") != 0 ) + { + // FIXME: 3Dfx needs to fix this shit + return true; + +#if 0 + grGet = (void *)GetProcAddress( hGlide, "_grGet@12"); + + if ( grGet ) + { + grGet( GR_NUM_BOARDS, sizeof(int), &numBoards); + } + else + { + // if we've reached this point, something is seriously wrong + ri.Printf( PRINT_WARNING, "WARNING: could not find grGet in GLIDE3X.DLL\n" ); + numBoards = 0; + } + + FreeLibrary( hGlide ); + hGlide = NULL; + + if ( numBoards > 0 ) + { + return true; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid Glide installation!\n" ); +#endif + } + + return false; +} + +# define GPA_GL( a ) GetProcAddress( winState.hinstOpenGL, a ) +# define GPA_GLU( a ) GetProcAddress( winState.hinstGLU, a ) + +/* +** QGL_Init +** +** This is responsible for binding our gl function pointers to +** the appropriate GL stuff. In Windows this means doing a +** LoadLibrary and a bunch of calls to GetProcAddress. On other +** operating systems we need to do the right thing, whatever that +** might be. +** +*/ + +//-------------------------------------- +bool QGL_Init( const char *dllname_gl, const char *dllname_glu ) +{ + if ( winState.hinstOpenGL && winState.hinstGLU) + return true; + + // Load OpenGL DLL + if (!winState.hinstOpenGL) + { + // NOTE: this assumes that 'dllname' is lower case (and it should be)! + if ( strstr( dllname_gl, "voodoo" ) ) + { + if ( !GlideIsValid() ) + { + return false; + } + } + if ( ( winState.hinstOpenGL = LoadLibrary( dllname_gl ) ) == 0 ) + return false; + } + + // Load OpenGL GLU DLL + if ( !winState.hinstGLU ) + { + if ( ( winState.hinstGLU = LoadLibrary( dllname_glu ) ) == 0 ) + return false; + } + + // GLU Functions + gluErrorString = dllgluErrorString = (gluErrorString_t) GPA_GLU( "gluErrorString" ); + gluGetString = dllgluGetString = (gluGetString_t) GPA_GLU( "gluGetString" ); + gluOrtho2D = dllgluOrtho2D = (gluOrtho2D_t) GPA_GLU( "gluOrtho2D" ); + gluPerspective = dllgluPerspective = (gluPerspective_t) GPA_GLU( "gluPerspective" ); + gluPickMatrix = dllgluPickMatrix = (gluPickMatrix_t) GPA_GLU( "gluPickMatrix" ); + gluLookAt = dllgluLookAt = (gluLookAt_t) GPA_GLU( "gluLookAt" ); + gluProject = dllgluProject = (gluProject_t) GPA_GLU( "gluProject" ); + gluUnProject = dllgluUnProject = (gluUnProject_t) GPA_GLU( "gluUnProject" ); + gluScaleImage = dllgluScaleImage = (gluScaleImage_t) GPA_GLU( "gluScaleImage" ); + gluBuild1DMipmaps = dllgluBuild1DMipmaps = (gluBuild1DMipmaps_t) GPA_GLU( "gluBuild1DMipmaps" ); + gluBuild2DMipmaps = dllgluBuild2DMipmaps = (gluBuild2DMipmaps_t) GPA_GLU( "gluBuild2DMipmaps" ); + + // GL Functions + glAccum = dllAccum = (glAccum_t) GPA_GL( "glAccum" ); + glAlphaFunc = dllAlphaFunc = (glAlphaFunc_t) GPA_GL( "glAlphaFunc" ); + glAreTexturesResident = dllAreTexturesResident = (glAreTexturesResident_t) GPA_GL( "glAreTexturesResident" ); + glArrayElement = dllArrayElement = (glArrayElement_t) GPA_GL( "glArrayElement" ); + glBegin = dllBegin = (glBegin_t) GPA_GL( "glBegin" ); + glBindTexture = dllBindTexture = (glBindTexture_t) GPA_GL( "glBindTexture" ); + glBitmap = dllBitmap = (glBitmap_t) GPA_GL( "glBitmap" ); + glBlendFunc = dllBlendFunc = (glBlendFunc_t) GPA_GL( "glBlendFunc" ); + glCallList = dllCallList = (glCallList_t) GPA_GL( "glCallList" ); + glCallLists = dllCallLists = (glCallLists_t) GPA_GL( "glCallLists" ); + glClear = dllClear = (glClear_t) GPA_GL( "glClear" ); + glClearAccum = dllClearAccum = (glClearAccum_t) GPA_GL( "glClearAccum" ); + glClearColor = dllClearColor = (glClearColor_t) GPA_GL( "glClearColor" ); + glClearDepth = dllClearDepth = (glClearDepth_t) GPA_GL( "glClearDepth" ); + glClearIndex = dllClearIndex = (glClearIndex_t) GPA_GL( "glClearIndex" ); + glClearStencil = dllClearStencil = (glClearStencil_t) GPA_GL( "glClearStencil" ); + glClipPlane = dllClipPlane = (glClipPlane_t) GPA_GL( "glClipPlane" ); + glColor3b = dllColor3b = (glColor3b_t) GPA_GL( "glColor3b" ); + glColor3bv = dllColor3bv = (glColor3bv_t) GPA_GL( "glColor3bv" ); + glColor3d = dllColor3d = (glColor3d_t) GPA_GL( "glColor3d" ); + glColor3dv = dllColor3dv = (glColor3dv_t) GPA_GL( "glColor3dv" ); + glColor3f = dllColor3f = (glColor3f_t) GPA_GL( "glColor3f" ); + glColor3fv = dllColor3fv = (glColor3fv_t) GPA_GL( "glColor3fv" ); + glColor3i = dllColor3i = (glColor3i_t) GPA_GL( "glColor3i" ); + glColor3iv = dllColor3iv = (glColor3iv_t) GPA_GL( "glColor3iv" ); + glColor3s = dllColor3s = (glColor3s_t) GPA_GL( "glColor3s" ); + glColor3sv = dllColor3sv = (glColor3sv_t) GPA_GL( "glColor3sv" ); + glColor3ub = dllColor3ub = (glColor3ub_t) GPA_GL( "glColor3ub" ); + glColor3ubv = dllColor3ubv = (glColor3ubv_t) GPA_GL( "glColor3ubv" ); + glColor3ui = dllColor3ui = (glColor3ui_t) GPA_GL( "glColor3ui" ); + glColor3uiv = dllColor3uiv = (glColor3uiv_t) GPA_GL( "glColor3uiv" ); + glColor3us = dllColor3us = (glColor3us_t) GPA_GL( "glColor3us" ); + glColor3usv = dllColor3usv = (glColor3usv_t) GPA_GL( "glColor3usv" ); + glColor4b = dllColor4b = (glColor4b_t) GPA_GL( "glColor4b" ); + glColor4bv = dllColor4bv = (glColor4bv_t) GPA_GL( "glColor4bv" ); + glColor4d = dllColor4d = (glColor4d_t) GPA_GL( "glColor4d" ); + glColor4dv = dllColor4dv = (glColor4dv_t) GPA_GL( "glColor4dv" ); + glColor4f = dllColor4f = (glColor4f_t) GPA_GL( "glColor4f" ); + glColor4fv = dllColor4fv = (glColor4fv_t) GPA_GL( "glColor4fv" ); + glColor4i = dllColor4i = (glColor4i_t) GPA_GL( "glColor4i" ); + glColor4iv = dllColor4iv = (glColor4iv_t) GPA_GL( "glColor4iv" ); + glColor4s = dllColor4s = (glColor4s_t) GPA_GL( "glColor4s" ); + glColor4sv = dllColor4sv = (glColor4sv_t) GPA_GL( "glColor4sv" ); + glColor4ub = dllColor4ub = (glColor4ub_t) GPA_GL( "glColor4ub" ); + glColor4ubv = dllColor4ubv = (glColor4ubv_t) GPA_GL( "glColor4ubv" ); + glColor4ui = dllColor4ui = (glColor4ui_t) GPA_GL( "glColor4ui" ); + glColor4uiv = dllColor4uiv = (glColor4uiv_t) GPA_GL( "glColor4uiv" ); + glColor4us = dllColor4us = (glColor4us_t) GPA_GL( "glColor4us" ); + glColor4usv = dllColor4usv = (glColor4usv_t) GPA_GL( "glColor4usv" ); + glColorMask = dllColorMask = (glColorMask_t) GPA_GL( "glColorMask" ); + glColorMaterial = dllColorMaterial = (glColorMaterial_t) GPA_GL( "glColorMaterial" ); + glColorPointer = dllColorPointer = (glColorPointer_t) GPA_GL( "glColorPointer" ); + glCopyPixels = dllCopyPixels = (glCopyPixels_t) GPA_GL( "glCopyPixels" ); + glCopyTexImage1D = dllCopyTexImage1D = (glCopyTexImage1D_t) GPA_GL( "glCopyTexImage1D" ); + glCopyTexImage2D = dllCopyTexImage2D = (glCopyTexImage2D_t) GPA_GL( "glCopyTexImage2D" ); + glCopyTexSubImage1D = dllCopyTexSubImage1D = (glCopyTexSubImage1D_t) GPA_GL( "glCopyTexSubImage1D" ); + glCopyTexSubImage2D = dllCopyTexSubImage2D = (glCopyTexSubImage2D_t) GPA_GL( "glCopyTexSubImage2D" ); + glCullFace = dllCullFace = (glCullFace_t) GPA_GL( "glCullFace" ); + glDeleteLists = dllDeleteLists = (glDeleteLists_t) GPA_GL( "glDeleteLists" ); + glDeleteTextures = dllDeleteTextures = (glDeleteTextures_t) GPA_GL( "glDeleteTextures" ); + glDepthFunc = dllDepthFunc = (glDepthFunc_t) GPA_GL( "glDepthFunc" ); + glDepthMask = dllDepthMask = (glDepthMask_t) GPA_GL( "glDepthMask" ); + glDepthRange = dllDepthRange = (glDepthRange_t) GPA_GL( "glDepthRange" ); + glDisable = dllDisable = (glDisable_t) GPA_GL( "glDisable" ); + glDisableClientState = dllDisableClientState = (glDisableClientState_t) GPA_GL( "glDisableClientState" ); + glDrawArrays = dllDrawArrays = (glDrawArrays_t) GPA_GL( "glDrawArrays" ); + glDrawBuffer = dllDrawBuffer = (glDrawBuffer_t) GPA_GL( "glDrawBuffer" ); + glDrawElements = dllDrawElements = (glDrawElements_t) GPA_GL( "glDrawElements" ); + glDrawPixels = dllDrawPixels = (glDrawPixels_t) GPA_GL( "glDrawPixels" ); + glEdgeFlag = dllEdgeFlag = (glEdgeFlag_t) GPA_GL( "glEdgeFlag" ); + glEdgeFlagPointer = dllEdgeFlagPointer = (glEdgeFlagPointer_t) GPA_GL( "glEdgeFlagPointer" ); + glEdgeFlagv = dllEdgeFlagv = (glEdgeFlagv_t) GPA_GL( "glEdgeFlagv" ); + glEnable = dllEnable = (glEnable_t) GPA_GL( "glEnable" ); + glEnableClientState = dllEnableClientState = (glEnableClientState_t) GPA_GL( "glEnableClientState" ); + glEnd = dllEnd = (glEnd_t) GPA_GL( "glEnd" ); + glEndList = dllEndList = (glEndList_t) GPA_GL( "glEndList" ); + glEvalCoord1d = dllEvalCoord1d = (glEvalCoord1d_t) GPA_GL( "glEvalCoord1d" ); + glEvalCoord1dv = dllEvalCoord1dv = (glEvalCoord1dv_t) GPA_GL( "glEvalCoord1dv" ); + glEvalCoord1f = dllEvalCoord1f = (glEvalCoord1f_t) GPA_GL( "glEvalCoord1f" ); + glEvalCoord1fv = dllEvalCoord1fv = (glEvalCoord1fv_t) GPA_GL( "glEvalCoord1fv" ); + glEvalCoord2d = dllEvalCoord2d = (glEvalCoord2d_t) GPA_GL( "glEvalCoord2d" ); + glEvalCoord2dv = dllEvalCoord2dv = (glEvalCoord2dv_t) GPA_GL( "glEvalCoord2dv" ); + glEvalCoord2f = dllEvalCoord2f = (glEvalCoord2f_t) GPA_GL( "glEvalCoord2f" ); + glEvalCoord2fv = dllEvalCoord2fv = (glEvalCoord2fv_t) GPA_GL( "glEvalCoord2fv" ); + glEvalMesh1 = dllEvalMesh1 = (glEvalMesh1_t) GPA_GL( "glEvalMesh1" ); + glEvalMesh2 = dllEvalMesh2 = (glEvalMesh2_t) GPA_GL( "glEvalMesh2" ); + glEvalPoint1 = dllEvalPoint1 = (glEvalPoint1_t) GPA_GL( "glEvalPoint1" ); + glEvalPoint2 = dllEvalPoint2 = (glEvalPoint2_t) GPA_GL( "glEvalPoint2" ); + glFeedbackBuffer = dllFeedbackBuffer = (glFeedbackBuffer_t) GPA_GL( "glFeedbackBuffer" ); + glFinish = dllFinish = (glFinish_t) GPA_GL( "glFinish" ); + glFlush = dllFlush = (glFlush_t) GPA_GL( "glFlush" ); + glFogf = dllFogf = (glFogf_t) GPA_GL( "glFogf" ); + glFogfv = dllFogfv = (glFogfv_t) GPA_GL( "glFogfv" ); + glFogi = dllFogi = (glFogi_t) GPA_GL( "glFogi" ); + glFogiv = dllFogiv = (glFogiv_t) GPA_GL( "glFogiv" ); + glFrontFace = dllFrontFace = (glFrontFace_t) GPA_GL( "glFrontFace" ); + glFrustum = dllFrustum = (glFrustum_t) GPA_GL( "glFrustum" ); + glGenLists = dllGenLists = (glGenLists_t) GPA_GL( "glGenLists" ); + glGenTextures = dllGenTextures = (glGenTextures_t) GPA_GL( "glGenTextures" ); + glGetBooleanv = dllGetBooleanv = (glGetBooleanv_t) GPA_GL( "glGetBooleanv" ); + glGetClipPlane = dllGetClipPlane = (glGetClipPlane_t) GPA_GL( "glGetClipPlane" ); + glGetDoublev = dllGetDoublev = (glGetDoublev_t) GPA_GL( "glGetDoublev" ); + glGetError = dllGetError = (glGetError_t) GPA_GL( "glGetError" ); + glGetFloatv = dllGetFloatv = (glGetFloatv_t) GPA_GL( "glGetFloatv" ); + glGetIntegerv = dllGetIntegerv = (glGetIntegerv_t) GPA_GL( "glGetIntegerv" ); + glGetLightfv = dllGetLightfv = (glGetLightfv_t) GPA_GL( "glGetLightfv" ); + glGetLightiv = dllGetLightiv = (glGetLightiv_t) GPA_GL( "glGetLightiv" ); + glGetMapdv = dllGetMapdv = (glGetMapdv_t) GPA_GL( "glGetMapdv" ); + glGetMapfv = dllGetMapfv = (glGetMapfv_t) GPA_GL( "glGetMapfv" ); + glGetMapiv = dllGetMapiv = (glGetMapiv_t) GPA_GL( "glGetMapiv" ); + glGetMaterialfv = dllGetMaterialfv = (glGetMaterialfv_t) GPA_GL( "glGetMaterialfv" ); + glGetMaterialiv = dllGetMaterialiv = (glGetMaterialiv_t) GPA_GL( "glGetMaterialiv" ); + glGetPixelMapfv = dllGetPixelMapfv = (glGetPixelMapfv_t) GPA_GL( "glGetPixelMapfv" ); + glGetPixelMapuiv = dllGetPixelMapuiv = (glGetPixelMapuiv_t) GPA_GL( "glGetPixelMapuiv" ); + glGetPixelMapusv = dllGetPixelMapusv = (glGetPixelMapusv_t) GPA_GL( "glGetPixelMapusv" ); + glGetPointerv = dllGetPointerv = (glGetPointerv_t) GPA_GL( "glGetPointerv" ); + glGetPolygonStipple = dllGetPolygonStipple = (glGetPolygonStipple_t) GPA_GL( "glGetPolygonStipple" ); + glGetString = dllGetString = (glGetString_t) GPA_GL( "glGetString" ); + glGetTexEnvfv = dllGetTexEnvfv = (glGetTexEnvfv_t) GPA_GL( "glGetTexEnvfv" ); + glGetTexEnviv = dllGetTexEnviv = (glGetTexEnviv_t) GPA_GL( "glGetTexEnviv" ); + glGetTexGendv = dllGetTexGendv = (glGetTexGendv_t) GPA_GL( "glGetTexGendv" ); + glGetTexGenfv = dllGetTexGenfv = (glGetTexGenfv_t) GPA_GL( "glGetTexGenfv" ); + glGetTexGeniv = dllGetTexGeniv = (glGetTexGeniv_t) GPA_GL( "glGetTexGeniv" ); + glGetTexImage = dllGetTexImage = (glGetTexImage_t) GPA_GL( "glGetTexImage" ); + glGetTexLevelParameterfv = dllGetTexLevelParameterfv = (glGetTexLevelParameterfv_t) GPA_GL( "glGetLevelParameterfv" ); + glGetTexLevelParameteriv = dllGetTexLevelParameteriv = (glGetTexLevelParameteriv_t) GPA_GL( "glGetLevelParameteriv" ); + glGetTexParameterfv = dllGetTexParameterfv = (glGetTexParameterfv_t) GPA_GL( "glGetTexParameterfv" ); + glGetTexParameteriv = dllGetTexParameteriv = (glGetTexParameteriv_t) GPA_GL( "glGetTexParameteriv" ); + glHint = dllHint = (glHint_t) GPA_GL( "glHint" ); + glIndexMask = dllIndexMask = (glIndexMask_t) GPA_GL( "glIndexMask" ); + glIndexPointer = dllIndexPointer = (glIndexPointer_t) GPA_GL( "glIndexPointer" ); + glIndexd = dllIndexd = (glIndexd_t) GPA_GL( "glIndexd" ); + glIndexdv = dllIndexdv = (glIndexdv_t) GPA_GL( "glIndexdv" ); + glIndexf = dllIndexf = (glIndexf_t) GPA_GL( "glIndexf" ); + glIndexfv = dllIndexfv = (glIndexfv_t) GPA_GL( "glIndexfv" ); + glIndexi = dllIndexi = (glIndexi_t) GPA_GL( "glIndexi" ); + glIndexiv = dllIndexiv = (glIndexiv_t) GPA_GL( "glIndexiv" ); + glIndexs = dllIndexs = (glIndexs_t) GPA_GL( "glIndexs" ); + glIndexsv = dllIndexsv = (glIndexsv_t) GPA_GL( "glIndexsv" ); + glIndexub = dllIndexub = (glIndexub_t) GPA_GL( "glIndexub" ); + glIndexubv = dllIndexubv = (glIndexubv_t) GPA_GL( "glIndexubv" ); + glInitNames = dllInitNames = (glInitNames_t) GPA_GL( "glInitNames" ); + glInterleavedArrays = dllInterleavedArrays = (glInterleavedArrays_t) GPA_GL( "glInterleavedArrays" ); + glIsEnabled = dllIsEnabled = (glIsEnabled_t) GPA_GL( "glIsEnabled" ); + glIsList = dllIsList = (glIsList_t) GPA_GL( "glIsList" ); + glIsTexture = dllIsTexture = (glIsTexture_t) GPA_GL( "glIsTexture" ); + glLightModelf = dllLightModelf = (glLightModelf_t) GPA_GL( "glLightModelf" ); + glLightModelfv = dllLightModelfv = (glLightModelfv_t) GPA_GL( "glLightModelfv" ); + glLightModeli = dllLightModeli = (glLightModeli_t) GPA_GL( "glLightModeli" ); + glLightModeliv = dllLightModeliv = (glLightModeliv_t) GPA_GL( "glLightModeliv" ); + glLightf = dllLightf = (glLightf_t) GPA_GL( "glLightf" ); + glLightfv = dllLightfv = (glLightfv_t) GPA_GL( "glLightfv" ); + glLighti = dllLighti = (glLighti_t) GPA_GL( "glLighti" ); + glLightiv = dllLightiv = (glLightiv_t) GPA_GL( "glLightiv" ); + glLineStipple = dllLineStipple = (glLineStipple_t) GPA_GL( "glLineStipple" ); + glLineWidth = dllLineWidth = (glLineWidth_t) GPA_GL( "glLineWidth" ); + glListBase = dllListBase = (glListBase_t) GPA_GL( "glListBase" ); + glLoadIdentity = dllLoadIdentity = (glLoadIdentity_t) GPA_GL( "glLoadIdentity" ); + glLoadMatrixd = dllLoadMatrixd = (glLoadMatrixd_t) GPA_GL( "glLoadMatrixd" ); + glLoadMatrixf = dllLoadMatrixf = (glLoadMatrixf_t) GPA_GL( "glLoadMatrixf" ); + glLoadName = dllLoadName = (glLoadName_t) GPA_GL( "glLoadName" ); + glLogicOp = dllLogicOp = (glLogicOp_t) GPA_GL( "glLogicOp" ); + glMap1d = dllMap1d = (glMap1d_t) GPA_GL( "glMap1d" ); + glMap1f = dllMap1f = (glMap1f_t) GPA_GL( "glMap1f" ); + glMap2d = dllMap2d = (glMap2d_t) GPA_GL( "glMap2d" ); + glMap2f = dllMap2f = (glMap2f_t) GPA_GL( "glMap2f" ); + glMapGrid1d = dllMapGrid1d = (glMapGrid1d_t) GPA_GL( "glMapGrid1d" ); + glMapGrid1f = dllMapGrid1f = (glMapGrid1f_t) GPA_GL( "glMapGrid1f" ); + glMapGrid2d = dllMapGrid2d = (glMapGrid2d_t) GPA_GL( "glMapGrid2d" ); + glMapGrid2f = dllMapGrid2f = (glMapGrid2f_t) GPA_GL( "glMapGrid2f" ); + glMaterialf = dllMaterialf = (glMaterialf_t) GPA_GL( "glMaterialf" ); + glMaterialfv = dllMaterialfv = (glMaterialfv_t) GPA_GL( "glMaterialfv" ); + glMateriali = dllMateriali = (glMateriali_t) GPA_GL( "glMateriali" ); + glMaterialiv = dllMaterialiv = (glMaterialiv_t) GPA_GL( "glMaterialiv" ); + glMatrixMode = dllMatrixMode = (glMatrixMode_t) GPA_GL( "glMatrixMode" ); + glMultMatrixd = dllMultMatrixd = (glMultMatrixd_t) GPA_GL( "glMultMatrixd" ); + glMultMatrixf = dllMultMatrixf = (glMultMatrixf_t) GPA_GL( "glMultMatrixf" ); + glNewList = dllNewList = (glNewList_t) GPA_GL( "glNewList" ); + glNormal3b = dllNormal3b = (glNormal3b_t) GPA_GL( "glNormal3b" ); + glNormal3bv = dllNormal3bv = (glNormal3bv_t) GPA_GL( "glNormal3bv" ); + glNormal3d = dllNormal3d = (glNormal3d_t) GPA_GL( "glNormal3d" ); + glNormal3dv = dllNormal3dv = (glNormal3dv_t) GPA_GL( "glNormal3dv" ); + glNormal3f = dllNormal3f = (glNormal3f_t) GPA_GL( "glNormal3f" ); + glNormal3fv = dllNormal3fv = (glNormal3fv_t) GPA_GL( "glNormal3fv" ); + glNormal3i = dllNormal3i = (glNormal3i_t) GPA_GL( "glNormal3i" ); + glNormal3iv = dllNormal3iv = (glNormal3iv_t) GPA_GL( "glNormal3iv" ); + glNormal3s = dllNormal3s = (glNormal3s_t) GPA_GL( "glNormal3s" ); + glNormal3sv = dllNormal3sv = (glNormal3sv_t) GPA_GL( "glNormal3sv" ); + glNormalPointer = dllNormalPointer = (glNormalPointer_t) GPA_GL( "glNormalPointer" ); + glOrtho = dllOrtho = (glOrtho_t) GPA_GL( "glOrtho" ); + glPassThrough = dllPassThrough = (glPassThrough_t) GPA_GL( "glPassThrough" ); + glPixelMapfv = dllPixelMapfv = (glPixelMapfv_t) GPA_GL( "glPixelMapfv" ); + glPixelMapuiv = dllPixelMapuiv = (glPixelMapuiv_t) GPA_GL( "glPixelMapuiv" ); + glPixelMapusv = dllPixelMapusv = (glPixelMapusv_t) GPA_GL( "glPixelMapusv" ); + glPixelStoref = dllPixelStoref = (glPixelStoref_t) GPA_GL( "glPixelStoref" ); + glPixelStorei = dllPixelStorei = (glPixelStorei_t) GPA_GL( "glPixelStorei" ); + glPixelTransferf = dllPixelTransferf = (glPixelTransferf_t) GPA_GL( "glPixelTransferf" ); + glPixelTransferi = dllPixelTransferi = (glPixelTransferi_t) GPA_GL( "glPixelTransferi" ); + glPixelZoom = dllPixelZoom = (glPixelZoom_t) GPA_GL( "glPixelZoom" ); + glPointSize = dllPointSize = (glPointSize_t) GPA_GL( "glPointSize" ); + glPolygonMode = dllPolygonMode = (glPolygonMode_t) GPA_GL( "glPolygonMode" ); + glPolygonOffset = dllPolygonOffset = (glPolygonOffset_t) GPA_GL( "glPolygonOffset" ); + glPolygonStipple = dllPolygonStipple = (glPolygonStipple_t) GPA_GL( "glPolygonStipple" ); + glPopAttrib = dllPopAttrib = (glPopAttrib_t) GPA_GL( "glPopAttrib" ); + glPopClientAttrib = dllPopClientAttrib = (glPopClientAttrib_t) GPA_GL( "glPopClientAttrib" ); + glPopMatrix = dllPopMatrix = (glPopMatrix_t) GPA_GL( "glPopMatrix" ); + glPopName = dllPopName = (glPopName_t) GPA_GL( "glPopName" ); + glPrioritizeTextures = dllPrioritizeTextures = (glPrioritizeTextures_t) GPA_GL( "glPrioritizeTextures" ); + glPushAttrib = dllPushAttrib = (glPushAttrib_t) GPA_GL( "glPushAttrib" ); + glPushClientAttrib = dllPushClientAttrib = (glPushClientAttrib_t) GPA_GL( "glPushClientAttrib" ); + glPushMatrix = dllPushMatrix = (glPushMatrix_t) GPA_GL( "glPushMatrix" ); + glPushName = dllPushName = (glPushName_t) GPA_GL( "glPushName" ); + glRasterPos2d = dllRasterPos2d = (glRasterPos2d_t) GPA_GL( "glRasterPos2d" ); + glRasterPos2dv = dllRasterPos2dv = (glRasterPos2dv_t) GPA_GL( "glRasterPos2dv" ); + glRasterPos2f = dllRasterPos2f = (glRasterPos2f_t) GPA_GL( "glRasterPos2f" ); + glRasterPos2fv = dllRasterPos2fv = (glRasterPos2fv_t) GPA_GL( "glRasterPos2fv" ); + glRasterPos2i = dllRasterPos2i = (glRasterPos2i_t) GPA_GL( "glRasterPos2i" ); + glRasterPos2iv = dllRasterPos2iv = (glRasterPos2iv_t) GPA_GL( "glRasterPos2iv" ); + glRasterPos2s = dllRasterPos2s = (glRasterPos2s_t) GPA_GL( "glRasterPos2s" ); + glRasterPos2sv = dllRasterPos2sv = (glRasterPos2sv_t) GPA_GL( "glRasterPos2sv" ); + glRasterPos3d = dllRasterPos3d = (glRasterPos3d_t) GPA_GL( "glRasterPos3d" ); + glRasterPos3dv = dllRasterPos3dv = (glRasterPos3dv_t) GPA_GL( "glRasterPos3dv" ); + glRasterPos3f = dllRasterPos3f = (glRasterPos3f_t) GPA_GL( "glRasterPos3f" ); + glRasterPos3fv = dllRasterPos3fv = (glRasterPos3fv_t) GPA_GL( "glRasterPos3fv" ); + glRasterPos3i = dllRasterPos3i = (glRasterPos3i_t) GPA_GL( "glRasterPos3i" ); + glRasterPos3iv = dllRasterPos3iv = (glRasterPos3iv_t) GPA_GL( "glRasterPos3iv" ); + glRasterPos3s = dllRasterPos3s = (glRasterPos3s_t) GPA_GL( "glRasterPos3s" ); + glRasterPos3sv = dllRasterPos3sv = (glRasterPos3sv_t) GPA_GL( "glRasterPos3sv" ); + glRasterPos4d = dllRasterPos4d = (glRasterPos4d_t) GPA_GL( "glRasterPos4d" ); + glRasterPos4dv = dllRasterPos4dv = (glRasterPos4dv_t) GPA_GL( "glRasterPos4dv" ); + glRasterPos4f = dllRasterPos4f = (glRasterPos4f_t) GPA_GL( "glRasterPos4f" ); + glRasterPos4fv = dllRasterPos4fv = (glRasterPos4fv_t) GPA_GL( "glRasterPos4fv" ); + glRasterPos4i = dllRasterPos4i = (glRasterPos4i_t) GPA_GL( "glRasterPos4i" ); + glRasterPos4iv = dllRasterPos4iv = (glRasterPos4iv_t) GPA_GL( "glRasterPos4iv" ); + glRasterPos4s = dllRasterPos4s = (glRasterPos4s_t) GPA_GL( "glRasterPos4s" ); + glRasterPos4sv = dllRasterPos4sv = (glRasterPos4sv_t) GPA_GL( "glRasterPos4sv" ); + glReadBuffer = dllReadBuffer = (glReadBuffer_t) GPA_GL( "glReadBuffer" ); + glReadPixels = dllReadPixels = (glReadPixels_t) GPA_GL( "glReadPixels" ); + glRectd = dllRectd = (glRectd_t) GPA_GL( "glRectd" ); + glRectdv = dllRectdv = (glRectdv_t) GPA_GL( "glRectdv" ); + glRectf = dllRectf = (glRectf_t) GPA_GL( "glRectf" ); + glRectfv = dllRectfv = (glRectfv_t) GPA_GL( "glRectfv" ); + glRecti = dllRecti = (glRecti_t) GPA_GL( "glRecti" ); + glRectiv = dllRectiv = (glRectiv_t) GPA_GL( "glRectiv" ); + glRects = dllRects = (glRects_t) GPA_GL( "glRects" ); + glRectsv = dllRectsv = (glRectsv_t) GPA_GL( "glRectsv" ); + glRenderMode = dllRenderMode = (glRenderMode_t) GPA_GL( "glRenderMode" ); + glRotated = dllRotated = (glRotated_t) GPA_GL( "glRotated" ); + glRotatef = dllRotatef = (glRotatef_t) GPA_GL( "glRotatef" ); + glScaled = dllScaled = (glScaled_t) GPA_GL( "glScaled" ); + glScalef = dllScalef = (glScalef_t) GPA_GL( "glScalef" ); + glScissor = dllScissor = (glScissor_t) GPA_GL( "glScissor" ); + glSelectBuffer = dllSelectBuffer = (glSelectBuffer_t) GPA_GL( "glSelectBuffer" ); + glShadeModel = dllShadeModel = (glShadeModel_t) GPA_GL( "glShadeModel" ); + glStencilFunc = dllStencilFunc = (glStencilFunc_t) GPA_GL( "glStencilFunc" ); + glStencilMask = dllStencilMask = (glStencilMask_t) GPA_GL( "glStencilMask" ); + glStencilOp = dllStencilOp = (glStencilOp_t) GPA_GL( "glStencilOp" ); + glTexCoord1d = dllTexCoord1d = (glTexCoord1d_t) GPA_GL( "glTexCoord1d" ); + glTexCoord1dv = dllTexCoord1dv = (glTexCoord1dv_t) GPA_GL( "glTexCoord1dv" ); + glTexCoord1f = dllTexCoord1f = (glTexCoord1f_t) GPA_GL( "glTexCoord1f" ); + glTexCoord1fv = dllTexCoord1fv = (glTexCoord1fv_t) GPA_GL( "glTexCoord1fv" ); + glTexCoord1i = dllTexCoord1i = (glTexCoord1i_t) GPA_GL( "glTexCoord1i" ); + glTexCoord1iv = dllTexCoord1iv = (glTexCoord1iv_t) GPA_GL( "glTexCoord1iv" ); + glTexCoord1s = dllTexCoord1s = (glTexCoord1s_t) GPA_GL( "glTexCoord1s" ); + glTexCoord1sv = dllTexCoord1sv = (glTexCoord1sv_t) GPA_GL( "glTexCoord1sv" ); + glTexCoord2d = dllTexCoord2d = (glTexCoord2d_t) GPA_GL( "glTexCoord2d" ); + glTexCoord2dv = dllTexCoord2dv = (glTexCoord2dv_t) GPA_GL( "glTexCoord2dv" ); + glTexCoord2f = dllTexCoord2f = (glTexCoord2f_t) GPA_GL( "glTexCoord2f" ); + glTexCoord2fv = dllTexCoord2fv = (glTexCoord2fv_t) GPA_GL( "glTexCoord2fv" ); + glTexCoord2i = dllTexCoord2i = (glTexCoord2i_t) GPA_GL( "glTexCoord2i" ); + glTexCoord2iv = dllTexCoord2iv = (glTexCoord2iv_t) GPA_GL( "glTexCoord2iv" ); + glTexCoord2s = dllTexCoord2s = (glTexCoord2s_t) GPA_GL( "glTexCoord2s" ); + glTexCoord2sv = dllTexCoord2sv = (glTexCoord2sv_t) GPA_GL( "glTexCoord2sv" ); + glTexCoord3d = dllTexCoord3d = (glTexCoord3d_t) GPA_GL( "glTexCoord3d" ); + glTexCoord3dv = dllTexCoord3dv = (glTexCoord3dv_t) GPA_GL( "glTexCoord3dv" ); + glTexCoord3f = dllTexCoord3f = (glTexCoord3f_t) GPA_GL( "glTexCoord3f" ); + glTexCoord3fv = dllTexCoord3fv = (glTexCoord3fv_t) GPA_GL( "glTexCoord3fv" ); + glTexCoord3i = dllTexCoord3i = (glTexCoord3i_t) GPA_GL( "glTexCoord3i" ); + glTexCoord3iv = dllTexCoord3iv = (glTexCoord3iv_t) GPA_GL( "glTexCoord3iv" ); + glTexCoord3s = dllTexCoord3s = (glTexCoord3s_t) GPA_GL( "glTexCoord3s" ); + glTexCoord3sv = dllTexCoord3sv = (glTexCoord3sv_t) GPA_GL( "glTexCoord3sv" ); + glTexCoord4d = dllTexCoord4d = (glTexCoord4d_t) GPA_GL( "glTexCoord4d" ); + glTexCoord4dv = dllTexCoord4dv = (glTexCoord4dv_t) GPA_GL( "glTexCoord4dv" ); + glTexCoord4f = dllTexCoord4f = (glTexCoord4f_t) GPA_GL( "glTexCoord4f" ); + glTexCoord4fv = dllTexCoord4fv = (glTexCoord4fv_t) GPA_GL( "glTexCoord4fv" ); + glTexCoord4i = dllTexCoord4i = (glTexCoord4i_t) GPA_GL( "glTexCoord4i" ); + glTexCoord4iv = dllTexCoord4iv = (glTexCoord4iv_t) GPA_GL( "glTexCoord4iv" ); + glTexCoord4s = dllTexCoord4s = (glTexCoord4s_t) GPA_GL( "glTexCoord4s" ); + glTexCoord4sv = dllTexCoord4sv = (glTexCoord4sv_t) GPA_GL( "glTexCoord4sv" ); + glTexCoordPointer = dllTexCoordPointer = (glTexCoordPointer_t) GPA_GL( "glTexCoordPointer" ); + glTexEnvf = dllTexEnvf = (glTexEnvf_t) GPA_GL( "glTexEnvf" ); + glTexEnvfv = dllTexEnvfv = (glTexEnvfv_t) GPA_GL( "glTexEnvfv" ); + glTexEnvi = dllTexEnvi = (glTexEnvi_t) GPA_GL( "glTexEnvi" ); + glTexEnviv = dllTexEnviv = (glTexEnviv_t) GPA_GL( "glTexEnviv" ); + glTexGend = dllTexGend = (glTexGend_t) GPA_GL( "glTexGend" ); + glTexGendv = dllTexGendv = (glTexGendv_t) GPA_GL( "glTexGendv" ); + glTexGenf = dllTexGenf = (glTexGenf_t) GPA_GL( "glTexGenf" ); + glTexGenfv = dllTexGenfv = (glTexGenfv_t) GPA_GL( "glTexGenfv" ); + glTexGeni = dllTexGeni = (glTexGeni_t) GPA_GL( "glTexGeni" ); + glTexGeniv = dllTexGeniv = (glTexGeniv_t) GPA_GL( "glTexGeniv" ); + glTexImage1D = dllTexImage1D = (glTexImage1D_t) GPA_GL( "glTexImage1D" ); + glTexImage2D = dllTexImage2D = (glTexImage2D_t) GPA_GL( "glTexImage2D" ); + glTexParameterf = dllTexParameterf = (glTexParameterf_t) GPA_GL( "glTexParameterf" ); + glTexParameterfv = dllTexParameterfv = (glTexParameterfv_t) GPA_GL( "glTexParameterfv" ); + glTexParameteri = dllTexParameteri = (glTexParameteri_t) GPA_GL( "glTexParameteri" ); + glTexParameteriv = dllTexParameteriv = (glTexParameteriv_t) GPA_GL( "glTexParameteriv" ); + glTexSubImage1D = dllTexSubImage1D = (glTexSubImage1D_t) GPA_GL( "glTexSubImage1D" ); + glTexSubImage2D = dllTexSubImage2D = (glTexSubImage2D_t) GPA_GL( "glTexSubImage2D" ); + glTranslated = dllTranslated = (glTranslated_t) GPA_GL( "glTranslated" ); + glTranslatef = dllTranslatef = (glTranslatef_t) GPA_GL( "glTranslatef" ); + glVertex2d = dllVertex2d = (glVertex2d_t) GPA_GL( "glVertex2d" ); + glVertex2dv = dllVertex2dv = (glVertex2dv_t) GPA_GL( "glVertex2dv" ); + glVertex2f = dllVertex2f = (glVertex2f_t) GPA_GL( "glVertex2f" ); + glVertex2fv = dllVertex2fv = (glVertex2fv_t) GPA_GL( "glVertex2fv" ); + glVertex2i = dllVertex2i = (glVertex2i_t) GPA_GL( "glVertex2i" ); + glVertex2iv = dllVertex2iv = (glVertex2iv_t) GPA_GL( "glVertex2iv" ); + + glVertex2s = dllVertex2s = (glVertex2s_t) GPA_GL( "glVertex2s" ); + glVertex2sv = dllVertex2sv = (glVertex2sv_t) GPA_GL( "glVertex2sv" ); + glVertex3d = dllVertex3d = (glVertex3d_t) GPA_GL( "glVertex3d" ); + glVertex3dv = dllVertex3dv = (glVertex3dv_t) GPA_GL( "glVertex3dv" ); + glVertex3f = dllVertex3f = (glVertex3f_t) GPA_GL( "glVertex3f" ); + glVertex3fv = dllVertex3fv = (glVertex3fv_t) GPA_GL( "glVertex3fv" ); + glVertex3i = dllVertex3i = (glVertex3i_t) GPA_GL( "glVertex3i" ); + glVertex3iv = dllVertex3iv = (glVertex3iv_t) GPA_GL( "glVertex3iv" ); + glVertex3s = dllVertex3s = (glVertex3s_t) GPA_GL( "glVertex3s" ); + glVertex3sv = dllVertex3sv = (glVertex3sv_t) GPA_GL( "glVertex3sv" ); + glVertex4d = dllVertex4d = (glVertex4d_t) GPA_GL( "glVertex4d" ); + glVertex4dv = dllVertex4dv = (glVertex4dv_t) GPA_GL( "glVertex4dv" ); + glVertex4f = dllVertex4f = (glVertex4f_t) GPA_GL( "glVertex4f" ); + glVertex4fv = dllVertex4fv = (glVertex4fv_t) GPA_GL( "glVertex4fv" ); + glVertex4i = dllVertex4i = (glVertex4i_t) GPA_GL( "glVertex4i" ); + glVertex4iv = dllVertex4iv = (glVertex4iv_t) GPA_GL( "glVertex4iv" ); + glVertex4s = dllVertex4s = (glVertex4s_t) GPA_GL( "glVertex4s" ); + glVertex4sv = dllVertex4sv = (glVertex4sv_t) GPA_GL( "glVertex4sv" ); + glVertexPointer = dllVertexPointer = (glVertexPointer_t) GPA_GL( "glVertexPointer" ); + glViewport = dllViewport = (glViewport_t) GPA_GL( "glViewport" ); + qwglChoosePixelFormat = (qwglChoosePixelFormat_t) GPA_GL( "wglChoosePixelFormat" ); + qwglDescribePixelFormat = (qwglDescribePixelFormat_t) GPA_GL( "wglDescribePixelFormat" ); + qwglGetPixelFormat = (qwglGetPixelFormat_t) GPA_GL( "wglGetPixelFormat" ); + qwglSetPixelFormat = (qwglSetPixelFormat_t) GPA_GL( "wglSetPixelFormat" ); + dllSwapBuffers = qwglSwapBuffers = (qwglSwapBuffers_t) GPA_GL( "wglSwapBuffers" ); + + if (dStrstr(dllname_gl,"D3D") != NULL) + { + qwglUseFontBitmaps = (qwglUseFontBitmaps_t) GPA_GL( "wd3dUseFontBitmapsA" ); + qwglUseFontOutlines = (qwglUseFontOutlines_t) GPA_GL( "wd3dUseFontOutlinesA" ); + qwglCreateContext = (qwglCreateContext_t) GPA_GL( "wd3dCreateContext" ); + qwglDeleteContext = (qwglDeleteContext_t) GPA_GL( "wd3dDeleteContext" ); + qwglGetCurrentContext = (qwglGetCurrentContext_t) GPA_GL( "wd3dGetCurrentContext" ); + qwglGetCurrentDC = (qwglGetCurrentDC_t) GPA_GL( "wd3dGetCurrentDC" ); + qwglGetProcAddress = (qwglGetProcAddress_t) GPA_GL( "wd3dGetProcAddress" ); + qwglMakeCurrent = (qwglMakeCurrent_t) GPA_GL( "wd3dMakeCurrent" ); + qwglCopyContext = (qwglCopyContext_t) GPA_GL( "wd3dCopyContext" ); + qwglCreateLayerContext = (qwglCreateLayerContext_t) GPA_GL( "wd3dCreateLayerContext" ); + qwglDescribeLayerPlane = (qwglDescribeLayerPlane_t) GPA_GL( "wd3dDescribeLayerPlane" ); + qwglGetLayerPaletteEntries = (qwglGetLayerPaletteEntries_t) GPA_GL( "wd3dGetLayerPaletteEntries" ); + qwglRealizeLayerPalette = (qwglRealizeLayerPalette_t) GPA_GL( "wd3dRealizeLayerPalette" ); + qwglSetLayerPaletteEntries = (qwglSetLayerPaletteEntries_t) GPA_GL( "wd3dSetLayerPaletteEntries" ); + qwglShareLists = (qwglShareLists_t) GPA_GL( "wd3dShareLists" ); + qwglSwapLayerBuffers = (qwglSwapLayerBuffers_t) GPA_GL( "wd3dSwapLayerBuffers" ); + } + else + { + qwglUseFontBitmaps = (qwglUseFontBitmaps_t) GPA_GL( "wglUseFontBitmapsA" ); + qwglUseFontOutlines = (qwglUseFontOutlines_t) GPA_GL( "wglUseFontOutlinesA" ); + qwglCreateContext = (qwglCreateContext_t) GPA_GL( "wglCreateContext" ); + qwglDeleteContext = (qwglDeleteContext_t) GPA_GL( "wglDeleteContext" ); + qwglGetCurrentContext = (qwglGetCurrentContext_t) GPA_GL( "wglGetCurrentContext" ); + qwglGetCurrentDC = (qwglGetCurrentDC_t) GPA_GL( "wglGetCurrentDC" ); + qwglGetProcAddress = (qwglGetProcAddress_t) GPA_GL( "wglGetProcAddress" ); + qwglMakeCurrent = (qwglMakeCurrent_t) GPA_GL( "wglMakeCurrent" ); + qwglCopyContext = (qwglCopyContext_t) GPA_GL( "wglCopyContext" ); + qwglCreateLayerContext = (qwglCreateLayerContext_t) GPA_GL( "wglCreateLayerContext" ); + qwglDescribeLayerPlane = (qwglDescribeLayerPlane_t) GPA_GL( "wglDescribeLayerPlane" ); + qwglGetLayerPaletteEntries = (qwglGetLayerPaletteEntries_t) GPA_GL( "wglGetLayerPaletteEntries" ); + qwglRealizeLayerPalette = (qwglRealizeLayerPalette_t) GPA_GL( "wglRealizeLayerPalette" ); + qwglSetLayerPaletteEntries = (qwglSetLayerPaletteEntries_t) GPA_GL( "wglSetLayerPaletteEntries" ); + qwglShareLists = (qwglShareLists_t) GPA_GL( "wglShareLists" ); + qwglSwapLayerBuffers = (qwglSwapLayerBuffers_t) GPA_GL( "wglSwapLayerBuffers" ); + } + + qwglSwapIntervalEXT = 0; + + return true; +} + + +bool QGL_EXT_Init( ) +{ + // Load extensions... + // + const char* pExtString = reinterpret_cast(glGetString(GL_EXTENSIONS)); + gGLState.primMode = 0; + +// extern void ( GLAPIENTRY* glColorTableEXT)(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data); + // EXT_paletted_texture + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_paletted_texture") != NULL) + { + glColorTableEXT = dllColorTableEXT = (glColorTable_t) qwglGetProcAddress("glColorTableEXT"); + gGLState.suppPalettedTexture = true; + } + else + { + gGLState.suppPalettedTexture = false; + } + + // EXT_compiled_vertex_array + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_compiled_vertex_array") != NULL) + { + glLockArraysEXT = dllLockArraysEXT = (glLockArrays_t) qwglGetProcAddress("glLockArraysEXT"); + glUnlockArraysEXT = dllUnlockArraysEXT = (glUnlockArrays_t) qwglGetProcAddress("glUnlockArraysEXT"); + gGLState.suppLockedArrays = true; + } + else + { + glLockArraysEXT = dllLockArraysEXT = NULL; + glUnlockArraysEXT = dllUnlockArraysEXT = NULL; + gGLState.suppLockedArrays = false; + } + + // ARB_multitexture + if (pExtString && dStrstr(pExtString, (const char*)"GL_ARB_multitexture") != NULL) { + glActiveTextureARB = dllActiveTextureARB = (glActiveTextureARB_t) qwglGetProcAddress("glActiveTextureARB"); + glClientActiveTextureARB = dllClientActiveTextureARB = (glClientActiveTextureARB_t) qwglGetProcAddress("glClientActiveTextureARB"); + glMultiTexCoord2fARB = dllMultiTexCoord2fARB = (glMultiTexCoord2fARB_t) qwglGetProcAddress("glMultiTexCoord2fARB"); + glMultiTexCoord2fvARB = dllMultiTexCoord2fvARB = (glMultiTexCoord2fvARB_t) qwglGetProcAddress("glMultiTexCoord2fvARB"); + gGLState.suppARBMultitexture = true; + } else { + glActiveTextureARB = dllActiveTextureARB = NULL; + glClientActiveTextureARB = dllClientActiveTextureARB = NULL; + glMultiTexCoord2fARB = dllMultiTexCoord2fARB = NULL; + glMultiTexCoord2fvARB = dllMultiTexCoord2fvARB = NULL; + gGLState.suppARBMultitexture = false; + } + + // NV_vertex_array_range + if (pExtString && dStrstr(pExtString, (const char*)"GL_NV_vertex_array_range") != NULL) { + glVertexArrayRangeNV = dllVertexArrayRangeNV = (glVertexArrayRange_t) qwglGetProcAddress("glVertexArrayRangeNV"); + glFlushVertexArrayRangeNV = dllFlushVertexArrayRangeNV = (glFlushVertexArrayRange_t) qwglGetProcAddress("glFlushVertexArrayRangeNV"); + wglAllocateMemoryNV = dllAllocateMemoryNV = (wglAllocateMemory_t) qwglGetProcAddress("wglAllocateMemoryNV"); + wglFreeMemoryNV = dllFreeMemoryNV = (wglFreeMemory_t) qwglGetProcAddress("wglFreeMemoryNV"); + + gGLState.suppVertexArrayRange = true; + } else { + glVertexArrayRangeNV = dllVertexArrayRangeNV = NULL; + glFlushVertexArrayRangeNV = dllFlushVertexArrayRangeNV = NULL; + wglAllocateMemoryNV = dllAllocateMemoryNV = NULL; + wglFreeMemoryNV = dllFreeMemoryNV = NULL; + gGLState.suppVertexArrayRange = false; + } + + // EXT_fog_coord + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_fog_coord") != NULL) { + glFogCoordfEXT = dllFogCoordfEXT = (glFogCoordf_t) qwglGetProcAddress("glFogCoordfEXT"); + glFogCoordPointerEXT = dllFogCoordPointerEXT = (glFogCoordPointer_t) qwglGetProcAddress("glFogCoordPointerEXT"); + gGLState.suppFogCoord = true; + } else { + glFogCoordfEXT = dllFogCoordfEXT = NULL; + glFogCoordPointerEXT = dllFogCoordPointerEXT = NULL; + gGLState.suppFogCoord = false; + } + + // ARB_texture_compression + if (pExtString && dStrstr(pExtString, (const char*)"GL_ARB_texture_compression") != NULL) { + glCompressedTexImage3DARB = dllCompressedTexImage3DARB = (glCompressedTexImage3DARB_t) qwglGetProcAddress("glCompressedTexImage3DARB"); + glCompressedTexImage2DARB = dllCompressedTexImage2DARB = (glCompressedTexImage2DARB_t) qwglGetProcAddress("glCompressedTexImage2DARB"); + glCompressedTexImage1DARB = dllCompressedTexImage1DARB = (glCompressedTexImage1DARB_t) qwglGetProcAddress("glCompressedTexImage1DARB"); + glCompressedTexSubImage3DARB = dllCompressedTexSubImage3DARB = (glCompressedTexSubImage3DARB_t) qwglGetProcAddress("glCompressedTexSubImage3DARB"); + glCompressedTexSubImage2DARB = dllCompressedTexSubImage2DARB = (glCompressedTexSubImage2DARB_t) qwglGetProcAddress("glCompressedTexSubImage2DARB"); + glCompressedTexSubImage1DARB = dllCompressedTexSubImage1DARB = (glCompressedTexSubImage1DARB_t) qwglGetProcAddress("glCompressedTexSubImage1DARB"); + glGetCompressedTexImageARB = dllGetCompressedTexImageARB = (glGetCompressedTexImageARB_t) qwglGetProcAddress("glGetCompressedTexImageARB"); + + gGLState.suppTextureCompression = true; + } else { + glCompressedTexImage3DARB = dllCompressedTexImage3DARB = NULL; + glCompressedTexImage2DARB = dllCompressedTexImage2DARB = NULL; + glCompressedTexImage1DARB = dllCompressedTexImage1DARB = NULL; + glCompressedTexSubImage3DARB = dllCompressedTexSubImage3DARB = NULL; + glCompressedTexSubImage2DARB = dllCompressedTexSubImage2DARB = NULL; + glCompressedTexSubImage1DARB = dllCompressedTexSubImage1DARB = NULL; + glGetCompressedTexImageARB = dllGetCompressedTexImageARB = NULL; + + gGLState.suppTextureCompression = false; + } + + // 3DFX_texture_compression_FXT1 + if (pExtString && dStrstr(pExtString, (const char*)"GL_3DFX_texture_compression_FXT1") != NULL) + gGLState.suppFXT1 = true; + else + gGLState.suppFXT1 = false; + + // EXT_texture_compression_S3TC + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_texture_compression_s3tc") != NULL) + gGLState.suppS3TC = true; + else + gGLState.suppS3TC = false; + + // WGL_3DFX_gamma_control + if (pExtString && dStrstr(pExtString, (const char*)"WGL_3DFX_gamma_control" ) != NULL) + { + qwglGetDeviceGammaRamp3DFX = (qwglGetDeviceGammaRamp3DFX_t) qwglGetProcAddress( "wglGetDeviceGammaRamp3DFX" ); + qwglSetDeviceGammaRamp3DFX = (qwglSetDeviceGammaRamp3DFX_t) qwglGetProcAddress( "wglSetDeviceGammaRamp3DFX" ); + } + else + { + qwglGetDeviceGammaRamp3DFX = NULL; + qwglSetDeviceGammaRamp3DFX = NULL; + } + + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_vertex_buffer") != NULL) + { + glAvailableVertexBufferEXT = dllAvailableVertexBufferEXT = (glAvailableVertexBufferEXT_t) qwglGetProcAddress("glAvailableVertexBufferEXT"); + glAllocateVertexBufferEXT = dllAllocateVertexBufferEXT = (glAllocateVertexBufferEXT_t) qwglGetProcAddress("glAllocateVertexBufferEXT"); + glLockVertexBufferEXT = dllLockVertexBufferEXT = (glLockVertexBufferEXT_t) qwglGetProcAddress("glLockVertexBufferEXT"); + glUnlockVertexBufferEXT = dllUnlockVertexBufferEXT = (glUnlockVertexBufferEXT_t) qwglGetProcAddress("glUnlockVertexBufferEXT"); + glSetVertexBufferEXT = dllSetVertexBufferEXT = (glSetVertexBufferEXT_t) qwglGetProcAddress("glSetVertexBufferEXT"); + glOffsetVertexBufferEXT = dllOffsetVertexBufferEXT = (glOffsetVertexBufferEXT_t) qwglGetProcAddress("glOffsetVertexBufferEXT"); + glFillVertexBufferEXT = dllFillVertexBufferEXT = (glFillVertexBufferEXT_t) qwglGetProcAddress("glFillVertexBufferEXT"); + glFreeVertexBufferEXT = dllFreeVertexBufferEXT = (glFreeVertexBufferEXT_t) qwglGetProcAddress("glFreeVertexBufferEXT"); + + gGLState.suppVertexBuffer = true; + } + else + { + glAvailableVertexBufferEXT = dllAvailableVertexBufferEXT = NULL; + glAllocateVertexBufferEXT = dllAllocateVertexBufferEXT = NULL; + glLockVertexBufferEXT = dllLockVertexBufferEXT = NULL; + glUnlockVertexBufferEXT = dllUnlockVertexBufferEXT = NULL; + glSetVertexBufferEXT = dllSetVertexBufferEXT = NULL; + glOffsetVertexBufferEXT = dllOffsetVertexBufferEXT = NULL; + glFillVertexBufferEXT = dllFillVertexBufferEXT = NULL; + glFreeVertexBufferEXT = dllFreeVertexBufferEXT = NULL; + + gGLState.suppVertexBuffer = false; + } + + // Binary states, i.e., no supporting functions + // EXT_packed_pixels + // EXT_texture_env_combine + // + 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.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; + + // Swap interval + if (pExtString && dStrstr(pExtString, (const char*)"WGL_EXT_swap_control") != NULL) + { + qwglSwapIntervalEXT = (qwglSwapIntervalEXT_t) qwglGetProcAddress( "wglSwapIntervalEXT" ); + gGLState.suppSwapInterval = ( qwglSwapIntervalEXT != NULL ); + } + else + { + qwglSwapIntervalEXT = NULL; + gGLState.suppSwapInterval = false; + } + + Con::printf("OpenGL Init: Enabled Extensions"); + if (gGLState.suppARBMultitexture) Con::printf(" ARB_multitexture (Max Texture Units: %d)", gGLState.maxTextureUnits); + 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.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; +} + + +//-------------------------------------- + +static bool loggingEnabled = false; +static bool outlineEnabled = false; +static bool perfEnabled = false; + +#if defined (DEBUG) || defined(INTERNAL_RELEASE) +ConsoleFunction(GLEnableLogging, void, 2, 2, "GLEnableLogging(bool);") +{ + argc; + bool enable = dAtob(argv[1]); + + if(loggingEnabled == enable) + return; + + if(enable && (outlineEnabled || perfEnabled)) + return; + + loggingEnabled = enable; + + if ( enable ) + { + if ( !winState.log_fp ) + { + struct tm *newtime; + time_t aclock; + + time( &aclock ); + newtime = localtime( &aclock ); + + asctime( newtime ); + + winState.log_fp = fopen( "gl_log.txt", "wt" ); + + fprintf( winState.log_fp, "%s\n", asctime( newtime ) ); + fflush(winState.log_fp); + } + + + // GLU Functions + gluOrtho2D = loggluOrtho2D; + gluPerspective = loggluPerspective; + gluPickMatrix = loggluPickMatrix; + gluLookAt = loggluLookAt; + gluProject = loggluProject; + gluUnProject = loggluUnProject; + gluScaleImage = loggluScaleImage; + gluBuild1DMipmaps = loggluBuild1DMipmaps; + gluBuild2DMipmaps = loggluBuild2DMipmaps; + + // GL Functions + glAccum = logAccum; + glAlphaFunc = logAlphaFunc; + glAreTexturesResident = logAreTexturesResident; + glArrayElement = logArrayElement; + glBegin = logBegin; + glBindTexture = logBindTexture; + glBitmap = logBitmap; + glBlendFunc = logBlendFunc; + glCallList = logCallList; + glCallLists = logCallLists; + glClear = logClear; + glClearAccum = logClearAccum; + glClearColor = logClearColor; + glClearDepth = logClearDepth; + glClearIndex = logClearIndex; + glClearStencil = logClearStencil; + glClipPlane = logClipPlane; + glColor3b = logColor3b; + glColor3bv = logColor3bv; + glColor3d = logColor3d; + glColor3dv = logColor3dv; + glColor3f = logColor3f; + glColor3fv = logColor3fv; + glColor3i = logColor3i; + glColor3iv = logColor3iv; + glColor3s = logColor3s; + glColor3sv = logColor3sv; + glColor3ub = logColor3ub; + glColor3ubv = logColor3ubv; + glColor3ui = logColor3ui; + glColor3uiv = logColor3uiv; + glColor3us = logColor3us; + glColor3usv = logColor3usv; + glColor4b = logColor4b; + glColor4bv = logColor4bv; + glColor4d = logColor4d; + glColor4dv = logColor4dv; + glColor4f = logColor4f; + glColor4fv = logColor4fv; + glColor4i = logColor4i; + glColor4iv = logColor4iv; + glColor4s = logColor4s; + glColor4sv = logColor4sv; + glColor4ub = logColor4ub; + glColor4ubv = logColor4ubv; + glColor4ui = logColor4ui; + glColor4uiv = logColor4uiv; + glColor4us = logColor4us; + glColor4usv = logColor4usv; + glColorMask = logColorMask; + glColorMaterial = logColorMaterial; + glColorPointer = logColorPointer; + glCopyPixels = logCopyPixels; + glCopyTexImage1D = logCopyTexImage1D; + glCopyTexImage2D = logCopyTexImage2D; + glCopyTexSubImage1D = logCopyTexSubImage1D; + glCopyTexSubImage2D = logCopyTexSubImage2D; + glCullFace = logCullFace; + glDeleteLists = logDeleteLists ; + glDeleteTextures = logDeleteTextures; + glDepthFunc = logDepthFunc; + glDepthMask = logDepthMask; + glDepthRange = logDepthRange; + glDisable = logDisable; + glDisableClientState = logDisableClientState; + glDrawArrays = logDrawArrays; + glDrawBuffer = logDrawBuffer; + glDrawElements = logDrawElements; + glDrawPixels = logDrawPixels; + glEdgeFlag = logEdgeFlag; + glEdgeFlagPointer = logEdgeFlagPointer; + glEdgeFlagv = logEdgeFlagv; + glEnable = logEnable; + glEnableClientState = logEnableClientState; + glEnd = logEnd; + glEndList = logEndList; + glEvalCoord1d = logEvalCoord1d; + glEvalCoord1dv = logEvalCoord1dv; + glEvalCoord1f = logEvalCoord1f; + glEvalCoord1fv = logEvalCoord1fv; + glEvalCoord2d = logEvalCoord2d; + glEvalCoord2dv = logEvalCoord2dv; + glEvalCoord2f = logEvalCoord2f; + glEvalCoord2fv = logEvalCoord2fv; + glEvalMesh1 = logEvalMesh1; + glEvalMesh2 = logEvalMesh2; + glEvalPoint1 = logEvalPoint1; + glEvalPoint2 = logEvalPoint2; + glFeedbackBuffer = logFeedbackBuffer; + glFinish = logFinish; + glFlush = logFlush; + glFogf = logFogf; + glFogfv = logFogfv; + glFogi = logFogi; + glFogiv = logFogiv; + glFrontFace = logFrontFace; + glFrustum = logFrustum; + glGenLists = logGenLists; + glGenTextures = logGenTextures; + glGetBooleanv = logGetBooleanv; + glGetClipPlane = logGetClipPlane; + glGetDoublev = logGetDoublev; + glGetError = logGetError; + glGetFloatv = logGetFloatv; + glGetIntegerv = logGetIntegerv; + glGetLightfv = logGetLightfv; + glGetLightiv = logGetLightiv; + glGetMapdv = logGetMapdv; + glGetMapfv = logGetMapfv; + glGetMapiv = logGetMapiv; + glGetMaterialfv = logGetMaterialfv; + glGetMaterialiv = logGetMaterialiv; + glGetPixelMapfv = logGetPixelMapfv; + glGetPixelMapuiv = logGetPixelMapuiv; + glGetPixelMapusv = logGetPixelMapusv; + glGetPointerv = logGetPointerv; + glGetPolygonStipple = logGetPolygonStipple; + glGetString = logGetString; + glGetTexEnvfv = logGetTexEnvfv; + glGetTexEnviv = logGetTexEnviv; + glGetTexGendv = logGetTexGendv; + glGetTexGenfv = logGetTexGenfv; + glGetTexGeniv = logGetTexGeniv; + glGetTexImage = logGetTexImage; + glGetTexLevelParameterfv = logGetTexLevelParameterfv; + glGetTexLevelParameteriv = logGetTexLevelParameteriv; + glGetTexParameterfv = logGetTexParameterfv; + glGetTexParameteriv = logGetTexParameteriv; + glHint = logHint; + glIndexMask = logIndexMask; + glIndexPointer = logIndexPointer; + glIndexd = logIndexd; + glIndexdv = logIndexdv; + glIndexf = logIndexf; + glIndexfv = logIndexfv; + glIndexi = logIndexi; + glIndexiv = logIndexiv; + glIndexs = logIndexs; + glIndexsv = logIndexsv; + glIndexub = logIndexub; + glIndexubv = logIndexubv; + glInitNames = logInitNames; + glInterleavedArrays = logInterleavedArrays; + glIsEnabled = logIsEnabled; + glIsList = logIsList; + glIsTexture = logIsTexture; + glLightModelf = logLightModelf; + glLightModelfv = logLightModelfv; + glLightModeli = logLightModeli; + glLightModeliv = logLightModeliv; + glLightf = logLightf; + glLightfv = logLightfv; + glLighti = logLighti; + glLightiv = logLightiv; + glLineStipple = logLineStipple; + glLineWidth = logLineWidth; + glListBase = logListBase; + glLoadIdentity = logLoadIdentity; + glLoadMatrixd = logLoadMatrixd; + glLoadMatrixf = logLoadMatrixf; + glLoadName = logLoadName; + glLogicOp = logLogicOp; + glMap1d = logMap1d; + glMap1f = logMap1f; + glMap2d = logMap2d; + glMap2f = logMap2f; + glMapGrid1d = logMapGrid1d; + glMapGrid1f = logMapGrid1f; + glMapGrid2d = logMapGrid2d; + glMapGrid2f = logMapGrid2f; + glMaterialf = logMaterialf; + glMaterialfv = logMaterialfv; + glMateriali = logMateriali; + glMaterialiv = logMaterialiv; + glMatrixMode = logMatrixMode; + glMultMatrixd = logMultMatrixd; + glMultMatrixf = logMultMatrixf; + glNewList = logNewList; + glNormal3b = logNormal3b; + glNormal3bv = logNormal3bv; + glNormal3d = logNormal3d; + glNormal3dv = logNormal3dv; + glNormal3f = logNormal3f; + glNormal3fv = logNormal3fv; + glNormal3i = logNormal3i; + glNormal3iv = logNormal3iv; + glNormal3s = logNormal3s; + glNormal3sv = logNormal3sv; + glNormalPointer = logNormalPointer; + glOrtho = logOrtho; + glPassThrough = logPassThrough; + glPixelMapfv = logPixelMapfv; + glPixelMapuiv = logPixelMapuiv; + glPixelMapusv = logPixelMapusv; + glPixelStoref = logPixelStoref; + glPixelStorei = logPixelStorei; + glPixelTransferf = logPixelTransferf; + glPixelTransferi = logPixelTransferi; + glPixelZoom = logPixelZoom; + glPointSize = logPointSize; + glPolygonMode = logPolygonMode; + glPolygonOffset = logPolygonOffset; + glPolygonStipple = logPolygonStipple; + glPopAttrib = logPopAttrib; + glPopClientAttrib = logPopClientAttrib; + glPopMatrix = logPopMatrix; + glPopName = logPopName; + glPrioritizeTextures = logPrioritizeTextures; + glPushAttrib = logPushAttrib; + glPushClientAttrib = logPushClientAttrib; + glPushMatrix = logPushMatrix; + glPushName = logPushName; + glRasterPos2d = logRasterPos2d; + glRasterPos2dv = logRasterPos2dv; + glRasterPos2f = logRasterPos2f; + glRasterPos2fv = logRasterPos2fv; + glRasterPos2i = logRasterPos2i; + glRasterPos2iv = logRasterPos2iv; + glRasterPos2s = logRasterPos2s; + glRasterPos2sv = logRasterPos2sv; + glRasterPos3d = logRasterPos3d; + glRasterPos3dv = logRasterPos3dv; + glRasterPos3f = logRasterPos3f; + glRasterPos3fv = logRasterPos3fv; + glRasterPos3i = logRasterPos3i; + glRasterPos3iv = logRasterPos3iv; + glRasterPos3s = logRasterPos3s; + glRasterPos3sv = logRasterPos3sv; + glRasterPos4d = logRasterPos4d; + glRasterPos4dv = logRasterPos4dv; + glRasterPos4f = logRasterPos4f; + glRasterPos4fv = logRasterPos4fv; + glRasterPos4i = logRasterPos4i; + glRasterPos4iv = logRasterPos4iv; + glRasterPos4s = logRasterPos4s; + glRasterPos4sv = logRasterPos4sv; + glReadBuffer = logReadBuffer; + glReadPixels = logReadPixels; + glRectd = logRectd; + glRectdv = logRectdv; + glRectf = logRectf; + glRectfv = logRectfv; + glRecti = logRecti; + glRectiv = logRectiv; + glRects = logRects; + glRectsv = logRectsv; + glRenderMode = logRenderMode; + glRotated = logRotated; + glRotatef = logRotatef; + glScaled = logScaled; + glScalef = logScalef; + glScissor = logScissor; + glSelectBuffer = logSelectBuffer; + glShadeModel = logShadeModel; + glStencilFunc = logStencilFunc; + glStencilMask = logStencilMask; + glStencilOp = logStencilOp; + glTexCoord1d = logTexCoord1d; + glTexCoord1dv = logTexCoord1dv; + glTexCoord1f = logTexCoord1f; + glTexCoord1fv = logTexCoord1fv; + glTexCoord1i = logTexCoord1i; + glTexCoord1iv = logTexCoord1iv; + glTexCoord1s = logTexCoord1s; + glTexCoord1sv = logTexCoord1sv; + glTexCoord2d = logTexCoord2d; + glTexCoord2dv = logTexCoord2dv; + glTexCoord2f = logTexCoord2f; + glTexCoord2fv = logTexCoord2fv; + glTexCoord2i = logTexCoord2i; + glTexCoord2iv = logTexCoord2iv; + glTexCoord2s = logTexCoord2s; + glTexCoord2sv = logTexCoord2sv; + glTexCoord3d = logTexCoord3d; + glTexCoord3dv = logTexCoord3dv; + glTexCoord3f = logTexCoord3f; + glTexCoord3fv = logTexCoord3fv; + glTexCoord3i = logTexCoord3i; + glTexCoord3iv = logTexCoord3iv; + glTexCoord3s = logTexCoord3s; + glTexCoord3sv = logTexCoord3sv; + glTexCoord4d = logTexCoord4d; + glTexCoord4dv = logTexCoord4dv; + glTexCoord4f = logTexCoord4f; + glTexCoord4fv = logTexCoord4fv; + glTexCoord4i = logTexCoord4i; + glTexCoord4iv = logTexCoord4iv; + glTexCoord4s = logTexCoord4s; + glTexCoord4sv = logTexCoord4sv; + glTexCoordPointer = logTexCoordPointer; + glTexEnvf = logTexEnvf; + glTexEnvfv = logTexEnvfv; + glTexEnvi = logTexEnvi; + glTexEnviv = logTexEnviv; + glTexGend = logTexGend; + glTexGendv = logTexGendv; + glTexGenf = logTexGenf; + glTexGenfv = logTexGenfv; + glTexGeni = logTexGeni; + glTexGeniv = logTexGeniv; + glTexImage1D = logTexImage1D; + glTexImage2D = logTexImage2D; + glTexParameterf = logTexParameterf; + glTexParameterfv = logTexParameterfv; + glTexParameteri = logTexParameteri; + glTexParameteriv = logTexParameteriv; + glTexSubImage1D = logTexSubImage1D; + glTexSubImage2D = logTexSubImage2D; + glTranslated = logTranslated; + glTranslatef = logTranslatef; + glVertex2d = logVertex2d; + glVertex2dv = logVertex2dv; + glVertex2f = logVertex2f; + glVertex2fv = logVertex2fv; + glVertex2i = logVertex2i; + glVertex2iv = logVertex2iv; + glVertex2s = logVertex2s; + glVertex2sv = logVertex2sv; + glVertex3d = logVertex3d; + glVertex3dv = logVertex3dv; + glVertex3f = logVertex3f; + glVertex3fv = logVertex3fv; + glVertex3i = logVertex3i; + glVertex3iv = logVertex3iv; + glVertex3s = logVertex3s; + glVertex3sv = logVertex3sv; + glVertex4d = logVertex4d; + glVertex4dv = logVertex4dv; + glVertex4f = logVertex4f; + glVertex4fv = logVertex4fv; + glVertex4i = logVertex4i; + glVertex4iv = logVertex4iv; + glVertex4s = logVertex4s; + glVertex4sv = logVertex4sv; + glVertexPointer = logVertexPointer; + glViewport = logViewport; + + if (dglDoesSupportPalettedTexture()) + { + glColorTableEXT = logColorTableEXT; + } + + if (dglDoesSupportCompiledVertexArray()) { + glLockArraysEXT = logLockArraysEXT; + glUnlockArraysEXT = logUnlockArraysEXT; + } + + if (dglDoesSupportARBMultitexture()) { + glActiveTextureARB = logActiveTextureARB; + glClientActiveTextureARB = logClientActiveTextureARB; + glMultiTexCoord2fARB = logMultiTexCoord2fARB; + glMultiTexCoord2fvARB = logMultiTexCoord2fvARB; + } + + if (dglDoesSupportVertexArrayRange()) { + glVertexArrayRangeNV = logVertexArrayRangeNV; + glFlushVertexArrayRangeNV = logFlushVertexArrayRangeNV; + wglAllocateMemoryNV = logAllocateMemoryNV; + wglFreeMemoryNV = logFreeMemoryNV; + } + + if (dglDoesSupportFogCoord()) { + glFogCoordfEXT = logFogCoordfEXT; + glFogCoordPointerEXT = logFogCoordPointerEXT; + } + + if (dglDoesSupportTextureCompression()) { + glCompressedTexImage3DARB = logCompressedTexImage3DARB; + glCompressedTexImage2DARB = logCompressedTexImage2DARB; + glCompressedTexImage1DARB = logCompressedTexImage1DARB; + glCompressedTexSubImage3DARB = logCompressedTexSubImage3DARB; + glCompressedTexSubImage2DARB = logCompressedTexSubImage2DARB; + glCompressedTexSubImage1DARB = logCompressedTexSubImage1DARB; + glGetCompressedTexImageARB = logGetCompressedTexImageARB; + } + + if (dglDoesSupportVertexBuffer()) { + glAvailableVertexBufferEXT = logAvailableVertexBufferEXT; + glAllocateVertexBufferEXT = logAllocateVertexBufferEXT; + glLockVertexBufferEXT = logLockVertexBufferEXT; + glUnlockVertexBufferEXT = logUnlockVertexBufferEXT; + glSetVertexBufferEXT = logSetVertexBufferEXT; + glOffsetVertexBufferEXT = logOffsetVertexBufferEXT; + glFillVertexBufferEXT = logFillVertexBufferEXT; + glFreeVertexBufferEXT = logFreeVertexBufferEXT; + } + } + else + { + if ( winState.log_fp ) { + fprintf( winState.log_fp, "*** CLOSING LOG ***\n" ); + fflush(winState.log_fp); + fclose( winState.log_fp ); + winState.log_fp = NULL; + } + + // GLU Functions + gluOrtho2D = dllgluOrtho2D; + gluPerspective = dllgluPerspective; + gluPickMatrix = dllgluPickMatrix; + gluLookAt = dllgluLookAt; + gluProject = dllgluProject; + gluUnProject = dllgluUnProject; + gluScaleImage = dllgluScaleImage; + gluBuild1DMipmaps = dllgluBuild1DMipmaps; + gluBuild2DMipmaps = dllgluBuild2DMipmaps; + + // GL Functions + glAccum = dllAccum; + glAlphaFunc = dllAlphaFunc; + glAreTexturesResident = dllAreTexturesResident; + glArrayElement = dllArrayElement; + glBegin = dllBegin; + glBindTexture = dllBindTexture; + glBitmap = dllBitmap; + glBlendFunc = dllBlendFunc; + glCallList = dllCallList; + glCallLists = dllCallLists; + glClear = dllClear; + glClearAccum = dllClearAccum; + glClearColor = dllClearColor; + glClearDepth = dllClearDepth; + glClearIndex = dllClearIndex; + glClearStencil = dllClearStencil; + glClipPlane = dllClipPlane; + glColor3b = dllColor3b; + glColor3bv = dllColor3bv; + glColor3d = dllColor3d; + glColor3dv = dllColor3dv; + glColor3f = dllColor3f; + glColor3fv = dllColor3fv; + glColor3i = dllColor3i; + glColor3iv = dllColor3iv; + glColor3s = dllColor3s; + glColor3sv = dllColor3sv; + glColor3ub = dllColor3ub; + glColor3ubv = dllColor3ubv; + glColor3ui = dllColor3ui; + glColor3uiv = dllColor3uiv; + glColor3us = dllColor3us; + glColor3usv = dllColor3usv; + glColor4b = dllColor4b; + glColor4bv = dllColor4bv; + glColor4d = dllColor4d; + glColor4dv = dllColor4dv; + glColor4f = dllColor4f; + glColor4fv = dllColor4fv; + glColor4i = dllColor4i; + glColor4iv = dllColor4iv; + glColor4s = dllColor4s; + glColor4sv = dllColor4sv; + glColor4ub = dllColor4ub; + glColor4ubv = dllColor4ubv; + glColor4ui = dllColor4ui; + glColor4uiv = dllColor4uiv; + glColor4us = dllColor4us; + glColor4usv = dllColor4usv; + glColorMask = dllColorMask; + glColorMaterial = dllColorMaterial; + glColorPointer = dllColorPointer; + glCopyPixels = dllCopyPixels; + glCopyTexImage1D = dllCopyTexImage1D; + glCopyTexImage2D = dllCopyTexImage2D; + glCopyTexSubImage1D = dllCopyTexSubImage1D; + glCopyTexSubImage2D = dllCopyTexSubImage2D; + glCullFace = dllCullFace; + glDeleteLists = dllDeleteLists; + glDeleteTextures = dllDeleteTextures; + glDepthFunc = dllDepthFunc; + glDepthMask = dllDepthMask; + glDepthRange = dllDepthRange; + glDisable = dllDisable; + glDisableClientState = dllDisableClientState; + glDrawArrays = dllDrawArrays; + glDrawBuffer = dllDrawBuffer; + glDrawElements = dllDrawElements; + glDrawPixels = dllDrawPixels; + glEdgeFlag = dllEdgeFlag; + glEdgeFlagPointer = dllEdgeFlagPointer; + glEdgeFlagv = dllEdgeFlagv; + glEnable = dllEnable; + glEnableClientState = dllEnableClientState; + glEnd = dllEnd; + glEndList = dllEndList; + glEvalCoord1d = dllEvalCoord1d; + glEvalCoord1dv = dllEvalCoord1dv; + glEvalCoord1f = dllEvalCoord1f; + glEvalCoord1fv = dllEvalCoord1fv; + glEvalCoord2d = dllEvalCoord2d; + glEvalCoord2dv = dllEvalCoord2dv; + glEvalCoord2f = dllEvalCoord2f; + glEvalCoord2fv = dllEvalCoord2fv; + glEvalMesh1 = dllEvalMesh1; + glEvalMesh2 = dllEvalMesh2; + glEvalPoint1 = dllEvalPoint1; + glEvalPoint2 = dllEvalPoint2; + glFeedbackBuffer = dllFeedbackBuffer; + glFinish = dllFinish; + glFlush = dllFlush; + glFogf = dllFogf; + glFogfv = dllFogfv; + glFogi = dllFogi; + glFogiv = dllFogiv; + glFrontFace = dllFrontFace; + glFrustum = dllFrustum; + glGenLists = dllGenLists; + glGenTextures = dllGenTextures; + glGetBooleanv = dllGetBooleanv; + glGetClipPlane = dllGetClipPlane; + glGetDoublev = dllGetDoublev; + glGetError = dllGetError; + glGetFloatv = dllGetFloatv; + glGetIntegerv = dllGetIntegerv; + glGetLightfv = dllGetLightfv; + glGetLightiv = dllGetLightiv; + glGetMapdv = dllGetMapdv; + glGetMapfv = dllGetMapfv; + glGetMapiv = dllGetMapiv; + glGetMaterialfv = dllGetMaterialfv; + glGetMaterialiv = dllGetMaterialiv; + glGetPixelMapfv = dllGetPixelMapfv; + glGetPixelMapuiv = dllGetPixelMapuiv; + glGetPixelMapusv = dllGetPixelMapusv; + glGetPointerv = dllGetPointerv; + glGetPolygonStipple = dllGetPolygonStipple; + glGetString = dllGetString; + glGetTexEnvfv = dllGetTexEnvfv; + glGetTexEnviv = dllGetTexEnviv; + glGetTexGendv = dllGetTexGendv; + glGetTexGenfv = dllGetTexGenfv; + glGetTexGeniv = dllGetTexGeniv; + glGetTexImage = dllGetTexImage; + glGetTexLevelParameterfv = dllGetTexLevelParameterfv; + glGetTexLevelParameteriv = dllGetTexLevelParameteriv; + glGetTexParameterfv = dllGetTexParameterfv; + glGetTexParameteriv = dllGetTexParameteriv; + glHint = dllHint; + glIndexMask = dllIndexMask; + glIndexPointer = dllIndexPointer; + glIndexd = dllIndexd; + glIndexdv = dllIndexdv; + glIndexf = dllIndexf; + glIndexfv = dllIndexfv; + glIndexi = dllIndexi; + glIndexiv = dllIndexiv; + glIndexs = dllIndexs; + glIndexsv = dllIndexsv; + glIndexub = dllIndexub; + glIndexubv = dllIndexubv; + glInitNames = dllInitNames; + glInterleavedArrays = dllInterleavedArrays; + glIsEnabled = dllIsEnabled; + glIsList = dllIsList; + glIsTexture = dllIsTexture; + glLightModelf = dllLightModelf; + glLightModelfv = dllLightModelfv; + glLightModeli = dllLightModeli; + glLightModeliv = dllLightModeliv; + glLightf = dllLightf; + glLightfv = dllLightfv; + glLighti = dllLighti; + glLightiv = dllLightiv; + glLineStipple = dllLineStipple; + glLineWidth = dllLineWidth; + glListBase = dllListBase; + glLoadIdentity = dllLoadIdentity; + glLoadMatrixd = dllLoadMatrixd; + glLoadMatrixf = dllLoadMatrixf; + glLoadName = dllLoadName; + glLogicOp = dllLogicOp; + glMap1d = dllMap1d; + glMap1f = dllMap1f; + glMap2d = dllMap2d; + glMap2f = dllMap2f; + glMapGrid1d = dllMapGrid1d; + glMapGrid1f = dllMapGrid1f; + glMapGrid2d = dllMapGrid2d; + glMapGrid2f = dllMapGrid2f; + glMaterialf = dllMaterialf; + glMaterialfv = dllMaterialfv; + glMateriali = dllMateriali; + glMaterialiv = dllMaterialiv; + glMatrixMode = dllMatrixMode; + glMultMatrixd = dllMultMatrixd; + glMultMatrixf = dllMultMatrixf; + glNewList = dllNewList; + glNormal3b = dllNormal3b; + glNormal3bv = dllNormal3bv; + glNormal3d = dllNormal3d; + glNormal3dv = dllNormal3dv; + glNormal3f = dllNormal3f; + glNormal3fv = dllNormal3fv; + glNormal3i = dllNormal3i; + glNormal3iv = dllNormal3iv; + glNormal3s = dllNormal3s; + glNormal3sv = dllNormal3sv; + glNormalPointer = dllNormalPointer; + glOrtho = dllOrtho; + glPassThrough = dllPassThrough; + glPixelMapfv = dllPixelMapfv; + glPixelMapuiv = dllPixelMapuiv; + glPixelMapusv = dllPixelMapusv; + glPixelStoref = dllPixelStoref; + glPixelStorei = dllPixelStorei; + glPixelTransferf = dllPixelTransferf; + glPixelTransferi = dllPixelTransferi; + glPixelZoom = dllPixelZoom; + glPointSize = dllPointSize; + glPolygonMode = dllPolygonMode; + glPolygonOffset = dllPolygonOffset; + glPolygonStipple = dllPolygonStipple; + glPopAttrib = dllPopAttrib; + glPopClientAttrib = dllPopClientAttrib; + glPopMatrix = dllPopMatrix; + glPopName = dllPopName; + glPrioritizeTextures = dllPrioritizeTextures; + glPushAttrib = dllPushAttrib; + glPushClientAttrib = dllPushClientAttrib; + glPushMatrix = dllPushMatrix; + glPushName = dllPushName; + glRasterPos2d = dllRasterPos2d; + glRasterPos2dv = dllRasterPos2dv; + glRasterPos2f = dllRasterPos2f; + glRasterPos2fv = dllRasterPos2fv; + glRasterPos2i = dllRasterPos2i; + glRasterPos2iv = dllRasterPos2iv; + glRasterPos2s = dllRasterPos2s; + glRasterPos2sv = dllRasterPos2sv; + glRasterPos3d = dllRasterPos3d; + glRasterPos3dv = dllRasterPos3dv; + glRasterPos3f = dllRasterPos3f; + glRasterPos3fv = dllRasterPos3fv; + glRasterPos3i = dllRasterPos3i; + glRasterPos3iv = dllRasterPos3iv; + glRasterPos3s = dllRasterPos3s; + glRasterPos3sv = dllRasterPos3sv; + glRasterPos4d = dllRasterPos4d; + glRasterPos4dv = dllRasterPos4dv; + glRasterPos4f = dllRasterPos4f; + glRasterPos4fv = dllRasterPos4fv; + glRasterPos4i = dllRasterPos4i; + glRasterPos4iv = dllRasterPos4iv; + glRasterPos4s = dllRasterPos4s; + glRasterPos4sv = dllRasterPos4sv; + glReadBuffer = dllReadBuffer; + glReadPixels = dllReadPixels; + glRectd = dllRectd; + glRectdv = dllRectdv; + glRectf = dllRectf; + glRectfv = dllRectfv; + glRecti = dllRecti; + glRectiv = dllRectiv; + glRects = dllRects; + glRectsv = dllRectsv; + glRenderMode = dllRenderMode; + glRotated = dllRotated; + glRotatef = dllRotatef; + glScaled = dllScaled; + glScalef = dllScalef; + glScissor = dllScissor; + glSelectBuffer = dllSelectBuffer; + glShadeModel = dllShadeModel; + glStencilFunc = dllStencilFunc; + glStencilMask = dllStencilMask; + glStencilOp = dllStencilOp; + glTexCoord1d = dllTexCoord1d; + glTexCoord1dv = dllTexCoord1dv; + glTexCoord1f = dllTexCoord1f; + glTexCoord1fv = dllTexCoord1fv; + glTexCoord1i = dllTexCoord1i; + glTexCoord1iv = dllTexCoord1iv; + glTexCoord1s = dllTexCoord1s; + glTexCoord1sv = dllTexCoord1sv; + glTexCoord2d = dllTexCoord2d; + glTexCoord2dv = dllTexCoord2dv; + glTexCoord2f = dllTexCoord2f; + glTexCoord2fv = dllTexCoord2fv; + glTexCoord2i = dllTexCoord2i; + glTexCoord2iv = dllTexCoord2iv; + glTexCoord2s = dllTexCoord2s; + glTexCoord2sv = dllTexCoord2sv; + glTexCoord3d = dllTexCoord3d; + glTexCoord3dv = dllTexCoord3dv; + glTexCoord3f = dllTexCoord3f; + glTexCoord3fv = dllTexCoord3fv; + glTexCoord3i = dllTexCoord3i; + glTexCoord3iv = dllTexCoord3iv; + glTexCoord3s = dllTexCoord3s; + glTexCoord3sv = dllTexCoord3sv; + glTexCoord4d = dllTexCoord4d; + glTexCoord4dv = dllTexCoord4dv; + glTexCoord4f = dllTexCoord4f; + glTexCoord4fv = dllTexCoord4fv; + glTexCoord4i = dllTexCoord4i; + glTexCoord4iv = dllTexCoord4iv; + glTexCoord4s = dllTexCoord4s; + glTexCoord4sv = dllTexCoord4sv; + glTexCoordPointer = dllTexCoordPointer; + glTexEnvf = dllTexEnvf; + glTexEnvfv = dllTexEnvfv; + glTexEnvi = dllTexEnvi; + glTexEnviv = dllTexEnviv; + glTexGend = dllTexGend; + glTexGendv = dllTexGendv; + glTexGenf = dllTexGenf; + glTexGenfv = dllTexGenfv; + glTexGeni = dllTexGeni; + glTexGeniv = dllTexGeniv; + glTexImage1D = dllTexImage1D; + glTexImage2D = dllTexImage2D; + glTexParameterf = dllTexParameterf; + glTexParameterfv = dllTexParameterfv; + glTexParameteri = dllTexParameteri; + glTexParameteriv = dllTexParameteriv; + glTexSubImage1D = dllTexSubImage1D; + glTexSubImage2D = dllTexSubImage2D; + glTranslated = dllTranslated; + glTranslatef = dllTranslatef; + glVertex2d = dllVertex2d; + glVertex2dv = dllVertex2dv; + glVertex2f = dllVertex2f; + glVertex2fv = dllVertex2fv; + glVertex2i = dllVertex2i; + glVertex2iv = dllVertex2iv; + glVertex2s = dllVertex2s; + glVertex2sv = dllVertex2sv; + glVertex3d = dllVertex3d; + glVertex3dv = dllVertex3dv; + glVertex3f = dllVertex3f; + glVertex3fv = dllVertex3fv; + glVertex3i = dllVertex3i; + glVertex3iv = dllVertex3iv; + glVertex3s = dllVertex3s; + glVertex3sv = dllVertex3sv; + glVertex4d = dllVertex4d; + glVertex4dv = dllVertex4dv; + glVertex4f = dllVertex4f; + glVertex4fv = dllVertex4fv; + glVertex4i = dllVertex4i; + glVertex4iv = dllVertex4iv; + glVertex4s = dllVertex4s; + glVertex4sv = dllVertex4sv; + glVertexPointer = dllVertexPointer; + glViewport = dllViewport; + + glColorTableEXT = dllColorTableEXT; + + glLockArraysEXT = dllLockArraysEXT; + glUnlockArraysEXT = dllUnlockArraysEXT; + + glActiveTextureARB = dllActiveTextureARB; + glClientActiveTextureARB = dllClientActiveTextureARB; + glMultiTexCoord2fARB = dllMultiTexCoord2fARB; + glMultiTexCoord2fvARB = dllMultiTexCoord2fvARB; + + glVertexArrayRangeNV = dllVertexArrayRangeNV; + glFlushVertexArrayRangeNV = dllFlushVertexArrayRangeNV; + wglAllocateMemoryNV = dllAllocateMemoryNV; + wglFreeMemoryNV = dllFreeMemoryNV; + + glFogCoordfEXT = dllFogCoordfEXT; + glFogCoordPointerEXT = dllFogCoordPointerEXT; + + glCompressedTexImage3DARB = dllCompressedTexImage3DARB; + glCompressedTexImage2DARB = dllCompressedTexImage2DARB; + glCompressedTexImage1DARB = dllCompressedTexImage1DARB; + glCompressedTexSubImage3DARB = dllCompressedTexSubImage3DARB; + glCompressedTexSubImage2DARB = dllCompressedTexSubImage2DARB; + glCompressedTexSubImage1DARB = dllCompressedTexSubImage1DARB; + glGetCompressedTexImageARB = dllGetCompressedTexImageARB; + + glAvailableVertexBufferEXT = dllAvailableVertexBufferEXT; + glAllocateVertexBufferEXT = dllAllocateVertexBufferEXT; + glLockVertexBufferEXT = dllLockVertexBufferEXT; + glUnlockVertexBufferEXT = dllUnlockVertexBufferEXT; + glSetVertexBufferEXT = dllSetVertexBufferEXT; + glOffsetVertexBufferEXT = dllOffsetVertexBufferEXT; + glFillVertexBufferEXT = dllFillVertexBufferEXT; + glFreeVertexBufferEXT = dllFreeVertexBufferEXT; + } +} + +static void APIENTRY outlineDrawArrays(GLenum mode, GLint first, GLsizei count) +{ + if(mode == GL_POLYGON) + mode = GL_LINE_LOOP; + + if(mode == GL_POINTS || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP || mode == GL_LINES) + dllDrawArrays( mode, first, count ); + else + { + dllBegin(GL_LINES); + if(mode == GL_TRIANGLE_STRIP) + { + U32 i; + for(i = 0; i < count - 1; i++) + { + dllArrayElement(first + i); + dllArrayElement(first + i + 1); + if(i + 2 != count) + { + dllArrayElement(first + i); + dllArrayElement(first + i + 2); + } + } + } + else if(mode == GL_TRIANGLE_FAN) + { + for(U32 i = 1; i < count; i ++) + { + dllArrayElement(first); + dllArrayElement(first + i); + if(i != count - 1) + { + dllArrayElement(first + i); + dllArrayElement(first + i + 1); + } + } + } + else if(mode == GL_TRIANGLES) + { + for(U32 i = 3; i <= count; i += 3) + { + dllArrayElement(first + i - 3); + dllArrayElement(first + i - 2); + dllArrayElement(first + i - 2); + dllArrayElement(first + i - 1); + dllArrayElement(first + i - 3); + dllArrayElement(first + i - 1); + } + } + else if(mode == GL_QUADS) + { + for(U32 i = 4; i <= count; i += 4) + { + dllArrayElement(first + i - 4); + dllArrayElement(first + i - 3); + dllArrayElement(first + i - 3); + dllArrayElement(first + i - 2); + dllArrayElement(first + i - 2); + dllArrayElement(first + i - 1); + dllArrayElement(first + i - 4); + dllArrayElement(first + i - 1); + } + } + else if(mode == GL_QUAD_STRIP) + { + if(count < 4) + return; + dllArrayElement(first + 0); + dllArrayElement(first + 1); + for(U32 i = 4; i <= count; i += 2) + { + dllArrayElement(first + i - 4); + dllArrayElement(first + i - 2); + + dllArrayElement(first + i - 3); + dllArrayElement(first + i - 1); + + dllArrayElement(first + i - 2); + dllArrayElement(first + i - 1); + } + } + dllEnd(); + } +} + +static U32 getIndex(GLenum type, const void *indices, U32 i) +{ + if(type == GL_UNSIGNED_BYTE) + return ((U8 *) indices)[i]; + else if(type == GL_UNSIGNED_SHORT) + return ((U16 *) indices)[i]; + else + return ((U32 *) indices)[i]; +} + +static BOOL WINAPI outlineSwapBuffers(HDC dc) +{ + bool ret = dllSwapBuffers(dc); + dllClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + return ret; +} + +static void APIENTRY outlineDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices) +{ + if(mode == GL_POLYGON) + mode = GL_LINE_LOOP; + + if(mode == GL_POINTS || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP || mode == GL_LINES) + dllDrawElements( mode, count, type, indices ); + else + { + dllBegin(GL_LINES); + if(mode == GL_TRIANGLE_STRIP) + { + U32 i; + for(i = 0; i < count - 1; i++) + { + dllArrayElement(getIndex(type, indices, i)); + dllArrayElement(getIndex(type, indices, i + 1)); + if(i + 2 != count) + { + dllArrayElement(getIndex(type, indices, i)); + dllArrayElement(getIndex(type, indices, i + 2)); + } + } + } + else if(mode == GL_TRIANGLE_FAN) + { + for(U32 i = 1; i < count; i ++) + { + dllArrayElement(getIndex(type, indices, 0)); + dllArrayElement(getIndex(type, indices, i)); + if(i != count - 1) + { + dllArrayElement(getIndex(type, indices, i)); + dllArrayElement(getIndex(type, indices, i + 1)); + } + } + } + else if(mode == GL_TRIANGLES) + { + for(U32 i = 3; i <= count; i += 3) + { + dllArrayElement(getIndex(type, indices, i - 3)); + dllArrayElement(getIndex(type, indices, i - 2)); + dllArrayElement(getIndex(type, indices, i - 2)); + dllArrayElement(getIndex(type, indices, i - 1)); + dllArrayElement(getIndex(type, indices, i - 3)); + dllArrayElement(getIndex(type, indices, i - 1)); + } + } + else if(mode == GL_QUADS) + { + for(U32 i = 4; i <= count; i += 4) + { + dllArrayElement(getIndex(type, indices, i - 4)); + dllArrayElement(getIndex(type, indices, i - 3)); + dllArrayElement(getIndex(type, indices, i - 3)); + dllArrayElement(getIndex(type, indices, i - 2)); + dllArrayElement(getIndex(type, indices, i - 2)); + dllArrayElement(getIndex(type, indices, i - 1)); + dllArrayElement(getIndex(type, indices, i - 4)); + dllArrayElement(getIndex(type, indices, i - 1)); + } + } + else if(mode == GL_QUAD_STRIP) + { + if(count < 4) + return; + dllArrayElement(getIndex(type, indices, 0)); + dllArrayElement(getIndex(type, indices, 1)); + for(U32 i = 4; i <= count; i += 2) + { + dllArrayElement(getIndex(type, indices, i - 4)); + dllArrayElement(getIndex(type, indices, i - 2)); + + dllArrayElement(getIndex(type, indices, i - 3)); + dllArrayElement(getIndex(type, indices, i - 1)); + + dllArrayElement(getIndex(type, indices, i - 2)); + dllArrayElement(getIndex(type, indices, i - 1)); + } + } + dllEnd(); + } +} + +ConsoleFunction(GLEnableOutline, void, 2, 2, "GLEnableOutline(bool);") +{ + argc; + bool enable = dAtob(argv[1]); + if(outlineEnabled == enable) + return; + + if(enable && (loggingEnabled || perfEnabled)) + return; + + outlineEnabled = enable; + + if ( enable ) + { + glDrawElements = outlineDrawElements; + glDrawArrays = outlineDrawArrays; + qwglSwapBuffers = outlineSwapBuffers; + } + else + { + glDrawElements = dllDrawElements; + glDrawArrays = dllDrawArrays; + qwglSwapBuffers = dllSwapBuffers; + + } +} + +static void APIENTRY perfDrawArrays(GLenum mode, GLint first, GLsizei count) +{ + gGLState.primCount[gGLState.primMode]++; + U32 tc = 0; + + if(mode == GL_TRIANGLES) + tc = count / 3; + else if(mode == GL_TRIANGLE_FAN || mode == GL_TRIANGLE_STRIP) + tc = count - 2; + + gGLState.triCount[gGLState.primMode] += tc; + dllDrawArrays( mode, first, count ); +} + +static void APIENTRY perfDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices) +{ + gGLState.primCount[gGLState.primMode]++; + U32 tc = 0; + + if(mode == GL_TRIANGLES) + tc = count / 3; + else if(mode == GL_TRIANGLE_FAN || mode == GL_TRIANGLE_STRIP) + tc = count - 2; + + gGLState.triCount[gGLState.primMode] += tc; + dllDrawElements( mode, count, type, indices ); +} + +ConsoleFunction(GLEnableMetrics, void, 2, 2, "GLEnableMetrics(bool);") +{ + argc; + static bool varsAdded = false; + + if(!varsAdded) + { + Con::addVariable("OpenGL::triCount0", TypeS32, &gGLState.triCount[0]); + Con::addVariable("OpenGL::triCount1", TypeS32, &gGLState.triCount[1]); + Con::addVariable("OpenGL::triCount2", TypeS32, &gGLState.triCount[2]); + Con::addVariable("OpenGL::triCount3", TypeS32, &gGLState.triCount[3]); + + Con::addVariable("OpenGL::primCount0", TypeS32, &gGLState.primCount[0]); + Con::addVariable("OpenGL::primCount1", TypeS32, &gGLState.primCount[1]); + Con::addVariable("OpenGL::primCount2", TypeS32, &gGLState.primCount[2]); + Con::addVariable("OpenGL::primCount3", TypeS32, &gGLState.primCount[3]); + varsAdded = true; + } + + bool enable = dAtob(argv[1]); + if(perfEnabled == enable) + return; + + if(enable && (loggingEnabled || outlineEnabled)) + return; + + perfEnabled = enable; + + if ( enable ) + { + glDrawElements = perfDrawElements; + glDrawArrays = perfDrawArrays; + } + else + { + glDrawElements = dllDrawElements; + glDrawArrays = dllDrawArrays; + } +} + +#endif diff --git a/platformWin32/winInput.cc b/platformWin32/winInput.cc new file mode 100644 index 0000000..8e311a2 --- /dev/null +++ b/platformWin32/winInput.cc @@ -0,0 +1,808 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformWIN32/platformWin32.h" +#include "platform/platformInput.h" +#include "platform/platformVideo.h" +#include "platformWIN32/winDirectInput.h" +#include "platform/event.h" +#include "console/console.h" + +#ifdef LOG_INPUT +#include +#include +#endif + +// Static class variables: +InputManager* Input::smManager; +bool Input::smActive; + +#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( "input.log", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + log( "Input log opened at %s\n", asctime( newTime ) ); +#endif + + smActive = false; + + 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." ); + } + + fillAsciiTable(); + Con::printf( "" ); +} + +//------------------------------------------------------------------------------ +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 ); + 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() +{ +#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() +{ + DInputDevice::resetModifierKeys(); + 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->isKeyboardEnabled() ) + dInputManager->activateKeyboard(); + + if ( Video::isFullScreen() ) + { + // DirectInput Mouse Hook-Up: + if ( dInputManager->isMouseEnabled() ) + dInputManager->activateMouse(); + } + else + dInputManager->deactivateMouse(); + + if ( dInputManager->isJoystickEnabled() ) + 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 ) + { + dInputManager->deactivateKeyboard(); + dInputManager->deactivateMouse(); + dInputManager->deactivateJoystick(); + } + + smActive = false; + Con::printf( "DirectInput deactivated." ); + } +} + +//------------------------------------------------------------------------------ +void Input::reactivate() +{ + // This is soo hacky... + SetForegroundWindow( winState.appWindow ); + PostMessage( winState.appWindow, WM_ACTIVATE, WA_ACTIVE, NULL ); +} + +//------------------------------------------------------------------------------ +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]; +} + +//----------------------------------------------------------------------------- +// 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.cc b/platformWin32/winMath.cc new file mode 100644 index 0000000..1f913ce --- /dev/null +++ b/platformWin32/winMath.cc @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.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(); + + + +//-------------------------------------- +static void cMathInit(SimObject*, S32 argc, const char** argv) +{ + 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) +{ + Con::addCommand( "MathInit", cMathInit, "MathInit(detect|C|FPU|MMX|3DNOW|SSE|...)", 1, 10); + + 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.cc b/platformWin32/winMath_ASM.cc new file mode 100644 index 0000000..4789efe --- /dev/null +++ b/platformWin32/winMath_ASM.cc @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Math/mMath.h" + +#if !defined(__MWERKS__) && defined(_MSC_VER) +#define asm _asm +#endif + +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; +} + + + + + + +//------------------------------------------------------------------------------ +void mInstallLibrary_ASM() +{ + m_mulDivS32 = m_mulDivS32_ASM; + m_mulDivU32 = m_mulDivU32_ASM; +} + + diff --git a/platformWin32/winMemory.cc b/platformWin32/winMemory.cc new file mode 100644 index 0000000..531ccaf --- /dev/null +++ b/platformWin32/winMemory.cc @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" + +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); +} + +#ifdef new +#undef new +#endif + +//-------------------------------------- +void* FN_CDECL operator new(dsize_t, void* ptr) +{ + return (ptr); +} + +void* dRealMalloc(dsize_t s) +{ + return malloc(s); +} + +void dRealFree(void* p) +{ + free(p); +} + diff --git a/platformWin32/winMutex.cc b/platformWin32/winMutex.cc new file mode 100644 index 0000000..ffcaa1e --- /dev/null +++ b/platformWin32/winMutex.cc @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" +#include "Platform/platformMutex.h" + +void * Mutex::createMutex() +{ + CRITICAL_SECTION * mutex = new CRITICAL_SECTION; + InitializeCriticalSection(mutex); + return((void*)mutex); +} + +void Mutex::destroyMutex(void * mutex) +{ + AssertFatal(mutex, "Mutex::destroyMutex: invalid mutex"); + DeleteCriticalSection((CRITICAL_SECTION*)mutex); + delete mutex; +} + +void Mutex::lockMutex(void * mutex) +{ + AssertFatal(mutex, "Mutex::lockMutex: invalid mutex"); + EnterCriticalSection((CRITICAL_SECTION*)mutex); +} + +void Mutex::unlockMutex(void * mutex) +{ + AssertFatal(mutex, "Mutex::unlockMutex: invalid mutex"); + LeaveCriticalSection((CRITICAL_SECTION*)mutex); +} diff --git a/platformWin32/winNet.cc b/platformWin32/winNet.cc new file mode 100644 index 0000000..dbfabf6 --- /dev/null +++ b/platformWin32/winNet.cc @@ -0,0 +1,801 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" +#include "Platform/platform.h" +#include "Platform/event.h" +#include +#include +#include "console/console.h" +#include "Platform/gameInterface.h" +#include "Core/fileStream.h" + +struct NameLookup +{ + U8 hostEntStruct[MAXGETHOSTSTRUCT]; + HANDLE lookupHandle; + SOCKET socket; + U16 port; + NameLookup *nextLookup; +}; + +static NameLookup *lookupList = NULL; + +// process receives data and posts it to the game. + +static Net::Error getLastError(); +static S32 defaultPort = 28000; +static S32 netPort = 0; +static SOCKET ipxSocket = INVALID_SOCKET; +static SOCKET udpSocket = INVALID_SOCKET; + +enum { + MaxConnections = 1024, +}; + +HWND winsockWindow = NULL; + +static LRESULT PASCAL WinsockProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + U32 error; + U32 bufLen; + U32 event; + SOCKET socket; + Net::Error err; + S32 bytesRead; + + static ConnectedNotifyEvent notifyEvent; + static ConnectedReceiveEvent receiveEvent; + static ConnectedAcceptEvent acceptEvent; + + switch(message) + { + case WM_USER: + error = WSAGETSELECTERROR(lParam); + event = WSAGETSELECTEVENT(lParam); + socket = wParam; + switch(event) + { + case FD_READ: + err = Net::recv(socket, receiveEvent.data, MaxPacketDataSize, &bytesRead); + if(err == Net::NoError && bytesRead != 0) + { + receiveEvent.tag = socket; + receiveEvent.size = ConnectedReceiveEventHeaderSize + bytesRead; + Game->postEvent(receiveEvent); + } + break; + case FD_CONNECT: + notifyEvent.tag = socket; + if(error) + notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; + else + notifyEvent.state = ConnectedNotifyEvent::Connected; + Game->postEvent(notifyEvent); + break; + case FD_CLOSE: + // see first if there is anything to read: + for(;;) + { + err = Net::recv(socket, receiveEvent.data, MaxPacketDataSize, &bytesRead); + if(err != Net::NoError || bytesRead == 0) + break; + + receiveEvent.tag = socket; + receiveEvent.size = ConnectedReceiveEventHeaderSize + bytesRead; + Game->postEvent(receiveEvent); + } + notifyEvent.tag = socket; + notifyEvent.state = ConnectedNotifyEvent::Disconnected; + Game->postEvent(notifyEvent); + break; + case FD_ACCEPT: + acceptEvent.portTag = socket; + acceptEvent.connectionTag = Net::accept(socket, &acceptEvent.address); + if(acceptEvent.connectionTag != InvalidSocket) + { + Net::setBlocking(acceptEvent.connectionTag, false); + WSAAsyncSelect(acceptEvent.connectionTag, winsockWindow, WM_USER, FD_READ | FD_CONNECT | FD_CLOSE); + Game->postEvent(acceptEvent); + } + break; + } + break; + case WM_USER + 1: + error = WSAGETASYNCERROR(lParam); + bufLen = WSAGETASYNCBUFLEN(lParam); + HANDLE handle; + handle = HANDLE(wParam); + NameLookup **walk; + for(walk = &lookupList; *walk; walk = &((*walk)->nextLookup)) + { + if((*walk)->lookupHandle == handle) + { + NameLookup *temp = *walk; + struct hostent *hp = (struct hostent *) temp->hostEntStruct; + + SOCKADDR_IN ipAddr; + memcpy(&ipAddr.sin_addr.s_addr, hp->h_addr, sizeof(IN_ADDR)); + ipAddr.sin_port = temp->port; + ipAddr.sin_family = AF_INET; + + notifyEvent.tag = temp->socket; + + bool wserr = ::connect(temp->socket, (PSOCKADDR) &ipAddr, sizeof(ipAddr)); // always errors out + if(wserr && WSAGetLastError() != WSAEWOULDBLOCK) + { + notifyEvent.state = ConnectedNotifyEvent::DNSFailed; + ::closesocket(temp->socket); + } + else + { + WSAAsyncSelect(temp->socket, winsockWindow, WM_USER, FD_READ | FD_CONNECT | FD_CLOSE); + notifyEvent.state = ConnectedNotifyEvent::DNSResolved; + } + Game->postEvent(notifyEvent); + + *walk = temp->nextLookup; + delete temp; + break; + } + } + break; + default: + return DefWindowProc( hWnd, message, wParam, lParam ); + } + return 0; +} + + +static void InitNetWindow() +{ + WNDCLASS wc; + dMemset(&wc, 0, sizeof(wc)); + + wc.style = 0; + wc.lpfnWndProc = WinsockProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = winState.appInstance; + wc.hIcon = 0; + wc.hCursor = 0; + wc.hbrBackground = 0; + wc.lpszMenuName = 0; + wc.lpszClassName = "WinSockClass"; + RegisterClass( &wc ); + winsockWindow = CreateWindowEx( + 0, + "WinSockClass", + "", + 0, + 0, 0, 0, 0, + NULL, NULL, + winState.appInstance, + NULL); +} + +bool Net::init() +{ + WSADATA stWSAData; + InitNetWindow(); + return !WSAStartup(0x0101, &stWSAData); +} + +void Net::shutdown() +{ + while(lookupList) + { + NameLookup *temp = lookupList; + lookupList = temp->nextLookup; + WSACancelAsyncRequest ( temp->lookupHandle ); + delete temp; + } + DestroyWindow(winsockWindow); + closePort(); + WSACleanup(); +} + +static void netToIPSocketAddress(const NetAddress *address, SOCKADDR_IN *sockAddr) +{ + dMemset(sockAddr, 0, sizeof(SOCKADDR_IN)); + sockAddr->sin_family = AF_INET; + sockAddr->sin_port = htons(address->port); + sockAddr->sin_addr.s_net = address->netNum[0]; + sockAddr->sin_addr.s_host = address->netNum[1]; + sockAddr->sin_addr.s_lh = address->netNum[2]; + sockAddr->sin_addr.s_impno = address->netNum[3]; +} + +static void IPSocketToNetAddress(const SOCKADDR_IN *sockAddr, NetAddress *address) +{ + address->type = NetAddress::IPAddress; + address->port = htons(sockAddr->sin_port); + 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; +} + +static void netToIPXSocketAddress(const NetAddress *address, SOCKADDR_IPX *sockAddr) +{ + dMemset(sockAddr, 0, sizeof(SOCKADDR_IPX)); + sockAddr->sa_family = AF_INET; + sockAddr->sa_socket = htons(address->port); + sockAddr->sa_netnum[0] = address->netNum[0]; + sockAddr->sa_netnum[1] = address->netNum[1]; + sockAddr->sa_netnum[2] = address->netNum[2]; + sockAddr->sa_netnum[3] = address->netNum[3]; + sockAddr->sa_nodenum[0] = address->nodeNum[0]; + sockAddr->sa_nodenum[1] = address->nodeNum[1]; + sockAddr->sa_nodenum[2] = address->nodeNum[2]; + sockAddr->sa_nodenum[3] = address->nodeNum[3]; + sockAddr->sa_nodenum[4] = address->nodeNum[4]; + sockAddr->sa_nodenum[5] = address->nodeNum[5]; +} + +static void IPXSocketToNetAddress(const SOCKADDR_IPX *sockAddr, NetAddress *address) +{ + address->type = NetAddress::IPXAddress; + address->port = htons(sockAddr->sa_socket); + address->netNum[0] = sockAddr->sa_netnum[0] ; + address->netNum[1] = sockAddr->sa_netnum[1] ; + address->netNum[2] = sockAddr->sa_netnum[2] ; + address->netNum[3] = sockAddr->sa_netnum[3] ; + address->nodeNum[0] = sockAddr->sa_nodenum[0]; + address->nodeNum[1] = sockAddr->sa_nodenum[1]; + address->nodeNum[2] = sockAddr->sa_nodenum[2]; + address->nodeNum[3] = sockAddr->sa_nodenum[3]; + address->nodeNum[4] = sockAddr->sa_nodenum[4]; + address->nodeNum[5] = sockAddr->sa_nodenum[5]; +} + +NetSocket Net::openListenPort(U16 port) +{ + if(Game->isJournalReading()) + { + U32 ret; + Game->journalRead(&ret); + return NetSocket(ret); + } + NetSocket sock = openSocket(); + bind(sock, port); + listen(sock, 4); + setBlocking(sock, false); + if(WSAAsyncSelect ( sock, winsockWindow, WM_USER, FD_ACCEPT )) + Con::printf("Error: %d", WSAGetLastError()); + 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; + + 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, (PSOCKADDR) &ipAddr, sizeof(ipAddr) ) ) + { + if(WSAGetLastError() != WSAEWOULDBLOCK) + { + Con::printf("Last error: %d", WSAGetLastError()); + ::closesocket(sock); + sock = InvalidSocket; + } + } + if(sock != InvalidSocket) + WSAAsyncSelect(sock, winsockWindow, WM_USER, FD_READ | FD_CONNECT | FD_CLOSE); + } + else + { + NameLookup *lookup = new NameLookup; + lookup->socket = sock; + lookup->port = port; + lookup->lookupHandle = WSAAsyncGetHostByName (winsockWindow, WM_USER + 1, remoteAddr, (char *) lookup->hostEntStruct, MAXGETHOSTSTRUCT ); + if(!lookup->lookupHandle) + { + delete lookup; + ::closesocket(sock); + sock = InvalidSocket; + } + else + { + lookup->nextLookup = lookupList; + lookupList = lookup; + } + } + if(Game->isJournalWriting()) + Game->journalWrite(U32(sock)); + return sock; +} + +void Net::closeConnectTo(NetSocket sock) +{ + if(Game->isJournalReading()) + return; + + for(NameLookup **walk = &lookupList; *walk; walk = &((*walk)->nextLookup) ) + { + NameLookup *lookup = *walk; + if(lookup->socket == sock) + { + WSACancelAsyncRequest ( lookup->lookupHandle ); + closesocket(lookup->socket); + *walk = lookup->nextLookup; + delete lookup; + return; + } + } + closesocket(sock); +} + +Net::Error Net::sendTo(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 != INVALID_SOCKET) + closesocket(udpSocket); + if(ipxSocket != INVALID_SOCKET) + closesocket(ipxSocket); + + udpSocket = socket(AF_INET, SOCK_DGRAM, 0); + ipxSocket = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX); + + if(udpSocket != INVALID_SOCKET) + { + 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 + { + closesocket(udpSocket); + udpSocket = INVALID_SOCKET; + Con::printf("Unable to initialize UDP - error %d", error); + } + } + if(ipxSocket != INVALID_SOCKET) + { + Net::Error error = NoError; + SOCKADDR_IPX ipxAddress; + memset((char *)&ipxAddress, 0, sizeof(ipxAddress)); + ipxAddress.sa_family = AF_IPX; + ipxAddress.sa_socket = htons(port); + S32 err = ::bind(ipxSocket, (PSOCKADDR) &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 + { + closesocket(ipxSocket); + ipxSocket = INVALID_SOCKET; + Con::printf("Unable to initialize IPX - error %d", error); + } + } + netPort = port; + return ipxSocket != INVALID_SOCKET || udpSocket != INVALID_SOCKET; +} + +void Net::closePort() +{ + if(ipxSocket != INVALID_SOCKET) + closesocket(ipxSocket); + if(udpSocket != INVALID_SOCKET) + closesocket(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, + (PSOCKADDR) &ipxAddr, sizeof(SOCKADDR_IPX)) == 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() +{ + SOCKADDR sa; + + PacketReceiveEvent receiveEvent; + for(;;) + { + S32 addrLen = sizeof(sa); + S32 bytesRead = SOCKET_ERROR; + if(udpSocket != INVALID_SOCKET) + bytesRead = recvfrom(udpSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); + if(bytesRead == SOCKET_ERROR && ipxSocket != INVALID_SOCKET) + { + addrLen = sizeof(sa); + bytesRead = recvfrom(ipxSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); + } + + if(bytesRead == SOCKET_ERROR) + 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); + } +} + +NetSocket Net::openSocket() +{ + SOCKET retSocket; + retSocket = socket(AF_INET, SOCK_STREAM, 0); + + if(retSocket == INVALID_SOCKET) + 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, (PSOCKADDR) &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; + S32 addrLen = sizeof(socketAddress); + + SOCKET retVal = ::accept(acceptSocket, (PSOCKADDR) &socketAddress, &addrLen); + if(retVal != INVALID_SOCKET) + { + 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( "Host::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, (PSOCKADDR) &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) +{ + DWORD notblock = !blockingIO; + S32 error = ioctlsocket(socket, FIONBIO, ¬block); + if(!error) + return NoError; + return getLastError(); +} + +Net::Error Net::send(NetSocket socket, const U8 *buffer, S32 bufferSize) +{ + S32 error = ::send(socket, (const char*)buffer, bufferSize, 0); + if(!error) + 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 == SOCKET_ERROR) + 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 + { + ipAddr.sin_addr.s_addr = inet_addr(remoteAddr); + if(ipAddr.sin_addr.s_addr == INADDR_NONE) + { + if((hp = gethostbyname(remoteAddr)) == NULL) + 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:%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)); + } + else + { + 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() +{ + S32 err = WSAGetLastError(); + switch(err) + { + case WSAEWOULDBLOCK: + return Net::WouldBlock; + default: + return Net::UnknownError; + } +} diff --git a/platformWin32/winOGLVideo.cc b/platformWin32/winOGLVideo.cc new file mode 100644 index 0000000..d1cd928 --- /dev/null +++ b/platformWin32/winOGLVideo.cc @@ -0,0 +1,1219 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformWIN32/platformGL.h" +#include "platformWIN32/platformWin32.h" +#include "platform/platformAudio.h" +#include "platformWIN32/winOGLVideo.h" +#include "platformWIN32/winD3DVideo.h" +#include "console/console.h" +#include "math/mPoint.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "core/fileStream.h" + +//------------------------------------------------------------------------------ + +bool OpenGLDevice::smCanSwitchBitDepth = true; + +struct CardProfile +{ + const char *vendor; // manufacturer + const char *renderer; // driver name + + bool safeMode; // destroy rendering context for resolution change + bool lockArray; // allow compiled vertex arrays + bool subImage; // allow glTexSubImage* + bool fogTexture; // require bound texture for combine extension + bool noEnvColor; // no texture environment color + bool clipHigh; // clip high resolutions + bool deleteContext; // delete rendering context + bool texCompress; // allow texture compression + bool interiorLock; // lock arrays for Interior render + bool skipFirstFog; // skip first two-pass fogging (dumb 3Dfx hack) + bool only16; // inhibit 32-bit resolutions + bool noArraysAlpha; // don't use glDrawArrays with a GL_ALPHA texture + + const char *proFile; // explicit profile of graphic settings +}; + +struct OSCardProfile +{ + const char *vendor; // manufacturer + const char *renderer; // driver name + + bool allowOpenGL; // allow OpenGL driver + bool allowD3D; // allow D3D driver + bool preferOpenGL; // prefer OpenGL driver +}; + +static Vector sCardProfiles(__FILE__, __LINE__); +static Vector sOSCardProfiles(__FILE__, __LINE__); + +struct ProcessorProfile +{ + U16 clock; // clock range max + U16 adjust; // CPU adjust +}; + +static U8 sNumProcessors = 4; +static ProcessorProfile sProcessorProfiles[] = +{ + { 400, 0 }, + { 600, 5 }, + { 800, 10 }, + { 1000, 15 }, +}; + +struct SettingProfile +{ + U16 performance; // metric range max + const char *settings; // default file +}; + +static U8 sNumSettings = 3; +static SettingProfile sSettingProfiles[] = +{ + { 33, "LowProfile.cs" }, + { 66, "MediumProfile.cs" }, + { 100, "HighProfile.cs" }, +}; + +//------------------------------------------------------------------------------ + +static void cAddCardProfile(SimObject *, S32, const char **argv) +{ + CardProfile profile; + + profile.vendor = dStrdup(argv[1]); + profile.renderer = dStrdup(argv[2]); + + profile.safeMode = dAtob(argv[3]); + profile.lockArray = dAtob(argv[4]); + profile.subImage = dAtob(argv[5]); + profile.fogTexture = dAtob(argv[6]); + profile.noEnvColor = dAtob(argv[7]); + profile.clipHigh = dAtob(argv[8]); + profile.deleteContext = dAtob(argv[9]); + profile.texCompress = dAtob(argv[10]); + profile.interiorLock = dAtob(argv[11]); + profile.skipFirstFog = dAtob(argv[12]); + profile.only16 = dAtob(argv[13]); + profile.noArraysAlpha = dAtob(argv[14]); + + if (strcmp(argv[15],"")) + profile.proFile = dStrdup(argv[15]); + else + profile.proFile = NULL; + + sCardProfiles.push_back(profile); +} + +static void cAddOSCardProfile(SimObject *, S32, const char **argv) +{ + OSCardProfile profile; + + profile.vendor = dStrdup(argv[1]); + profile.renderer = dStrdup(argv[2]); + + profile.allowOpenGL = dAtob(argv[3]); + profile.allowD3D = dAtob(argv[4]); + profile.preferOpenGL = dAtob(argv[5]); + + sOSCardProfiles.push_back(profile); +} + +static void clearCardProfiles() +{ + while (sCardProfiles.size()) + { + dFree((char *) sCardProfiles.last().vendor); + dFree((char *) sCardProfiles.last().renderer); + + dFree((char *) sCardProfiles.last().proFile); + + sCardProfiles.decrement(); + } +} + +static void clearOSCardProfiles() +{ + while (sOSCardProfiles.size()) + { + dFree((char *) sOSCardProfiles.last().vendor); + dFree((char *) sOSCardProfiles.last().renderer); + + sOSCardProfiles.decrement(); + } +} + +static void execScript(const char *scriptFile) +{ + // execute the script + FileStream str; + + if (!str.open(scriptFile, FileStream::Read)) + return; + + U32 size = str.getStreamSize(); + char *script = new char[size + 1]; + + str.read(size, script); + str.close(); + + script[size] = 0; + Con::executef(2, "eval", script); + delete[] script; +} + +static void profileSystem(const char *vendor, const char *renderer) +{ + Con::addCommand("addCardProfile", cAddCardProfile, "addCardProfile(vendor,renderer,safeMode,lockArray,subImage,fogTexture,noEnvColor,clipHigh,deleteContext,texCompress,interiorLock,skipFirstFog,only16,noArraysAlpha,proFile);", 16, 16); + Con::addCommand("addOSCardProfile", cAddOSCardProfile, "addOSCardProfile(vendor,renderer,allowOpenGL,allowD3D,preferOpenGL);", 6, 6); + + //Con::executef(2, "exec", "scripts/CardProfiles.cs"); + execScript("CardProfiles.cs"); + + const char *arch; + OSVERSIONINFO OSVersionInfo; + const char *os = NULL; + char osProfiles[64]; + + if (dStrstr(Platform::SystemInfo.processor.name,"AMD") != NULL) + arch = "AMD"; + else + arch = "Intel"; + + + dMemset( &OSVersionInfo, 0, sizeof( OSVERSIONINFO ) ); + OSVersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + if ( GetVersionEx( &OSVersionInfo ) ) + { + if (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) + if ( OSVersionInfo.dwMinorVersion == 0 ) + { + if (dStrcmp(arch,"Intel") == 0) + os = "W95"; + } + else if ( OSVersionInfo.dwMinorVersion == 10 ) + if ( OSVersionInfo.szCSDVersion[1] != 'A' ) + os = "W98"; + else + os = "W98SE"; + else + os = "WME"; + else + if ( OSVersionInfo.dwMajorVersion >= 5 ) + os = "W2K"; + + if ( os != NULL ) + { + dSprintf(osProfiles,64,"%s%sCardProfiles.cs",arch,os); + //Con::executef(2, "exec", osProfiles); + execScript(osProfiles); + } + } + + const char *proFile = NULL; + U32 i; + + for (i = 0; i < sCardProfiles.size(); ++i) + if (dStrstr(vendor, sCardProfiles[i].vendor) && + (!dStrcmp(sCardProfiles[i].renderer, "*") || + dStrstr(renderer, sCardProfiles[i].renderer))) + { + Con::setBoolVariable("$pref::Video::safeModeOn", sCardProfiles[i].safeMode); + Con::setBoolVariable("$pref::OpenGL::disableEXTCompiledVertexArray", !sCardProfiles[i].lockArray); + Con::setBoolVariable("$pref::OpenGL::disableSubImage", !sCardProfiles[i].subImage); + Con::setBoolVariable("$pref::TS::fogTexture", sCardProfiles[i].fogTexture); + Con::setBoolVariable("$pref::OpenGL::noEnvColor", sCardProfiles[i].noEnvColor); + Con::setBoolVariable("$pref::Video::clipHigh", sCardProfiles[i].clipHigh); + if (!sCardProfiles[i].deleteContext) + { + OSVERSIONINFO OSVersionInfo; + + // HACK: The Voodoo3/5 on W2K crash when deleting a rendering context + // So we're not deleting it. + // Oh, and the Voodoo3 returns a Banshee renderer string under W2K + dMemset( &OSVersionInfo, 0, sizeof( OSVERSIONINFO ) ); + OSVersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + if ( GetVersionEx( &OSVersionInfo ) && + OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && + OSVersionInfo.dwMajorVersion == 5) + Con::setBoolVariable("$pref::Video::deleteContext", false); + else + Con::setBoolVariable("$pref::Video::deleteContext", true); + } + else + Con::setBoolVariable("$pref::Video::deleteContext", true); + Con::setBoolVariable("$pref::OpenGL::disableARBTextureCompression", !sCardProfiles[i].texCompress); + Con::setBoolVariable("$pref::Interior::lockArrays", sCardProfiles[i].interiorLock); + Con::setBoolVariable("$pref::TS::skipFirstFog", sCardProfiles[i].skipFirstFog); + Con::setBoolVariable("$pref::Video::only16", sCardProfiles[i].only16); + Con::setBoolVariable("$pref::OpenGL::noDrawArraysAlpha", sCardProfiles[i].noArraysAlpha); + + proFile = sCardProfiles[i].proFile; + + break; + } + + // defaults + U16 glProfile; + + if (!proFile) + { + // no driver GL profile -- make one via weighting GL extensions + glProfile = 25; + + glProfile += gGLState.suppARBMultitexture * 25; + glProfile += gGLState.suppLockedArrays * 15; + glProfile += gGLState.suppVertexArrayRange * 10; + glProfile += gGLState.suppTextureEnvCombine * 5; + glProfile += gGLState.suppPackedPixels * 5; + glProfile += gGLState.suppTextureCompression * 5; + glProfile += gGLState.suppS3TC * 5; + glProfile += gGLState.suppFXT1 * 5; + + Con::setBoolVariable("$pref::Video::safeModeOn", true); + Con::setBoolVariable("$pref::OpenGL::disableEXTCompiledVertexArray", false); + Con::setBoolVariable("$pref::OpenGL::disableSubImage", false); + Con::setBoolVariable("$pref::TS::fogTexture", false); + Con::setBoolVariable("$pref::OpenGL::noEnvColor", false); + Con::setBoolVariable("$pref::Video::clipHigh", false); + Con::setBoolVariable("$pref::Video::deleteContext", true); + Con::setBoolVariable("$pref::OpenGL::disableARBTextureCompression", false); + Con::setBoolVariable("$pref::Interior::lockArrays", true); + Con::setBoolVariable("$pref::TS::skipFirstFog", false); + Con::setBoolVariable("$pref::Video::only16", false); + Con::setBoolVariable("$pref::OpenGL::noDrawArraysAlpha", false); + } + + Con::setVariable("$pref::Video::profiledVendor", vendor); + Con::setVariable("$pref::Video::profiledRenderer", renderer); + + if (!Con::getBoolVariable("$DisableSystemProfiling") && + ( dStrcmp(vendor, Con::getVariable("$pref::Video::defaultsVendor")) || + dStrcmp(renderer, Con::getVariable("$pref::Video::defaultsRenderer")) )) + { + if (proFile) + { + char settings[64]; + + dSprintf(settings,64,"%s.cs",proFile); + //Con::executef(2, "exec", settings); + execScript(settings); + } + else + { + U16 adjust; + + // match clock with profile + for (i = 0; i < sNumProcessors; ++i) + { + adjust = sProcessorProfiles[i].adjust; + + if (Platform::SystemInfo.processor.mhz < sProcessorProfiles[i].clock) break; + } + + const char *settings; + + // match performance metric with profile + for (i = 0; i < sNumSettings; ++i) + { + settings = sSettingProfiles[i].settings; + + if (glProfile+adjust <= sSettingProfiles[i].performance) break; + } + + //Con::executef(2, "exec", settings); + execScript(settings); + } + + bool match = false; + + for (i = 0; i < sOSCardProfiles.size(); ++i) + if (dStrstr(vendor, sOSCardProfiles[i].vendor) && + (!dStrcmp(sOSCardProfiles[i].renderer, "*") || + dStrstr(renderer, sOSCardProfiles[i].renderer))) + { + Con::setBoolVariable("$pref::Video::allowOpenGL", sOSCardProfiles[i].allowOpenGL); + Con::setBoolVariable("$pref::Video::allowD3D", sOSCardProfiles[i].allowD3D); + Con::setBoolVariable("$pref::Video::preferOpenGL", sOSCardProfiles[i].preferOpenGL); + + match = true; + + break; + } + + if (!match) + { + Con::setBoolVariable("$pref::Video::allowOpenGL", true); + Con::setBoolVariable("$pref::Video::allowD3D", true); + Con::setBoolVariable("$pref::Video::preferOpenGL", true); + } + + Con::setVariable("$pref::Video::defaultsVendor", vendor); + Con::setVariable("$pref::Video::defaultsRenderer", renderer); + } + + // write out prefs + gEvalState.globalVars.exportVariables("$pref::*", "prefs/ClientPrefs.cs", false); + + clearCardProfiles(); + clearOSCardProfiles(); +} + +//------------------------------------------------------------------------------ +OpenGLDevice::OpenGLDevice() +{ + initDevice(); +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::initDevice() +{ + // Set the device name: + mDeviceName = "OpenGL"; + + // Set some initial conditions: + mResolutionList.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 >= 640 && devMode.dmPelsHeight >= 480 + && ( devMode.dmBitsPerPel == 16 || devMode.dmBitsPerPel == 32 ) && + ( smCanSwitchBitDepth || devMode.dmBitsPerPel == winState.desktopBitsPixel ) ) + { + // Only add this resolution if it is not already in the list: + bool alreadyInList = false; + for ( U32 i = 0; i < mResolutionList.size(); i++ ) + { + if ( devMode.dmPelsWidth == mResolutionList[i].w + && devMode.dmPelsHeight == mResolutionList[i].h + && devMode.dmBitsPerPel == mResolutionList[i].bpp ) + { + alreadyInList = true; + break; + } + } + + if ( !alreadyInList ) + { + Resolution newRes( devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel ); + mResolutionList.push_back( newRes ); + } + } + } +} + + +//------------------------------------------------------------------------------ +bool OpenGLDevice::activate( U32 width, U32 height, U32 bpp, bool fullScreen ) +{ + Con::printf( "Activating the OpenGL display device..." ); + + bool needResurrect = false; + + // If the rendering context exists, delete it: + if ( winState.hGLRC ) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + + Con::printf( "Making the rendering context not current..." ); + if ( !qwglMakeCurrent( NULL, NULL ) ) + { + AssertFatal( false, "OpenGLDevice::activate\nqwglMakeCurrent( NULL, NULL ) failed!" ); + return false; + } + + Con::printf( "Deleting the rendering context ..." ); + if ( !qwglDeleteContext( winState.hGLRC ) ) + { + AssertFatal( false, "OpenGLDevice::activate\nqwglDeleteContext failed!" ); + return false; + } + winState.hGLRC = NULL; + } + + // If the window already exists, kill it so we can start fresh: + if ( winState.appWindow ) + { + if ( winState.appDC ) + { + Con::printf( "Releasing the device context..." ); + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + } + + Con::printf( "Destroying the window..." ); + DestroyWindow( winState.appWindow ); + winState.appWindow = NULL; + } + + // If OpenGL library already loaded, shut it down and reload it: + if ( winState.hinstOpenGL ) + QGL_Shutdown(); + + QGL_Init( "opengl32", "glu32" ); + + static bool onceAlready = false; + bool profiled = false; + + if ( !mFullScreenOnly && fullScreen && !onceAlready ) + { + OSVERSIONINFO OSVersionInfo; + + // HACK: The Voodoo5 on W2K will only work if the initial rendering + // context is windowed. Can you believe this crap? + dMemset( &OSVersionInfo, 0, sizeof( OSVERSIONINFO ) ); + OSVersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + if ( GetVersionEx( &OSVersionInfo ) && + OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && + OSVersionInfo.dwMajorVersion == 5 ) + if ( !setScreenMode( 640, 480, bpp, false, true, false ) ) + return false; + else + { + const char* vendorString = (const char*) glGetString( GL_VENDOR ); + const char* rendererString = (const char*) glGetString( GL_RENDERER ); + + // only do this for the first session + if (!Con::getBoolVariable("$DisableSystemProfiling") && + ( dStrcmp(vendorString, Con::getVariable("$pref::Video::profiledVendor")) || + dStrcmp(rendererString, Con::getVariable("$pref::Video::profiledRenderer")) )) + { + profileSystem(vendorString, rendererString); + profiled = true; + } + } + + onceAlready = true; + } + // Set the resolution: + if ( !setScreenMode( width, height, bpp, ( fullScreen || mFullScreenOnly ), true, false ) ) + return false; + + // Get original gamma ramp + mRestoreGamma = GetDeviceGammaRamp(winState.appDC, mOriginalRamp); + + // 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 ); + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + + QGL_EXT_Init(); + + Con::setVariable( "$pref::Video::displayDevice", mDeviceName ); + + // only do this for the first session + if (!profiled && + !Con::getBoolVariable("$DisableSystemProfiling") && + ( dStrcmp(vendorString, Con::getVariable("$pref::Video::profiledVendor")) || + dStrcmp(rendererString, Con::getVariable("$pref::Video::profiledRenderer")) )) + { + profileSystem(vendorString, rendererString); + profiled = true; + } + + if (profiled) + { + U32 width, height, bpp; + + if (Con::getBoolVariable("$pref::Video::clipHigh", false)) + for (S32 i = mResolutionList.size()-1; i >= 0; --i) + if (mResolutionList[i].w > 1152 || mResolutionList[i].h > 864) + mResolutionList.erase(i); + + if (Con::getBoolVariable("$pref::Video::only16", false)) + for (S32 i = mResolutionList.size()-1; i >= 0; --i) + if (mResolutionList[i].bpp == 32) + mResolutionList.erase(i); + + dSscanf(Con::getVariable("$pref::Video::resolution"), "%d %d %d", &width, &height, &bpp); + setScreenMode(width, height, bpp, + Con::getBoolVariable("$pref::Video::fullScreen", true), false, false); + } + + // 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() +{ + Con::printf( "Shutting down the OpenGL display device..." ); + + if ( winState.hGLRC ) + { + if (mRestoreGamma) + SetDeviceGammaRamp(winState.appDC, mOriginalRamp); + + Con::printf( "Making the GL rendering context not current..." ); + qwglMakeCurrent( NULL, NULL ); + if ( Con::getBoolVariable("$pref::Video::deleteContext", true) ) + { + Con::printf( "Deleting the GL rendering context..." ); + qwglDeleteContext( winState.hGLRC ); + } + winState.hGLRC = NULL; + } + + if ( winState.appDC ) + { + Con::printf( "Releasing the device context..." ); + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + } + + if ( smIsFullScreen ) + { + Con::printf( "Restoring the desktop display settings (%dx%dx%d)...", winState.desktopWidth, winState.desktopHeight, winState.desktopBitsPixel ); + ChangeDisplaySettings( NULL, 0 ); + } +} + + +//------------------------------------------------------------------------------ +// This is the real workhorse function of the DisplayDevice... +// +bool OpenGLDevice::setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen, bool forceIt, bool repaint ) +{ + HWND curtain = NULL; + char errorMessage[256]; + Resolution newRes( width, height, bpp ); + bool newFullScreen = fullScreen; + bool safeModeOn = Con::getBoolVariable( "$pref::Video::safeModeOn" ); + + if ( !newFullScreen && mFullScreenOnly ) + { + Con::warnf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode - device or desktop color depth does not allow windowed mode!" ); + newFullScreen = true; + } + + if ( !newFullScreen && ( newRes.w >= winState.desktopWidth || newRes.h >= winState.desktopHeight ) ) + { + Con::warnf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode -- can't switch to resolution larger than desktop in windowed mode!" ); + // Find the largest standard resolution that will fit on the desktop: + U32 resIndex; + for ( resIndex = mResolutionList.size() - 1; resIndex > 0; resIndex-- ) + { + if ( mResolutionList[resIndex].w < winState.desktopWidth + && mResolutionList[resIndex].h < winState.desktopHeight ) + break; + } + newRes = mResolutionList[resIndex]; + } + + if ( newRes.w < 640 || newRes.h < 480 ) + { + Con::warnf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode -- can't go smaller than 640x480!" ); + return false; + } + + if ( newFullScreen ) + { + if (newRes.bpp != 16 && mFullScreenOnly) + newRes.bpp = 16; + + // Match the new resolution to one in the list: + U32 resIndex = 0; + U32 bestScore = 0, thisScore = 0; + for ( int i = 0; i < mResolutionList.size(); i++ ) + { + if ( newRes == mResolutionList[i] ) + { + resIndex = i; + break; + } + else + { + thisScore = abs( S32( newRes.w ) - S32( mResolutionList[i].w ) ) + + abs( S32( newRes.h ) - S32( mResolutionList[i].h ) ) + + ( newRes.bpp == mResolutionList[i].bpp ? 0 : 1 ); + + if ( !bestScore || ( thisScore < bestScore ) ) + { + bestScore = thisScore; + resIndex = i; + } + } + } + + newRes = mResolutionList[resIndex]; + } + else + { + // Basically ignore the bit depth parameter: + newRes.bpp = winState.desktopBitsPixel; + } + + // Return if already at this resolution: + if ( !forceIt && newRes == smCurrentRes && newFullScreen == smIsFullScreen ) + return true; + + Con::printf( "Setting screen mode to %dx%dx%d (%s)...", newRes.w, newRes.h, newRes.bpp, ( newFullScreen ? "fs" : "w" ) ); + + bool needResurrect = false; + + if ( ( newRes.bpp != smCurrentRes.bpp ) || ( safeModeOn && ( ( smIsFullScreen != newFullScreen ) || newFullScreen ) ) ) + { + // Delete the rendering context: + if ( winState.hGLRC ) + { + if (!Video::smNeedResurrect) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + } + + Con::printf( "Making the rendering context not current..." ); + if ( !qwglMakeCurrent( NULL, NULL ) ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglMakeCurrent( NULL, NULL ) failed!" ); + return false; + } + + Con::printf( "Deleting the rendering context..." ); + if ( Con::getBoolVariable("$pref::Video::deleteContext",true) && + !qwglDeleteContext( winState.hGLRC ) ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglDeleteContext failed!" ); + return false; + } + winState.hGLRC = NULL; + } + + // Release the device context: + if ( winState.appDC ) + { + Con::printf( "Releasing the device context..." ); + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + } + + // Destroy the window: + if ( winState.appWindow ) + { + Con::printf( "Destroying the window..." ); + DestroyWindow( winState.appWindow ); + winState.appWindow = NULL; + } + } + else if ( smIsFullScreen != newFullScreen ) + { + // Change the window style: + Con::printf( "Changing the window style..." ); + S32 windowStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + if ( newFullScreen ) + windowStyle |= ( WS_POPUP | WS_MAXIMIZE ); + else + windowStyle |= ( WS_OVERLAPPED | WS_CAPTION ); + + if ( !SetWindowLong( winState.appWindow, GWL_STYLE, windowStyle ) ) + Con::errorf( "SetWindowLong failed to change the window style!" ); + } + + U32 test; + if ( newFullScreen ) + { + // Change the display settings: + DEVMODE devMode; + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmPelsWidth = newRes.w; + devMode.dmPelsHeight = newRes.h; + devMode.dmBitsPerPel = newRes.bpp; + devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; + + Con::printf( "Changing the display settings to %dx%dx%d...", newRes.w, newRes.h, newRes.bpp ); + curtain = CreateCurtain( newRes.w, newRes.h ); + test = ChangeDisplaySettings( &devMode, CDS_FULLSCREEN ); + if ( test != DISP_CHANGE_SUCCESSFUL ) + { + smIsFullScreen = false; + Con::setBoolVariable( "$pref::Video::fullScreen", false ); + ChangeDisplaySettings( NULL, 0 ); + Con::errorf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode - ChangeDisplaySettings failed." ); + switch( test ) + { + case DISP_CHANGE_RESTART: + Platform::AlertOK( "Display Change Failed", "You must restart your machine to get the specified mode." ); + break; + + case DISP_CHANGE_BADMODE: + Platform::AlertOK( "Display Change Failed", "The specified mode is not supported by this device." ); + break; + + default: + Platform::AlertOK( "Display Change Failed", "Hardware failed to switch to the specified mode." ); + break; + }; + DestroyWindow( curtain ); + return false; + } + else + smIsFullScreen = true; + } + else if ( smIsFullScreen ) + { + Con::printf( "Changing to the desktop display settings (%dx%dx%d)...", winState.desktopWidth, winState.desktopHeight, winState.desktopBitsPixel ); + ChangeDisplaySettings( NULL, 0 ); + smIsFullScreen = false; + } + Con::setBoolVariable( "$pref::Video::fullScreen", smIsFullScreen ); + + bool newWindow = false; + if ( !winState.appWindow ) + { + Con::printf( "Creating a new %swindow...", ( fullScreen ? "full-screen " : "" ) ); + winState.appWindow = CreateOpenGLWindow( newRes.w, newRes.h, newFullScreen ); + if ( !winState.appWindow ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nFailed to create a new window!" ); + return false; + } + newWindow = true; + } + + // Move the window: + if ( !newFullScreen ) + { + // Adjust the window rect to compensate for the window style: + RECT windowRect; + windowRect.left = windowRect.top = 0; + windowRect.right = newRes.w; + windowRect.bottom = newRes.h; + + AdjustWindowRect( &windowRect, GetWindowLong( winState.appWindow, GWL_STYLE ), false ); + U32 adjWidth = windowRect.right - windowRect.left; + U32 adjHeight = windowRect.bottom - windowRect.top; + + // Center the window on the desktop: + U32 xPos = ( winState.desktopWidth - adjWidth ) / 2; + U32 yPos = ( winState.desktopHeight - adjHeight ) / 2; + test = SetWindowPos( winState.appWindow, 0, xPos, yPos, adjWidth, adjHeight, SWP_NOZORDER ); + if ( !test ) + { + dSprintf( errorMessage, 255, "OpenGLDevice::setScreenMode\nSetWindowPos failed trying to move the window to (%d,%d) and size it to %dx%d.", xPos, yPos, newRes.w, newRes.h ); + AssertFatal( false, errorMessage ); + return false; + } + } + else if ( !newWindow ) + { + // Move and size the window to take up the whole screen: + if ( !SetWindowPos( winState.appWindow, 0, 0, 0, newRes.w, newRes.h, SWP_NOZORDER ) ) + { + dSprintf( errorMessage, 255, "OpenGLDevice::setScreenMode\nSetWindowPos failed to move the window to (0,0) and size it to %dx%d.", newRes.w, newRes.h ); + AssertFatal( false, errorMessage ); + return false; + } + } + + bool newDeviceContext = false; + if ( !winState.appDC ) + { + // Get a new device context: + Con::printf( "Acquiring a new device context..." ); + winState.appDC = GetDC( winState.appWindow ); + if ( !winState.appDC ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nGetDC failed to get a valid device context!" ); + return false; + } + newDeviceContext = true; + } + + if ( newWindow ) + { + // Set the pixel format of the new window: + PIXELFORMATDESCRIPTOR pfd; + CreatePixelFormat( &pfd, newRes.bpp, 24, 8, false ); + S32 chosenFormat = ChooseBestPixelFormat( winState.appDC, &pfd ); + if ( !chosenFormat ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nNo valid pixel formats found!" ); + return false; + } + qwglDescribePixelFormat( winState.appDC, chosenFormat, sizeof( pfd ), &pfd ); + if ( !SetPixelFormat( winState.appDC, chosenFormat, &pfd ) ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nFailed to set the pixel format!" ); + return false; + } + Con::printf( "Pixel format set:" ); + Con::printf( " %d color bits, %d depth bits, %d stencil bits", pfd.cColorBits, pfd.cDepthBits, pfd.cStencilBits ); + } + + if ( !winState.hGLRC ) + { + // Create a new rendering context: + Con::printf( "Creating a new rendering context..." ); + winState.hGLRC = qwglCreateContext( winState.appDC ); + if ( !winState.hGLRC ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglCreateContext failed to create an OpenGL rendering context!" ); + return false; + } + + // Make the new rendering context current: + Con::printf( "Making the new rendering context current..." ); + if ( !qwglMakeCurrent( winState.appDC, winState.hGLRC ) ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglMakeCurrent failed to make the rendering context current!" ); + return false; + } + + // Just for kicks. Seems a relatively central place to put this... + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + } + + // Just for kicks. Seems a relatively central place to put this... + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + if ( newDeviceContext && gGLState.suppSwapInterval ) + setVerticalSync( !Con::getBoolVariable( "$pref::Video::disableVerticalSync" ) ); + + smCurrentRes = newRes; + Platform::setWindowSize( newRes.w, newRes.h ); + char tempBuf[15]; + dSprintf( tempBuf, sizeof( tempBuf ), "%d %d %d", smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp ); + Con::setVariable( "$pref::Video::resolution", tempBuf ); + + if ( curtain ) + DestroyWindow( curtain ); + + // Doesn't hurt to do this even it isn't necessary: + ShowWindow( winState.appWindow, SW_SHOW ); + SetForegroundWindow( winState.appWindow ); + SetFocus( winState.appWindow ); + + if ( repaint ) + Con::evaluate( "resetCanvas();" ); + + return true; +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::swapBuffers() +{ + qwglSwapBuffers( winState.appDC ); +} + + +//------------------------------------------------------------------------------ +const char* OpenGLDevice::getDriverInfo() +{ + // 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 ); + 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 ramp[256*3]; + + if (!GetDeviceGammaRamp(winState.appDC, ramp)) + return false; + + F32 csum = 0.0; + U32 ccount = 0; + + for (U16 i = 0; i < 256; ++i) + { + if (i != 0 && ramp[i] != 0 && ramp[i] != 65535) + { + F64 b = (F64) i/256.0; + F64 a = (F64) ramp[i]/65535.0; + F32 c = (F32) (mLog(a)/mLog(b)); + + csum += c; + ++ccount; + } + } + g = csum/ccount; + + return true; +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setGammaCorrection(F32 g) +{ + U16 ramp[256*3]; + + for (U16 i = 0; i < 256; ++i) + ramp[i] = mPow((F32) i/256.0f, g) * 65535.0f; + dMemcpy(&ramp[256],ramp,256*sizeof(U16)); + dMemcpy(&ramp[512],ramp,256*sizeof(U16)); + + return SetDeviceGammaRamp(winState.appDC, ramp); +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setVerticalSync( bool on ) +{ + if ( !gGLState.suppSwapInterval ) + return( false ); + + return( qwglSwapIntervalEXT( on ? 1 : 0 ) ); +} + +//------------------------------------------------------------------------------ +DisplayDevice* OpenGLDevice::create() +{ + bool result = false; + bool fullScreenOnly = false; + + OSVERSIONINFO OSVersionInfo; + U32 switchedNT = false; + + dMemset( &OSVersionInfo, 0, sizeof( OSVERSIONINFO ) ); + OSVersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + if ( GetVersionEx( &OSVersionInfo ) && + OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) + { + DEVMODE devMode; + + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmBitsPerPel = winState.desktopBitsPixel == 16 ? 32 : 16; + devMode.dmFields = DM_BITSPERPEL; + + // attempt bit-depth change to test Windows 95B + if ( ChangeDisplaySettings( &devMode, CDS_TEST ) != DISP_CHANGE_SUCCESSFUL ) + smCanSwitchBitDepth = false; + } + // switching NT to 32-bit desktop to determine if we have a 16-bit card + else if (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && + OSVersionInfo.dwMajorVersion == 4 && + winState.desktopBitsPixel != 32) + { + DEVMODE devMode; + + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmBitsPerPel = 32; + devMode.dmFields = DM_BITSPERPEL; + + switchedNT = ChangeDisplaySettings( &devMode, 0 ) == DISP_CHANGE_SUCCESSFUL; + } + + // This shouldn't happen, but just to be safe... + if ( winState.hinstOpenGL ) + QGL_Shutdown(); + + if (!QGL_Init( "opengl32", "glu32" )) + { + if (switchedNT) + ChangeDisplaySettings( NULL, 0 ); + + return NULL; + } + + // Create a test window to see if OpenGL hardware acceleration is available: + WNDCLASS wc; + dMemset(&wc, 0, sizeof(wc)); + wc.style = CS_OWNDC; + wc.lpfnWndProc = DefWindowProc; + wc.hInstance = winState.appInstance; + wc.lpszClassName = "OGLTest"; + RegisterClass( &wc ); + + HWND testWindow = CreateWindow( + "OGLTest", + "", + WS_POPUP, + 0, 0, 640, 480, + NULL, + NULL, + winState.appInstance, + NULL ); + + if ( testWindow ) + { + HDC testDC = GetDC( testWindow ); + if ( testDC ) + { + PIXELFORMATDESCRIPTOR pfd; + CreatePixelFormat( &pfd, 16, 16, 8, false ); + U32 chosenFormat = ChooseBestPixelFormat( testDC, &pfd ); + if ( chosenFormat != 0 ) + { + qwglDescribePixelFormat( testDC, chosenFormat, sizeof( pfd ), &pfd ); + + result = !( pfd.dwFlags & PFD_GENERIC_FORMAT ); + + if ( result && (winState.desktopBitsPixel == 16 || pfd.cColorBits == 16) ) + D3DDevice::smStay16 = true; + + if ( result && winState.desktopBitsPixel != 16 && !smCanSwitchBitDepth) + { + // If Windows 95 cannot switch bit depth, it should only attempt 16-bit cards + // with a 16-bit desktop + + // See if we can get a 32-bit pixel format: + PIXELFORMATDESCRIPTOR pfd; + + CreatePixelFormat( &pfd, 32, 24, 8, false ); + S32 chosenFormat = ChooseBestPixelFormat( testDC, &pfd ); + if ( chosenFormat != 0 ) + { + qwglDescribePixelFormat( winState.appDC, chosenFormat, sizeof( pfd ), &pfd ); + + if (pfd.cColorBits == 16) + { + Platform::AlertOK("Requires 16-Bit Desktop", + "You must run in 16-bit color to play \"Tribes 2\".\nPlease quit the game, set your desktop color depth to 16-bit\nand then restart the application."); + + result = false; + } + } + } + // Don't allow 16-bit cards to do windowed mode on NT + else if (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && + OSVersionInfo.dwMajorVersion == 4 && + result && pfd.cColorBits == 16) + fullScreenOnly = true; + } + else if ( (winState.desktopBitsPixel != 16 || switchedNT) && smCanSwitchBitDepth ) + { + // Try again after changing the display to 16-bit: + ReleaseDC( testWindow, testDC ); + DestroyWindow( testWindow ); + + DEVMODE devMode; + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmBitsPerPel = 16; + devMode.dmFields = DM_BITSPERPEL; + + U32 test = ChangeDisplaySettings( &devMode, 0 ); + if ( test == DISP_CHANGE_SUCCESSFUL ) + { + testWindow = CreateWindow( + "OGLTest", + "", + WS_OVERLAPPED | WS_CAPTION, + 0, 0, 640, 480, + NULL, + NULL, + winState.appInstance, + NULL ); + + if ( testWindow ) + { + testDC = GetDC( testWindow ); + if ( testDC ) + { + CreatePixelFormat( &pfd, 16, 16, 8, false ); + chosenFormat = ChooseBestPixelFormat( testDC, &pfd ); + if ( chosenFormat != 0 ) + { + qwglDescribePixelFormat( testDC, chosenFormat, sizeof( pfd ), &pfd ); + + result = !( pfd.dwFlags & PFD_GENERIC_FORMAT ); + if ( result ) + fullScreenOnly = true; + } + } + } + } + ChangeDisplaySettings( NULL, 0 ); + switchedNT = false; + } + else if ( winState.desktopBitsPixel != 16 && !smCanSwitchBitDepth ) + Platform::AlertOK("Requires 16-Bit Desktop", + "You must run in 16-bit color to play \"Tribes 2\".\nPlease quit the game, set your desktop color depth to 16-bit\nand then restart the application."); + + ReleaseDC( testWindow, testDC ); + } + DestroyWindow( testWindow ); + } + + UnregisterClass( "OGLTest", winState.appInstance ); + + QGL_Shutdown(); + + // Return NT to previous desktop bit depth + if (switchedNT) + ChangeDisplaySettings( NULL, 0 ); + + if ( result ) + { + OpenGLDevice* newOGLDevice = new OpenGLDevice(); + if ( newOGLDevice ) + { + newOGLDevice->mFullScreenOnly = fullScreenOnly; + return (DisplayDevice*) newOGLDevice; + } + else + return NULL; + } + else + return NULL; +} diff --git a/platformWin32/winOGLVideo.h b/platformWin32/winOGLVideo.h new file mode 100644 index 0000000..00f0326 --- /dev/null +++ b/platformWin32/winOGLVideo.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WINOGLVIDEO_H_ +#define _WINOGLVIDEO_H_ + +#ifndef _PLATFORMVIDEO_H_ +#include "Platform/platformVideo.h" +#endif + +class OpenGLDevice : public DisplayDevice +{ + static bool smCanSwitchBitDepth; + + bool mRestoreGamma; + U16 mOriginalRamp[256*3]; + + public: + 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 ); + + static DisplayDevice* create(); +}; + +#endif // _H_WINOGLVIDEO diff --git a/platformWin32/winOpenAL.cc b/platformWin32/winOpenAL.cc new file mode 100644 index 0000000..720d4a6 --- /dev/null +++ b/platformWin32/winOpenAL.cc @@ -0,0 +1,253 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" +#include "console/console.h" +#define AL_NO_PROTOTYPES +#include +#include +#include + +#define MILES_REENTRANT_CHECK + +// Stub functions: --------------------------------------------------------- +// AL: +ALvoid stub_alEnable( ALenum ) {} +ALvoid stub_alDisable( ALenum ) {} +ALboolean stub_alIsEnabled( ALenum ) {return(AL_FALSE);} +ALvoid stub_alHint( ALenum , ALenum ) {} +ALboolean stub_alGetBoolean( ALenum ) {return(AL_FALSE);} +ALint stub_alGetInteger( ALenum ) {return(0);} +ALfloat stub_alGetFloat( ALenum ) {return(0.f);} +ALdouble stub_alGetDouble( ALenum ) {return(0.f);} +ALvoid stub_alGetBooleanv( ALenum, ALboolean* ) {} +ALvoid stub_alGetIntegerv( ALenum, ALint* ) {} +ALvoid stub_alGetFloatv( ALenum, ALfloat* ) {} +ALvoid stub_alGetDoublev( ALenum, ALdouble* ) {} +const ALubyte* stub_alGetString( ALenum pname ) +{ + switch(pname) + { + case AL_VENDOR: return (ALubyte*)"None"; + case AL_VERSION: return (ALubyte*)"OpenAL 0.1"; + case AL_RENDERER: return (ALubyte*)"None"; + case AL_EXTENSIONS: return (ALubyte*)""; + } + return(0); +} +ALenum stub_alGetError( ALvoid ) {return(0);} +ALboolean stub_alIsExtensionPresent( const ALubyte* ) {return(AL_FALSE);} +ALvoid* stub_alGetProcAddress( const ALubyte* ) {return(0);} +ALenum stub_alGetEnumValue( const ALubyte* ) {return(0);} +ALvoid stub_alListenerf( ALenum, ALfloat ) {} +ALvoid stub_alListener3f( ALenum, ALfloat, ALfloat, ALfloat ) {} +ALvoid stub_alListenerfv( ALenum, ALfloat* ) {} +ALvoid stub_alGetListeneri( ALenum, ALint* ) {} +ALvoid stub_alGetListenerf( ALenum, ALfloat* ) {} +ALvoid stub_alGetListenerfv( ALenum, ALfloat* ) {} +ALvoid stub_alGenSources( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteSources( ALsizei, ALuint* ) {} +ALboolean stub_alIsSource( ALuint ) {return(AL_FALSE);} +ALvoid stub_alSourcei( ALuint, ALenum, ALint ) {} +ALvoid stub_alSourcef( ALuint, ALenum, ALfloat ) {} +ALvoid stub_alSource3f( ALuint, ALenum, ALfloat, ALfloat, ALfloat ) {} +ALvoid stub_alSourcefv( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alGetSourcei( ALuint, ALenum, ALint* ) {} +ALvoid stub_alGetSourcef( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alGetSourcefv( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alSourcePlayv( ALuint, ALuint* ) {} +ALvoid stub_alSourceStopv( ALuint, ALuint* ) {} +ALvoid stub_alSourcePlay( ALuint ) {} +ALvoid stub_alSourcePause( ALuint ) {} +ALvoid stub_alSourceStop( ALuint ) {} +ALvoid stub_alGenBuffers( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteBuffers( ALsizei, ALuint* ) {} +ALboolean stub_alIsBuffer( ALuint ) {return(AL_FALSE);} +ALvoid stub_alBufferData( ALuint, ALenum, ALvoid*, ALsizei, ALsizei ) {} +ALsizei stub_alBufferAppendData( ALuint, ALenum, ALvoid*, ALsizei, ALsizei ) {return(0);} +ALvoid stub_alGetBufferi( ALuint, ALenum, ALint* ) {} +ALvoid stub_alGetBufferf( ALuint, ALenum, ALfloat* ) {} + +// ALC: +ALvoid* stub_alcCreateContext( ALint *) {return(0);} +ALCenum stub_alcMakeContextCurrent( ALvoid *) {return(ALC_INVALID);} +ALvoid* stub_alcUpdateContext( ALvoid * ) {return(0);} +ALCenum stub_alcDestroyContext( ALvoid * ) {return(ALC_INVALID);} +ALCenum stub_alcGetError( ALvoid ) {return(ALC_INVALID);} +const ALubyte* stub_alcGetErrorString( ALvoid ) {return(0);} +ALvoid* stub_alcGetCurrentContext( ALvoid ) {return(0);} + +// ALUT: +void stub_alutInit( int *, char ** ) {} +void stub_alutExit( ALvoid ) {} +ALboolean stub_alutLoadWAV( const char *, ALvoid **, ALsizei *, ALsizei *, ALsizei *, ALsizei *) {return(AL_FALSE);} + +// Extension: IASIG +ALvoid stub_alGenEnvironmentIASIG( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteEnvironmentIASIG( ALsizei, ALuint*) {} +ALboolean stub_alIsEnvironmentIASIG( ALuint ) {return(AL_FALSE);} +ALvoid stub_alEnvironmentiIASIG( ALuint, ALenum, ALint ) {} +ALvoid stub_alEnvironmentfIASIG( ALuint, ALenum, ALfloat ) {} +ALvoid stub_alGetEnvironmentiIASIG_EXT( ALuint, ALenum, ALint * ) {} +ALvoid stub_alGetEnvironmentfIASIG_EXT( ALuint, ALenum, ALfloat * ) {} + +// Extension: Dynamix +ALboolean stub_alBufferi_EXT( ALuint, ALenum, ALint ) { return(AL_FALSE); } +ALboolean stub_alBufferSyncData_EXT( ALuint, ALenum, ALvoid *, ALsizei, ALsizei ) {return(AL_FALSE); } +ALboolean stub_alBufferStreamFile_EXT( ALuint, const ALubyte * ) { return(AL_FALSE); } +ALboolean stub_alSourceCallback_EXT( ALuint, ALvoid * ) { return(AL_FALSE); } +ALvoid stub_alSourceResetEnvironment_EXT( ALuint ) {} +ALboolean stub_alContexti_EXT( ALenum, ALint) { return(AL_FALSE); } +ALboolean stub_alGetContexti_EXT( ALenum, ALint * ) { return(AL_FALSE); } +ALboolean stub_alGetContextstr_EXT( ALenum, ALuint, ALubyte ** ) { return(AL_FALSE); } +ALboolean stub_alCaptureInit_EXT( ALenum, ALuint, ALsizei ) { return(AL_FALSE); } +ALboolean stub_alCaptureDestroy_EXT( ALvoid ) { return(AL_FALSE); } +ALboolean stub_alCaptureStart_EXT( ALvoid ) { return(AL_FALSE); } +ALboolean stub_alCaptureStop_EXT( ALvoid ) { return(AL_FALSE); } +ALsizei stub_alCaptureGetData_EXT( ALvoid *, ALsizei, ALenum, ALuint ) { return(AL_FALSE); } + +// declare OpenAL functions +#define AL_EXTENSION(ext_name) bool gDoesSupport_##ext_name = false; +#define AL_FUNCTION(fn_return,fn_name,fn_args) fn_return (*fn_name)fn_args = stub_##fn_name; +#define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_return (*fn_name)fn_args = stub_##fn_name; +#include + +// DLL's: ------------------------------------------------------------------ +static bool findExtension( const char* name ) +{ + bool result = false; + if (alGetString != NULL) + { + result = dStrstr( (const char*)alGetString(AL_EXTENSIONS), name) != NULL; + } + return result; +} + +static bool bindFunction( void *&fnAddress, const char *name ) +{ + fnAddress = GetProcAddress( winState.hinstOpenAL, name ); + if( !fnAddress ) + Con::errorf(ConsoleLogEntry::General, " Missing OpenAL function '%s'", name); + return (fnAddress != NULL); +} + +static void bindExtension( void *&fnAddress, const char *name, bool &supported ) +{ + if (supported) + { + bindFunction(fnAddress, name); + if (fnAddress == NULL) + supported = NULL; + } + else + fnAddress = NULL; +} + +static bool bindDLLFunctions() +{ + bool result = true; + #define AL_FUNCTION(fn_return,fn_name,fn_args) result &= bindFunction( *(void**)&fn_name, #fn_name); +#include + return result; +} + +// Stub: ------------------------------------------------------------------- +static void bindStubFunctions() +{ + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = false; + #define AL_FUNCTION(fn_return,fn_name,fn_parms) fn_name = stub_##fn_name; + #define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_name = stub_##fn_name; +#include +} + +// Miles: ------------------------------------------------------------------ +#ifndef NO_MILES_OPENAL + #ifdef MILES_REENTRANT_CHECK + #define AL_FUNCTION(fn_return, fn_name, fn_args) extern fn_return miles_api_##fn_name fn_args; + #define AL_EXT_FUNCTION(ext_name, fn_return, fn_name, fn_args) extern fn_return miles_api_##fn_name fn_args; + #else + #define AL_FUNCTION(fn_return, fn_name, fn_args) extern fn_return miles_##fn_name fn_args; + #define AL_EXT_FUNCTION(ext_name, fn_return, fn_name, fn_args) extern fn_return miles_##fn_name fn_args; + #endif +#include +#endif + +static void bindMilesFunctions() +{ +#ifndef NO_MILES_OPENAL + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = false; + #ifdef MILES_REENTRANT_CHECK + #define AL_FUNCTION(fn_return,fn_name,fn_parms) fn_name = miles_api_##fn_name; + #define AL_EXT_FUNCTION(ext_name, fn_return, fn_name, fn_args) fn_name = miles_api_##fn_name; + #else + #define AL_FUNCTION(fn_return,fn_name,fn_parms) fn_name = miles_##fn_name; + #define AL_EXT_FUNCTION(ext_name, fn_return, fn_name, fn_args) fn_name = miles_##fn_name; + #endif +#include +#endif +} + +namespace Audio +{ + +static bool sStaticLibrary; + +void libraryShutdown() +{ + if (winState.hinstOpenAL) + FreeLibrary(winState.hinstOpenAL); + winState.hinstOpenAL = NULL; + + bindStubFunctions(); + sStaticLibrary = true; +} + +bool libraryInit(const char *library) +{ + libraryShutdown(); + + if(!library || !library[0]) + return(false); + + // static drivers... + if(!dStricmp(library, "Miles")) + { + bindMilesFunctions(); + return(true); + } + + winState.hinstOpenAL = LoadLibrary( avar("%s.dll", library) ); + if(winState.hinstOpenAL != NULL) + { + if(bindDLLFunctions()) + { + sStaticLibrary = false; + return(true); + } + libraryShutdown(); + } + + return(false); +} + +void libraryInitExtensions() +{ + // static library extensions are bound on libraryInit (need to be defined anyways...) + if(sStaticLibrary) + { + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = findExtension( #ext_name ); +#include + } + else + { + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = findExtension( #ext_name ); + #define AL_EXT_FUNCTION(ext_name, fn_return,fn_name,fn_args) bindExtension( *(void**)&fn_name, #fn_name, gDoesSupport_##ext_name ); +#include + } +} + +} // end namespace Audio diff --git a/platformWin32/winProcessControl.cc b/platformWin32/winProcessControl.cc new file mode 100644 index 0000000..3ec105a --- /dev/null +++ b/platformWin32/winProcessControl.cc @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" + +void Platform::postQuitMessage(const U32 in_quitVal) +{ + PostQuitMessage(in_quitVal); +} + +void Platform::debugBreak() +{ + DebugBreak(); +} + +void Platform::forceShutdown(S32 returnValue) +{ + ExitProcess(returnValue); +} diff --git a/platformWin32/winRedbook.cc b/platformWin32/winRedbook.cc new file mode 100644 index 0000000..4090327 --- /dev/null +++ b/platformWin32/winRedbook.cc @@ -0,0 +1,468 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" +#include "Platform/platformRedBook.h" + +class Win32RedBookDevice : public RedBookDevice +{ + private: + typedef RedBookDevice Parent; + + U32 mDeviceId; + + void setLastError(const char *); + void setLastError(U32); + + MIXERCONTROLDETAILS mMixerVolumeDetails; + MIXERCONTROLDETAILS_UNSIGNED mMixerVolumeValue; + + U32 mVolumeDeviceId; + 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]; + + ::GetLogicalDriveStrings(bufSize, buf); + + char * str = buf; + while(*str) + { + if(::GetDriveType(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; + openParms.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO; + openParms.lpstrElementName = mDeviceName; + + error = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD)(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)(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)(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)(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)(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)(LPMCI_STATUS_PARMS)&statusParms); + + if(error) + { + setLastError(error); + return(false); + } + + playParms.dwTo = playParms.dwFrom + statusParms.dwReturn; + + // play the track + playParms.dwCallback = MAKELONG(winState.appWindow, 0); + error = mciSendCommand(mDeviceId, MCI_PLAY, MCI_FROM|MCI_TO|MCI_NOTIFY, + (DWORD)(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)(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)(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((HMIXEROBJ)mVolumeDeviceId, &mMixerVolumeDetails, MIXER_GETCONTROLDETAILSF_VALUE); + vol = mMixerVolumeValue.dwValue; + } + else + auxGetVolume(mVolumeDeviceId, (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((HMIXEROBJ)mVolumeDeviceId, &mMixerVolumeDetails, MIXER_SETCONTROLDETAILSF_VALUE); + } + else + { + vol |= vol << 16; + auxSetVolume(mVolumeDeviceId, 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((HMIXEROBJ)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((HMIXEROBJ)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((HMIXEROBJ)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)) + { + mVolumeDeviceId = 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((HMIXEROBJ)mVolumeDeviceId, &mMixerVolumeDetails, MIXER_SETCONTROLDETAILSF_VALUE); + mixerClose((HMIXER)mVolumeDeviceId); + } + else + auxSetVolume(mVolumeDeviceId, mOriginalVolume); + + mVolumeInitialized = false; +} + +//------------------------------------------------------------------------------ + +void Win32RedBookDevice::setLastError(const char * error) +{ + RedBook::setLastError(error); +} + +void Win32RedBookDevice::setLastError(U32 errorId) +{ + char buffer[256]; + if(!mciGetErrorString(errorId, buffer, sizeof(buffer) - 1)) + setLastError("Failed to get MCI error string!"); + else + setLastError(buffer); +} + diff --git a/platformWin32/winSemaphore.cc b/platformWin32/winSemaphore.cc new file mode 100644 index 0000000..83038b6 --- /dev/null +++ b/platformWin32/winSemaphore.cc @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" +#include "Platform/platformSemaphore.h" + +void * Semaphore::createSemaphore(U32 initialCount) +{ + HANDLE * semaphore = new HANDLE; + *semaphore = CreateSemaphore(0, initialCount, -1, 0); + return(semaphore); +} + +void Semaphore::destroySemaphore(void * semaphore) +{ + AssertFatal(semaphore, "Semaphore::destroySemaphore: invalid semaphore"); + CloseHandle(*(HANDLE*)semaphore); + delete semaphore; +} + +bool Semaphore::acquireSemaphore(void * semaphore, bool block) +{ + AssertFatal(semaphore, "Semaphore::acquireSemaphore: invalid semaphore"); + if(block) + { + WaitForSingleObject(*(HANDLE*)semaphore, INFINITE); + return(true); + } + else + { + DWORD result = WaitForSingleObject(*(HANDLE*)semaphore, 0); + return(result == WAIT_OBJECT_0); + } +} + +void Semaphore::releaseSemaphore(void * semaphore) +{ + AssertFatal(semaphore, "Semaphore::releaseSemaphore: invalid semaphore"); + ReleaseSemaphore(*(HANDLE*)semaphore, 1, 0); +} diff --git a/platformWin32/winStrings.cc b/platformWin32/winStrings.cc new file mode 100644 index 0000000..2dc034b --- /dev/null +++ b/platformWin32/winStrings.cc @@ -0,0 +1,288 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" + +#ifdef HAS_VSSCANF +# undef HAS_VSSCANF +#endif + +#if defined(__MWERKS__) && __MWERKS__ >= 0x2400 +# define HAS_VSSCANF +# define __vsscanf vsscanf +#endif + +#ifdef __BORLANDC__ +# define _stricmp stricmp +# define _strnicmp strnicmp +# define _strupr strupr +# define _strlwr strlwr +# define __vsscanf vsscanf +#endif + + + +char *dStrdup_r(const char *src, const char *fileName, U32 lineNumber) +{ + char *buffer = (char *) dMalloc_r(dStrlen(src) + 1, fileName, lineNumber); + dStrcpy(buffer, src); + return buffer; +} + +char* dStrcat(char *dst, const char *src) +{ + return strcat(dst,src); +} + +S32 dStrcmp(const char *str1, const char *str2) +{ + return strcmp(str1, str2); +} + +S32 dStricmp(const char *str1, const char *str2) +{ + return _stricmp(str1, str2); +} + +S32 dStrncmp(const char *str1, const char *str2, U32 len) +{ + return strncmp(str1, str2, len); +} + +S32 dStrnicmp(const char *str1, const char *str2, U32 len) +{ + return _strnicmp(str1, str2, len); +} + +char* dStrcpy(char *dst, const char *src) +{ + return strcpy(dst,src); +} + +char* dStrncpy(char *dst, const char *src, U32 len) +{ + return strncpy(dst,src,len); +} + +U32 dStrlen(const char *str) +{ + return strlen(str); +} + + +char* dStrupr(char *str) +{ +#ifdef __MWERKS__ // metrowerks strupr is broken + _strupr(str); + return str; +#else + return _strupr(str); +#endif +} + + +char* dStrlwr(char *str) +{ + return _strlwr(str); +} + + +char* dStrchr(char *str, S32 c) +{ + return strchr(str,c); +} + + +const char* dStrchr(const char *str, S32 c) +{ + return strchr(str,c); +} + + +const char* dStrrchr(const char *str, S32 c) +{ + return strrchr(str,c); +} + +char* dStrrchr(char *str, S32 c) +{ + return strrchr(str,c); +} + +U32 dStrspn(const char *str, const char *set) +{ + return(strspn(str, set)); +} + +U32 dStrcspn(const char *str, const char *set) +{ + return strcspn(str, set); +} + + +char* dStrstr(char *str1, char *str2) +{ + return strstr(str1,str2); +} + + +const char* dStrstr(const char *str1, const char *str2) +{ + return strstr(str1,str2); +} + +char* dStrtok(char *str, const char *sep) +{ + return strtok(str, sep); +} + + +S32 dAtoi(const char *str) +{ + return atoi(str); +} + +F32 dAtof(const char *str) +{ + return atof(str); +} + +bool dAtob(const char *str) +{ + return !dStricmp(str, "true") || dAtof(str); +} + + +bool dIsalnum(const char c) +{ + return isalnum(c); +} + +bool dIsalpha(const char c) +{ + return(isalpha(c)); +} + +bool dIsspace(const char c) +{ + return(isspace(c)); +} + +bool dIsdigit(const char c) +{ + return(isdigit(c)); +} + +void dPrintf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vprintf(format, args); +} + +S32 dVprintf(const char *format, void *arglist) +{ + S32 len = vprintf(format, (char*)arglist); + return (len); +} + +S32 dSprintf(char *buffer, U32 bufferSize, const char *format, ...) +{ + va_list args; + va_start(args, format); + +#ifdef __MWERKS__ + S32 len = vsnprintf(buffer, bufferSize, format, args); +#else + bufferSize; + S32 len = vsprintf(buffer, format, args); +#endif + return (len); +} + + +S32 dVsprintf(char *buffer, U32 bufferSize, const char *format, void *arglist) +{ +#ifdef __MWERKS__ + S32 len = vsnprintf(buffer, bufferSize, format, (char*)arglist); +#else + bufferSize; + S32 len = vsprintf(buffer, format, (char*)arglist); +#endif +// S32 len = vsnprintf(buffer, bufferSize, format, (char*)arglist); + return (len); +} + + +S32 dSscanf(const char *buffer, const char *format, ...) +{ + va_list args; +#if defined(HAS_VSSCANF) + va_start(args, format); + return __vsscanf(buffer, format, args); +#else + 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; +#endif +} + +S32 dFflushStdout() +{ + return fflush(stdout); +} + +S32 dFflushStderr() +{ + return fflush(stderr); +} + +void dQsort(void *base, U32 nelem, U32 width, S32 (QSORT_CALLBACK *fcmp)(const void *, const void *)) +{ + qsort(base, nelem, width, fcmp); +} diff --git a/platformWin32/winThread.cc b/platformWin32/winThread.cc new file mode 100644 index 0000000..54df61c --- /dev/null +++ b/platformWin32/winThread.cc @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Platform/platformThread.h" +#include "PlatformWin32/platformWin32.h" +#include "Platform/platformSemaphore.h" + +//-------------------------------------------------------------------------- +struct WinThreadData +{ + ThreadRunFunction mRunFunc; + S32 mRunArg; + Thread * mThread; + void * mSemaphore; + + WinThreadData() + { + mRunFunc = 0; + mRunArg = 0; + mThread = 0; + mSemaphore = 0; + }; +}; + +//-------------------------------------------------------------------------- +Thread::Thread(ThreadRunFunction func, S32 arg, bool start_thread) +{ + WinThreadData * threadData = new WinThreadData(); + threadData->mRunFunc = func; + threadData->mRunArg = arg; + threadData->mThread = this; + threadData->mSemaphore = Semaphore::createSemaphore(); + + mData = reinterpret_cast(threadData); + if (start_thread) + start(); +} + +Thread::~Thread() +{ + join(); + + WinThreadData * threadData = reinterpret_cast(mData); + Semaphore::destroySemaphore(threadData->mSemaphore); + delete threadData; +} + +static DWORD WINAPI ThreadRunHandler(void * arg) +{ + WinThreadData * threadData = reinterpret_cast(arg); + + threadData->mThread->run(threadData->mRunArg); + Semaphore::releaseSemaphore(threadData->mSemaphore); + + return(0); +} + +void Thread::start() +{ + if(isAlive()) + return; + + WinThreadData * threadData = reinterpret_cast(mData); + Semaphore::acquireSemaphore(threadData->mSemaphore); + + DWORD threadID; + CreateThread(0, 0, ThreadRunHandler, mData, 0, &threadID); +} + +bool Thread::join() +{ + if(!isAlive()) + return(false); + + WinThreadData * threadData = reinterpret_cast(mData); + return(Semaphore::acquireSemaphore(threadData->mSemaphore)); +} + +void Thread::run(S32 arg) +{ + WinThreadData * threadData = reinterpret_cast(mData); + if(threadData->mRunFunc) + threadData->mRunFunc(arg); +} + +bool Thread::isAlive() +{ + WinThreadData * threadData = reinterpret_cast(mData); + + bool signal = Semaphore::acquireSemaphore(threadData->mSemaphore, false); + if(signal) + Semaphore::releaseSemaphore(threadData->mSemaphore); + return(!signal); +} diff --git a/platformWin32/winTime.cc b/platformWin32/winTime.cc new file mode 100644 index 0000000..132c4ce --- /dev/null +++ b/platformWin32/winTime.cc @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "PlatformWin32/platformWin32.h" +#include "time.h" + +//-------------------------------------- +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; +} + +U32 Platform::getTime() +{ + time_t long_time; + time( &long_time ); + return long_time; +} + +U32 Platform::getRealMilliseconds() +{ + return GetTickCount(); +} + +U32 Platform::getVirtualMilliseconds() +{ + return winState.currentTime; +} + +void Platform::advanceTime(U32 delta) +{ + winState.currentTime += delta; +} + + + + +//------------------------------------------------------------------------------ +//-------------------------------------- Linux Implementation +// +// +// static bool sg_initialized = false; +// static U32 sg_secsOffset = 0; +// +// //-------------------------------------- +// U32 +// Platform::getTickCount() +// { +// // 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); +// } + + diff --git a/platformWin32/winV2Video.cc b/platformWin32/winV2Video.cc new file mode 100644 index 0000000..b1fcee3 --- /dev/null +++ b/platformWin32/winV2Video.cc @@ -0,0 +1,668 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformWIN32/platformGL.h" +#include "platformWIN32/platformWin32.h" +#include "platform/platformAudio.h" +#include "platformWIN32/winV2Video.h" +#include "platform/3dfx.h" +#include "console/console.h" +#include "math/mPoint.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "core/fileStream.h" + + +static U8 sResCode; // Used for initializing the resolution list only + +static void execScript(const char *scriptFile) +{ + // execute the script + FileStream str; + + if (!str.open(scriptFile, FileStream::Read)) + return; + + U32 size = str.getStreamSize(); + char *script = new char[size + 1]; + + str.read(size, script); + str.close(); + + script[size] = 0; + Con::executef(2, "eval", script); + delete[] script; +} + +//------------------------------------------------------------------------------ +Voodoo2Device::Voodoo2Device() +{ + initDevice(); +} + + +//------------------------------------------------------------------------------ +void Voodoo2Device::initDevice() +{ + // Set the device name: + mDeviceName = "Voodoo2"; + + // Set some initial conditions: + mResolutionList.clear(); + mFullScreenOnly = true; + + // Enumerate all available resolutions: + Resolution newRes; + newRes = Resolution( 640, 480, 16 ); + mResolutionList.push_back( newRes ); + + if ( sResCode & 1 ) + { + newRes = Resolution( 800, 600, 16 ); + mResolutionList.push_back( newRes ); + } + + if ( sResCode & 2 ) + { + Con::printf( "SLI detected." ); + + newRes = Resolution( 960, 720, 16 ); + mResolutionList.push_back( newRes ); + + newRes = Resolution( 1024, 768, 16 ); + mResolutionList.push_back( newRes ); + } +} + + +//------------------------------------------------------------------------------ +bool Voodoo2Device::activate( U32 width, U32 height, U32 /*bpp*/, bool /*fullScreen*/ ) +{ + bool needResurrect = false; + + // If the rendering context exists, delete it: + if ( winState.hGLRC ) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + + Con::printf( "Making the rendering context not current..." ); + if ( !qwglMakeCurrent( NULL, NULL ) ) + { + AssertFatal( false, "Voodoo2Device::activate\nqwglMakeCurrent( NULL, NULL ) failed!" ); + return false; + } + + Con::printf( "Deleting the rendering context..." ); + if ( !qwglDeleteContext( winState.hGLRC ) ) + { + AssertFatal( false, "Voodoo2Device::activate\nqwglDeleteContext failed!" ); + return false; + } + winState.hGLRC = NULL; + } + + // If window already exists, kill it so we can start fresh: + if ( winState.appWindow ) + { + Con::printf( "Releasing the device context..." ); + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + + Con::printf( "Destroying the window..." ); + DestroyWindow( winState.appWindow ); + winState.appWindow = NULL; + } + + // If OpenGL library already loaded, shut it down and reload the 3Dfx standalone driver: + if ( winState.hinstOpenGL ) + QGL_Shutdown(); + + QGL_Init( "3dfxvgl", "glu32" ); + + // This device only supports full-screen, so force it: + smIsFullScreen = true; + Con::setVariable( "$pref::Video::fullScreen", "true" ); + + // Create the new window: + Con::printf( "Creating a new full-screen window..." ); + winState.appWindow = CreateOpenGLWindow( width, height, true ); + if ( !winState.appWindow ) + { + AssertFatal( false, "Voodoo2Device::activate\nFailed to create a window!" ); + return false; + } + + // Get a device context from the new window: + HDC tempDC = GetDC( winState.appWindow ); + if ( !tempDC ) + { + AssertFatal( false, "Voodoo2Device::activate\nFailed to get a device context!" ); + return false; + } + + // Set the pixel format of the new window: + PIXELFORMATDESCRIPTOR pfd; + CreatePixelFormat( &pfd, 16, 16, 8, false ); + S32 chosenFormat = ChooseBestPixelFormat( tempDC, &pfd ); + qwglDescribePixelFormat( tempDC, chosenFormat, sizeof( pfd ), &pfd ); + bool test = qwglSetPixelFormat( tempDC, chosenFormat, &pfd ); + ReleaseDC( winState.appWindow, tempDC ); + if ( !test ) + { + AssertFatal( false, "Voodoo2Device::activate\nFailed to set the pixel format of the device context!" ); + return false; + } + Con::printf( "Pixel format set:" ); + Con::printf( " %d color bits, %d depth bits, %d stencil bits", pfd.cColorBits, pfd.cDepthBits, pfd.cStencilBits ); + + // Set the resolution: + smCurrentRes.bpp = 16; // Force 16-bit color. + if ( !setScreenMode( width, height, 16, true, true, false ) ) + 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 ); + + // Set a few variables: + if ( Con::getIntVariable( "$pref::OpenGL::mipReduction" ) < 1 ) + Con::setIntVariable( "$pref::OpenGL::mipReduction", 1 ); + if ( Con::getIntVariable( "$pref::OpenGL::interiorMipReduction" ) < 1 ) + Con::setIntVariable( "$pref::OpenGL::interiorMipReduction", 1 ); + if ( Con::getIntVariable( "$pref::OpenGL::skyMipReduction" ) < 1 ) + Con::setIntVariable( "$pref::OpenGL::skyMipReduction", 1 ); + + if (dStrcmp(vendorString, Con::getVariable("$pref::Video::profiledVendor")) || + dStrcmp(rendererString, Con::getVariable("$pref::Video::profiledRenderer"))) + { + // Voodoo2 defaults + Con::setBoolVariable("$pref::OpenGL::disableEXTCompiledVertexArray", false); + Con::setBoolVariable("$pref::OpenGL::disableSubImage", false); + Con::setBoolVariable("$pref::OpenGL::noEnvColor", true); + Con::setBoolVariable("$pref::OpenGL::disableARBTextureCompression", false); + Con::setBoolVariable("$pref::Interior::lockArrays", true); + Con::setBoolVariable("$pref::TS::skipFirstFog", false); + Con::setBoolVariable("$pref::OpenGL::noDrawArraysAlpha", true); + + //Con::executef(2, "exec", "scripts/V2Profile.cs"); + execScript("V2Profile.cs"); + + Con::setVariable("$pref::Video::profiledVendor", vendorString); + Con::setVariable("$pref::Video::profiledRenderer", rendererString); + + // write out prefs + gEvalState.globalVars.exportVariables("$pref::*", "prefs/ClientPrefs.cs", false); + } + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + + // Set the new window to the foreground: + ShowWindow( winState.appWindow, SW_SHOW ); + SetForegroundWindow( winState.appWindow ); + SetFocus( winState.appWindow ); + + QGL_EXT_Init(); + + Con::setBoolVariable( "$SwapIntervalSupported", false ); + Con::setVariable( "$pref::Video::displayDevice", mDeviceName ); + Con::setBoolVariable("$pref::OpenGL::allowTexGen", true); + + return true; +} + + +//------------------------------------------------------------------------------ +void Voodoo2Device::shutdown() +{ + if ( winState.hGLRC ) + { + qwglMakeCurrent( NULL, NULL ); + qwglDeleteContext( winState.hGLRC ); + winState.hGLRC = NULL; + } + + if ( winState.appDC ) + { + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + } + + ChangeDisplaySettings( NULL, 0 ); +} + + +//------------------------------------------------------------------------------ +bool Voodoo2Device::setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen, bool forceIt, bool repaint ) +{ + HWND curtain = NULL; + bool needResurrect = false; + Resolution newRes( width, height, bpp ); + + if ( !fullScreen ) + { + // The 3Dfx Voodoo2 OpenGL driver only runs in full-screen, so ignore: + Con::warnf( ConsoleLogEntry::General, "Sorry, the Voodoo 2 display device only supports full-screen!" ); + } + + // Force the res to be one of the ones in the supported list: + U32 resIndex = 0; + U32 bestScore = 0, thisScore = 0; + for ( U32 i = 0; i < mResolutionList.size(); i++ ) + { + if ( newRes == mResolutionList[i] ) + { + resIndex = i; + break; + } + else + { + thisScore = abs( S32( newRes.w ) - S32( mResolutionList[i].w ) ) + abs( S32( newRes.h ) - S32( mResolutionList[i].h ) ); + if ( !bestScore || thisScore < bestScore ) + { + bestScore = thisScore; + resIndex = i; + } + } + } + newRes = mResolutionList[resIndex]; + + // Return if we aren't forcing it and we are already in the desired resolution: + if ( !forceIt && newRes == smCurrentRes ) + return true; + + Con::printf( "Setting screen mode to %dx%dx16 (fs)...", newRes.w, newRes.h ); + + // Delete the rendering context: + if ( winState.hGLRC ) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + + Con::printf( "Making the rendering context not current..." ); + if ( !qwglMakeCurrent( NULL, NULL ) ) + { + AssertFatal( false, "Voodoo2Device::setScreenMode\nqwglMakeCurrent( NULL, NULL ) failed!" ); + return false; + } + + Con::printf( "Deleting the rendering context..." ); + if ( !qwglDeleteContext( winState.hGLRC ) ) + { + AssertFatal( false, "Voodoo2Device::setScreenMode\nqwglDeleteContext failed!" ); + return false; + } + winState.hGLRC = NULL; + } + + // Release the device context: + if ( winState.appDC ) + { + Con::printf( "Releasing the device context..." ); + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + } + + // Change the display settings (shouldn't really be necessary, but is): + U32 test; + DEVMODE devMode; + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmPelsWidth = newRes.w; + devMode.dmPelsHeight = newRes.h; + devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; + + Con::printf( "Changing the display settings to %dx%dx16...", newRes.w, newRes.h ); + curtain = CreateCurtain( newRes.w, newRes.h ); + test = ChangeDisplaySettings( &devMode, CDS_FULLSCREEN ); + if ( test != DISP_CHANGE_SUCCESSFUL ) + { + ChangeDisplaySettings( NULL, 0 ); + switch( test ) + { + case DISP_CHANGE_RESTART: + Platform::AlertOK( "Display Change Failed", "You must restart your machine to get the specified mode." ); + break; + + case DISP_CHANGE_BADMODE: + Platform::AlertOK( "Display Change Failed", "The specified mode is not supported by this device." ); + break; + + default: + Platform::AlertOK( "Display Change Failed", "Hardware failed to switch to the specified mode." ); + break; + }; + return false; + } + + // Resize the window: + if ( !SetWindowPos( winState.appWindow, NULL, 0, 0, newRes.w, newRes.h, SWP_NOZORDER | SWP_NOMOVE ) ) + { + Con::printf( "Voodoo2Device::setScreenMode - SetWindowPos sizing to %dx%d failed.", newRes.w, newRes.h ); + return false; + } + + // Get a new device context: + Con::printf( "Acquiring a new device context..." ); + winState.appDC = GetDC( winState.appWindow ); + if ( !winState.appDC ) + { + AssertFatal( false, "Voodoo2Device::setScreenMode\nFailed to get a valid device context!" ); + return false; + } + + // Create a new rendering context: + Con::printf( "Creating a new rendering context..." ); + winState.hGLRC = qwglCreateContext( winState.appDC ); + if ( !winState.hGLRC ) + { + AssertFatal( false, "Voodoo2Device::setScreenMode\nFailed to create a GL rendering context!" ); + return false; + } + + // Make the new rendering context current: + Con::printf( "Making the new rendering context current..." ); + if ( !qwglMakeCurrent( winState.appDC, winState.hGLRC ) ) + { + AssertFatal( false, "Voodoo2Device::setScreenMode\nFailed to make the rendering context current!" ); + return false; + } + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + + smCurrentRes = newRes; + Platform::setWindowSize( newRes.w, newRes.h ); + char tempBuf[15]; + dSprintf( tempBuf, sizeof( tempBuf ), "%d %d %d", smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp ); + Con::setVariable( "$pref::Video::resolution", tempBuf ); + + if ( curtain ) + DestroyWindow( curtain ); + + if ( repaint ) + Con::evaluate( "resetCanvas();" ); + + return true; +} + + +//------------------------------------------------------------------------------ +void Voodoo2Device::swapBuffers() +{ + qwglSwapBuffers( winState.appDC ); +} + + +//------------------------------------------------------------------------------ +const char* Voodoo2Device::getDriverInfo() +{ + // 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 ); + const char* extensionsString = (const char*) glGetString( GL_EXTENSIONS ); + + // TODO - move strings out of code and into script... + 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 Voodoo2Device::getGammaCorrection(F32 &g) +{ + // apparently the qwlGetDeviceGammaRamp3DFX doesn't actually work. So, this + // is a good default. We don't need to worry about restoring the original + // gamma. Anything we do is wiped out once we destroy the Voodoo2 window. + g = 0.8; + + return true; + + U16 ramp[256*3]; + + if (!qwglGetDeviceGammaRamp3DFX(winState.appDC, ramp)) + return false; + + F32 csum = 0.0; + U32 ccount = 0; + + for (U16 i = 0; i < 256; ++i) + { + if (i != 0 && ramp[i] != 0 && ramp[i] != 65535) + { + F64 b = (F64) i/256.0; + F64 a = (F64) ramp[i]/65535.0; + F32 c = (F32) (mLog(a)/mLog(b)); + + csum += c; + ++ccount; + } + } + g = csum/ccount; + + return true; +} + + +//------------------------------------------------------------------------------ +bool Voodoo2Device::setGammaCorrection(F32 g) +{ + U16 ramp[256*3]; + + for (U16 i = 0; i < 256; ++i) + ramp[i] = mPow((F32) i/256.0f, g) * 65535.0f; + dMemcpy(&ramp[256],ramp,256*sizeof(U16)); + dMemcpy(&ramp[512],ramp,256*sizeof(U16)); + + return qwglSetDeviceGammaRamp3DFX(winState.appDC, ramp); +} + +//------------------------------------------------------------------------------ +bool Voodoo2Device::setVerticalSync( bool on ) +{ + on; + return( false ); +} + +//------------------------------------------------------------------------------ +// Stuff for Voodoo 2 detection: +#ifndef GR_HARDWARE +#define GR_HARDWARE 0xa1 +#endif + +#define GLIDE_NUM_TMU 2 +#define MAX_NUM_SST 4 + +typedef S32 GrSstType; +#define GR_SSTTYPE_VOODOO 0 +#define GR_SSTTYPE_SST96 1 +#define GR_SSTTYPE_AT3D 2 + +typedef struct GrTMUConfig_St +{ + S32 tmuRev; /* Rev of Texelfx chip */ + S32 tmuRam; /* 1, 2, or 4 MB */ +} GrTMUConfig_t; + +typedef struct GrVoodooConfig_St +{ + S32 fbRam; /* 1, 2, or 4 MB */ + S32 fbiRev; /* Rev of Pixelfx chip */ + S32 nTexelfx; /* How many texelFX chips are there? */ + FxBool sliDetect; /* Is it a scan-line interleaved board? */ + GrTMUConfig_t tmuConfig[GLIDE_NUM_TMU]; /* Configuration of the Texelfx chips */ +} GrVoodooConfig_t; + +typedef struct GrSst96Config_St +{ + S32 fbRam; /* How much? */ + S32 nTexelfx; + GrTMUConfig_t tmuConfig; +} GrSst96Config_t; + +typedef struct GrAT3DConfig_St +{ + S32 rev; +} GrAT3DConfig_t; + + +typedef struct +{ + S32 num_sst; /* # of HW units in the system */ + struct + { + GrSstType type; /* Which hardware is it? */ + union SstBoard_u + { + GrVoodooConfig_t VoodooConfig; + GrSst96Config_t SST96Config; + GrAT3DConfig_t AT3DConfig; + } sstBoard; + } SSTs[MAX_NUM_SST]; /* configuration for each board */ +} GrHwConfiguration; + +typedef FxBool( FX_CALL *grSstQueryBoards_fpt )( GrHwConfiguration *hwconfig ); +typedef FxBool( FX_CALL *grSstQueryHardware_fpt )( GrHwConfiguration *hwconfig ); + +typedef void ( FX_CALL *grGlideInit_fpt )( void ); +typedef const char* ( FX_CALL *grGetString_fpt )( FxU32 pName ); +typedef void( FX_CALL *grGlideShutdown_fpt )( void ); + + +//------------------------------------------------------------------------------ +DisplayDevice* Voodoo2Device::create() +{ + // Detect the Voodoo2 OpenGL driver: + GrHwConfiguration hwConfig; + bool result = true; + + // Reset the available resolution specifier code: + sResCode = 0; + + grGlideInit_fpt grGlideInit = NULL; + grGlideShutdown_fpt grGlideShutdown = NULL; + grSstQueryBoards_fpt grSstQueryBoards = NULL; + grSstQueryHardware_fpt grSstQueryHardware = NULL; + grGetString_fpt grGetString = NULL; + + // Let the Glide 2 stuff go first: + HINSTANCE glide2DLL = LoadLibrary( "glide2x" ); + + if ( !glide2DLL ) + return NULL; + + grSstQueryBoards = (grSstQueryBoards_fpt) GetProcAddress( glide2DLL, "_grSstQueryBoards@4" ); + + if ( grSstQueryBoards ) + { + grSstQueryBoards( &hwConfig ); + if ( hwConfig.num_sst == 0 ) + result = false; + else + { + grGlideInit = (grGlideInit_fpt) GetProcAddress( glide2DLL, "_grGlideInit@0" ); + grSstQueryHardware = (grSstQueryHardware_fpt) GetProcAddress( glide2DLL, "_grSstQueryHardware@4" ); + grGlideShutdown = (grGlideShutdown_fpt) GetProcAddress( glide2DLL, "_grGlideShutdown@0" ); + + if ( grGlideInit && grSstQueryHardware && grGlideShutdown ) + { + grGlideInit(); + grSstQueryHardware( &hwConfig ); + + // Find out what resolutions are available: + if ( hwConfig.SSTs[0].sstBoard.VoodooConfig.sliDetect == 0 ) // SLI not detected. + { + if ( hwConfig.SSTs[0].sstBoard.VoodooConfig.fbRam >= 4 ) + sResCode = 1; // low bit indicates 800x600 + } + else // SLI detected. + { + sResCode = 1; + + if ( hwConfig.SSTs[0].sstBoard.VoodooConfig.fbRam >= 4 ) + sResCode += 2; + } + + grGlideShutdown(); + } + + grGlideInit = NULL; + grGlideShutdown = NULL; + } + } + else + result = false; + + FreeLibrary( glide2DLL ); + + if ( result ) + { + // Glide 3's turn: + HINSTANCE glide3DLL = LoadLibrary( "glide3x" ); + + if ( !glide3DLL ) + return NULL; + + grGlideInit = (grGlideInit_fpt) GetProcAddress( glide3DLL, "_grGlideInit@0" ); + grGetString = (grGetString_fpt) GetProcAddress( glide3DLL, "_grGetString@4" ); + grGlideShutdown = (grGlideShutdown_fpt) GetProcAddress( glide3DLL, "_grGlideShutdown@0" ); + + if ( grGlideInit && grGetString && grGlideShutdown ) + { + grGlideInit(); + + const char* hardware = grGetString( GR_HARDWARE ); + if ( dStrcmp( hardware, "Voodoo2" ) != 0 ) + result = false; + + grGlideShutdown(); + } + else + result = false; + + FreeLibrary( glide3DLL ); + } + + if ( result ) + return new Voodoo2Device(); + else + return NULL; +} diff --git a/platformWin32/winV2Video.h b/platformWin32/winV2Video.h new file mode 100644 index 0000000..b45d621 --- /dev/null +++ b/platformWin32/winV2Video.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WINV2VIDEO_H_ +#define _WINV2VIDEO_H_ + +#ifndef _PLATFORMVIDEO_H_ +#include "Platform/platformVideo.h" +#endif + +class Voodoo2Device : public DisplayDevice +{ + public: + Voodoo2Device(); + + 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 ); + + static DisplayDevice* create(); +}; + + +#endif // _H_WINV2VIDEO diff --git a/platformWin32/winWindow.cc b/platformWin32/winWindow.cc new file mode 100644 index 0000000..5debf0e --- /dev/null +++ b/platformWin32/winWindow.cc @@ -0,0 +1,1385 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformWIN32/platformWin32.h" +#include "platform/platform.h" +#include "platformWIN32/platformGL.h" +#include "platform/platformVideo.h" +#include "platformWIN32/winOGLVideo.h" +#include "platformWIN32/winD3DVideo.h" +#include "platformWIN32/winV2Video.h" +#include "platform/event.h" +#include "console/console.h" +#include "platformWIN32/winConsole.h" +#include "platformWIN32/winDirectInput.h" +#include "platform/gameInterface.h" +#include "math/mRandom.h" +#include "core/fileStream.h" +#include "game/resource.h" + +//-------------------------------------- Resource Includes +#include "dgl/gBitmap.h" +#include "dgl/gFont.h" + +extern void createFontInit(); +extern void createFontShutdown(); +extern void installRedBookDevices(); +extern void handleRedBookCallback(U32, U32); + +static const char *windowClassName = "Darkstar Window Class"; +static char windowName[256] = "Darkstar Window"; +static bool gWindowCreated = false; + +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 U16 DIK_to_Key( U8 dikCode ); + +Win32PlatState winState; + +//-------------------------------------- +Win32PlatState::Win32PlatState() +{ + log_fp = NULL; + hinstOpenGL = NULL; + hinstGLU = NULL; + hinstOpenAL = NULL; + appWindow = NULL; + appDC = NULL; + appInstance = NULL; + currentTime = 0; + processId = 0; +} + +static bool windowLocked = false; + +static BYTE keyboardState[256]; +static bool mouseButtonState[3]; +static bool capsLockDown = false; +static S32 modifierKeys = 0; +static bool windowActive = true; +static Point2I lastCursorPos(0,0); +static Point2I windowSize; +static HANDLE gMutexHandle = NULL; +static bool sgDoubleByteEnabled = false; + +//-------------------------------------- +static const char *getMessageName(S32 msg) +{ + switch(msg) + { + case WM_KEYDOWN: + return "WM_KEYDOWN"; + case WM_KEYUP: + return "WM_KEYUP"; + case WM_SYSKEYUP: + return "WM_SYSKEYUP"; + case WM_SYSKEYDOWN: + return "WM_SYSKEYDOWN"; + default: + return "Unknown!!"; + } +} + +//-------------------------------------- +bool Platform::excludeOtherInstances(const char *mutexName) +{ + gMutexHandle = CreateMutex(NULL, true, mutexName); + if(!gMutexHandle) + return false; + if(GetLastError() == ERROR_ALREADY_EXISTS) + { + CloseHandle(gMutexHandle); + gMutexHandle = NULL; + return false; + } + return true; +} + +//-------------------------------------- +void Platform::AlertOK(const char *windowTitle, const char *message) +{ + MessageBox(NULL, message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OK); +} + +//-------------------------------------- +bool Platform::AlertOKCancel(const char *windowTitle, const char *message) +{ + return MessageBox(NULL, message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OKCANCEL) == IDOK; +} + +//-------------------------------------- +bool Platform::AlertRetry(const char *windowTitle, const char *message) +{ + return (MessageBox(NULL, message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_RETRYCANCEL) == IDRETRY); +} + +//-------------------------------------- +static void InitInput() +{ + dMemset( keyboardState, 0, 256 ); + dMemset( mouseButtonState, 0, sizeof( mouseButtonState ) ); + capsLockDown = (GetKeyState(VK_CAPITAL) & 0x01); + if (capsLockDown) + { + keyboardState[VK_CAPITAL] |= 0x01; + } +} + +//-------------------------------------- +static void setMouseClipping() +{ + ClipCursor(NULL); + if(windowActive) + { + ShowCursor(false); + + RECT r; + GetWindowRect(winState.appWindow, &r); + + if(windowLocked) + { + POINT p; + GetCursorPos(&p); + lastCursorPos.set(p.x - r.left, p.y - r.top); + + ClipCursor(&r); + + S32 centerX = (r.right + r.left) >> 1; + S32 centerY = (r.bottom + r.top) >> 1; + SetCursorPos(centerX, centerY); + } + else + SetCursorPos(lastCursorPos.x + r.left, lastCursorPos.y + r.top); + } + else + ShowCursor(true); +} + +//-------------------------------------- +static bool sgTaskbarHidden = false; +static HWND sgTaskbar = NULL; + +static void hideTheTaskbar() +{ +// if ( !sgTaskbarHidden ) +// { +// sgTaskbar = FindWindow( "Shell_TrayWnd", NULL ); +// if ( sgTaskbar ) +// { +// APPBARDATA abData; +// dMemset( &abData, 0, sizeof( abData ) ); +// abData.cbSize = sizeof( abData ); +// abData.hWnd = sgTaskbar; +// SHAppBarMessage( ABM_REMOVE, &abData ); +// //ShowWindow( sgTaskbar, SW_HIDE ); +// sgTaskbarHidden = true; +// } +// } +} + +static void restoreTheTaskbar() +{ +// if ( sgTaskbarHidden ) +// { +// APPBARDATA abData; +// dMemset( &abData, 0, sizeof( abData ) ); +// abData.cbSize = sizeof( abData ); +// abData.hWnd = sgTaskbar; +// SHAppBarMessage( ABM_ACTIVATE, &abData ); +// //ShowWindow( sgTaskbar, SW_SHOW ); +// sgTaskbarHidden = false; +// } +} + +//-------------------------------------- +void Platform::enableKeyboardTranslation(void) +{ +} + +//-------------------------------------- +void Platform::disableKeyboardTranslation(void) +{ +} + +//-------------------------------------- +void Platform::setWindowLocked(bool locked) +{ + windowLocked = locked; + setMouseClipping(); +} + +//-------------------------------------- +void Platform::minimizeWindow() +{ + ShowWindow(winState.appWindow, SW_MINIMIZE); + restoreTheTaskbar(); +} + +//-------------------------------------- +static void processKeyMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + S32 repeatCount = lParam & 0xffff; + S32 scanCode = (lParam >> 16) & 0xff; + S32 nVirtkey = dIsStandardVK(wParam) ? TranslateOSKeyCode(wParam) : DIK_to_Key(scanCode); + S32 keyCode; + if ( wParam == VK_PROCESSKEY && sgDoubleByteEnabled ) + keyCode = MapVirtualKey( scanCode, 1 ); // This is the REAL virtual key... + else + keyCode = wParam; + + bool extended = (lParam >> 24) & 1; + bool repeat = (lParam >> 30) & 1; + bool make = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN); + + S32 newVirtKey = nVirtkey; + switch(nVirtkey) + { + case KEY_ALT: + newVirtKey = extended ? KEY_RALT : KEY_LALT; + break; + case KEY_CONTROL: + newVirtKey = extended ? KEY_RCONTROL : KEY_LCONTROL; + break; + case KEY_SHIFT: + newVirtKey = ( scanCode == 54 ) ? KEY_RSHIFT : KEY_LSHIFT; + break; + case KEY_RETURN: + if ( extended ) + newVirtKey = KEY_NUMPADENTER; + break; + } + + S32 modKey = modifierKeys; + + if(make) + { + switch (newVirtKey) + { + case KEY_LSHIFT: modifierKeys |= SI_LSHIFT; modKey = 0; break; + case KEY_RSHIFT: modifierKeys |= SI_RSHIFT; modKey = 0; break; + case KEY_LCONTROL: modifierKeys |= SI_LCTRL; modKey = 0; break; + case KEY_RCONTROL: modifierKeys |= SI_RCTRL; modKey = 0; break; + case KEY_LALT: modifierKeys |= SI_LALT; modKey = 0; break; + case KEY_RALT: modifierKeys |= SI_RALT; modKey = 0; break; + } + if(nVirtkey == KEY_CAPSLOCK) + { + capsLockDown = !capsLockDown; + if(capsLockDown) + keyboardState[keyCode] |= 0x01; + else + keyboardState[keyCode] &= 0xFE; + } + keyboardState[keyCode] |= 0x80; + } + else + { + switch (newVirtKey) + { + case KEY_LSHIFT: modifierKeys &= ~SI_LSHIFT; modKey = 0; break; + case KEY_RSHIFT: modifierKeys &= ~SI_RSHIFT; modKey = 0; break; + case KEY_LCONTROL: modifierKeys &= ~SI_LCTRL; modKey = 0; break; + case KEY_RCONTROL: modifierKeys &= ~SI_RCTRL; modKey = 0; break; + case KEY_LALT: modifierKeys &= ~SI_LALT; modKey = 0; break; + case KEY_RALT: modifierKeys &= ~SI_RALT; modKey = 0; break; + } + keyboardState[keyCode] &= 0x7f; + } + + U16 ascii[3]; + WORD asciiCode = 0; + dMemset( &ascii, 0, sizeof( ascii ) ); + + S32 res = ToAscii( keyCode, scanCode, keyboardState, ascii, 0 ); + if (res == 2) + { + asciiCode = ascii[1]; + } + else if ((res == 1) || (res < 0)) + { + asciiCode = ascii[0]; + } + + InputEvent event; + + event.deviceInst = 0; + event.deviceType = KeyboardDeviceType; + event.objType = SI_KEY; + event.objInst = newVirtKey; + event.action = make ? (repeat ? SI_REPEAT : SI_MAKE ) : SI_BREAK; + event.modifier = modKey; + event.ascii = asciiCode; + event.fValue = make ? 1.0 : 0.0; + +#ifdef LOG_INPUT + char keyName[25]; + GetKeyNameText( lParam, keyName, 24 ); + if ( event.action == SI_MAKE ) + Input::log( "EVENT (Win): %s key pressed (Repeat count: %d). MODS:%c%c%c\n", keyName, repeatCount, + ( modKey & SI_SHIFT ? 'S' : '.' ), + ( modKey & SI_CTRL ? 'C' : '.' ), + ( modKey & SI_ALT ? 'A' : '.' ) ); + else + Input::log( "EVENT (Win): %s key released.\n", keyName ); +#endif + + Game->postEvent(event); +} + +// static void processCharMessage( WPARAM wParam, LPARAM lParam ) +// { +// TCHAR charCode = wParam; +// if ( charCode ) +// { +// S32 repeatCount = lParam & 0xFFFF; +// InputEvent event; +// event.deviceInst = 0; +// event.deviceType = KeyboardDeviceType; +// event.action = SI_CHAR; +// event.modifier = modifierKeys; +// event.ascii = charCode; +// +// for ( U32 i = 0; i < repeatCount; i++ ) +// { +// Game->postEvent( event ); +// } +// } +// } + +static S32 mouseX = 0xFFFFFFFF; +static S32 mouseY = 0xFFFFFFFF; + +//-------------------------------------- +static void CheckCursorPos() +{ + if(windowLocked && windowActive) + { + POINT mousePos; + GetCursorPos(&mousePos); + RECT r; + + GetWindowRect(winState.appWindow, &r); + + S32 centerX = (r.right + r.left) >> 1; + S32 centerY = (r.bottom + r.top) >> 1; + + if(mousePos.x != centerX) + { + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_XAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = (mousePos.x - centerX); + Game->postEvent(event); + } + if(mousePos.y != centerY) + { + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_YAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = (mousePos.y - centerY); + Game->postEvent(event); + } + SetCursorPos(centerX, centerY); + } +} + +//-------------------------------------- +static void mouseButtonEvent(S32 action, S32 objInst) +{ + CheckCursorPos(); + if(!windowLocked) + { + if(action == SI_MAKE) + SetCapture(winState.appWindow); + else + ReleaseCapture(); + } + + U32 buttonId = objInst - KEY_BUTTON0; + if ( buttonId < 3 ) + mouseButtonState[buttonId] = ( action == SI_MAKE ) ? true : false; + + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_BUTTON; + event.objInst = objInst; + event.action = action; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = action == SI_MAKE ? 1.0 : 0.0; + +#ifdef LOG_INPUT + if ( action == SI_MAKE ) + Input::log( "EVENT (Win): mouse button%d pressed. MODS:%c%c%c\n", buttonId, ( modifierKeys & SI_SHIFT ? 'S' : '.' ), ( modifierKeys & SI_CTRL ? 'C' : '.' ), ( modifierKeys & SI_ALT ? 'A' : '.' ) ); + else + Input::log( "EVENT (Win): mouse button%d released.\n", buttonId ); +#endif + Game->postEvent(event); +} + +//-------------------------------------- +static void mouseWheelEvent( S32 delta ) +{ + static S32 _delta = 0; + + _delta += delta; + if ( abs( delta ) >= WHEEL_DELTA ) + { + _delta = 0; + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_ZAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = delta; + +#ifdef LOG_INPUT + Input::log( "EVENT (Win): mouse wheel moved. delta = %d\n", delta ); +#endif + + Game->postEvent( event ); + } +} + + +struct WinMessage +{ + UINT message; + WPARAM wParam; + LPARAM lParam; + + WinMessage(UINT m, WPARAM w, LPARAM l) : message(m), wParam(w), lParam(l) {} +}; + +Vector sgWinMessages; + +//-------------------------------------- +static LRESULT PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch ( message ) + { + case WM_POWERBROADCAST: + { + if(wParam == PBT_APMQUERYSUSPEND) + return BROADCAST_QUERY_DENY; + return true; + } + case WM_ACTIVATEAPP: + if ((bool) wParam) + { + Video::reactivate(); + ShowCursor(false); + if ( Video::isFullScreen() ) + hideTheTaskbar(); + // HACK: Windows 98 (after switching from fullscreen to windowed mode) + SetForegroundWindow( winState.appWindow ); + } + else + { + Video::deactivate(); + restoreTheTaskbar(); + } + break; + case WM_SYSCOMMAND: + switch(wParam) + { + case SC_SCREENSAVE: + case SC_MONITORPOWER: + if(GetForegroundWindow() == winState.appWindow) + { + return 0; + } + break; + } + break; + case WM_ACTIVATE: + windowActive = LOWORD(wParam) != WA_INACTIVE; + if ( windowActive ) + { + Game->refreshWindow(); + Input::activate(); + } + else + { + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isMouseActive() ) + { + // Deactivate all the mouse triggers: + for ( U32 i = 0; i < 3; i++ ) + { + if ( mouseButtonState[i] ) + mouseButtonEvent( SI_BREAK, KEY_BUTTON0 + i ); + } + } + Input::deactivate(); + } + setMouseClipping(); + break; + + case WM_MOVE: + Game->refreshWindow(); + break; + + case MM_MCINOTIFY: + handleRedBookCallback(wParam, lParam); + break; + + //case WM_DESTROY: + case WM_CLOSE: + PostQuitMessage(0); + break; + default: + { + if (sgQueueEvents) + { + WinMessage msg(message,wParam,lParam); + + sgWinMessages.push_front(msg); + } + } + } + + return DefWindowProc(hWnd, message, wParam, lParam); +} + +//-------------------------------------- +static void OurDispatchMessages() +{ + WinMessage msg(0,0,0); + UINT message; + WPARAM wParam; + LPARAM lParam; + + DInputManager* mgr = dynamic_cast( Input::getManager() ); + + while (sgWinMessages.size()) + { + msg = sgWinMessages.front(); + sgWinMessages.pop_front(); + message = msg.message; + wParam = msg.wParam; + lParam = msg.lParam; + + if ( !mgr || !mgr->isMouseActive() ) + { + switch ( message ) + { + case WM_MOUSEMOVE: + if ( !windowLocked ) + { + MouseMoveEvent event; + + event.xPos = LOWORD(lParam); // horizontal position of cursor + event.yPos = HIWORD(lParam); // vertical position of cursor + event.modifier = modifierKeys; + +#ifdef LOG_INPUT +#ifdef LOG_MOUSEMOVE + Input::log( "EVENT (Win): mouse move to (%d, %d).\n", event.xPos, event.yPos ); +#endif +#endif + Game->postEvent(event); + } + break; + case WM_LBUTTONDOWN: + mouseButtonEvent(SI_MAKE, KEY_BUTTON0); + break; + case WM_MBUTTONDOWN: + mouseButtonEvent(SI_MAKE, KEY_BUTTON2); + break; + case WM_RBUTTONDOWN: + mouseButtonEvent(SI_MAKE, KEY_BUTTON1); + break; + case WM_LBUTTONUP: + mouseButtonEvent(SI_BREAK, KEY_BUTTON0); + break; + case WM_MBUTTONUP: + mouseButtonEvent(SI_BREAK, KEY_BUTTON2); + break; + case WM_RBUTTONUP: + mouseButtonEvent(SI_BREAK, KEY_BUTTON1); + break; + case WM_MOUSEWHEEL: + mouseWheelEvent( (S16) HIWORD( wParam ) ); + break; + +// case WM_CHAR: +// processCharMessage( wParam, lParam ); +// break; + } + } + + if ( !mgr || !mgr->isKeyboardActive() ) + { + switch ( message ) + { + case WM_KEYUP: + case WM_SYSKEYUP: + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + processKeyMessage(message, wParam, lParam); + break; + } + } + } +} + +//-------------------------------------- +static bool ProcessMessages() +{ + MSG msg; + + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + if(msg.message == WM_QUIT) + return false; + + TranslateMessage(&msg); + DispatchMessage(&msg); + OurDispatchMessages(); + } + + return true; +} + +//-------------------------------------- +void Platform::process() +{ + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isMouseActive() ) + CheckCursorPos(); + WindowsConsole->process(); + + if(!ProcessMessages()) + { + // generate a quit event + Event quitEvent; + quitEvent.type = QuitEventType; + + Game->postEvent(quitEvent); + } + // if there's no window, we sleep 1, otherwise we sleep 0 + if(!Game->isJournalReading()) + Sleep(gWindowCreated ? 0 : 1); // give others some process time if necessary... + HWND window = GetForegroundWindow(); + if (window && gWindowCreated) + { + // check to see if we are in the foreground or not + // if not Sleep for 100ms or until a Win32 message/input is recieved + DWORD foregroundProcessId; + GetWindowThreadProcessId(window, &foregroundProcessId); + if (foregroundProcessId != winState.processId) + MsgWaitForMultipleObjects(0, NULL, false, 100, QS_ALLINPUT); + } + + Input::process(); +} + +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 + +//-------------------------------------- +static void InitWindowClass() +{ + WNDCLASS wc; + dMemset(&wc, 0, sizeof(wc)); + + wc.style = CS_OWNDC; + wc.lpfnWndProc = WindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = winState.appInstance; + wc.hIcon = LoadIcon(winState.appInstance, MAKEINTRESOURCE(IDI_ICON2)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); + wc.lpszMenuName = 0; + wc.lpszClassName = windowClassName; + RegisterClass( &wc ); + + // Curtain window class: + wc.lpfnWndProc = DefWindowProc; + wc.hCursor = NULL; + wc.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH); + wc.lpszClassName = "Curtain"; + RegisterClass( &wc ); +} + +//-------------------------------------- +static void GetDesktopState() +{ + HWND hDesktop = GetDesktopWindow(); + HDC hDeskDC = GetDC( hDesktop ); + winState.desktopBitsPixel = GetDeviceCaps( hDeskDC, BITSPIXEL ); + winState.desktopWidth = GetDeviceCaps( hDeskDC, HORZRES ); + winState.desktopHeight = GetDeviceCaps( hDeskDC, VERTRES ); + ReleaseDC( hDesktop, hDeskDC ); +} + +//-------------------------------------- +HWND CreateOpenGLWindow( U32 width, U32 height, bool fullScreen ) +{ + S32 windowStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + S32 exWindowStyle = 0; + + if ( fullScreen ) + windowStyle |= ( WS_POPUP | WS_MAXIMIZE ); + else + windowStyle |= ( WS_OVERLAPPED | WS_CAPTION ); + + return CreateWindowEx( + exWindowStyle, + windowClassName, + windowName, + windowStyle, + 0, 0, width, height, + NULL, NULL, + winState.appInstance, + NULL); +} + +//-------------------------------------- +HWND CreateCurtain( U32 width, U32 height ) +{ + return CreateWindow( + "Curtain", + "", + ( WS_POPUP | WS_MAXIMIZE | WS_VISIBLE ), + 0, 0, + width, height, + NULL, NULL, + winState.appInstance, + NULL ); +} + +//-------------------------------------- +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; +} + +//-------------------------------------- +enum { MAX_PFDS = 256 }; +S32 ChooseBestPixelFormat(HDC hDC, PIXELFORMATDESCRIPTOR *pPFD) +{ + PIXELFORMATDESCRIPTOR pfds[MAX_PFDS+1]; + S32 i; + S32 bestMatch = 0; + + S32 maxPFD = qwglDescribePixelFormat(hDC, 1, sizeof(PIXELFORMATDESCRIPTOR), &pfds[0]); + if(maxPFD > MAX_PFDS) + maxPFD = MAX_PFDS; + + bool accelerated = false; + + for(i = 1; i <= maxPFD; i++) + { + qwglDescribePixelFormat(hDC, i, sizeof(PIXELFORMATDESCRIPTOR), &pfds[i]); + + // make sure this has hardware acceleration: + if ( ( pfds[i].dwFlags & PFD_GENERIC_FORMAT ) != 0 ) + continue; + + // verify pixel type + if ( pfds[i].iPixelType != PFD_TYPE_RGBA ) + continue; + + // verify proper flags + if ( ( ( pfds[i].dwFlags & pPFD->dwFlags ) & pPFD->dwFlags ) != pPFD->dwFlags ) + continue; + + accelerated = !(pfds[i].dwFlags & PFD_GENERIC_FORMAT); + + // + // selection criteria (in order of priority): + // + // PFD_STEREO + // colorBits + // depthBits + // stencilBits + // + if ( bestMatch ) + { + // check stereo + if ( ( pfds[i].dwFlags & PFD_STEREO ) && ( !( pfds[bestMatch].dwFlags & PFD_STEREO ) ) && ( pPFD->dwFlags & PFD_STEREO ) ) + { + bestMatch = i; + continue; + } + + if ( !( pfds[i].dwFlags & PFD_STEREO ) && ( pfds[bestMatch].dwFlags & PFD_STEREO ) && ( pPFD->dwFlags & PFD_STEREO ) ) + { + bestMatch = i; + continue; + } + + // check color + if ( pfds[bestMatch].cColorBits != pPFD->cColorBits ) + { + // prefer perfect match + if ( pfds[i].cColorBits == pPFD->cColorBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( pfds[i].cColorBits > pfds[bestMatch].cColorBits ) + { + bestMatch = i; + continue; + } + } + + // check depth + if ( pfds[bestMatch].cDepthBits != pPFD->cDepthBits ) + { + // prefer perfect match + if ( pfds[i].cDepthBits == pPFD->cDepthBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( pfds[i].cDepthBits > pfds[bestMatch].cDepthBits ) + { + bestMatch = i; + continue; + } + } + + // check stencil + if ( pfds[bestMatch].cStencilBits != pPFD->cStencilBits ) + { + // prefer perfect match + if ( pfds[i].cStencilBits == pPFD->cStencilBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( ( pfds[i].cStencilBits > pfds[bestMatch].cStencilBits ) && + ( pPFD->cStencilBits > 0 ) ) + { + bestMatch = i; + continue; + } + } + } + else + { + bestMatch = i; + } + } + + if ( !bestMatch ) + return 0; + + else if ( pfds[bestMatch].dwFlags & PFD_GENERIC_ACCELERATED ) + { + // MCD + } + else + { + // ICD + } + + *pPFD = pfds[bestMatch]; + + return bestMatch; +} + +//-------------------------------------- +// +// This function exists so DirectInput can communicate with the Windows mouse +// in windowed mode. +// +//-------------------------------------- +void setModifierKeys( S32 modKeys ) +{ + modifierKeys = modKeys; +} + +//-------------------------------------- +const Point2I &Platform::getWindowSize() +{ + return windowSize; +} + +//-------------------------------------- +void Platform::setWindowSize( U32 newWidth, U32 newHeight ) +{ + windowSize.set( newWidth, newHeight ); +} + +//-------------------------------------- +static void InitWindow(const Point2I &initialSize) +{ + windowSize = initialSize; + + // The window is created when the display device is activated. BH + + winState.processId = GetCurrentProcessId(); +} + +//-------------------------------------- +static void InitOpenGL() +{ + // The OpenGL initialization stuff has been mostly moved to the display + // devices' activate functions. BH + + 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" ); + + if ( !Video::setDevice( Con::getVariable( "$pref::Video::displayDevice" ), width, height, bpp, fullScreen ) ) + { + // Next, try the default OpenGL device: + if ( !Video::setDevice( "OpenGL", width, height, bpp, fullScreen ) ) + { + // Next, try the D3D device: + if ( !Video::setDevice( "D3D", width, height, bpp, fullScreen ) ) + { + // Finally, try the Voodoo2 device: + if ( !Video::setDevice( "Voodoo2", width, height, bpp, fullScreen ) ) + { + AssertFatal( false, "Could not find a compatible display device!" ); + return; + } + } + } + } +} + +//-------------------------------------- +ConsoleFunction( getDesktopResolution, const char*, 1, 1, "getDesktopResolution()" ) +{ + argc; argv; + char buffer[256]; + dSprintf( buffer, sizeof( buffer ), "%d %d %d", winState.desktopWidth, winState.desktopHeight, winState.desktopBitsPixel ); + char* returnString = Con::getReturnBuffer( dStrlen( buffer ) + 1 ); + dStrcpy( returnString, buffer ); + return( returnString ); +} + +//-------------------------------------- +void Platform::init() +{ + // Set the platform variable for the scripts + Con::setVariable( "$platform", "windows" ); + + WinConsole::create(); + if ( !WinConsole::isEnabled() ) + Input::init(); + InitInput(); // in case DirectInput falls through + InitWindowClass(); + GetDesktopState(); + installRedBookDevices(); + + sgDoubleByteEnabled = GetSystemMetrics( SM_DBCSENABLED ); + sgQueueEvents = true; +} + +//-------------------------------------- +void Platform::shutdown() +{ + sgQueueEvents = false; + + if(gMutexHandle) + CloseHandle(gMutexHandle); + setWindowLocked( false ); + Video::destroy(); + Input::destroy(); + WinConsole::destroy(); +} + + +static U32 lastTimeTick; + +//-------------------------------------- +static S32 run(S32 argc, const char **argv) +{ + createFontInit(); + windowSize.set(0,0); + + lastTimeTick = GetTickCount(); + + S32 ret = Game->main(argc, argv); + createFontShutdown(); + return ret; +} + +//-------------------------------------- +void Platform::initWindow(const Point2I &initialSize, const char *name) +{ + Con::printf( "Video Init:" ); + Video::init(); + if ( Video::installDevice( OpenGLDevice::create() ) ) + Con::printf( " Accelerated OpenGL display device detected." ); + else + Con::printf( " Accelerated OpenGL display device not detected." ); + + if ( Video::installDevice( D3DDevice::create() ) ) + Con::printf( " Accelerated D3D device detected." ); + else + Con::printf( " Accelerated D3D device not detected." ); + Con::printf( "" ); + + if ( Video::installDevice( Voodoo2Device::create() ) ) + Con::printf( " Voodoo 2 display device detected." ); + else + Con::printf( " Voodoo 2 display device not detected." ); + Con::printf( "" ); + + gWindowCreated = true; + dStrcpy(windowName, name); + InitWindow(initialSize); + InitOpenGL(); +} + +//-------------------------------------- +S32 main(S32 argc, const char **argv) +{ + winState.appInstance = GetModuleHandle(argv[0]); + return run(argc, argv); +} + +//-------------------------------------- +S32 PASCAL WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR lpszCmdLine, S32) +{ + const char *ptr = lpszCmdLine; + Vector argv; + char moduleName[256]; + GetModuleFileName(NULL, moduleName, sizeof(moduleName)); + + argv.push_back(moduleName); + S32 i = 0; + for(;;) + { + char c = ptr[i]; + if(c == 0 || c == ' ') + { + if(i) + { + char *arg = (char *) dMalloc(i+1); + dStrncpy(arg, ptr, i); + arg[i] = 0; + argv.push_back(arg); + ptr += i + 1; + i = 0; + } + if(!c) + break; + } + else + i++; + } + 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; +} + +//-------------------------------------- +void TimeManager::process() +{ + U32 curTime = GetTickCount(); + TimeEvent event; + event.elapsedTime = curTime - lastTimeTick; + if(event.elapsedTime > 5) + { + lastTimeTick = curTime; + Game->postEvent(event); + } +} + +/* +GLimp_Init + GLW_LoadOpenGL + QGL_Init(driver); + GLW_StartDriverAndSetMode + GLW_SetMode + ChangeDisplaySettings + GLW_CreateWindow + GLW_InitDriver + GLW_CreatePFD + GLW_MakeContext + GLW_ChoosePFD + DescribePixelFormat + SetPixelFormat + + GLW_InitExtensions + WG_CheckHardwareGamma +*/ + +F32 Platform::getRandom() +{ + return sgPlatRandom.randF(); +} + +//-------------------------------------- +// Web browser function: +//-------------------------------------- +bool Platform::openWebBrowser( const char* webAddress ) +{ + static bool sHaveKey = false; + static U8 sWebKey[512]; + + if ( !sHaveKey ) + { + DWORD size = sizeof( sWebKey ); + HKEY regKey; + + if ( RegOpenKeyEx( HKEY_CLASSES_ROOT, ".htm", 0, KEY_QUERY_VALUE, ®Key ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Failed to open the .htm registry key!!!" ); + return( false ); + } + + if ( RegQueryValueEx( regKey, "", NULL, NULL, sWebKey, &size ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Failed to query the .htm registry key!!!" ); + return( false ); + } + + RegCloseKey( regKey ); + + Con::printf( "Registry key = %s", (const char*) sWebKey ); + dStrcat( (char*) sWebKey, "\\shell\\open\\command" ); + + if ( RegOpenKeyEx( HKEY_CLASSES_ROOT, (const char*) sWebKey, 0, KEY_QUERY_VALUE, ®Key ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Failed to open the %s registry key!!!", (const char*) sWebKey ); + return( false ); + } + + size = sizeof( sWebKey ); + if ( RegQueryValueEx( regKey, "", NULL, NULL, sWebKey, &size ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Failed to query the open command registry key!!!" ); + return( false ); + } + + RegCloseKey( regKey ); + sHaveKey = true; + } + + char buf[1024]; + dSprintf( buf, sizeof( buf ), "%s %s", (const char*) sWebKey, webAddress ); + + STARTUPINFO si; + dMemset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + //Con::errorf( ConsoleLogEntry::General, "** Web browser command = %s **", buf ); + + PROCESS_INFORMATION pi; + dMemset( &pi, 0, sizeof( pi ) ); + CreateProcess( NULL, + buf, + NULL, + NULL, + false, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + &si, + &pi ); + + return( true ); +} + +//-------------------------------------- +// Login password routines: +//-------------------------------------- +static const char* Tribes2RegKey = "SOFTWARE\\Sierra On-Line\\Tribes2"; + +const char* Platform::getLoginPassword() +{ + HKEY regKey; + char* returnString = NULL; + if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, Tribes2RegKey, 0, KEY_QUERY_VALUE, ®Key ) == ERROR_SUCCESS ) + { + U8 buf[32]; + DWORD size = sizeof( buf ); + if ( RegQueryValueEx( regKey, "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, Tribes2RegKey, 0, KEY_WRITE, ®Key ) == ERROR_SUCCESS ) + { + if ( RegSetValueEx( regKey, "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 Tribes 2 registry key!" ); + + return( false ); +} + +//-------------------------------------- +// Dedicated server launcher: +//-------------------------------------- +ConsoleFunction( launchDedicatedServer, bool, 4, 4, "launchDedicatedServer( missionType, map, botCount )" ) +{ + argc; + + // Set up the command line: + char cmdLine[512]; + const char* thisCmdLine = GetCommandLine(); + const char* temp = dStrstr( thisCmdLine, ".exe" ); + if ( !temp ) + return( false ); + + U32 len = temp - thisCmdLine + 4; + dStrncpy( cmdLine, thisCmdLine, len ); + if ( cmdLine[0] == '\"' ) + { + cmdLine[len] = '\"'; + len++; + } + dSprintf( cmdLine + len, sizeof( cmdLine ) - len, " %s-dedicated -mission %s %s -bot %d", + Con::getBoolVariable( "$PlayingOnline" ) ? "" : "-nologin ", + argv[1], + argv[2], + dAtoi( argv[3] ) ); + Con::errorf( "** launching dedicated server - command line = \"%s\" **", cmdLine ); + + STARTUPINFO si; + dMemset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + dMemset( &pi, 0, sizeof( pi ) ); + return CreateProcess( NULL, + cmdLine, + NULL, + NULL, + false, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + &si, + &pi ); +} + +//-------------------------------------- +// Silly Korean registry key checker: +//-------------------------------------- +ConsoleFunction( isKoreanBuild, bool, 1, 1, "isKoreanBuild()" ) +{ + argc; argv; + HKEY regKey; + bool result = false; + if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, Tribes2RegKey, 0, KEY_QUERY_VALUE, ®Key ) == ERROR_SUCCESS ) + { + DWORD val; + DWORD size = sizeof( val ); + if ( RegQueryValueEx( regKey, "Korean", NULL, NULL, (U8*) &val, &size ) == ERROR_SUCCESS ) + result = ( val > 0 ); + + RegCloseKey( regKey ); + } + + return( result ); +} diff --git a/platformWin32/win_common_prefix.h b/platformWin32/win_common_prefix.h new file mode 100644 index 0000000..ebd23f2 --- /dev/null +++ b/platformWin32/win_common_prefix.h @@ -0,0 +1,18 @@ +//win_common_prefix.h + +// define our platform +#define TARG_WIN32 1 +#define WIN32 1 + +// normally, this should be on for a PC build +#define USEASSEMBLYTERRBLEND 1 + +// 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 + +// these are general build flags V12 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..0782e44 --- /dev/null +++ b/platformWin32/win_debug_prefix.h @@ -0,0 +1,9 @@ +//win_debug_prefix.h + +#include "win_common_prefix.h" + +#define V12_DEBUG 1 +#define BUILD_SUFFIX "_DEBUG" + +//OPENGL2D3D=glFOO +//GLU2D3D=gluFOO diff --git a/platformWin32/win_release_prefix.h b/platformWin32/win_release_prefix.h new file mode 100644 index 0000000..c44ad1d --- /dev/null +++ b/platformWin32/win_release_prefix.h @@ -0,0 +1,23 @@ +//win_debug_prefix.h + +#include "win_common_prefix.h" + +// our defines +#define TARG_RELEASE 1 +#define V12_NO_ASSERTS 1 +#define BUILD_SUFFIX "" + +// turn these off for now. +//#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_func.h b/platformX86UNIX/gl_func.h new file mode 100644 index 0000000..5db96fa --- /dev/null +++ b/platformX86UNIX/gl_func.h @@ -0,0 +1,798 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +// Most of this is from... +/* + * 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. + */ + +//------------------------------------------------------------------------------ +// GL functions +//------------------------------------------------------------------------------ + +/* + * Miscellaneous + */ + +GL_FUNCTION(void, glClearIndex, ( GLfloat c ), return; ) + +GL_FUNCTION(void, glClearColor, ( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha ), return; ) + +GL_FUNCTION(void, glClear, ( GLbitfield mask ), return; ) + +GL_FUNCTION(void, glIndexMask, ( GLuint mask ), return; ) + +GL_FUNCTION(void, glColorMask, ( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha ), return; ) + +GL_FUNCTION(void, glAlphaFunc, ( GLenum func, GLclampf ref ), return; ) + +GL_FUNCTION(void, glBlendFunc, ( GLenum sfactor, GLenum dfactor ), return; ) + +GL_FUNCTION(void, glLogicOp, ( GLenum opcode ), return; ) + +GL_FUNCTION(void, glCullFace, ( GLenum mode ), return; ) + +GL_FUNCTION(void, glFrontFace, ( GLenum mode ), return; ) + +GL_FUNCTION(void, glPointSize, ( GLfloat size ), return; ) + +GL_FUNCTION(void, glLineWidth, ( GLfloat width ), return; ) + +GL_FUNCTION(void, glLineStipple, ( GLint factor, GLushort pattern ), return; ) + +GL_FUNCTION(void, glPolygonMode, ( GLenum face, GLenum mode ), return; ) + +GL_FUNCTION(void, glPolygonOffset, ( GLfloat factor, GLfloat units ), return; ) + +GL_FUNCTION(void, glPolygonStipple, ( const GLubyte *mask ), return; ) + +GL_FUNCTION(void, glGetPolygonStipple, ( GLubyte *mask ), return; ) + +GL_FUNCTION(void, glEdgeFlag, ( GLboolean flag ), return; ) + +GL_FUNCTION(void, glEdgeFlagv, ( const GLboolean *flag ), return; ) + +GL_FUNCTION(void, glScissor, ( GLint x, GLint y, GLsizei width, GLsizei height), return; ) + +GL_FUNCTION(void, glClipPlane, ( GLenum plane, const GLdouble *equation ), return; ) + +GL_FUNCTION(void, glGetClipPlane, ( GLenum plane, GLdouble *equation ), return; ) + +GL_FUNCTION(void, glDrawBuffer, ( GLenum mode ), return; ) + +GL_FUNCTION(void, glReadBuffer, ( GLenum mode ), return; ) + +GL_FUNCTION(void, glEnable, ( GLenum cap ), return; ) + +GL_FUNCTION(void, glDisable, ( GLenum cap ), return; ) + +GL_FUNCTION(GLboolean, glIsEnabled, ( GLenum cap ), return GL_FALSE; ) + + +GL_FUNCTION(void, glEnableClientState, ( GLenum cap ), return; ) /* 1.1 */ + +GL_FUNCTION(void, glDisableClientState, ( GLenum cap ), return; ) /* 1.1 */ + + +GL_FUNCTION(void, glGetBooleanv, ( GLenum pname, GLboolean *params ), return; ) + +GL_FUNCTION(void, glGetDoublev, ( GLenum pname, GLdouble *params ), return; ) + +GL_FUNCTION(void, glGetFloatv, ( GLenum pname, GLfloat *params ), return; ) + +GL_FUNCTION(void, glGetIntegerv, ( GLenum pname, GLint *params ), return; ) + + +GL_FUNCTION(void, glPushAttrib, ( GLbitfield mask ), return; ) + +GL_FUNCTION(void, glPopAttrib, ( void ), return; ) + + +GL_FUNCTION(void, glPushClientAttrib, ( GLbitfield mask ), return; ) /* 1.1 */ + +GL_FUNCTION(void, glPopClientAttrib, ( void ), return; ) /* 1.1 */ + + +GL_FUNCTION(GLint, glRenderMode, ( GLenum mode ), return 0; ) + +GL_FUNCTION(GLenum, glGetError, ( void ), return 0; ) + +GL_FUNCTION(const GLubyte*, glGetString, ( GLenum name ), return (const GLubyte*)""; ) + +GL_FUNCTION(void, glFinish, ( void ), return; ) + +GL_FUNCTION(void, glFlush, ( void ), return; ) + +GL_FUNCTION(void, glHint, ( GLenum target, GLenum mode ), return; ) + + + +/* + * Depth Buffer + */ + +GL_FUNCTION(void, glClearDepth, ( GLclampd depth ), return; ) + +GL_FUNCTION(void, glDepthFunc, ( GLenum func ), return; ) + +GL_FUNCTION(void, glDepthMask, ( GLboolean flag ), return; ) + +GL_FUNCTION(void, glDepthRange, ( GLclampd near_val, GLclampd far_val ), return; ) + + +/* + * Accumulation Buffer + */ + +GL_FUNCTION(void, glClearAccum, ( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha ), return; ) + +GL_FUNCTION(void, glAccum, ( GLenum op, GLfloat value ), return; ) + + + +/* + * Transformation + */ + +GL_FUNCTION(void, glMatrixMode, ( GLenum mode ), return; ) + +GL_FUNCTION(void, glOrtho, ( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val ), return; ) + +GL_FUNCTION(void, glFrustum, ( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val ), return; ) + +GL_FUNCTION(void, glViewport, ( GLint x, GLint y, GLsizei width, GLsizei height ), return; ) + +GL_FUNCTION(void, glPushMatrix, ( void ), return; ) + +GL_FUNCTION(void, glPopMatrix, ( void ), return; ) + +GL_FUNCTION(void, glLoadIdentity, ( void ), return; ) + +GL_FUNCTION(void, glLoadMatrixd, ( const GLdouble *m ), return; ) +GL_FUNCTION(void, glLoadMatrixf, ( const GLfloat *m ), return; ) + +GL_FUNCTION(void, glMultMatrixd, ( const GLdouble *m ), return; ) +GL_FUNCTION(void, glMultMatrixf, ( const GLfloat *m ), return; ) + +GL_FUNCTION(void, glRotated, ( GLdouble angle, GLdouble x, GLdouble y, GLdouble z ), return; ) +GL_FUNCTION(void, glRotatef, ( GLfloat angle, GLfloat x, GLfloat y, GLfloat z ), return; ) + +GL_FUNCTION(void, glScaled, ( GLdouble x, GLdouble y, GLdouble z ), return; ) +GL_FUNCTION(void, glScalef, ( GLfloat x, GLfloat y, GLfloat z ), return; ) + +GL_FUNCTION(void, glTranslated, ( GLdouble x, GLdouble y, GLdouble z ), return; ) +GL_FUNCTION(void, glTranslatef, ( GLfloat x, GLfloat y, GLfloat z ), return; ) + + + +/* + * Display Lists + */ + +GL_FUNCTION(GLboolean, glIsList, ( GLuint list ), return GL_FALSE; ) + +GL_FUNCTION(void, glDeleteLists, ( GLuint list, GLsizei range ), return; ) + +GL_FUNCTION(GLuint, glGenLists, ( GLsizei range ), return 0; ) + +GL_FUNCTION(void, glNewList, ( GLuint list, GLenum mode ), return; ) + +GL_FUNCTION(void, glEndList, ( void ), return; ) + +GL_FUNCTION(void, glCallList, ( GLuint list ), return; ) + +GL_FUNCTION(void, glCallLists, ( GLsizei n, GLenum type, const GLvoid *lists ), return; ) + +GL_FUNCTION(void, glListBase, ( GLuint base ), return; ) + + + +/* + * Drawing Functions + */ + +GL_FUNCTION(void, glBegin, ( GLenum mode ), return; ) + +GL_FUNCTION(void, glEnd, ( void ), return; ) + + +GL_FUNCTION(void, glVertex2d, ( GLdouble x, GLdouble y ), return; ) +GL_FUNCTION(void, glVertex2f, ( GLfloat x, GLfloat y ), return; ) +GL_FUNCTION(void, glVertex2i, ( GLint x, GLint y ), return; ) +GL_FUNCTION(void, glVertex2s, ( GLshort x, GLshort y ), return; ) + +GL_FUNCTION(void, glVertex3d, ( GLdouble x, GLdouble y, GLdouble z ), return; ) +GL_FUNCTION(void, glVertex3f, ( GLfloat x, GLfloat y, GLfloat z ), return; ) +GL_FUNCTION(void, glVertex3i, ( GLint x, GLint y, GLint z ), return; ) +GL_FUNCTION(void, glVertex3s, ( GLshort x, GLshort y, GLshort z ), return; ) + +GL_FUNCTION(void, glVertex4d, ( GLdouble x, GLdouble y, GLdouble z, GLdouble w ), return; ) +GL_FUNCTION(void, glVertex4f, ( GLfloat x, GLfloat y, GLfloat z, GLfloat w ), return; ) +GL_FUNCTION(void, glVertex4i, ( GLint x, GLint y, GLint z, GLint w ), return; ) +GL_FUNCTION(void, glVertex4s, ( GLshort x, GLshort y, GLshort z, GLshort w ), return; ) + +GL_FUNCTION(void, glVertex2dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glVertex2fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glVertex2iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glVertex2sv, ( const GLshort *v ), return; ) + +GL_FUNCTION(void, glVertex3dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glVertex3fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glVertex3iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glVertex3sv, ( const GLshort *v ), return; ) + +GL_FUNCTION(void, glVertex4dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glVertex4fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glVertex4iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glVertex4sv, ( const GLshort *v ), return; ) + + +GL_FUNCTION(void, glNormal3b, ( GLbyte nx, GLbyte ny, GLbyte nz ), return; ) +GL_FUNCTION(void, glNormal3d, ( GLdouble nx, GLdouble ny, GLdouble nz ), return; ) +GL_FUNCTION(void, glNormal3f, ( GLfloat nx, GLfloat ny, GLfloat nz ), return; ) +GL_FUNCTION(void, glNormal3i, ( GLint nx, GLint ny, GLint nz ), return; ) +GL_FUNCTION(void, glNormal3s, ( GLshort nx, GLshort ny, GLshort nz ), return; ) + +GL_FUNCTION(void, glNormal3bv, ( const GLbyte *v ), return; ) +GL_FUNCTION(void, glNormal3dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glNormal3fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glNormal3iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glNormal3sv, ( const GLshort *v ), return; ) + + +GL_FUNCTION(void, glIndexd, ( GLdouble c ), return; ) +GL_FUNCTION(void, glIndexf, ( GLfloat c ), return; ) +GL_FUNCTION(void, glIndexi, ( GLint c ), return; ) +GL_FUNCTION(void, glIndexs, ( GLshort c ), return; ) +GL_FUNCTION(void, glIndexub, ( GLubyte c ), return; ) /* 1.1 */ + +GL_FUNCTION(void, glIndexdv, ( const GLdouble *c ), return; ) +GL_FUNCTION(void, glIndexfv, ( const GLfloat *c ), return; ) +GL_FUNCTION(void, glIndexiv, ( const GLint *c ), return; ) +GL_FUNCTION(void, glIndexsv, ( const GLshort *c ), return; ) +GL_FUNCTION(void, glIndexubv, ( const GLubyte *c ), return; ) /* 1.1 */ + +GL_FUNCTION(void, glColor3b, ( GLbyte red, GLbyte green, GLbyte blue ), return; ) +GL_FUNCTION(void, glColor3d, ( GLdouble red, GLdouble green, GLdouble blue ), return; ) +GL_FUNCTION(void, glColor3f, ( GLfloat red, GLfloat green, GLfloat blue ), return; ) +GL_FUNCTION(void, glColor3i, ( GLint red, GLint green, GLint blue ), return; ) +GL_FUNCTION(void, glColor3s, ( GLshort red, GLshort green, GLshort blue ), return; ) +GL_FUNCTION(void, glColor3ub, ( GLubyte red, GLubyte green, GLubyte blue ), return; ) +GL_FUNCTION(void, glColor3ui, ( GLuint red, GLuint green, GLuint blue ), return; ) +GL_FUNCTION(void, glColor3us, ( GLushort red, GLushort green, GLushort blue ), return; ) + +GL_FUNCTION(void, glColor4b, ( GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha ), return; ) +GL_FUNCTION(void, glColor4d, ( GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha ), return; ) +GL_FUNCTION(void, glColor4f, ( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha ), return; ) +GL_FUNCTION(void, glColor4i, ( GLint red, GLint green, GLint blue, GLint alpha ), return; ) +GL_FUNCTION(void, glColor4s, ( GLshort red, GLshort green, GLshort blue, GLshort alpha ), return; ) +GL_FUNCTION(void, glColor4ub, ( GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha ), return; ) +GL_FUNCTION(void, glColor4ui, ( GLuint red, GLuint green, GLuint blue, GLuint alpha ), return; ) +GL_FUNCTION(void, glColor4us, ( GLushort red, GLushort green, GLushort blue, GLushort alpha ), return; ) + + +GL_FUNCTION(void, glColor3bv, ( const GLbyte *v ), return; ) +GL_FUNCTION(void, glColor3dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glColor3fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glColor3iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glColor3sv, ( const GLshort *v ), return; ) +GL_FUNCTION(void, glColor3ubv, ( const GLubyte *v ), return; ) +GL_FUNCTION(void, glColor3uiv, ( const GLuint *v ), return; ) +GL_FUNCTION(void, glColor3usv, ( const GLushort *v ), return; ) + +GL_FUNCTION(void, glColor4bv, ( const GLbyte *v ), return; ) +GL_FUNCTION(void, glColor4dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glColor4fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glColor4iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glColor4sv, ( const GLshort *v ), return; ) +GL_FUNCTION(void, glColor4ubv, ( const GLubyte *v ), return; ) +GL_FUNCTION(void, glColor4uiv, ( const GLuint *v ), return; ) +GL_FUNCTION(void, glColor4usv, ( const GLushort *v ), return; ) + + +GL_FUNCTION(void, glTexCoord1d, ( GLdouble s ), return; ) +GL_FUNCTION(void, glTexCoord1f, ( GLfloat s ), return; ) +GL_FUNCTION(void, glTexCoord1i, ( GLint s ), return; ) +GL_FUNCTION(void, glTexCoord1s, ( GLshort s ), return; ) + +GL_FUNCTION(void, glTexCoord2d, ( GLdouble s, GLdouble t ), return; ) +GL_FUNCTION(void, glTexCoord2f, ( GLfloat s, GLfloat t ), return; ) +GL_FUNCTION(void, glTexCoord2i, ( GLint s, GLint t ), return; ) +GL_FUNCTION(void, glTexCoord2s, ( GLshort s, GLshort t ), return; ) + +GL_FUNCTION(void, glTexCoord3d, ( GLdouble s, GLdouble t, GLdouble r ), return; ) +GL_FUNCTION(void, glTexCoord3f, ( GLfloat s, GLfloat t, GLfloat r ), return; ) +GL_FUNCTION(void, glTexCoord3i, ( GLint s, GLint t, GLint r ), return; ) +GL_FUNCTION(void, glTexCoord3s, ( GLshort s, GLshort t, GLshort r ), return; ) + +GL_FUNCTION(void, glTexCoord4d, ( GLdouble s, GLdouble t, GLdouble r, GLdouble q ), return; ) +GL_FUNCTION(void, glTexCoord4f, ( GLfloat s, GLfloat t, GLfloat r, GLfloat q ), return; ) +GL_FUNCTION(void, glTexCoord4i, ( GLint s, GLint t, GLint r, GLint q ), return; ) +GL_FUNCTION(void, glTexCoord4s, ( GLshort s, GLshort t, GLshort r, GLshort q ), return; ) + +GL_FUNCTION(void, glTexCoord1dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glTexCoord1fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glTexCoord1iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glTexCoord1sv, ( const GLshort *v ), return; ) + +GL_FUNCTION(void, glTexCoord2dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glTexCoord2fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glTexCoord2iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glTexCoord2sv, ( const GLshort *v ), return; ) + +GL_FUNCTION(void, glTexCoord3dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glTexCoord3fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glTexCoord3iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glTexCoord3sv, ( const GLshort *v ), return; ) + +GL_FUNCTION(void, glTexCoord4dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glTexCoord4fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glTexCoord4iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glTexCoord4sv, ( const GLshort *v ), return; ) + + +GL_FUNCTION(void, glRasterPos2d, ( GLdouble x, GLdouble y ), return; ) +GL_FUNCTION(void, glRasterPos2f, ( GLfloat x, GLfloat y ), return; ) +GL_FUNCTION(void, glRasterPos2i, ( GLint x, GLint y ), return; ) +GL_FUNCTION(void, glRasterPos2s, ( GLshort x, GLshort y ), return; ) + +GL_FUNCTION(void, glRasterPos3d, ( GLdouble x, GLdouble y, GLdouble z ), return; ) +GL_FUNCTION(void, glRasterPos3f, ( GLfloat x, GLfloat y, GLfloat z ), return; ) +GL_FUNCTION(void, glRasterPos3i, ( GLint x, GLint y, GLint z ), return; ) +GL_FUNCTION(void, glRasterPos3s, ( GLshort x, GLshort y, GLshort z ), return; ) + +GL_FUNCTION(void, glRasterPos4d, ( GLdouble x, GLdouble y, GLdouble z, GLdouble w ), return; ) +GL_FUNCTION(void, glRasterPos4f, ( GLfloat x, GLfloat y, GLfloat z, GLfloat w ), return; ) +GL_FUNCTION(void, glRasterPos4i, ( GLint x, GLint y, GLint z, GLint w ), return; ) +GL_FUNCTION(void, glRasterPos4s, ( GLshort x, GLshort y, GLshort z, GLshort w ), return; ) + +GL_FUNCTION(void, glRasterPos2dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glRasterPos2fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glRasterPos2iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glRasterPos2sv, ( const GLshort *v ), return; ) + +GL_FUNCTION(void, glRasterPos3dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glRasterPos3fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glRasterPos3iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glRasterPos3sv, ( const GLshort *v ), return; ) + +GL_FUNCTION(void, glRasterPos4dv, ( const GLdouble *v ), return; ) +GL_FUNCTION(void, glRasterPos4fv, ( const GLfloat *v ), return; ) +GL_FUNCTION(void, glRasterPos4iv, ( const GLint *v ), return; ) +GL_FUNCTION(void, glRasterPos4sv, ( const GLshort *v ), return; ) + + +GL_FUNCTION(void, glRectd, ( GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2 ), return; ) +GL_FUNCTION(void, glRectf, ( GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2 ), return; ) +GL_FUNCTION(void, glRecti, ( GLint x1, GLint y1, GLint x2, GLint y2 ), return; ) +GL_FUNCTION(void, glRects, ( GLshort x1, GLshort y1, GLshort x2, GLshort y2 ), return; ) + + +GL_FUNCTION(void, glRectdv, ( const GLdouble *v1, const GLdouble *v2 ), return; ) +GL_FUNCTION(void, glRectfv, ( const GLfloat *v1, const GLfloat *v2 ), return; ) +GL_FUNCTION(void, glRectiv, ( const GLint *v1, const GLint *v2 ), return; ) +GL_FUNCTION(void, glRectsv, ( const GLshort *v1, const GLshort *v2 ), return; ) + + + +/* + * Vertex Arrays (1.1) + */ + +GL_FUNCTION(void, glVertexPointer, ( GLint size, GLenum type, GLsizei stride, const GLvoid *ptr ), return; ) + +GL_FUNCTION(void, glNormalPointer, ( GLenum type, GLsizei stride, const GLvoid *ptr ), return; ) + +GL_FUNCTION(void, glColorPointer, ( GLint size, GLenum type, GLsizei stride, const GLvoid *ptr ), return; ) + +GL_FUNCTION(void, glIndexPointer, ( GLenum type, GLsizei stride, const GLvoid *ptr ), return; ) + +GL_FUNCTION(void, glTexCoordPointer, ( GLint size, GLenum type, GLsizei stride, const GLvoid *ptr ), return; ) + +GL_FUNCTION(void, glEdgeFlagPointer, ( GLsizei stride, const GLvoid *ptr ), return; ) + +GL_FUNCTION(void, glGetPointerv, ( GLenum pname, void **params ), return; ) + +GL_FUNCTION(void, glArrayElement, ( GLint i ), return; ) + +GL_FUNCTION(void, glDrawArrays, ( GLenum mode, GLint first, GLsizei count ), return; ) + +GL_FUNCTION(void, glDrawElements, ( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ), return; ) + +GL_FUNCTION(void, glInterleavedArrays, ( GLenum format, GLsizei stride, const GLvoid *pointer ), return; ) + + +/* + * Lighting + */ + +GL_FUNCTION(void, glShadeModel, ( GLenum mode ), return; ) + +GL_FUNCTION(void, glLightf, ( GLenum light, GLenum pname, GLfloat param ), return; ) +GL_FUNCTION(void, glLighti, ( GLenum light, GLenum pname, GLint param ), return; ) +GL_FUNCTION(void, glLightfv, ( GLenum light, GLenum pname, const GLfloat *params ), return; ) +GL_FUNCTION(void, glLightiv, ( GLenum light, GLenum pname, const GLint *params ), return; ) + +GL_FUNCTION(void, glGetLightfv, ( GLenum light, GLenum pname, GLfloat *params ), return; ) +GL_FUNCTION(void, glGetLightiv, ( GLenum light, GLenum pname, GLint *params ), return; ) + +GL_FUNCTION(void, glLightModelf, ( GLenum pname, GLfloat param ), return; ) +GL_FUNCTION(void, glLightModeli, ( GLenum pname, GLint param ), return; ) +GL_FUNCTION(void, glLightModelfv, ( GLenum pname, const GLfloat *params ), return; ) +GL_FUNCTION(void, glLightModeliv, ( GLenum pname, const GLint *params ), return; ) + +GL_FUNCTION(void, glMaterialf, ( GLenum face, GLenum pname, GLfloat param ), return; ) +GL_FUNCTION(void, glMateriali, ( GLenum face, GLenum pname, GLint param ), return; ) +GL_FUNCTION(void, glMaterialfv, ( GLenum face, GLenum pname, const GLfloat *params ), return; ) +GL_FUNCTION(void, glMaterialiv, ( GLenum face, GLenum pname, const GLint *params ), return; ) + +GL_FUNCTION(void, glGetMaterialfv, ( GLenum face, GLenum pname, GLfloat *params ), return; ) +GL_FUNCTION(void, glGetMaterialiv, ( GLenum face, GLenum pname, GLint *params ), return; ) + +GL_FUNCTION(void, glColorMaterial, ( GLenum face, GLenum mode ), return; ) + + + + +/* + * Raster functions + */ + +GL_FUNCTION(void, glPixelZoom, ( GLfloat xfactor, GLfloat yfactor ), return; ) + +GL_FUNCTION(void, glPixelStoref, ( GLenum pname, GLfloat param ), return; ) +GL_FUNCTION(void, glPixelStorei, ( GLenum pname, GLint param ), return; ) + +GL_FUNCTION(void, glPixelTransferf, ( GLenum pname, GLfloat param ), return; ) +GL_FUNCTION(void, glPixelTransferi, ( GLenum pname, GLint param ), return; ) + +GL_FUNCTION(void, glPixelMapfv, ( GLenum map, GLint mapsize, const GLfloat *values ), return; ) +GL_FUNCTION(void, glPixelMapuiv, ( GLenum map, GLint mapsize, const GLuint *values ), return; ) +GL_FUNCTION(void, glPixelMapusv, ( GLenum map, GLint mapsize, const GLushort *values ), return; ) + +GL_FUNCTION(void, glGetPixelMapfv, ( GLenum map, GLfloat *values ), return; ) +GL_FUNCTION(void, glGetPixelMapuiv, ( GLenum map, GLuint *values ), return; ) +GL_FUNCTION(void, glGetPixelMapusv, ( GLenum map, GLushort *values ), return; ) + +GL_FUNCTION(void, glBitmap, ( GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap ), return; ) + +GL_FUNCTION(void, glReadPixels, ( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels ), return; ) + +GL_FUNCTION(void, glDrawPixels, ( GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ), return; ) + +GL_FUNCTION(void, glCopyPixels, ( GLint x, GLint y, GLsizei width, GLsizei height, GLenum type ), return; ) + + + +/* + * Stenciling + */ + +GL_FUNCTION(void, glStencilFunc, ( GLenum func, GLint ref, GLuint mask ), return; ) + +GL_FUNCTION(void, glStencilMask, ( GLuint mask ), return; ) + +GL_FUNCTION(void, glStencilOp, ( GLenum fail, GLenum zfail, GLenum zpass ), return; ) + +GL_FUNCTION(void, glClearStencil, ( GLint s ), return; ) + + + +/* + * Texture mapping + */ + +GL_FUNCTION(void, glTexGend, ( GLenum coord, GLenum pname, GLdouble param ), return; ) +GL_FUNCTION(void, glTexGenf, ( GLenum coord, GLenum pname, GLfloat param ), return; ) +GL_FUNCTION(void, glTexGeni, ( GLenum coord, GLenum pname, GLint param ), return; ) + +GL_FUNCTION(void, glTexGendv, ( GLenum coord, GLenum pname, const GLdouble *params ), return; ) +GL_FUNCTION(void, glTexGenfv, ( GLenum coord, GLenum pname, const GLfloat *params ), return; ) +GL_FUNCTION(void, glTexGeniv, ( GLenum coord, GLenum pname, const GLint *params ), return; ) + +GL_FUNCTION(void, glGetTexGendv, ( GLenum coord, GLenum pname, GLdouble *params ), return; ) +GL_FUNCTION(void, glGetTexGenfv, ( GLenum coord, GLenum pname, GLfloat *params ), return; ) +GL_FUNCTION(void, glGetTexGeniv, ( GLenum coord, GLenum pname, GLint *params ), return; ) + + +GL_FUNCTION(void, glTexEnvf, ( GLenum target, GLenum pname, GLfloat param ), return; ) +GL_FUNCTION(void, glTexEnvi, ( GLenum target, GLenum pname, GLint param ), return; ) + +GL_FUNCTION(void, glTexEnvfv, ( GLenum target, GLenum pname, const GLfloat *params ), return; ) +GL_FUNCTION(void, glTexEnviv, ( GLenum target, GLenum pname, const GLint *params ), return; ) + +GL_FUNCTION(void, glGetTexEnvfv, ( GLenum target, GLenum pname, GLfloat *params ), return; ) +GL_FUNCTION(void, glGetTexEnviv, ( GLenum target, GLenum pname, GLint *params ), return; ) + + +GL_FUNCTION(void, glTexParameterf, ( GLenum target, GLenum pname, GLfloat param ), return; ) +GL_FUNCTION(void, glTexParameteri, ( GLenum target, GLenum pname, GLint param ), return; ) + +GL_FUNCTION(void, glTexParameterfv, ( GLenum target, GLenum pname, const GLfloat *params ), return; ) +GL_FUNCTION(void, glTexParameteriv, ( GLenum target, GLenum pname, const GLint *params ), return; ) + +GL_FUNCTION(void, glGetTexParameterfv, ( GLenum target, GLenum pname, GLfloat *params), return; ) +GL_FUNCTION(void, glGetTexParameteriv, ( GLenum target, GLenum pname, GLint *params ), return; ) + +GL_FUNCTION(void, glGetTexLevelParameterfv, ( GLenum target, GLint level, GLenum pname, GLfloat *params ), return; ) +GL_FUNCTION(void, glGetTexLevelParameteriv, ( GLenum target, GLint level, GLenum pname, GLint *params ), return; ) + + +GL_FUNCTION(void, glTexImage1D, ( GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels ), return; ) + +GL_FUNCTION(void, glTexImage2D, ( GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels ), return; ) + +GL_FUNCTION(void, glGetTexImage, ( GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels ), return; ) + + + +/* 1.1 functions */ + +GL_FUNCTION(void, glGenTextures, ( GLsizei n, GLuint *textures ), return; ) + +GL_FUNCTION(void, glDeleteTextures, ( GLsizei n, const GLuint *textures), return; ) + +GL_FUNCTION(void, glBindTexture, ( GLenum target, GLuint texture ), return; ) + +GL_FUNCTION(void, glPrioritizeTextures, ( GLsizei n, const GLuint *textures, const GLclampf *priorities ), return; ) + +GL_FUNCTION(GLboolean, glAreTexturesResident, ( GLsizei n, const GLuint *textures, GLboolean *residences ), return GL_FALSE; ) + +GL_FUNCTION(GLboolean, glIsTexture, ( GLuint texture ), return GL_FALSE; ) + + +GL_FUNCTION(void, glTexSubImage1D, ( GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels ), return; ) + + +GL_FUNCTION(void, glTexSubImage2D, ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ), return; ) + + +GL_FUNCTION(void, glCopyTexImage1D, ( GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border ), return; ) + + +GL_FUNCTION(void, glCopyTexImage2D, ( GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border ), return; ) + + +GL_FUNCTION(void, glCopyTexSubImage1D, ( GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width ), return; ) + + +GL_FUNCTION(void, glCopyTexSubImage2D, ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height ), return; ) + + + + +/* + * Evaluators + */ + +GL_FUNCTION(void, glMap1d, ( GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points ), return; ) +GL_FUNCTION(void, glMap1f, ( GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points ), return; ) + +GL_FUNCTION(void, glMap2d, ( GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points ), return; ) +GL_FUNCTION(void, glMap2f, ( GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points ), return; ) + +GL_FUNCTION(void, glGetMapdv, ( GLenum target, GLenum query, GLdouble *v ), return; ) +GL_FUNCTION(void, glGetMapfv, ( GLenum target, GLenum query, GLfloat *v ), return; ) +GL_FUNCTION(void, glGetMapiv, ( GLenum target, GLenum query, GLint *v ), return; ) + +GL_FUNCTION(void, glEvalCoord1d, ( GLdouble u ), return; ) +GL_FUNCTION(void, glEvalCoord1f, ( GLfloat u ), return; ) + +GL_FUNCTION(void, glEvalCoord1dv, ( const GLdouble *u ), return; ) +GL_FUNCTION(void, glEvalCoord1fv, ( const GLfloat *u ), return; ) + +GL_FUNCTION(void, glEvalCoord2d, ( GLdouble u, GLdouble v ), return; ) +GL_FUNCTION(void, glEvalCoord2f, ( GLfloat u, GLfloat v ), return; ) + +GL_FUNCTION(void, glEvalCoord2dv, ( const GLdouble *u ), return; ) +GL_FUNCTION(void, glEvalCoord2fv, ( const GLfloat *u ), return; ) + +GL_FUNCTION(void, glMapGrid1d, ( GLint un, GLdouble u1, GLdouble u2 ), return; ) +GL_FUNCTION(void, glMapGrid1f, ( GLint un, GLfloat u1, GLfloat u2 ), return; ) + +GL_FUNCTION(void, glMapGrid2d, ( GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2 ), return; ) +GL_FUNCTION(void, glMapGrid2f, ( GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2 ), return; ) + +GL_FUNCTION(void, glEvalPoint1, ( GLint i ), return; ) + +GL_FUNCTION(void, glEvalPoint2, ( GLint i, GLint j ), return; ) + +GL_FUNCTION(void, glEvalMesh1, ( GLenum mode, GLint i1, GLint i2 ), return; ) + +GL_FUNCTION(void, glEvalMesh2, ( GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2 ), return; ) + + + +/* + * Fog + */ + +GL_FUNCTION(void, glFogf, ( GLenum pname, GLfloat param ), return; ) + +GL_FUNCTION(void, glFogi, ( GLenum pname, GLint param ), return; ) + +GL_FUNCTION(void, glFogfv, ( GLenum pname, const GLfloat *params ), return; ) + +GL_FUNCTION(void, glFogiv, ( GLenum pname, const GLint *params ), return; ) + + + +/* + * Selection and Feedback + */ + +GL_FUNCTION(void, glFeedbackBuffer, ( GLsizei size, GLenum type, GLfloat *buffer ), return; ) + +GL_FUNCTION(void, glPassThrough, ( GLfloat token ), return; ) + +GL_FUNCTION(void, glSelectBuffer, ( GLsizei size, GLuint *buffer ), return; ) + +GL_FUNCTION(void, glInitNames, ( void ), return; ) + +GL_FUNCTION(void, glLoadName, ( GLuint name ), return; ) + +GL_FUNCTION(void, glPushName, ( GLuint name ), return; ) + +GL_FUNCTION(void, glPopName, ( void ), return; ) + + + +/* 1.2 functions */ +GL_FUNCTION(void, glDrawRangeElements, ( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices ), return; ) + +GL_FUNCTION(void, glTexImage3D, ( GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels ), return; ) + +GL_FUNCTION(void, glTexSubImage3D, ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels), return; ) + +GL_FUNCTION(void, glCopyTexSubImage3D, ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height ), return; ) + + +/* 1.2 imaging extension functions */ + +GL_FUNCTION(void, glColorTable, ( GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table ), return; ) + +GL_FUNCTION(void, glColorSubTable, ( GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data ), return; ) + +GL_FUNCTION(void, glColorTableParameteriv, (GLenum target, GLenum pname, const GLint *params), return; ) + +GL_FUNCTION(void, glColorTableParameterfv, (GLenum target, GLenum pname, const GLfloat *params), return; ) + +GL_FUNCTION(void, glCopyColorSubTable, ( GLenum target, GLsizei start, GLint x, GLint y, GLsizei width ), return; ) + +GL_FUNCTION(void, glCopyColorTable, ( GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width ), return; ) + +GL_FUNCTION(void, glGetColorTable, ( GLenum target, GLenum format, GLenum type, GLvoid *table ), return; ) + +GL_FUNCTION(void, glGetColorTableParameterfv, ( GLenum target, GLenum pname, GLfloat *params ), return; ) + +GL_FUNCTION(void, glGetColorTableParameteriv, ( GLenum target, GLenum pname, GLint *params ), return; ) + +GL_FUNCTION(void, glBlendEquation, ( GLenum mode ), return; ) + +GL_FUNCTION(void, glBlendColor, ( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha ), return; ) + +GL_FUNCTION(void, glHistogram, ( GLenum target, GLsizei width, GLenum internalformat, GLboolean sink ), return; ) + +GL_FUNCTION(void, glResetHistogram, ( GLenum target ), return; ) + +GL_FUNCTION(void, glGetHistogram, ( GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values ), return; ) + +GL_FUNCTION(void, glGetHistogramParameterfv, ( GLenum target, GLenum pname, GLfloat *params ), return; ) + +GL_FUNCTION(void, glGetHistogramParameteriv, ( GLenum target, GLenum pname, GLint *params ), return; ) + +GL_FUNCTION(void, glMinmax, ( GLenum target, GLenum internalformat, GLboolean sink ), return; ) + +GL_FUNCTION(void, glResetMinmax, ( GLenum target ), return; ) + +GL_FUNCTION(void, glGetMinmax, ( GLenum target, GLboolean reset, GLenum format, GLenum types, GLvoid *values ), return; ) + +GL_FUNCTION(void, glGetMinmaxParameterfv, ( GLenum target, GLenum pname, GLfloat *params ), return; ) + +GL_FUNCTION(void, glGetMinmaxParameteriv, ( GLenum target, GLenum pname, GLint *params ), return; ) + +GL_FUNCTION(void, glConvolutionFilter1D, ( GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image ), return; ) + +GL_FUNCTION(void, glConvolutionFilter2D, ( GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image ), return; ) + +GL_FUNCTION(void, glConvolutionParameterf, ( GLenum target, GLenum pname, GLfloat params ), return; ) + +GL_FUNCTION(void, glConvolutionParameterfv, ( GLenum target, GLenum pname, const GLfloat *params ), return; ) + +GL_FUNCTION(void, glConvolutionParameteri, ( GLenum target, GLenum pname, GLint params ), return; ) + +GL_FUNCTION(void, glConvolutionParameteriv, ( GLenum target, GLenum pname, const GLint *params ), return; ) + +GL_FUNCTION(void, glCopyConvolutionFilter1D, ( GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width ), return; ) + +GL_FUNCTION(void, glCopyConvolutionFilter2D, ( GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height), return; ) + +GL_FUNCTION(void, glGetConvolutionFilter, ( GLenum target, GLenum format, GLenum type, GLvoid *image ), return; ) + +GL_FUNCTION(void, glGetConvolutionParameterfv, ( GLenum target, GLenum pname, GLfloat *params ), return; ) + +GL_FUNCTION(void, glGetConvolutionParameteriv, ( GLenum target, GLenum pname, GLint *params ), return; ) + +GL_FUNCTION(void, glSeparableFilter2D, ( GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column ), return; ) +GL_FUNCTION(void, glGetSeparableFilter, ( GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span ), return; ) + +GL_FUNCTION(void, glActiveTextureARB, (GLenum texture), return; ) +GL_FUNCTION(void, glClientActiveTextureARB, (GLenum texture), return; ) +GL_FUNCTION(void, glMultiTexCoord1dARB, (GLenum target, GLdouble s), return; ) +GL_FUNCTION(void, glMultiTexCoord1dvARB, (GLenum target, const GLdouble *v), return; ) +GL_FUNCTION(void, glMultiTexCoord1fARB, (GLenum target, GLfloat s), return; ) +GL_FUNCTION(void, glMultiTexCoord1fvARB, (GLenum target, const GLfloat *v), return; ) +GL_FUNCTION(void, glMultiTexCoord1iARB, (GLenum target, GLint s), return; ) +GL_FUNCTION(void, glMultiTexCoord1ivARB, (GLenum target, const GLint *v), return; ) +GL_FUNCTION(void, glMultiTexCoord1sARB, (GLenum target, GLshort s), return; ) +GL_FUNCTION(void, glMultiTexCoord1svARB, (GLenum target, const GLshort *v), return; ) +GL_FUNCTION(void, glMultiTexCoord2dARB, (GLenum target, GLdouble s, GLdouble t), return; ) +GL_FUNCTION(void, glMultiTexCoord2dvARB, (GLenum target, const GLdouble *v), return; ) +GL_FUNCTION(void, glMultiTexCoord2fARB, (GLenum target, GLfloat s, GLfloat t), return; ) +GL_FUNCTION(void, glMultiTexCoord2fvARB, (GLenum target, const GLfloat *v), return; ) +GL_FUNCTION(void, glMultiTexCoord2iARB, (GLenum target, GLint s, GLint t), return; ) +GL_FUNCTION(void, glMultiTexCoord2ivARB, (GLenum target, const GLint *v), return; ) +GL_FUNCTION(void, glMultiTexCoord2sARB, (GLenum target, GLshort s, GLshort t), return; ) +GL_FUNCTION(void, glMultiTexCoord2svARB, (GLenum target, const GLshort *v), return; ) +GL_FUNCTION(void, glMultiTexCoord3dARB, (GLenum target, GLdouble s, GLdouble t, GLdouble r), return; ) +GL_FUNCTION(void, glMultiTexCoord3dvARB, (GLenum target, const GLdouble *v), return; ) +GL_FUNCTION(void, glMultiTexCoord3fARB, (GLenum target, GLfloat s, GLfloat t, GLfloat r), return; ) +GL_FUNCTION(void, glMultiTexCoord3fvARB, (GLenum target, const GLfloat *v), return; ) +GL_FUNCTION(void, glMultiTexCoord3iARB, (GLenum target, GLint s, GLint t, GLint r), return; ) +GL_FUNCTION(void, glMultiTexCoord3ivARB, (GLenum target, const GLint *v), return; ) +GL_FUNCTION(void, glMultiTexCoord3sARB, (GLenum target, GLshort s, GLshort t, GLshort r), return; ) +GL_FUNCTION(void, glMultiTexCoord3svARB, (GLenum target, const GLshort *v), return; ) +GL_FUNCTION(void, glMultiTexCoord4dARB, (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q), return; ) +GL_FUNCTION(void, glMultiTexCoord4dvARB, (GLenum target, const GLdouble *v), return; ) +GL_FUNCTION(void, glMultiTexCoord4fARB, (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q), return; ) +GL_FUNCTION(void, glMultiTexCoord4fvARB, (GLenum target, const GLfloat *v), return; ) +GL_FUNCTION(void, glMultiTexCoord4iARB, (GLenum target, GLint s, GLint t, GLint r, GLint q), return; ) +GL_FUNCTION(void, glMultiTexCoord4ivARB, (GLenum target, const GLint *v), return; ) +GL_FUNCTION(void, glMultiTexCoord4sARB, (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q), return; ) +GL_FUNCTION(void, glMultiTexCoord4svARB, (GLenum target, const GLshort *v), return; ) + +/* EXT_paletted_texture */ +GL_FUNCTION(void, glColorTableEXT, (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data), return; ) + +/* EXT_compiled_vertex_array */ +GL_FUNCTION(void, glLockArraysEXT, (GLint first, GLsizei count), return; ) +GL_FUNCTION(void, glUnlockArraysEXT, (), return; ) + +/* EXT_fog_coord */ +GL_FUNCTION(void, glFogCoordfEXT, (GLfloat coord), return; ) +GL_FUNCTION(void, glFogCoordPointerEXT, (GLenum type, GLsizei stride, void *pointer), return; ) + +/* EXT_vertex_buffer */ +GL_FUNCTION(GLboolean, glAvailableVertexBufferEXT, (void), return GL_FALSE; ) +GL_FUNCTION(GLint, glAllocateVertexBufferEXT, (GLsizei size, GLint format, GLboolean preserve), return 0; ) +GL_FUNCTION(void*, glLockVertexBufferEXT, (GLint handle, GLsizei size), return NULL; ) +GL_FUNCTION(void, glUnlockVertexBufferEXT, (GLint handle), return; ) +GL_FUNCTION(void, glSetVertexBufferEXT, (GLint handle), return; ) +GL_FUNCTION(void, glOffsetVertexBufferEXT, (GLint handle, GLuint offset), return; ) +GL_FUNCTION(void, glFillVertexBufferEXT, (GLint handle, GLint first, GLsizei count), return; ) +GL_FUNCTION(void, glFreeVertexBufferEXT, (GLint handle), return; ) + diff --git a/platformX86UNIX/gl_types.h b/platformX86UNIX/gl_types.h new file mode 100644 index 0000000..0e1fdbc --- /dev/null +++ b/platformX86UNIX/gl_types.h @@ -0,0 +1,939 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 + +#endif // #ifndef _X86UNIX_GL_TYPES_H_ diff --git a/platformX86UNIX/glu_func.h b/platformX86UNIX/glu_func.h new file mode 100644 index 0000000..1d2f381 --- /dev/null +++ b/platformX86UNIX/glu_func.h @@ -0,0 +1,14 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +//------------------------------------------------------------------------------ +// GLU functions +//------------------------------------------------------------------------------ + +GL_FUNCTION(int, gluProject, (GLdouble objx, GLdouble objy, GLdouble objz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *winx, GLdouble *winy, GLdouble *winz), return 0; ) +GL_FUNCTION(int, gluUnProject, (GLdouble winx, GLdouble winy, GLdouble winz, const GLdouble modelMatrix[16], const GLdouble projMatrix[16], const GLint viewport[4], GLdouble *objx, GLdouble *objy, GLdouble *objz), return 0; ) + diff --git a/platformX86UNIX/platformAL.h b/platformX86UNIX/platformAL.h new file mode 100644 index 0000000..461cef3 --- /dev/null +++ b/platformX86UNIX/platformAL.h @@ -0,0 +1,147 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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/platformGL.h b/platformX86UNIX/platformGL.h new file mode 100644 index 0000000..4b70b91 --- /dev/null +++ b/platformX86UNIX/platformGL.h @@ -0,0 +1,2552 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMGL_H_ +#define _PLATFORMGL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* GL headers (here for now) */ +#include +#include +//#include + +#if 0 +/* X11 headers */ +#include +#include +#include +#include + +/* our "externs" used for doing stuff with the window and GL */ +extern GLXContext ctx; +extern Display *display; +extern int screen_num; +extern Window win; +extern Screen *screen_ptr; +#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] */ + + +/* 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_CLIENT_ALL_ATTRIB_BITS 0xFFFFFFFF + +/* + * GL_ARB_multitexture (ARB extension 1 and OpenGL 1.2.1) + */ +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 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 + +#endif /* GL_ARB_multitexture */ + + +/* + * GLext + */ +#ifndef 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 +#endif + +#ifndef 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 +#endif + +#ifndef 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 + +#ifndef 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 +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef 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 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef 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 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef 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 GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef 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 +#endif + +#ifndef 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 + +#ifndef 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 +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef 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 + +#ifndef 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 +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#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_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 + +#ifndef 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 +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#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 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef 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 + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifndef 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 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef 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 + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#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_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 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#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 + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef 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 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef 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 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef 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 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef 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 + +#ifndef 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 +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef 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 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef 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 +#endif + +#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 + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef 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 +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef 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 +#endif + +#ifndef 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 + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef 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 + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef 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 + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef 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 + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW_MATRIX1_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#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 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef 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 +#endif + +#ifndef 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 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef 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 + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef 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 + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef 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 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef 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 + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef 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 +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + +/* A forgotten token. */ +#define GL_CLAMP_TO_EDGE_EXT 0x812F + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +extern void (*glPixelTexGenSGIX) (GLenum); +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +extern void (*glHintPGI) (GLenum, GLint); +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_SGI_vertex_preclip +#define GL_SGI_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_EXT_vertex_buffer +#define GL_EXT_vertex_buffer 1 + +#define GL_V12MTVFMT_EXT 0x8702 +#define GL_V12MTNVFMT_EXT 0x8703 +#define GL_V12FTVFMT_EXT 0x8704 +#define GL_V12FMTVFMT_EXT 0x8705 + +#endif + + +/* + * GL state information. + */ +struct GLState +{ + bool suppARBMultitexture; + bool suppPackedPixels; + bool suppTexEnvAdd; + bool suppLockedArrays; + bool suppTextureEnvCombine; + bool suppVertexArrayRange; + bool suppFogCoord; + bool suppEdgeClamp; + bool suppTextureCompression; + bool suppS3TC; + bool suppFXT1; + bool suppTexAnisotropic; + bool suppPalettedTexture; + bool suppVertexBuffer; + bool suppSwapInterval; + unsigned int triCount[4]; + unsigned int primCount[4]; + unsigned int primMode; // 0-3 + + GLfloat maxAnisotropy; + GLint maxTextureUnits; +}; + +extern GLState gGLState; + +extern bool gOpenGLDisablePT; +extern bool gOpenGLDisableCVA; +extern bool gOpenGLDisableTEC; +extern bool gOpenGLDisableARBMT; +extern bool gOpenGLDisableFC; +extern bool gOpenGLDisableTCompress; +extern bool gOpenGLNoEnvColor; +extern float gOpenGLGammaCorrection; +extern bool gOpenGLNoDrawArraysAlpha; + +inline void dglSetRenderPrimType(unsigned int type) +{ + gGLState.primMode = type; +} + +inline void dglClearPrimMetrics() +{ + for(int i = 0; i < 4; i++) + gGLState.triCount[i] = gGLState.primCount[i] = 0; +} + +inline bool dglDoesSupportPalettedTexture() +{ + return gGLState.suppPalettedTexture && (gOpenGLDisablePT == false); +} + +inline bool dglDoesSupportCompiledVertexArray() +{ + return gGLState.suppLockedArrays && (gOpenGLDisableCVA == false); +} + +inline bool dglDoesSupportTextureEnvCombine() +{ + return gGLState.suppTextureEnvCombine && (gOpenGLDisableTEC == false); +} + +inline bool dglDoesSupportARBMultitexture() +{ + return gGLState.suppARBMultitexture && (gOpenGLDisableARBMT == false); +} + +inline bool dglDoesSupportVertexArrayRange() +{ + return gGLState.suppVertexArrayRange; +} + +inline bool dglDoesSupportFogCoord() +{ + return gGLState.suppFogCoord && (gOpenGLDisableFC == false); +} + +inline bool dglDoesSupportEdgeClamp() +{ + return gGLState.suppEdgeClamp; +} + +inline bool dglDoesSupportTextureCompression() +{ + return gGLState.suppTextureCompression && (gOpenGLDisableTCompress == false); +} + +inline bool dglDoesSupportS3TC() +{ + return gGLState.suppS3TC; +} + +inline bool dglDoesSupportFXT1() +{ + return gGLState.suppFXT1; +} + +inline bool dglDoesSupportTexEnvAdd() +{ + return gGLState.suppTexEnvAdd; +} + +inline bool dglDoesSupportTexAnisotropy() +{ + return gGLState.suppTexAnisotropic; +} + +inline bool dglDoesSupportVertexBuffer() +{ + return gGLState.suppVertexBuffer; +} + +inline GLfloat dglGetMaxAnisotropy() +{ + return gGLState.maxAnisotropy; +} + +inline GLint dglGetMaxTextureUnits() +{ + if (dglDoesSupportARBMultitexture()) + return gGLState.maxTextureUnits; + else + return 1; +} + +#define GLAPIENTRY + +/* EXT_paletted_texture */ +extern void ( GLAPIENTRY* glColorTableEXT)(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data); + +/* EXT_compiled_vertex_array */ +extern void ( GLAPIENTRY* glLockArraysEXT)(GLint first, GLsizei count); +extern void ( GLAPIENTRY* glUnlockArraysEXT)(); + +/* EXT_vertex_buffer */ +extern GLboolean (GLAPIENTRY* glAvailableVertexBufferEXT)(void); +extern GLint (GLAPIENTRY* glAllocateVertexBufferEXT)(GLsizei size, GLint format, GLboolean preserve); +extern void* (GLAPIENTRY* glLockVertexBufferEXT)(GLint handle, GLsizei size); +extern void (GLAPIENTRY* glUnlockVertexBufferEXT)(GLint handle); +extern void (GLAPIENTRY* glSetVertexBufferEXT)(GLint handle); +extern void (GLAPIENTRY* glOffsetVertexBufferEXT)(GLint handle, GLuint offset); +extern void (GLAPIENTRY* glFillVertexBufferEXT)(GLint handle, GLint first, GLsizei count); +extern void (GLAPIENTRY* glFreeVertexBufferEXT)(GLint handle); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/platformX86UNIX/platformX86UNIX.h b/platformX86UNIX/platformX86UNIX.h new file mode 100644 index 0000000..849eac4 --- /dev/null +++ b/platformX86UNIX/platformX86UNIX.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 + +/* files needed for X11 */ +//#include +//#include +//#include +//#include +//#include + +class x86UNIXPlatformState +{ + public: + S32 desktopBitsPixel; + S32 desktopWidth; + S32 desktopHeight; + S32 width; + S32 height; + S32 bpp; + bool videoInitted; + U32 currentTime; + struct __GLXcontextRec *ctx; +// GLXContext ctx; + +/* X11 specific */ +// Display *display; +// int screen_num; +// Window win; +// Screen *screen_ptr; + + x86UNIXPlatformState( ) { + width = 0; + height = 0; + bpp = 0; + videoInitted = false; + currentTime = 0; + } +}; + +extern x86UNIXPlatformState x86UNIXState; + +extern bool QGL_Init( const char* glName, const char* gluName ); +extern bool QGL_EXT_Init( void ); +extern void QGL_Shutdown( void ); + +extern void GetDesktopState( void ); + +extern char* dStrncat( char* d, const char* s, size_t n ); + +extern void x86UNIXInitTicks( void ); + +#ifdef DEDICATED +extern void x86UNIXPostQuitMessage( void ); +#endif + +extern void createFontInit( void ); +extern void createFontShutdown( void ); + +extern void installRedBookDevices( void ); + +extern void PlatformBlitInit( void ); + +extern U8 getHatState( U8 hat ); +extern void setHatState( U8 hat, U8 state ); + + +#endif diff --git a/platformX86UNIX/x86UNIXAsmBlit.cc b/platformX86UNIX/x86UNIXAsmBlit.cc new file mode 100644 index 0000000..90b4692 --- /dev/null +++ b/platformX86UNIX/x86UNIXAsmBlit.cc @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/terrData.h" +#include "math/mMath.h" +#include "dgl/dgl.h" +#include "dgl/gBitmap.h" +#include "terrain/terrRender.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) + { + } +// terrMipBlit = terrMipBlit_asm; +} diff --git a/platformX86UNIX/x86UNIXCPUInfo.cc b/platformX86UNIX/x86UNIXCPUInfo.cc new file mode 100644 index 0000000..27ff3ac --- /dev/null +++ b/platformX86UNIX/x86UNIXCPUInfo.cc @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include "console/console.h" +#include "core/stringTable.h" +#include + +Platform::SystemInfo_struct Platform::SystemInfo; +extern void PlatformBlitInit(); + +/* used in the asm */ +U32 time[2]; +U32 clockticks = 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; + + //-------------------------------------- + // calculate the aproximate Mhz of the CPU + const U32 MS_INTERVAL = 750; + __asm__(" + pushl %eax + pushl %edx + //db 0fh, 31h + rdtsc + movl %eax, (time) + movl %edx, (time+4) + popl %edx + popl %eax + "); + U32 ms = Platform::getRealMilliseconds(); + while ( Platform::getRealMilliseconds() < ms+MS_INTERVAL ) + { /* empty */ } + ms = Platform::getRealMilliseconds()-ms; + asm(" + pushl %eax + pushl %edx + //db 0fh, 31h + rdtsc + sub (time+4), %edx + sbb (time), %eax + mov %eax, (clockticks) + popl %edx + popl %eax + "); + U32 mhz = 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.cc b/platformX86UNIX/x86UNIXConsole.cc new file mode 100644 index 0000000..3897bb5 --- /dev/null +++ b/platformX86UNIX/x86UNIXConsole.cc @@ -0,0 +1,257 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platformX86UNIX/x86UNIXStdConsole.h" +#include "platform/event.h" +#include "platform/gameInterface.h" + +#include +#include +#include +#include +#include + +StdConsole *stdConsole = NULL; + +ConsoleFunction(enableWinConsole, void, 2, 2, "enableWinConsole(bool);") +{ + argc; + stdConsole->enable(dAtob(argv[1])); +} + +void StdConsole::create() +{ + stdConsole = new StdConsole(); +} + +void StdConsole::destroy() +{ + delete stdConsole; + stdConsole = NULL; +} + +void StdConsole::enable(bool enabled) +{ + stdConsoleEnabled = enabled; + if (stdConsoleEnabled) + { + stdOut = dup(1); + stdIn = dup(0); + stdErr = dup(2); + /* setup the proper terminal modes */ + struct termios termModes; + tcgetattr(fileno(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); + /* turn on non-blocking mode so that if no data is available at read(), then it returns */ + fcntl(fileno(stdin), F_SETFL, O_NONBLOCK); + + printf("%s", Con::getVariable("Con::Prompt")); + } +} + +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'; + + iCmdIndex = 0; + stdConsoleEnabled = false; + Con::addConsumer(stdConsoleConsumer); + inpos = 0; + lineOutput = false; +} + +void StdConsole::printf(const char *s, ...) +{ + static char buffer[512]; + long bytes; + va_list args; + + va_start(args, s); + vsprintf(buffer, s, args); + + 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 + int numEvents = read(stdIn, typedData, 64); + if (numEvents == -1) return; + typedData[numEvents] = '\0'; +//if (typedData[0] == 27) { +// fprintf(stdout, "1 = %c, 2 = %c\n", typedData[1], typedData[2]); +//} +// fprintf(stdout, "read %d elements\n",numEvents);fflush(NULL); +// +// int numEvents = read(stdIn, &key, sizeof(char)); + if (numEvents > 0) + { + char outbuf[256]; + 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; + case '\n': + /* new line */ + outbuf[outpos++] = '\n'; + + inbuf[inpos] = 0; + outbuf[outpos] = 0; + printf("%s", outbuf); + + S32 eventSize; + eventSize = 1; + + dStrcpy(postEvent.data, inbuf); + postEvent.size = eventSize + dStrlen(inbuf) + 1; + Game->postEvent(postEvent); + // 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: + if (typedData[++i] == 91) { + // 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/x86UNIXDedicatedStub.cc b/platformX86UNIX/x86UNIXDedicatedStub.cc new file mode 100644 index 0000000..02dd7af --- /dev/null +++ b/platformX86UNIX/x86UNIXDedicatedStub.cc @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +// $Id: x86UNIXDedicatedStub.cc,v 1.1 2002/03/16 19:43:09 jmquigs Exp $ + +// GL stubs +#ifndef NULL +#define NULL 0 +#endif + +#include "platformX86UNIX/platformGL.h" +#include "platform/platformAL.h" +#include "platform/platformInput.h" + +// declare stub functions +#define GL_FUNCTION(fn_return, fn_name, fn_args, fn_value) fn_return stub_##fn_name fn_args{ fn_value } +#include "platformX86UNIX/gl_func.h" +#include "platformX86UNIX/glu_func.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 "platformX86UNIX/gl_func.h" +#include "platformX86UNIX/glu_func.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 + + +// 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() {} +} + +// Platform Stubs +const char* Platform::getClipboard() { return ""; } +bool Platform::setClipboard(const char *text) { return false; } +GFont *createFont(const char *name, S32 size) { return NULL; } +void PlatformBlitInit( void ) {} + +// #include + +// void NotifySelectionEvent(XEvent& event) +// { +// } + +// Input stubs +void Input::init() {} +void Input::destroy() {} +bool Input::enable() { return false; } +void Input::disable() {} +void Input::activate() {} +void Input::deactivate() {} +void Input::reactivate() {} +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; } diff --git a/platformX86UNIX/x86UNIXFileio.cc b/platformX86UNIX/x86UNIXFileio.cc new file mode 100644 index 0000000..5d768ab --- /dev/null +++ b/platformX86UNIX/x86UNIXFileio.cc @@ -0,0 +1,785 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "core/fileio.h" +#include "core/tVector.h" +#include "core/stringTable.h" +#include "console/console.h" + +#include + +/* include sys/param.h for MAXPATHLEN */ +#include +#ifndef MAX_PATH +#define MAX_PATH MAXPATHLEN +#endif + +/* these are for reading directors, getting stats, etc. */ +#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); + +//-------------------------------------- Helper Functions +static void forwardslash(char *str) +{ + while(*str) + { + if(*str == '\\') + *str = '/'; + str++; + } +} + +static void backslash(char *str) +{ + while(*str) + { + if(*str == '/') + *str = '\\'; + str++; + } +} + +//----------------------------------------------------------------------------- +bool dFileDelete(const char * name) +{ + if(!name || (dStrlen(name) >= MAX_PATH)) + return(false); + if (remove(name) == -1) + return(false); + + return(true); +} + +bool dFileTouch(const char * name) +{ + // change the modified time to the current time (0byte WriteFile fails!) + return(utime(name, 0) != -1); +} + +//----------------------------------------------------------------------------- +// 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) +{ + static char filebuf[2048]; + dStrcpy(filebuf, filename); + filename = filebuf; + + 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(); + + int fd; + handle = (void *)dRealMalloc(sizeof(int)); + // create the appropriate type of file... + switch (openMode) + { + case Read: + fd = x86UNIXOpen(filename, O_RDONLY); + dMemcpy(handle, &fd, sizeof(int)); +// handle = (void *)&x86UNIXOpen(filename, O_RDONLY); + break; + case Write: + fd = x86UNIXOpen(filename, O_WRONLY | O_CREAT); + dMemcpy(handle, &fd, sizeof(int)); +// handle = (void *)x86UNIXOpen(filename, O_WRONLY | O_CREAT); + break; + case ReadWrite: + fd = x86UNIXOpen(filename, O_RDWR); + dMemcpy(handle, &fd, sizeof(int)); +// handle = (void *)x86UNIXOpen(filename, O_RDWR); + break; + case WriteAppend: + fd = x86UNIXOpen(filename, O_APPEND); + dMemcpy(handle, &fd, sizeof(int)); +// handle = (void *)x86UNIXOpen(filename, O_APPEND); + break; + default: + AssertFatal(false, "File::open: bad access mode"); // impossible + } + +#ifdef DEBUG +// fprintf(stdout,"fd = %d, handle = %d\n", fd, *((int *)handle)); +#endif + + if (*((int *)handle) == -1) // 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(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; + 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 (fdatasync(*((int *)handle)) == 0) + return currentStatus = Ok; // success! + else + return setStatus(); // unsuccessful +} + +//----------------------------------------------------------------------------- +// 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 (NULL != handle) + { + if (x86UNIXClose(*((int *)handle)) != 0) + return setStatus(); // unsuccessful + } + handle = (void *)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 EINVAL: + case EBADFD: + case EIO: + return currentStatus = IOError; + default: + return currentStatus = Ok; + } +} + +//----------------------------------------------------------------------------- +// 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 ((FILE *)handle == NULL || dst == NULL) { +//fprintf(stdout, "ack!\n"); +//} +//fprintf(stdout, "%d-%p\n",strlen(dst),dst); +//fprintf(stdout, "feof() = %d\n",feof((FILE *)handle)); +//fprintf(stdout, "sizeof(U32) = %d\nsize = %d\n",sizeof(U32), size); +//fprintf(stdout, "bytesRead = %d\n", *((U32 *)bytes) = x86UNIXRead(fileno((FILE *)handle), dst, size) ); + +// (*((U32 *)bytes)) = fread(dst, sizeof(U32), size, (FILE *)handle); +//fprintf(stdout, "bytes = %d\n", (*(int *)bytes));fflush(NULL); +//exit(*((int *)bytes)); +// if (0 != (*((size_t *)bytes) = fread(dst, sizeof(U32), size, (FILE *)handle)) ) + 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) +{ + 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 + { + long lastBytes; + long *bytes = (NULL == bytesWritten) ? &lastBytes : (long *)bytesWritten; + if (0 != (*((U32 *)bytes) = x86UNIXWrite(*((int *)handle), src, size))) + 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 > b) + return 1; + if(a < b) + return -1; + if(a > b) + return 1; + if(a < b) + return -1; + return 0; +} + + +static bool recurseDumpPath(const char *path, 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; + + char child[1024]; + dSprintf(child, sizeof(child), "%s/%s", path, fEntry->d_name); + recurseDumpPath(child, pattern, fileVector); + } + else + { + // File + + // add it to the list + fileVector.increment(); + Platform::FileInfo& rInfo = fileVector.last(); + + rInfo.pFullPath = StringTable->insert(path); + rInfo.pFileName = StringTable->insert(fEntry->d_name); + rInfo.fileSize = fStat.st_size; + } + + } while( (fEntry = readdir(directory)) != NULL ); + + closedir(directory); + return true; +} + +//static bool _recurseDumpPath(const char* in_pBasePath, +// const char* in_pCurPath, +// Vector& out_rFileVector) +//{ +// char buf[1024]; +// char curPath[1024]; +// char basePath[1024]; +// char scratchBuf[1024]; +// +// if(in_pCurPath) +// dStrcpy(curPath, in_pCurPath); +// else +// curPath[0] = 0; +// +// dStrcpy(basePath, in_pBasePath); +// in_pBasePath = basePath; +// +// +// if (curPath[0] != '\0') +// dSprintf(buf, sizeof(buf), "%s/%s", basePath, curPath); +// else +// dSprintf(buf, sizeof(buf), "%s", basePath); +// +// +// backslash(buf); +// DIR *directory = opendir(buf); // open the directory +// +// if (directory == (DIR *)NULL) +// return false; +// +// struct dirent *fEntry; +// fEntry = readdir(directory); // read the first "file" in the directory +// +// while (fEntry != (struct dirent *)NULL) { +// char filename[BUFSIZ+1]; +// struct stat fStat; +// +// dSprintf(filename, sizeof(filename), "%s/%s", buf, fEntry->d_name); // "construct" the file name +// stat(filename, &fStat); // get the file stats +// +// /* we AND the st_mode and S_IFMT to get the type of file */ +// if ( (fStat.st_mode & S_IFMT) == S_IFDIR ) { +// // Directory +// if (filename[0] != '.') { +// scratchBuf[0] = '\0'; +// if (curPath[0] != '\0') { +// dStrcpy(scratchBuf, curPath); +// dStrcat(scratchBuf, "/"); +// } +// dStrcat(scratchBuf, filename); +// +// _recurseDumpPath(basePath, scratchBuf, out_rFileVector); +// } +// } else { +// // File +// out_rFileVector.increment(); +// Platform::FileInfo& rInfo = out_rFileVector.last(); +// +// if (curPath[0] != '\0') { +// dSprintf(scratchBuf, sizeof(scratchBuf), "%s/%s", basePath, curPath); +// rInfo.pFullPath = StringTable->insert(scratchBuf); +// rInfo.pVirtPath = StringTable->insert(curPath); +// } else { +// rInfo.pFullPath = StringTable->insert(basePath); +// rInfo.pVirtPath = NULL; +// } +// rInfo.pFileName = StringTable->insert(filename); +// rInfo.fileSize = fStat.st_size; +// } +// +// if ( (fEntry = readdir(directory)) == (struct dirent *)NULL) { +// closedir(directory); +// fEntry = (struct dirent *)NULL; +// } +// } +// +// return true; +//} + +//-------------------------------------- + +bool Platform::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 = (FileTime *)&fStat.st_ctime; + } + if(modifyTime) + { + modifyTime = (FileTime *)&fStat.st_mtime; + } + + return true; +} + +//-------------------------------------- +bool Platform::createPath(const char *file) +{ + char pathbuf[1024]; + const char *dir; + pathbuf[0] = 0; + U32 pathLen = 0; + + while((dir = dStrchr(file, '/')) != NULL) + { + dStrncpy(pathbuf + pathLen, file, dir - file); + pathbuf[pathLen + dir-file] = 0; + bool ret = mkdir(pathbuf, 0666); + pathLen += dir - file; + pathbuf[pathLen++] = '/'; + file = dir + 1; + } + return true; +} + +/* this will be a pain and it's not being used so we are leaving it out, for now :-D + - rjp +*/ +/*************************************************************************************** +bool Platform::cdFileExists(const char *filePath, const char *volumeName, S32 serialNum) +{ + if (!filePath || !filePath[0]) + return true; + + //first find the CD device... + char fileBuf[256]; + char drivesBuf[256]; + S32 length = GetLogicalDriveStrings(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) && + GetVolumeInformation((const char*)drivePtr, &driveVolume[0], (unsigned long)255, + (unsigned long*)&driveSerial, (unsigned long*)&driveFNLength, + (unsigned long*)&driveFlags, &driveFileSystem[0], (unsigned long)255)) + { +#if defined (DEBUG) || defined (INTERNAL_RELEASE) + 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 (DEBUG) || defined (INTERNAL_RELEASE) + Con::printf("Looking for file: %s on %s", fileBuf, driveVolume); +#endif + WIN32_FIND_DATA findData; + HANDLE h = FindFirstFile(fileBuf, &findData); + if(h != INVALID_HANDLE_VALUE) + { + FindClose(h); + return true; + } + FindClose(h); + } + } + + //check the next drive + drivePtr += dStrlen(drivePtr) + 1; + } + + return false; +} +***************************************************************************************/ + +//-------------------------------------- +bool Platform::dumpPath(const char *path, Vector &fileVector) +{ + return recurseDumpPath(path, "*", fileVector); +} + +//-------------------------------------- +StringTableEntry Platform::getWorkingDirectory() +{ + static StringTableEntry cwd = NULL; + + if (!cwd) + { + char cwd_buf[2048]; + getcwd(cwd_buf, 2047); + cwd = StringTable->insert(cwd_buf); + } + return cwd; +} + +//-------------------------------------- +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; +} + + +//-------------------------------------- +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[MAXPATHLEN]; + struct stat fStat; + + dSprintf(dirBuf, sizeof(dirBuf), "%s/%s", pParent, fEntry->d_name); + if (stat(dirBuf, &fStat) < 0) + return false; + // 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; +} diff --git a/platformX86UNIX/x86UNIXFont.cc b/platformX86UNIX/x86UNIXFont.cc new file mode 100644 index 0000000..e887bfa --- /dev/null +++ b/platformX86UNIX/x86UNIXFont.cc @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "dgl/gFont.h" +#include "dgl/gBitmap.h" +#include "math/mRect.h" +#include "console/console.h" + +void createFontInit(void); +void createFontShutdown(void); +//void CopyCharToBitmap(GBitmap *pDstBMP, HDC hSrcHDC, const RectI &r); + +void createFontInit() +{ +// 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; +// } +// } +//} + +GFont *createFont(const char *name, S32 size) +{ +/* + if(!name) + return NULL; + if(size < 1) + return NULL; + + + HFONT hNewFont = CreateFont(size,0,0,0,0,0,0,0,0,0,0,0,0,name); + if(!hNewFont) + return NULL; + + GFont *retFont = new GFont; + static U8 scratchPad[65536]; + + TEXTMETRIC textMetric; + COLORREF backgroundColorRef = RGB( 0, 0, 0); + COLORREF foregroundColorRef = RGB(255, 255, 255); + + SelectObject(fontHDC, fontBMP); + SelectObject(fontHDC, hNewFont); + SetBkColor(fontHDC, backgroundColorRef); + SetTextColor(fontHDC, foregroundColorRef); + GetTextMetrics(fontHDC, &textMetric); + 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; + S32 glyphCount = 0; + + for(S32 i = 32; i < 256; i++) + { + if(GetGlyphOutline( + fontHDC, // handle of device context + i, // 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) + { + glyphCount++; + U32 rowStride = (metrics.gmBlackBoxX + 3) & ~3; // DWORD aligned + U32 size = rowStride * metrics.gmBlackBoxY; + for(U32 j = 0; j < size; j++) + { + U32 pad = U32(scratchPad[j]) << 2; + if(pad > 255) + pad = 255; + scratchPad[j] = pad; + } + S32 inc = metrics.gmCellIncX; + if(inc < 0) + inc = -inc; + retFont->insertBitmap(i, scratchPad, rowStride, metrics.gmBlackBoxX, metrics.gmBlackBoxY, metrics.gmptGlyphOrigin.x, metrics.gmptGlyphOrigin.y, metrics.gmCellIncX); + } + else + { + char b = i; + SIZE size; + GetTextExtentPoint32(fontHDC, &b, 1, &size); + if(size.cx) + retFont->insertBitmap(i, scratchPad, 0, 0, 0, 0, 0, size.cx); + } + } + retFont->pack(textMetric.tmHeight, textMetric.tmAscent); + //clean up local vars + DeleteObject(hNewFont); + + if (!glyphCount) + Con::errorf(ConsoleLogEntry::General,"Error creating font: %s %d",name, size); + + return retFont; +*/ +} diff --git a/platformX86UNIX/x86UNIXGL.cc b/platformX86UNIX/x86UNIXGL.cc new file mode 100644 index 0000000..2b9d999 --- /dev/null +++ b/platformX86UNIX/x86UNIXGL.cc @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformGL.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include +#include "console/console.h" +#include "console/consoleTypes.h" + +#include +#include +#include + +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; + +GLboolean (GLAPIENTRY* glAvailableVertexBufferEXT)(void); +GLint (GLAPIENTRY* glAllocateVertexBufferEXT)(GLsizei size, GLint format, GLboolean preserve); +void* (GLAPIENTRY* glLockVertexBufferEXT)(GLint handle, GLsizei size); +void (GLAPIENTRY* glUnlockVertexBufferEXT)(GLint handle); +void (GLAPIENTRY* glSetVertexBufferEXT)(GLint handle); +void (GLAPIENTRY* glOffsetVertexBufferEXT)(GLint handle, GLuint offset); +void (GLAPIENTRY* glFillVertexBufferEXT)(GLint handle, GLint first, GLsizei count); +void (GLAPIENTRY* glFreeVertexBufferEXT)(GLint handle); + +// #define GL_EXT_abgr 1 +// #define GL_EXT_blend_color 1 +// #define GL_EXT_blend_minmax 1 +// #define GL_EXT_blend_subtract 1 +// #define GL_EXT_compiled_vertex_array 1 +// #define GL_ARB_multitexture 1 +// #define GL_APPLE_specular_vector 1 +// #define GL_APPLE_transform_hint 1 + +bool QGL_EXT_Init( ) +{ + // Load extensions... + // + const char* pExtString = reinterpret_cast(glGetString(GL_EXTENSIONS)); + + // EXT_compiled_vertex_array + if (GL_EXT_compiled_vertex_array) //pExtString && dStrstr(pExtString, (const char*)"GL_EXT_compiled_vertex_array") != NULL) + { + //glLockArraysEXT = dllLockArraysEXT = (glLockArrays_t) qwglGetProcAddress("glLockArraysEXT"); + //glUnlockArraysEXT = dllUnlockArraysEXT = (glUnlockArrays_t) qwglGetProcAddress("glUnlockArraysEXT"); + gGLState.suppLockedArrays = true; + } else { + //glLockArraysEXT = dllLockArraysEXT = NULL; + //glUnlockArraysEXT = dllUnlockArraysEXT = NULL; + gGLState.suppLockedArrays = false; + } + + // ARB_multitexture + if (GL_ARB_multitexture) //pExtString && dStrstr(pExtString, (const char*)"GL_ARB_multitexture") != NULL) + { + //glActiveTextureARB = dllActiveTextureARB = (glActiveTextureARB_t) qwglGetProcAddress("glActiveTextureARB"); + //glClientActiveTextureARB = dllClientActiveTextureARB = (glClientActiveTextureARB_t) qwglGetProcAddress("glClientActiveTextureARB"); + //glMultiTexCoord2fARB = dllMultiTexCoord2fARB = (glMultiTexCoord2fARB_t) qwglGetProcAddress("glMultiTexCoord2fARB"); + //glMultiTexCoord2fvARB = dllMultiTexCoord2fvARB = (glMultiTexCoord2fvARB_t) qwglGetProcAddress("glMultiTexCoord2fvARB"); + gGLState.suppARBMultitexture = true; + } else { + //glActiveTextureARB = dllActiveTextureARB = NULL; + //glClientActiveTextureARB = dllClientActiveTextureARB = NULL; + //glMultiTexCoord2fARB = dllMultiTexCoord2fARB = NULL; + //glMultiTexCoord2fvARB = dllMultiTexCoord2fvARB = NULL; + gGLState.suppARBMultitexture = false; + } + + // NV_vertex_array_range + if (false) //pExtString && dStrstr(pExtString, (const char*)"GL_NV_vertex_array_range") != NULL) + { + //glVertexArrayRangeNV = dllVertexArrayRangeNV = (glVertexArrayRange_t) qwglGetProcAddress("glVertexArrayRangeNV"); + //glFlushVertexArrayRangeNV = dllFlushVertexArrayRangeNV = (glFlushVertexArrayRange_t) qwglGetProcAddress("glFlushVertexArrayRangeNV"); + gGLState.suppVertexArrayRange = true; + } else { + //glVertexArrayRangeNV = dllVertexArrayRangeNV = NULL; + //glFlushVertexArrayRangeNV = dllFlushVertexArrayRangeNV = NULL; + gGLState.suppVertexArrayRange = false; + } + + // EXT_fog_coord + if (false) //pExtString && dStrstr(pExtString, (const char*)"GL_EXT_fog_coord") != NULL) + { + //glFogCoordfEXT = dllFogCoordfEXT = (glFogCoordf_t) qwglGetProcAddress("glFogCoordfEXT"); + //glFogCoordPointerEXT = dllFogCoordPointerEXT = (glFogCoordPointer_t) qwglGetProcAddress("glFogCoordPointerEXT"); + gGLState.suppFogCoord = true; + } else { + //glFogCoordfEXT = dllFogCoordfEXT = NULL; + //glFogCoordPointerEXT = dllFogCoordPointerEXT = NULL; + gGLState.suppFogCoord = false; + } + + // Binary states, i.e., no supporting functions + // EXT_packed_pixels + // EXT_texture_env_combine + // + gGLState.suppPackedPixels = false; //pExtString? (dStrstr(pExtString, (const char*)"GL_EXT_packed_pixels") != NULL) : false; + gGLState.suppTextureEnvCombine = false; //pExtString? (dStrstr(pExtString, (const char*)"GL_EXT_texture_env_combine") != NULL) : false; + + Con::printf("OpenGL Init: Enabled Extensions"); + if (gGLState.suppARBMultitexture) Con::printf(" ARB_multitexture"); + 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"); + + Con::warnf(ConsoleLogEntry::General, "OpenGL Init: Disabled Extensions"); + if (!gGLState.suppARBMultitexture) Con::warnf(ConsoleLogEntry::General, " ARB_multitexture"); + if (!gGLState.suppLockedArrays) Con::warnf(ConsoleLogEntry::General, " EXT_compiled_vertex_array"); + if (!gGLState.suppVertexArrayRange) Con::warnf(ConsoleLogEntry::General, " NV_vertex_array_range"); + if (!gGLState.suppTextureEnvCombine) Con::warnf(ConsoleLogEntry::General, " EXT_texture_env_combine"); + if (!gGLState.suppPackedPixels) Con::warnf(ConsoleLogEntry::General, " EXT_packed_pixels"); + if (!gGLState.suppFogCoord) Con::warnf(ConsoleLogEntry::General, " EXT_fog_coord"); + Con::printf(""); + + return true; +} diff --git a/platformX86UNIX/x86UNIXGLX.h b/platformX86UNIX/x86UNIXGLX.h new file mode 100644 index 0000000..9b0229b --- /dev/null +++ b/platformX86UNIX/x86UNIXGLX.h @@ -0,0 +1,377 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIXGLX_H_ +#define _X86UNIXGLX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/***************** GLX TOKENS ***************/ + +/* $XFree86: xc/include/GL/glxtokens.h,v 1.4 2000/06/17 00:02:46 martin Exp $ */ +/* +** 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 Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: The application programming interfaces +** established by SGI in conjunction with the Original Code are The +** OpenGL(R) Graphics System: A Specification (Version 1.2.1), released +** April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version +** 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X +** Window System(R) (Version 1.3), released October 19, 1998. This software +** was created using the OpenGL(R) version 1.2.1 Sample Implementation +** published by SGI, but has not been independently verified as being +** compliant with the OpenGL(R) version 1.2.1 Specification. +*/ + +#define GLX_VERSION_1_1 1 +#define GLX_VERSION_1_2 1 +#define GLX_VERSION_1_3 1 + +/* +** Visual Config Attributes (glXGetConfig, glXGetFBConfigAttrib) +*/ +#define GLX_USE_GL 1 /* support GLX rendering */ +#define GLX_BUFFER_SIZE 2 /* depth of the color buffer */ +#define GLX_LEVEL 3 /* level in plane stacking */ +#define GLX_RGBA 4 /* true if RGBA mode */ +#define GLX_DOUBLEBUFFER 5 /* double buffering supported */ +#define GLX_STEREO 6 /* stereo buffering supported */ +#define GLX_AUX_BUFFERS 7 /* number of aux buffers */ +#define GLX_RED_SIZE 8 /* number of red component bits */ +#define GLX_GREEN_SIZE 9 /* number of green component bits */ +#define GLX_BLUE_SIZE 10 /* number of blue component bits */ +#define GLX_ALPHA_SIZE 11 /* number of alpha component bits */ +#define GLX_DEPTH_SIZE 12 /* number of depth bits */ +#define GLX_STENCIL_SIZE 13 /* number of stencil bits */ +#define GLX_ACCUM_RED_SIZE 14 /* number of red accum bits */ +#define GLX_ACCUM_GREEN_SIZE 15 /* number of green accum bits */ +#define GLX_ACCUM_BLUE_SIZE 16 /* number of blue accum bits */ +#define GLX_ACCUM_ALPHA_SIZE 17 /* number of alpha accum bits */ +/* +** FBConfig-specific attributes +*/ +#define GLX_X_VISUAL_TYPE 0x22 +#define GLX_CONFIG_CAVEAT 0x20 /* Like visual_info VISUAL_CAVEAT_EXT */ +#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_DRAWABLE_TYPE 0x8010 +#define GLX_RENDER_TYPE 0x8011 +#define GLX_X_RENDERABLE 0x8012 +#define GLX_FBCONFIG_ID 0x8013 +#define GLX_MAX_PBUFFER_WIDTH 0x8016 +#define GLX_MAX_PBUFFER_HEIGHT 0x8017 +#define GLX_MAX_PBUFFER_PIXELS 0x8018 +#define GLX_VISUAL_ID 0x800B + +/* +** Error return values from glXGetConfig. Success is indicated by +** a value of 0. +*/ +#define GLX_BAD_SCREEN 1 /* screen # is bad */ +#define GLX_BAD_ATTRIBUTE 2 /* attribute to get is bad */ +#define GLX_NO_EXTENSION 3 /* no glx extension on server */ +#define GLX_BAD_VISUAL 4 /* visual # not known by GLX */ +#define GLX_BAD_CONTEXT 5 /* returned only by import_context EXT? */ +#define GLX_BAD_VALUE 6 /* returned only by glXSwapIntervalSGI? */ +#define GLX_BAD_ENUM 7 /* unused? */ + +/* FBConfig attribute values */ + +/* +** Generic "don't care" value for glX ChooseFBConfig attributes (except +** GLX_LEVEL) +*/ +#define GLX_DONT_CARE 0xFFFFFFFF + +/* GLX_RENDER_TYPE bits */ +#define GLX_RGBA_BIT 0x00000001 +#define GLX_COLOR_INDEX_BIT 0x00000002 + +/* GLX_DRAWABLE_TYPE bits */ +#define GLX_WINDOW_BIT 0x00000001 +#define GLX_PIXMAP_BIT 0x00000002 +#define GLX_PBUFFER_BIT 0x00000004 + +/* GLX_CONFIG_CAVEAT attribute values */ +#define GLX_NONE 0x8000 +#define GLX_SLOW_CONFIG 0x8001 +#define GLX_NON_CONFORMANT_CONFIG 0x800D + +/* GLX_X_VISUAL_TYPE attribute values */ +#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 + +/* GLX_TRANSPARENT_TYPE attribute values */ +/* #define GLX_NONE 0x8000 */ +#define GLX_TRANSPARENT_RGB 0x8008 +#define GLX_TRANSPARENT_INDEX 0x8009 + +/* glXCreateGLXPbuffer attributes */ +#define GLX_PRESERVED_CONTENTS 0x801B +#define GLX_LARGEST_PBUFFER 0x801C +#define GLX_PBUFFER_HEIGHT 0x8040 /* New for GLX 1.3 */ +#define GLX_PBUFFER_WIDTH 0x8041 /* New for GLX 1.3 */ + +/* glXQueryGLXPBuffer attributes */ +#define GLX_WIDTH 0x801D +#define GLX_HEIGHT 0x801E +#define GLX_EVENT_MASK 0x801F + +/* glXCreateNewContext render_type attribute values */ +#define GLX_RGBA_TYPE 0x8014 +#define GLX_COLOR_INDEX_TYPE 0x8015 + +/* glXQueryContext attributes */ +/* #define GLX_FBCONFIG_ID 0x8013 */ +/* #define GLX_RENDER_TYPE 0x8011 */ +#define GLX_SCREEN 0x800C + +/* glXSelectEvent event mask bits */ +#define GLX_PBUFFER_CLOBBER_MASK 0x08000000 + +/* GLXPbufferClobberEvent event_type values */ +#define GLX_DAMAGED 0x8020 +#define GLX_SAVED 0x8021 + +/* GLXPbufferClobberEvent draw_type values */ +#define GLX_WINDOW 0x8022 +#define GLX_PBUFFER 0x8023 + +/* GLXPbufferClobberEvent buffer_mask bits */ +#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 + +/* +** Extension return values from glXGetConfig. These are also +** accepted as parameter values for glXChooseVisual. +*/ + +#define GLX_X_VISUAL_TYPE_EXT 0x22 /* visual_info extension type */ +#define GLX_TRANSPARENT_TYPE_EXT 0x23 /* visual_info extension */ +#define GLX_TRANSPARENT_INDEX_VALUE_EXT 0x24 /* visual_info extension */ +#define GLX_TRANSPARENT_RED_VALUE_EXT 0x25 /* visual_info extension */ +#define GLX_TRANSPARENT_GREEN_VALUE_EXT 0x26 /* visual_info extension */ +#define GLX_TRANSPARENT_BLUE_VALUE_EXT 0x27 /* visual_info extension */ +#define GLX_TRANSPARENT_ALPHA_VALUE_EXT 0x28 /* visual_info extension */ + +/* Property values for visual_type */ +#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 + +/* Property values for transparent pixel */ +#define GLX_NONE_EXT 0x8000 +#define GLX_TRANSPARENT_RGB_EXT 0x8008 +#define GLX_TRANSPARENT_INDEX_EXT 0x8009 + +/* Property values for visual_rating */ +#define GLX_VISUAL_CAVEAT_EXT 0x20 /* visual_rating extension type */ +#define GLX_SLOW_VISUAL_EXT 0x8001 +#define GLX_NON_CONFORMANT_VISUAL_EXT 0x800D + +/* +** Names for attributes to glXGetClientString. +*/ +#define GLX_VENDOR 0x1 +#define GLX_VERSION 0x2 +#define GLX_EXTENSIONS 0x3 + +/* +** Names for attributes to glXQueryContextInfoEXT. +*/ +#define GLX_SHARE_CONTEXT_EXT 0x800A /* id of share context */ +#define GLX_VISUAL_ID_EXT 0x800B /* id of context's visual */ +#define GLX_SCREEN_EXT 0x800C /* screen number */ + +/* GLX Extension Strings */ +#define GLX_EXT_import_context 1 +#define GLX_EXT_visual_info 1 +#define GLX_EXT_visual_rating 1 +#define GLX_ARB_get_proc_address 1 + +/************** END OF GLX TOKENS ***********/ + +/* $XFree86: xc/include/GL/glx.h,v 1.7 2000/06/30 18:27:00 dawes Exp $ */ +/* +** 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 Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: The application programming interfaces +** established by SGI in conjunction with the Original Code are The +** OpenGL(R) Graphics System: A Specification (Version 1.2.1), released +** April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version +** 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X +** Window System(R) (Version 1.3), released October 19, 1998. This software +** was created using the OpenGL(R) version 1.2.1 Sample Implementation +** published by SGI, but has not been independently verified as being +** compliant with the OpenGL(R) version 1.2.1 Specification. +*/ + +#include +#include + +/* +** GLX resources. +*/ +typedef XID GLXContextID; +typedef XID GLXPixmap; +typedef XID GLXDrawable; +typedef XID GLXPbuffer; +typedef XID GLXWindow; +typedef XID GLXFBConfigID; + +/* +** GLXContext is a pointer to opaque data. +*/ +typedef struct __GLXcontextRec *GLXContext; + +/* +** GLXFBConfig is a pointer to opaque data. +*/ +typedef struct __GLXFBConfigRec *GLXFBConfig; + +/************************************************************************/ + +extern XVisualInfo* glXChooseVisual (Display *dpy, int screen, int *attribList); +extern void glXCopyContext (Display *dpy, GLXContext src, GLXContext dst, unsigned long mask); +extern GLXContext glXCreateContext (Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct); +extern GLXPixmap glXCreateGLXPixmap (Display *dpy, XVisualInfo *vis, Pixmap pixmap); +extern void glXDestroyContext (Display *dpy, GLXContext ctx); +extern void glXDestroyGLXPixmap (Display *dpy, GLXPixmap pix); +extern int glXGetConfig (Display *dpy, XVisualInfo *vis, int attrib, int *value); +extern GLXContext glXGetCurrentContext (void); +extern GLXDrawable glXGetCurrentDrawable (void); +extern Bool glXIsDirect (Display *dpy, GLXContext ctx); +extern Bool glXMakeCurrent (Display *dpy, GLXDrawable drawable, GLXContext ctx); +extern Bool glXQueryExtension (Display *dpy, int *errorBase, int *eventBase); +extern Bool glXQueryVersion (Display *dpy, int *major, int *minor); +extern void glXSwapBuffers (Display *dpy, GLXDrawable drawable); +extern void glXUseXFont (Font font, int first, int count, int listBase); +extern void glXWaitGL (void); +extern void glXWaitX (void); +extern const char * glXGetClientString (Display *dpy, int name ); +extern const char * glXQueryServerString (Display *dpy, int screen, int name ); +extern const char * glXQueryExtensionsString (Display *dpy, int screen ); + +/* New for GLX 1.3 */ +extern GLXFBConfig * glXGetFBConfigs (Display *dpy, int screen, int *nelements); +extern GLXFBConfig * glXChooseFBConfig (Display *dpy, int screen, const int *attrib_list, int *nelements); +extern int glXGetFBConfigAttrib (Display *dpy, GLXFBConfig config, int attribute, int *value); +extern XVisualInfo * glXGetVisualFromFBConfig (Display *dpy, GLXFBConfig config); +extern GLXWindow glXCreateWindow (Display *dpy, GLXFBConfig config, Window win, const int *attrib_list); +extern void glXDestroyWindow (Display *dpy, GLXWindow win); +extern GLXPixmap glXCreatePixmap (Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attrib_list); +extern void glXDestroyPixmap (Display *dpy, GLXPixmap pixmap); +extern GLXPbuffer glXCreatePbuffer (Display *dpy, GLXFBConfig config, const int *attrib_list); +extern void glXDestroyPbuffer (Display *dpy, GLXPbuffer pbuf); +extern void glXQueryDrawable (Display *dpy, GLXDrawable draw, int attribute, unsigned int *value); +extern GLXContext glXCreateNewContext (Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); +extern Bool glXMakeContextCurrent (Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +extern GLXDrawable glXGetCurrentReadDrawable (void); +extern Display * glXGetCurrentDisplay (void); +extern int glXQueryContext (Display *dpy, GLXContext ctx, int attribute, int *value); +extern void glXSelectEvent (Display *dpy, GLXDrawable draw, unsigned long event_mask); +extern void glXGetSelectedEvent (Display *dpy, GLXDrawable draw, unsigned long *event_mask); + +/*** SGI GLX extensions */ +extern GLXContextID glXGetContextIDEXT (const GLXContext ctx); +extern GLXDrawable glXGetCurrentDrawableEXT (void); +extern GLXContext glXImportContextEXT (Display *dpy, GLXContextID contextID); +extern void glXFreeContextEXT (Display *dpy, GLXContext ctx); +extern int glXQueryContextInfoEXT (Display *dpy, GLXContext ctx, int attribute, int *value); + +extern void (*glXGetProcAddressARB(const GLubyte *procName))( void ); + +/*** Should these go here, or in another header? */ +/* +** GLX Events +*/ +typedef struct { + int event_type; /* GLX_DAMAGED or GLX_SAVED */ + int draw_type; /* GLX_WINDOW or GLX_PBUFFER */ + unsigned long serial; /* # of last request processed by server */ + Bool send_event; /* true if this came for SendEvent request */ + Display *display; /* display the event was read from */ + GLXDrawable drawable; /* XID of Drawable */ + unsigned int buffer_mask; /* mask indicating which buffers are affected */ + unsigned int aux_buffer; /* which aux buffer was affected */ + int x, y; + int width, height; + int count; /* if nonzero, at least this many more */ +} GLXPbufferClobberEvent; + +typedef union __GLXEvent { + GLXPbufferClobberEvent glxpbufferclobber; + long pad[24]; +} GLXEvent; + +/* end of GLX stuff */ + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/platformX86UNIX/x86UNIXIO.cc b/platformX86UNIX/x86UNIXIO.cc new file mode 100644 index 0000000..7f058c3 --- /dev/null +++ b/platformX86UNIX/x86UNIXIO.cc @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +int x86UNIXOpen(const char *path, int oflag) +{ + return open(path, oflag); +} + +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.cc b/platformX86UNIX/x86UNIXInput.cc new file mode 100644 index 0000000..fc2d542 --- /dev/null +++ b/platformX86UNIX/x86UNIXInput.cc @@ -0,0 +1,831 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 +#include +#include + +#ifdef LOG_INPUT +#include +#include +#endif + +// Static class variables: +InputManager* Input::smManager; +bool Input::smActive; + +#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(); + smActive = false; + +// struct termios termModes; +// tcgetattr(fileno(stdin), &termModes); +// termModes.c_lflag &= ~ICANON; // enable non-canonical mode +// termModes.c_cc[VMIN] = 1; +// termModes.c_cc[VTIME] = 0; +// tcsetattr(fileno(stdin), TCSAFLUSH, &termModes); + +#ifdef LOG_INPUT + struct tm* newTime; + time_t aclock; + time( &aclock ); + newTime = localtime( &aclock ); + asctime( newTime ); + + gInputLog = CreateFile( "input.log", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + log( "Input log opened at %s\n", asctime( newTime ) ); +#endif +/* + + smActive = false; + + 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 + +*/ + fillAsciiTable(); + Con::printf( "" ); +} + +//------------------------------------------------------------------------------ +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 ); + 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() +{ +#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() +{ +/* + DInputDevice::resetModifierKeys(); + 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->isKeyboardEnabled() ) + dInputManager->activateKeyboard(); + + if ( Video::isFullScreen() ) + { + // DirectInput Mouse Hook-Up: + if ( dInputManager->isMouseEnabled() ) + dInputManager->activateMouse(); + } + else + dInputManager->deactivateMouse(); + + if ( dInputManager->isJoystickEnabled() ) + dInputManager->activateJoystick(); + } + } +*/ +} + +//------------------------------------------------------------------------------ +void Input::deactivate() +{ + if ( smManager && smManager->isEnabled() && smActive ) + { +#ifdef LOG_INPUT + Input::log( "Deactivating Input...\n" ); +#endif + + smActive = false; + Con::printf( "Input deactivated." ); + } +} + +//------------------------------------------------------------------------------ +void Input::reactivate() +{ +/* + // This is soo hacky... + SetForegroundWindow( winState.appWindow ); + PostMessage( winState.appWindow, WM_ACTIVATE, WA_ACTIVE, NULL ); +*/ +} + +//------------------------------------------------------------------------------ +bool Input::isEnabled() +{ + if ( smManager ) + return smManager->isEnabled(); + return false; +} + +//------------------------------------------------------------------------------ +bool Input::isActive() +{ + return smActive; +} + +//------------------------------------------------------------------------------ +void Input::process() +{ +/* + U16 key; + int i; + if ( ( i = read(fileno(stdin), &key, sizeof(char)) ) != 0) { +// Con::printf( "key: %d = '%c'", toascii(key), key); +// Con::printf( "posting event." ); + InputEvent e; + e.deviceInst = 0; + e.deviceType = KeyboardDeviceType; + e.objType = SI_KEY; + e.objInst = TranslateOSKeyCode( key ); // NOTE: what is objInst? + e.action = SI_MAKE; + e.modifier = 0; + e.ascii = key; + e.fValue = 1.0f; + Game->postEvent(e); + } +// 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]; +} + +//----------------------------------------------------------------------------- +// 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/platformX86UNIX/x86UNIXInputManager.cc b/platformX86UNIX/x86UNIXInputManager.cc new file mode 100644 index 0000000..4135440 --- /dev/null +++ b/platformX86UNIX/x86UNIXInputManager.cc @@ -0,0 +1,1154 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 +#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(SDLKey SDLkey, U8 tkey, KeySym xkeysym); +static void InitKeyMaps(); +static inline U8 TranslateSDLKeytoTKey(SDLKey keysym); + +// JMQTODO: these should go in platform state +extern bool windowLocked; +extern bool windowActive; + +// 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; + +//============================================================================== +// Static helper functions +//============================================================================== +static void MapKey(SDLKey SDLkey, U8 tkey, KeySym xkeysym) +{ + SDLtoTKeyMap[SDLkey] = tkey; + + if (xkeysym == 0) + return; + + XKeyPressedEvent fooKey; + const int keybufSize = 256; + char keybuf[keybufSize]; + + // find the x keycode for the keysym + KeyCode xkeycode = XKeysymToKeycode( + x86UNIXState->GetDisplayPointer(), 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 = x86UNIXState->GetDisplayPointer(); + fooKey.window = DefaultRootWindow(x86UNIXState->GetDisplayPointer()); + 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; + } + // JMQTODO: how to get goofy modifiers? +} + +//------------------------------------------------------------------------------ +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(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(SDLK_RIGHTBRACKET, 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(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); + + // these are unsupported +// case KEY_WIN_APPS: return DIK_APPS; +// case KEY_OEM_102: return DIK_OEM_102; + + 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; + 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; + + return true; +} + +//------------------------------------------------------------------------------ +void UInputManager::disable() +{ + deactivate(); + mEnabled = false; + return; +} + +void UInputManager::initJoystick() +{ + // 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."); + + // enable joystick events + SDL_JoystickEventState(SDL_ENABLE); + + // install joysticks + for(int i = 0; i < numJoysticks; i++ ) + { + JoystickInputDevice* newDevice = new JoystickInputDevice(i); + addObject(newDevice); + Con::printf(" %s: %s", + newDevice->getDeviceName(), newDevice->getName()); + } + + mJoystickEnabled = true; +} + +//------------------------------------------------------------------------------ +void UInputManager::activate() +{ + if (mEnabled && !isActive()) + { + mActive = true; + SDL_ShowCursor(SDL_DISABLE); + resetInputState(); + activateMouse(); + activateKeyboard(); + activateJoystick(); + if (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(); + + // 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); + // JMQTODO: joystick events +} + +//------------------------------------------------------------------------------ +void UInputManager::lockInput() +{ + if (windowActive && windowLocked && + 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 (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); + } + } + else + { + MouseMoveEvent mmevent; + mmevent.xPos = mLastMouseX = event.motion.x; + mmevent.yPos = mLastMouseY = event.motion.y; + mmevent.modifier = mModifierKeys; + Game->postEvent(mmevent); + } +} + +//------------------------------------------------------------------------------ +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; + } + 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; + } + + Game->postEvent(ievent); +} + +//------------------------------------------------------------------------------ +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); + ievent.action = action; + ievent.fValue = (action == SI_MAKE) ? 1.0 : 0.0; + + processKeyEvent(ievent); + Game->postEvent(ievent); +} + +//------------------------------------------------------------------------------ +// 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 ) + { + // 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; + + if (mMouseActive) + mask |= MouseMask; + if (mKeyboardActive) + mask |= KeyboardMask; + if (mJoystickActive) + mask |= JoystickMask; + + if (mask == 0) + return; + + SDL_PumpEvents(); + S32 numEvents = SDL_PeepEvents(events, MaxEvents, SDL_GETEVENT, mask); + if (numEvents == 0) + return; + + 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; + case SDL_JOYAXISMOTION: + Con::printf("Axis: device: %d, axis: %d, value: %d", + events[i].jaxis.which, events[i].jaxis.axis, events[i].jaxis.value); + break; + case SDL_JOYBUTTONUP: + Con::printf("ButtonUp: device: %d, button: %d", + events[i].jbutton.which, events[i].jbutton.button); + break; + case SDL_JOYBUTTONDOWN: + Con::printf("ButtonDown: device: %d, button: %d", + events[i].jbutton.which, events[i].jbutton.button); + break; + case SDL_JOYBALLMOTION: + Con::printf("SDL_JOYBALLMOTION"); + break; + case SDL_JOYHATMOTION: + Con::printf("SDL_JOYHATMOTION"); + break; +// default: +// Con::printf("Unknown input event: %d", events[i].type); +// break; + } + } +} + +//------------------------------------------------------------------------------ +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 ); + + // JMQTODO: detect joystick + + 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 ) +{ +// 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( "" ); +} + +//============================================================================== +// JoystickInputDevice +//============================================================================== +JoystickInputDevice::JoystickInputDevice(U8 deviceID) +{ + mActive = false; + mStick = NULL; + mDeviceID = deviceID; + dSprintf(mName, 29, "joystick%d", mDeviceID); +} + +//------------------------------------------------------------------------------ +JoystickInputDevice::~JoystickInputDevice() +{ + if (isActive()) + deactivate(); +} + +//------------------------------------------------------------------------------ +bool JoystickInputDevice::activate() +{ + if (isActive()) + return true; + + mStick = SDL_JoystickOpen(mDeviceID); + if (mStick == NULL) + { + Con::printf("Unable to activate %s: %s", getDeviceName(), SDL_GetError()); + return false; + } + + 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::getJoystickAxesString() +{ + return ""; +} + +//------------------------------------------------------------------------------ +const char* JoystickInputDevice::getName() +{ + return SDL_JoystickName(mDeviceID); +} + + +//------------------------------------------------------------------------------ +bool JoystickInputDevice::process() +{ + if (!isActive()) + return false; + return true; +} + +//============================================================================== +// Console Functions +//============================================================================== +ConsoleFunction( activateKeyboard, bool, 1, 1, "activateKeyboard()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + return( mgr->activateKeyboard() ); + + return( false ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( deactivateKeyboard, void, 1, 1, "deactivateKeyboard()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + mgr->deactivateKeyboard(); +} + +//------------------------------------------------------------------------------ +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( 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..1f6ece8 --- /dev/null +++ b/platformX86UNIX/x86UNIXInputManager.h @@ -0,0 +1,140 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +// $Id: x86UNIXInputManager.h,v 1.1 2002/01/26 23:58:06 jmquigs Exp $ + +#ifndef _X86UNIXINPUTMANAGER_H_ +#define _X86UNIXINPUTMANAGER_H_ + +#include "platform/platformInput.h" +#include "platformX86UNIX/platformX86UNIX.h" + +#include + +// JMQTODO: make these not be defines +#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; + +//------------------------------------------------------------------------------ +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(); + + bool process(); + + private: + bool mActive; + U8 mDeviceID; + SDL_Joystick* mStick; +}; + +//------------------------------------------------------------------------------ +class UInputManager : public InputManager +{ + 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 ); } + + const char* getJoystickAxesString( U32 deviceID ); + private: + typedef SimGroup Parent; + + 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(); + + 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/x86UNIXMath.cc b/platformX86UNIX/x86UNIXMath.cc new file mode 100644 index 0000000..ccc7c42 --- /dev/null +++ b/platformX86UNIX/x86UNIXMath.cc @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "math/mMath.h" + + +extern void mInstallLibrary_C(); +extern void mInstallLibrary_ASM(); + + +extern void mInstall_AMD_Math(); + +//-------------------------------------- +static void cMathInit(SimObject*, S32 argc, const char** argv) +{ + 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) +{ + Con::addCommand( "MathInit", cMathInit, "MathInit(detect|C|FPU|MMX|3DNOW|SSE|...)", 1, 10); + + 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(" "); +} + + diff --git a/platformX86UNIX/x86UNIXMath_ASM.cc b/platformX86UNIX/x86UNIXMath_ASM.cc new file mode 100644 index 0000000..486ee34 --- /dev/null +++ b/platformX86UNIX/x86UNIXMath_ASM.cc @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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.cc b/platformX86UNIX/x86UNIXMemory.cc new file mode 100644 index 0000000..e883567 --- /dev/null +++ b/platformX86UNIX/x86UNIXMemory.cc @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.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); +} + +#ifdef new +#undef new +#endif + +//-------------------------------------- +void* FN_CDECL operator new(dsize_t, void* ptr) +{ + return (ptr); +} + +void* dRealMalloc(dsize_t s) +{ + return malloc(s); +} + +void dRealFree(void* p) +{ + free(p); +} + diff --git a/platformX86UNIX/x86UNIXMessageBox.cc b/platformX86UNIX/x86UNIXMessageBox.cc new file mode 100644 index 0000000..a8613fa --- /dev/null +++ b/platformX86UNIX/x86UNIXMessageBox.cc @@ -0,0 +1,341 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +// TODO + +//#include "platformX86UNIX/platformX86UNIX.h" +#include "platformX86UNIX/x86UNIXMessageBox.h" + +#include +#include +#include +#include +#include +#include +#include + +#define MaxWinWidth 800 +#define MaxWinHeight 600 + +//------------------------------------------------------------------------------ +// XMessageBoxButton +//------------------------------------------------------------------------------ +XMessageBoxButton::XMessageBoxButton() +{ + dStrcpy(mLabel, ""); + mClickVal = -1; + mMB = NULL; +} + +//------------------------------------------------------------------------------ +XMessageBoxButton::XMessageBoxButton( + const char* label, int clickVal, XMessageBox* mb) +{ + dStrncpy(mLabel, label, LabelSize); + mClickVal = clickVal; + mMB = mb; +} + +//------------------------------------------------------------------------------ +void XMessageBoxButton::dialogCallback(Widget w, + XtPointer client_data, + XtPointer call_data) +{ + XMessageBoxButton* button = + reinterpret_cast(client_data); + if (button != NULL) + button->clicked(); + XtAppSetExitFlag(XtWidgetToApplicationContext(w)); +} + +//------------------------------------------------------------------------------ +void XMessageBoxButton::clicked() +{ + if (mMB) + mMB->setClickedButton(this); +} + +//------------------------------------------------------------------------------ +// XMessageBox +//------------------------------------------------------------------------------ +XMessageBox::XMessageBox() +{ + mWindowTitle = ""; + mMessage = ""; + mWrappedMessage = NULL; + mWrappedMessageSize = 0; + mDisplay = NULL; + mFS = NULL; + mClickedButton = NULL; +} + +//------------------------------------------------------------------------------ +XMessageBox::~XMessageBox() +{ + clearWrappedMessage(); +} + +//------------------------------------------------------------------------------ +int XMessageBox::alertOK(const char *windowTitle, const char *message) +{ + mWindowTitle = windowTitle; + mMessage = message; + mButtons.clear(); + mButtons.push_back(XMessageBoxButton("OK", OK, this)); + int val = show(); + if (val == -1) + return OK; + return val; +} + +//------------------------------------------------------------------------------ +int XMessageBox::alertOKCancel(const char *windowTitle, const char *message) +{ + mWindowTitle = windowTitle; + mMessage = message; + mButtons.clear(); + mButtons.push_back(XMessageBoxButton("OK", OK, this)); + mButtons.push_back(XMessageBoxButton("Cancel", Cancel, this)); + int val = show(); + if (val == -1) + return Cancel; + return val; +} + +//------------------------------------------------------------------------------ +int XMessageBox::alertRetryCancel(const char *windowTitle, const char *message) +{ + mWindowTitle = windowTitle; + mMessage = message; + mButtons.clear(); + mButtons.push_back(XMessageBoxButton("Retry", Retry, this)); + mButtons.push_back(XMessageBoxButton("Cancel", Cancel, this)); + int val = show(); + if (val == -1) + return Cancel; + return val; +} + +//------------------------------------------------------------------------------ +template +static inline Type min(Type v1, Type v2) +{ + if (v1 > v2) + return v2; + else + return v1; +} + +//------------------------------------------------------------------------------ +void XMessageBox::clearWrappedMessage() +{ + if (mWrappedMessage != NULL) + { + delete [] mWrappedMessage; + mWrappedMessageSize = 0; + } +} + +//------------------------------------------------------------------------------ +void XMessageBox::splitMessage() +{ + clearWrappedMessage(); + if (mMessage == NULL || dStrlen(mMessage)==0) + return; + + int numChars = dStrlen(mMessage); + mWrappedMessageSize = (numChars * 2) + 1; + mWrappedMessage = new char[mWrappedMessageSize]; + dMemset(mWrappedMessage, 0, mWrappedMessageSize); + + int fontDirection, fontAscent, fontDescent; + XCharStruct strInfo; + + char *curChar = const_cast(mMessage); + char *endChar; + char *curWrapped = mWrappedMessage; + int curWidth = 0; + + while ( // while pointers are in range... + (curChar - mMessage) < numChars && + (curWrapped - mWrappedMessage) < mWrappedMessageSize) + { + // 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, insert a newline and reset curWidth + if ((curWidth + strInfo.width) > MaxWinWidth) + { + *curWrapped = '\n'; + curWrapped++; + curWidth = 0; + } + // copy the current string into curWrapped if we have enough room + int bytesRemaining = + mWrappedMessageSize - (curWrapped - mWrappedMessage); + if (bytesRemaining >= len) + dStrncpy(curWrapped, curChar, len); + + curWrapped += len; + curWidth += strInfo.width; + curChar = endChar; + } + + // shouldn't be necessary due to the dMemset, but just in case + mWrappedMessage[mWrappedMessageSize-1] = '\0'; +} + +//------------------------------------------------------------------------------ +int XMessageBox::show() +{ + Widget toplevel; + int argc = 3; + // this is a hack to specify a target font + char *argv[] = { const_cast(mWindowTitle), + "-fn", "-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*" }; + + // init the top level widget + XtAppContext appContext; + + toplevel = XtOpenApplication(&appContext, "", NULL, 0, &argc, argv, NULL, + applicationShellWidgetClass, NULL, 0); + +//this is supposed to initialize the toolkit from a pre existing display, +//but I couldn't get it to work. +// XtToolkitInitialize(); +// XtAppContext xtAppContext = XtCreateApplicationContext(); +// XtAppSetFallbackResources(xtAppContext, fallback) ; +// XtDisplayInitialize(xtAppContext, mDisplay, "", "", NULL, 0, +// &argc, argv); +// // toplevel = XtAppCreateShell("Torque Alert", "", applicationShellWidgetClass, +// // mDisplay, NULL, 0); +// toplevel = XtVaAppCreateShell("", "", +// applicationShellWidgetClass, mDisplay, 0) ; + + // set the display member + mDisplay = XtDisplay(toplevel); + + // don't map it when managed + XtVaSetValues(toplevel, + XtNmappedWhenManaged, False, + 0); + + // read the font resource to see what font we got + struct _appRes + { + XFontStruct* font; + } appRes; + + XtResource resources[] = { + { + "font", "Font", + XtRFontStruct, + sizeof(XFontStruct*), + XtOffsetOf( struct _appRes, font ), + XtRImmediate, NULL + }, + }; + + XtVaGetApplicationResources( toplevel, (XtPointer)&appRes, + resources, XtNumber(resources), 0 ); + + mFS = appRes.font; + + // set the maximum window dimensions + mScreenWidth = DisplayWidth(mDisplay, DefaultScreen(mDisplay)); + mScreenHeight = DisplayHeight(mDisplay, DefaultScreen(mDisplay)); + mMaxWindowWidth = min(mScreenWidth, MaxWinWidth); + mMaxWindowHeight = min(mScreenHeight, MaxWinHeight); + + // split the message into a vector of lines + splitMessage(); + + // create the dialog + Widget dialog = XtVaCreateManagedWidget("ok_dialog", dialogWidgetClass, + toplevel, XtNlabel, + mWrappedMessageSize != 0 ? mWrappedMessage : "", + 0); + Vector::iterator iter; + for (iter = mButtons.begin(); iter != mButtons.end(); ++iter) + { + XawDialogAddButton(dialog, iter->getLabel(), + iter->dialogCallback, iter); + } + + XtRealizeWidget(toplevel); + +// XChangeProperty(mDisplay, XtWindow(toplevel), +// XInternAtom(mDisplay, "WM_TRANSIENT_FOR", False), +// XA_ATOM, 32, PropModeAppend, +// NULL, 0); + + // set the x, y position to center of display + Position x, y; + Dimension width, height; + XtVaGetValues(toplevel, XtNx, &x, XtNy, &y, + XtNwidth, &width, XtNheight, &height, 0); + x = (mScreenWidth - width) / 2; + y = (mScreenHeight - height) / 2; + XtVaSetValues(toplevel, XtNx, x, XtNy, y, 0); + + // initialize wm_protocols, so that we can respond to window destroyed + // message + Atom wm_delete_window = + XInternAtom(mDisplay, "WM_DELETE_WINDOW", False); + Atom wm_protocols = + XInternAtom(mDisplay, "WM_PROTOCOLS", False); + XSetWMProtocols (mDisplay, XtWindow(toplevel), + &wm_delete_window, 1); + + XSetWindowAttributes xattr; + xattr.override_redirect = True; + +// XChangeWindowAttributes(mDisplay, XtWindow(toplevel), +// CWOverrideRedirect, &xattr); + + // show the widget + XtMapWidget(toplevel); + XRaiseWindow(mDisplay, XtWindow(toplevel)); + + // do event loop + XEvent event; + + while (!XtAppGetExitFlag(appContext)) + { + XtAppNextEvent(appContext, &event); + if (event.type == ClientMessage && + event.xclient.message_type == wm_protocols && + event.xclient.data.l[0] == static_cast(wm_delete_window)) + XtAppSetExitFlag(appContext); + XtDispatchEvent(&event); + } + + XtUnrealizeWidget(toplevel); + XtDestroyWidget(dialog); + XtDestroyWidget(toplevel); + XtDestroyApplicationContext(appContext); + + // return the clicked val if we have one + if (mClickedButton != NULL) + return mClickedButton->getClickVal(); + else + return -1; +} diff --git a/platformX86UNIX/x86UNIXMessageBox.h b/platformX86UNIX/x86UNIXMessageBox.h new file mode 100644 index 0000000..2faaacb --- /dev/null +++ b/platformX86UNIX/x86UNIXMessageBox.h @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIXMESSAGEBOX_H_ +#define _X86UNIXMESSAGEBOX_H_ + +#include "core/tVector.h" +#include + +class XMessageBox; + +//------------------------------------------------------------------------------ +class XMessageBoxButton +{ + public: + XMessageBoxButton(); + XMessageBoxButton(const char* label, int clickVal, XMessageBox* mb); + + const char *getLabel() { return static_cast(mLabel); } + int getClickVal() { return mClickVal; } + + void clicked(); + + static void dialogCallback(Widget w, + XtPointer client_data, + XtPointer call_data); + + private: + static const int LabelSize = 100; + char mLabel[LabelSize]; + int mClickVal; + XMessageBox *mMB; +}; + +//------------------------------------------------------------------------------ +class XMessageBox +{ + public: + static const int OK = 4; + static const int Cancel = 5; + static const int Retry = 6; + + XMessageBox(); + ~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); + static void onWMProtocols(); + + void setClickedButton(XMessageBoxButton* clickedButton) + { + mClickedButton = clickedButton; + } + + private: + int show(); + void splitMessage(); + void clearWrappedMessage(); + + XMessageBoxButton* mClickedButton; + const char* mMessage; + const char* mWindowTitle; + char* mWrappedMessage; + int mWrappedMessageSize; + Vector mButtons; + + Display* mDisplay; + XFontStruct* mFS; + + int mScreenWidth, mScreenHeight, mMaxWindowWidth, mMaxWindowHeight; +}; + +#endif // #define _X86UNIXMESSAGEBOX_H_ diff --git a/platformX86UNIX/x86UNIXMutex.cc b/platformX86UNIX/x86UNIXMutex.cc new file mode 100644 index 0000000..b6d70c7 --- /dev/null +++ b/platformX86UNIX/x86UNIXMutex.cc @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/platformMutex.h" +#include + +void * Mutex::createMutex() +{ + pthread_mutex_t *mutex; + + mutex = (pthread_mutex_t *) dRealMalloc(sizeof(pthread_mutex_t)); + pthread_mutex_init(mutex, NULL); + + return((void*)mutex); +} + +void Mutex::destroyMutex(void * mutex) +{ + AssertFatal(mutex, "Mutex::destroyMutex: invalid mutex"); + pthread_mutex_destroy((pthread_mutex_t *)mutex); + delete mutex; +} + +void Mutex::lockMutex(void * mutex) +{ + AssertFatal(mutex, "Mutex::lockMutex: invalid mutex"); + pthread_mutex_lock((pthread_mutex_t *)mutex); +} + +void Mutex::unlockMutex(void * mutex) +{ + AssertFatal(mutex, "Mutex::unlockMutex: invalid mutex"); + pthread_mutex_unlock((pthread_mutex_t *)mutex); +} diff --git a/platformX86UNIX/x86UNIXMutex.h b/platformX86UNIX/x86UNIXMutex.h new file mode 100644 index 0000000..5df9827 --- /dev/null +++ b/platformX86UNIX/x86UNIXMutex.h @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIXMUTEX_H_ +#define _X86UNIXMUTEX_H_ + +// A process-shared mutex class, used by Platform::excludeOtherInstances() +class ProcessMutex +{ + public: + ProcessMutex(); + ~ProcessMutex(); + + bool acquire(const char *mutexName); + void release(); + private: + int mFD; + static const int LockFileNameSize=256; + char mLockFileName[LockFileNameSize]; +}; + +#endif diff --git a/platformX86UNIX/x86UNIXNet.cc b/platformX86UNIX/x86UNIXNet.cc new file mode 100644 index 0000000..54a06bf --- /dev/null +++ b/platformX86UNIX/x86UNIXNet.cc @@ -0,0 +1,782 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/platform.h" +#include "platform/event.h" + +//#include +//#include +#include +#include +#include +#include +#include +/* for PROTO_IPX */ +#include +#include +#include + +#include "console/console.h" +#include "platform/gameInterface.h" +#include "core/fileStream.h" + +struct NameLookup +{ + struct hostent *hostEnt; + int socket; + U16 port; + NameLookup *nextLookup; +}; + +static NameLookup *lookupList = NULL; + +// process receives data and posts it to the game. + +static Net::Error getLastError(); +static S32 defaultPort = 28000; +static S32 netPort = 0; +static int ipxSocket = NULL; +static int udpSocket = NULL; + +enum { + MaxConnections = 1024, +}; + +/* +HWND winsockWindow = NULL; + +static LRESULT PASCAL WinsockProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + U32 error; + U32 bufLen; + U32 event; + SOCKET socket; + Net::Error err; + S32 bytesRead; + + static ConnectedNotifyEvent notifyEvent; + static ConnectedReceiveEvent receiveEvent; + static ConnectedAcceptEvent acceptEvent; + + switch(message) + { + case WM_USER: + error = WSAGETSELECTERROR(lParam); + event = WSAGETSELECTEVENT(lParam); + socket = wParam; + switch(event) + { + case FD_READ: + err = Net::recv(socket, receiveEvent.data, MaxPacketDataSize, &bytesRead); + if(err == Net::NoError && bytesRead != 0) + { + receiveEvent.tag = socket; + receiveEvent.size = ConnectedReceiveEventHeaderSize + bytesRead; + Game->postEvent(receiveEvent); + } + break; + case FD_CONNECT: + notifyEvent.tag = socket; + if(error) + notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; + else + notifyEvent.state = ConnectedNotifyEvent::Connected; + Game->postEvent(notifyEvent); + break; + case FD_CLOSE: + // see first if there is anything to read: + for(;;) + { + err = Net::recv(socket, receiveEvent.data, MaxPacketDataSize, &bytesRead); + if(err != Net::NoError || bytesRead == 0) + break; + + receiveEvent.tag = socket; + receiveEvent.size = ConnectedReceiveEventHeaderSize + bytesRead; + Game->postEvent(receiveEvent); + } + notifyEvent.tag = socket; + notifyEvent.state = ConnectedNotifyEvent::Disconnected; + Game->postEvent(notifyEvent); + break; + case FD_ACCEPT: + acceptEvent.portTag = socket; + acceptEvent.connectionTag = Net::accept(socket, &acceptEvent.address); + if(acceptEvent.connectionTag != InvalidSocket) + { + Net::setBlocking(acceptEvent.connectionTag, false); + WSAAsyncSelect(acceptEvent.connectionTag, winsockWindow, WM_USER, FD_READ | FD_CONNECT | FD_CLOSE); + Game->postEvent(acceptEvent); + } + break; + } + break; + case WM_USER + 1: + error = WSAGETASYNCERROR(lParam); + bufLen = WSAGETASYNCBUFLEN(lParam); + HANDLE handle; + handle = HANDLE(wParam); + NameLookup **walk; + for(walk = &lookupList; *walk; walk = &((*walk)->nextLookup)) + { + if((*walk)->lookupHandle == handle) + { + NameLookup *temp = *walk; + struct hostent *hp = (struct hostent *) temp->hostEntStruct; + + SOCKADDR_IN ipAddr; + memcpy(&ipAddr.sin_addr.s_addr, hp->h_addr, sizeof(IN_ADDR)); + ipAddr.sin_port = temp->port; + ipAddr.sin_family = AF_INET; + + notifyEvent.tag = temp->socket; + + bool wserr = ::connect(temp->socket, (PSOCKADDR) &ipAddr, sizeof(ipAddr)); // always errors out + if(wserr && WSAGetLastError() != WSAEWOULDBLOCK) + { + notifyEvent.state = ConnectedNotifyEvent::DNSFailed; + ::closesocket(temp->socket); + } + else + { + WSAAsyncSelect(temp->socket, winsockWindow, WM_USER, FD_READ | FD_CONNECT | FD_CLOSE); + notifyEvent.state = ConnectedNotifyEvent::DNSResolved; + } + Game->postEvent(notifyEvent); + + *walk = temp->nextLookup; + delete temp; + break; + } + } + break; + default: + return DefWindowProc( hWnd, message, wParam, lParam ); + } + return 0; +} +*/ + +bool Net::init() +{ + /* nothing to do */ +} + +void Net::shutdown() +{ + while (lookupList) + { + NameLookup *temp = lookupList; + lookupList = temp->nextLookup; + delete temp; + } + closePort(); +} + +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]; + sprintf(tAddr, "%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) +{ + 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]; +} + +static void IPXSocketToNetAddress(const sockaddr_ipx *sockAddr, NetAddress *address) +{ + 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]; +} + +NetSocket Net::openListenPort(U16 port) +{ + if(Game->isJournalReading()) + { + U32 ret; + Game->journalRead(&ret); + return NetSocket(ret); + } + NetSocket sock = openSocket(); + fd_set sockFDSet; + FD_ZERO(&sockFDSet); + FD_SET(sock, &sockFDSet); + bind(sock, port); + listen(sock, 4); + setBlocking(sock, false); + if (select(sock, &sockFDSet, &sockFDSet, &sockFDSet, NULL) == -1) + Con::printf("Error: error in select()\n"); + 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; + + 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) + { + Con::printf("Couldn't connect()"); + ::close(sock); + sock = InvalidSocket; + } + if(sock != InvalidSocket) { + fd_set sockFDSet; + FD_ZERO(&sockFDSet); + FD_SET(sock, &sockFDSet); + select(sock, &sockFDSet, &sockFDSet, &sockFDSet, NULL); + } + } + else + { + NameLookup *lookup = new NameLookup; + lookup->socket = sock; + lookup->port = port; + lookup->hostEnt = gethostbyname(remoteAddr); + + if(lookup->hostEnt == NULL) + { + delete lookup; + ::close(sock); + sock = InvalidSocket; + } + else + { + lookup->nextLookup = lookupList; + lookupList = lookup; + } + } + if(Game->isJournalWriting()) + Game->journalWrite(U32(sock)); + return sock; +} + +void Net::closeConnectTo(NetSocket sock) +{ + if(Game->isJournalReading()) + return; + + for(NameLookup **walk = &lookupList; *walk; walk = &((*walk)->nextLookup) ) + { + NameLookup *lookup = *walk; + if(lookup->socket == sock) + { + close(lookup->socket); + *walk = lookup->nextLookup; + delete lookup; + return; + } + } + close(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(;;) + { + S32 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); + } +} + +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; + S32 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( "Host::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) +{ + S32 error = ::send(socket, (const char*)buffer, bufferSize, 0); + if(!error) + 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 + { + ipAddr.sin_addr.s_addr = inet_addr(remoteAddr); + if(ipAddr.sin_addr.s_addr == INADDR_NONE) + { + if((hp = gethostbyname(remoteAddr)) == NULL) + 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 + { + 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() +{ + return Net::UnknownError; +} diff --git a/platformX86UNIX/x86UNIXOGLVideo.cc b/platformX86UNIX/x86UNIXOGLVideo.cc new file mode 100644 index 0000000..b409254 --- /dev/null +++ b/platformX86UNIX/x86UNIXOGLVideo.cc @@ -0,0 +1,613 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +//#include "console/ast.h" +#include "console/console.h" +//#include "console/consoleInternal.h" +//#include "core/fileStream.h" +//#include "math/mPoint.h" + +#include "platformX86UNIX/platformGL.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "platformX86UNIX/x86UNIXOGLVideo.h" +#include "platform/platformAudio.h" + +#include +#include +#include +#include +#include + +/* our windowing/gl vars */ +GLXContext ctx; +Display *display; +int screen_num; +Window win; +Screen *screen_ptr; + +//------------------------------------------------------------------------------ + +bool OpenGLDevice::smCanSwitchBitDepth = true; + +//------------------------------------------------------------------------------ +OpenGLDevice::OpenGLDevice() +{ + initDevice(); +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::initDevice() +{ + // Set the device name: + mDeviceName = "OpenGL"; + + // Set some initial conditions: + mResolutionList.clear(); + + Resolution newRes( 640, 480, 32 ); + mResolutionList.push_back( newRes ); + return; + +/* + // 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 >= 640 && devMode.dmPelsHeight >= 480 + && ( devMode.dmBitsPerPel == 16 || devMode.dmBitsPerPel == 32 ) && + ( smCanSwitchBitDepth || devMode.dmBitsPerPel == winState.desktopBitsPixel ) ) + { + // Only add this resolution if it is not already in the list: + bool alreadyInList = false; + for ( U32 i = 0; i < mResolutionList.size(); i++ ) + { + if ( devMode.dmPelsWidth == mResolutionList[i].w + && devMode.dmPelsHeight == mResolutionList[i].h + && devMode.dmBitsPerPel == mResolutionList[i].bpp ) + { + alreadyInList = true; + break; + } + } + + if ( !alreadyInList ) + { + Resolution newRes( devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel ); + mResolutionList.push_back( newRes ); + } + } + } +*/ +} + + +//------------------------------------------------------------------------------ +bool OpenGLDevice::activate( U32 width, U32 height, U32 bpp, bool fullScreen ) +{ + Con::printf( "Activating the OpenGL display device..." ); + + bool needResurrect = false; + + // If the rendering context exists, delete it: + if ( ctx ) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + + Con::printf( "Making the rendering context not current..." ); + if ( !glXMakeCurrent(display, None, NULL) ) + { + AssertFatal( false, "OpenGLDevice::activate\nglXMakeCurrent(display, None, NULL) failed!" ); + return false; + } + + Con::printf( "Deleting the rendering context ..." ); + glXDestroyContext(display, ctx); + ctx = NULL; + } + + // GLX attributes + static int attr[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, None }; + + // create a GLX instance + Con::printf(" Creating a new rendering context..." ); + XVisualInfo *vi = glXChooseVisual(display, screen_num, attr); + ctx = glXCreateContext(display, vi, 0, GL_TRUE); + + // attach the GLX context to the window + glXMakeCurrent(display, win, ctx); + + // 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 ); + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + + QGL_EXT_Init(); + + return true; +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::shutdown() +{ + Con::printf( "Shutting down the OpenGL display device..." ); + + if ( ctx ) + { + Con::printf( "Making the GL rendering context not current..." ); + if ( !glXMakeCurrent(display, None, NULL) ) + { + AssertFatal( false, "OpenGLDevice::activate\nglXMakeCurrent(display, None, NULL) failed!" ); + return; + } + + Con::printf( "Deleting the rendering context ..." ); + glXDestroyContext(display, ctx); + ctx = NULL; + } + +// if ( smIsFullScreen ) +// { +// Con::printf( "Restoring the desktop display settings (%dx%dx%d)...", winState.desktopWidth, winState.desktopHeight, winState.desktopBitsPixel ); +// ChangeDisplaySettings( NULL, 0 ); +// } +} + + +//------------------------------------------------------------------------------ +// This is the real workhorse function of the DisplayDevice... +// +bool OpenGLDevice::setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen, bool forceIt, bool repaint ) +{ +#if 0 + HWND curtain = NULL; + char errorMessage[256]; + Resolution newRes( width, height, bpp ); + bool newFullScreen = fullScreen; + bool safeModeOn = Con::getBoolVariable( "$pref::Video::safeModeOn" ); + + if ( !newFullScreen && mFullScreenOnly ) + { + Con::warnf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode - device or desktop color depth does not allow windowed mode!" ); + newFullScreen = true; + } + + if ( !newFullScreen && ( newRes.w >= winState.desktopWidth || newRes.h >= winState.desktopHeight ) ) + { + Con::warnf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode -- can't switch to resolution larger than desktop in windowed mode!" ); + // Find the largest standard resolution that will fit on the desktop: + U32 resIndex; + for ( resIndex = mResolutionList.size() - 1; resIndex > 0; resIndex-- ) + { + if ( mResolutionList[resIndex].w < winState.desktopWidth + && mResolutionList[resIndex].h < winState.desktopHeight ) + break; + } + newRes = mResolutionList[resIndex]; + } + + if ( newRes.w < 640 || newRes.h < 480 ) + { + Con::warnf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode -- can't go smaller than 640x480!" ); + return false; + } + + if ( newFullScreen ) + { + if (newRes.bpp != 16 && mFullScreenOnly) + newRes.bpp = 16; + + // Match the new resolution to one in the list: + U32 resIndex = 0; + U32 bestScore = 0, thisScore = 0; + for ( int i = 0; i < mResolutionList.size(); i++ ) + { + if ( newRes == mResolutionList[i] ) + { + resIndex = i; + break; + } + else + { + thisScore = abs( S32( newRes.w ) - S32( mResolutionList[i].w ) ) + + abs( S32( newRes.h ) - S32( mResolutionList[i].h ) ) + + ( newRes.bpp == mResolutionList[i].bpp ? 0 : 1 ); + + if ( !bestScore || ( thisScore < bestScore ) ) + { + bestScore = thisScore; + resIndex = i; + } + } + } + + newRes = mResolutionList[resIndex]; + } + else + { + // Basically ignore the bit depth parameter: + newRes.bpp = winState.desktopBitsPixel; + } + + // Return if already at this resolution: + if ( !forceIt && newRes == smCurrentRes && newFullScreen == smIsFullScreen ) + return true; + + Con::printf( "Setting screen mode to %dx%dx%d (%s)...", newRes.w, newRes.h, newRes.bpp, ( newFullScreen ? "fs" : "w" ) ); + + bool needResurrect = false; + + if ( ( newRes.bpp != smCurrentRes.bpp ) || ( safeModeOn && ( ( smIsFullScreen != newFullScreen ) || newFullScreen ) ) ) + { + // Delete the rendering context: + if ( winState.hGLRC ) + { + if (!Video::smNeedResurrect) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + } + + Con::printf( "Making the rendering context not current..." ); + if ( !qwglMakeCurrent( NULL, NULL ) ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglMakeCurrent( NULL, NULL ) failed!" ); + return false; + } + + Con::printf( "Deleting the rendering context..." ); + if ( Con::getBoolVariable("$pref::Video::deleteContext",true) && + !qwglDeleteContext( winState.hGLRC ) ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglDeleteContext failed!" ); + return false; + } + winState.hGLRC = NULL; + } + + // Release the device context: + if ( winState.appDC ) + { + Con::printf( "Releasing the device context..." ); + ReleaseDC( winState.appWindow, winState.appDC ); + winState.appDC = NULL; + } + + // Destroy the window: + if ( winState.appWindow ) + { + Con::printf( "Destroying the window..." ); + DestroyWindow( winState.appWindow ); + winState.appWindow = NULL; + } + } + else if ( smIsFullScreen != newFullScreen ) + { + // Change the window style: + Con::printf( "Changing the window style..." ); + S32 windowStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + if ( newFullScreen ) + windowStyle |= ( WS_POPUP | WS_MAXIMIZE ); + else + windowStyle |= ( WS_OVERLAPPED | WS_CAPTION ); + + if ( !SetWindowLong( winState.appWindow, GWL_STYLE, windowStyle ) ) + Con::errorf( "SetWindowLong failed to change the window style!" ); + } + + U32 test; + if ( newFullScreen ) + { + // Change the display settings: + DEVMODE devMode; + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + devMode.dmPelsWidth = newRes.w; + devMode.dmPelsHeight = newRes.h; + devMode.dmBitsPerPel = newRes.bpp; + devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; + + Con::printf( "Changing the display settings to %dx%dx%d...", newRes.w, newRes.h, newRes.bpp ); + curtain = CreateCurtain( newRes.w, newRes.h ); + test = ChangeDisplaySettings( &devMode, CDS_FULLSCREEN ); + if ( test != DISP_CHANGE_SUCCESSFUL ) + { + smIsFullScreen = false; + Con::setBoolVariable( "$pref::Video::fullScreen", false ); + ChangeDisplaySettings( NULL, 0 ); + Con::errorf( ConsoleLogEntry::General, "OpenGLDevice::setScreenMode - ChangeDisplaySettings failed." ); + switch( test ) + { + case DISP_CHANGE_RESTART: + Platform::AlertOK( "Display Change Failed", "You must restart your machine to get the specified mode." ); + break; + + case DISP_CHANGE_BADMODE: + Platform::AlertOK( "Display Change Failed", "The specified mode is not supported by this device." ); + break; + + default: + Platform::AlertOK( "Display Change Failed", "Hardware failed to switch to the specified mode." ); + break; + }; + DestroyWindow( curtain ); + return false; + } + else + smIsFullScreen = true; + } + else if ( smIsFullScreen ) + { + Con::printf( "Changing to the desktop display settings (%dx%dx%d)...", winState.desktopWidth, winState.desktopHeight, winState.desktopBitsPixel ); + ChangeDisplaySettings( NULL, 0 ); + smIsFullScreen = false; + } + Con::setBoolVariable( "$pref::Video::fullScreen", smIsFullScreen ); + + bool newWindow = false; + if ( !winState.appWindow ) + { + Con::printf( "Creating a new %swindow...", ( fullScreen ? "full-screen " : "" ) ); + winState.appWindow = CreateOpenGLWindow( newRes.w, newRes.h, newFullScreen ); + if ( !winState.appWindow ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nFailed to create a new window!" ); + return false; + } + newWindow = true; + } + + // Move the window: + if ( !newFullScreen ) + { + // Adjust the window rect to compensate for the window style: + RECT windowRect; + windowRect.left = windowRect.top = 0; + windowRect.right = newRes.w; + windowRect.bottom = newRes.h; + + AdjustWindowRect( &windowRect, GetWindowLong( winState.appWindow, GWL_STYLE ), false ); + U32 adjWidth = windowRect.right - windowRect.left; + U32 adjHeight = windowRect.bottom - windowRect.top; + + // Center the window on the desktop: + U32 xPos = ( winState.desktopWidth - adjWidth ) / 2; + U32 yPos = ( winState.desktopHeight - adjHeight ) / 2; + test = SetWindowPos( winState.appWindow, 0, xPos, yPos, adjWidth, adjHeight, SWP_NOZORDER ); + if ( !test ) + { + dSprintf( errorMessage, 255, "OpenGLDevice::setScreenMode\nSetWindowPos failed trying to move the window to (%d,%d) and size it to %dx%d.", xPos, yPos, newRes.w, newRes.h ); + AssertFatal( false, errorMessage ); + return false; + } + } + else if ( !newWindow ) + { + // Move and size the window to take up the whole screen: + if ( !SetWindowPos( winState.appWindow, 0, 0, 0, newRes.w, newRes.h, SWP_NOZORDER ) ) + { + dSprintf( errorMessage, 255, "OpenGLDevice::setScreenMode\nSetWindowPos failed to move the window to (0,0) and size it to %dx%d.", newRes.w, newRes.h ); + AssertFatal( false, errorMessage ); + return false; + } + } + + bool newDeviceContext = false; + if ( !winState.appDC ) + { + // Get a new device context: + Con::printf( "Acquiring a new device context..." ); + winState.appDC = GetDC( winState.appWindow ); + if ( !winState.appDC ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nGetDC failed to get a valid device context!" ); + return false; + } + newDeviceContext = true; + } + + if ( newWindow ) + { + // Set the pixel format of the new window: + PIXELFORMATDESCRIPTOR pfd; + CreatePixelFormat( &pfd, newRes.bpp, 24, 8, false ); + S32 chosenFormat = ChooseBestPixelFormat( winState.appDC, &pfd ); + if ( !chosenFormat ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nNo valid pixel formats found!" ); + return false; + } + qwglDescribePixelFormat( winState.appDC, chosenFormat, sizeof( pfd ), &pfd ); + if ( !SetPixelFormat( winState.appDC, chosenFormat, &pfd ) ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nFailed to set the pixel format!" ); + return false; + } + Con::printf( "Pixel format set:" ); + Con::printf( " %d color bits, %d depth bits, %d stencil bits", pfd.cColorBits, pfd.cDepthBits, pfd.cStencilBits ); + } + + if ( !winState.hGLRC ) + { + // Create a new rendering context: + Con::printf( "Creating a new rendering context..." ); + winState.hGLRC = qwglCreateContext( winState.appDC ); + if ( !winState.hGLRC ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglCreateContext failed to create an OpenGL rendering context!" ); + return false; + } + + // Make the new rendering context current: + Con::printf( "Making the new rendering context current..." ); + if ( !qwglMakeCurrent( winState.appDC, winState.hGLRC ) ) + { + AssertFatal( false, "OpenGLDevice::setScreenMode\nqwglMakeCurrent failed to make the rendering context current!" ); + return false; + } + + // Just for kicks. Seems a relatively central place to put this... + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + } + + // Just for kicks. Seems a relatively central place to put this... + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + if ( newDeviceContext && gGLState.suppSwapInterval ) + setVerticalSync( !Con::getBoolVariable( "$pref::Video::disableVerticalSync" ) ); + + smCurrentRes = newRes; + Platform::setWindowSize( newRes.w, newRes.h ); + char tempBuf[15]; + dSprintf( tempBuf, sizeof( tempBuf ), "%d %d %d", smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp ); + Con::setVariable( "$pref::Video::resolution", tempBuf ); + + if ( curtain ) + DestroyWindow( curtain ); + + // Doesn't hurt to do this even it isn't necessary: + ShowWindow( winState.appWindow, SW_SHOW ); + SetForegroundWindow( winState.appWindow ); + SetFocus( winState.appWindow ); + + if ( repaint ) + Con::evaluate( "resetCanvas();" ); + + return true; +#endif +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::swapBuffers() +{ + glXSwapBuffers(display, win); +} + + +//------------------------------------------------------------------------------ +const char* OpenGLDevice::getDriverInfo() +{ +#if 0 + // 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 ); + 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 ); +#endif +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::getGammaCorrection(F32 &g) +{ +#if 0 + U16 ramp[256*3]; + + if (!GetDeviceGammaRamp(winState.appDC, ramp)) + return false; + + F32 csum = 0.0; + U32 ccount = 0; + + for (U16 i = 0; i < 256; ++i) + { + if (i != 0 && ramp[i] != 0 && ramp[i] != 65535) + { + F64 b = (F64) i/256.0; + F64 a = (F64) ramp[i]/65535.0; + F32 c = (F32) (mLog(a)/mLog(b)); + + csum += c; + ++ccount; + } + } + g = csum/ccount; + + return true; +#endif +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setGammaCorrection(F32 g) +{ +#if 0 + U16 ramp[256*3]; + + for (U16 i = 0; i < 256; ++i) + ramp[i] = mPow((F32) i/256.0f, g) * 65535.0f; + dMemcpy(&ramp[256],ramp,256*sizeof(U16)); + dMemcpy(&ramp[512],ramp,256*sizeof(U16)); + + return SetDeviceGammaRamp(winState.appDC, ramp); +#endif +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setVerticalSync( bool on ) +{ +#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..04b45d8 --- /dev/null +++ b/platformX86UNIX/x86UNIXOGLVideo.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LINUXOGLVIDEO_H_ +#define _LINUXOGLVIDEO_H_ + +#ifndef _PLATFORMVIDEO_H_ +#include "platform/platformVideo.h" +#endif + +class OpenGLDevice : public DisplayDevice +{ + static bool smCanSwitchBitDepth; + + bool mRestoreGamma; + U16 mOriginalRamp[256*3]; + + public: + 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 ); + + static DisplayDevice* create(); +}; + +#endif // _H_LINUXOGLVIDEO diff --git a/platformX86UNIX/x86UNIXOpenAL.cc b/platformX86UNIX/x86UNIXOpenAL.cc new file mode 100644 index 0000000..bc5342f --- /dev/null +++ b/platformX86UNIX/x86UNIXOpenAL.cc @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "console/console.h" +#define AL_NO_PROTOTYPES +#include +#include +#include + +// Stub functions: --------------------------------------------------------- +// AL: +ALvoid stub_alEnable( ALenum ) {} +ALvoid stub_alDisable( ALenum ) {} +ALboolean stub_alIsEnabled( ALenum ) {return(AL_FALSE);} +ALvoid stub_alHint( ALenum , ALenum ) {} +ALboolean stub_alGetBoolean( ALenum ) {return(AL_FALSE);} +ALint stub_alGetInteger( ALenum ) {return(0);} +ALfloat stub_alGetFloat( ALenum ) {return(0.f);} +ALdouble stub_alGetDouble( ALenum ) {return(0.f);} +ALvoid stub_alGetBooleanv( ALenum, ALboolean* ) {} +ALvoid stub_alGetIntegerv( ALenum, ALint* ) {} +ALvoid stub_alGetFloatv( ALenum, ALfloat* ) {} +ALvoid stub_alGetDoublev( ALenum, ALdouble* ) {} +const ALubyte* stub_alGetString( ALenum pname ) +{ + switch(pname) + { + case AL_VENDOR: return (ALubyte*)"None"; + case AL_VERSION: return (ALubyte*)"OpenAL 0.1"; + case AL_RENDERER: return (ALubyte*)"None"; + case AL_EXTENSIONS: return (ALubyte*)""; + } + return(0); +} +ALenum stub_alGetError( ALvoid ) {return(0);} +ALboolean stub_alIsExtensionPresent( const ALubyte* ) {return(AL_FALSE);} +ALvoid* stub_alGetProcAddress( const ALubyte* ) {return(0);} +ALenum stub_alGetEnumValue( const ALubyte* ) {return(0);} +ALvoid stub_alListenerf( ALenum, ALfloat ) {} +ALvoid stub_alListener3f( ALenum, ALfloat, ALfloat, ALfloat ) {} +ALvoid stub_alListenerfv( ALenum, ALfloat* ) {} +ALvoid stub_alGetListeneri( ALenum, ALint* ) {} +ALvoid stub_alGetListenerf( ALenum, ALfloat* ) {} +ALvoid stub_alGetListenerfv( ALenum, ALfloat* ) {} +ALvoid stub_alGenSources( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteSources( ALsizei, ALuint* ) {} +ALboolean stub_alIsSource( ALuint ) {return(AL_FALSE);} +ALvoid stub_alSourcei( ALuint, ALenum, ALint ) {} +ALvoid stub_alSourcef( ALuint, ALenum, ALfloat ) {} +ALvoid stub_alSource3f( ALuint, ALenum, ALfloat, ALfloat, ALfloat ) {} +ALvoid stub_alSourcefv( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alGetSourcei( ALuint, ALenum, ALint* ) {} +ALvoid stub_alGetSourcef( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alGetSourcefv( ALuint, ALenum, ALfloat* ) {} +ALvoid stub_alSourcePlayv( ALuint, ALuint* ) {} +ALvoid stub_alSourceStopv( ALuint, ALuint* ) {} +ALvoid stub_alSourcePlay( ALuint ) {} +ALvoid stub_alSourcePause( ALuint ) {} +ALvoid stub_alSourceStop( ALuint ) {} +ALvoid stub_alGenBuffers( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteBuffers( ALsizei, ALuint* ) {} +ALboolean stub_alIsBuffer( ALuint ) {return(AL_FALSE);} +ALvoid stub_alBufferData( ALuint, ALenum, ALvoid*, ALsizei, ALsizei ) {} +ALsizei stub_alBufferAppendData( ALuint, ALenum, ALvoid*, ALsizei, ALsizei ) {return(0);} +ALvoid stub_alGetBufferi( ALuint, ALenum, ALint* ) {} +ALvoid stub_alGetBufferf( ALuint, ALenum, ALfloat* ) {} + +// ALC: +ALvoid* stub_alcCreateContext( ALint *) {return(0);} +ALCenum stub_alcMakeContextCurrent( ALvoid *) {return(ALC_INVALID);} +ALvoid* stub_alcUpdateContext( ALvoid * ) {return(0);} +ALCenum stub_alcDestroyContext( ALvoid * ) {return(ALC_INVALID);} +ALCenum stub_alcGetError( ALvoid ) {return(ALC_INVALID);} +const ALubyte* stub_alcGetErrorString( ALvoid ) {return(0);} +ALvoid* stub_alcGetCurrentContext( ALvoid ) {return(0);} + +// ALUT: +void stub_alutInit( int *, char ** ) {} +void stub_alutExit( ALvoid ) {} +ALboolean stub_alutLoadWAV( const char *, ALvoid **, ALsizei *, ALsizei *, ALsizei *, ALsizei *) {return(AL_FALSE);} + +// Extension: IASIG +ALvoid stub_alGenEnvironmentIASIG( ALsizei, ALuint* ) {} +ALvoid stub_alDeleteEnvironmentIASIG( ALsizei, ALuint*) {} +ALboolean stub_alIsEnvironmentIASIG( ALuint ) {return(AL_FALSE);} +ALvoid stub_alEnvironmentiIASIG( ALuint, ALenum, ALint ) {} +ALvoid stub_alEnvironmentfIASIG( ALuint, ALenum, ALfloat ) {} +ALvoid stub_alGetEnvironmentiIASIG_EXT( ALuint, ALenum, ALint * ) {} +ALvoid stub_alGetEnvironmentfIASIG_EXT( ALuint, ALenum, ALfloat * ) {} + +// Extension: Dynamix +ALboolean stub_alBufferi_EXT( ALuint, ALenum, ALint ) { return(AL_FALSE); } +ALboolean stub_alBufferSyncData_EXT( ALuint, ALenum, ALvoid *, ALsizei, ALsizei ) {return(AL_FALSE); } +ALboolean stub_alBufferStreamFile_EXT( ALuint, const ALubyte * ) { return(AL_FALSE); } +ALboolean stub_alSourceCallback_EXT( ALuint, ALvoid * ) { return(AL_FALSE); } +ALvoid stub_alSourceResetEnvironment_EXT( ALuint ) {} +ALboolean stub_alContexti_EXT( ALenum, ALint) { return(AL_FALSE); } +ALboolean stub_alGetContexti_EXT( ALenum, ALint * ) { return(AL_FALSE); } +ALboolean stub_alGetContextstr_EXT( ALenum, ALuint, ALubyte ** ) { return(AL_FALSE); } +ALboolean stub_alCaptureInit_EXT( ALenum, ALuint, ALsizei ) { return(AL_FALSE); } +ALboolean stub_alCaptureDestroy_EXT( ALvoid ) { return(AL_FALSE); } +ALboolean stub_alCaptureStart_EXT( ALvoid ) { return(AL_FALSE); } +ALboolean stub_alCaptureStop_EXT( ALvoid ) { return(AL_FALSE); } +ALsizei stub_alCaptureGetData_EXT( ALvoid *, ALsizei, ALenum, ALuint ) { return(AL_FALSE); } + +// declare OpenAL functions +#define AL_EXTENSION(ext_name) bool gDoesSupport_##ext_name = false; +#define AL_FUNCTION(fn_return,fn_name,fn_args) fn_return (*fn_name)fn_args = stub_##fn_name; +#define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_return (*fn_name)fn_args = stub_##fn_name; +#include + +// DLL's: ------------------------------------------------------------------ +static bool findExtension( const char* name ) +{ + bool result = false; + if (alGetString != NULL) + { + result = dStrstr( (const char*)alGetString(AL_EXTENSIONS), name) != NULL; + } + return result; +} + +// Stub: ------------------------------------------------------------------- +static void bindStubFunctions() +{ + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = false; + #define AL_FUNCTION(fn_return,fn_name,fn_parms) fn_name = stub_##fn_name; + #define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) fn_name = stub_##fn_name; + #include +} + +namespace Audio +{ + +static bool sStaticLibrary; + +void libraryShutdown() +{ + bindStubFunctions(); + sStaticLibrary = true; +} + +bool libraryInit(const char *library) +{ + libraryShutdown(); + + if(!library || !library[0]) + return(false); + + libraryShutdown(); + return(false); +} + +void libraryInitExtensions() +{ + // static library extensions are bound on libraryInit (need to be defined anyways...) + if(sStaticLibrary) + { + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = findExtension( #ext_name ); + #include + } + else + { + #define AL_EXTENSION(ext_name) gDoesSupport_##ext_name = findExtension( #ext_name ); +// #define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) bindExtension(*(void**)&fn_name, #fn_name, gDoesSupport_##ext_name ); + #include + } +} + +} // end namespace Audio diff --git a/platformX86UNIX/x86UNIXProcessControl.cc b/platformX86UNIX/x86UNIXProcessControl.cc new file mode 100644 index 0000000..98f135b --- /dev/null +++ b/platformX86UNIX/x86UNIXProcessControl.cc @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" + +void Platform::postQuitMessage(const U32 in_quitVal) +{ +// PostQuitMessage(in_quitVal); +} + +void Platform::debugBreak() +{ +// DebugBreak(); +} + +void Platform::forceShutdown(S32 returnValue) +{ +// ExitProcess(returnValue); +} diff --git a/platformX86UNIX/x86UNIXRedbook.cc b/platformX86UNIX/x86UNIXRedbook.cc new file mode 100644 index 0000000..0ef7333 --- /dev/null +++ b/platformX86UNIX/x86UNIXRedbook.cc @@ -0,0 +1,407 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/platformRedBook.h" + +#if defined(__linux__) +#include +#include +#include +#include +#endif + +#include + +class UnixRedBookDevice : public RedBookDevice +{ + private: + S32 mDeviceId; + SDL_CD *mCD; + cdrom_volctrl mOriginalVolume; + bool mVolumeInitialized; + 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() +{ + mVolumeInitialized = false; + mDeviceId = -1; + mDeviceName = NULL; + mCD = NULL; + mPlaying = false; +} + +//------------------------------------------------------------------------------ +UnixRedBookDevice::~UnixRedBookDevice() +{ + close(); +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::updateStatus() +{ + 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; +} + +//------------------------------------------------------------------------------ +void UnixRedBookDevice::setDeviceInfo(S32 deviceId, const char *deviceName) +{ + mDeviceId = deviceId; + mDeviceName = new char[dStrlen(deviceName) + 1]; + dStrcpy(mDeviceName, deviceName); +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::open() +{ + 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); +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::close() +{ + 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); +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::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); + } + + 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); +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::stop() +{ + 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); +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::getTrackCount(U32 * numTracks) +{ + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + if (!updateStatus()) + return false; + + AssertFatal(mCD, "mCD is NULL"); + *numTracks = mCD->numtracks; + + return(true); +} + +template +static inline Type max(Type v1, Type v2) +{ + if (v1 <= v2) + return v2; + else + return v1; +} +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::getVolume(F32 * volume) +{ + 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 +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::setVolume(F32 volume) +{ + 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 +} + +//------------------------------------------------------------------------------ +void UnixRedBookDevice::openVolume() +{ +// 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 +} + +void UnixRedBookDevice::closeVolume() +{ + 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; +} + +//------------------------------------------------------------------------------ +void UnixRedBookDevice::setLastError(const char * error) +{ + RedBook::setLastError(error); +} + +//------------------------------------------------------------------------------ +void InstallRedBookDevices() +{ + 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(" "); +} + +//------------------------------------------------------------------------------ +void PollRedbookDevices() +{ + // JMQTODO: poll at longer intervals if this function is expensive + static const U32 PollDelay = 1000; + + static U32 lastPollTime = 0; + U32 curTime = Platform::getVirtualMilliseconds(); + + if (lastPollTime != 0 && + (curTime - lastPollTime) < PollDelay) + return; + + lastPollTime = curTime; + + RedBookDevice *rbDevice = RedBook::getCurrentDevice(); + if (rbDevice == NULL) + return; + + UnixRedBookDevice *device = dynamic_cast(rbDevice); + if (device == NULL) + return; + + if (device->isPlaying()) + { + device->updateStatus(); + if (!device->isPlaying()) + RedBook::handleCallback(RedBook::PlayFinished); + } +} diff --git a/platformX86UNIX/x86UNIXSemaphore.cc b/platformX86UNIX/x86UNIXSemaphore.cc new file mode 100644 index 0000000..7378342 --- /dev/null +++ b/platformX86UNIX/x86UNIXSemaphore.cc @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/platformSemaphore.h" +#include +#include + +void * Semaphore::createSemaphore(U32 initialCount) +{ + sem_t *semaphore; + /* hell, I want an elite semaphore, OK?! - rjp */ + semaphore = sem_open("/tmp/eliteQueue.31337", O_CREAT, 0664, initialCount); + return(semaphore); +} + +void Semaphore::destroySemaphore(void * semaphore) +{ + AssertFatal(semaphore, "Semaphore::destroySemaphore: invalid semaphore"); + sem_close((sem_t *)semaphore); + delete semaphore; +} + +bool Semaphore::acquireSemaphore(void * semaphore, bool block) +{ + AssertFatal(semaphore, "Semaphore::acquireSemaphore: invalid semaphore"); + if(block) + { + sem_wait((sem_t *)semaphore); + return(true); + } + else + { + U32 result = sem_trywait((sem_t *)semaphore); + return(result == 0); + } +} + +void Semaphore::releaseSemaphore(void * semaphore) +{ + AssertFatal(semaphore, "Semaphore::releaseSemaphore: invalid semaphore"); + sem_unlink("/tmp/eliteQueue.31337"); +} diff --git a/platformX86UNIX/x86UNIXState.h b/platformX86UNIX/x86UNIXState.h new file mode 100644 index 0000000..a676f80 --- /dev/null +++ b/platformX86UNIX/x86UNIXState.h @@ -0,0 +1,377 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +// +// Initial revision: 12/13/01 Paul G. Allen, Random Logic Consulting +//----------------------------------------------------------------------------- + +#include "math/mPoint.h" +#include "platformX86UNIX/platformGL.h" +#include "platformX86UNIX/x86UNIXGLX.h" + +/****************************************************************************** +* +* Class: x86UNIXPlatformState +* Description: Class used to store the state of things for X Windows based +* implementations. +* +* Data Objects: +* Point2I DesktopSize hor. and vert. X desktop resolution +* Point2I WindowSize hor. and vert. X window resolution +* GLXContext OpenGLContext Current GLX context +* S32 Desktop_bpp X desktop color depth +* Display *display Pointer to current X display +* Window CurrentWindow Current X window for game +* Screen *ScreenPointer Pointer to current screen on display +* int ScreenNumber Current X screen number +* Colormap CurrentColormap Current X color map +* XVisualInfo *VisualInfo Visual information stuct for GLX +* XSetWindowAttributes WindowAttributes +* Attributes for CurrentWindow +* XSizeHints SizeHints Size hints for CurrentWindow +* bool KeepAspect true = keep aspect ratio of window +* bool OverrideSettings true = disable "Graphics" dialog +* char WindowName[40] CurrentWindow name (for title bar) +* +* bool videoInitted Video is initialized +* U32 currentTime Current game time +* char *DisplayHint Display hints for display +* +* Changelog: +* PGA 12/18/01: Initial revision +******************************************************************************/ + +class x86UNIXPlatformState +{ + private: + Point2I DesktopSize; + Point2I WindowSize; + GLXContext OpenGLContext; + S32 Desktop_bpp; + Display *display; + Window CurrentWindow; + Screen *ScreenPointer; + int ScreenNumber; + Colormap CurrentColormap; + XVisualInfo *VisualInfo; + XSetWindowAttributes WindowAttributes; + XSizeHints SizeHints; + bool KeepAspect, + OverrideSettings; + char WindowName[40]; + + public: + bool videoInitted; + U32 currentTime; + char *DisplayHint; + + +/****************************************************************************** +* +* Member routines: Get and set XSetWindowAttributes private data member. +* +******************************************************************************/ + + + void SetWindowAttributes( XSetWindowAttributes NewAttributes ) + { + WindowAttributes = NewAttributes; + } + + XSetWindowAttributes * GetWindowAttributes() + { + return &WindowAttributes; + } + + +/****************************************************************************** +* +* Member routines: Get and set Colormap private data member. +* +******************************************************************************/ + + void SetColormap( Colormap NewColormap ) + { + CurrentColormap = NewColormap; + } + + Colormap GetColormap() + { + return CurrentColormap; + } + + +/****************************************************************************** +* +* Member routines: Get and set XVisualInfo private data member. +* +******************************************************************************/ + + void SetVisualInfo( XVisualInfo *NewInfo ) + { + VisualInfo = NewInfo; + } + + XVisualInfo * GetVisualInfo() + { + return VisualInfo; + } + + +/****************************************************************************** +* +* Member routines: Get and set X display Screen number private data member. +* +******************************************************************************/ + + void SetScreenNumber( int NewNumber ) + { + ScreenNumber = NewNumber; + } + + int GetScreenNumber() + { + return ScreenNumber; + } + + +/****************************************************************************** +* +* Member routines: Get and set Screen pointer private data member. +* +******************************************************************************/ + + void SetScreenPointer( Screen *NewScreenPointer ) + { + ScreenPointer = NewScreenPointer; + } + + Screen * GetScreenPointer() + { + return ScreenPointer; + } + + +/****************************************************************************** +* +* Member routines: Get and set Window private data member. +* +******************************************************************************/ + + void SetWindow( Window NewWindow ) + { + CurrentWindow = NewWindow; + } + + Window GetWindow() + { + return CurrentWindow; + } + + +/****************************************************************************** +* +* Member routines: Get and set desktop color depth private data member. +* +******************************************************************************/ + + void SetDesktop_bpp( S32 bpp ) + { + Desktop_bpp = bpp; + } + + S32 GetDesktop_bpp() + { + return Desktop_bpp; + } + + +/****************************************************************************** +* +* Member routines: Get and set GLXContext private data member. +* +******************************************************************************/ + + void SetGLContextPointer( GLXContext GLContext ) + { + OpenGLContext = GLContext; + } + + GLXContext GetGLContextPointer() + { + return OpenGLContext; + } + + +/****************************************************************************** +* +* Member routines: Get and set Display private data member. +* +******************************************************************************/ + + void SetDisplayPointer( Display *DisplayPointer ) + { + display = DisplayPointer; + } + + Display * GetDisplayPointer() + { + return display; + } + + +/****************************************************************************** +* +* Member routines: Set Point2I window size private data members given two +* seperate values, one for each WindowSize data mamber. +* +******************************************************************************/ + + void SetWindowSize (S32 horizontal, S32 vertical ) + { + WindowSize.set ( horizontal, vertical ); + } + + +/****************************************************************************** +* +* Member routines: Set Point2I window size private data class given a Point2I +* data object. +* +******************************************************************************/ + + void SetWindowSize( Point2I Size ) + { + WindowSize = Size; + } + + +/****************************************************************************** +* +* Member routines: Return both x and y data members of the WindowSize private +* data class. +* +******************************************************************************/ + + Point2I& GetWindowSize() + { + return ( WindowSize ); + } + + +/****************************************************************************** +* +* Member routines: Get and set the window name private data member. +* +******************************************************************************/ + + void SetName (const char * Name) + { + strcpy( WindowName, "\0" ); + strncpy( WindowName, Name, sizeof( WindowName ) ); + } + + const char * GetWindowName() + { + return WindowName; + } + + +/****************************************************************************** +* +* Member routines: Get a pointer to XSizeHints private data member. +* +******************************************************************************/ + + XSizeHints * GetSizeHints() + { + return ( &SizeHints ); + } + + +/****************************************************************************** +* +* Member routines: Get and set the command line override private data member. +* +******************************************************************************/ + + bool GetOverrideSetting() + { + return OverrideSettings; + } + + void SetOverrideSetting( bool NewOverride ) + { + OverrideSettings = NewOverride; + } + + +/****************************************************************************** +* +* Member routines: Get and set aspect ratio command line private data member. +* +******************************************************************************/ + + bool GetAspectSetting() + { + return KeepAspect; + } + + void SetAspectSetting( bool NewAspect ) + { + KeepAspect = NewAspect; + } + + +/****************************************************************************** +* +* Member routine: Get the Point2I desktop size private class data member. +* +******************************************************************************/ + + Point2I GetDesktopSize() + { + return DesktopSize; + } + + +/****************************************************************************** +* +* Member routine: Set the Point2I desktop size private class data member +* given a Point2I data object. +* +******************************************************************************/ + + void SetDesktopSize( S32 horizontal, S32 vertical ) + { + DesktopSize.set( horizontal, vertical ); + } + +/****************************************************************************** +* +* Member routine: Initialize the class to a known state. +* +******************************************************************************/ + + x86UNIXPlatformState() + { + Desktop_bpp = 16; + SizeHints.flags = None; + SizeHints.x = 0; + SizeHints.y = 0; + videoInitted = false; + currentTime = 0; + DisplayHint = NULL; + KeepAspect = true; + OverrideSettings = false; + strcpy( WindowName, "Torque" ); + DesktopSize.set( 0, 0 ); + WindowSize.set( 800, 600 ); + CurrentColormap = 0; + VisualInfo = NULL; + } +}; + +extern x86UNIXPlatformState * x86UNIXState; diff --git a/platformX86UNIX/x86UNIXStdConsole.h b/platformX86UNIX/x86UNIXStdConsole.h new file mode 100644 index 0000000..8ef4c76 --- /dev/null +++ b/platformX86UNIX/x86UNIXStdConsole.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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 + +class StdConsole +{ + bool stdConsoleEnabled; + + int stdOut; + int stdIn; + int stdErr; + ConsoleEvent postEvent; + 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: + StdConsole(); + void process(); + void enable(bool); + void processConsoleLine(const char *consoleLine); + static void create(); + static void destroy(); + static bool isEnabled(); +}; + +extern StdConsole *stdConsole; + +#endif diff --git a/platformX86UNIX/x86UNIXStrings.cc b/platformX86UNIX/x86UNIXStrings.cc new file mode 100644 index 0000000..9b9a3cf --- /dev/null +++ b/platformX86UNIX/x86UNIXStrings.cc @@ -0,0 +1,364 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include +#include +#include + +#ifdef HAS_VSSCANF +# undef HAS_VSSCANF +#endif + +/* this routine turns a string to all uppercase - rjp */ +char *__strtoup(char *str) +{ + char *newStr = str; + if (newStr == NULL) + return(NULL); + while (*newStr) + { + *newStr = toupper(*newStr); + *newStr++; + } + return(str); +} +/* this routine turns a string to all lowercase - rjp */ +char *__strtolwr(char *str) +{ + char *newStr = str; + if (newStr == NULL) + return(NULL); + while (*newStr) + { + *newStr = tolower(*newStr); + *newStr++; + } + return(str); +} + +char *dStrdup_r(const char *src, const char *fileName, U32 lineNumber) +{ + char *buffer = (char *) dMalloc_r(dStrlen(src) + 1, fileName, lineNumber); + dStrcpy(buffer, src); + return buffer; +} + +char* dStrcat(char *dst, const char *src) +{ + return strcat(dst,src); +} + +// 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, U32 dstSize, ...) +{ + const char* src; + 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, U32 dstSize, ...) +{ + const char* src; + 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; +} + + +S32 dStrcmp(const char *str1, const char *str2) +{ + return strcmp(str1, str2); +} + +S32 dStricmp(const char *str1, const char *str2) +{ + return strcasecmp(str1, str2); +} + +S32 dStrncmp(const char *str1, const char *str2, U32 len) +{ + return strncmp(str1, str2, len); +} + +S32 dStrnicmp(const char *str1, const char *str2, U32 len) +{ + return strncasecmp(str1, str2, len); +} + +char* dStrcpy(char *dst, const char *src) +{ + return strcpy(dst,src); +} + +char* dStrncpy(char *dst, const char *src, U32 len) +{ + return strncpy(dst,src,len); +} + +U32 dStrlen(const char *str) +{ + return strlen(str); +} + + +char* dStrupr(char *str) +{ +#ifdef __MWERKS__ // metrowerks strupr is broken + _strupr(str); + return str; +#else + return __strtoup(str); +#endif +} + + +char* dStrlwr(char *str) +{ + return __strtolwr(str); +} + + +char* dStrchr(char *str, S32 c) +{ + return strchr(str,c); +} + + +const char* dStrchr(const char *str, S32 c) +{ + return strchr(str,c); +} + + +const char* dStrrchr(const char *str, S32 c) +{ + return strrchr(str,c); +} + +char* dStrrchr(char *str, S32 c) +{ + return strrchr(str,c); +} + +U32 dStrspn(const char *str, const char *set) +{ + return(strspn(str, set)); +} + +U32 dStrcspn(const char *str, const char *set) +{ + return strcspn(str, set); +} + + +char* dStrstr(const char *str1, const char *str2) +{ + return strstr(str1,str2); +} + + +char* dStrtok(char *str, const char *sep) +{ + return strtok(str, sep); +} + + +S32 dAtoi(const char *str) +{ + return atoi(str); +} + +F32 dAtof(const char *str) +{ + return atof(str); +} + +bool dAtob(const char *str) +{ + return !dStricmp(str, "true") || dAtof(str); +} + + +bool dIsalnum(const char c) +{ + return isalnum(c); +} + +bool dIsalpha(const char c) +{ + return(isalpha(c)); +} + +bool dIsspace(const char c) +{ + return(isspace(c)); +} + +bool dIsdigit(const char c) +{ + return(isdigit(c)); +} + +void dPrintf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vprintf(format, args); +} + +S32 dVprintf(const char *format, void *arglist) +{ + S32 len = vprintf(format, (char*)arglist); + return (len); +} + +S32 dSprintf(char *buffer, U32 bufferSize, const char *format, ...) +{ + va_list args; + va_start(args, format); + +#ifdef __MWERKS__ + S32 len = vsnprintf(buffer, bufferSize, format, args); +#else + bufferSize; + S32 len = vsprintf(buffer, format, args); +#endif + return (len); +} + + +S32 dVsprintf(char *buffer, U32 bufferSize, const char *format, void *arglist) +{ +#ifdef __MWERKS__ + S32 len = vsnprintf(buffer, bufferSize, format, (char*)arglist); +#else + bufferSize; + S32 len = vsprintf(buffer, format, (char*)arglist); +#endif +// S32 len = vsnprintf(buffer, bufferSize, format, (char*)arglist); + return (len); +} + + +S32 dSscanf(const char *buffer, const char *format, ...) +{ + va_list args; +#if defined(HAS_VSSCANF) + va_start(args, format); + return __vsscanf(buffer, format, args); +#else + 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; +#endif +} + +S32 dFflushStdout() +{ + return fflush(stdout); +} + +S32 dFflushStderr() +{ + return fflush(stderr); +} + +void dQsort(void *base, U32 nelem, U32 width, S32 (QSORT_CALLBACK *fcmp)(const void *, const void *)) +{ + qsort(base, nelem, width, fcmp); +} diff --git a/platformX86UNIX/x86UNIXThread.cc b/platformX86UNIX/x86UNIXThread.cc new file mode 100644 index 0000000..0a459f2 --- /dev/null +++ b/platformX86UNIX/x86UNIXThread.cc @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platformThread.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/platformSemaphore.h" +#include + +//-------------------------------------------------------------------------- +struct x86UNIXThreadData +{ + ThreadRunFunction mRunFunc; + S32 mRunArg; + Thread * mThread; + void * mSemaphore; + + x86UNIXThreadData() + { + mRunFunc = 0; + mRunArg = 0; + mThread = 0; + mSemaphore = 0; + }; +}; + +//-------------------------------------------------------------------------- +Thread::Thread(ThreadRunFunction func, S32 arg, bool start_thread) +{ + x86UNIXThreadData * threadData = new x86UNIXThreadData(); + threadData->mRunFunc = func; + threadData->mRunArg = arg; + threadData->mThread = this; + threadData->mSemaphore = Semaphore::createSemaphore(); + + mData = reinterpret_cast(threadData); + if (start_thread) + start(); +} + +Thread::~Thread() +{ + join(); + + x86UNIXThreadData * threadData = reinterpret_cast(mData); + Semaphore::destroySemaphore(threadData->mSemaphore); + delete threadData; +} + +static void *ThreadRunHandler(void * arg) +{ + x86UNIXThreadData * threadData = reinterpret_cast(arg); + + threadData->mThread->run(threadData->mRunArg); + Semaphore::releaseSemaphore(threadData->mSemaphore); +} + +void Thread::start() +{ + if(isAlive()) + return; + + x86UNIXThreadData * threadData = reinterpret_cast(mData); + Semaphore::acquireSemaphore(threadData->mSemaphore); + + pthread_t threadID; + pthread_create(&threadID, NULL, ThreadRunHandler, mData); +} + +bool Thread::join() +{ + if(!isAlive()) + return(false); + + x86UNIXThreadData * threadData = reinterpret_cast(mData); + return(Semaphore::acquireSemaphore(threadData->mSemaphore)); +} + +void Thread::run(S32 arg) +{ + x86UNIXThreadData * threadData = reinterpret_cast(mData); + if(threadData->mRunFunc) + threadData->mRunFunc(arg); +} + +bool Thread::isAlive() +{ + x86UNIXThreadData * threadData = reinterpret_cast(mData); + + bool signal = Semaphore::acquireSemaphore(threadData->mSemaphore, false); + if(signal) + Semaphore::releaseSemaphore(threadData->mSemaphore); + return(!signal); +} diff --git a/platformX86UNIX/x86UNIXTime.cc b/platformX86UNIX/x86UNIXTime.cc new file mode 100644 index 0000000..1cec6bd --- /dev/null +++ b/platformX86UNIX/x86UNIXTime.cc @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "time.h" +#include +#include + +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; +} + +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 x86UNIXState.currentTime; +} + +void Platform::advanceTime(U32 delta) +{ + x86UNIXState.currentTime += delta; +} + + + +//------------------------------------------------------------------------------ +//-------------------------------------- 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); +} + + diff --git a/platformX86UNIX/x86UNIXUtils.cc b/platformX86UNIX/x86UNIXUtils.cc new file mode 100644 index 0000000..96b5798 --- /dev/null +++ b/platformX86UNIX/x86UNIXUtils.cc @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/x86UNIXUtils.h" + +#include +#include +#include + +UnixUtils *UUtils = NULL; +UnixUtils utils; + +UnixUtils::UnixUtils() +{ + UUtils = this; +} + +bool UnixUtils::inBackground() +{ + int terminalGroupId = tcgetpgrp(fileno(stdin)); + int myPid = getpid(); + if (terminalGroupId != myPid) + return true; + else + return false; +} diff --git a/platformX86UNIX/x86UNIXUtils.h b/platformX86UNIX/x86UNIXUtils.h new file mode 100644 index 0000000..1d911d5 --- /dev/null +++ b/platformX86UNIX/x86UNIXUtils.h @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIXUTILS_H_ +#define _X86UNIXUTILS_H_ + +class UnixUtils +{ +public: + UnixUtils(); + + /** + Return 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(); +}; + +extern UnixUtils *UUtils; + +#endif diff --git a/platformX86UNIX/x86UNIXWindow.cc b/platformX86UNIX/x86UNIXWindow.cc new file mode 100644 index 0000000..d89d242 --- /dev/null +++ b/platformX86UNIX/x86UNIXWindow.cc @@ -0,0 +1,1525 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "core/fileStream.h" +#include "game/resource.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/platformInput.h" +#include "platform/platformVideo.h" +#include "platformX86UNIX/platformGL.h" +#include + +#include +#include +#include +#include +#include + +/* our window and GL vars */ +extern GLXContext ctx; +extern Display *display; +extern int screen_num; +extern Window win; +extern Screen *screen_ptr; + +//-------------------------------------- Resource Includes +#include "dgl/gBitmap.h" +#include "dgl/gFont.h" + +extern void createFontInit(); +extern void createFontShutdown(); +extern void installRedBookDevices(); +extern void handleRedBookCallback(U32, U32); + +static const char *windowClassName = "Darkstar Window Class"; +static char windowName[256] = "Darkstar Window"; +static bool gWindowCreated = false; + +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 U16 DIK_to_Key( U8 dikCode ); + +x86UNIXPlatformState x86UNIXState; +//Win32PlatState winState; + +//-------------------------------------- +//Win32PlatState::Win32PlatState() +//{ +// log_fp = NULL; +// hinstOpenGL = NULL; +// hinstGLU = NULL; +// hinstOpenAL = NULL; +// appWindow = NULL; +// appDC = NULL; +// appInstance = NULL; +// currentTime = 0; +// processId = 0; +//} + +//static bool windowLocked = false; + +static int keyboardState[256]; +static bool mouseButtonState[3]; +static bool capsLockDown = false; +static S32 modifierKeys = 0; +static bool windowActive = true; +static Point2I lastCursorPos(0,0); +static Point2I windowSize; +//static HANDLE gMutexHandle = NULL; +static bool sgDoubleByteEnabled = false; + +//-------------------------------------- +static const char *getMessageName(S32 msg) +{ +/* + switch(msg) + { + case WM_KEYDOWN: + return "WM_KEYDOWN"; + case WM_KEYUP: + return "WM_KEYUP"; + case WM_SYSKEYUP: + return "WM_SYSKEYUP"; + case WM_SYSKEYDOWN: + return "WM_SYSKEYDOWN"; + default: + return "Unknown!!"; + } +*/ +} + +//-------------------------------------- +bool Platform::excludeOtherInstances(const char *mutexName) +{ +/* + gMutexHandle = CreateMutex(NULL, true, mutexName); + if(!gMutexHandle) + return false; + if(GetLastError() == ERROR_ALREADY_EXISTS) + { + CloseHandle(gMutexHandle); + gMutexHandle = NULL; + return false; + } + return true; +*/ +} + +//-------------------------------------- +void Platform::AlertOK(const char *windowTitle, const char *message) +{ +/* + ShowCursor(true); + MessageBox(NULL, message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OK); +*/ +} + +//-------------------------------------- +bool Platform::AlertOKCancel(const char *windowTitle, const char *message) +{ +/* + ShowCursor(true); + return MessageBox(NULL, message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OKCANCEL) == IDOK; +*/ +} + +//-------------------------------------- +bool Platform::AlertRetry(const char *windowTitle, const char *message) +{ +/* + ShowCursor(true); + return (MessageBox(NULL, message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_RETRYCANCEL) == IDRETRY); +*/ +} + +//-------------------------------------- +static void InitInput() +{ + dMemset( keyboardState, 0, 256 ); + dMemset( mouseButtonState, 0, sizeof( mouseButtonState ) ); +// capsLockDown = (GetKeyState(VK_CAPITAL) & 0x01); +// if (capsLockDown) +// { +// keyboardState[VK_CAPITAL] |= 0x01; +// } +} + +//-------------------------------------- +static void setMouseClipping() +{ +/* + ClipCursor(NULL); + if(windowActive) + { + ShowCursor(false); + + RECT r; + GetWindowRect(winState.appWindow, &r); + + if(windowLocked) + { + POINT p; + GetCursorPos(&p); + lastCursorPos.set(p.x - r.left, p.y - r.top); + + ClipCursor(&r); + + S32 centerX = (r.right + r.left) >> 1; + S32 centerY = (r.bottom + r.top) >> 1; + SetCursorPos(centerX, centerY); + } + else + SetCursorPos(lastCursorPos.x + r.left, lastCursorPos.y + r.top); + } + else + ShowCursor(true); +*/ +} + +//-------------------------------------- +//static bool sgTaskbarHidden = false; +//static HWND sgTaskbar = NULL; + +static void hideTheTaskbar() +{ +// if ( !sgTaskbarHidden ) +// { +// sgTaskbar = FindWindow( "Shell_TrayWnd", NULL ); +// if ( sgTaskbar ) +// { +// APPBARDATA abData; +// dMemset( &abData, 0, sizeof( abData ) ); +// abData.cbSize = sizeof( abData ); +// abData.hWnd = sgTaskbar; +// SHAppBarMessage( ABM_REMOVE, &abData ); +// //ShowWindow( sgTaskbar, SW_HIDE ); +// sgTaskbarHidden = true; +// } +// } +} + +static void restoreTheTaskbar() +{ +// if ( sgTaskbarHidden ) +// { +// APPBARDATA abData; +// dMemset( &abData, 0, sizeof( abData ) ); +// abData.cbSize = sizeof( abData ); +// abData.hWnd = sgTaskbar; +// SHAppBarMessage( ABM_ACTIVATE, &abData ); +// //ShowWindow( sgTaskbar, SW_SHOW ); +// sgTaskbarHidden = false; +// } +} + +//-------------------------------------- +void Platform::enableKeyboardTranslation(void) +{ +} + +//-------------------------------------- +void Platform::disableKeyboardTranslation(void) +{ +} + +//-------------------------------------- +void Platform::setWindowLocked(bool locked) +{ +/* + windowLocked = locked; + setMouseClipping(); +*/ +} + +//-------------------------------------- +void Platform::minimizeWindow() +{ +/* + ShowWindow(winState.appWindow, SW_MINIMIZE); + restoreTheTaskbar(); +*/ +} + +//-------------------------------------- +/* +static void processKeyMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + S32 repeatCount = lParam & 0xffff; + S32 scanCode = (lParam >> 16) & 0xff; + S32 nVirtkey = dIsStandardVK(wParam) ? TranslateOSKeyCode(wParam) : DIK_to_Key(scanCode); + S32 keyCode; + if ( wParam == VK_PROCESSKEY && sgDoubleByteEnabled ) + keyCode = MapVirtualKey( scanCode, 1 ); // This is the REAL virtual key... + else + keyCode = wParam; + + bool extended = (lParam >> 24) & 1; + bool repeat = (lParam >> 30) & 1; + bool make = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN); + + S32 newVirtKey = nVirtkey; + switch(nVirtkey) + { + case KEY_ALT: + newVirtKey = extended ? KEY_RALT : KEY_LALT; + break; + case KEY_CONTROL: + newVirtKey = extended ? KEY_RCONTROL : KEY_LCONTROL; + break; + case KEY_SHIFT: + newVirtKey = ( scanCode == 54 ) ? KEY_RSHIFT : KEY_LSHIFT; + break; + case KEY_RETURN: + if ( extended ) + newVirtKey = KEY_NUMPADENTER; + break; + } + + S32 modKey = modifierKeys; + + if(make) + { + switch (newVirtKey) + { + case KEY_LSHIFT: modifierKeys |= SI_LSHIFT; modKey = 0; break; + case KEY_RSHIFT: modifierKeys |= SI_RSHIFT; modKey = 0; break; + case KEY_LCONTROL: modifierKeys |= SI_LCTRL; modKey = 0; break; + case KEY_RCONTROL: modifierKeys |= SI_RCTRL; modKey = 0; break; + case KEY_LALT: modifierKeys |= SI_LALT; modKey = 0; break; + case KEY_RALT: modifierKeys |= SI_RALT; modKey = 0; break; + } + if(nVirtkey == KEY_CAPSLOCK) + { + capsLockDown = !capsLockDown; + if(capsLockDown) + keyboardState[keyCode] |= 0x01; + else + keyboardState[keyCode] &= 0xFE; + } + keyboardState[keyCode] |= 0x80; + } + else + { + switch (newVirtKey) + { + case KEY_LSHIFT: modifierKeys &= ~SI_LSHIFT; modKey = 0; break; + case KEY_RSHIFT: modifierKeys &= ~SI_RSHIFT; modKey = 0; break; + case KEY_LCONTROL: modifierKeys &= ~SI_LCTRL; modKey = 0; break; + case KEY_RCONTROL: modifierKeys &= ~SI_RCTRL; modKey = 0; break; + case KEY_LALT: modifierKeys &= ~SI_LALT; modKey = 0; break; + case KEY_RALT: modifierKeys &= ~SI_RALT; modKey = 0; break; + } + keyboardState[keyCode] &= 0x7f; + } + + U16 ascii[3]; + WORD asciiCode = 0; + dMemset( &ascii, 0, sizeof( ascii ) ); + + S32 res = ToAscii( keyCode, scanCode, keyboardState, ascii, 0 ); + if (res == 2) + { + asciiCode = ascii[1]; + } + else if ((res == 1) || (res < 0)) + { + asciiCode = ascii[0]; + } + + InputEvent event; + + event.deviceInst = 0; + event.deviceType = KeyboardDeviceType; + event.objType = SI_KEY; + event.objInst = newVirtKey; + event.action = make ? (repeat ? SI_REPEAT : SI_MAKE ) : SI_BREAK; + event.modifier = modKey; + event.ascii = asciiCode; + event.fValue = make ? 1.0 : 0.0; + +#ifdef LOG_INPUT + char keyName[25]; + GetKeyNameText( lParam, keyName, 24 ); + if ( event.action == SI_MAKE ) + Input::log( "EVENT (Win): %s key pressed (Repeat count: %d). MODS:%c%c%c\n", keyName, repeatCount, + ( modKey & SI_SHIFT ? 'S' : '.' ), + ( modKey & SI_CTRL ? 'C' : '.' ), + ( modKey & SI_ALT ? 'A' : '.' ) ); + else + Input::log( "EVENT (Win): %s key released.\n", keyName ); +#endif + + Game->postEvent(event); +} +*/ + +// static void processCharMessage( WPARAM wParam, LPARAM lParam ) +// { +// TCHAR charCode = wParam; +// if ( charCode ) +// { +// S32 repeatCount = lParam & 0xFFFF; +// InputEvent event; +// event.deviceInst = 0; +// event.deviceType = KeyboardDeviceType; +// event.action = SI_CHAR; +// event.modifier = modifierKeys; +// event.ascii = charCode; +// +// for ( U32 i = 0; i < repeatCount; i++ ) +// { +// Game->postEvent( event ); +// } +// } +// } + +static S32 mouseX = 0xFFFFFFFF; +static S32 mouseY = 0xFFFFFFFF; + +//-------------------------------------- +static void CheckCursorPos() +{ +/* + if(windowLocked && windowActive) + { + POINT mousePos; + GetCursorPos(&mousePos); + RECT r; + + GetWindowRect(winState.appWindow, &r); + + S32 centerX = (r.right + r.left) >> 1; + S32 centerY = (r.bottom + r.top) >> 1; + + if(mousePos.x != centerX) + { + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_XAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = (mousePos.x - centerX); + Game->postEvent(event); + } + if(mousePos.y != centerY) + { + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_YAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = (mousePos.y - centerY); + Game->postEvent(event); + } + SetCursorPos(centerX, centerY); + } +*/ +} + +//-------------------------------------- +static void mouseButtonEvent(S32 action, S32 objInst) +{ +/* + CheckCursorPos(); + if(!windowLocked) + { + if(action == SI_MAKE) + SetCapture(winState.appWindow); + else + ReleaseCapture(); + } + + U32 buttonId = objInst - KEY_BUTTON0; + if ( buttonId < 3 ) + mouseButtonState[buttonId] = ( action == SI_MAKE ) ? true : false; + + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_BUTTON; + event.objInst = objInst; + event.action = action; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = action == SI_MAKE ? 1.0 : 0.0; + +#ifdef LOG_INPUT + if ( action == SI_MAKE ) + Input::log( "EVENT (Win): mouse button%d pressed. MODS:%c%c%c\n", buttonId, ( modifierKeys & SI_SHIFT ? 'S' : '.' ), ( modifierKeys & SI_CTRL ? 'C' : '.' ), ( modifierKeys & SI_ALT ? 'A' : '.' ) ); + else + Input::log( "EVENT (Win): mouse button%d released.\n", buttonId ); +#endif + Game->postEvent(event); +*/ +} + +//-------------------------------------- +static void mouseWheelEvent( S32 delta ) +{ +/* + static S32 _delta = 0; + + _delta += delta; + if ( abs( delta ) >= WHEEL_DELTA ) + { + _delta = 0; + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_ZAXIS; + event.objInst = 0; + event.action = SI_MOVE; + event.modifier = modifierKeys; + event.ascii = 0; + event.fValue = delta; + +#ifdef LOG_INPUT + Input::log( "EVENT (Win): mouse wheel moved. delta = %d\n", delta ); +#endif + + Game->postEvent( event ); + } +*/ +} + + +//struct WinMessage +//{ +// UINT message; +// WPARAM wParam; +// LPARAM lParam; +// +// WinMessage(UINT m, WPARAM w, LPARAM l) : message(m), wParam(w), lParam(l) {} +//}; + +//Vector sgWinMessages; + +//-------------------------------------- +//static LRESULT PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +//{ +// switch ( message ) +// { +// case WM_POWERBROADCAST: +// { +// if(wParam == PBT_APMQUERYSUSPEND) +// return BROADCAST_QUERY_DENY; +// return true; +// } +// case WM_ACTIVATEAPP: +// if ((bool) wParam) +// { +// Video::reactivate(); +// ShowCursor(false); +// if ( Video::isFullScreen() ) +// hideTheTaskbar(); +// // HACK: Windows 98 (after switching from fullscreen to windowed mode) +// SetForegroundWindow( winState.appWindow ); +// } +// else +// { +// Video::deactivate(); +// restoreTheTaskbar(); +// } +// break; +// case WM_SYSCOMMAND: +// switch(wParam) +// { +// case SC_SCREENSAVE: +// case SC_MONITORPOWER: +// if(GetForegroundWindow() == winState.appWindow) +// { +// return 0; +// } +// break; +// } +// break; +// case WM_ACTIVATE: +// windowActive = LOWORD(wParam) != WA_INACTIVE; +// if ( windowActive ) +// { +// Game->refreshWindow(); +// Input::activate(); +// } +// else +// { +// DInputManager* mgr = dynamic_cast( Input::getManager() ); +// if ( !mgr || !mgr->isMouseActive() ) +// { +// // Deactivate all the mouse triggers: +// for ( U32 i = 0; i < 3; i++ ) +// { +// if ( mouseButtonState[i] ) +// mouseButtonEvent( SI_BREAK, KEY_BUTTON0 + i ); +// } +// } +// Input::deactivate(); +// } +// setMouseClipping(); +// break; +// +// case WM_MOVE: +// Game->refreshWindow(); +// break; +// +// case MM_MCINOTIFY: +// handleRedBookCallback(wParam, lParam); +// break; +// +// //case WM_DESTROY: +// case WM_CLOSE: +// PostQuitMessage(0); +// break; +// default: +// { +// if (sgQueueEvents) +// { +// WinMessage msg(message,wParam,lParam); +// +// sgWinMessages.push_front(msg); +// } +// } +// } +// +// return DefWindowProc(hWnd, message, wParam, lParam); +//} + +//-------------------------------------- +static void OurDispatchMessages() +{ +/* + WinMessage msg(0,0,0); + UINT message; + WPARAM wParam; + LPARAM lParam; + + DInputManager* mgr = dynamic_cast( Input::getManager() ); + + while (sgWinMessages.size()) + { + msg = sgWinMessages.front(); + sgWinMessages.pop_front(); + message = msg.message; + wParam = msg.wParam; + lParam = msg.lParam; + + if ( !mgr || !mgr->isMouseActive() ) + { + switch ( message ) + { + case WM_MOUSEMOVE: + if ( !windowLocked ) + { + MouseMoveEvent event; + + event.xPos = LOWORD(lParam); // horizontal position of cursor + event.yPos = HIWORD(lParam); // vertical position of cursor + event.modifier = modifierKeys; + +#ifdef LOG_INPUT +#ifdef LOG_MOUSEMOVE + Input::log( "EVENT (Win): mouse move to (%d, %d).\n", event.xPos, event.yPos ); +#endif +#endif + Game->postEvent(event); + } + break; + case WM_LBUTTONDOWN: + mouseButtonEvent(SI_MAKE, KEY_BUTTON0); + break; + case WM_MBUTTONDOWN: + mouseButtonEvent(SI_MAKE, KEY_BUTTON2); + break; + case WM_RBUTTONDOWN: + mouseButtonEvent(SI_MAKE, KEY_BUTTON1); + break; + case WM_LBUTTONUP: + mouseButtonEvent(SI_BREAK, KEY_BUTTON0); + break; + case WM_MBUTTONUP: + mouseButtonEvent(SI_BREAK, KEY_BUTTON2); + break; + case WM_RBUTTONUP: + mouseButtonEvent(SI_BREAK, KEY_BUTTON1); + break; + case WM_MOUSEWHEEL: + mouseWheelEvent( (S16) HIWORD( wParam ) ); + break; + +// case WM_CHAR: +// processCharMessage( wParam, lParam ); +// break; + } + } + + if ( !mgr || !mgr->isKeyboardActive() ) + { + switch ( message ) + { + case WM_KEYUP: + case WM_SYSKEYUP: + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + processKeyMessage(message, wParam, lParam); + break; + } + } + } +*/ +} + +//-------------------------------------- +static bool ProcessMessages() +{ +/* + MSG msg; + + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + if(msg.message == WM_QUIT) + return false; + + TranslateMessage(&msg); + DispatchMessage(&msg); + OurDispatchMessages(); + } + + return true; +*/ +} + +//-------------------------------------- +void Platform::process() +{ + stdConsole->process(); +/* + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isMouseActive() ) + CheckCursorPos(); + WindowsConsole->process(); + + if(!ProcessMessages()) + { + // generate a quit event + Event quitEvent; + quitEvent.type = QuitEventType; + + Game->postEvent(quitEvent); + } + // if there's no window, we sleep 1, otherwise we sleep 0 + if(!Game->isJournalReading()) + Sleep(gWindowCreated ? 0 : 1); // give others some process time if necessary... + HWND window = GetForegroundWindow(); + if (window && gWindowCreated) + { + // check to see if we are in the foreground or not + // if not Sleep for 100ms or until a Win32 message/input is recieved + DWORD foregroundProcessId; + GetWindowThreadProcessId(window, &foregroundProcessId); + if (foregroundProcessId != winState.processId) + MsgWaitForMultipleObjects(0, NULL, false, 100, QS_ALLINPUT); + } + +*/ +// Input::process(); +} + +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 + +//-------------------------------------- +static void createX11Window(const Point2I &initialSize, const char *name) +{ + const char *window_name = name; + const char *icon_name = name; + char *display_name = NULL; + XSizeHints *size_hints; + XWMHints *wm_hints; + XClassHint *class_hints; + + /* get various "HINTS" */ + size_hints = XAllocSizeHints(); + wm_hints = XAllocWMHints(); + class_hints = XAllocClassHint(); + + /* open a connection to the X server */ + display = XOpenDisplay(display_name); + AssertFatal(display, "unable to connect to display"); +// if ( (display = XOpenDisplay(display_name)) == NULL ) +// { +// Con::printf("unable to connect to display %s", XDisplayName(display_name)); +// exit(-1); +// } + + screen_num = DefaultScreen(display); + screen_ptr = DefaultScreenOfDisplay(display); + + /* create the window */ + win = XCreateSimpleWindow(display, RootWindow(display, screen_num), 0, 0, initialSize.x, initialSize.x, 4, BlackPixel(display, screen_num), WhitePixel(display, screen_num)); + + /* set the windows' title and icon names */ + XTextProperty windowName, iconName; + XStringListToTextProperty(&window_name, 1, &windowName); + XStringListToTextProperty(&icon_name, 1, &iconName); + XSetWMProperties(display, win, &windowName, &iconName, NULL, 1, size_hints, wm_hints, class_hints); + + /* show window */ + XMapWindow(display, win); + XFlush(display); +} + +static void getX11DesktopState() +{ + XWindowAttributes xwa; + XGetWindowAttributes(display, win, &xwa); + x86UNIXState.desktopWidth = DisplayWidth(display, screen_num); + x86UNIXState.desktopHeight = DisplayHeight(display, screen_num); + x86UNIXState.desktopBitsPixel = xwa.depth; +} + +static void InitWindowClass() +{ +/* + WNDCLASS wc; + dMemset(&wc, 0, sizeof(wc)); + + wc.style = CS_OWNDC; + wc.lpfnWndProc = WindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = winState.appInstance; + wc.hIcon = LoadIcon(winState.appInstance, MAKEINTRESOURCE(IDI_ICON2)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); + wc.lpszMenuName = 0; + wc.lpszClassName = windowClassName; + RegisterClass( &wc ); + + // Curtain window class: + wc.lpfnWndProc = DefWindowProc; + wc.hCursor = NULL; + wc.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH); + wc.lpszClassName = "Curtain"; + RegisterClass( &wc ); +*/ +} + +//-------------------------------------- +/* +static void GetDesktopState() +{ + HWND hDesktop = GetDesktopWindow(); + HDC hDeskDC = GetDC( hDesktop ); + winState.desktopBitsPixel = GetDeviceCaps( hDeskDC, BITSPIXEL ); + winState.desktopWidth = GetDeviceCaps( hDeskDC, HORZRES ); + winState.desktopHeight = GetDeviceCaps( hDeskDC, VERTRES ); + ReleaseDC( hDesktop, hDeskDC ); +} +*/ + +//-------------------------------------- +/* +HWND CreateOpenGLWindow( U32 width, U32 height, bool fullScreen ) +{ + S32 windowStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + S32 exWindowStyle = 0; + + if ( fullScreen ) + windowStyle |= ( WS_POPUP | WS_MAXIMIZE ); + else + windowStyle |= ( WS_OVERLAPPED | WS_CAPTION ); + + return CreateWindowEx( + exWindowStyle, + windowClassName, + windowName, + windowStyle, + 0, 0, width, height, + NULL, NULL, + winState.appInstance, + NULL); +} +*/ + +//-------------------------------------- +/* +HWND CreateCurtain( U32 width, U32 height ) +{ + return CreateWindow( + "Curtain", + "", + ( WS_POPUP | WS_MAXIMIZE | WS_VISIBLE ), + 0, 0, + width, height, + NULL, NULL, + winState.appInstance, + NULL ); +} +*/ + +//-------------------------------------- +/* +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; +} +*/ + +//-------------------------------------- +enum { MAX_PFDS = 256 }; +/* +S32 ChooseBestPixelFormat(HDC hDC, PIXELFORMATDESCRIPTOR *pPFD) +{ + PIXELFORMATDESCRIPTOR pfds[MAX_PFDS+1]; + S32 i; + S32 bestMatch = 0; + + S32 maxPFD = qwglDescribePixelFormat(hDC, 1, sizeof(PIXELFORMATDESCRIPTOR), &pfds[0]); + if(maxPFD > MAX_PFDS) + maxPFD = MAX_PFDS; + + bool accelerated = false; + + for(i = 1; i <= maxPFD; i++) + { + qwglDescribePixelFormat(hDC, i, sizeof(PIXELFORMATDESCRIPTOR), &pfds[i]); + + // make sure this has hardware acceleration: + if ( ( pfds[i].dwFlags & PFD_GENERIC_FORMAT ) != 0 ) + continue; + + // verify pixel type + if ( pfds[i].iPixelType != PFD_TYPE_RGBA ) + continue; + + // verify proper flags + if ( ( ( pfds[i].dwFlags & pPFD->dwFlags ) & pPFD->dwFlags ) != pPFD->dwFlags ) + continue; + + accelerated = !(pfds[i].dwFlags & PFD_GENERIC_FORMAT); + + // + // selection criteria (in order of priority): + // + // PFD_STEREO + // colorBits + // depthBits + // stencilBits + // + if ( bestMatch ) + { + // check stereo + if ( ( pfds[i].dwFlags & PFD_STEREO ) && ( !( pfds[bestMatch].dwFlags & PFD_STEREO ) ) && ( pPFD->dwFlags & PFD_STEREO ) ) + { + bestMatch = i; + continue; + } + + if ( !( pfds[i].dwFlags & PFD_STEREO ) && ( pfds[bestMatch].dwFlags & PFD_STEREO ) && ( pPFD->dwFlags & PFD_STEREO ) ) + { + bestMatch = i; + continue; + } + + // check color + if ( pfds[bestMatch].cColorBits != pPFD->cColorBits ) + { + // prefer perfect match + if ( pfds[i].cColorBits == pPFD->cColorBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( pfds[i].cColorBits > pfds[bestMatch].cColorBits ) + { + bestMatch = i; + continue; + } + } + + // check depth + if ( pfds[bestMatch].cDepthBits != pPFD->cDepthBits ) + { + // prefer perfect match + if ( pfds[i].cDepthBits == pPFD->cDepthBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( pfds[i].cDepthBits > pfds[bestMatch].cDepthBits ) + { + bestMatch = i; + continue; + } + } + + // check stencil + if ( pfds[bestMatch].cStencilBits != pPFD->cStencilBits ) + { + // prefer perfect match + if ( pfds[i].cStencilBits == pPFD->cStencilBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( ( pfds[i].cStencilBits > pfds[bestMatch].cStencilBits ) && + ( pPFD->cStencilBits > 0 ) ) + { + bestMatch = i; + continue; + } + } + } + else + { + bestMatch = i; + } + } + + if ( !bestMatch ) + return 0; + + else if ( pfds[bestMatch].dwFlags & PFD_GENERIC_ACCELERATED ) + { + // MCD + } + else + { + // ICD + } + + *pPFD = pfds[bestMatch]; + + return bestMatch; +} +*/ + +//-------------------------------------- +// +// This function exists so DirectInput can communicate with the Windows mouse +// in windowed mode. +// +//-------------------------------------- +void setModifierKeys( S32 modKeys ) +{ +// modifierKeys = modKeys; +} + +//-------------------------------------- +const Point2I &Platform::getWindowSize() +{ + return windowSize; +} + +//-------------------------------------- +void Platform::setWindowSize( U32 newWidth, U32 newHeight ) +{ + windowSize.set( newWidth, newHeight ); +} + +//-------------------------------------- +static void InitWindow(const Point2I &initialSize) +{ +// windowSize = initialSize; +// +// // The window is created when the display device is activated. BH +// +// winState.processId = GetCurrentProcessId(); +} + +//-------------------------------------- +static void InitOpenGL() +{ +/* + // The OpenGL initialization stuff has been mostly moved to the display + // devices' activate functions. BH + + 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" ); + + // If no device is specified, see which ones we have... + if ( !Video::setDevice( Con::getVariable( "$pref::Video::displayDevice" ), width, height, bpp, fullScreen ) ) + { + // First, try the default OpenGL device: + if ( !Video::setDevice( "OpenGL", width, height, bpp, fullScreen ) ) + { + // Next, try the D3D device: + if ( !Video::setDevice( "D3D", width, height, bpp, fullScreen ) ) + { + // Finally, try the Voodoo2 device: + if ( !Video::setDevice( "Voodoo2", width, height, bpp, fullScreen ) ) + { + AssertFatal( false, "Could not find a compatible display device!" ); + return; + } + } + } + } +*/ +} + +//-------------------------------------- +ConsoleFunction( getDesktopResolution, const char*, 1, 1, "getDesktopResolution()" ) +{ +/* + argc; argv; + char buffer[256]; + dSprintf( buffer, sizeof( buffer ), "%d %d %d", winState.desktopWidth, winState.desktopHeight, winState.desktopBitsPixel ); + char* returnString = Con::getReturnBuffer( dStrlen( buffer ) + 1 ); + dStrcpy( returnString, buffer ); + return( returnString ); +*/ +} + +//-------------------------------------- +void Platform::init() +{ + // Set the platform variable for the scripts + Con::setVariable( "$platform", "x86UNIX" ); + + StdConsole::create(); +// createX11Window(); +// getX11DesktopState(); + +// Input::init(); +// InitInput(); // just in case Input::init() doesn't work. +// if ( !StdConsole::isEnabled() ) +// Input::init(); +// InitInput(); // in case DirectInput falls through +// InitWindowClass(); +// GetDesktopState(); +// installRedBookDevices(); +// +// sgDoubleByteEnabled = GetSystemMetrics( SM_DBCSENABLED ); +// sgQueueEvents = true; +} + +//-------------------------------------- +void Platform::shutdown() +{ + Input::destroy(); + StdConsole::destroy(); +/* + sgQueueEvents = false; + + if(gMutexHandle) + CloseHandle(gMutexHandle); + setWindowLocked( false ); + Video::destroy(); + Input::destroy(); + WinConsole::destroy(); +*/ +} + + +static U32 lastTimeTick; + +//-------------------------------------- +static S32 run(S32 argc, const char **argv) +{ +// createFontInit(); + windowSize.set(0,0); + + lastTimeTick = Platform::getRealMilliseconds(); + + S32 ret = Game->main(argc, argv); +// createFontShutdown(); + return ret; +} + +//-------------------------------------- +void Platform::initWindow(const Point2I &initialSize, const char *name) +{ +/* + Con::printf( "Video Init:" ); + Video::init(); + if ( Video::installDevice( OpenGLDevice::create() ) ) + Con::printf( " Accelerated OpenGL display device detected." ); + else + Con::printf( " Accelerated OpenGL display device not detected." ); + + if ( Video::installDevice( D3DDevice::create() ) ) + Con::printf( " Accelerated D3D device detected." ); + else + Con::printf( " Accelerated D3D device not detected." ); + + if ( Video::installDevice( Voodoo2Device::create() ) ) + Con::printf( " Voodoo 2 display device detected." ); + else + Con::printf( " Voodoo 2 display device not detected." ); + Con::printf( "" ); + + gWindowCreated = true; +*/ +// dStrcpy(windowName, name); +// InitWindow(initialSize); +//fprintf(stdout,"%d, %d\n", initialSize.x, initialSize.y); + createX11Window(initialSize, name); + InitOpenGL(); +} + +//-------------------------------------- +S32 main(S32 argc, const char **argv) +{ +/* + winState.appInstance = GetModuleHandle(argv[0]); +*/ + return run(argc, argv); +} + +//-------------------------------------- +/* +S32 PASCAL WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR lpszCmdLine, S32) +{ + const char *ptr = lpszCmdLine; + Vector argv; + char moduleName[256]; + GetModuleFileName(NULL, moduleName, sizeof(moduleName)); + + argv.push_back(moduleName); + S32 i = 0; + for(;;) + { + char c = ptr[i]; + if(c == 0 || c == ' ') + { + if(i) + { + char *arg = (char *) dMalloc(i+1); + dStrncpy(arg, ptr, i); + arg[i] = 0; + argv.push_back(arg); + ptr += i + 1; + i = 0; + } + if(!c) + break; + } + else + i++; + } + 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; +} +*/ + +//-------------------------------------- +void TimeManager::process() +{ + U32 curTime = Platform::getRealMilliseconds(); + TimeEvent event; + event.elapsedTime = curTime - lastTimeTick; + if(event.elapsedTime > 5) + { + lastTimeTick = curTime; + Game->postEvent(event); + } +} + +/* +GLimp_Init + GLW_LoadOpenGL + QGL_Init(driver); + GLW_StartDriverAndSetMode + GLW_SetMode + ChangeDisplaySettings + GLW_CreateWindow + GLW_InitDriver + GLW_CreatePFD + GLW_MakeContext + GLW_ChoosePFD + DescribePixelFormat + SetPixelFormat + + GLW_InitExtensions + WG_CheckHardwareGamma +*/ + +F32 Platform::getRandom() +{ +// return sgPlatRandom.randF(); + return rand(); +} + +//-------------------------------------- +// Web browser function: +//-------------------------------------- +bool Platform::openWebBrowser( const char* webAddress ) +{ +/* + static bool sHaveKey = false; + static U8 sWebKey[512]; + + if ( !sHaveKey ) + { + DWORD size = sizeof( sWebKey ); + HKEY regKey; + + if ( RegOpenKeyEx( HKEY_CLASSES_ROOT, ".htm", 0, KEY_QUERY_VALUE, ®Key ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Failed to open the .htm registry key!!!" ); + return( false ); + } + + if ( RegQueryValueEx( regKey, "", NULL, NULL, sWebKey, &size ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Failed to query the .htm registry key!!!" ); + return( false ); + } + + RegCloseKey( regKey ); + + Con::printf( "Registry key = %s", (const char*) sWebKey ); + dStrcat( (char*) sWebKey, "\\shell\\open\\command" ); + + if ( RegOpenKeyEx( HKEY_CLASSES_ROOT, (const char*) sWebKey, 0, KEY_QUERY_VALUE, ®Key ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Failed to open the %s registry key!!!", (const char*) sWebKey ); + return( false ); + } + + size = sizeof( sWebKey ); + if ( RegQueryValueEx( regKey, "", NULL, NULL, sWebKey, &size ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Failed to query the open command registry key!!!" ); + return( false ); + } + + RegCloseKey( regKey ); + sHaveKey = true; + } + + char buf[1024]; + dSprintf( buf, sizeof( buf ), "%s %s", (const char*) sWebKey, webAddress ); + + STARTUPINFO si; + dMemset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + //Con::errorf( ConsoleLogEntry::General, "** Web browser command = %s **", buf ); + + PROCESS_INFORMATION pi; + dMemset( &pi, 0, sizeof( pi ) ); + CreateProcess( NULL, + buf, + NULL, + NULL, + false, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + &si, + &pi ); + + return( true ); +*/ +} + +//-------------------------------------- +// Login password routines: +//-------------------------------------- +static const char* V12RegKey = "SOFTWARE\\GarageGames\\V12"; + +const char* Platform::getLoginPassword() +{ +/* + HKEY regKey; + char* returnString = NULL; + if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, V12RegKey, 0, KEY_QUERY_VALUE, ®Key ) == ERROR_SUCCESS ) + { + U8 buf[32]; + DWORD size = sizeof( buf ); + if ( RegQueryValueEx( regKey, "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, V12RegKey, 0, KEY_WRITE, ®Key ) == ERROR_SUCCESS ) + { + if ( RegSetValueEx( regKey, "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 V12 registry key!" ); + + return( false ); +*/ +} + +//-------------------------------------- +// Dedicated server launcher: +//-------------------------------------- +ConsoleFunction( launchDedicatedServer, bool, 4, 4, "launchDedicatedServer( missionType, map, botCount )" ) +{ +/* + argc; + + // Set up the command line: + char cmdLine[512]; + const char* thisCmdLine = GetCommandLine(); + const char* temp = dStrstr( thisCmdLine, ".exe" ); + if ( !temp ) + return( false ); + + U32 len = temp - thisCmdLine + 4; + dStrncpy( cmdLine, thisCmdLine, len ); + if ( cmdLine[0] == '\"' ) + { + cmdLine[len] = '\"'; + len++; + } + dSprintf( cmdLine + len, sizeof( cmdLine ) - len, " %s-dedicated -mission %s %s -bot %d", + Con::getBoolVariable( "$PlayingOnline" ) ? "" : "-nologin ", + argv[1], + argv[2], + dAtoi( argv[3] ) ); + Con::errorf( "** launching dedicated server - command line = \"%s\" **", cmdLine ); + + STARTUPINFO si; + dMemset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + dMemset( &pi, 0, sizeof( pi ) ); + + return CreateProcess( NULL, + cmdLine, + NULL, + NULL, + false, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + &si, + &pi ); +*/ +} + +//-------------------------------------- +// Silly Korean registry key checker: +//-------------------------------------- +ConsoleFunction( isKoreanBuild, bool, 1, 1, "isKoreanBuild()" ) +{ +/* + argc; argv; + HKEY regKey; + bool result = false; + if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, V12RegKey, 0, KEY_QUERY_VALUE, ®Key ) == ERROR_SUCCESS ) + { + DWORD val; + DWORD size = sizeof( val ); + if ( RegQueryValueEx( regKey, "Korean", NULL, NULL, (U8*) &val, &size ) == ERROR_SUCCESS ) + result = ( val > 0 ); + + RegCloseKey( regKey ); + } + + return( result ); +*/ +} diff --git a/sceneGraph/detailManager.cc b/sceneGraph/detailManager.cc new file mode 100644 index 0000000..e88589d --- /dev/null +++ b/sceneGraph/detailManager.cc @@ -0,0 +1,447 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/detailManager.h" +#include "ts/tsShapeInstance.h" +#include "ts/tsPartInstance.h" +#include "dgl/dgl.h" + +// bias towards using same detail level as previous frame +#define MatchPreviousReward 0.99f +#define NotMatchPreviousPenalty 1.01f + +// this is the pref value that the user should be able to set +F32 DetailManager::smDetailScale = 1.0f; +// this should be fixed at some large upper-bound +S32 DetailManager::smMaxPolyLimit = 20000; +// this should be fixed at some small lower-bound +S32 DetailManager::smMinPolyLimit = 2000; +// this determines how much we can under-shoot poly limit when reducing +S32 DetailManager::smLimitRange = 1000; + +// default detail profile -- delay detailing out 2 times... +DetailManager::DetailProfile DetailManager::smDefaultProfile = { 0, 0, 2 }; + +DetailManager * DetailManager::smDetailManager = NULL; + +S32 DetailManager::smPolysDidRender = 0; +S32 DetailManager::smPolysTriedToRender = 0; + +S32 QSORT_CALLBACK FN_CDECL compareDetailData( const void * e1, const void * e2 ) +{ + const DetailManager::DetailData * dd1 = *(const DetailManager::DetailData**)e1; + const DetailManager::DetailData * dd2 = *(const DetailManager::DetailData**)e2; + + if (dd1->priority < dd2->priority) + return -1; + else if (dd2->priority < dd1->priority) + return 1; + else + return 0; +} + +//--------------------------------------------------------- +DetailManager::DetailManager() +{ + mInPrepRender = false; +} + +DetailManager::~DetailManager() +{ + S32 i; + for (i=0; idl = dd->dls[bump]; + dd->intraDL = 1.0f; + mPolyCount -= dd->bump[bump]; + for (S32 i=0; ibump[i] -= dd->bump[bump]; +} + +void DetailManager::bumpAll(S32 bump) +{ + for (S32 i=0; i=0;i--) + if (mDetailData[i]->tag!=mTag) + { + // not rendering this time + mDetailData[i]->shapeInstance = NULL; // enough to disconnect it from shape instance... + mDetailData[i]->partInstance = NULL; // enough to disconnect it from part instance... + mFreeDetailData.push_back(mDetailData[i]); + mDetailData.erase(i); + } + + // we may already be below the poly limit + if (mPolyCountbump[bump] > mPolyLimit-smLimitRange) + { + bumpOne(mDetailData[i],bump); + if (mPolyCountselectCurrentDetail2(dist); + if (dl<0) + // don't render + return; + + DetailData * dd; + if (!si->mData || ((DetailData*)si->mData)->shapeInstance!=si) + { + // we weren't rendered last time + // get new detail data and set prevDL to -2 to encode that we're fresh meat... + si->mData = (U32) getNewDetailData(); + dd = (DetailData*)si->mData; + dd->prevDL = -2; + } + else + { + // we were rendered last time + dd = (DetailData*)si->mData; + dd->prevDL = dd->dl; + } + + dd->shapeInstance = si; + dd->partInstance = NULL; + dd->tag = mTag; + dd->dl = dl; + dd->pixelSize = -dist; //pixelRadius; BROKEN, but this'll be fine for now + dd->intraDL = si->getCurrentIntraDetail(); + + // add in poly count for preferred detail level + S32 polyCount = si->getShape()->details[dl].polyCount; + mPolyCount += polyCount; + + S32 countFirst = 0; + S32 countMiddle = 0; + S32 countLast = 0; + for (S32 i=0; iskipFirst) + { + countFirst++; + dup = true; + } + + // duplicate last detail...or... + if (dl==si->getShape()->mSmallestVisibleDL && countLastskipLast) + { + countLast++; + dup = true; + } + + // duplicate other details... + if (countMiddleskipMiddle) + { + countMiddle++; + dup = true; + } + else + countMiddle = 0; + + // find the next detail... + if (!dup) + { + if (dl==si->getShape()->mSmallestVisibleDL) + dl = -1; + else + dl++; + } + + dd->dls[i] = dl; + S32 detailPolys = dl>=0 ? si->getShape()->details[dl].polyCount : 0; + dd->bump[i] = polyCount - detailPolys; + mBumpPolyCount[i] += dd->bump[i]; + + if (dl<0) + { + for (S32 j=i+1; jdls[j] = -1; + dd->bump[j] = polyCount; + mBumpPolyCount[j] += dd->bump[j]; + } + break; + } + } +} + +//--------------------------------------------------------- +void DetailManager::selectPotential(TSPartInstance * pi, F32 dist, F32 invScale, const DetailProfile * dp) +{ + AssertFatal(mInPrepRender,"DetailManager::selectPotentialDetails"); + + dist *= invScale; + pi->selectCurrentDetail2(dist); + S32 dl = pi->getCurrentObjectDetail(); + if (dl<0) + // don't render + return; + + DetailData * dd; + if (!pi->mData || ((DetailData*)pi->mData)->partInstance!=pi) + { + // we weren't rendered last time + // get new detail data and set prevDL to -2 to encode that we're fresh meat... + pi->mData = (U32) getNewDetailData(); + dd = (DetailData*)pi->mData; + dd->prevDL = -2; + } + else + { + // we were rendered last time + dd = (DetailData*)pi->mData; + dd->prevDL = dd->dl; + } + + dd->shapeInstance = NULL; + dd->partInstance = pi; + dd->tag = mTag; + dd->dl = dl; + dd->pixelSize = -dist; + dd->intraDL = pi->getCurrentIntraDetail(); + + // add in poly count for preferred detail level + S32 polyCount = pi->getPolyCount(dl); + mPolyCount += polyCount; + + S32 countFirst = 0; + S32 countMiddle = 0; + S32 countLast = 0; + for (S32 i=0; iskipFirst) + { + countFirst++; + dup = true; + } + + // duplicate last detail...or... + if (dl==pi->getNumDetails()-1 && countLastskipLast) + { + countLast++; + dup = true; + } + + // duplicate other details... + if (countMiddleskipMiddle) + { + countMiddle++; + dup = true; + } + else + countMiddle = 0; + + // find the next detail... + if (!dup) + { + if (dl==pi->getNumDetails()-1) + dl = -1; + else + dl++; + } + + dd->dls[i] = dl; + S32 detailPolys = pi->getPolyCount(dl); + dd->bump[i] = polyCount - detailPolys; + mBumpPolyCount[i] += dd->bump[i]; + + if (dl<0) + { + for (S32 j=i+1; jdls[j] = -1; + dd->bump[j] = polyCount; + mBumpPolyCount[j] += dd->bump[j]; + } + break; + } + } +} + +//--------------------------------------------------------- +bool DetailManager::selectCurrent(TSShapeInstance * si) +{ + AssertFatal(!mInPrepRender,"DetailManager::selectCurrentDetail"); + + DetailData * dd = (DetailData*)si->mData; + if ( !dd || dd->shapeInstance != si) + // not rendering... + return false; + si->setCurrentDetail(dd->dl,dd->intraDL); + return true; +} + +//--------------------------------------------------------- +bool DetailManager::selectCurrent(TSPartInstance * pi) +{ + AssertFatal(!mInPrepRender,"DetailManager::selectCurrentDetail"); + + DetailData * dd = (DetailData*)pi->mData; + if ( !dd || dd->partInstance != pi) + // not rendering... + return false; + pi->setCurrentObjectDetail(dd->dl); + pi->setCurrentIntraDetail(dd->intraDL); + return true; +} + +//--------------------------------------------------------- +void DetailManager::computePriority(DetailData * detailData, S32 bump) +{ + S32 oldSize, newSize; + if (detailData->shapeInstance) + { + // shape instance... + if (bump>0) + oldSize = detailData->dl>=0 ? detailData->shapeInstance->getShape()->details[detailData->dl].size : 0; + else + oldSize = detailData->pixelSize; + newSize = detailData->dls[bump]>=0 ? detailData->shapeInstance->getShape()->details[detailData->dls[bump]].size : 0; + } + else + { + // part instance... + AssertFatal(detailData->partInstance,"DetailManager::computePriority"); + if (bump>0) + oldSize = detailData->partInstance->getDetailSize(detailData->dl); + else + oldSize = detailData->pixelSize; + newSize = detailData->partInstance->getDetailSize(detailData->dls[bump]); + } + + // priority weighted by both total bump and recent bump (total bump component helps break ties + // when recent bump has the same effect). + detailData->priority = 0.5f * (detailData->pixelSize - oldSize) + (oldSize - newSize); + + // try to be consistent between frames...as much as possible + if (detailData->prevDL<0) + { + if (detailData->prevDL==-1) + { + // weren't visible last time...penalize for being visible this time, reward for being invisible + if (detailData->dls[bump]==-1) + detailData->priority *= MatchPreviousReward; + else if (detailData->dl!=-1) + detailData->priority *= NotMatchPreviousPenalty; + } + } + else + { + // reward for bumping us down if we are at a higher detail than last time, + // penalize for bumping us down if our detail is at or lower than last time + // Note: higher detail --> smaller dl number + if (detailData->dl > detailData->prevDL) + detailData->priority *= MatchPreviousReward; + else + detailData->priority *= NotMatchPreviousPenalty; + } +} + +//--------------------------------------------------------- +DetailManager::DetailData * DetailManager::getNewDetailData() +{ + DetailData * ret; + if (mFreeDetailData.size()) + { + ret = mFreeDetailData.last(); + mFreeDetailData.decrement(); + } + else + ret = new DetailData; + mDetailData.push_back(ret); + return ret; +} + diff --git a/sceneGraph/detailManager.h b/sceneGraph/detailManager.h new file mode 100644 index 0000000..6177a57 --- /dev/null +++ b/sceneGraph/detailManager.h @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DETAILMANAGER_H_ +#define _DETAILMANAGER_H_ + +#ifndef _PLATFORMASSERT_H_ +#include "Platform/platformAssert.h" +#endif +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + +#define MAX_BUMP 4 + +class TSShapeInstance; +class TSPartInstance; + +class DetailManager +{ +public: + + struct DetailData + { + S32 tag; + TSShapeInstance * shapeInstance; // either this + TSPartInstance * partInstance; // or this + S16 bump[MAX_BUMP]; + S16 dls[MAX_BUMP]; + S32 dl; + F32 intraDL; + S32 prevDL; + F32 pixelSize; + F32 priority; + }; + + struct DetailProfile + { + S32 skipFirst; // for how many rounds do we want to skip "bumping" first detail + S32 skipMiddle; // for how many rounds do we want to skip "bumping" every middle detail + S32 skipLast; // for how many rounds do we want to skip "bumping" last detail + }; + + static DetailProfile smDefaultProfile; + +private: + + static DetailManager * smDetailManager; + static DetailManager * get() { AssertFatal(smDetailManager,"DetailManager::get"); return smDetailManager; } + + S32 mTag; + S32 mPolyLimit; // our poly budget...computed at the start of each frame based on detail scale + bool mInPrepRender; + + S32 mPolyCount; // current poly count + S32 mBumpPolyCount[MAX_BUMP]; // number of polys we save at each "bump" level + + Vector mDetailData; + Vector mFreeDetailData; + + void begin(); + void end(); + + void selectPotential(TSShapeInstance * si, F32 dist, F32 invScale, const DetailProfile * dp = &smDefaultProfile); + bool selectCurrent(TSShapeInstance * si); + + void selectPotential(TSPartInstance * pi, F32 dist, F32 invScale, const DetailProfile * dp = &smDefaultProfile); + bool selectCurrent(TSPartInstance * pi); + + void bumpOne(DetailData*, S32 bump); + void bumpAll(S32 bump); + + void computePriority(DetailData *, S32 bump); + DetailData * getNewDetailData(); + +public: + + DetailManager(); + ~DetailManager(); + + static F32 smDetailScale; // user can adjust this + + static S32 smMaxPolyLimit; // app sets this once -- or uses default value + static S32 smMinPolyLimit; // app sets this once -- or uses default value + static S32 smLimitRange; // app sets this once -- or uses default value + + static S32 smPolysTriedToRender; // not used here, but can be read for misc. uses + static S32 smPolysDidRender; // not used here, but can be read for misc. uses + + static void init() { AssertFatal(!smDetailManager,"DetailManger::init"); smDetailManager = new DetailManager; } + static void shutdown() { delete smDetailManager; smDetailManager = NULL; } + + static void beginPrepRender() { get()->begin(); } + static void endPrepRender() { get()->end(); } + + static void selectPotentialDetails(TSShapeInstance * si, F32 dist, F32 invScale) { get()->selectPotential(si,dist,invScale); } + static bool selectCurrentDetail(TSShapeInstance * si) { return get()->selectCurrent(si); } + + static void selectPotentialDetails(TSPartInstance * pi, F32 dist, F32 invScale) { get()->selectPotential(pi,dist,invScale); } + static bool selectCurrentDetail(TSPartInstance * pi) { return get()->selectCurrent(pi); } +}; + +#endif diff --git a/sceneGraph/lightManager.cc b/sceneGraph/lightManager.cc new file mode 100644 index 0000000..85c4ac2 --- /dev/null +++ b/sceneGraph/lightManager.cc @@ -0,0 +1,412 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "scenegraph/lightManager.h" +#include "dgl/dgl.h" +#include "console/console.h" +#include "console/simBase.h" +#include "scenegraph/sceneGraph.h" +#include "console/consoleTypes.h" + +namespace { + static GLfloat zeroColor[] = {0.f, 0.f, 0.f, 0.f}; + static GLfloat whiteColor[] = {1.f, 1.f, 1.f, 1.f}; + + // global settings for the lightmanager + static ColorF gOpenGLLightingAmbientColor(0.f, 0.f, 0.f, 0.f); + static ColorF gOpenGLMaterialAmbientColor(0.f, 0.f, 0.f, 0.f); + static ColorF gOpenGLMaterialDiffuseColor(1.f, 1.f, 1.f, 1.f); + static S32 gOpenGLMaxHardwareLights = LightManager::DefaultMaxLights; + + bool colorIsZero(const ColorF & col) + { + return((col.red == 0.f) && (col.green == 0.f) && (col.blue == 0.f)); + } +} + +//------------------------------------------------------------------------------ +// Class LightManager +//------------------------------------------------------------------------------ +static void cResetLighting(SimObject *, S32, const char **) +{ + if(!gClientSceneGraph) + { + Con::errorf(ConsoleLogEntry::General, "LightManager::cResetLighting: no client scenegraph!"); + return; + } + LightManager * lighting = gClientSceneGraph->getLightManager(); + if(!lighting) + { + Con::errorf(ConsoleLogEntry::General, "LightManager::cResetLighting: lightmanager not found!"); + return; + } + lighting->resetGL(); +} + +LightManager::LightManager() +{ + mGLInitialized = false; + mGLLightsInstalled = false; + mGLLightCount = 0; + mAmbientLightColor.set(0.f,0.f,0.f); + mVectorLightsAttenuation = 1.f; + mVectorLightsEnabled = true; + +#ifdef DEBUG + Con::addVariable("$pref::OpenGL::lightingAmbientColor", TypeColorF, &gOpenGLLightingAmbientColor); + Con::addVariable("$pref::OpenGL::materialAmbientColor", TypeColorF, &gOpenGLMaterialAmbientColor); + Con::addVariable("$pref::OpenGL::materialDiffuseColor", TypeColorF, &gOpenGLMaterialDiffuseColor); +#endif + Con::addVariable("$pref::OpenGL::maxHardwareLights", TypeS32, &gOpenGLMaxHardwareLights); + Con::addCommand("resetLighting", cResetLighting, "resetLighting();", 1, 1); +} + +void LightManager::setMaxGLLights(U32 max) +{ + // ask gl for max light value, and check if this is greater.. + GLint glMaxLights = 0; + glGetIntegerv(GL_MAX_LIGHTS, &glMaxLights); + + // clamp to the number gl returned + if(max > glMaxLights) + { + Con::errorf(ConsoleLogEntry::General, "LightManager::setMaxGLLights: too many lights."); + max = glMaxLights; + } + + mMaxGLLights = max; + + // cleanup any installed lights + if(mGLLightsInstalled) + uninstallGLLights(); +} + +// will crash if called prior to PlatformVideo:: initialization! +void LightManager::resetGL() +{ + if(!mGLInitialized) + return; + + if(mGLLightsInstalled) + uninstallGLLights(); + + // set ambient color + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, (const F32 *)gOpenGLMaterialAmbientColor); + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, (const F32 *)gOpenGLMaterialDiffuseColor); + + setMaxGLLights(gOpenGLMaxHardwareLights); +} + +void LightManager::registerLights(bool lightingScene) +{ + if(!mGLInitialized) + { + mGLInitialized = true; + resetGL(); + } + + if(mGLLightsInstalled) + { + Con::warnf(ConsoleLogEntry::General, "LightManager::registerLights: lights are already installed."); + uninstallGLLights(); + } + + // clear out the light list + mLights.clear(); + + // walk the lightlist and query objects for theirg lights + SimSet * lightSet = Sim::getLightSet(); + for(SimObject ** itr = lightSet->begin(); itr != lightSet->end(); itr++) + (*itr)->registerLights(this, lightingScene); +} + +void LightManager::addLight(LightInfo * light) +{ + mLights.push_back(light); +} + +void LightManager::removeLight(LightInfo * light) +{ + for(U32 i = 0; i < mLights.size(); i++) + { + if(mLights[i] == light) + { + mLights.erase_fast(i); + break; + } + } +} + +//-------------------------------------------------------------------------- +U32 LightManager::installGLLights(const SphereF & sphere) +{ + // score the lights: + for(U32 i = 0; i < mLights.size(); i++) + { + if(!mVectorLightsEnabled && (mLights[i]->mType == LightInfo::Vector)) + mLights[i]->mScore = 0; + else + scoreLight(mLights[i], sphere); + } + + return(installGLLights()); +} + +U32 LightManager::installGLLights(const Box3F & box) +{ + SphereF sphere; + box.getCenter(&sphere.center); + sphere.radius = Point3F(box.max - sphere.center).len(); + + return(installGLLights(sphere)); +} + +//-------------------------------------------------------------------------- +U32 LightManager::installGLLights() +{ + AssertFatal(!mGLLightsInstalled, "LightManager::installGLLights: lights are already installed!"); + mGLLightsInstalled = true; + + // sort them + dQsort(mLights.address(), mLights.size(), sizeof(LightInfo*), compareLights); + + // grab the best ones... + U32 count = 0; + while(count < mLights.size()) + { + if(count >= mMaxGLLights) + break; + + if(!mLights[count]->mScore) + break; + + installGLLight(mLights[count++]); + } + + // enable ambient and lighting always: + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (const F32 *)ColorF(gOpenGLLightingAmbientColor + mAmbientLightColor)); + glEnable(GL_LIGHTING); + + return(count); +} + +//-------------------------------------------------------------------------- +// - restores ambient light changes +// - restores vector light enabled flag +void LightManager::uninstallGLLights() +{ + if(!mGLLightsInstalled) + { + Con::errorf(ConsoleLogEntry::General, "LightManager::uninstallGLLights: no lights installed!"); + return; + } + mGLLightsInstalled = false; + + // restore values: + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (const F32*)gOpenGLLightingAmbientColor); + + mAmbientLightColor.set(0.f, 0.f, 0.f); + mVectorLightsAttenuation = 1.f; + mVectorLightsEnabled = true; + + glDisable(GL_LIGHTING); + for(U32 i = 0; i < mGLLightCount; i++) + glDisable(GL_LIGHT0 + i); + mGLLightCount = 0; +} + +void LightManager::installGLLight(LightInfo * lightInfo) +{ + AssertFatal(mGLLightCount < mMaxGLLights, "LightManager::installGLLight: too many lights installed"); + + GLfloat lightColor[] = {lightInfo->mColor.red, lightInfo->mColor.green, lightInfo->mColor.blue, 1.f}; + GLfloat ambientColor[] = {lightInfo->mAmbient.red, lightInfo->mAmbient.green, lightInfo->mAmbient.blue, 1.f}; + GLfloat lightPos[] = {lightInfo->mPos.x, lightInfo->mPos.y, lightInfo->mPos.z, 1.f}; + GLfloat lightDir[] = {-lightInfo->mDirection.x, -lightInfo->mDirection.y, -lightInfo->mDirection.z, 0.f}; + + U32 light = GL_LIGHT0 + mGLLightCount++; + + glEnable(light); + + switch(lightInfo->mType) + { + case LightInfo::Spot: + + // set the light params + glLightfv(light, GL_POSITION, (const GLfloat*)lightPos); + glLightfv(light, GL_SPOT_DIRECTION, (const GLfloat*)lightDir); + glLightfv(light, GL_DIFFUSE, (const GLfloat*)lightColor); + glLightfv(light, GL_AMBIENT, (const GLfloat*)zeroColor); + glLightfv(light, GL_SPECULAR, (const GLfloat*)zeroColor); + + break; + + // falls through to LightInfo::Ambient + case LightInfo::Vector: + { + F32 ambientFactor = (ambientColor[0] + ambientColor[1] + ambientColor[2]) / 3.f; + F32 factor = mClampF(mVectorLightsAttenuation - ambientFactor, 0.f, 1.f); + + // attenuate (for in shadow...) + for(U32 i = 0; i < 3; i++) + lightColor[i] *= factor; + } + + case LightInfo::Ambient: + + // set the light params + glLightfv(light, GL_POSITION, (const GLfloat*)lightDir); + glLightfv(light, GL_DIFFUSE, (const GLfloat*)lightColor); + glLightfv(light, GL_AMBIENT, (const GLfloat*)ambientColor); + glLightfv(light, GL_SPECULAR, (const GLfloat*)zeroColor); + + break; + + case LightInfo::Point: + + // set the light params + glLightfv(light, GL_POSITION, (const GLfloat*)lightPos); + glLightfv(light, GL_DIFFUSE, (const GLfloat*)lightColor); + glLightfv(light, GL_AMBIENT, (const GLfloat*)zeroColor); + glLightfv(light, GL_SPECULAR, (const GLfloat*)zeroColor); + + glLightf(light, GL_SPOT_CUTOFF, 180.f); + + glLightf(light, GL_CONSTANT_ATTENUATION, 0.f); + glLightf(light, GL_LINEAR_ATTENUATION, 1 / lightInfo->mRadius); + glLightf(light, GL_QUADRATIC_ATTENUATION, 0.f); + + break; + } +} + +//------------------------------------------------------------------------------ +void LightManager::getBestLights(const PlaneF * testPlanes, const U32 numPlanes, + const Point3F & pos, LightInfoList & lightList, const U32 max) +{ + for(U32 i = 0; i < mLights.size(); i++) + scoreLight(mLights[i], testPlanes, numPlanes, pos); + + fillLightList(lightList, max); +} + +void LightManager::getBestLights(const SphereF & sphere, LightInfoList & lightList, const U32 max) +{ + // score the lights + for(U32 i = 0; i < mLights.size(); i++) + scoreLight(mLights[i], sphere); + + fillLightList(lightList, max); +} + +void LightManager::getBestLights(const Box3F & box, LightInfoList & lightList, const U32 max) +{ + SphereF sphere; + box.getCenter(&sphere.center); + sphere.radius = Point3F(box.max - sphere.center).len(); + + getBestLights(sphere, lightList, max); +} + +//------------------------------------------------------------------------------ +void LightManager::fillLightList(LightInfoList & lightList, const U32 max) +{ + dQsort(mLights.address(), mLights.size(), sizeof(LightInfo*), compareLights); + + // grab the best ones... + U32 count = 0; + for(U32 i = 0; i < mLights.size(); i++) + { + if(!mLights[i]->mScore) + continue; + + if(count++ >= max) + break; + + lightList.push_back(mLights[i]); + } +} + +//------------------------------------------------------------------------------ +// 0 not lit +// 1->100 point/spot lights +// 100->200 vector/ambient lights +void LightManager::scoreLight(LightInfo * lightInfo, const SphereF & sphere) const +{ + switch(lightInfo->mType) + { + case LightInfo::Ambient: + case LightInfo::Vector: + { + // 100 + (color_intensity * 100) + float intensity = (lightInfo->mColor.red + lightInfo->mAmbient.red) * 0.346f + + (lightInfo->mColor.green + lightInfo->mAmbient.green) * 0.588f + + (lightInfo->mColor.blue + lightInfo->mAmbient.blue) * 0.070f; + + lightInfo->mScore = 100 + S32(100.f * mClampF(intensity, 0.f, 1.f)); + break; + } + + case LightInfo::Point: + { + Point3F diff = lightInfo->mPos - sphere.center; + F32 lenSq = diff.lenSquared(); + F32 sumRadiusSq = (sphere.radius + lightInfo->mRadius) * (sphere.radius + lightInfo->mRadius); + + if(lenSq > sumRadiusSq) + { + lightInfo->mScore = 0; + break; + } + + F32 radSq = sphere.radius * sphere.radius; + lightInfo->mScore = 1 + S32(99.f * (getMin((sumRadiusSq - lenSq), radSq) / radSq)); + break; + } + + default: + lightInfo->mScore = 1; + break; + } +} + +void LightManager::scoreLight(LightInfo* lightInfo, + const PlaneF* /*testPlanes*/, + const U32 /*numPlanes*/, + const Point3F& /*pos*/) const +{ + switch(lightInfo->mType) + { + case LightInfo::Ambient: + case LightInfo::Vector: + { + // 100 + (color_intensity * 100) + float intensity = (lightInfo->mColor.red + lightInfo->mAmbient.red) * 0.346f + + (lightInfo->mColor.green + lightInfo->mAmbient.green) * 0.588f + + (lightInfo->mColor.blue + lightInfo->mAmbient.blue) * 0.070f; + + lightInfo->mScore = 100 + S32(100.f * mClampF(intensity, 0.f, 1.f)); + break; + } + + case LightInfo::Point: + { + lightInfo->mScore = 1; + break; + } + + default: + lightInfo->mScore = 1; + break; + } +} + +//------------------------------------------------------------------------------ +S32 QSORT_CALLBACK LightManager::compareLights(const void* a, const void* b) +{ + return((*(LightInfo**)b)->mScore - (*(LightInfo**)a)->mScore); +} diff --git a/sceneGraph/lightManager.h b/sceneGraph/lightManager.h new file mode 100644 index 0000000..af991db --- /dev/null +++ b/sceneGraph/lightManager.h @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTMANAGER_H_ +#define _LIGHTMANAGER_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif + +class LightInfo +{ + friend class LightManager; + + public: + enum Type { + Point = 0, + Spot = 1, + Vector = 2, + Ambient = 3 + }; + Type mType; + + Point3F mPos; + VectorF mDirection; + ColorF mColor; + ColorF mAmbient; + F32 mRadius; + + private: + S32 mScore; +}; +typedef Vector LightInfoList; + +//------------------------------------------------------------------------------ +class SimObject; +class SceneObject; +static void cResetLighting(SimObject *, S32, const char **); + +class LightManager +{ + friend void cResetLighting(SimObject *, S32, const char **); + + public: + + enum { + DefaultMaxLights = 8 + }; + + LightManager(); + + void setMaxGLLights(U32); + void registerLights(bool); + void addLight(LightInfo *); + void removeLight(LightInfo *); + + U32 installGLLights(const SphereF &); + U32 installGLLights(const Box3F &); + void uninstallGLLights(); + + S32 getNumLights() const { return mLights.size(); } + void getLights(LightInfoList& lightList) { lightList = mLights; } + void getLights(LightInfo** lightArray) + { + for (U32 i = 0; i < mLights.size(); i++) + lightArray[i] = mLights[i]; + } + + void getBestLights(const PlaneF *, const U32, const Point3F &, LightInfoList &, const U32 max = 0xffffffff); + void getBestLights(const SphereF &, LightInfoList&, const U32 max = 0xffffffff); + void getBestLights(const Box3F &, LightInfoList&, const U32 max = 0xffffffff); + + void setVectorLightsEnabled(bool enable) {mVectorLightsEnabled = enable;} + bool getVectorLightsEnabled() {return(mVectorLightsEnabled);} + + void setVectorLightsAttenuation(F32 factor) {mVectorLightsAttenuation = factor;} + F32 getVectorLightsAttenuation() {return(mVectorLightsAttenuation);} + + void setAmbientColor(const ColorF & col) {mAmbientLightColor = col;} + const ColorF & getAmbientColor() {return(mAmbientLightColor);} + + private: + + void resetGL(); + U32 installGLLights(); + void installGLLight(LightInfo *); + + void fillLightList(LightInfoList&, U32); + void scoreLight(LightInfo *, const SphereF &) const; + void scoreLight(LightInfo *, const PlaneF *, const U32, const Point3F &) const; + static S32 QSORT_CALLBACK compareLights(const void *, const void *); + + LightInfoList mLights; + U32 mMaxGLLights; + U32 mGLLightCount; + bool mGLLightsInstalled; + bool mGLInitialized; + bool mVectorLightsEnabled; + F32 mVectorLightsAttenuation; + ColorF mAmbientLightColor; +}; + +#endif diff --git a/sceneGraph/sceneGraph.cc b/sceneGraph/sceneGraph.cc new file mode 100644 index 0000000..22bbc66 --- /dev/null +++ b/sceneGraph/sceneGraph.cc @@ -0,0 +1,1042 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/sceneGraph.h" +#include "Sim/sceneObject.h" +#include "sceneGraph/sceneRoot.h" +#include "sceneGraph/sceneState.h" +#include "dgl/dgl.h" +#include "Sim/netConnection.h" +#include "dgl/gBitmap.h" +#include "terrain/Sky.h" +#include "terrain/terrData.h" +#include "terrain/terrRender.h" +#include "terrain/waterBlock.h" +#include "Sim/decalManager.h" +#include "sceneGraph/detailManager.h" +#include "ts/tsShapeInstance.h" +#include "Core/fileStream.h" +#include "Platform/profiler.h" + +const U32 SceneGraph::csmMaxTraversalDepth = 4; +U32 SceneGraph::smStateKey = 0; +SceneGraph* gClientSceneGraph = NULL; +SceneGraph* gServerSceneGraph = NULL; +const U32 SceneGraph::csmRefPoolBlockSize = 4096; +F32 SceneGraph::smVisibleDistanceMod = 1.0; + +F32 SceneGraph::mHazeArray[FogTextureDistSize]; +U32 SceneGraph::mHazeArrayi[FogTextureDistSize]; +F32 SceneGraph::mDistArray[FogTextureDistSize]; + +//------------------------------------------------------------------------------ +//-------------------------------------- IMPLEMENTATION +SceneGraph::SceneGraph(bool isClient) +{ + VECTOR_SET_ASSOCIATION(mRefPoolBlocks); + VECTOR_SET_ASSOCIATION(mZoneManagers); + VECTOR_SET_ASSOCIATION(mZoneLists); + + mHazeArrayDirty = true; + mCurrZoneEnd = 0; + mNumActiveZones = 0; + + mIsClient = isClient; + mNumFogVolumes = 0; + mFogDistance = 250; + mVisibleDistance = 500; + mFogColor.set(128, 128, 128); + + mCurrSky = NULL; + mCurrTerrain = NULL; + mFreeRefPool = NULL; + addRefPoolBlock(); + + mCurrDecalManager = NULL; +} + +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; +} + + +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]); +} + + +bool SceneGraph::useSpecial = false; + +void SceneGraph::renderScene(const U32 objectMask) +{ + PROFILE_START(SceneGraphRender); + if (smVisibleDistanceMod > 1.0f) + smVisibleDistanceMod = 1.0f; + else if (smVisibleDistanceMod < 0.5f) + smVisibleDistanceMod = 0.5f; + + static bool skipFirstFog = TSShapeInstance::smSkipFirstFog; + + if (skipFirstFog) + // HACK: This is the dumbest hack for 3Dfx yet: + // don't two-pass fog for the first frame + if (TSShapeInstance::smSkipFog) + { + TSShapeInstance::smSkipFog = false; + skipFirstFog = false; + } + else + TSShapeInstance::smSkipFog = true; + + // Determine the camera position, and store off render state... + MatrixF modelview; + MatrixF mv; + Point3F cp; + + dglClearPrimMetrics(); + dglSetRenderPrimType(0); + + bool multitex = dglDoesSupportARBMultitexture(); + + dglGetModelview(&modelview); + mv = modelview; + mv.inverse(); + mv.getColumn(3, &cp); + setBaseCameraPosition(cp); + + static bool prevEnvColor; + static bool prevMultiTex; + + // Set up the base SceneState. + F64 left, right, top, bottom, nearPlane, farPlane; + RectI viewport; + + dglGetFrustum(&left, &right, &bottom, &top, &nearPlane, &farPlane); + dglGetViewport(&viewport); + + TextureHandle envMap; + if (getCurrentSky() != NULL) + envMap = getCurrentSky()->getEnvironmentMap(); + SceneState* pBaseState = new SceneState(NULL, + mCurrZoneEnd, + left, right, + bottom, top, + nearPlane, + F64(getVisibleDistanceMod()), + viewport, + cp, + modelview, + getFogDistanceMod(), + getVisibleDistanceMod(), + mFogColor, + mNumFogVolumes, + mFogVolumes, + envMap, + smVisibleDistanceMod); + // build the fog texture + PROFILE_START(BuildFogTexture); + if(!useSpecial) + buildFogTexture( pBaseState ); + else + buildFogTextureSpecial( pBaseState ); + PROFILE_END(); + + // Find the start zone... + SceneObject* startObject; + U32 startZone; + findZone(cp, startObject, startZone); + PROFILE_START(BuildSceneTree); + + DetailManager::beginPrepRender(); + + buildSceneTree(pBaseState, startObject, startZone, 1, objectMask); + + DetailManager::endPrepRender(); + PROFILE_END(); + + // grab the lights... + PROFILE_START(RegisterLights); + mLightManager.registerLights(false); + PROFILE_END(); + + PROFILE_START(TraverseScene); + traverseSceneTree(pBaseState); + PROFILE_END(); + + delete pBaseState; + PROFILE_END(); +} + +void SceneGraph::buildFogTexture(SceneState *pState) +{ + const Point3F &cp = pState->getCameraPosition(); + + if (!bool(mFogTexture)) + { + mFogTexture = TextureHandle(NULL,new GBitmap(64, 64, false, GBitmap::RGBA), true); + mFogTextureIntensity = mFogTexture; + } + + // build the fog texture + TerrainBlock *block = getCurrentTerrain(); + if(block) + { + GridSquare *sq = block->findSquare(TerrainBlock::BlockShift, Point2I(0,0)); + F32 heightRange = fixedToFloat(sq->maxHeight - sq->minHeight); + mHeightOffset = fixedToFloat(sq->minHeight); + + mInvHeightRange = 1 / heightRange; + mInvVisibleDistance = 1 / getVisibleDistanceMod(); + + GBitmap *fogBitmap = mFogTexture.getBitmap(); + GBitmap *fogBitmapInten = mFogTextureIntensity.getBitmap(); + + F32 heightStep = heightRange / F32(fogBitmap->getHeight()); + + // inset distance half a texel (to the texel center) + F32 distStart = getVisibleDistanceMod() - (getVisibleDistanceMod() / (fogBitmap->getWidth() * 2)); + ColorI fogColor(mFogColor); + + if(mHazeArrayDirty) + { + F32 distStep = - getVisibleDistanceMod() / F32(fogBitmap->getWidth()); + F32 dist = distStart; + for(U32 i = 0; i < FogTextureDistSize; i++) + { + mHazeArray[i] = pState->getHaze(dist); + F32 prevDist = dist; + dist += distStep; + mDistArray[i] = dist / prevDist; + } + mHazeArrayDirty = false; + } + + F32 ht = mHeightOffset + (heightStep / 2) - cp.z; + U32 fc = *((U32 *) &fogColor) & 0x00FFFFFF; + for(U32 j = 0; j < fogBitmap->getHeight(); j++) + { + F32 dist = distStart; + U32 *ptr = (U32 *) fogBitmap->getAddress(0, j); + + // fog texture goes from dist = visibleDistance at u = 0 to dist = 0 at u = 1 + // makes the math on the texture computation cleaner + F32 fogStart = pState->getFog(dist, ht); + + for(U32 i = 0; i < fogBitmap->getWidth(); i++) + { + U32 fog = (fogStart + mHazeArray[i]) * 255; + fogStart *= mDistArray[i]; + if(fog > 255) + fog = 255; + *ptr++ = fc | (fog << 24); + } + ht += heightStep; + } + mFogTexture.refresh(); + } +} + +// most of this is work in progress. +static bool takeShot = true; + +static void fogTextureShot(TextureHandle fogTexture) +{ + GBitmap *fogBitmap = fogTexture.getBitmap(); + + FileStream fStream; + if(fStream.open("fogTextureTest.png", FileStream::Write) != NULL) + { + fogBitmap->writePNG(fStream); + } + takeShot = false; +} + +void SceneGraph::buildFogTextureSpecial(SceneState *pState) +{ + const Point3F &cp = pState->getCameraPosition(); + + if (!bool(mFogTexture)) + { + mFogTexture = TextureHandle(NULL,new GBitmap(64, 64, false, GBitmap::RGBA), true); + mFogTextureIntensity = mFogTexture; + } + + // build the fog texture + TerrainBlock *block = getCurrentTerrain(); + if(block) + { + GridSquare *sq = block->findSquare(TerrainBlock::BlockShift, Point2I(0,0)); + F32 heightRange = fixedToFloat(sq->maxHeight - sq->minHeight); + mHeightOffset = fixedToFloat(sq->minHeight); + + mInvHeightRange = 1 / heightRange; + mInvVisibleDistance = 1 / getVisibleDistanceMod(); + + GBitmap *fogBitmap = mFogTexture.getBitmap(); + GBitmap *fogBitmapInten = mFogTextureIntensity.getBitmap(); + + F32 heightStep = heightRange / F32(fogBitmap->getHeight()); + + // inset distance half a texel (to the texel center) + F32 distStart = getVisibleDistance() - (getVisibleDistanceMod() / (fogBitmap->getWidth() * 2) ); + + ColorI fogColor(mFogColor); + ColorF ffogColor = mFogColor; + ColorF array[3]; + ffogColor.red *= 255; + ffogColor.green *= 255; + ffogColor.blue *= 255; + U32 numFogs = mNumFogVolumes; + + F32 distStep = - getVisibleDistanceMod() / F32(fogBitmap->getWidth()); + if(mHazeArrayDirty) + { + F32 dist = distStart; + for(U32 i = 0; i < FogTextureDistSize; i++) + { + mHazeArray[i] = pState->getHaze(dist); + mHazeArrayi[i] = mHazeArray[i] * 255; + + F32 prevDist = dist; + dist += distStep; + mDistArray[i] = dist / prevDist; + } + + mHazeArrayDirty = false; + } + + F32 ht = mHeightOffset + (heightStep / 2) - cp.z; + U32 fc = *((U32 *) &fogColor) & 0x00FFFFFF; + for(U32 j = 0; j < fogBitmap->getHeight(); j++) + { + F32 dist = distStart; + U32 *ptr = (U32 *) fogBitmap->getAddress(0, j); + + // fog texture goes from dist = visibleDistance at u = 0 to dist = 0 at u = 1 + // makes the math on the texture computation cleaner + pState->getFogs(dist, ht, array, numFogs); + if(numFogs == 0) + { + for(U32 i = 0; i < fogBitmap->getWidth(); i++) + *ptr++ = fc | (mHazeArrayi[i] << 24); + } + else if(numFogs == 1) + { + for(U32 i = 0; i < fogBitmap->getWidth(); i++) + { + F32 bandPct = array[0].alpha; + F32 hazePct = mHazeArray[i]; + if(bandPct > 1) + bandPct = 1; + if(bandPct + hazePct > 1) + hazePct = 1 - bandPct; + ColorI c(hazePct * ffogColor.red + bandPct * (array[0].red * 255), + hazePct * ffogColor.green + bandPct * (array[0].green * 255), + hazePct * ffogColor.blue + bandPct * (array[0].blue * 255), + (hazePct + bandPct) * 255); + *ptr++ = *((U32 *) &c); + array[0].alpha *= mDistArray[i]; + } + } + else if(numFogs == 2) + { + for(U32 i = 0; i < fogBitmap->getWidth(); i++) + { + F32 hazePct = mHazeArray[i]; + F32 bandPct0 = array[0].alpha; + F32 bandPct1 = array[1].alpha; + + if(bandPct0 > 1) + bandPct0 = 1; + + if(bandPct0 + bandPct1 > 1) + bandPct1 = 1 - bandPct0; + + if(bandPct1 + bandPct0 + hazePct > 1) + hazePct = 1 - bandPct1 - bandPct0; + + ColorI c(hazePct * ffogColor.red + bandPct0 * array[0].red + bandPct1 * array[1].red, + hazePct * ffogColor.green + bandPct0 * array[0].green + bandPct1 * array[1].green, + hazePct * ffogColor.blue + bandPct0 * array[0].blue + bandPct1 * array[1].blue, + (hazePct + bandPct0 + bandPct1) * 255); + *ptr++ = *((U32 *) &c); + array[0].alpha *= mDistArray[i]; + } + } + + ht += heightStep; + } + mFogTexture.refresh(); + } + + if(takeShot) + fogTextureShot(mFogTexture); +} + +//-------------------------------------------------------------------------- +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); + + if (pState->mFlipCull) + glCullFace(GL_FRONT); + + // Render the objects in this subsidiary... + pState->renderCurrentImages(); + + if (pState->mFlipCull) + glCullFace(GL_BACK); + + // close the portal + pPortalOwner->closePortal(portalIndex, pState, pState->mParent); + } else { + pState->renderCurrentImages(); + } +} + + +//---------------------------------------------------------------------------- +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 one 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 traveral 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); + } + if (obj->getType() & EnvironmentObjectType) { + if (dynamic_cast(obj) != NULL) { + mCurrSky = static_cast(obj); + } + } + if (obj->getType() & DecalManagerObjectType) { + if (dynamic_cast(obj) != NULL) { + mCurrDecalManager = static_cast(obj); + } + } + if (obj->getType() & WaterObjectType) + { + addToWaterList(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; + } + if (obj->getType() & EnvironmentObjectType) { + if (dynamic_cast(obj) != NULL && mCurrSky == static_cast(obj)) + mCurrSky = NULL; + } + if (obj->getType() & DecalManagerObjectType) { + if (dynamic_cast(obj) != NULL && mCurrDecalManager == static_cast(obj)) + mCurrDecalManager = NULL; + } + if (obj->getType() & WaterObjectType) + { + removeFromWaterList(obj); + } + + 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 + Container* pQueryContainer = mIsClient ? &gClientContainer : + &gServerContainer; + // query + SimpleQueryList list; + pQueryContainer->findObjects(obj->mWorldBox, 0xFFFFFFFF, SimpleQueryList::insertionCallback, S32(&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)) + { + Container* pQueryContainer = mIsClient ? &gClientContainer : &gServerContainer; + SimpleQueryList list; + pQueryContainer->findObjects(obj->mWorldBox, 0xFFFFFFFF, SimpleQueryList::insertionCallback, S32(&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; + + while (true) { + bool cont = false; + + // Loop, but don't consider the root... + for (U32 i = 1; i < mZoneManagers.size(); i++) { + 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 = mZoneManagers[i].obj; + break; + } + } + } + + // Have we gone as far as we can? + if (cont == false) + break; + } + + zone = currZone; + owner = currOwner; +} + + +//------------------------------------------------------------------------------ +void SceneGraph::rezoneObject(SceneObject* obj) +{ + AssertFatal(obj->mSceneManager != NULL && obj->mSceneManager == this, "Error, bad or no scenemanager here!"); + + 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) + // + 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; + } +} + +void SceneGraph::zoneInsert(SceneObject* obj) +{ + 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 + Container* pQueryContainer = mIsClient ? &gClientContainer : + &gServerContainer; + // query + SimpleQueryList list; + pQueryContainer->findObjects(obj->mWorldBox, 0xFFFFFFFF, SimpleQueryList::insertionCallback, S32(&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++) { + rezoneObject(list.mList[i]); + } + } +} + + +//------------------------------------------------------------------------------ +void SceneGraph::zoneRemove(SceneObject* obj) +{ + 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; +} + +void SceneGraph::setFogColor(ColorF color) +{ + mFogColor = color; +} + +void SceneGraph::setVisibleDistance(F32 dist) +{ + mHazeArrayDirty = true; + mVisibleDistance = dist; +} + +void SceneGraph::setFogDistance(F32 dist) +{ + mHazeArrayDirty = true; + mFogDistance = dist; +} + +void SceneGraph::setFogVolumes(U32 numFogVolumes, FogVolume *fogVolumes) +{ + mNumFogVolumes = getMin(numFogVolumes, U32(MaxFogVolumes)); + for(U32 i = 0; i < mNumFogVolumes; i++) + mFogVolumes[i] = fogVolumes[i]; +} + +void SceneGraph::getWaterObjectList(SimpleQueryList& sql) +{ + sql.mList.setSize(mWaterList.size()); + for (S32 i = 0; i < mWaterList.size(); i++) + sql.mList[i] = mWaterList[i]; +} + +void SceneGraph::addToWaterList(SceneObject* obj) +{ +#ifdef DEBUG + for (S32 i = 0; i < mWaterList.size(); i++) + { + AssertFatal(mWaterList[i] != obj, "Error, object already on water list!"); + } +#endif + + mWaterList.push_back(obj); +} + +void SceneGraph::removeFromWaterList(SceneObject* obj) +{ +#ifdef DEBUG + bool found = false; + for (S32 i = 0; i < mWaterList.size(); i++) + { + if (obj == mWaterList[i]) + found = true; + } + AssertFatal(found, "Error, object not on water list!"); +#endif + + for (S32 i = 0; i < mWaterList.size(); i++) + { + if (mWaterList[i] == obj) + { + mWaterList.erase(i); + return; + } + } +} + diff --git a/sceneGraph/sceneGraph.h b/sceneGraph/sceneGraph.h new file mode 100644 index 0000000..d6dc59b --- /dev/null +++ b/sceneGraph/sceneGraph.h @@ -0,0 +1,318 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENEGRAPH_H_ +#define _SCENEGRAPH_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _POLYLIST_H_ +#include "Core/polyList.h" +#endif +#ifndef _MRECT_H_ +#include "Math/mRect.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "sceneGraph/lightManager.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "Sim/sceneObject.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _GBITMAP_H_ +#include "dgl/gBitmap.h" +#endif + +class SceneState; +class NetConnection; + +class Sky; +class TerrainBlock; +class DecalManager; + +struct FogVolume +{ + float visibleDistance; + float minHeight; + float maxHeight; + float percentage; + ColorF color; +}; + +enum +{ + MaxFogVolumes = 3, + FogTextureDistSize = 64, + FogTextureHeightSize = 64 +}; + +//-------------------------------------------------------------------------- +//-------------------------------------- SceneGraph +// +class SceneGraph +{ + // Public interface... + public: + SceneGraph(bool isClient); + ~SceneGraph(); + + // SceneObject Management + public: + bool addObjectToScene(SceneObject*); + void removeObjectFromScene(SceneObject*); + void zoneInsert(SceneObject*); + void zoneRemove(SceneObject*); + + // Zone management. + public: + void registerZones(SceneObject*, U32 numZones); + void unregisterZones(SceneObject*); + SceneObject* getZoneOwner(const U32 zone); + + // Rendering and scope management + public: + void renderScene(const U32 objectMask = 0xffffffff); + void scopeScene(const Point3F& scopePosition, + const F32 scopeDistance, + NetConnection* netConnection); + + // For objects, valid only during the rendering cycle + public: + const Point3F& getBaseCameraPosition() const; + const Point3F& getCurrCameraPosition() const; + + ColorF getFogColor(); + F32 getFogDistance(); + F32 getVisibleDistance(); + F32 getFogDistanceMod(); + F32 getVisibleDistanceMod(); + + void setFogDistance(F32 dist); + void setVisibleDistance(F32 dist); + void setFogColor(ColorF fogColor); + void setFogVolumes(U32 numFogVolumes, FogVolume *fogVolumes); + void getFogVolumes(U32 &numFogVolumes, FogVolume * &fogVolumes); + void buildFogTexture( SceneState *pState ); + void buildFogTextureSpecial( SceneState *pState ); + + void getFogCoordPair(F32 dist, F32 z, F32 &x, F32 &y); + F32 getFogCoord(F32 dist, F32 z); + + private: + void setBaseCameraPosition(const Point3F&); + void setCurrCameraPosition(const Point3F&); + + //-------------------------------------- Private interface and data + protected: + TextureHandle mFogTexture; + TextureHandle mFogTextureIntensity; + static const U32 csmMaxTraversalDepth; + static U32 smStateKey; + + public: + static F32 smVisibleDistanceMod; + + + public: + static bool useSpecial; + + protected: + bool mIsClient; + + bool mHazeArrayDirty; + static F32 mHazeArray[FogTextureDistSize]; + static U32 mHazeArrayi[FogTextureDistSize]; + static F32 mDistArray[FogTextureDistSize]; + + F32 mInvVisibleDistance; + F32 mHeightRangeBase; + F32 mHeightOffset; + F32 mInvHeightRange; + + U32 mCurrZoneEnd; + U32 mNumActiveZones; + + Point3F mBaseCameraPosition; + Point3F mCurrCameraPosition; + + U32 mNumFogVolumes; + FogVolume mFogVolumes[MaxFogVolumes]; + F32 mFogDistance; + F32 mVisibleDistance; + ColorF mFogColor; + + LightManager mLightManager; + + Sky* mCurrSky; + TerrainBlock* mCurrTerrain; + DecalManager* mCurrDecalManager; + + Vector mWaterList; + + void addRefPoolBlock(); + SceneObjectRef* allocateObjectRef(); + void freeObjectRef(SceneObjectRef*); + SceneObjectRef* mFreeRefPool; + Vector mRefPoolBlocks; + static const U32 csmRefPoolBlockSize; + + public: + LightManager * getLightManager(); + + Sky* getCurrentSky() { return mCurrSky; } + TerrainBlock* getCurrentTerrain() { return mCurrTerrain; } + DecalManager* getCurrentDecalManager() { return mCurrDecalManager; } + void getWaterObjectList(SimpleQueryList&); + + TextureHandle getFogTexture() { return mFogTexture; } + TextureHandle getFogTextureIntensity() { return mFogTextureIntensity; } + // Object database for zone managers + protected: + struct ZoneManager { + SceneObject* obj; + U32 zoneRangeStart; + U32 numZones; + }; + Vector mZoneManagers; + + // 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: + void buildSceneTree(SceneState*, SceneObject*, const U32, const U32, const U32); + void traverseSceneTree(SceneState* pState); + void treeTraverseVisit(SceneObject*, SceneState*, const U32); + + void compactZonesCheck(); + bool alreadyManagingZones(SceneObject*) const; + void findZone(const Point3F&, SceneObject*&, U32&); + void rezoneObject(SceneObject*); + + void addToWaterList(SceneObject* obj); + void removeFromWaterList(SceneObject* obj); +}; + +extern SceneGraph* gClientSceneGraph; +extern SceneGraph* gServerSceneGraph; + +inline LightManager * SceneGraph::getLightManager() +{ + return(&mLightManager); +} + +inline const Point3F& SceneGraph::getBaseCameraPosition() const +{ + return mBaseCameraPosition; +} + +inline const Point3F& SceneGraph::getCurrCameraPosition() const +{ + return mCurrCameraPosition; +} + +inline void SceneGraph::setBaseCameraPosition(const Point3F& p) +{ + mBaseCameraPosition = p; +} + +inline void SceneGraph::getFogCoordPair(F32 dist, F32 z, F32 &x, F32 &y) +{ + x = (getVisibleDistanceMod() - dist) * mInvVisibleDistance; + y = (z - mHeightOffset) * mInvHeightRange; +} + +inline F32 SceneGraph::getFogCoord(F32 dist, F32 z) +{ + GBitmap* pBitmap = mFogTexture.getBitmap(); + AssertFatal(pBitmap->getFormat() == GBitmap::RGBA, "Error, wrong format for this query"); + AssertFatal(pBitmap->getWidth() == 64 && pBitmap->getHeight() == 64, "Error, fog texture wrong dimensions"); + + S32 x = S32(((getVisibleDistanceMod() - dist) * mInvVisibleDistance) * 63.0f); + S32 y = S32(((z - mHeightOffset) * mInvHeightRange) * 63.0f); + U32 samplex = mClamp(x, 0, 63); + U32 sampley = mClamp(y, 0, 63); + + return F32(pBitmap->pBits[(((sampley * 64) + samplex) * 4) + 3]) / 255.0f; +} + +inline ColorF SceneGraph::getFogColor() +{ + return(mFogColor); +} + +inline F32 SceneGraph::getFogDistance() +{ + return(mFogDistance); +} + +inline F32 SceneGraph::getFogDistanceMod() +{ + return(mFogDistance * smVisibleDistanceMod); +} + +inline F32 SceneGraph::getVisibleDistance() +{ + return(mVisibleDistance); +} + +inline F32 SceneGraph::getVisibleDistanceMod() +{ + return(mVisibleDistance * smVisibleDistanceMod); +} + +inline void SceneGraph::setCurrCameraPosition(const Point3F& p) +{ + mCurrCameraPosition = p; +} + +inline void SceneGraph::getFogVolumes(U32 &numFogVolumes, FogVolume * &fogVolumes) +{ + numFogVolumes = mNumFogVolumes; + fogVolumes = mFogVolumes; +} + +//-------------------------------------------------------------------------- +inline SceneObjectRef* SceneGraph::allocateObjectRef() +{ + if (mFreeRefPool == NULL) { + addRefPoolBlock(); + } + AssertFatal(mFreeRefPool, "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/sceneLighting.cc b/sceneGraph/sceneLighting.cc new file mode 100644 index 0000000..f1c6d9f --- /dev/null +++ b/sceneGraph/sceneLighting.cc @@ -0,0 +1,2904 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "scenegraph/sceneLighting.h" +#include "interior/interiorInstance.h" +#include "interior/interiorRes.h" +#include "interior/interior.h" +#include "dgl/gBitmap.h" +#include "math/mPlane.h" +#include "scenegraph/sceneGraph.h" +#include "core/fileStream.h" +#include "console/consoleTypes.h" +#include "gui/guiCanvas.h" +#include "core/zipSubStream.h" +#include "game/gameConnection.h" + +namespace { + + static void findObjectsCallback(SceneObject* obj, S32 val) + { + Vector * list = (Vector*)val; + list->push_back(obj); + } + + static U16 convertColor(ColorF color) + { + if(color.red > 1) + color.red = 1; + if(color.green > 1) + color.green = 1; + if(color.blue > 1) + color.blue = 1; + + return (U32(color.red * 31) << 11) | + (U32(color.green * 31) << 6) | + (U32(color.blue * 31) << 1) | 1; + } + + 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 U32 TerrainSquareIndices[][3] = { + {2, 1, 0}, // 45 + {3, 2, 0}, + {3, 1, 0}, // 135 + {3, 2, 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) + }; + + SceneLighting * gLighting = 0; + F32 gPlaneNormThresh = 0.999; + F32 gPlaneDistThresh = 0.001; + F32 gParellelVectorThresh = 0.01; + bool gTerminateLighting = false; + F32 gLightingProgress = 0.f; + const char * gCompleteCallback = 0; + U32 gConnectionMissionCRC = 0xffffffff; +} + + +//------------------------------------------------------------------------------ +class SceneLightingProcessEvent : public SimEvent +{ + private: + U32 mLightIndex; + S32 mObjectIndex; + + public: + SceneLightingProcessEvent(U32 lightIndex, S32 objectIndex) + { + mLightIndex = lightIndex; // size(): end of lighting + mObjectIndex = objectIndex; // -1: preLight, size(): next light + } + + void process(SimObject * obj) + { + AssertFatal(obj, "SceneLightingProcessEvent:: null event object!"); + if(obj) + static_cast(obj)->processEvent(mLightIndex, mObjectIndex); + }; +}; + +//------------------------------------------------------------------------------ +bool SceneLighting::smUseVertexLighting = false; + +SceneLighting::SceneLighting() +{ + mStartTime = 0; + mFileName[0] = 0; + smUseVertexLighting = Interior::smUseVertexLighting; + + static bool initialized = false; + if(!initialized) + { + Con::addVariable("SceneLighting::terminateLighting", TypeBool, &gTerminateLighting); + Con::addVariable("SceneLighting::lightingProgress", TypeF32, &gLightingProgress); + initialized = true; + } +} + +SceneLighting::~SceneLighting() +{ + gLighting = 0; + gLightingProgress = 0.f; + + ObjectProxy ** proxyItr; + for(proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) + delete *proxyItr; +} + +void SceneLighting::completed(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(); + } + } + + if(gCompleteCallback && gCompleteCallback[0]) + Con::executef(1, gCompleteCallback); +} + +//------------------------------------------------------------------------------ +// Static access method: there can be only one SceneLighting object +bool SceneLighting::lightScene(const char * callback, BitSet32 flags) +{ + if(gLighting) + { + Con::errorf(ConsoleLogEntry::General, "SceneLighting:: forcing restart of lighting!"); + gLighting->deleteObject(); + gLighting = 0; + } + + SceneLighting * lighting = new SceneLighting; + + // register the object + if(!lighting->registerObject()) + { + AssertFatal(0, "SceneLighting:: Unable to register SceneLighting object!"); + Con::errorf(ConsoleLogEntry::General, "SceneLighting:: Unable to register SceneLighting object!"); + delete lighting; + return(false); + } + + // could have interior resources but no instances (hey, got this far didnt we...) + GameConnection * con = dynamic_cast(NetConnection::getServerConnection()); + if(!con) + { + Con::errorf(ConsoleLogEntry::General, "SceneLighting:: no game connection"); + return(false); + } + con->addObject(lighting); + + // set the globals + gLighting = lighting; + gTerminateLighting = false; + gLightingProgress = 0.f; + gCompleteCallback = callback; + gConnectionMissionCRC = con->getMissionCRC(); + + // dont want to delete the current textures because bitmaps may already be loaded + gInteriorLMManager.purgeGLTextures(); + + if(!lighting->light(flags)) + { + lighting->completed(true); + lighting->deleteObject(); + return(false); + } + return(true); +} + +bool SceneLighting::isLighting() +{ + return(bool(gLighting)); +} + +//------------------------------------------------------------------------------ +// Class SceneLighting::PersistInfo +//------------------------------------------------------------------------------ +U32 SceneLighting::PersistInfo::smFileVersion = 0x0f; + +SceneLighting::PersistInfo::~PersistInfo() +{ + for(U32 i = 0; i < mChunks.size(); i++) + delete mChunks[i]; +} + +//------------------------------------------------------------------------------ +bool SceneLighting::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 chunk + switch(chunkType) + { + case PersistChunk::MissionChunkType: + mChunks.push_back(new SceneLighting::PersistInfo::MissionChunk); + break; + + case PersistChunk::InteriorChunkType: + mChunks.push_back(new SceneLighting::PersistInfo::InteriorChunk); + break; + + case PersistChunk::TerrainChunkType: + mChunks.push_back(new SceneLighting::PersistInfo::TerrainChunk); + break; + + default: + return(false); + break; + } + + // load the chunk info + if(!mChunks[i]->read(stream)) + return(false); + } + + return(true); +} + +bool SceneLighting::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 SceneLighting::PersistInfo::PersistChunk::read(Stream & stream) +{ + if(!stream.read(&mChunkCRC)) + return(false); + return(true); +} + +bool SceneLighting::PersistInfo::PersistChunk::write(Stream & stream) +{ + if(!stream.write(mChunkCRC)) + return(false); + return(true); +} + +//------------------------------------------------------------------------------ +// Class SceneLighting::PersistInfo::MissionChunk +//------------------------------------------------------------------------------ +SceneLighting::PersistInfo::MissionChunk::MissionChunk() +{ + mChunkType = PersistChunk::MissionChunkType; +} + +//------------------------------------------------------------------------------ +// Class SceneLighting::PersistInfo::InteriorChunk +//------------------------------------------------------------------------------ +SceneLighting::PersistInfo::InteriorChunk::InteriorChunk() +{ + mChunkType = PersistChunk::InteriorChunkType; +} + +SceneLighting::PersistInfo::InteriorChunk::~InteriorChunk() +{ + for(U32 i = 0; i < mLightmaps.size(); i++) + delete mLightmaps[i]; +} + +//------------------------------------------------------------------------------ +// - always read in vertex lighting, lightmaps may not be needed +bool SceneLighting::PersistInfo::InteriorChunk::read(Stream & stream) +{ + if(!Parent::read(stream)) + return(false); + + U32 size; + U32 i; + + // lightmaps->vertex-info + 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]->readPNG(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 SceneLighting::PersistInfo::InteriorChunk::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::SceneLighting::PersistInfo::InteriorChunk::Write: Invalid bitmap!"); + if(!mLightmaps[i]->writePNG(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); + + ZipSubWStream zipStream; + if(!zipStream.attachStream(&stream)) + return(false); + + if(!zipStream.write(U32(mHasAlarmState))) + return(false); + if(!zipStream.write(U32(mDetailVertexCount.size()))) + return(false); + + for(U32 i = 0; i < mDetailVertexCount.size(); i++) + if(!zipStream.write(mDetailVertexCount[i])) + return(false); + + U32 curPos = 0; + for(i = 0; i < mDetailVertexCount.size(); i++) + { + U32 count = mDetailVertexCount[i]; + for(U32 j = 0; j < count; j++) + if(!zipStream.write(mVertexColorsNormal[curPos + j])) + return(false); + + // do alarm.. check if the same + if(mHasAlarmState) + { + if(!dMemcmp(&mVertexColorsNormal[curPos], &mVertexColorsAlarm[curPos], count * sizeof(ColorI))) + { + if(!zipStream.write(U32(1))) + return(false); + } + else + { + if(!zipStream.write(U32(0))) + return(false); + for(U32 j = 0; j < count; j++) + if(!zipStream.write(mVertexColorsAlarm[curPos + j])) + return(false); + } + } + + curPos += count; + } + zipStream.detachStream(); + + // 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); +} + +//------------------------------------------------------------------------------ +// Class SceneLighting::PersistInfo::TerrainChunk +//------------------------------------------------------------------------------ +SceneLighting::PersistInfo::TerrainChunk::TerrainChunk() +{ + mChunkType = PersistChunk::TerrainChunkType; + mLightmap = 0; +} + +SceneLighting::PersistInfo::TerrainChunk::~TerrainChunk() +{ + delete [] mLightmap; +} + +//------------------------------------------------------------------------------ + +bool SceneLighting::PersistInfo::TerrainChunk::read(Stream & stream) +{ + if(!Parent::read(stream)) + return(false); + + GBitmap bitmap; + if(!bitmap.readPNG(stream)) + return(false); + + if((bitmap.getWidth() != TerrainBlock::LightmapSize) || + (bitmap.getHeight() != TerrainBlock::LightmapSize) || + (bitmap.getFormat() != GBitmap::RGB)) + return(false); + + mLightmap = new U16[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize]; + dMemset(mLightmap, 0, sizeof(U16) * TerrainBlock::LightmapSize * TerrainBlock::LightmapSize); + + // copy the bitmap: bits should already be clamped + U8 * bp = bitmap.getAddress(0,0); + for(U32 i = 0; i < TerrainBlock::LightmapSize * TerrainBlock::LightmapSize; i++) + { + mLightmap[i] = (U16(bp[0]) << 11) | (U16(bp[1]) << 6) | (U16(bp[2]) << 1) | 1; + bp += 3; + } + + return(true); +} + +bool SceneLighting::PersistInfo::TerrainChunk::write(Stream & stream) +{ + if(!Parent::write(stream)) + return(false); + + if(!mLightmap) + return(false); + + // write out an RGB bitmap so it can be PNG compressed + GBitmap bitmap(TerrainBlock::LightmapSize, TerrainBlock::LightmapSize, false, GBitmap::RGB); + U8 * bp = bitmap.getAddress(0,0); + + for(U32 i = 0; i < TerrainBlock::LightmapSize * TerrainBlock::LightmapSize; i++) + { + bp[0] = mLightmap[i] >> 11; + bp[1] = (mLightmap[i] >> 6) & 0x1f; + bp[2] = (mLightmap[i] >> 1) & 0x1f; + bp += 3; + } + + if(!bitmap.writePNG(stream)) + return(false); + + return(true); +} + +//------------------------------------------------------------------------------ +bool SceneLighting::verifyMissionInfo(SceneLighting::PersistInfo::PersistChunk * chunk) +{ + SceneLighting::PersistInfo::MissionChunk * info = dynamic_cast(chunk); + if(!info) + return(false); + + SceneLighting::PersistInfo::MissionChunk curInfo; + if(!getMissionInfo(&curInfo)) + return(false); + + return(curInfo.mChunkCRC == info->mChunkCRC); +} + +bool SceneLighting::getMissionInfo(SceneLighting::PersistInfo::PersistChunk * chunk) +{ + SceneLighting::PersistInfo::MissionChunk * info = dynamic_cast(chunk); + if(!info) + return(false); + + info->mChunkCRC = gConnectionMissionCRC ^ PersistInfo::smFileVersion; + return(true); +} + +//------------------------------------------------------------------------------ +bool SceneLighting::loadPersistInfo(const char * fileName) +{ + // open the file + Stream * stream = 0; + stream = ResourceManager->openStream(fileName); + if(!stream) + return(false); + + PersistInfo persistInfo; + bool success = persistInfo.read(*stream); + delete stream; + if(!success) + return(false); + + // verify the mission chunk + if(!verifyMissionInfo(persistInfo.mChunks[0])) + return(false); + + if(mSceneObjects.size() != (persistInfo.mChunks.size() - 1)) + return(false); + + Vector chunks; + + // the scene objects may not be in the same order as the chunks: O(n^2) + U32 i; + for(i = 0; i < mSceneObjects.size(); i++) + { + bool found = false; + for(U32 j = 1; j < persistInfo.mChunks.size() && !found; j++) + if(mSceneObjects[i]->isValidChunk(persistInfo.mChunks[j])) + { + found = true; + chunks.push_back(persistInfo.mChunks[j]); + } + + if(!found) + return(false); + } + + // 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; + if(!ResourceManager->openFileForWrite(file, ResourceManager->getModPathOf(fileName), fileName)) + return(false); + + PersistInfo persistInfo; + + // add in the mission chunk + persistInfo.mChunks.push_back(new SceneLighting::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 + for(U32 i = 0; i < mSceneObjects.size(); i++) + { + if(isInterior(mSceneObjects[i]->mObj)) + persistInfo.mChunks.push_back(new SceneLighting::PersistInfo::InteriorChunk); + else if(isTerrain(mSceneObjects[i]->mObj)) + persistInfo.mChunks.push_back(new SceneLighting::PersistInfo::TerrainChunk); + else + return(false); + + if(!mSceneObjects[i]->getPersistInfo(persistInfo.mChunks.last())) + return(false); + } + + // + return(persistInfo.write(file)); +} + +//------------------------------------------------------------------------------ +// SceneLighting +//------------------------------------------------------------------------------ + +void SceneLighting::addInterior(ShadowVolumeBSP * shadowVolume, InteriorProxy & interior, LightInfo * light, S32 level) +{ + if(light->mType != LightInfo::Vector) + return; + + bool shadowedTree = true; + + // check if just getting shadow detail + if(level == SHADOW_DETAIL) + { + shadowedTree = false; + level = interior->mInteriorRes->getNumDetailLevels() - 1; + } + + Interior * detail = interior->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(); + + // 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->mDirection); + if(dot > -gParellelVectorThresh) + { + if(shadowedTree) + { + // alarm lighting + TextureHandle * normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getNormalLMapIndex(surfaceIndex)); + TextureHandle * alarmHandle = 0; + + 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(); + } + } + + // 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 < surface.mapSizeY; y++) + { + ColorI color = light->mAmbient; + U8 * pBits = lightmap->getAddress(surface.mapOffsetX, surface.mapOffsetY + y); + for(U32 x = 0; x < surface.mapSizeX; x++) + { +#ifdef SET_COLORS + *pBits++ = color.red; + *pBits++ = color.green; + *pBits++ = color.blue; +#else + // the previous *pBit++ = ... code is broken. + U32 _r = static_cast( color.red ) + static_cast( *pBits ); + *pBits = ( _r <= 255 ) ? _r : 255; + pBits++; + + U32 _g = static_cast( color.green ) + static_cast( *pBits ); + *pBits = ( _g <= 255 ) ? _g : 255; + pBits++; + + U32 _b = static_cast( color.blue ) + static_cast( *pBits ); + *pBits = ( _b <= 255 ) ? _b : 255; + pBits++; +#endif + } + } + } + } + continue; + } + + ShadowVolumeBSP::SVPoly * poly = buildInteriorPoly(shadowVolume, interior, detail, + surfaceIndex, light, shadowedTree); + + // insert it into the SVBSP tree + shadowVolume->insertPoly(poly); + } + } +} + +//------------------------------------------------------------------------------ +ShadowVolumeBSP::SVPoly * SceneLighting::buildInteriorPoly(ShadowVolumeBSP * shadowVolumeBSP, + InteriorProxy & interior, Interior * detail, U32 surfaceIndex, LightInfo * light, + bool createSurfaceInfo) +{ + // 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); +} + +//-------------------------------------------------------------------------- +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 + SceneLighting::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); + + return(calculateCRC(crc.address(), sizeof(U32) * crc.size(), 0xffffffff)); +} + +bool SceneLighting::light(BitSet32 flags) +{ + if(!gClientSceneGraph) + return(false); + + mStartTime = Platform::getRealMilliseconds(); + + // register static lights + LightManager * lManager = gClientSceneGraph->getLightManager(); + lManager->registerLights(true); + + // grab all the lights + mLights.clear(); + lManager->getLights(mLights); + if(!mLights.size()) + return(false); + + // get all the objects and create proxy's for them + Vector objects; + gClientContainer.findObjects(InteriorObjectType | TerrainObjectType, findObjectsCallback, (S32)&objects); + + for(SceneObject ** itr = objects.begin(); itr != objects.end(); itr++) + { + ObjectProxy * proxy; + if(isInterior(*itr)) + proxy = new InteriorProxy(*itr); + else if(isTerrain(*itr)) + proxy = new TerrainProxy(*itr); + else + { + AssertFatal(0, "SceneLighting:: invalid object returned from container search"); + continue; + } + + 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(); + + // get the mission name + dSprintf(mFileName, sizeof(mFileName), "lighting/%s_%x.ml", Con::getVariable("$MissionName"), missionCRC); + if(!ResourceManager->isValidWriteFileName(mFileName)) + { + Con::warnf("Invalid filename '%s'. Failed to light mission.", mFileName); + return(false); + } + + // 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... + char fileBuf[1024]; + dSprintf(fileBuf, sizeof(fileBuf), "base/%s", mFileName); + + if(!dFileTouch(fileBuf)) + Con::warnf(" Failed to touch file '%s'. File may be read only.", fileBuf); + + 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 fileStream; + if(!ResourceManager->openFileForWrite(fileStream, ResourceManager->getModPathOf(mFileName), mFileName)) + { + 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 SceneLightingProcessEvent(0, -1), Sim::getTargetTime() + 1); + return(true); +} + +//------------------------------------------------------------------------------ +void SceneLighting::processEvent(U32 light, S32 object) +{ + // cancel lighting? + if(gTerminateLighting) + { + completed(false); + deleteObject(); + return; + } + + ObjectProxy ** proxyItr; + + // last object? + if(object == mLitObjects.size()) + { + for(proxyItr = mLitObjects.begin(); proxyItr != mLitObjects.end(); proxyItr++) + { + if(!(*proxyItr)->getObject()) + { + AssertFatal(0, "SceneLighting:: missing sceneobject on light end"); + continue; + } + (*proxyItr)->postLight(); + } + mLitObjects.clear(); + + Canvas->paint(); + Sim::postEvent(this, new SceneLightingProcessEvent(light + 1, -1), Sim::getTargetTime() + 1); + } + else + { + // done lighting? + if(light == mLights.size()) + { + Con::printf(" Scene lit in %3.3f seconds", (Platform::getRealMilliseconds()-mStartTime)/1000.f); + + // 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); + + // update the resource manager (need size of this file now) + U32 size = dStrlen(ResourceManager->getModPaths()); + if(size) + { + char * pathBuffer = new char [size+1]; + dMemcpy(pathBuffer, ResourceManager->getModPaths(), size+1); + + Vector paths; + char * token = dStrtok(pathBuffer, ";"); + + // grab the paths + while(token) + { + paths.push_back(token); + token = dStrtok(0, ";"); + } + + ResourceManager->setModPaths(paths.size(), const_cast(paths.address())); + delete [] pathBuffer; + } + } + } + + // wrap things up... + processCache(); + completed(true); + deleteObject(); + } + else + { + // start of this light? + if(object == -1) + { + for(proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) + { + if(!(*proxyItr)->getObject()) + { + AssertFatal(0, "SceneLighting:: missing sceneobject on light start"); + Con::errorf(ConsoleLogEntry::General, "SceneLighting:: missing sceneobject on light start"); + continue; + } + if((*proxyItr)->preLight(mLights[light])) + mLitObjects.push_back(*proxyItr); + } + } + else + { + if(mLitObjects[object]->getObject()) + { + gLightingProgress = (F32(light) / F32(mLights.size())) + ((F32(object + 1) / F32(mLitObjects.size())) / F32(mLights.size())); + mLitObjects[object]->light(mLights[light]); + } + else + { + AssertFatal(0, "SceneLighting:: missing sceneobject on light update"); + Con::errorf(ConsoleLogEntry::General, "SceneLighting:: missing sceneobject on light update"); + } + } + + Canvas->paint(); + Sim::postEvent(this, new SceneLightingProcessEvent(light, object + 1), Sim::getTargetTime() + 1); + } + } +} + +//------------------------------------------------------------------------------ + +struct CacheEntry { + ResourceObject * mFileObject; + const char * mFileName; + + CacheEntry() { + mFileObject = 0; + 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->fileSize - entry1->mFileObject->fileSize); +} + +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->fileSize - entry2->mFileObject->fileSize); +} + +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); + + // no size restriction? + if(quota == -1) + return; + + Vector files; + + ResourceObject * match = 0; + const char * name; + + S32 curCacheSize = 0; + match = ResourceManager->findMatch("lighting/*.ml", &name, 0); + while(match) + { + if(match->flags & ResourceObject::File) + { + // dont allow the current file to be removed... + if(!dStrstr(name, mFileName)) + { + CacheEntry entry; + entry.mFileObject = match; + + // get out of vfs... + char fileName[1024]; + dSprintf(fileName, sizeof(fileName), "%s/%s", match->filePath, match->fileName); + + entry.mFileName = StringTable->insert(fileName); + files.push_back(entry); + } + else + curCacheSize += match->fileSize; + } + + match = ResourceManager->findMatch("lighting/*.ml", &name, match); + } + + for(U32 i = 0; i < files.size(); i++) + curCacheSize += files[i].mFileObject->fileSize; + + // 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->fileSize; + ResourceManager->purge(files.last().mFileObject); + + // no sneaky names + if(!dStrstr(files.last().mFileName, "..")) + { + Con::warnf("Removing lighting file '%s'.", files.last().mFileName); + dFileDelete(files.last().mFileName); + } + + files.pop_back(); + } +} + +//------------------------------------------------------------------------------ +// Class SceneLighting::ObjectProxy: +//------------------------------------------------------------------------------ +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); +} + +//------------------------------------------------------------------------------ +// Class SceneLighting::InteriorProxy: +//------------------------------------------------------------------------------ +SceneLighting::InteriorProxy::InteriorProxy(SceneObject * obj) : + Parent(obj) +{ + mBoxShadowBSP = 0; +} + +SceneLighting::InteriorProxy::~InteriorProxy() +{ + delete mBoxShadowBSP; +} + +bool SceneLighting::InteriorProxy::loadResources() +{ + InteriorInstance * interior = getObject(); + if(!interior) + return(false); + + Resource & interiorRes = interior->getResource(); + if(!bool(interiorRes)) + return(false); + + if(!gInteriorLMManager.loadBaseLightmaps(interiorRes->getDetailLevel(0)->getLMHandle(), + interior->getLMHandle())) + return(false); + + return(true); +} + +bool SceneLighting::InteriorProxy::preLight(LightInfo * light) +{ + // create shadow volume of the bounding box of this object + InteriorInstance * interior = getObject(); + if(!interior) + return(false); + + if(light->mType != 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.min)[(i-1)>>1]); + else + plane.d = -(((const float*)objBox.max)[i>>1]); + + // project + mTransformPlane(objTransform, objScale, plane, &testPlanes[numPlanes]); + + planeIndices[numPlanes] = i; + + if(mDot(testPlanes[numPlanes], light->mDirection) < gParellelVectorThresh) + numPlanes++; + } + AssertFatal(numPlanes, "SceneLighting::InteriorProxy::preLight: no planes found"); + + // project the points + Point3F projPnts[8]; + for(i = 0; i < 8; i++) + { + Point3F pnt; + pnt.set(BoxPnts[i].x ? objBox.max.x : objBox.min.x, + BoxPnts[i].y ? objBox.max.y : objBox.min.y, + BoxPnts[i].z ? objBox.max.z : objBox.min.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 SceneLighting::InteriorProxy::isShadowedBy(InteriorProxy * 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 SceneLighting::InteriorProxy::light(LightInfo * light) +{ + InteriorInstance * interior = getObject(); + if(!interior) + return; + + 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; + + if(gLighting->isInterior((*itr)->mObj)) + { + if(*itr == this) + continue; + + if(isShadowedBy(static_cast(*itr))) + gLighting->addInterior(&shadowVolume, *static_cast(*itr), 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(); + + 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 + TextureHandle * normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getNormalLMapIndex(surfaceIndex)); + TextureHandle * alarmHandle = 0; + + 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->mDirection); + + // shadowed? + if(!surfaceInfo->mShadowed.size()) + { + // calc the color and convert to U8 rep + ColorF tmp = (light->mColor * dot) + light->mAmbient; + 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; + + // fill it + for(U32 y = 0; y < surface.mapSizeY; y++) + { + U8 * pBits = lightmap->getAddress(surface.mapOffsetX, surface.mapOffsetY + y); + for(U32 x = 0; x < surface.mapSizeX; x++) + { +#ifdef SET_COLORS + *pBits++ = color.red; + *pBits++ = color.green; + *pBits++ = color.blue; +#else + U32 _r = static_cast( color.red ) + static_cast( *pBits ); + *pBits = ( _r <= 255 ) ? _r : 255; + pBits++; + + U32 _g = static_cast( color.green ) + static_cast( *pBits ); + *pBits = ( _g <= 255 ) ? _g : 255; + pBits++; + + U32 _b = static_cast( color.blue ) + static_cast( *pBits ); + *pBits = ( _b <= 255 ) ? _b : 255; + pBits++; +#endif + } + } + } + + continue; + } + + // 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; + S32 ti; + 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); + + // get the start point on the lightmap + pStart[si] = ((surface.mapOffsetX / (1 / lGenX[si])) - lGenX[3]) / lGenX[si]; + pStart[ti] = ((surface.mapOffsetY / (1 / lGenY[ti])) - lGenY[3]) / lGenY[ti]; + pStart[axis] = ((pNormal[si] * pStart[si]) + + (pNormal[ti] * pStart[ti]) + plane.d) / -pNormal[axis]; + + transform.mulP(start); + start.convolve(scale); + + // 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 + F32 lumelScale = lGenX[si] * normLightmap->getWidth(); + + sVec *= lumelScale; + tVec *= lumelScale; + + // project vecs + transform.mulV(sVec); + sVec.convolve(scale); + + transform.mulV(tVec); + tVec.convolve(scale); + + Point3F & curPos = start; + Point3F sRun = sVec * surface.mapSizeX; + + // 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 < surface.mapSizeY; y++) + { + U8 * normBits = normLightmap->getAddress(surface.mapOffsetX, surface.mapOffsetY + y); + U8 * alarmBits = alarmLightmap ? alarmLightmap->getAddress(surface.mapOffsetX, surface.mapOffsetY + y) : 0; + + for(U32 x = 0; x < surface.mapSizeX; 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->mColor * dot * shadowScale) + light->mAmbient; + tmp.clamp(); + ColorI color = tmp; + + // attempt to light both normal and alarm lightmaps + for(U32 c = 0; c < 2; c++) + { + U8* & pBits = (c == 0) ? normBits : alarmBits; + if(!pBits) + continue; + +#ifdef SET_COLORS + *pBits++ = color.red; + *pBits++ = color.green; + *pBits++ = color.blue; +#else + U32 _r = static_cast( color.red ) + static_cast( *pBits ); + *pBits = ( _r <= 255 ) ? _r : 255; + pBits++; + + U32 _g = static_cast( color.green ) + static_cast( *pBits ); + *pBits = ( _g <= 255 ) ? _g : 255; + pBits++; + + U32 _b = static_cast( color.blue ) + static_cast( *pBits ); + *pBits = ( _b <= 255 ) ? _b : 255; + pBits++; +#endif + } + + curPos += sVec; + } + + curPos -= sRun; + curPos += tVec; + } + } + } + + Con::printf(" = interior lit in %3.3f seconds", (Platform::getRealMilliseconds()-time)/1000.f); +} + +void SceneLighting::InteriorProxy::postLight() +{ + delete mBoxShadowBSP; + mBoxShadowBSP = 0; + + InteriorInstance * interior = getObject(); + if(!interior) + return; + + interior->rebuildVertexColors(); +} + +//------------------------------------------------------------------------------ +U32 SceneLighting::InteriorProxy::getResourceCRC() +{ + InteriorInstance * interior = getObject(); + if(!interior) + return(0); + return(interior->getCRC()); +} + +//------------------------------------------------------------------------------ +bool SceneLighting::InteriorProxy::setPersistInfo(SceneLighting::PersistInfo::PersistChunk * info) +{ + if(!Parent::setPersistInfo(info)) + return(false); + + SceneLighting::PersistInfo::InteriorChunk * chunk = dynamic_cast(info); + AssertFatal(chunk, "SceneLighting::InteriorProxy::setPersistInfo: invalid info chunk!"); + + InteriorInstance * interior = getObject(); + if(!interior) + return(false); + + U32 numDetails = interior->getNumDetailLevels(); + + // check the lighting method + AssertFatal(SceneLighting::smUseVertexLighting == Interior::smUseVertexLighting, "SceneLighting::InteriorProxy::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(), "SceneLighting::InteriorProxy::setPersistInfo: invalid chunk info"); + AssertFatal(!chunk->mHasAlarmState || chunk->mVertexColorsAlarm.size(), "SceneLighting::InteriorProxy::setPersistInfo: invalid chunk info"); + AssertFatal(!(chunk->mHasAlarmState ^ interior->getDetailLevel(0)->hasAlarmState()), "SceneLighting::InteriorProxy::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) + { + 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], "SceneLighting::InteriorProxy::setPersistInfo: bunk bitmap!"); + if(chunk->mLightmaps[idx]->getWidth() != baseHandles[baseIndex]->getWidth() || + chunk->mLightmaps[idx]->getHeight() != baseHandles[baseIndex]->getHeight()) + return(false); + + TextureHandle * tHandle = gInteriorLMManager.duplicateBaseLightmap(interiorHandle, instanceHandle, baseIndex); + + // create the diff bitmap + U8 * pDiff = chunk->mLightmaps[idx]->getAddress(0,0); + U8 * pBase = baseHandles[baseIndex]->getBitmap()->getAddress(0,0); + U8 * pDest = tHandle->getBitmap()->getAddress(0,0); + + Point2I extent(tHandle->getWidth(), tHandle->getHeight()); + for(U32 y = 0; y < extent.y; y++) + for(U32 x = 0; x < extent.x; x++) + { + *pDest++ = *pBase++ + *pDiff++; + *pDest++ = *pBase++ + *pDiff++; + *pDest++ = *pBase++ + *pDiff++; + } + + idx++; + } + } + } + + return(true); +} + +bool SceneLighting::InteriorProxy::getPersistInfo(SceneLighting::PersistInfo::PersistChunk * info) +{ + if(!Parent::getPersistInfo(info)) + return(false); + + SceneLighting::PersistInfo::InteriorChunk * chunk = dynamic_cast(info); + AssertFatal(chunk, "SceneLighting::InteriorProxy::getPersistInfo: invalid info chunk!"); + + InteriorInstance * interior = getObject(); + if(!interior) + return(false); + + LM_HANDLE instanceHandle = interior->getLMHandle(); + + AssertFatal(!chunk->mDetailLightmapCount.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid array!"); + AssertFatal(!chunk->mDetailLightmapIndices.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid array!"); + AssertFatal(!chunk->mLightmaps.size(), "SceneLighting::InteriorProxy::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, GBitmap::RGB); + + U8 * pBase = baseBitmap->getAddress(0,0); + U8 * pInstance = instanceBitmap->getAddress(0,0); + U8 * pDest = diffLightmap->getAddress(0,0); + + // fill the diff lightmap + for(U32 y = 0; y < extent.y; y++) + for(U32 x = 0; x < extent.x; x++) + { + *pDest++ = *pInstance++ - *pBase++; + *pDest++ = *pInstance++ - *pBase++; + *pDest++ = *pInstance++ - *pBase++; + } + + chunk->mLightmaps.push_back(diffLightmap); + } + + chunk->mDetailLightmapCount.push_back(litCount); + } + + // process the vertex lighting... + AssertFatal(!chunk->mDetailVertexCount.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid chunk info"); + AssertFatal(!chunk->mVertexColorsNormal.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid chunk info"); + AssertFatal(!chunk->mVertexColorsAlarm.size(), "SceneLighting::InteriorProxy::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, bad vectors returned!"); + + 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); +} + +//------------------------------------------------------------------------------- +// Class SceneLighting::TerrainProxy: +//------------------------------------------------------------------------------- +SceneLighting::TerrainProxy::TerrainProxy(SceneObject * obj) : + Parent(obj) +{ + mLightmap = 0; +} + +SceneLighting::TerrainProxy::~TerrainProxy() +{ + delete [] mLightmap; +} + +//------------------------------------------------------------------------------- +void SceneLighting::TerrainProxy::init() +{ + mLightmap = new ColorF[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize]; + dMemset(mLightmap, 0, TerrainBlock::LightmapSize * TerrainBlock::LightmapSize * sizeof(ColorF)); + mShadowMask.setSize(TerrainBlock::BlockSize * TerrainBlock::BlockSize); +} + +bool SceneLighting::TerrainProxy::preLight(LightInfo * light) +{ + if(!bool(mObj)) + return(false); + + if(light->mType != LightInfo::Vector) + return(false); + + mShadowMask.clear(); + return(true); +} + +void SceneLighting::TerrainProxy::light(LightInfo * light) +{ + TerrainBlock * terrain = getObject(); + if(!terrain) + return; + + AssertFatal(light->mType == LightInfo::Vector, "wrong light type"); + + S32 time = Platform::getRealMilliseconds(); + + // reset + mShadowVolume = new ShadowVolumeBSP; + + // build interior shadow volume + for(ObjectProxy ** itr = gLighting->mLitObjects.begin(); itr != gLighting->mLitObjects.end(); itr++) + { + if(!gLighting->isInterior((*itr)->mObj)) + continue; + + if(markInteriorShadow(static_cast(*itr))) + gLighting->addInterior(mShadowVolume, *static_cast(*itr), light, SceneLighting::SHADOW_DETAIL); + } + + lightVector(light); + + // set the lightmap... + U16 * lPtr = (U16*)terrain->lightMap->getAddress(0,0); + for(U32 i = 0; i < (TerrainBlock::LightmapSize * TerrainBlock::LightmapSize); i++) + lPtr[i] = convertColor(mLightmap[i]); + + terrain->buildMaterialMap(); + + delete mShadowVolume; + + Con::printf(" = terrain lit in %3.3f seconds", (Platform::getRealMilliseconds()-time)/1000.f); +} + +//------------------------------------------------------------------------------ +S32 SceneLighting::TerrainProxy::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 SceneLighting::TerrainProxy::getShadowedSquares(const Vector & clipPlanes, Vector & shadowList) +{ + TerrainBlock * terrain = getObject(); + if(!terrain) + return(false); + + 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(); + + 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, squareSize, 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); +} + +bool SceneLighting::TerrainProxy::markInteriorShadow(InteriorProxy * proxy) +{ + U32 i; + // setup the clip planes: add the test and the lit planes + Vector clipPlanes; + clipPlanes = proxy->mTerrainTestPlanes; + for(i = 0; i < proxy->mLitBoxSurfaces.size(); i++) + clipPlanes.push_back(proxy->mLitBoxSurfaces[i]->mPlane); + + Vector shadowList; + if(!getShadowedSquares(clipPlanes, shadowList)) + return(false); + + // set the correct bit + for(i = 0; i < shadowList.size(); i++) + mShadowMask.set(shadowList[i]); + + return(true); +} + +//------------------------------------------------------------------------------- +// BUGS: does not work with x or y directions of 0 +// : light dir of 0.1, 0.3, -0.8 causes strange behavior +void SceneLighting::TerrainProxy::lightVector(LightInfo * light) +{ + TerrainBlock * terrain = getObject(); + if(!terrain) + return; + + Point3F lightDir = light->mDirection; + lightDir.normalize(); + + ColorF & ambient = light->mAmbient; + ColorF & lightColor = light->mColor; + + if(lightDir.x == 0 && lightDir.y == 0) + return; + + S32 generateLevel = Con::getIntVariable("$pref::sceneLighting::terrainGenerateLevel", 0); + generateLevel = mClamp(generateLevel, 0, 4); + + bool allowLexelSplits = Con::getBoolVariable("$pref::sceneLighting::terrainAllowLexelSplits", false); + + U32 generateDim = TerrainBlock::LightmapSize << generateLevel; + U32 generateShift = TerrainBlock::LightmapShift + generateLevel; + U32 generateMask = generateDim - 1; + + F32 zStep; + F32 frac; + + Point2I blockColStep; + Point2I blockRowStep; + Point2I blockFirstPos; + + F32 terrainDim = F32(terrain->getSquareSize()) * F32(TerrainBlock::BlockSize); + F32 stepSize = F32(terrain->getSquareSize()) / F32(generateDim / TerrainBlock::BlockSize); + + if(mFabs(lightDir.x) >= mFabs(lightDir.y)) + { + if(lightDir.x > 0) + { + zStep = lightDir.z / lightDir.x; + frac = lightDir.y / lightDir.x; + + blockColStep.set(1, 0); + blockRowStep.set(0, 1); + blockFirstPos.set(0, 0); + } + else + { + zStep = -lightDir.z / lightDir.x; + frac = -lightDir.y / lightDir.x; + + blockColStep.set(-1, 0); + blockRowStep.set(0, 1); + blockFirstPos.set(255, 0); + } + } + else + { + if(lightDir.y > 0) + { + zStep = lightDir.z / lightDir.y; + frac = lightDir.x / lightDir.y; + + blockColStep.set(0, 1); + blockRowStep.set(1, 0); + blockFirstPos.set(0, 0); + } + else + { + zStep = -lightDir.z / lightDir.y; + frac = -lightDir.x / lightDir.y; + + blockColStep.set(0, -1); + blockRowStep.set(1, 0); + blockFirstPos.set(0, 255); + } + } + zStep *= stepSize; + + F32 * heightArray = new F32[generateDim]; + + S32 fracStep = -1; + if(frac < 0) + { + fracStep = 1; + frac = -frac; + } + + F32 * nextHeightArray = new F32[generateDim]; + F32 oneMinusFrac = 1 - frac; + + U32 blockShift = generateShift - TerrainBlock::BlockShift; + U32 lightmapShift = generateShift - TerrainBlock::LightmapShift; + + U32 blockStep = 1 << blockShift; + U32 blockMask = blockStep - 1; + U32 lightmapStep = 1 << lightmapShift; + U32 lightmapNormalOffset = lightmapStep >> 1; + U32 lightmapMask = lightmapStep - 1; + + Point2I bp = blockFirstPos; + F32 terrainHeights[2][TerrainBlock::BlockSize]; + U32 i; + + F32 * pTerrainHeights = static_cast(terrainHeights[0]); + F32 * pNextTerrainHeights = static_cast(terrainHeights[1]); + + // get first set of heights + for(i = 0; i < TerrainBlock::BlockSize; i++) { + pTerrainHeights[i] = fixedToFloat(terrain->getHeight(bp.x, bp.y)); + bp += blockRowStep; + } + + // get second set of heights + bp = blockFirstPos + blockColStep; + for(i = 0; i < TerrainBlock::BlockSize; i++) { + pNextTerrainHeights[i] = fixedToFloat(terrain->getHeight(bp.x, bp.y)); + bp += blockRowStep; + } + + F32 heightStep = 1.f / blockStep; + + F32 terrainZRowStep[TerrainBlock::BlockSize]; + F32 nextTerrainZRowStep[TerrainBlock::BlockSize]; + F32 terrainZColStep[TerrainBlock::BlockSize]; + + // fill in the row steps + for(i = 0; i < TerrainBlock::BlockSize; i++) + { + terrainZRowStep[i] = (pTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[i]) * heightStep; + nextTerrainZRowStep[i] = (pNextTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pNextTerrainHeights[i]) * heightStep; + terrainZColStep[i] = (pNextTerrainHeights[i] - pTerrainHeights[i]) * heightStep; + } + + // get first row of process heights + for(i = 0; i < generateDim; i++) + { + U32 bi = i >> blockShift; + heightArray[i] = pTerrainHeights[bi] + (i & blockMask) * terrainZRowStep[bi]; + } + + bp = blockFirstPos; + if(generateDim == TerrainBlock::BlockSize) + bp += blockColStep; + + // generate the initial run + U32 x, y; + for(x = 1; x < generateDim; x++) + { + U32 xmask = x & blockMask; + + // generate new height step rows? + if(!xmask) + { + F32 * tmp = pTerrainHeights; + pTerrainHeights = pNextTerrainHeights; + pNextTerrainHeights = tmp; + + bp += blockColStep; + + Point2I bwalk = bp; + for(i = 0; i < TerrainBlock::BlockSize; i++, bwalk += blockRowStep) + pNextTerrainHeights[i] = fixedToFloat(terrain->getHeight(bwalk.x, bwalk.y)); + + // fill in the row steps + for(i = 0; i < TerrainBlock::BlockSize; i++) + { + terrainZRowStep[i] = (pTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[i]) * heightStep; + nextTerrainZRowStep[i] = (pNextTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pNextTerrainHeights[i]) * heightStep; + terrainZColStep[i] = (pNextTerrainHeights[i] - pTerrainHeights[i]) * heightStep; + } + } + + Point2I bwalk = bp - blockRowStep; + for(y = 0; y < generateDim; y++) + { + U32 ymask = y & blockMask; + if(!ymask) + bwalk += blockRowStep; + + U32 bi = y >> blockShift; + U32 binext = (bi + 1) & TerrainBlock::BlockMask; + + F32 height; + + // 135? + if((bwalk.x ^ bwalk.y) & 1) + { + U32 xsub = blockStep - xmask; + if(xsub > ymask) // bottom + height = pTerrainHeights[bi] + xmask * terrainZColStep[bi] + + ymask * terrainZRowStep[bi]; + else // top + height = pNextTerrainHeights[bi] - xmask * terrainZColStep[binext] + + ymask * nextTerrainZRowStep[bi]; + } + else + { + if(xmask > ymask) // bottom + height = pTerrainHeights[bi] + xmask * terrainZColStep[bi] + + ymask * nextTerrainZRowStep[bi]; + else // top + height = pTerrainHeights[bi] + xmask * terrainZColStep[binext] + + ymask * terrainZRowStep[bi]; + } + + F32 intHeight = heightArray[y] * oneMinusFrac + heightArray[(y + fracStep) & generateMask] * frac + zStep; + nextHeightArray[y] = getMax(height, intHeight); + } + + // swap the height rows + F32 * tmp = heightArray; + heightArray = nextHeightArray; + nextHeightArray = tmp; + } + + F32 squareSize = terrain->getSquareSize(); + F32 lexelDim = squareSize * F32(TerrainBlock::BlockSize) / F32(TerrainBlock::LightmapSize); + + // calculate normal runs + Point3F normals[2][TerrainBlock::BlockSize]; + + Point3F * pNormals = static_cast(normals[0]); + Point3F * pNextNormals = static_cast(normals[1]); + + // calculate the normal lookup table + F32 * normTable = new F32 [blockStep * blockStep * 4]; + + Point2F corners[4] = { + Point2F(0.f, 0.f), + Point2F(1.f, 0.f), + Point2F(1.f, 1.f), + Point2F(0.f, 1.f) + }; + + U32 idx = 0; + F32 step = 1.f / blockStep; + Point2F pos(0.f, 0.f); + + // fill it + for(x = 0; x < blockStep; x++, pos.x += step, pos.y = 0.f) { + for(y = 0; y < blockStep; y++, pos.y += step) { + for(U32 i = 0; i < 4; i++, idx++) + normTable[idx] = 1.f - getMin(Point2F(pos - corners[i]).len(), 1.f); + } + } + + // fill first column + bp = blockFirstPos; + for(x = 0; x < TerrainBlock::BlockSize; x++) + { + pNextTerrainHeights[x] = fixedToFloat(terrain->getHeight(bp.x, bp.y)); + Point2F pos(bp.x * squareSize, bp.y * squareSize); + terrain->getNormal(pos, &pNextNormals[x]); + bp += blockRowStep; + } + + // get swapped on first pass + pTerrainHeights = static_cast(terrainHeights[1]); + pNextTerrainHeights = static_cast(terrainHeights[0]); + + // get the world offset of the terrain + const MatrixF & transform = terrain->getTransform(); + Point3F worldOffset; + transform.getColumn(3, &worldOffset); + + F32 ratio = F32(1 << lightmapShift); + F32 ratioSquared = ratio * ratio; + F32 inverseRatioSquared = 1.f / ratioSquared; + + F32 lightScale[TerrainBlock::LightmapSize]; + + // walk it... + bp = blockFirstPos - blockColStep; + for(x = 0; x < generateDim; x++) + { + U32 xmask = x & blockMask; + U32 lxmask = x & lightmapMask; + + // generate new runs? + if(!xmask) + { + bp += blockColStep; + + // do the normals + Point3F * temp = pNormals; + pNormals = pNextNormals; + pNextNormals = temp; + + // fill the row + Point2I bwalk = bp + blockColStep; + for(U32 i = 0; i < TerrainBlock::BlockSize; i++) + { + Point2F pos(bwalk.x * squareSize, bwalk.y * squareSize); + terrain->getNormal(pos, &pNextNormals[i]); + bwalk += blockRowStep; + } + + // do the heights + F32 * tmp = pTerrainHeights; + pTerrainHeights = pNextTerrainHeights; + pNextTerrainHeights = tmp; + + bwalk = bp + blockColStep; + for(i = 0; i < TerrainBlock::BlockSize; i++, bwalk += blockRowStep) + pNextTerrainHeights[i] = fixedToFloat(terrain->getHeight(bwalk.x, bwalk.y)); + + // fill in the row steps + for(i = 0; i < TerrainBlock::BlockSize; i++) + { + terrainZRowStep[i] = (pTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[i]) * heightStep; + nextTerrainZRowStep[i] = (pNextTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pNextTerrainHeights[i]) * heightStep; + terrainZColStep[i] = (pNextTerrainHeights[i] - pTerrainHeights[i]) * heightStep; + } + } + + // reset the light scale table + if(!lxmask) + for(i = 0; i < TerrainBlock::LightmapSize; i++) + lightScale[i] = 1.f; + + Point2I bwalk = bp - blockRowStep; + for(y = 0; y < generateDim; y++) + { + U32 lymask = y & lightmapMask; + U32 ymask = y & blockMask; + if(!ymask) + bwalk += blockRowStep; + + U32 bi = y >> blockShift; + U32 binext = (bi + 1) & TerrainBlock::BlockMask; + + F32 height; + F32 xstep, ystep; + + // 135? + if((bwalk.x ^ bwalk.y) & 1) + { + U32 xsub = blockStep - xmask; + if(xsub > ymask) // bottom + { + xstep = terrainZColStep[bi]; + ystep = terrainZRowStep[bi]; + height = pTerrainHeights[bi] + xmask * xstep + ymask * ystep; + } + else // top + { + xstep = -terrainZColStep[binext]; + ystep = nextTerrainZRowStep[bi]; + height = pNextTerrainHeights[bi] + xsub * xstep + ymask * ystep; + } + } + else + { + if(xmask > ymask) // bottom + { + xstep = terrainZColStep[bi]; + ystep = nextTerrainZRowStep[bi]; + height = pTerrainHeights[bi] + xmask * xstep + ymask * ystep; + } + else // top + { + xstep = terrainZColStep[binext]; + ystep = terrainZRowStep[bi]; + height = pTerrainHeights[bi] + xmask * xstep + ymask * ystep; + } + } + + F32 intHeight = heightArray[y] * oneMinusFrac + heightArray[(y + fracStep) & generateMask] * frac + zStep; + + U32 lsi = y >> lightmapShift; + ColorF & col = mLightmap[(x >> lightmapShift) + (lsi << TerrainBlock::LightmapShift)]; + + // lexel shaded by an interior? + if(!lxmask && !lymask && mShadowMask.test((x >> blockShift) + ((y >> blockShift) << TerrainBlock::BlockShift))) + { + U32 idx = (xmask + lightmapNormalOffset + ((ymask + lightmapNormalOffset) << blockShift)) << 2; + + // get the normal for this lexel + Point3F normal = pNormals[bi] * normTable[idx++]; + normal += pNormals[binext] * normTable[idx++]; + normal += pNextNormals[binext] * normTable[idx++]; + normal += pNextNormals[bi] * normTable[idx]; + normal.normalize(); + + nextHeightArray[y] = height; + F32 colorScale = -mDot(normal, lightDir); + + if(colorScale > 0.f) + { + // split lexels which cross the square split? + if(allowLexelSplits) + { + // jff:todo + } + else + { + Point2F wPos((x >> lightmapShift) * lexelDim + worldOffset.x, + (y >> lightmapShift) * lexelDim + worldOffset.y); + + F32 xh = xstep * ratio; + F32 yh = ystep * ratio; + + ShadowVolumeBSP::SVPoly * poly = mShadowVolume->createPoly(); + poly->mWindingCount = 4; + poly->mWinding[0].set(wPos.x, wPos.y, height); + poly->mWinding[1].set(wPos.x + lexelDim, wPos.y, height + xh); + poly->mWinding[2].set(wPos.x + lexelDim, wPos.y + lexelDim, height + xh + yh); + poly->mWinding[3].set(wPos.x, wPos.y + lexelDim, height + yh); + poly->mPlane.set(poly->mWinding[0], poly->mWinding[1], poly->mWinding[2]); + + F32 lexelSize = mShadowVolume->getPolySurfaceArea(poly); + F32 intensity = mShadowVolume->getClippedSurfaceArea(poly) / lexelSize; + lightScale[lsi] = mClampF(intensity, 0.f, 1.f); + } + } + else + lightScale[lsi] = 0.f; + } + + // non shadowed? + if(height >= intHeight) + { + U32 idx = (xmask + (ymask << blockShift)) << 2; + + Point3F normal = pNormals[bi] * normTable[idx++]; + normal += pNormals[binext] * normTable[idx++]; + normal += pNextNormals[binext] * normTable[idx++]; + normal += pNextNormals[bi] * normTable[idx]; + normal.normalize(); + + nextHeightArray[y] = height; + F32 colorScale = -mDot(normal, lightDir); + + if(colorScale > 0.f) + col += ambient + lightColor * colorScale * lightScale[lsi]; + else + col += ambient; + } + else + { + nextHeightArray[y] = intHeight; + col += ambient; + } + } + + F32 * tmp = heightArray; + heightArray = nextHeightArray; + nextHeightArray = tmp; + } + + // set the proper color + for(i = 0; i < TerrainBlock::LightmapSize * TerrainBlock::LightmapSize; i++) + mLightmap[i] *= inverseRatioSquared; + + delete [] normTable; + delete [] heightArray; + delete [] nextHeightArray; +} + +//-------------------------------------------------------------------------- +U32 SceneLighting::TerrainProxy::getResourceCRC() +{ + TerrainBlock * terrain = getObject(); + if(!terrain) + return(0); + return(terrain->getCRC()); +} + +//-------------------------------------------------------------------------- +bool SceneLighting::TerrainProxy::setPersistInfo(SceneLighting::PersistInfo::PersistChunk * info) +{ + if(!Parent::setPersistInfo(info)) + return(false); + + SceneLighting::PersistInfo::TerrainChunk * chunk = dynamic_cast(info); + AssertFatal(chunk, "SceneLighting::TerrainProxy::setPersistInfo: invalid info chunk!"); + + TerrainBlock * terrain = getObject(); + if(!terrain || !terrain->lightMap) + return(false); + + dMemcpy(terrain->lightMap->getAddress(0,0), chunk->mLightmap, sizeof(U16) * TerrainBlock::LightmapSize * TerrainBlock::LightmapSize); + terrain->buildMaterialMap(); + return(true); +} + +bool SceneLighting::TerrainProxy::getPersistInfo(SceneLighting::PersistInfo::PersistChunk * info) +{ + if(!Parent::getPersistInfo(info)) + return(false); + + SceneLighting::PersistInfo::TerrainChunk * chunk = dynamic_cast(info); + AssertFatal(chunk, "SceneLighting::TerrainProxy::getPersistInfo: invalid info chunk!"); + + TerrainBlock * terrain = getObject(); + if(!terrain || !terrain->lightMap) + return(false); + + chunk->mLightmap = new U16[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize]; + dMemcpy(chunk->mLightmap, terrain->lightMap->getAddress(0,0), sizeof(U16) * TerrainBlock::LightmapSize * TerrainBlock::LightmapSize); + return(true); +} diff --git a/sceneGraph/sceneLighting.h b/sceneGraph/sceneLighting.h new file mode 100644 index 0000000..ec021b7 --- /dev/null +++ b/sceneGraph/sceneLighting.h @@ -0,0 +1,266 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SceneLighting_H_ +#define _SceneLighting_H_ + +#ifndef _LIGHTMANAGER_H_ +#include "sceneGraph/lightManager.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "Sim/sceneObject.h" +#endif +#ifndef _TERRDATA_H_ +#include "terrain/terrData.h" +#endif +#ifndef _INTERIORINSTANCE_H_ +#include "interior/interiorInstance.h" +#endif +#ifndef _SHADOWVOLUMEBSP_H_ +#include "sceneGraph/shadowVolumeBSP.h" +#endif + +//------------------------------------------------------------------------------ +class SceneLighting : public SimObject +{ + typedef SimObject Parent; + public: + + struct PersistInfo + { + struct PersistChunk + { + enum { + MissionChunkType = 0, + InteriorChunkType, + TerrainChunkType + }; + + U32 mChunkType; + U32 mChunkCRC; + + virtual ~PersistChunk() {} + + virtual bool read(Stream &); + virtual bool write(Stream &); + }; + + struct MissionChunk : public PersistChunk + { + typedef PersistChunk Parent; + MissionChunk(); + }; + + struct InteriorChunk : public PersistChunk + { + typedef PersistChunk Parent; + + InteriorChunk(); + ~InteriorChunk(); + + Vector mDetailLightmapCount; + Vector mDetailLightmapIndices; + Vector mLightmaps; + + bool mHasAlarmState; + Vector mDetailVertexCount; + Vector mVertexColorsNormal; + Vector mVertexColorsAlarm; + + bool read(Stream &); + bool write(Stream &); + }; + + struct TerrainChunk : public PersistChunk + { + typedef PersistChunk Parent; + + TerrainChunk(); + ~TerrainChunk(); + + U16 * mLightmap; + + bool read(Stream &); + bool write(Stream &); + }; + + ~PersistInfo(); + + Vector mChunks; + static U32 smFileVersion; + + bool read(Stream &); + bool write(Stream &); + }; + + U32 calcMissionCRC(); + + bool verifyMissionInfo(PersistInfo::PersistChunk *); + bool getMissionInfo(PersistInfo::PersistChunk *); + + bool loadPersistInfo(const char *); + bool savePersistInfo(const char *); + + class ObjectProxy; + class TerrainProxy; + class InteriorProxy; + + enum { + SHADOW_DETAIL = -1 + }; + + void addInterior(ShadowVolumeBSP *, InteriorProxy &, LightInfo *, S32); + ShadowVolumeBSP::SVPoly * buildInteriorPoly(ShadowVolumeBSP *, InteriorProxy &, Interior *, U32, LightInfo *, bool); + + //------------------------------------------------------------------------------ + // 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);} + + // lighting interface + virtual bool loadResources() {return(true);} + virtual void init() {} + virtual bool preLight(LightInfo *) {return(false);} + virtual void light(LightInfo *) {} + virtual void postLight() {} + + // persistance + bool calcValidation(); + bool isValidChunk(PersistInfo::PersistChunk *); + + virtual U32 getResourceCRC() = 0; + virtual bool setPersistInfo(PersistInfo::PersistChunk *); + virtual bool getPersistInfo(PersistInfo::PersistChunk *); + }; + + class InteriorProxy : public ObjectProxy + { + private: + typedef ObjectProxy Parent; + bool isShadowedBy(InteriorProxy *); + + public: + + InteriorProxy(SceneObject * obj); + ~InteriorProxy(); + 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; + + // lighting interface + bool loadResources(); + bool preLight(LightInfo *); + void light(LightInfo *); + void postLight(); + + // persist + U32 getResourceCRC(); + bool setPersistInfo(PersistInfo::PersistChunk *); + bool getPersistInfo(PersistInfo::PersistChunk *); + }; + + class TerrainProxy : public ObjectProxy + { + private: + typedef ObjectProxy Parent; + + BitVector mShadowMask; + ShadowVolumeBSP * mShadowVolume; + ColorF * mLightmap; + + void lightVector(LightInfo *); + + struct SquareStackNode + { + U8 mLevel; + U16 mClipFlags; + Point2I mPos; + }; + + S32 testSquare(const Point3F &, const Point3F &, S32, F32, const Vector &); + bool markInteriorShadow(InteriorProxy *); + + public: + + TerrainProxy(SceneObject * obj); + ~TerrainProxy(); + 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 *); + }; + + typedef Vector ObjectProxyList; + + ObjectProxyList mSceneObjects; + ObjectProxyList mLitObjects; + + LightInfoList mLights; + + SceneLighting(); + ~SceneLighting(); + + enum Flags { + ForceAlways = BIT(0), + ForceWritable = BIT(1), + LoadOnly = BIT(2), + }; + static bool lightScene(const char *, BitSet32 flags = 0); + static bool isLighting(); + + S32 mStartTime; + char mFileName[1024]; + static bool smUseVertexLighting; + + bool light(BitSet32); + void completed(bool success); + void processEvent(U32 light, S32 object); + void processCache(); + + // inlined + bool isTerrain(SceneObject *); + bool isInterior(SceneObject *); +}; + + + +//------------------------------------------------------------------------------ + +inline bool SceneLighting::isTerrain(SceneObject * obj) +{ + return obj && ((obj->getTypeMask() & TerrainObjectType) != 0); +} + +inline bool SceneLighting::isInterior(SceneObject * obj) +{ + return obj && ((obj->getTypeMask() & InteriorObjectType) != 0); +} + +#endif diff --git a/sceneGraph/sceneRoot.cc b/sceneGraph/sceneRoot.cc new file mode 100644 index 0000000..b7b6cef --- /dev/null +++ b/sceneGraph/sceneRoot.cc @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/sceneRoot.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" + +SceneRoot* gClientSceneRoot = NULL; +SceneRoot* gServerSceneRoot = NULL; + +SceneRoot::SceneRoot() +{ + mObjBox.min.set(-1e10, -1e10, -1e10); + mObjBox.max.set( 1e10, 1e10, 1e10); + 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. + dMemcpy(state->getZoneStateNC(0).frustum, state->getBaseZoneState().frustum, sizeof(state->getZoneStateNC(0).frustum)); + 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; +} + diff --git a/sceneGraph/sceneRoot.h b/sceneGraph/sceneRoot.h new file mode 100644 index 0000000..9290d43 --- /dev/null +++ b/sceneGraph/sceneRoot.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENEROOT_H_ +#define _SCENEROOT_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "Sim/sceneObject.h" +#endif + +class SceneRoot : public SceneObject +{ + typedef SceneObject Parent; + + protected: + bool onSceneAdd(SceneGraph*); + void onSceneRemove(); + + bool getOverlappingZones(SceneObject*, U32* zones, U32* numZones); + + bool prepRenderImage(SceneState*, const U32 stateKey, const U32 startZone, + const bool modifyBaseZoneState); + + bool scopeObject(const Point3F& rootPosition, + const F32 rootDistance, + bool* zoneScopeState); + + public: + SceneRoot(); + ~SceneRoot(); +}; + +extern SceneRoot* gClientSceneRoot; +extern SceneRoot* gServerSceneRoot; + +#endif //_SCENEROOT_H_ diff --git a/sceneGraph/sceneState.cc b/sceneGraph/sceneState.cc new file mode 100644 index 0000000..f694f70 --- /dev/null +++ b/sceneGraph/sceneState.cc @@ -0,0 +1,992 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "scenegraph/sceneState.h" +#include "sim/sceneObject.h" +#include "platformWIN32/platformGL.h" +#include "dgl/dgl.h" +#include "scenegraph/sceneGraph.h" +#include "terrain/Sky.h" +#include "platform/profiler.h" + +namespace { + +S32 FN_CDECL +cmpImageFunc(const void* p1, const void* p2) +{ + const SceneRenderImage* psri1 = *((const SceneRenderImage**)p1); + const SceneRenderImage* psri2 = *((const SceneRenderImage**)p2); + // Compares only non-transcluent images + AssertFatal(psri1->isTranslucent == false && psri2->isTranslucent == false, + "Error, only non-translucent images allowed here."); + + if (psri1->sortType != psri2->sortType) + { + // Normal render images are setup in such a way that increasing order + // renders + return S32(psri1->sortType) - S32(psri2->sortType); + } + else + { + // Otherwise, sort on primary texture, as set by the sort key + return S32(psri1->textureSortKey) - S32(psri2->textureSortKey); + } +} + +S32 FN_CDECL +cmpTPImageFunc(const void* p1, const void* p2) +{ + const SceneRenderImage* psri1 = *((const SceneRenderImage**)p1); + const SceneRenderImage* psri2 = *((const SceneRenderImage**)p2); + // Compares only non-transcluent images + AssertFatal(psri1->isTranslucent == true && psri2->isTranslucent == true, + "Error, only non-translucent images allowed here."); + + return S32(psri1->textureSortKey) - S32(psri2->textureSortKey); +} + +S32 FN_CDECL +cmpPlaneImageFunc(const void* p1, const void* p2) +{ + const SceneRenderImage** psri1 = (const SceneRenderImage**)p1; + const SceneRenderImage** psri2 = (const SceneRenderImage**)p2; + + // Normal render images are setup in such a way that increasing order + // renders + if (((*psri2)->polyArea - (*psri1)->polyArea) < 0.0) + return -1; + else if (((*psri2)->polyArea - (*psri1)->polyArea) == 0.0) + return 0; + else + return 1; +} + + +S32 FN_CDECL +cmpPointImageFunc(const void* p1, const void* p2) +{ + const SceneRenderImage* psri1 = *((const SceneRenderImage**)p1); + const SceneRenderImage* psri2 = *((const SceneRenderImage**)p2); + + if (psri1->pointDistSq != psri2->pointDistSq) + { + if (psri1->pointDistSq > psri2->pointDistSq) + { + return -1; + } + else + { + return 1; + } + } + else + { + if (psri1->tieBreaker == true) + { + return -1; + } + else + { + return 1; + } + } +} + +inline void renderImage(SceneState* state, SceneRenderImage* image) +{ + PROFILE_START(SceneStateRenderImage); +#ifdef DEBUG + S32 m, p, t0, t1, v[4]; + F32 t0m[16], t1m[16]; + dglGetTransformState(&m, &p, &t0, t0m, &t1, t1m, v); +#endif + image->obj->renderObject(state, image); + +#ifdef DEBUG + if (dglCheckState(m, p, t0, t0m, t1, t1m, v) == false) { + S32 bm, bp, bt0, bt1, bv[4]; + F32 bt0m[16], bt1m[16]; + dglGetTransformState(&bm, &bp, &bt0, bt0m, &bt1, bt1m, bv); + AssertFatal(false, + avar("Error, object of class %s either unbalanced the xform stacks, or didn't reset the viewport!" + " mv(%d %d) proj(%d %d) t0(%d %d), t1(%d %d) (%d %d %d %d: %d %d %d %d)", + image->obj->getClassName(), + m, bm, p, bp, t0, bt0, t1, bt1, v[0], v[1], v[2], v[3], bv[0], bv[1], bv[2], bv[3])); + } +#endif + PROFILE_END(); +} + + + +} // namespace {} + +void SceneState::setupClipPlanes(ZoneState& rState) +{ + F32 farOverNear = getFarPlane() / getNearPlane(); + + Point3F farPosLeftUp = Point3F(rState.frustum[0] * farOverNear, + getFarPlane(), + rState.frustum[3] * farOverNear); + Point3F farPosLeftDown = Point3F(rState.frustum[0] * farOverNear, + getFarPlane(), + rState.frustum[2] * farOverNear); + Point3F farPosRightUp = Point3F(rState.frustum[1] * farOverNear, + getFarPlane(), + rState.frustum[3] * farOverNear); + Point3F farPosRightDown = Point3F(rState.frustum[1] * farOverNear, + getFarPlane(), + rState.frustum[2] * farOverNear); + + MatrixF temp = mModelview; + temp.inverse(); + + temp.mulP(farPosLeftUp); + temp.mulP(farPosLeftDown); + temp.mulP(farPosRightUp); + temp.mulP(farPosRightDown); + + rState.clipPlanes[0].set(getCameraPosition(), farPosLeftUp, farPosLeftDown); + rState.clipPlanes[1].set(getCameraPosition(), farPosRightUp, farPosLeftUp); + rState.clipPlanes[2].set(getCameraPosition(), farPosRightDown, farPosRightUp); + rState.clipPlanes[3].set(getCameraPosition(), farPosLeftDown, farPosRightDown); + rState.clipPlanes[4].set(farPosLeftUp, farPosRightUp, farPosRightDown); + rState.clipPlanesValid = true; +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +SceneState::SceneState(SceneState* parent, + const U32 numZones, + F64 left, + F64 right, + F64 bottom, + F64 top, + F64 nearPlane, + F64 farPlane, + RectI viewport, + const Point3F& camPos, + const MatrixF& modelview, + F32 fogDistance, + F32 visibleDistance, + ColorF fogColor, + U32 numFogVolumes, + FogVolume* fogVolumes, + TextureHandle envMap, + F32 visFactor) +{ + mVisFactor = visFactor; + + mParent = parent; + mFlipCull = false; + + mBaseZoneState.render = false; + mBaseZoneState.clipPlanesValid = false; + mBaseZoneState.frustum[0] = left; + mBaseZoneState.frustum[1] = right; + mBaseZoneState.frustum[2] = bottom; + mBaseZoneState.frustum[3] = top; + mBaseZoneState.viewport = viewport; +#ifdef DEBUG + // Avoid FPU exceptions in ZoneState constructors + dMemset(mBaseZoneState.clipPlanes, 0, (sizeof mBaseZoneState.clipPlanes)); +#endif + + mNearPlane = nearPlane; + mFarPlane = farPlane; + + mModelview = modelview; + mCamPosition = camPos; + mFogDistance = fogDistance; + mVisibleDistance = visibleDistance; + mFogColor = fogColor; + + mZoneStates.setSize(numZones); + for (U32 i = 0; i < numZones; i++) + { + mZoneStates[i].render = false; + mZoneStates[i].clipPlanesValid = false; + } + + mPortalOwner = NULL; + mPortalIndex = 0xFFFFFFFF; + + mNumFogVolumes = numFogVolumes; + mFogVolumes = fogVolumes; + setupFog(); + + mTerrainOverride = false; + + mEnvironmentMap = envMap; + + mRenderImages.reserve(128); + mTranslucentPlaneImages.reserve(128); + mTranslucentPointImages.reserve(128); + mTranslucentBeginImages.reserve(32); + mTranslucentEndImages.reserve(32); + mTranslucentBSP.reserve(64); + mTranslucentBSP.setSize(1); + mTranslucentBSP[0].riList = NULL; + mTranslucentBSP[0].frontIndex = 0xFFFF; + mTranslucentBSP[0].backIndex = 0xFFFF; + mTranslucentBSP[0].rimage = NULL; +} + +SceneState::~SceneState() +{ + U32 i; + for (i = 0; i < mSubsidiaries.size(); i++) + delete mSubsidiaries[i]; + + for (i = 0; i < mRenderImages.size(); i++) + delete mRenderImages[i]; + for (i = 0; i < mTranslucentPlaneImages.size(); i++) + delete mTranslucentPlaneImages[i]; + for (i = 0; i < mTranslucentPointImages.size(); i++) + delete mTranslucentPointImages[i]; + for (i = 0; i < mTranslucentEndImages.size(); i++) + delete mTranslucentEndImages[i]; + for (i = 0; i < mTranslucentBeginImages.size(); i++) + delete mTranslucentBeginImages[i]; +} + +void SceneState::setPortal(SceneObject* owner, const U32 index) +{ + mPortalOwner = owner; + mPortalIndex = index; +} + +void SceneState::insertRenderImage(SceneRenderImage* ri) +{ + if (ri->isTranslucent == false) + mRenderImages.push_back(ri); + else + { + if (ri->sortType == SceneRenderImage::Plane) + { + mTranslucentPlaneImages.push_back(ri); + } + else if (ri->sortType == SceneRenderImage::Point) + { + mTranslucentPointImages.push_back(ri); + } + else if (ri->sortType == SceneRenderImage::BeginSort) + { + mTranslucentBeginImages.push_back(ri); + } + else + { + AssertFatal(ri->sortType == SceneRenderImage::EndSort, "Error, bad transcluent sortType"); + mTranslucentEndImages.push_back(ri); + } + } +} + +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::sortRenderImages() +{ + dQsort(mRenderImages.address(), mRenderImages.size(), sizeof(SceneRenderImage*), cmpImageFunc); + dQsort(mTranslucentPointImages.address(), mTranslucentPointImages.size(), sizeof(SceneRenderImage*), cmpTPImageFunc); + dQsort(mTranslucentPlaneImages.address(), mTranslucentPlaneImages.size(), sizeof(SceneRenderImage*), cmpPlaneImageFunc); +} + +void SceneState::insertIntoNode(RenderBSPNode& rNode, SceneRenderImage* pImage, bool rendered) +{ + if (rNode.frontIndex == 0xFFFF) + { + // Split the node + rNode.plane = pImage->plane; + rNode.frontIndex = mTranslucentBSP.size() + 0; + rNode.backIndex = mTranslucentBSP.size() + 1; + if (rendered) + rNode.rimage = pImage; + + mTranslucentBSP.increment(2); + mTranslucentBSP[rNode.frontIndex].riList = NULL; + mTranslucentBSP[rNode.frontIndex].frontIndex = 0xFFFF; + mTranslucentBSP[rNode.frontIndex].backIndex = 0xFFFF; + mTranslucentBSP[rNode.frontIndex].rimage = NULL; + mTranslucentBSP[rNode.backIndex].riList = NULL; + mTranslucentBSP[rNode.backIndex].frontIndex = 0xFFFF; + mTranslucentBSP[rNode.backIndex].backIndex = 0xFFFF; + mTranslucentBSP[rNode.backIndex].rimage = NULL; + + return; + } + + // Determine which side we're on... + U32 mask = 0; + F32 dist = 0.0f; + for (U32 i = 0; i < 4; i++) + { + F32 d = rNode.plane.distToPlane(pImage->poly[i]); + if (d >= 0.0f) + { + mask |= (1 << i); + } + dist += d; + } + + if (mask == 0xF) + { + // Front only + insertIntoNode(mTranslucentBSP[rNode.frontIndex], pImage); + } + else if (mask == 0) + { + // Back only + insertIntoNode(mTranslucentBSP[rNode.backIndex], pImage); + } + else + { + // Both + if (dist >= 0.0f) + { + // Render front + insertIntoNode(mTranslucentBSP[rNode.frontIndex], pImage, true); + insertIntoNode(mTranslucentBSP[rNode.backIndex], pImage, false); + } + else + { + // Render back + insertIntoNode(mTranslucentBSP[rNode.frontIndex], pImage, false); + insertIntoNode(mTranslucentBSP[rNode.backIndex], pImage, true); + } + } +} + + +void SceneState::buildTranslucentBSP() +{ + U32 i; + for (i = 0; i < mTranslucentPlaneImages.size(); i++) + { + SceneRenderImage* pImage = mTranslucentPlaneImages[i]; + AssertFatal(pImage->sortType == SceneRenderImage::Plane, "Error, bad sort type on plane list!"); + + insertIntoNode(mTranslucentBSP[0], pImage); + } + + for (i = 0; i < mTranslucentPointImages.size(); i++) + { + SceneRenderImage* pImage = mTranslucentPointImages[i]; + AssertFatal(pImage->sortType == SceneRenderImage::Point, "Error, bad sort type on point list!"); + + RenderBSPNode* pNode = &mTranslucentBSP[0]; + while (true) + { + if (pNode->frontIndex != 0xFFFF) + { + if (pNode->plane.distToPlane(pImage->poly[0]) >= 0) + pNode = &mTranslucentBSP[pNode->frontIndex]; + else + pNode = &mTranslucentBSP[pNode->backIndex]; + } + else + { + pImage->pNext = pNode->riList; + pNode->riList = pImage; + break; + } + } + } +} + +void SceneState::renderNode(RenderBSPNode& rNode) +{ + if (rNode.frontIndex != 0xFFFF) + { + if (rNode.plane.distToPlane(mCamPosition) >= 0) + { + renderNode(mTranslucentBSP[rNode.backIndex]); + if (rNode.rimage != NULL) + renderImage(this, rNode.rimage); + + renderNode(mTranslucentBSP[rNode.frontIndex]); + } + else + { + renderNode(mTranslucentBSP[rNode.frontIndex]); + if (rNode.rimage != NULL) + renderImage(this, rNode.rimage); + + renderNode(mTranslucentBSP[rNode.backIndex]); + } + } + else + { + Vector imageList(128); + SceneRenderImage* pImage = rNode.riList; + while (pImage != NULL) + { + pImage->pointDistSq = (mCamPosition - pImage->poly[0]).lenSquared(); + imageList.push_back(pImage); + pImage = pImage->pNext; + } + + dQsort(imageList.address(), imageList.size(), sizeof(SceneRenderImage*), cmpPointImageFunc); + + for (U32 i = 0; i < imageList.size(); i++) + { + renderImage(this, imageList[i]); + } + } +} + + +void SceneState::renderCurrentImages() +{ + sortRenderImages(); + buildTranslucentBSP(); + + if (mPortalOwner != NULL) { + // If we're a portalized object, we need to setup a user clip plane... + PlaneF clipPlane; + mPortalOwner->getWSPortalPlane(mPortalIndex, &clipPlane); + + if (mFlipCull) + clipPlane.neg(); + + GLdouble planeEQ[4]; + planeEQ[0] = clipPlane.x; + planeEQ[1] = clipPlane.y; + planeEQ[2] = clipPlane.z; + planeEQ[3] = clipPlane.d; + glClipPlane(GL_CLIP_PLANE0, planeEQ); + glEnable(GL_CLIP_PLANE0); + } + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglLoadMatrix(&mModelview); + + U32 i; + for (i = 0; i < mRenderImages.size(); i++) + renderImage(this, mRenderImages[i]); + + + for (i = 0; i < mTranslucentBeginImages.size(); i++) + renderImage(this, mTranslucentBeginImages[i]); + + renderNode(mTranslucentBSP[0]); + + + for (i = 0; i < mTranslucentEndImages.size(); i++) + renderImage(this, mTranslucentEndImages[i]); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + if (mPortalOwner != NULL) + glDisable(GL_CLIP_PLANE0); +} + +void SceneState::setupZoneProjection(const U32 zone) +{ + const ZoneState& rState = getZoneState(zone); + AssertFatal(rState.render == true, "Error, should never set up a non-rendering zone!"); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + dglSetFrustum(rState.frustum[0], rState.frustum[1], + rState.frustum[2], rState.frustum[3], + getNearPlane(), getFarPlane()); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(rState.viewport); +} + +void SceneState::setupObjectProjection(const SceneObject* obj) +{ + RectI viewport; + F64 frustum[4] = { 1e10, -1e10, 1e10, -1e10 }; + + bool init = false; + SceneObjectRef* pWalk = obj->mZoneRefHead; + AssertFatal(pWalk != NULL, "Error, object must exist in at least one zone to call this!"); + while (pWalk) { + const ZoneState& rState = getZoneState(pWalk->zone); + if (rState.render == true) { + // frustum + if (rState.frustum[0] < frustum[0]) frustum[0] = rState.frustum[0]; + if (rState.frustum[1] > frustum[1]) frustum[1] = rState.frustum[1]; + if (rState.frustum[2] < frustum[2]) frustum[2] = rState.frustum[2]; + if (rState.frustum[3] > frustum[3]) frustum[3] = rState.frustum[3]; + + // 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!"); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + dglSetFrustum(frustum[0], frustum[1], + frustum[2], frustum[3], + getNearPlane(), getFarPlane()); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); +} + +void SceneState::setupBaseProjection() +{ + const ZoneState& rState = getBaseZoneState(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + dglSetFrustum(rState.frustum[0], rState.frustum[1], + rState.frustum[2], rState.frustum[3], + getNearPlane(), getFarPlane()); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(rState.viewport); +} + + +bool SceneState::isObjectRendered(const SceneObject* obj) +{ + 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); + } + + const Box3F& rObjBox = obj->getObjBox(); + const Point3F& rScale = obj->getScale(); + + Point3F center; + rObjBox.getCenter(¢er); + center.convolve(rScale); + + Point3F xRad((rObjBox.max.x - rObjBox.min.x) * 0.5 * rScale.x, 0, 0); + Point3F yRad(0, (rObjBox.max.y - rObjBox.min.y) * 0.5 * rScale.y, 0); + Point3F zRad(0, 0, (rObjBox.max.z - rObjBox.min.z) * 0.5 * rScale.z); + + obj->getRenderTransform().mulP(center); + obj->getRenderTransform().mulV(xRad); + obj->getRenderTransform().mulV(yRad); + obj->getRenderTransform().mulV(zRad); + + bool render = true; + for (U32 i = 0; i < 5; i++) { + if (rState.clipPlanes[i].whichSideBox(center, xRad, yRad, zRad, Point3F(0, 0, 0)) == PlaneF::Back) { + render = false; + break; + } + } + + if (render) + return true; + } + + pWalk = pWalk->nextInObj; + } + + return false; +} + +//-------------------------------------------------------------------------- +//-------------------------------------- + +bool checkFogBandBoxVisible(F32 dist, F32 haze, F32 low, F32 high, Vector &fb) +{ + // if there are no fog bands, no fog - it's visible + if(!fb.size()) + return true; + // if the first fog band is unfogged and the box + // is inside the band, it's visible + if(fb[0].isFog == false && low < fb[0].cap) + return true; + + // check the case of the camera in a fog band + if(fb[0].isFog) + { + // if the low point is in the fog, we check that + + if(low < fb[0].cap) + { + if(haze + dist * fb[0].factor < 1) + return true; + // if low and high are both in the fog band + // and low isn't visible, neither is high + if(high < fb[0].cap) + return false; + // check the high point... + F32 highDist = mSqrt(high * high + dist * dist - low * low); + return haze + (fb[0].cap / high) * highDist * fb[0].factor < 1; + } + // ok, both low and high are above the cap of the plane + // so we have to check only the high point (bigger triangle means less fog + // applied (higher top means steeper slope on the hypotenuse)) + + F32 highDist = mSqrt(high * high + dist * dist - low * low); + return haze + (fb[0].cap / high) * highDist * fb[0].factor < 1; + } + // ok, fb[0] is not fogged, meaning there is an empty layer + // followed by a fog plane, followed by the box. + + // we only test the one fog volume for visibility of the box... + F32 fogStart = fb[0].cap; + F32 fogEnd = fogStart + fb[1].cap; + + // if the low is in the fog band, we have to check + // low, followed by possibly high + // if low is above the fog band we only have to check high point + if(low > fogEnd) + { + // only check the high point through the fog + F32 highDist = mSqrt(high * high + dist * dist - low * low); + return haze + (fb[1].cap / high) * highDist * fb[1].factor < 1; + } + // last case, low is in the fog band + // check low vis: + if(haze + fb[1].factor * dist * (low - fogStart) / low < 1) + return true; + // if the high point is in the same fog band, it's not visible + if(high < fogEnd) + return false; + // ok, check the high point + F32 highDist = mSqrt(high * high + dist * dist - low * low); + return haze + (fb[1].cap / high) * highDist * fb[1].factor < 1; +} + +bool SceneState::isBoxFogVisible(F32 dist, F32 top, F32 bottom) +{ + F32 camZ = mCamPosition.z; + float haze = 0; + if(dist > mFogDistance) { + float distFactor = (dist - mFogDistance) * mFogScale - 1.0; + haze = 1.0 - distFactor * distFactor; + } + F32 distSq = dist * dist; + // the object is below: + if(top < camZ) + { + return checkFogBandBoxVisible(dist, haze, camZ - top, camZ - bottom, mNegFogBands); + } + else if(bottom > camZ) + { + return checkFogBandBoxVisible(dist, haze, bottom - camZ, top - camZ, mPosFogBands); + } + else + { + // spans the fog... + if(!mNegFogBands.size() || !mPosFogBands.size() || !mPosFogBands[0].isFog) + return true; + // ok, we know there is at least one fog band and the camera is in it. + // check if the object is visible through the fog... + if(haze + dist * mPosFogBands[0].factor < 1) + return true; + + // ok, check the top stretch... + // we know now that since the box spans the horizontal, + // that dist is a horizontal (deltaZ = 0) + // so we want the segment of the hypotenuse that goes through + // the fog. + + F32 ht = top - camZ; + // don't do it if the top is in the fog + if(ht > mPosFogBands[0].cap) + { + if(haze + (mPosFogBands[0].cap / ht) * mSqrt(dist * dist + ht * ht) * mPosFogBands[0].factor < 1) + return true; + } + + // ok, last chance, check the bottom segment + ht = camZ - bottom; + if(ht < mNegFogBands[0].cap) + return false; + return haze + (mNegFogBands[0].cap / ht) * mSqrt(dist * dist + ht * ht) * mNegFogBands[0].factor < 1; + } +} + +void SceneState::setupFog() +{ + + if( mVisibleDistance == mFogDistance ) { + // FIXME: arbitrary large constant + mFogScale = 1000.0f; + } else { + mFogScale = 1.0 / (mVisibleDistance - mFogDistance); + } + + // construct positive fog volumes + mPosFogBands.clear(); + F32 camZ = mCamPosition.z; + + S32 i; + for(i = 0; i < mNumFogVolumes; i++) + { + if(camZ < mFogVolumes[i].maxHeight) + break; + } + if(i < mNumFogVolumes) + { + float prevHeight = camZ; + for(;i < mNumFogVolumes; i++) + { + if(prevHeight < mFogVolumes[i].minHeight) + { + FogBand fb; + fb.isFog = false; + fb.color.set(mFogVolumes[i].color.red, + mFogVolumes[i].color.green, + mFogVolumes[i].color.blue, + mFogVolumes[i].color.alpha); + fb.cap = mFogVolumes[i].minHeight - prevHeight; + prevHeight = mFogVolumes[i].minHeight; + mPosFogBands.push_back(fb); + } + FogBand fb; + fb.isFog = true; + fb.cap = mFogVolumes[i].maxHeight - prevHeight; + fb.color.set(mFogVolumes[i].color.red, + mFogVolumes[i].color.green, + mFogVolumes[i].color.blue, + mFogVolumes[i].color.alpha); + fb.factor = (1 / (mFogVolumes[i].visibleDistance * mVisFactor)) * mFogVolumes[i].percentage; + prevHeight = mFogVolumes[i].maxHeight; + mPosFogBands.push_back(fb); + } + } + // construct negative fog volumes + mNegFogBands.clear(); + for(i = mNumFogVolumes - 1; i >= 0; i--) + { + if(camZ > mFogVolumes[i].minHeight) + break; + } + if(i >= 0) + { + float prevHeight = camZ; + for(;i >= 0; i--) + { + if(prevHeight > mFogVolumes[i].maxHeight) + { + FogBand fb; + fb.isFog = false; + fb.cap = prevHeight - mFogVolumes[i].maxHeight; + prevHeight = mFogVolumes[i].maxHeight; + fb.color.set(mFogVolumes[i].color.red, + mFogVolumes[i].color.green, + mFogVolumes[i].color.blue, + mFogVolumes[i].color.alpha); + mNegFogBands.push_back(fb); + } + FogBand fb; + fb.isFog = true; + fb.cap = prevHeight - mFogVolumes[i].minHeight; + fb.factor = (1 / (mFogVolumes[i].visibleDistance * mVisFactor)) * mFogVolumes[i].percentage; + prevHeight = mFogVolumes[i].minHeight; + fb.color.set(mFogVolumes[i].color.red, + mFogVolumes[i].color.green, + mFogVolumes[i].color.blue, + mFogVolumes[i].color.alpha); + mNegFogBands.push_back(fb); + } + } +} + +void SceneState::getFogs(float dist, float deltaZ, ColorF *array, U32 &numFogs) +{ + numFogs = 0; + Vector *band; + if(deltaZ < 0) + { + deltaZ = -deltaZ; + band = &mNegFogBands; + } + else + band = &mPosFogBands; + + float ht = deltaZ; + for(int i = 0; i < band->size(); i++) + { + FogBand &bnd = (*band)[i]; + + if(ht < bnd.cap) + { + if(bnd.isFog) + array[numFogs++] = ColorF(bnd.color.red, bnd.color.green, bnd.color.blue, dist * bnd.factor); + break; + } + float subDist = dist * bnd.cap / ht; + if(bnd.isFog) + { + array[numFogs++] = ColorF(bnd.color.red, + bnd.color.green, + bnd.color.blue, + subDist * bnd.factor); + } + dist -= subDist; + ht -= bnd.cap; + } +} + +F32 SceneState::getFog(float dist, float deltaZ, S32 volKey) +{ + float haze = 0; + Vector *band; + if(deltaZ < 0) + { + deltaZ = -deltaZ; + band = &mNegFogBands; + } + else + band = &mPosFogBands; + + if(band->size() < 1) + return haze; + + float ht = deltaZ; + FogBand &bnd = (*band)[volKey]; + + if(ht < bnd.cap) + { + if(bnd.isFog) + haze += dist * bnd.factor; + } + else + { + float subDist = dist * bnd.cap / ht; + if(bnd.isFog) + haze += subDist * bnd.factor; + } + + return haze; +} + +F32 SceneState::getFog(float dist, float deltaZ) +{ + float haze = 0; + Vector *band; + if(deltaZ < 0) + { + deltaZ = -deltaZ; + band = &mNegFogBands; + } + else + band = &mPosFogBands; + + float ht = deltaZ; + for(int i = 0; i < band->size(); i++) + { + FogBand &bnd = (*band)[i]; + + if(ht < bnd.cap) + { + if(bnd.isFog) + haze += dist * bnd.factor; + break; + } + float subDist = dist * bnd.cap / ht; + if(bnd.isFog) + haze += subDist * bnd.factor; + dist -= subDist; + ht -= bnd.cap; + } + return haze; +} + +F32 SceneState::getHazeAndFog(float dist, float deltaZ) +{ + float haze = 0; + + if(dist > mFogDistance) { + if (dist > mVisibleDistance) + return 1.0; + + float distFactor = (dist - mFogDistance) * mFogScale - 1.0; + haze = 1.0 - distFactor * distFactor; + } + + Vector *band; + if(deltaZ < 0) + { + deltaZ = -deltaZ; + band = &mNegFogBands; + } + else + band = &mPosFogBands; + + float ht = deltaZ; + for(int i = 0; i < band->size(); i++) + { + FogBand &bnd = (*band)[i]; + + if(ht < bnd.cap) + { + if(bnd.isFog) + haze += dist * bnd.factor; + break; + } + float subDist = dist * bnd.cap / ht; + if(bnd.isFog) + haze += subDist * bnd.factor; + dist -= subDist; + ht -= bnd.cap; + } + if(haze > 1) + return 1; + return haze; +} + + +void SceneState::setImageRefPoint(SceneObject* obj, SceneRenderImage* image) const +{ + const Box3F& rBox = obj->getObjBox(); + Point3F objSpaceCamPosition = mCamPosition; + obj->getRenderWorldTransform().mulP(objSpaceCamPosition); + objSpaceCamPosition.convolveInverse(obj->getScale()); + image->poly[0] = rBox.getClosestPoint(objSpaceCamPosition); + image->poly[0].convolve(obj->getScale()); + obj->getRenderTransform().mulP(image->poly[0]); +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +SceneRenderImage::~SceneRenderImage() +{ + +} + diff --git a/sceneGraph/sceneState.h b/sceneGraph/sceneState.h new file mode 100644 index 0000000..0397725 --- /dev/null +++ b/sceneGraph/sceneState.h @@ -0,0 +1,308 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENESTATE_H_ +#define _SCENESTATE_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _PLATFORMASSERT_H_ +#include "platform/platformAssert.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif +#ifndef _MRECT_H_ +#include "math/mRect.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif + +class SceneObject; + +class SceneRenderImage +{ + public: + enum SortType + { + Sky = 0, + Terrain = 1, + Normal = 2, + // Only valid when isTranslucent == true + Point, // If point sort, poly[0] is the sort point + Plane, + EndSort, + BeginSort + }; + + SceneRenderImage() + : sortType(Normal), + isTranslucent(false), + tieBreaker(false), + useSmallTextures(false), + textureSortKey(0) + { + // + } + virtual ~SceneRenderImage(); + + SceneObject* obj; + + bool isTranslucent; + bool tieBreaker; + bool useSmallTextures; + + SortType sortType; + PlaneF plane; + Point3F poly[4]; + F32 polyArea; + F32 pointDistSq; + + U32 textureSortKey; + + // NEVER set this. + SceneRenderImage* pNext; +}; + +//-------------------------------------------------------------------------- +//-------------------------------------- SceneState +// + +struct FogVolume; + +class SceneState +{ + friend class SceneGraph; + + public: + struct FogBand + { + bool isFog; + float cap; + float factor; + ColorF color; + }; + + struct ZoneState { + bool render; + F64 frustum[4]; // l r b t + RectI viewport; + + bool clipPlanesValid; + PlaneF clipPlanes[5]; + }; + void setupClipPlanes(ZoneState&); + + struct TransformPortal { + SceneObject* owner; + U32 portalIndex; + U32 globalZone; + Point3F traverseStart; + bool flipCull; + }; + + public: + SceneState(SceneState* parent, + const U32 numZones, + F64 left, + F64 right, + F64 bottom, + F64 top, + F64 nearPlane, + F64 farPlane, + RectI viewport, + const Point3F& camPos, + const MatrixF& modelview, + F32 fogDistance, + F32 visibleDistance, + ColorF fogColor, + U32 numFogVolumes, + FogVolume *fogVolumes, + TextureHandle environmentMap, + F32 visFactor); + + ~SceneState(); + void setPortal(SceneObject*, const U32); + + void setImageRefPoint(SceneObject*, SceneRenderImage*) const; + + const Point3F& getCameraPosition() const { return mCamPosition; } + F64 getNearPlane() const { return mNearPlane; } + F64 getFarPlane() const { return mFarPlane; } + + const ZoneState& getBaseZoneState() const; + ZoneState& getBaseZoneStateNC(); + + const ZoneState& getZoneState(const U32 zoneId) const; + ZoneState& getZoneStateNC(const U32 zoneId); + + void insertRenderImage(SceneRenderImage*); + void insertTransformPortal(SceneObject* owner, U32 portalIndex, + U32 globalZone, const Point3F& traversalStartPoint, + const bool flipCull); + + void enableTerrainOverride(); + bool isTerrainOverridden() const; + + void renderCurrentImages(); + + // Utility functions to be used in rendering. SetupZoneProjection + // sets the viewport and projection up for the given zone. setupObjectProjection + // unions all of an objects zones frustums. + bool isObjectRendered(const SceneObject*); + + void setupZoneProjection(const U32 zone); + void setupObjectProjection(const SceneObject*); + void setupBaseProjection(); + F32 getVisibleDistance(); + F32 getFogDistance(); + ColorF getFogColor(); + + TextureHandle getEnvironmentMap() { return mEnvironmentMap; } + + void setupFog(); + bool isBoxFogVisible(F32 dist, F32 top, F32 bottom); + + F32 getHazeAndFog(float dist, float deltaZ); + F32 getFog(float dist, float deltaZ); + F32 getFog(float dist, float deltaZ, S32 volKey); + void getFogs(float dist, float deltaZ, ColorF *array, U32 &numFogs); + F32 getHaze(F32 dist); + + private: + struct RenderBSPNode + { + PlaneF plane; + SceneRenderImage* riList; + U16 frontIndex; + U16 backIndex; + SceneRenderImage* rimage; + }; + Vector mTranslucentBSP; + + + Vector mZoneStates; + Vector mRenderImages; + Vector mTranslucentPlaneImages; + Vector mTranslucentPointImages; + Vector mTranslucentBeginImages; + Vector mTranslucentEndImages; + void sortRenderImages(); + void buildTranslucentBSP(); + void insertIntoNode(RenderBSPNode&, SceneRenderImage*, bool rendered = true); + void renderNode(RenderBSPNode&); + + bool mTerrainOverride; + + // Closely related. Transform portals are turned into sorted mSubsidiaries + // by the traversal process... + Vector mSubsidiaries; + Vector mTransformPortals; + + Vector mPosFogBands; + Vector mNegFogBands; + + ZoneState mBaseZoneState; + Point3F mCamPosition; + + F64 mNearPlane; + F64 mFarPlane; + + F32 mVisFactor; + + SceneState* mParent; + + SceneObject* mPortalOwner; + U32 mPortalIndex; + + ColorF mFogColor; + F32 mFogDistance; + F32 mVisibleDistance; + F32 mFogScale; + + U32 mNumFogVolumes; + FogVolume *mFogVolumes; + + TextureHandle mEnvironmentMap; + + public: + bool mFlipCull; + MatrixF mModelview; + Vector *getPosFogBands() { return &mPosFogBands; } + Vector *getNegFogBands() { return &mNegFogBands; } +}; + +inline F32 SceneState::getHaze(F32 dist) +{ + if(dist <= mFogDistance) + return 0; + + if (dist > mVisibleDistance) + return 1.0; + + F32 distFactor = (dist - mFogDistance) * mFogScale - 1.0; + return 1.0 - distFactor * distFactor; +} + +inline F32 SceneState::getVisibleDistance() +{ + return mVisibleDistance; +} + +inline F32 SceneState::getFogDistance() +{ + return mFogDistance; +} + +inline ColorF SceneState::getFogColor() +{ + return mFogColor; +} + +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 < mZoneStates.size(), "Error, out of bounds zone!"); + return mZoneStates[zoneId]; +} + +inline SceneState::ZoneState& SceneState::getZoneStateNC(const U32 zoneId) +{ + AssertFatal(zoneId < 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.cc b/sceneGraph/sceneTraversal.cc new file mode 100644 index 0000000..2daa3c0 --- /dev/null +++ b/sceneGraph/sceneTraversal.cc @@ -0,0 +1,419 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "scenegraph/sceneGraph.h" +#include "sim/sceneObject.h" +#include "scenegraph/sceneState.h" +#include "math/mMatrix.h" +#include "dgl/dgl.h" +#include "core/polyList.h" +#include "terrain/terrData.h" + +namespace { + +class PotentialRenderList +{ + public: + Point3F farPosLeftUp; + Point3F farPosLeftDown; + Point3F farPosRightUp; + Point3F farPosRightDown; + Point3F camPos; + F32 viewDistSquared; + Box3F mBox; + + Vector mList; + + PlaneF viewPlanes[5]; + SceneState* mState; + + public: + void insertObject(SceneObject* obj); + void setupClipPlanes(SceneState*); +}; + +void PotentialRenderList::setupClipPlanes(SceneState* state) +{ + mState = state; + + camPos = state->getCameraPosition(); + F32 farOverNear = state->getFarPlane() / state->getNearPlane(); + + farPosLeftUp = Point3F(state->getBaseZoneState().frustum[0] * farOverNear, + state->getFarPlane(), + state->getBaseZoneState().frustum[3] * farOverNear); + farPosLeftDown = Point3F(state->getBaseZoneState().frustum[0] * farOverNear, + state->getFarPlane(), + state->getBaseZoneState().frustum[2] * farOverNear); + farPosRightUp = Point3F(state->getBaseZoneState().frustum[1] * farOverNear, + state->getFarPlane(), + state->getBaseZoneState().frustum[3] * farOverNear); + farPosRightDown = Point3F(state->getBaseZoneState().frustum[1] * farOverNear, + state->getFarPlane(), + state->getBaseZoneState().frustum[2] * farOverNear); + + MatrixF temp = state->mModelview; + temp.inverse(); + + temp.mulP(farPosLeftUp); + temp.mulP(farPosLeftDown); + temp.mulP(farPosRightUp); + temp.mulP(farPosRightDown); + + mBox.min = camPos; + mBox.min.setMin(farPosLeftUp); + mBox.min.setMin(farPosLeftDown); + mBox.min.setMin(farPosRightUp); + mBox.min.setMin(farPosRightDown); + mBox.max = camPos; + mBox.max.setMax(farPosLeftUp); + mBox.max.setMax(farPosLeftDown); + mBox.max.setMax(farPosRightUp); + mBox.max.setMax(farPosRightDown); + + viewPlanes[0].set(camPos, farPosLeftUp, farPosLeftDown); + viewPlanes[1].set(camPos, farPosRightUp, farPosLeftUp); + viewPlanes[2].set(camPos, farPosRightDown, farPosRightUp); + viewPlanes[3].set(camPos, farPosLeftDown, farPosRightDown); + viewPlanes[4].set(farPosLeftUp, farPosRightUp, farPosRightDown); +} + +void PotentialRenderList::insertObject(SceneObject* obj) +{ + if (obj->getType() & PlayerObjectType) + U32 test = 0; + + const Box3F& rObjBox = obj->getObjBox(); + const Point3F& rScale = obj->getScale(); + + Point3F center; + rObjBox.getCenter(¢er); + center.convolve(rScale); + + Point3F xRad((rObjBox.max.x - rObjBox.min.x) * 0.5 * rScale.x, 0, 0); + Point3F yRad(0, (rObjBox.max.y - rObjBox.min.y) * 0.5 * rScale.y, 0); + Point3F zRad(0, 0, (rObjBox.max.z - rObjBox.min.z) * 0.5 * rScale.z); + + obj->getRenderTransform().mulP(center); + obj->getRenderTransform().mulV(xRad); + obj->getRenderTransform().mulV(yRad); + obj->getRenderTransform().mulV(zRad); + + bool render = true; + for (U32 i = 0; i < 5; i++) { + if (viewPlanes[i].whichSideBox(center, xRad, yRad, zRad, Point3F(0, 0, 0)) == PlaneF::Back) { + render = false; + break; + } + } + + if (render) + mList.push_back(obj); +} + +void prlInsertionCallback(SceneObject* obj, S32 key) +{ + PotentialRenderList* prList = (PotentialRenderList*)key; + + Point3F closestPt = obj->getWorldBox().getClosestPoint(prList->camPos); + F32 lenSquared = (closestPt - prList->camPos).lenSquared(); + if (lenSquared >= prList->viewDistSquared) + return; + + F32 len = mSqrt(lenSquared); + F32 top = obj->getWorldBox().max.z; + F32 bottom = obj->getWorldBox().min.z; + if (prList->mState->isBoxFogVisible(len, top, bottom)) + prList->insertObject(obj); +} + +} // namespace {} + +void SceneGraph::buildSceneTree(SceneState* state, + SceneObject* baseObject, + const U32 baseZone, + const U32 currDepth, + const U32 objectMask ) +{ + AssertFatal(this == gClientSceneGraph, "Error, only the client scenegraph can support this call!"); + + // 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 one 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); + prl.viewDistSquared = getVisibleDistanceMod() * getVisibleDistanceMod(); + + // We only have to clip the mBox field + AssertFatal(prl.mBox.isOverlapped(pTraversalRoot->getWorldBox()), + "Error, prl box must overlap the traversal root"); + prl.mBox.min.setMax(pTraversalRoot->getWorldBox().min); + prl.mBox.max.setMin(pTraversalRoot->getWorldBox().max); + prl.mBox.min -= Point3F(5, 5, 5); + prl.mBox.max += 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, S32(&prl)); + + // Clear the object colors + U32 i; + for (i = 0; i < prl.mList.size(); i++) + prl.mList[i]->setTraverseColor(SceneObject::White); + + for (i = 0; i < prl.mList.size(); i++) + if (prl.mList[i]->getTraverseColor() == SceneObject::White) + treeTraverseVisit(prl.mList[i], state, smStateKey); + + if (currDepth < csmMaxTraversalDepth && state->mTransformPortals.size() != 0) { + // 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; + MatrixF newMV; + dglGetModelview(&oldMV); + 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->mNearPlane, + state->mFarPlane, + 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; + } + + SceneState* newState = new SceneState(state, + mCurrZoneEnd, + newFrustum[0], + newFrustum[1], + newFrustum[2], + newFrustum[3], + state->mNearPlane, + state->mFarPlane, + newViewport, + cameraPosition, + newMV, + mFogDistance, + mVisibleDistance, + mFogColor, + mNumFogVolumes, + mFogVolumes, + state->getEnvironmentMap(), + smVisibleDistanceMod); + newState->mFlipCull = state->mFlipCull ^ rPortal.flipCull; + newState->setPortal(rPortal.owner, rPortal.portalIndex); + + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglLoadMatrix(&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, startObject, startZone, currDepth + 1, objectMask); + + // Pop off the new modelview + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + // 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->setTraverseColor(SceneObject::Black); + return; + } + + AssertFatal(obj->getTraverseColor() == SceneObject::White, + "Wrong color for this stage of the traversal!"); + obj->setTraverseColor(SceneObject::Grey); + + 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->getTraverseColor() == SceneObject::White) + treeTraverseVisit(pOwner, state, stateKey); + + pWalk = pWalk->nextInObj; + } + + obj->setTraverseColor(SceneObject::Black); + + if (getCurrentTerrain() != NULL && obj->getWorldBox().min.x > -1e5) + { + 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) + return; + } + + obj->prepRenderImage(state, stateKey, 0xFFFFFFFF); +} + +bool terrCheck(TerrainBlock* pBlock, + SceneObject* pObj, + const Point3F camPos) +{ + 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) + return false; + + const Box3F& oBox = pObj->getObjBox(); + F32 minSide = getMin(oBox.len_x(), oBox.len_y()); + if (minSide > 85.0f) + return false; + + const Box3F& rBox = pObj->getWorldBox(); + Point3F ul(rBox.min.x, rBox.min.y, rBox.max.z); + Point3F ur(rBox.min.x, rBox.max.y, rBox.max.z); + Point3F ll(rBox.max.x, rBox.min.y, rBox.max.z); + Point3F lr(rBox.max.x, rBox.max.y, rBox.max.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; + + return true; + } + + return false; +} diff --git a/sceneGraph/sgUtil.cc b/sceneGraph/sgUtil.cc new file mode 100644 index 0000000..78b14d1 --- /dev/null +++ b/sceneGraph/sgUtil.cc @@ -0,0 +1,365 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/sgUtil.h" +#include "Math/mRect.h" +#include "Math/mMatrix.h" +#include "PlatformWin32/platformGL.h" +#include "dgl/dgl.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!"); + 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 F64* 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) +{ + // Ok, here's the deal. We need to project and clip the portal windings given the + // above parameters. We then re-compute the new frustum, and return it. + + // We need the projection matrix. This is an ugly way to do this... + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + dglSetFrustum(oldFrustum[0], oldFrustum[1], + oldFrustum[2], oldFrustum[3], + nearPlane, farPlane); + dglGetProjection(&sgProjMatrix); + glPopMatrix(); + + MatrixF finalModelView = modelview; + sgProjMatrix.mul(finalModelView); + + finalModelView.inverse(); + finalModelView.mulP(Point3F(0, 0, 0), &sgCamPoint); + sgWSToOSMatrix = finalModelView; + + // Create the object space clipping planes... + Point3F ul(oldFrustum[0], nearPlane, oldFrustum[3]); + Point3F ur(oldFrustum[1], nearPlane, oldFrustum[3]); + Point3F ll(oldFrustum[0], nearPlane, oldFrustum[2]); + Point3F lr(oldFrustum[1], nearPlane, oldFrustum[2]); + Point3F farPlanePoint(0, farPlane, 0); + + sgWSToOSMatrix.mulP(ul); + sgWSToOSMatrix.mulP(ur); + sgWSToOSMatrix.mulP(ll); + sgWSToOSMatrix.mulP(lr); + sgWSToOSMatrix.mulP(farPlanePoint); + + sgOSPlaneFar.set(farPlanePoint, sgCamPoint - farPlanePoint); + sgOSPlaneXMin.set(sgCamPoint, ul, ll); + sgOSPlaneXMax.set(sgCamPoint, lr, ur); + sgOSPlaneYMin.set(sgCamPoint, ur, ul); + sgOSPlaneYMax.set(sgCamPoint, ll, lr); + + if (flippedMatrix == true) { + sgOSPlaneXMin.neg(); + sgOSPlaneXMax.neg(); + sgOSPlaneYMin.neg(); + sgOSPlaneYMax.neg(); + } + + bool goodResult = false; + newFrustum[0] = 1e10; + newFrustum[1] = -1e10; + newFrustum[2] = 1e10; + newFrustum[3] = -1e10; + for (U32 i = 0; i < numWindings; i++) { + if (projectClipAndBoundWinding(windings[i], newFrustum)) + goodResult = true; + } + + if (goodResult == true) { + // good rect, build frustum and viewport + F64 minx = ((newFrustum[0] + 1.0f) / 2.0f) * (oldFrustum[1] - oldFrustum[0]) + oldFrustum[0]; + F64 maxx = ((newFrustum[1] + 1.0f) / 2.0f) * (oldFrustum[1] - oldFrustum[0]) + oldFrustum[0]; + F64 miny = ((newFrustum[2] + 1.0f) / 2.0f) * (oldFrustum[3] - oldFrustum[2]) + oldFrustum[2]; + F64 maxy = ((newFrustum[3] + 1.0f) / 2.0f) * (oldFrustum[3] - oldFrustum[2]) + oldFrustum[2]; + RectD bogus(minx, miny, (maxx - minx), (maxy - miny)); + newFrustum[0] = bogus.point.x; // left + newFrustum[1] = bogus.point.x + bogus.extent.x; // right + newFrustum[2] = bogus.point.y; // bottom + newFrustum[3] = bogus.point.y + bogus.extent.y; // top + + fixupViewport(oldFrustum, oldViewport, newFrustum, newViewport); + + return true; + } else { + // No portal visibility + 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); +} + diff --git a/sceneGraph/sgUtil.h b/sceneGraph/sgUtil.h new file mode 100644 index 0000000..28ac3c6 --- /dev/null +++ b/sceneGraph/sgUtil.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SGUTIL_H_ +#define _SGUTIL_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif + +class RectI; +class MatrixF; + +struct SGWinding +{ + Point3F points[32]; + U32 numPoints; +}; + +bool sgComputeNewFrustum(const F64* 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); + + +// 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); + +#endif // _H_SGUTIL_ diff --git a/sceneGraph/shadowVolumeBSP.cc b/sceneGraph/shadowVolumeBSP.cc new file mode 100644 index 0000000..46d3c21 --- /dev/null +++ b/sceneGraph/shadowVolumeBSP.cc @@ -0,0 +1,708 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/shadowVolumeBSP.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; + + 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->mType != LightInfo::Vector) + return; + + // build the poly + Point3F pointOffset = light->mDirection * 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, "Doh!"); + + // 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/sceneGraph/shadowVolumeBSP.h b/sceneGraph/shadowVolumeBSP.h new file mode 100644 index 0000000..2a51a56 --- /dev/null +++ b/sceneGraph/shadowVolumeBSP.h @@ -0,0 +1,140 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADOWVOLUMEBSP_H_ +#define _SHADOWVOLUMEBSP_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _DATACHUNKER_H_ +#include "Core/dataChunker.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "sceneGraph/lightManager.h" +#endif +#ifndef _INTERIOR_H_ +#include "interior/interior.h" +#endif + +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(); + + // 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));} + + // helpers + void buildPolyVolume(SVPoly *, LightInfo *); + SVPoly * copyPoly(SVPoly *); +}; + +#endif diff --git a/sceneGraph/windingClipper.cc b/sceneGraph/windingClipper.cc new file mode 100644 index 0000000..12d711c --- /dev/null +++ b/sceneGraph/windingClipper.cc @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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..b4237f5 --- /dev/null +++ b/sceneGraph/windingClipper.h @@ -0,0 +1,17 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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/shell/shellFancyArray.cc b/shell/shellFancyArray.cc new file mode 100644 index 0000000..6d33b95 --- /dev/null +++ b/shell/shellFancyArray.cc @@ -0,0 +1,2132 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "shell/shellFancyArray.h" +#include "gui/guiCanvas.h" +#include "console/consoleTypes.h" + +//------------------------------------------------------------------------------ +// +// VirtualScrollContentCtrl functions +// +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(VirtualScrollContentCtrl); + +//------------------------------------------------------------------------------ +VirtualScrollContentCtrl::VirtualScrollContentCtrl() : GuiScrollContentCtrl() +{ + mVirtualContent = NULL; +} + +//------------------------------------------------------------------------------ +bool VirtualScrollContentCtrl::onAdd() +{ + if ( !Parent::onAdd() ) + return( false ); + + GuiControl* control = new GuiControl(); + if ( !control->registerObject() ) + Con::errorf( ConsoleLogEntry::General, "Failed to add virtual control to VirtualScrollContentCtrl!" ); + addObject( control ); + + return( true ); +} + +//------------------------------------------------------------------------------ +void VirtualScrollContentCtrl::addObject( SimObject* obj ) +{ + // The virtual scroll control can accept only one content control. + GuiControl* control = dynamic_cast( obj ); + AssertFatal( control, "Object added to the VirtualScrollContentCtrl is not a GuiControl!" ); + + if ( mVirtualContent ) + mVirtualContent->deleteObject(); + Parent::addObject( control ); + mVirtualContent = control; + + // Pass the virtual content back through the line: + GuiControl* parent = getParent(); + if ( parent ) + { + AssertFatal( dynamic_cast( parent ), "VirtualScrollContentCtrl's parent is not a VirtualScrollCtrl!" ); + VirtualScrollCtrl* scrollCtrl = static_cast( parent ); + scrollCtrl->setVirtualContent( control ); + } +} + +//------------------------------------------------------------------------------ +void VirtualScrollContentCtrl::removeObject( SimObject* obj ) +{ + // If we are deleting the virtual content control, we need to let the "family" know:. + GuiControl* control = dynamic_cast( obj ); + if ( mVirtualContent == control ) + mVirtualContent = NULL; + + GuiControl* parent = getParent(); + if ( parent ) + { + AssertFatal( dynamic_cast( parent ), "VirtualScrollContentCtrl's parent is not a VirtualScrollCtrl!" ); + VirtualScrollCtrl* scrollCtrl = static_cast( parent ); + scrollCtrl->setVirtualContent( NULL ); + } + + Parent::removeObject( obj ); +} + +//------------------------------------------------------------------------------ +// +// VirtualScrollCtrl functions +// +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(VirtualScrollCtrl); + +//------------------------------------------------------------------------------ +VirtualScrollCtrl::VirtualScrollCtrl() : ShellScrollCtrl() +{ + // The virtual scroll control should not have a field background: + mFieldBase = StringTable->insert( "" ); +} + +//------------------------------------------------------------------------------ +void VirtualScrollCtrl::setVirtualContent( GuiControl* control ) +{ + GuiControl* parent = getParent(); + if ( parent ) + { + AssertFatal( dynamic_cast( parent ), "VirtualScrollCtrl's parent is not a ShellFancyArrayScrollCtrl!" ); + ShellFancyArrayScrollCtrl* arrayCtrl = static_cast( parent ); + arrayCtrl->setVirtualContent( control ); + } +} + +//------------------------------------------------------------------------------ +GuiControl* VirtualScrollCtrl::getVirtualContent() +{ + if ( mContentCtrl ) + { + VirtualScrollContentCtrl* scrollContentCtrl = static_cast( mContentCtrl ); + return( scrollContentCtrl->mVirtualContent ); + } + + return( NULL ); +} + +//------------------------------------------------------------------------------ +bool VirtualScrollCtrl::onAdd() +{ + // We have to bypass the GuiScrollCtrl::onAdd here: + if ( !GuiControl::onAdd() ) + return( false ); + + VirtualScrollContentCtrl* content = new VirtualScrollContentCtrl(); + if ( !content->registerObject() ) + Con::errorf( ConsoleLogEntry::General, "Failed to add virtual content control to VirtualScrollCtrl!" ); + addObject( content ); + + return( true ); +} + +//------------------------------------------------------------------------------ +void VirtualScrollCtrl::addObject( SimObject* obj ) +{ + VirtualScrollContentCtrl* contentCtrl = dynamic_cast( obj ); + if ( contentCtrl ) + { + if ( mContentCtrl ) + mContentCtrl->deleteObject(); + // We have to bypass GuiScrollCtrl::addObject here: + GuiControl::addObject( obj ); + mContentCtrl = (GuiScrollContentCtrl*) contentCtrl; + return; + } + AssertFatal( mContentCtrl, "ERROR - VirtualScrollCtrl has no content control!" ); + + mContentCtrl->addObject( obj ); + computeSizes(); +} + +//------------------------------------------------------------------------------ +// +// ShellFancyArray functions +// +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(ShellFancyArray); + +//------------------------------------------------------------------------------ +ShellFancyArray::ShellFancyArray() : GuiControl() +{ + dMemset( mBmpBounds, 0, sizeof( mBmpBounds ) ); + mHeaderBitmap = StringTable->insert( "gui/server_tabs" ); + mSortArrowBitmap = StringTable->insert( "gui/shll_sortarrow" ); + mBarBase = StringTable->insert( "gui/shll_bar" ); + + mTexHeader = NULL; + mTexSortArrow = NULL; + mTexCellSelected = NULL; + mTexCellRollover = NULL; + + mFieldBase = StringTable->insert( "" ); + mTexLeftTop = NULL; + mTexCenterTop = NULL; + mTexRightTop = NULL; + mTexLeftCenter = NULL; + mTexCenter = NULL; + mTexRightCenter = NULL; + mTexLeftBottom = NULL; + mTexCenterBottom = NULL; + mTexRightBottom = NULL; + + // Initialize object statics: + mActive = true; + mFixedHorizontal = false; + mColumnInfoList.clear(); + mNumColumns = 0; + mRowHeight = 20; // Default--can change the field + mHeaderHeight = 0; // This is sized automatically in onWake + mGlowOffset = 4; // This should be set based on the header bitmap + mMinColumnWidth = 2; // This gets set automatically in onWake + mStartScrollRgn.set( 0, 0 ); + + mFont = NULL; + mHeaderFont = NULL; + + mHeaderFontType = StringTable->insert( "" ); + mHeaderFontSize = 0; + + mDefaultCursor = NULL; + mResizeCursor = NULL; + mRepositionCursor = NULL; + + // Default color values: + mHeaderFontColor.set( 8, 19, 6 ); + mHeaderFontColorHL.set( 25, 68, 56 ); + mSeparatorColor.set( 192, 192, 192 ); + + mDrawCellSeparators = false; + mHeaderSort = true; + mAllowReposition = true; + mNoSelect = false; + + mNumRows = 0; + mSelectedRow = -1; + mMouseOverRow = -2; + mMouseOverColumn = -1; + mScrollPanePos.set( 0, 0 ); + + mColumnState = None; + mActiveColumn = 0; + mDragAnchor.set( 0, 0 ); + + mSortColumnKey = -1; + mSecondarySortColumnKey = -1; + mSortInc = true; + mSecondarySortInc = true; + + mAbsResizeLeftMargin = 0; + mAbsResizeRightMargin = 0; + mResizeFixedColumn = false; + mResizeColumnOrigSize = 0; + + mRepositionColumnTo = 0; + mRepositionCursorPos.set( 0, 0 ); +} + +//------------------------------------------------------------------------------ +bool ShellFancyArray::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Initialize cursors: + SimObject* obj = Sim::findObject( "DefaultCursor" ); + mDefaultCursor = dynamic_cast( obj ); + obj = Sim::findObject( "LeftRightCursor" ); + mResizeCursor = dynamic_cast( obj ); + obj = Sim::findObject( "GrabCursor" ); + mRepositionCursor = dynamic_cast( obj ); + + return true; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onRemove() +{ + mColumnInfoList.clear(); + mNumColumns = 0; + Parent::onRemove(); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::initPersistFields() +{ + Parent::initPersistFields(); + addField( "startScrollRegion", TypePoint2I, Offset( mStartScrollRgn, ShellFancyArray ) ); + addField( "headerBitmap", TypeString, Offset( mHeaderBitmap, ShellFancyArray ) ); + addField( "sortArrowBitmap", TypeString, Offset( mSortArrowBitmap, ShellFancyArray ) ); + addField( "fieldBase", TypeString, Offset( mFieldBase, ShellFancyArray ) ); + addField( "barBase", TypeString, Offset( mBarBase, ShellFancyArray ) ); + addField( "glowOffset", TypeS32, Offset( mGlowOffset, ShellFancyArray ) ); + addField( "rowHeight", TypeS32, Offset( mRowHeight, ShellFancyArray ) ); + addField( "headerFontType", TypeString, Offset( mHeaderFontType, ShellFancyArray ) ); + addField( "headerFontSize", TypeS32, Offset( mHeaderFontSize, ShellFancyArray ) ); + addField( "headerFontColor", TypeColorI, Offset( mHeaderFontColor, ShellFancyArray ) ); + addField( "headerFontColorHL", TypeColorI, Offset( mHeaderFontColorHL, ShellFancyArray ) ); + addField( "separatorColor", TypeColorI, Offset( mSeparatorColor, ShellFancyArray ) ); + addField( "drawSeparators", TypeBool, Offset( mDrawCellSeparators, ShellFancyArray ) ); + addField( "headerSort", TypeBool, Offset( mHeaderSort, ShellFancyArray ) ); + addField( "allowReposition", TypeBool, Offset( mAllowReposition, ShellFancyArray ) ); + addField( "noSelect", TypeBool, Offset( mNoSelect, ShellFancyArray ) ); +} + +//------------------------------------------------------------------------------ +static void cShellFancyArrayClearColumns( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArrayClearColumns!" ); + ShellFancyArray* array = static_cast( obj ); + array->clearColumns(); +} + +//------------------------------------------------------------------------------ +static S32 cShellFancyArrayGetNumColumns( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArrayClearColumns!" ); + ShellFancyArray* array = static_cast( obj ); + return array->getNumColumns(); +} + +//------------------------------------------------------------------------------ +static void cShellFancyArrayAddColumn( SimObject* obj, S32 argc, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArrayAddColumn!" ); + ShellFancyArray* array = static_cast( obj ); + array->addColumn( dAtoi( argv[2] ), argv[3], dAtoi( argv[4] ), dAtoi( argv[5] ), dAtoi( argv[6] ), ( argc == 8 ) ? argv[7] : NULL ); +} + +//------------------------------------------------------------------------------ +static void cShellFancyArrayAddRow( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArrayAddRow!" ); + ShellFancyArray* array = static_cast( obj ); + array->setNumRows( array->getNumRows() + 1 ); +} + +//------------------------------------------------------------------------------ +static void cShellFancyArraySetNumRows( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArraySetNumRows!" ); + ShellFancyArray* array = static_cast( obj ); + array->setNumRows( dAtoi( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static S32 cShellFancyArrayRowCount( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArrayRowCount!" ); + ShellFancyArray* array = static_cast( obj ); + return( array->getNumRows() ); +} + +//------------------------------------------------------------------------------ +static void cShellFancyArrayForceUpdate( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArrayForceUpdate!" ); + ShellFancyArray* array = static_cast( obj ); + array->updateList(); +} + +//------------------------------------------------------------------------------ +static void cShellFancyArrayClearList( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArrayClearList!" ); + ShellFancyArray* array = static_cast( obj ); + array->clearList(); +} + +//------------------------------------------------------------------------------ +static void cShellFancyArraySetSelectedRow( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArraySetSelectedRow!" ); + ShellFancyArray* array = static_cast( obj ); + array->selectCell( dAtoi( argv[2] ), 0 ); +} + +//------------------------------------------------------------------------------ +static void cShellFancyArraySetSortColumn( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArraySetSortColumn!" ); + ShellFancyArray* array = static_cast( obj ); + array->setSortColumnKey( dAtoi( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static void cShellFancyArraySetSortIncreasing( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArraySetSortIncreasing!" ); + ShellFancyArray* array = static_cast( obj ); + array->setSortInc( dAtob( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static void cSFASetSecondarySortColumn( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cSFASetSecondarySortColumn!" ); + ShellFancyArray* array = static_cast( obj ); + array->setSecondarySortColumnKey( dAtoi( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static void cSFASetSecondarySortIncreasing( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cSFASetSecondarySortIncreasing!" ); + ShellFancyArray* array = static_cast( obj ); + array->setSecondarySortInc( dAtob( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static S32 cShellFancyArrayGetSelectedRow( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Non-ShellFancyArray passed to cShellFancyArrayGetSelectedRow!" ); + ShellFancyArray* array = static_cast( obj ); + return( array->getSelectedRow() ); +} + +//------------------------------------------------------------------------------ +static S32 cShellFancyArrayGetColumnKey( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cShellFancyArrayGetColumnKey is not a ShellFancyArray!" ); + ShellFancyArray* array = static_cast( obj ); + return ( array->getColumnKey( dAtoi( argv[2] ) ) ); +} + +//------------------------------------------------------------------------------ +static S32 cShellFancyArrayGetColumnWidth( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cShellFancyArrayGetColumnWidth is not a ShellFancyArray!" ); + ShellFancyArray* array = static_cast( obj ); + return ( array->getColumnWidth( dAtoi( argv[2] ) ) ); +} + +//------------------------------------------------------------------------------ +static S32 cShellFancyArrayGetSortColumnKey( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cShellFancyArrayGetSortColumnKey is not a ShellFancyArray!" ); + ShellFancyArray* array = static_cast( obj ); + return ( array->getSortColumnKey() ); +} + +//------------------------------------------------------------------------------ +static bool cShellFancyArrayGetSortIncreasing( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cShellFancyArrayGetSortIncreasing is not a ShellFancyArray!" ); + ShellFancyArray* array = static_cast( obj ); + return ( array->getSortIncreasing() ); +} + +//------------------------------------------------------------------------------ +static S32 cSFAGetSecondarySortColumnKey( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cSFAGetSecondarySortColumnKey is not a ShellFancyArray!" ); + ShellFancyArray* array = static_cast( obj ); + return ( array->getSecondarySortColumnKey() ); +} + +//------------------------------------------------------------------------------ +static bool cSFAGetSecondarySortIncreasing( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cSFAGetSecondarySortIncreasing is not a ShellFancyArray!" ); + ShellFancyArray* array = static_cast( obj ); + return ( array->getSecondarySortIncreasing() ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::consoleInit() +{ + Con::addCommand( "ShellFancyArray", "clearColumns", cShellFancyArrayClearColumns, "array.clearColumns();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "getNumColumns", cShellFancyArrayGetNumColumns, "array.getNumColumns();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "addColumn", cShellFancyArrayAddColumn, "array.addColumn( key, name, defaultWidth, minWidth, maxWidth{, flags} );", 7, 8 ); + Con::addCommand( "ShellFancyArray", "addRow", cShellFancyArrayAddRow, "array.addRow();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "setNumRows", cShellFancyArraySetNumRows, "array.setNumRows( numRows );", 3, 3 ); + Con::addCommand( "ShellFancyArray", "rowCount", cShellFancyArrayRowCount, "array.rowCount();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "clearList", cShellFancyArrayClearList, "array.clearList();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "forceUpdate", cShellFancyArrayForceUpdate, "array.forceUpdate();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "setSelectedRow", cShellFancyArraySetSelectedRow, "array.setSelectedRow( row );", 3, 3 ); + Con::addCommand( "ShellFancyArray", "setSortColumn", cShellFancyArraySetSortColumn, "array.setSortColumn( key );", 3, 3 ); + Con::addCommand( "ShellFancyArray", "setSortIncreasing", cShellFancyArraySetSortIncreasing, "array.setSortIncreasing( );", 3, 3 ); + Con::addCommand( "ShellFancyArray", "setSecondarySortColumn", cSFASetSecondarySortColumn, "array.setSecondarySortColumn( key );", 3, 3 ); + Con::addCommand( "ShellFancyArray", "setSecondarySortIncreasing", cSFASetSecondarySortIncreasing, "array.setSecondarySortIncreasing( );", 3, 3 ); + Con::addCommand( "ShellFancyArray", "getSelectedRow", cShellFancyArrayGetSelectedRow, "array.getSelectedRow();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "getColumnKey", cShellFancyArrayGetColumnKey, "array.getColumnKey( index );", 3, 3 ); + Con::addCommand( "ShellFancyArray", "getColumnWidth", cShellFancyArrayGetColumnWidth, "array.getColumnWidth( index );", 3, 3 ); + Con::addCommand( "ShellFancyArray", "getSortColumnKey", cShellFancyArrayGetSortColumnKey, "array.getSortColumnKey();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "getSortIncreasing", cShellFancyArrayGetSortIncreasing, "array.getSortIncreasing();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "getSecondarySortColumnKey", cSFAGetSecondarySortColumnKey, "array.getSecondarySortColumnKey();", 2, 2 ); + Con::addCommand( "ShellFancyArray", "getSecondarySortIncreasing", cSFAGetSecondarySortIncreasing, "array.getSecondarySortIncreasing();", 2, 2 ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::clearColumns() +{ + mColumnInfoList.clear(); + mNumColumns = 0; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::addColumn( S32 key, const char* name, S32 defaultWidth, S32 minWidth, S32 maxWidth, const char* flags ) +{ + ColumnInfo newColumn; + newColumn.name = StringTable->insert( name, true ); + newColumn.key = key; + newColumn.minWidth = getMax( mMinColumnWidth, minWidth ); + newColumn.maxWidth = maxWidth; + newColumn.width = getMin( maxWidth, getMax( newColumn.minWidth, defaultWidth ) ); + newColumn.flags = 0; + if ( flags ) + { + if ( dStrstr( flags, "numeric" ) ) + newColumn.flags |= Column_Numeric; + else if ( dStrstr( flags, "icon" ) ) + newColumn.flags |= Column_Icon; + if ( dStrstr( flags, "center" ) ) + newColumn.flags |= Column_Center; + else if ( dStrstr( flags, "right" ) ) + newColumn.flags |= Column_Right; + } + + mColumnInfoList.push_back( newColumn ); + mNumColumns = mColumnInfoList.size(); + if ( mFixedHorizontal ) + mStartScrollRgn.x = mNumColumns; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::clearList() +{ + mSelectedRow = -1; + mMouseOverRow = -2; + setNumRows( 0 ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::updateList() +{ + // This function should be called whenever the contents of the + // array have changed and usually only has to call setNumRows + // and setUpdate. +} + +//------------------------------------------------------------------------------ +bool ShellFancyArray::onWake() +{ + if ( !Parent::onWake() ) + return false; + + // Get the font: + mFont = mProfile->mFont; + + // Get the header font: + if ( mHeaderFontType[0] ) + mHeaderFont = GFont::create( mHeaderFontType, mHeaderFontSize ); + + if ( !mHeaderFont ) + mHeaderFont = mFont; + + // Get the cell textures: + char buf[512]; + bool result; + if ( mHeaderBitmap[0] ) + { + dSprintf( buf, sizeof( buf ), "%s.png", mHeaderBitmap ); + mTexHeader = TextureHandle( buf, BitmapKeepTexture ); + result = createBitmapArray( mTexHeader.getBitmap(), mBmpBounds, StateCount, BmpCount ); + AssertFatal( result, "Failed to create the header bitmap array for the ShellFancyArray!" ); + mHeaderHeight = mBmpBounds[0].extent.y - ( 2 * mGlowOffset ); + mMinColumnWidth = mBmpBounds[StateCount * BmpLeft].extent.x + mBmpBounds[StateCount * BmpRight].extent.x - ( 2 * mGlowOffset ); + + // Set the minimum extents: + mMinExtent.y = mGlowOffset + mHeaderHeight; + resize( mBounds.point, mBounds.extent ); + } + + if ( mSortArrowBitmap[0] ) + { + dSprintf( buf, sizeof( buf ), "%s.png", mSortArrowBitmap ); + mTexSortArrow = TextureHandle( buf, BitmapTexture ); + } + + // Get the field: + if ( mFieldBase[0] ) + { + dSprintf( buf, sizeof( buf ), "%s_TL.png", mFieldBase ); + mTexLeftTop = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_TM.png", mFieldBase ); + mTexCenterTop = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_TR.png", mFieldBase ); + mTexRightTop = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_ML.png", mFieldBase ); + mTexLeftCenter = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_MM.png", mFieldBase ); + mTexCenter = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_MR.png", mFieldBase ); + mTexRightCenter = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_BL.png", mFieldBase ); + mTexLeftBottom = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_BM.png", mFieldBase ); + mTexCenterBottom = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_BR.png", mFieldBase ); + mTexRightBottom = TextureHandle( buf, BitmapTexture ); + } + + if ( mBarBase[0] ) + { + dSprintf( buf, sizeof( buf ), "%s_rol.png", mBarBase ); + mTexCellRollover = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_act.png", mBarBase ); + mTexCellSelected = TextureHandle( buf, BitmapTexture ); + } + + return true; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onSleep() +{ + Parent::onSleep(); + + mFont = NULL; + mHeaderFont = NULL; + + mTexHeader = NULL; + mTexSortArrow = NULL; + mTexLeftTop = NULL; + mTexCenterTop = NULL; + mTexRightTop = NULL; + mTexLeftCenter = NULL; + mTexCenter = NULL; + mTexRightCenter = NULL; + mTexLeftBottom = NULL; + mTexCenterBottom = NULL; + mTexRightBottom = NULL; + mTexCellRollover = NULL; + mTexCellSelected = NULL; +} + +//------------------------------------------------------------------------------ +bool ShellFancyArray::pointInColumn( bool inHeader, Point2I pt, S32 &column, bool &inResizeRgn, bool &resizeLeft ) +{ + S32 columnLimit = ( mNumColumns > mStartScrollRgn.x ) ? mStartScrollRgn.x : mNumColumns; + S32 startPos = 0, endPos = 0; + inResizeRgn = false; + + if ( inHeader ) + { + if ( pt.x < 0 || pt.y < 0 || pt.x >= mBounds.extent.x || pt.y >= ( mHeaderHeight + mGlowOffset ) ) + return false; + } + else + { + if ( pt.x < 0 || pt.y < mHeaderHeight || pt.x >= mBounds.extent.x || pt.y >= mBounds.extent.y ) + return false; + } + + for ( column = 0; column < columnLimit; column++ ) + { + endPos = startPos + mColumnInfoList[column].width; + if ( pt.x < endPos ) + { + if ( ( ( pt.x - startPos ) < 4 && column ) || ( ( endPos - pt.x ) < 4 + && ( !mFixedHorizontal || column != mNumColumns - 1 ) ) ) + inResizeRgn = true; + resizeLeft = pt.x < ( startPos + ( ( endPos - startPos ) / 2 ) ); + return true; + } + startPos = endPos; + } + + RectI columnScrollRect; + if ( getColumnScrollViewRect( columnScrollRect ) ) + { + startPos = columnScrollRect.point.x + mScrollPanePos.x; + for ( column = mStartScrollRgn.x; column < mColumnInfoList.size(); column++ ) + { + endPos = startPos + mColumnInfoList[column].width; + if ( pt.x < endPos ) + { + if ( ( ( pt.x - startPos ) < 4 && column ) || ( ( endPos - pt.x ) < 4 + && ( !mFixedHorizontal || column != mNumColumns - 1 ) ) ) + inResizeRgn = true; + resizeLeft = ( pt.x < ( startPos + ( ( endPos - startPos ) / 2 ) ) ); + return true; + } + startPos = endPos; + } + } + return false; +} + +//------------------------------------------------------------------------------ +bool ShellFancyArray::getScrollRect( RectI &rect ) +{ + bool hasHorizontalScroll = getColumnScrollViewRect( rect ); + rect.point.y = mHeaderHeight + mGlowOffset + ( mRowHeight * mStartScrollRgn.y ); + rect.extent.y = mBounds.extent.y - rect.point.y; + + return hasHorizontalScroll; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::setSortColumnKey( S32 newKey ) +{ + if ( mSortColumnKey == newKey ) + mSortInc = !mSortInc; + else + mSortColumnKey = newKey; + + sort(); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::setSecondarySortColumnKey( S32 newKey ) +{ + if ( mSecondarySortColumnKey == newKey ) + mSecondarySortInc = !mSecondarySortInc; + else + mSecondarySortColumnKey = ( newKey == mSortColumnKey ) ? -1 : newKey; + + sort(); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::sort() +{ +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::selectCell( S32 row, S32 column ) +{ + if ( row < 0 || row >= mNumRows ) + { + mSelectedRow = -1; + return; + } + + mSelectedRow = row; + onCellSelected( row, column ); + scrollSelectedRowVisible(); + setUpdate(); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::setNumRows( S32 numRows ) +{ + if ( mNumRows > numRows ) + { + // Need to scroll off the dead rows: + if ( numRows > mStartScrollRgn.y ) + mScrollPanePos.y += ( mNumRows - numRows ) * mRowHeight; + else + mScrollPanePos.y = 0; + + if ( mScrollPanePos.y > 0 ) + mScrollPanePos.y = 0; + } + + if ( numRows != mNumRows ) + { + mNumRows = numRows; + if ( mNumRows ) + scrollSelectedRowVisible(); + setUpdate(); + } +} + +//------------------------------------------------------------------------------ +S32 ShellFancyArray::getNoScrollWidth() +{ + S32 result = 0; + U32 colCount = getMin( mStartScrollRgn.x, mColumnInfoList.size() ); + for ( U32 i = 0; i < colCount; i++ ) + result += mColumnInfoList[i].width; + + return( result ); +} + +//------------------------------------------------------------------------------ +S32 ShellFancyArray::getColumnKey( S32 index ) +{ + if ( index < 0 || index >= mColumnInfoList.size() ) + return( -1 ); + + return( mColumnInfoList[index].key ); +} + +//------------------------------------------------------------------------------ +S32 ShellFancyArray::getColumnWidth( S32 index ) +{ + if ( index < 0 || index >= mColumnInfoList.size() ) + return( -1 ); + + return( mColumnInfoList[index].width ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::forceFillScrollRegion() +{ + if ( mStartScrollRgn.x >= mColumnInfoList.size() ) + return; + + RectI r; + if ( !getColumnScrollViewRect( r ) ) + return; + + S32 i, len = 0; + for ( i = mStartScrollRgn.x; i < mColumnInfoList.size(); i++ ) + len += mColumnInfoList[i].width; + + if ( len >= r.extent.x ) + return; + + S32 delta = ( r.extent.x - len ) / ( mColumnInfoList.size() - mStartScrollRgn.x ); + len = 0; + + for ( i = mStartScrollRgn.x; i < mColumnInfoList.size() - 1; i++ ) + { + mColumnInfoList[i].width += delta; + if ( mColumnInfoList[i].maxWidth < mColumnInfoList[i].width ) + mColumnInfoList[i].maxWidth = mColumnInfoList[i].width; + + len += mColumnInfoList[i].width; + } + + mColumnInfoList[i].width = r.extent.x - len; + if ( mColumnInfoList[i].maxWidth < mColumnInfoList[i].width ) + mColumnInfoList[i].maxWidth = mColumnInfoList[i].width; + if ( mColumnInfoList[i].minWidth > mColumnInfoList[i].width ) + mColumnInfoList[i].minWidth = mColumnInfoList[i].width; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onHeaderAction( S32 column ) +{ + setSortColumnKey( mColumnInfoList[column].key ); + Con::executef( this, 3, "onSetSortKey", Con::getIntArg( mSortColumnKey ), Con::getIntArg( mSortInc ) ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onSecondaryHeaderAction( S32 column ) +{ + setSecondarySortColumnKey( mColumnInfoList[column].key ); + Con::executef( this, 3, "onSetSecondarySortKey", Con::getIntArg( mSecondarySortColumnKey ), Con::getIntArg( mSecondarySortInc ) ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onCellSelected( S32 row, S32 column ) +{ + Con::executef( this, 3, "onSelect", Con::getIntArg( row ), Con::getIntArg( column ) ); + + // Call the console function: + if ( mConsoleCommand[0] ) + Con::evaluate( mConsoleCommand, false ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::scrollSelectedRowVisible() +{ + RectI r; + if ( !getRowScrollViewRect( r ) ) + return; + + // First, make sure we are within the acceptable range: + S32 totalLen = ( mNumRows - mStartScrollRgn.y ) * mRowHeight; + if ( totalLen > r.extent.y && ( ( mScrollPanePos.y + totalLen ) < r.extent.y ) ) + { + mScrollPanePos.y = r.extent.y - totalLen; + setUpdate(); + } + + if ( mSelectedRow < 0 || mSelectedRow >= mNumRows || mSelectedRow < mStartScrollRgn.y ) + return; + + S32 rowStartPos_y = r.point.y + mScrollPanePos.y + ( mRowHeight * ( mSelectedRow - mStartScrollRgn.y ) ); + S32 rowEndPos_y = rowStartPos_y + mRowHeight; + + if ( rowStartPos_y >= r.point.y && rowEndPos_y <= ( r.point.y + r.extent.y ) ) + return; + + // Position the scrollable row region such that the selected row is visible: + if ( rowStartPos_y < r.point.y ) + mScrollPanePos.y += ( r.point.y - rowStartPos_y ); + if ( rowEndPos_y > ( r.point.y + r.extent.y ) ) + mScrollPanePos.y += ( ( r.point.y + r.extent.y ) - rowEndPos_y ) + 1; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::computeFixedResizingVals() +{ + if ( mActiveColumn < 0 || mActiveColumn >= mNumColumns || mActiveColumn >= mStartScrollRgn.x ) + return; + + S32 i; + mAbsResizeLeftMargin = 0; + for ( i = 0; i < mActiveColumn; i++ ) + mAbsResizeLeftMargin += mColumnInfoList[i].width; + mAbsResizeLeftMargin += mColumnInfoList[mActiveColumn].minWidth; + + mAbsResizeRightMargin = mBounds.extent.x; + S32 columnLimit = mNumColumns - 1; + if ( mStartScrollRgn.x < mNumColumns ) + { + // Subtract 100 if we have a scroll region. + // ( the minimum scroll region size ) + mAbsResizeRightMargin -= 100; + columnLimit = mStartScrollRgn.x - 1; + } + + for ( i = columnLimit; i < mActiveColumn; i-- ) + mAbsResizeRightMargin -= mColumnInfoList[i].minWidth; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::resizeFixedColumn( const GuiEvent &event ) +{ + if ( mActiveColumn < 0 || mActiveColumn >= mNumColumns || mActiveColumn > mStartScrollRgn.x ) + return; + + Point2I pt = globalToLocalCoord( event.mousePoint ); + + if ( pt.x < mAbsResizeLeftMargin ) + pt.x = mAbsResizeLeftMargin; + + if ( pt.x > mAbsResizeRightMargin ) + pt.x = mAbsResizeRightMargin; + + S32 widthDelta = mColumnInfoList[mActiveColumn].width; + mColumnInfoList[mActiveColumn].width = pt.x - mAbsResizeLeftMargin + mColumnInfoList[mActiveColumn].minWidth; + if ( mColumnInfoList[mActiveColumn].width > mColumnInfoList[mActiveColumn].maxWidth ) + mColumnInfoList[mActiveColumn].width = mColumnInfoList[mActiveColumn].maxWidth; + widthDelta = mColumnInfoList[mActiveColumn].width - widthDelta; + + ShellFancyArrayScrollCtrl* daddy = static_cast( getParent() ); + if ( daddy ) + daddy->positionChildren(); + + setUpdate(); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::resizeScrollColumn( const GuiEvent &event ) +{ + if ( mActiveColumn < mStartScrollRgn.x || mActiveColumn >= mNumColumns ) + return; + + S32 deltaX = event.mousePoint.x - mDragAnchor.x; + mColumnInfoList[mActiveColumn].width += ( deltaX - mColumnInfoList[mActiveColumn].width + mResizeColumnOrigSize ); + if ( mColumnInfoList[mActiveColumn].width < mColumnInfoList[mActiveColumn].minWidth ) + mColumnInfoList[mActiveColumn].width = mColumnInfoList[mActiveColumn].minWidth; + if ( mColumnInfoList[mActiveColumn].width > mColumnInfoList[mActiveColumn].maxWidth ) + mColumnInfoList[mActiveColumn].width = mColumnInfoList[mActiveColumn].maxWidth; + + ShellFancyArrayScrollCtrl* daddy = static_cast( getParent() ); + if ( daddy ) + { + if ( mActiveColumn == mNumColumns - 1 ) + { + RectI r; + if ( getScrollRect( r ) ) + mScrollPanePos.x = r.extent.x - getScrollExtent().x; + } + + daddy->positionChildren(); + } + + setUpdate(); +} + +//------------------------------------------------------------------------------ +Point2I ShellFancyArray::getScrollExtent() +{ + Point2I extent( 0, 0 ); + if ( mNumRows > mStartScrollRgn.y ) + extent.y = ( mNumRows - mStartScrollRgn.y ) * mRowHeight; + + if ( mNumColumns > mStartScrollRgn.x ) + { + for ( S32 i = mStartScrollRgn.x; i < mNumColumns; i++ ) + extent.x += mColumnInfoList[i].width; + } + + return extent; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onMouseDown( const GuiEvent& event ) +{ + if ( !mActive || !mVisible || !mAwake ) + return; + + if ( mProfile->mCanKeyFocus ) + setFirstResponder(); + + // Lock the mouse to this control: + mouseLock(); + + Point2I pt = globalToLocalCoord( event.mousePoint ); + S32 prevSelected, column; + bool inResizeRgn, resizeLeft; + + if ( pt.y > ( mHeaderHeight + mGlowOffset ) ) + { + if ( mNoSelect ) + return; + + // In browser region, so select the row: + if ( pointInColumn( false, pt, column, inResizeRgn, resizeLeft ) ) + { + S32 rowPos = ( pt.y - ( mHeaderHeight + mGlowOffset ) ) / mRowHeight; + if ( rowPos < mStartScrollRgn.y ) + { + prevSelected = mSelectedRow; + selectCell( rowPos, column ); + + // Test for double-click on same cell: + if ( ( event.mouseClickCount > 1 ) && ( prevSelected == mSelectedRow ) && mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand ); + return; + } + + RectI rowScrollRect; + if ( getRowScrollViewRect( rowScrollRect ) ) + { + rowPos = ( ( pt.y - rowScrollRect.point.y - mScrollPanePos.y ) / mRowHeight ) + mStartScrollRgn.y; + if ( rowPos < mNumRows ) + { + prevSelected = mSelectedRow; + selectCell( rowPos, column ); + + // Test for double-click on same cell: + if ( ( event.mouseClickCount > 1 ) && ( prevSelected == mSelectedRow ) && mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand ); + } + } + } + return; + } + + if ( pointInColumn( true, pt, column, inResizeRgn, resizeLeft ) ) + { + if ( inResizeRgn ) + { + // Start resizing: + mColumnState = Resizing; + mActiveColumn = column; + mMouseOverColumn = -1; + if ( resizeLeft ) + mActiveColumn--; + mResizeFixedColumn = mActiveColumn < mStartScrollRgn.x; + if ( mResizeFixedColumn ) + computeFixedResizingVals(); + else + { + mDragAnchor = event.mousePoint; + mResizeColumnOrigSize = mColumnInfoList[mActiveColumn].width; + } + } + else if ( mHeaderSort ) + { + // Select the column header: + mColumnState = Sorting; + mActiveColumn = column; + mDragAnchor = event.mousePoint; + determineRepositionColumn( pt ); + + if ( mProfile->mSoundButtonDown ) + { + F32 pan = ( F32( event.mousePoint.x ) / F32( Canvas->mBounds.extent.x ) * 2.0f - 1.0f ) * 0.8f; + AUDIOHANDLE handle = alxCreateSource( mProfile->mSoundButtonDown ); + alxSourcef( handle, AL_PAN, pan ); + alxPlay( handle ); + } + + setUpdate(); + } + } +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onMouseUp( const GuiEvent& event ) +{ + if ( !mActive || !mVisible || !mAwake ) + return; + + Point2I pt = globalToLocalCoord( event.mousePoint ); + + switch ( mColumnState ) + { + case Sorting: + { + S32 column; + bool inResizeRgn, resizeLeft; + + if ( pt.y <= ( mHeaderHeight + mGlowOffset ) && pointInColumn( true, pt, column, inResizeRgn, resizeLeft ) ) + { + if ( column == mActiveColumn ) + onHeaderAction( column ); + } + break; + } + + case Repositioning: + { + if ( mActiveColumn == mRepositionColumnTo || ( mActiveColumn + 1 ) == mRepositionColumnTo ) + break; + + ColumnInfo temp = mColumnInfoList[mActiveColumn]; + S32 insertPos = mRepositionColumnTo; + if ( mActiveColumn < mRepositionColumnTo ) + insertPos--; + + mColumnInfoList.erase( mActiveColumn ); + mColumnInfoList.insert( insertPos ); + mColumnInfoList[insertPos] = temp; + + // Call the console function: + Con::executef( this, 3, "onColumnRepositioned", Con::getIntArg( mActiveColumn ), Con::getIntArg( mRepositionColumnTo ) ); + break; + } + + case Resizing: + // Call the console function: + Con::executef( this, 4, "onColumnResize", Con::getIntArg( mActiveColumn ), + Con::getIntArg( mColumnInfoList[mActiveColumn].width ), + Con::getIntArg( mColumnInfoList[mActiveColumn].key ) ); + break; + } + + // Unlock the mouse: + setUpdate(); + mouseUnlock(); + Canvas->setCursor( mDefaultCursor ); + mColumnState = None; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onMouseMove( const GuiEvent& event ) +{ + Point2I pt = globalToLocalCoord( event.mousePoint ); + + if ( pt.y < ( mGlowOffset + mHeaderHeight ) ) + { + GuiCursor* newCursor = mDefaultCursor; + S32 column; + bool inResizeRgn, unused2; + if ( pointInColumn( true, pt, column, inResizeRgn, unused2 ) ) + { + if ( inResizeRgn && mResizeCursor ) + { + setMouseOverColumn( -1 ); + newCursor = mResizeCursor; + } + else if ( mHeaderSort ) + setMouseOverColumn( column, event.mousePoint.x ); + } + setMouseOverRow( -1 ); // -1 is the code for the header + Canvas->setCursor( newCursor ); + return; + } + else + { + Canvas->setCursor( mDefaultCursor ); + setMouseOverColumn( -1 ); + S32 row = ( pt.y - mGlowOffset - mHeaderHeight ) / mRowHeight; + if ( row < mStartScrollRgn.y ) + { + setMouseOverRow( row ); + return; + } + + RectI rowScrollRect; + if ( getRowScrollViewRect( rowScrollRect ) ) + { + row = ( ( pt.y - rowScrollRect.point.y - mScrollPanePos.y ) / mRowHeight ) + mStartScrollRgn.y; + if ( row < mNumRows ) + { + setMouseOverRow( row ); + return; + } + } + } + + setMouseOverRow( -2 ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onMouseDragged( const GuiEvent& event ) +{ + if ( mColumnState == None ) + return; + + if ( mColumnState == Resizing ) + { + if ( mResizeFixedColumn ) + resizeFixedColumn( event ); + else + resizeScrollColumn( event ); + } + else + { + if ( !mAllowReposition || mActiveColumn < mStartScrollRgn.x || ( mColumnInfoList.size() - mStartScrollRgn.x ) <= 1 ) + mColumnState = Sorting; + else + mColumnState = ( mAbs( event.mousePoint.x - mDragAnchor.x ) > 4 ) ? Repositioning : Sorting; + + if ( mColumnState == Repositioning ) + { + if ( mRepositionCursor ) + Canvas->setCursor( mRepositionCursor ); + RectI columnRect; + if ( getColumnScrollViewRect( columnRect ) ) + { + // Scroll the columns for positioning if necessary: + columnRect.point = localToGlobalCoord( columnRect.point ); + Point2I newMousePt = event.mousePoint; + + if ( event.mousePoint.x < columnRect.point.x ) + { + // Scroll right: + newMousePt.set( columnRect.point.x, event.mousePoint.y ); + + mScrollPanePos.x += ( columnRect.point.x - event.mousePoint.x ); + if ( mScrollPanePos.x > 0 ) + { + mScrollPanePos.x = 0; + ShellFancyArrayScrollCtrl* daddy = static_cast( getParent() ); + if ( daddy ) + daddy->positionChildren(); + } + GuiCanvas* root = getRoot(); + if ( root ) + root->setCursorPos( newMousePt ); + setUpdate(); + } + else if ( event.mousePoint.x > ( columnRect.point.x + columnRect.extent.x ) ) + { + // Scroll left: + newMousePt.set( columnRect.point.x + columnRect.extent.x, event.mousePoint.y ); + + mScrollPanePos.x += ( columnRect.point.x + columnRect.extent.x - event.mousePoint.x ); + if ( columnRect.extent.x > ( getScrollExtent().x + mScrollPanePos.x ) ) + { + mScrollPanePos.x = columnRect.extent.x - getScrollExtent().x; + ShellFancyArrayScrollCtrl* daddy = static_cast( getParent() ); + if ( daddy ) + daddy->positionChildren(); + } + GuiCanvas* root = getRoot(); + if ( root ) + root->setCursorPos( newMousePt ); + setUpdate(); + } + + newMousePt = globalToLocalCoord( newMousePt ); + determineRepositionColumn( newMousePt ); + } + } + } +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onMouseEnter( const GuiEvent &/*event*/ ) +{ + // Change the cursor to the appropriate one: + if ( mColumnState == None ) + return; + if ( mColumnState == Resizing && mResizeCursor ) + Canvas->setCursor( mResizeCursor ); + if ( mColumnState == Repositioning && mRepositionCursor ) + Canvas->setCursor( mRepositionCursor ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onMouseLeave( const GuiEvent &/*event*/ ) +{ + // Restore the default cursor: + Canvas->setCursor( mDefaultCursor ); + setMouseOverRow( -2 ); + setMouseOverColumn( -1 ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onRightMouseDown( const GuiEvent &event ) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + Parent::onRightMouseDown( event ); + + // Ignore if left mouse button is down: + if ( mColumnState != None ) + return; + + Point2I pt = globalToLocalCoord( event.mousePoint ); + S32 column, row; + bool inResizeRgn, resizeLeft; + if ( pt.y <= ( mHeaderHeight + mGlowOffset ) ) + { +#if 0 + // Not yet ready for prime time... + if ( mHeaderSort ) + { + if ( pointInColumn( true, pt, column, inResizeRgn, resizeLeft ) ) + { + if ( !inResizeRgn ) + { + // Set the secondary sort column: + mColumnState = SecondarySorting; + mActiveColumn = column; + + if ( mProfile->mSoundButtonDown ) + { + F32 pan = ( F32( event.mousePoint.x ) / F32( Canvas->mBounds.extent.x ) * 2.0f - 1.0f ) * 0.8f; + AUDIOHANDLE handle = alxCreateSource( mProfile->mSoundButtonDown ); + alxSourcef( handle, AL_PAN, pan ); + alxPlay( handle ); + } + } + } + } +#endif + + return; + } + + // In browser region, so find the hit row: + if ( pointInColumn( false, pt, column, inResizeRgn, resizeLeft ) ) + { + row = ( pt.y - ( mHeaderHeight + mGlowOffset ) ) / mRowHeight; + if ( row >= mStartScrollRgn.y ) + { + RectI rowScrollRect; + if ( getRowScrollViewRect( rowScrollRect ) ) + { + row = ( ( pt.y - rowScrollRect.point.y - mScrollPanePos.y ) / mRowHeight ) + mStartScrollRgn.y; + if ( row >= mNumRows ) + return; // Didn't actually hit a row... + } + } + } + + // Pass it to the console: + char buf[32]; + dSprintf( buf, sizeof( buf ), "%d %d", event.mousePoint.x, event.mousePoint.y ); + Con::executef( this, 4, "onRightMouseDown", Con::getIntArg( column ), Con::getIntArg( row ), buf ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onRightMouseUp( const GuiEvent &event ) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + if ( mColumnState == SecondarySorting ) + { + Point2I pt = globalToLocalCoord( event.mousePoint ); + S32 column; + bool inResizeRgn, resizeLeft; + if ( pt.y <= ( mHeaderHeight + mGlowOffset ) + && pointInColumn( true, pt, column, inResizeRgn, resizeLeft ) ) + { + if ( column == mActiveColumn ) + onSecondaryHeaderAction( column ); + } + } + + mColumnState = None; +} + +//------------------------------------------------------------------------------ +bool ShellFancyArray::onMouseWheelUp( const GuiEvent &event ) +{ + if ( !mVisible || !mAwake ) + return( false ); + + // scroll up a row... + if ( mScrollPanePos.y + mRowHeight > 0 ) + mScrollPanePos.y = 0; + else + mScrollPanePos.y += mRowHeight; + + // update the mouse over row... + onMouseMove( event ); + return( true ); +} + +//------------------------------------------------------------------------------ +bool ShellFancyArray::onMouseWheelDown( const GuiEvent &event ) +{ + if ( !mVisible || !mAwake ) + return( false ); + + RectI r; + if ( !getRowScrollViewRect( r ) ) + return( true ); + + S32 minPos = getMin( r.extent.y - ( ( mNumRows - mStartScrollRgn.y ) * mRowHeight ), 0 ); + + // scroll down a row... + if ( mScrollPanePos.y - mRowHeight < minPos ) + mScrollPanePos.y = minPos; + else + mScrollPanePos.y -= mRowHeight; + + // update the mouse over row... + onMouseMove( event ); + return( true ); +} + +//------------------------------------------------------------------------------ +bool ShellFancyArray::onKeyDown( const GuiEvent& event ) +{ + if ( !mVisible || !mActive || !mAwake ) + return true; + + if ( mSelectedRow < 0 ) + return true; + + if ( event.modifier == 0 ) + { + switch ( event.keyCode ) + { + case KEY_RETURN: + if ( mSelectedRow > 0 && mAltConsoleCommand[0] ) + { + Con::evaluate( mAltConsoleCommand, false ); + return true; + } + break; + + case KEY_UP: + if ( mSelectedRow > 0 ) + selectCell( mSelectedRow - 1, 0 ); + return true; + + case KEY_DOWN: + if ( mSelectedRow < ( mNumRows - 1 ) ) + selectCell( mSelectedRow + 1, 0 ); + return true; + } + } + + // Not processed, so pass to parent: + return Parent::onKeyDown( event ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::resize( const Point2I &newPos, const Point2I &newExtent ) +{ + Parent::resize( newPos, newExtent ); + + if ( mFixedHorizontal && mNumColumns > 0 ) + { + // Fit the columns to the new width: + S32 i, width = 0; + S32 realWidth = newExtent.x - mGlowOffset; + for ( i = 0; i < mNumColumns; i++ ) + width += mColumnInfoList[i].width; + + if ( width == realWidth ) + return; + + S32 delta = realWidth - width; + if ( delta > 0 ) + { + // Just add the leftovers to the last column: + mColumnInfoList[mNumColumns - 1].width += delta; + if ( mColumnInfoList[mNumColumns - 1].maxWidth < mColumnInfoList[mNumColumns - 1].width ) + mColumnInfoList[mNumColumns - 1].maxWidth = mColumnInfoList[mNumColumns - 1].width; + } + else + { + // Subtract width to make the columns fit, but don't make + // columns smaller than their minimum widths. + i = mNumColumns - 1; + while ( i >= 0 ) + { + mColumnInfoList[i].width += delta; + if ( mColumnInfoList[i].width < mColumnInfoList[i].minWidth ) + { + delta = mColumnInfoList[i].width - mColumnInfoList[i].minWidth; + mColumnInfoList[i].width = mColumnInfoList[i].minWidth; + } + else + break; + i--; + } + } + } +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onRenderColumnHeader( Point2I offset, RectI clipRect, S32 column, bool mouseOver ) +{ + ColumnInfo* ci = &mColumnInfoList[column]; + + bool realMouseOver = mHeaderSort ? mouseOver : false; + + // Draw the button-like background: + dglClearBitmapModulation(); + RectI drawRect; + drawRect.point = offset - Point2I( mGlowOffset, mGlowOffset ); + U32 state = realMouseOver ? ( ( ( mColumnState == Sorting || mColumnState == Repositioning ) && mActiveColumn == column ) ? StatePressed : StateRollover ) : StateNormal; + + // Draw the left edge: + U32 bitmap = BmpLeft * StateCount + state; + dglDrawBitmapSR( mTexHeader, drawRect.point, mBmpBounds[bitmap] ); + + // Draw the center section: + drawRect.point.x += mBmpBounds[bitmap].extent.x; + drawRect.extent.x = ci->width - mBmpBounds[bitmap].extent.x - mBmpBounds[BmpRight * StateCount].extent.x + ( 2 * mGlowOffset ) - 1; + drawRect.extent.y = mHeaderHeight + ( 2 * mGlowOffset ); + if ( drawRect.extent.x > 0 ) + { + bitmap = BmpCenter * StateCount + state; + dglDrawBitmapStretchSR( mTexHeader, drawRect, mBmpBounds[bitmap] ); + } + + // Draw the right edge: + drawRect.point.x += drawRect.extent.x; + bitmap = BmpRight * StateCount + state; + dglDrawBitmapSR( mTexHeader, drawRect.point, mBmpBounds[bitmap] ); + + // If this is the sort column, draw the sort arrow: + bool arrowDrawn = false; + if ( mHeaderSort && mTexSortArrow + && ( ci->key == mSortColumnKey || ci->key == mSecondarySortColumnKey ) ) + { + bool flip; + if ( ci->key == mSortColumnKey ) + flip = mSortInc; + else + { + dglSetBitmapModulation( ColorF( 1.0f, 1.0f, 1.0f, 0.5f ) ); + flip = mSecondarySortInc; + } + drawRect.point.x = offset.x + ci->width - mTexSortArrow.getWidth() - 6; + drawRect.point.y = ( mHeaderHeight > mTexSortArrow.getHeight() ) ? offset.y + ( ( mHeaderHeight - mTexSortArrow.getHeight() ) / 2 ): offset.y; + dglDrawBitmap( mTexSortArrow, drawRect.point, ( flip ? GFlip_Y : GFlip_None ) ); + arrowDrawn = true; + } + + // Draw the text: + S32 textWidth = mHeaderFont->getStrWidth( ci->name ); + drawRect.point.x = offset.x + getMax( 4, ( ci->width - textWidth ) / 2 ); + drawRect.point.y = offset.y + ( ( mHeaderHeight - mHeaderFont->getHeight() ) / 2 ); + drawRect.extent.x = ci->width + ( offset.x - drawRect.point.x ) - ( arrowDrawn ? mTexSortArrow.getWidth() + 6 : 4 ); + drawRect.extent.y = mHeaderHeight; + if ( drawRect.extent.x > 0 ) + { + RectI textRect( drawRect ); + if ( textRect.intersect( clipRect ) ) + { + dglSetClipRect( textRect ); + dglSetBitmapModulation( realMouseOver ? mHeaderFontColorHL : mHeaderFontColor ); + dglDrawText( mHeaderFont, drawRect.point, ci->name ); + } + } +} + +//------------------------------------------------------------------------------ +// Most likely you will want to override this function since you probably +// want something drawn in the cells...left this open so you can use text, bitmaps, etc. +void ShellFancyArray::onRenderCell( Point2I offset, Point2I cell, bool selected, bool mouseOver ) +{ + ColumnInfo* ci = &mColumnInfoList[cell.x]; + + // If mouse is over or row is selected, draw the background: + dglClearBitmapModulation(); + RectI drawRect( offset, Point2I( ci->width, mRowHeight ) ); + if ( selected ) + dglDrawBitmapStretch( mTexCellSelected, drawRect ); + else if ( mouseOver ) + dglDrawBitmapStretch( mTexCellRollover, drawRect ); + + // Draw cell contents... + + // Draw the cell limit: + if ( mDrawCellSeparators && mSeparatorColor ) + { + Point2I leftPt( offset.x, offset.y + mRowHeight ); + Point2I rightPt( offset.x + ci->width, leftPt.y ); + dglDrawLine( leftPt, rightPt, mSeparatorColor ); + } +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::onRender( Point2I offset, const RectI &updateRect, GuiControl* /*firstResponder*/ ) +{ + RectI clipRect( updateRect ); + + // Draw the background field: + if ( mTexLeftTop && mTexCenterTop && mTexRightTop && mTexLeftCenter && mTexCenter && mTexRightCenter + && mTexLeftBottom && mTexCenterBottom && mTexRightBottom ) + { + RectI drawRect; + dglClearBitmapModulation(); + + U32 stretchWidth = mBounds.extent.x - mTexLeftTop.getWidth() - mTexRightTop.getWidth() - mGlowOffset; + U32 topEdgeHeight = mBounds.extent.y - mHeaderHeight - mGlowOffset - mTexLeftBottom.getHeight(); + if ( topEdgeHeight > mTexLeftTop.getHeight() ) + topEdgeHeight = mTexLeftTop.getHeight(); + + // Draw upper left corner: + drawRect.point = offset + Point2I( mGlowOffset, mHeaderHeight + mGlowOffset ); + drawRect.extent.x = mTexLeftTop.getWidth(); + drawRect.extent.y = topEdgeHeight; + dglDrawBitmapStretch( mTexLeftTop, drawRect ); + + // Draw upper center edge: + drawRect.point.x += drawRect.extent.x; + drawRect.extent.x = stretchWidth; + if ( drawRect.extent.x > 0 ) + dglDrawBitmapStretch( mTexCenterTop, drawRect ); + + // Draw upper right corner: + drawRect.point.x += drawRect.extent.x; + drawRect.extent.x = mTexRightTop.getWidth(); + dglDrawBitmapStretch( mTexRightTop, drawRect ); + + drawRect.point.x = offset.x + mGlowOffset; + drawRect.point.y += drawRect.extent.y; + drawRect.extent.y = mBounds.extent.y - mHeaderHeight - mGlowOffset - topEdgeHeight - mTexLeftBottom.getHeight(); + if ( drawRect.extent.y > 0 ) + { + // Draw center left edge: + drawRect.extent.x = mTexLeftCenter.getWidth(); + dglDrawBitmapStretch( mTexLeftCenter, drawRect ); + + // Draw center: + drawRect.point.x += drawRect.extent.x; + drawRect.extent.x = stretchWidth; + if ( drawRect.extent.x > 0 ) + dglDrawBitmapStretch( mTexCenter, drawRect ); + + // Draw center right edge: + drawRect.point.x += drawRect.extent.x; + drawRect.extent.x = mTexRightCenter.getWidth(); + dglDrawBitmapStretch( mTexRightCenter, drawRect ); + } + + // Draw bottom left corner: + drawRect.point.x = offset.x + mGlowOffset; + drawRect.point.y += drawRect.extent.y; + dglDrawBitmap( mTexLeftBottom, drawRect.point ); + + // Draw bottom center edge: + drawRect.point.x += mTexLeftBottom.getWidth(); + drawRect.extent.x = stretchWidth; + drawRect.extent.y = mTexCenterBottom.getHeight(); + if ( drawRect.extent.x > 0 ) + dglDrawBitmapStretch( mTexCenterBottom, drawRect ); + + // Draw bottom right corner: + drawRect.point.x += drawRect.extent.x; + dglDrawBitmap( mTexRightBottom, drawRect.point ); + } + + // First draw the non-scrollable columns: + S32 columnLimit = ( mNumColumns > mStartScrollRgn.x ) ? mStartScrollRgn.x : mNumColumns; + S32 colStartPos_x = offset.x + mGlowOffset; + S32 colStartPos_y = offset.y + mGlowOffset; + RectI rowScrollRect; + bool hasRowScrollRect = getRowScrollViewRect( rowScrollRect ); + rowScrollRect.point += offset; + + S32 column; + for ( column = 0; column < columnLimit; column++ ) + { + dglSetClipRect( clipRect ); + drawColumn( Point2I( colStartPos_x, colStartPos_y ), clipRect, hasRowScrollRect, rowScrollRect, column ); + colStartPos_x += mColumnInfoList[column].width; + } + + // Now draw the columns within the scroll range: + RectI columnScrollRect; + if ( getColumnScrollViewRect( columnScrollRect ) ) + { + columnScrollRect.point += offset; + + colStartPos_x = columnScrollRect.point.x + mScrollPanePos.x; + + if ( columnScrollRect.intersect( clipRect ) ) + { + for ( column = mStartScrollRgn.x; column < mColumnInfoList.size(); column++ ) + { + dglSetClipRect( columnScrollRect ); + drawColumn( Point2I( colStartPos_x, colStartPos_y ), columnScrollRect, hasRowScrollRect, rowScrollRect, column ); + colStartPos_x += mColumnInfoList[column].width; + } + } + } +} + +//------------------------------------------------------------------------------ +S32 ShellFancyArray::findColumn( S32 key ) +{ + for ( S32 col = 0; col < mNumColumns; col++ ) + { + if ( mColumnInfoList[col].key == key ) + return( col ); + } + + return( -1 ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::drawColumn( Point2I offset, RectI clipRect, bool hasRowScrollRect, RectI rowScrollRect, S32 column ) +{ + S32 colStartPos_x = offset.x; + S32 colEndPos_x = offset.x + mColumnInfoList[column].width; + + // Draw the header first: + RectI headerRect( colStartPos_x - mGlowOffset, offset.y - mGlowOffset, mColumnInfoList[column].width + ( 2 * mGlowOffset ) - 1, mHeaderHeight + ( 2 * mGlowOffset ) ); + if ( headerRect.intersect( clipRect ) ) + { + dglSetClipRect( headerRect ); + bool mouseOver = ( ( mMouseOverRow == -1 ) && ( mMouseOverColumn == column ) ); + onRenderColumnHeader( Point2I( colStartPos_x, offset.y ), headerRect, column, mouseOver ); + } + + // Now any non-scrollable rows: + S32 rowLimit = ( mNumRows > mStartScrollRgn.y ) ? mStartScrollRgn.y : mNumRows; + S32 rowStartPos_y = offset.y + mHeaderHeight + mGlowOffset; + S32 rowEndPos_y = rowStartPos_y + mRowHeight; + S32 row; + for ( row = 0; row < rowLimit; row++ ) + { + RectI rowRect( colStartPos_x, rowStartPos_y, colEndPos_x - colStartPos_x - 1, rowEndPos_y - rowStartPos_y ); + if ( rowRect.intersect( clipRect ) ) + { + dglSetClipRect( rowRect ); + onRenderCell( Point2I( colStartPos_x, rowStartPos_y ), Point2I( column, row ), ( row == mSelectedRow ), ( row == mMouseOverRow ) ); + } + rowStartPos_y += mRowHeight; + rowEndPos_y += mRowHeight; + } + + // Finally, draw all of the rows in the scroll range: + if ( hasRowScrollRect ) + { + RectI scrollRect = rowScrollRect; + if ( scrollRect.intersect( clipRect ) ) + { + dglSetClipRect( scrollRect ); + + rowStartPos_y = mScrollPanePos.y + rowScrollRect.point.y; + rowEndPos_y = rowStartPos_y + mRowHeight; + for ( row = mStartScrollRgn.y; row < mNumRows; row++ ) + { + RectI cellRect( colStartPos_x, rowStartPos_y, colEndPos_x - colStartPos_x - 1, rowEndPos_y - rowStartPos_y ); + if ( cellRect.intersect( scrollRect ) ) + { + dglSetClipRect( cellRect ); + onRenderCell( Point2I( colStartPos_x, rowStartPos_y ), Point2I( column, row ), ( row == mSelectedRow ), ( row == mMouseOverRow ) ); + } + rowStartPos_y += mRowHeight; + rowEndPos_y += mRowHeight; + } + } + } + + // Draw the column separators: + if ( mDrawCellSeparators && mSeparatorColor ) + { + dglSetClipRect( clipRect ); + Point2I lineTop( offset.x + mColumnInfoList[column].width - 1, offset.y + mHeaderHeight ); + Point2I lineBottom( lineTop.x, lineTop.y + mBounds.extent.y ); + dglDrawLine( lineTop, lineBottom, mSeparatorColor ); + } +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::determineRepositionColumn( Point2I pt ) +{ + RectI columnScrollRect; + getColumnScrollViewRect( columnScrollRect ); + + S32 startPos = columnScrollRect.point.x + mScrollPanePos.x; + S32 endPos; + mRepositionColumnTo = mNumColumns; + for ( S32 column = mStartScrollRgn.x; column < mNumColumns; column++ ) + { + endPos = startPos + mColumnInfoList[column].width; + if ( pt.x < endPos ) + { + mRepositionColumnTo = column; + if ( pt.x > ( startPos + ( ( endPos - startPos ) / 2 ) ) ) + { + mRepositionColumnTo++; + startPos = endPos; + } + break; + } + startPos = endPos; + } + + mRepositionCursorPos.set( startPos, mHeaderHeight ); + if ( mRepositionCursorPos.x < columnScrollRect.point.x ) + mRepositionCursorPos.x = columnScrollRect.point.x; + if ( mRepositionCursorPos.x > ( columnScrollRect.point.x + columnScrollRect.extent.x ) ) + mRepositionCursorPos.x = columnScrollRect.point.x + columnScrollRect.extent.x; +} + +//------------------------------------------------------------------------------ +bool ShellFancyArray::getColumnScrollViewRect( RectI &rect ) +{ + // Get the view rect that the columns scroll + // left and right in, in local coordinates. + rect.point.set( mGlowOffset, 0 ); + rect.extent.y = mBounds.extent.y; + + if ( mStartScrollRgn.x >= mNumColumns ) + return false; + + for ( int i = 0; i < mStartScrollRgn.x; i++ ) + rect.point.x += mColumnInfoList[i].width; + + rect.extent.x = mBounds.extent.x - rect.point.x; + + if ( rect.extent.x <= 0 ) + return false; + + return true; +} + +//------------------------------------------------------------------------------ +bool ShellFancyArray::getRowScrollViewRect( RectI &rect ) +{ + // Get the view rect that the rows scroll + // up and down in, in local coordinates. + if ( mStartScrollRgn.y >= mNumRows ) + return false; + + rect.point.x = 0; + rect.point.y = mHeaderHeight + mGlowOffset + ( mRowHeight * mStartScrollRgn.y ); + rect.extent.x = mBounds.extent.x; + rect.extent.y = mBounds.extent.y - rect.point.y; + + if ( rect.extent.y <= 0 ) + return false; + + return true; +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::setMouseOverRow( S32 row ) +{ + if ( mMouseOverRow == row ) + return; + + mMouseOverRow = row; + setUpdate(); +} + +//------------------------------------------------------------------------------ +void ShellFancyArray::setMouseOverColumn( S32 column, S32 xPos ) +{ + if ( mMouseOverColumn == column || mMouseOverRow != -1 ) + return; + + mMouseOverColumn = column; + + // Play a mouse-over noise: + if ( column != -1 && mProfile->mSoundButtonOver ) + { + F32 pan = ( F32( xPos ) / F32( Canvas->mBounds.extent.x ) * 2.0f - 1.0f ) * 0.8f; + AUDIOHANDLE handle = alxCreateSource(mProfile->mSoundButtonOver); + alxSourcef( handle, AL_PAN, pan ); + alxPlay( handle ); + } + + setUpdateRegion( mBounds.point, Point2I( mBounds.extent.x, mHeaderHeight ) ); +} + +//------------------------------------------------------------------------------ +// +// ShellFancyArrayScrollCtrl functions +// +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(ShellFancyArrayScrollCtrl); + +//------------------------------------------------------------------------------ +ShellFancyArrayScrollCtrl::ShellFancyArrayScrollCtrl() : GuiControl() +{ + mArray = NULL; + mScrollView = NULL; + mVirtualContent = NULL; + + mVSpacerBitmap = StringTable->insert( "gui/shll_vertspacer" ); + mHSpacerBitmap = StringTable->insert( "gui/shll_horzspacer" ); + mTexVSpacer = NULL; + mTexHSpacer = NULL; + + mPrevArrayPos.set( 0, 0 ); + mPrevArrayExtent.set( 0, 0 ); + mPrevContentPos.set( 0, 0 ); + + mFixedHorizontal = false; +} + +//------------------------------------------------------------------------------ +void ShellFancyArrayScrollCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField( "fixedHorizontal", TypeBool, Offset( mFixedHorizontal, ShellFancyArrayScrollCtrl ) ); + addField( "vertSpacerBitmap", TypeString, Offset( mVSpacerBitmap, ShellFancyArrayScrollCtrl ) ); + addField( "horzSpacerBitmap", TypeString, Offset( mHSpacerBitmap, ShellFancyArrayScrollCtrl ) ); +} + +//------------------------------------------------------------------------------ +bool ShellFancyArrayScrollCtrl::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Add the scroll control: + VirtualScrollCtrl* scrollView = new VirtualScrollCtrl(); + scrollView->mWillFirstRespond = false; + scrollView->mForceHScrollBar = ( mFixedHorizontal ? GuiScrollCtrl::ScrollBarAlwaysOff : GuiScrollCtrl::ScrollBarAlwaysOn ); + scrollView->mForceVScrollBar = GuiScrollCtrl::ScrollBarAlwaysOn; + scrollView->mProfile = mProfile; // Share... + if ( !scrollView->registerObject() ) + Con::errorf( ConsoleLogEntry::General, "Failed to add scroll control to ShellFancyArrayScrollCtrl!" ); + addObject( scrollView ); + + mVirtualContent = scrollView->getVirtualContent(); + + // Add the server browser: + ShellFancyArray* textArray = new ShellFancyArray(); + textArray->mProfile = mProfile; // Share... + if ( !textArray->registerObject() ) + Con::errorf( ConsoleLogEntry::General, "Failed to add browser to ShellFancyArrayScrollCtrl!" ); + addObject( textArray ); + + positionChildren(); + return true; +} + +//------------------------------------------------------------------------------ +void ShellFancyArrayScrollCtrl::addObject( SimObject* obj ) +{ + // Only allow one array: + ShellFancyArray* array = dynamic_cast( obj ); + if ( array ) + { + if ( mArray ) + mArray->deleteObject(); + Parent::addObject( obj ); + mArray = array; + if ( mFixedHorizontal ) + mArray->setFixedHorizontal(); + positionChildren(); + return; + } + + // Only allow one scroll control: + VirtualScrollCtrl* scrollCtrl = dynamic_cast( obj ); + if ( scrollCtrl ) + { + if ( mScrollView ) + { + mScrollView->deleteObject(); + // Deleting this also deletes the virtual content: + mVirtualContent = NULL; + } + Parent::addObject( obj ); + mScrollView = scrollCtrl; + mScrollView->mForceHScrollBar = ( mFixedHorizontal ? GuiScrollCtrl::ScrollBarAlwaysOff : GuiScrollCtrl::ScrollBarAlwaysOn ); + positionChildren(); + return; + } + + Parent::addObject( obj ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArrayScrollCtrl::removeObject( SimObject* obj ) +{ + // Keep track of the kids, so we don't crash: + ShellFancyArray* array = dynamic_cast( obj ); + if ( mArray == array ) + mArray = NULL; + + VirtualScrollCtrl* scrollCtrl = dynamic_cast( obj ); + if ( mScrollView == scrollCtrl ) + mScrollView = NULL; + + Parent::removeObject( obj ); +} + +//------------------------------------------------------------------------------ +bool ShellFancyArrayScrollCtrl::onWake() +{ + if ( !Parent::onWake() ) + return( false ); + + char buf[256]; + if ( mVSpacerBitmap[0] ) + { + dSprintf( buf, sizeof( buf ), "%s.png", mVSpacerBitmap ); + mTexVSpacer = TextureHandle( buf, BitmapTexture ); + } + + if ( mHSpacerBitmap[0] ) + { + dSprintf( buf, sizeof( buf ), "%s.png", mHSpacerBitmap ); + mTexHSpacer = TextureHandle( buf, BitmapTexture ); + } + + // Make sure all the kids are sized and aligned correctly: + positionChildren(); + + return( true ); +} + +//------------------------------------------------------------------------------ +void ShellFancyArrayScrollCtrl::onSleep() +{ + Parent::onSleep(); + mTexVSpacer = NULL; + mTexHSpacer = NULL; +} + +//------------------------------------------------------------------------------ +void ShellFancyArrayScrollCtrl::positionChildren() +{ + // Don't position the children until all are present: + if ( !mArray || !mScrollView || !mVirtualContent ) + return; + + S32 glowOffset = mArray->getGlowOffset(); + S32 scrollWidth = mScrollView->scrollBarThickness(); + mArray->mBounds.point.set( 0, 0 ); + mArray->mBounds.extent.set( mBounds.extent.x - scrollWidth - glowOffset, mBounds.extent.y - glowOffset ); + if ( !mFixedHorizontal ) + mArray->mBounds.extent.y -= ( scrollWidth + 1 ); + mArray->resize( mArray->mBounds.point, mArray->mBounds.extent ); + + RectI scrollRect; + mArray->getScrollRect( scrollRect ); + mScrollView->mBounds.point = scrollRect.point - Point2I( glowOffset, glowOffset ); + mScrollView->mBounds.extent.set( mBounds.extent.x - scrollRect.point.x + glowOffset, mBounds.extent.y - scrollRect.point.y + glowOffset ); + + mVirtualContent->mBounds.point = mArray->getScrollPos(); + mVirtualContent->mBounds.extent = mArray->getScrollExtent() + Point2I( 2, 2 ); + mScrollView->resize( mScrollView->mBounds.point, mScrollView->mBounds.extent ); + mArray->forceFillScrollRegion(); +} + +//------------------------------------------------------------------------------ +void ShellFancyArrayScrollCtrl::resize( const Point2I &newPos, const Point2I &newExtent ) +{ + Parent::resize( newPos, newExtent ); + + // Move the kids around: + positionChildren(); +} + +//------------------------------------------------------------------------------ +void ShellFancyArrayScrollCtrl::onPreRender() +{ + if ( mScrollView && mArray && mVirtualContent ) + { + // First see if the browser repositioned itself: + Point2I newPos = mArray->getScrollPos(); + Point2I newExtent = mArray->getScrollExtent(); + if ( mPrevArrayExtent != newExtent ) + { + mVirtualContent->mBounds.extent = newExtent + Point2I( 2, 2 ); + mPrevArrayExtent = newExtent; + mScrollView->resize( mScrollView->mBounds.point, mScrollView->mBounds.extent ); + } + + if ( mPrevArrayPos != newPos ) + { + mVirtualContent->mBounds.point = newPos; + mPrevArrayPos = newPos; + mPrevContentPos = newPos; + mScrollView->resize( mScrollView->mBounds.point, mScrollView->mBounds.extent ); + mArray->setUpdate(); + } + else + { + newPos = mVirtualContent->mBounds.point; + if ( mPrevContentPos != newPos ) + { + mArray->setScrollPos( newPos ); + mPrevArrayPos = newPos; + mPrevContentPos = newPos; + mArray->setUpdate(); + } + } + } +} + +//------------------------------------------------------------------------------ +void ShellFancyArrayScrollCtrl::onRender( Point2I offset, const RectI &updateRect, GuiControl* firstResponder ) +{ + if ( mArray ) + { + Point2I drawPos; + RectI clipRect; + S32 glowOffset = mArray->getGlowOffset(); + dglClearBitmapModulation(); + + if ( mTexVSpacer ) + { + // Draw the upper right notch: + S32 fillHeight = mArray->getNoScrollHeight() - 1; + drawPos.x = offset.x + mArray->mBounds.extent.x; + drawPos.y = offset.y + glowOffset; + clipRect.set( drawPos, Point2I( mTexVSpacer.getWidth(), fillHeight ) ); + if ( clipRect.intersect( updateRect ) ) + { + dglSetClipRect( clipRect ); + while ( fillHeight > 0 ) + { + dglDrawBitmap( mTexVSpacer, drawPos ); + drawPos.y += mTexVSpacer.getHeight(); + fillHeight -= mTexVSpacer.getHeight(); + } + + dglSetClipRect( updateRect ); + } + } + + if ( !mFixedHorizontal && mTexHSpacer ) + { + // Draw the lower left notch: + S32 fillWidth = mArray->getNoScrollWidth() - 1; + drawPos.x = offset.x + glowOffset; + drawPos.y = offset.y + mArray->mBounds.extent.y; + clipRect.set( drawPos, Point2I( fillWidth, mTexHSpacer.getHeight() ) ); + if ( clipRect.intersect( updateRect ) ) + { + dglSetClipRect( clipRect ); + while ( fillWidth > 0 ) + { + dglDrawBitmap( mTexHSpacer, drawPos ); + drawPos.x += mTexHSpacer.getWidth(); + fillWidth -= mTexHSpacer.getWidth(); + } + + dglSetClipRect( updateRect ); + } + } + } + + Parent::onRender( offset, updateRect, firstResponder ); +} + diff --git a/shell/shellFancyArray.h b/shell/shellFancyArray.h new file mode 100644 index 0000000..d464605 --- /dev/null +++ b/shell/shellFancyArray.h @@ -0,0 +1,324 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHELLFANCYARRAY_H_ +#define _SHELLFANCYARRAY_H_ + +#ifndef _GUITYPES_H_ +#include "gui/guiTypes.h" +#endif +#ifndef _SHELLSCROLLCTRL_H_ +#include "shell/shellScrollCtrl.h" +#endif + +//------------------------------------------------------------------------------ +class VirtualScrollContentCtrl : public GuiScrollContentCtrl +{ + private: + typedef GuiScrollContentCtrl Parent; + + public: + GuiControl* mVirtualContent; + + DECLARE_CONOBJECT(VirtualScrollContentCtrl); + VirtualScrollContentCtrl(); + + bool onAdd(); + void addObject( SimObject* obj ); + void removeObject( SimObject* obj ); +}; + + +//------------------------------------------------------------------------------ +class VirtualScrollCtrl : public ShellScrollCtrl +{ + private: + typedef ShellScrollCtrl Parent; + + public: + DECLARE_CONOBJECT(VirtualScrollCtrl); + VirtualScrollCtrl(); + + void setVirtualContent( GuiControl* control ); + GuiControl* getVirtualContent(); + + bool onAdd(); + void addObject( SimObject* obj ); +}; + + +//------------------------------------------------------------------------------ +class ShellFancyArray : public GuiControl +{ + private: + typedef GuiControl Parent; + + protected: + // Bitmap defines for the header: + enum BitmapIndices + { + BmpLeft, + BmpCenter, + BmpRight, + + BmpCount + }; + + enum BitmapStates + { + StateNormal, + StatePressed, + StateRollover, + + StateCount + }; + + RectI mBmpBounds[BmpCount * StateCount]; + StringTableEntry mHeaderBitmap; + TextureHandle mTexHeader; + StringTableEntry mSortArrowBitmap; + TextureHandle mTexSortArrow; + + StringTableEntry mBarBase; + TextureHandle mTexCellSelected; + TextureHandle mTexCellRollover; + + // Field stuff: + StringTableEntry mFieldBase; + TextureHandle mTexLeftTop; + TextureHandle mTexCenterTop; + TextureHandle mTexRightTop; + TextureHandle mTexLeftCenter; + TextureHandle mTexCenter; + TextureHandle mTexRightCenter; + TextureHandle mTexLeftBottom; + TextureHandle mTexCenterBottom; + TextureHandle mTexRightBottom; + + // Do not change after construction: + enum ColumnFlags + { + Column_Numeric = BIT(0), + Column_Center = BIT(1), + Column_Right = BIT(2), + Column_Icon = BIT(3), + }; + + struct ColumnInfo + { + StringTableEntry name; + S32 width; + S32 minWidth; + S32 maxWidth; + S32 key; + BitSet32 flags; + }; + + bool mFixedHorizontal; + Vector mColumnInfoList; + S32 mNumColumns; + S32 mRowHeight; + S32 mHeaderHeight; + S32 mGlowOffset; + S32 mMinColumnWidth; + Point2I mStartScrollRgn; + + Resource mFont; + Resource mHeaderFont; + + StringTableEntry mHeaderFontType; + S32 mHeaderFontSize; + + GuiCursor* mDefaultCursor; + GuiCursor* mResizeCursor; + GuiCursor* mRepositionCursor; + + ColorI mHeaderFontColor; + ColorI mHeaderFontColorHL; + ColorI mSeparatorColor; + + bool mDrawCellSeparators; + bool mHeaderSort; + bool mAllowReposition; + bool mNoSelect; + + // State variables: + S32 mNumRows; + S32 mSelectedRow; + S32 mMouseOverRow; + S32 mMouseOverColumn; + Point2I mScrollPanePos; + + enum ColumnState + { + None, + Resizing, + Repositioning, + Sorting, + SecondarySorting, // For derived classes + }; + + ColumnState mColumnState; + S32 mActiveColumn; + Point2I mDragAnchor; + + // Sorting variables: + S32 mSortColumnKey; + S32 mSecondarySortColumnKey; + S32 mSortInc; + S32 mSecondarySortInc; + + // Column resizing work variables: + S32 mAbsResizeLeftMargin; + S32 mAbsResizeRightMargin; + bool mResizeFixedColumn; + S32 mResizeColumnOrigSize; + + // Column reposition work variables: + S32 mRepositionColumnTo; + Point2I mRepositionCursorPos; + + void determineRepositionColumn( Point2I pt ); + + bool getColumnScrollViewRect( RectI &rect ); + bool getRowScrollViewRect( RectI &rect ); + + void setMouseOverRow( S32 row ); + void setMouseOverColumn( S32 column, S32 xPos = 0 ); + + S32 findColumn( S32 key ); + void drawColumn( Point2I offset, RectI clipRect, bool hasRowScrollRect, RectI rowScrollRect, S32 column ); + + public: + DECLARE_CONOBJECT(ShellFancyArray); + ShellFancyArray(); + + bool onAdd(); + void onRemove(); + + static void initPersistFields(); + static void consoleInit(); + + bool onWake(); + void onSleep(); + + void setFixedHorizontal() { mFixedHorizontal = true; } + void clearColumns(); + void addColumn( S32 key, const char* name, S32 defaultWidth, S32 minWidth, S32 maxWidth, const char* flags = NULL ); + S32 getNumColumns() { return( mNumColumns ); } + + virtual void clearList(); + virtual void updateList(); + + bool pointInColumn( bool inHeader, Point2I pt, S32 &column, bool &inResizeRgn, bool &resizeLeft ); + bool getScrollRect( RectI &rect ); + + void setSortColumnKey( S32 newKey ); + void setSortInc( bool newSortInc ) { mSortInc = newSortInc; sort(); } + void setSecondarySortColumnKey( S32 newKey ); + void setSecondarySortInc( bool newSortInc ) { mSecondarySortInc = newSortInc; sort(); } + virtual void sort(); + + void selectCell( S32 row, S32 column ); + S32 getSelectedRow() { return( mSelectedRow ); } + void setNumRows( S32 numRows ); + U32 getNumRows() { return( mNumRows ); } + + S32 getGlowOffset() { return( mGlowOffset ); } + S32 getHeaderHeight() { return( mHeaderHeight ); } + S32 getRowHeight() { return( mRowHeight ); } + + S32 getNoScrollWidth(); + S32 getNoScrollHeight() { return( mHeaderHeight + ( mRowHeight * mStartScrollRgn.y ) ); } + + S32 getColumnKey( S32 index ); + S32 getColumnWidth( S32 index ); + S32 getSortColumnKey() { return( mSortColumnKey ); } + bool getSortIncreasing() { return( mSortInc ); } + S32 getSecondarySortColumnKey() { return( mSecondarySortColumnKey ); } + bool getSecondarySortIncreasing() { return( mSecondarySortInc ); } + + virtual void onHeaderAction( S32 column ); + virtual void onSecondaryHeaderAction( S32 column ); + virtual void onCellSelected( S32 row, S32 column ); + + void forceFillScrollRegion(); + void scrollSelectedRowVisible(); + + void computeFixedResizingVals(); + void resizeFixedColumn( const GuiEvent &event ); + void resizeScrollColumn( const GuiEvent &event ); + + Point2I getScrollPos() { return mScrollPanePos; } + void setScrollPos( Point2I newPos ) { mScrollPanePos = newPos; } + Point2I getScrollExtent(); + + 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 ); + bool onMouseWheelUp( const GuiEvent &event ); + bool onMouseWheelDown( const GuiEvent &event ); + bool onKeyDown( const GuiEvent& event ); + + void resize( const Point2I &newPos, const Point2I &newExtent ); + + void onRenderColumnHeader( Point2I offset, RectI clipRect, S32 column, bool mouseOver ); + virtual void onRenderCell( Point2I offset, Point2I cell, bool selected, bool mouseOver ); + void onRender( Point2I offset, const RectI &updateRect, GuiControl* firstResponder ); +}; + + +//------------------------------------------------------------------------------ +class ShellFancyArrayScrollCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + + protected: + ShellFancyArray* mArray; + VirtualScrollCtrl* mScrollView; + GuiControl* mVirtualContent; + + Point2I mPrevArrayPos; + Point2I mPrevArrayExtent; + Point2I mPrevContentPos; + + bool mFixedHorizontal; + + StringTableEntry mVSpacerBitmap; + StringTableEntry mHSpacerBitmap; + TextureHandle mTexVSpacer; + TextureHandle mTexHSpacer; + + public: + DECLARE_CONOBJECT(ShellFancyArrayScrollCtrl); + ShellFancyArrayScrollCtrl(); + + static void initPersistFields(); + + bool onAdd(); + + void addObject( SimObject* obj ); + void removeObject( SimObject* obj ); + + bool onWake(); + void onSleep(); + + void setVirtualContent( GuiControl* control ) { mVirtualContent = control; } + void positionChildren(); + void resize( const Point2I &newPos, const Point2I &newExtent ); + + void onPreRender(); + void onRender( Point2I offset, const RectI &updateRect, GuiControl* firstResponder ); +}; + +#endif // _SHELL_FANCYARRAY_H diff --git a/shell/shellFancyTextList.cc b/shell/shellFancyTextList.cc new file mode 100644 index 0000000..8170cd0 --- /dev/null +++ b/shell/shellFancyTextList.cc @@ -0,0 +1,669 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "dgl/dgl.h" +#include "Shell/shellFancyTextList.h" +#include "GUI/guiCanvas.h" +#include "console/consoleTypes.h" + +//------------------------------------------------------------------------------ +// Static stuff for sorting: +//------------------------------------------------------------------------------ +static S32 sSortColumn; +static bool sSortInc; + +//------------------------------------------------------------------------------ +static const char* getColumn( U32 column, const char* text ) +{ + U32 ct = column; + while ( ct-- ) + { + text = dStrchr( text, '\t' ); + if ( !text ) + return( "" ); + text++; + } + return( text ); +} + +//------------------------------------------------------------------------------ +static S32 QSORT_CALLBACK fancyListRowCompare( const void* a, const void* b ) +{ + ShellFancyTextList::Entry* entryA = (ShellFancyTextList::Entry*) a; + ShellFancyTextList::Entry* entryB = (ShellFancyTextList::Entry*) b; + S32 result = dStricmp( getColumn( sSortColumn, entryA->text ), getColumn( sSortColumn, entryB->text ) ); + return( sSortInc ? result : -result ); +} + +//------------------------------------------------------------------------------ +static S32 QSORT_CALLBACK fancyListRowNumCompare( const void* a, const void* b ) +{ + ShellFancyTextList::Entry* entryA = (ShellFancyTextList::Entry*) a; + ShellFancyTextList::Entry* entryB = (ShellFancyTextList::Entry*) b; + const char* aCol = getColumn( sSortColumn, entryA->text ); + const char* bCol = getColumn( sSortColumn, entryB->text ); + char* aBuf = new char[dStrlen( aCol ) + 1]; + char* bBuf = new char[dStrlen( bCol ) + 1]; + dStrcpy( aBuf, aCol ); + dStrcpy( bBuf, bCol ); + char* ptr = dStrchr( aBuf, '\t' ); + if ( ptr ) + *ptr = '\0'; + ptr = dStrchr( bBuf, '\t' ); + if ( ptr ) + *ptr = '\0'; + S32 result = dAtoi( aBuf ) - dAtoi( bBuf ); + if ( result == 0 ) + return( dStricmp( entryA->text, entryB->text ) ); + delete [] aBuf; + delete [] bBuf; + return( sSortInc ? result : -result ); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(ShellFancyTextList); + +//------------------------------------------------------------------------------ +ShellFancyTextList::ShellFancyTextList() : ShellFancyArray() +{ + mAllowReposition = false; // Repositioning columns currently not supported for this class. + mStyleList = NULL; +} + +//------------------------------------------------------------------------------ +ShellFancyTextList::~ShellFancyTextList() +{ + for ( StyleSet* walk = mStyleList; walk; walk = walk->next ) + walk->font = NULL; + mStyleList = NULL; + mResourceChunker.freeBlocks(); +} + +//------------------------------------------------------------------------------ +static S32 cFTLGetSelectedId( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLGetSelectedId is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + return( ftl->getSelectedId() ); +} + +//------------------------------------------------------------------------------ +static void cFTLSelectRowById( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLSelectRowById is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + ftl->selectRowById( dAtoi( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static void cFTLClearSelection( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLClearSelection is not a ShellFancyArray!" ); + ShellFancyArray* array = static_cast( obj ); + array->selectCell( -1, -1 ); +} + +//------------------------------------------------------------------------------ +static void cFTLClear( SimObject* obj, S32, const char** ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLClear is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + ftl->clearList(); +} + +//------------------------------------------------------------------------------ +static void cFTLAddRow( SimObject* obj, S32 argc, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLAddRow is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + if ( argc == 4 ) + ftl->addEntry( dAtoi( argv[2] ), argv[3] ); + else + ftl->insertEntry( dAtoi( argv[2] ), argv[3], dAtoi( argv[4] ) ); +} + +//------------------------------------------------------------------------------ +static void cFTLSetRowById( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLSetRowById is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + ftl->setEntry( dAtoi( argv[2] ), argv[3] ); +} + +//------------------------------------------------------------------------------ +static S32 cFTLGetRowId( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLGetRowId is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + return( ftl->getRowId( dAtoi( argv[2] ) ) ); +} + +//------------------------------------------------------------------------------ +static void cFTLRemoveRowById( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLRemoveRowById is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + ftl->removeEntry( dAtoi( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static const char* cFTLGetRowTextById( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLGetRowTextById is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + return( ftl->getText( ftl->findEntryById( dAtoi( argv[2] ) ) ) ); +} + +//------------------------------------------------------------------------------ +static void cFTLRemoveRowByIndex( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLRemoveRowByIndex is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + ftl->removeEntryByIndex( dAtoi( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static S32 cFTLFindById( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLFindById is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + return( ftl->findEntryById( dAtoi( argv[2] ) ) ); +} + +//------------------------------------------------------------------------------ +static const char* cFTLGetRowText( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLGetRowText is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + return( ftl->getText( dAtoi( argv[2] ) ) ); +} + +//------------------------------------------------------------------------------ +static S32 cFTLFindText( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLFindText is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + return( ftl->findEntryByText( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +static void cFTLSort( SimObject* obj, S32 argc, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLSort is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + if ( argc > 2 ) + { + S32 key = ftl->getColumnKey( dAtoi( argv[2] ) ); + ftl->setSortColumnKey( key ); + if ( argc > 3 ) + ftl->setSortInc( dAtob( argv[3] ) ); + } + ftl->sort(); +} + +//------------------------------------------------------------------------------ +static bool cFTLAddStyle( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLAddStyle is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + + ColorI fontColor, fontColorHL, fontColorSEL; + fontColor.set( 0, 0, 0, 255 ); + fontColorHL.set( 0, 0, 0, 255 ); + fontColorSEL.set( 0, 0, 0, 255 ); + S32 r, g, b, a; + S32 args = dSscanf( argv[5], "%d %d %d %d", &r, &g, &b, &a ); + fontColor.red = r; + fontColor.green = g; + fontColor.blue = b; + if ( args == 4 ) + fontColor.alpha = a; + + args = dSscanf( argv[6], "%d %d %d %d", &r, &g, &b, &a ); + fontColorHL.red = r; + fontColorHL.green = g; + fontColorHL.blue = b; + if ( args == 4 ) + fontColorHL.alpha = a; + + args = dSscanf( argv[7], "%d %d %d %d", &r, &g, &b, &a ); + fontColorSEL.red = r; + fontColorSEL.green = g; + fontColorSEL.blue = b; + if ( args == 4 ) + fontColorSEL.alpha = a; + + return( ftl->addStyleSet( dAtoi( argv[2] ), argv[3], dAtoi( argv[4] ), fontColor, fontColorHL, fontColorSEL ) ); +} + +//------------------------------------------------------------------------------ +static void cFTLSetRowStyle( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLSetRowStyle is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + ftl->setEntryStyle( dAtoi( argv[2] ), dAtoi( argv[3] ) ); +} + +//------------------------------------------------------------------------------ +static void cFTLSetRowStyleById( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLSetRowStyleById is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + ftl->setEntryStyle( ftl->findEntryById( dAtoi( argv[2] ) ), dAtoi( argv[3] ) ); +} + +//------------------------------------------------------------------------------ +static S32 cFTLGetRowStyle( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLGetRowStyle is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + return( ftl->getEntryStyle( dAtoi( argv[2] ) ) ); +} + +//------------------------------------------------------------------------------ +static S32 cFTLGetRowStyleById( SimObject* obj, S32, const char** argv ) +{ + AssertFatal( dynamic_cast( obj ), "Object passed to cFTLGetRowStyleById is not a ShellFancyTextList!" ); + ShellFancyTextList* ftl = static_cast( obj ); + return( ftl->getEntryStyle( ftl->findEntryById( dAtoi( argv[2] ) ) ) ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::consoleInit() +{ + Con::addCommand( "ShellFancyTextList", "getSelectedId", cFTLGetSelectedId, "fancytextlist.getSelectedId()", 2, 2 ); + Con::addCommand( "ShellFancyTextList", "setSelectedById", cFTLSelectRowById, "fancytextlist.setSelectedById( id )", 3, 3 ); + Con::addCommand( "ShellFancyTextList", "clearSelection", cFTLClearSelection, "fancytextlist.clearSelection()", 2, 2); + Con::addCommand( "ShellFancyTextList", "clear", cFTLClear, "fancytextlist.clear()", 2, 2 ); + Con::addCommand( "ShellFancyTextList", "addRow", cFTLAddRow, "fancytextlist.addRow( id, text{, index } )", 4, 5 ); + Con::addCommand( "ShellFancyTextList", "setRowById", cFTLSetRowById, "fancytextlist.setRowById( id, text )", 4, 4 ); + Con::addCommand( "ShellFancyTextList", "getRowId", cFTLGetRowId, "fancytextlist.getRowId( index )", 3, 3 ); + Con::addCommand( "ShellFancyTextList", "removeRowById", cFTLRemoveRowById, "fancytextlist.removeRowById( id )", 3, 3 ); + Con::addCommand( "ShellFancyTextList", "getRowTextById", cFTLGetRowTextById, "fancytextlist.getRowTextById( id )", 3, 3 ); + Con::addCommand( "ShellFancyTextList", "getRowNumById", cFTLFindById, "fancytextlist.getRowNumById( id )", 3, 3 ); + Con::addCommand( "ShellFancyTextList", "getRowText", cFTLGetRowText, "fancytextlist.getRowText( index )", 3, 3 ); + Con::addCommand( "ShellFancyTextList", "removeRow", cFTLRemoveRowByIndex, "fancytextlist.removeRow( index )", 3, 3 ); + Con::addCommand( "ShellFancyTextList", "findTextIndex", cFTLFindText, "fancytextlist.findTextIndex( text )", 3, 3 ); + Con::addCommand( "ShellFancyTextList", "sort", cFTLSort, "fancytextlist.sort( { column{, increasing} } )", 2, 4 ); + Con::addCommand( "ShellFancyTextList", "addStyle", cFTLAddStyle, "fancytextlist.addStyle( id, fontType, fontSize, fontColor, fontColorHL, fontColorSEL )", 8, 8 ); + Con::addCommand( "ShellFancyTextList", "setRowStyle", cFTLSetRowStyle, "fancytextlist.setRowStyle( row, style )", 4, 4 ); + Con::addCommand( "ShellFancyTextList", "setRowStyleById", cFTLSetRowStyleById, "fancytextlist.setRowStyleById( id, style )", 4, 4 ); + Con::addCommand( "ShellFancyTextList", "getRowStyle", cFTLGetRowStyle, "fancytextlist.getRowStyle( row )", 3, 3 ); + Con::addCommand( "ShellFancyTextList", "getRowStyleById", cFTLGetRowStyleById, "fancytextlist.getRowStyleById( id )", 3, 3 ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::initPersistFields() +{ + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ +const char* ShellFancyTextList::getScriptValue() +{ + return( NULL ); +} + +//------------------------------------------------------------------------------ +U32 ShellFancyTextList::getNumEntries() +{ + return( mList.size() ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::clearList() +{ + while ( mList.size() ) + removeEntry( mList[0].id ); + + mSelectedRow = -1; + mMouseOverRow = -2; + mMouseOverColumn = -1; + setNumRows( 0 ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::addEntry( U32 id, const char* text ) +{ + Entry newEntry; + newEntry.text = dStrdup( text ); + newEntry.id = id; + newEntry.active = true; + newEntry.styleId = 0; + mList.push_back( newEntry ); + setNumRows( mList.size() ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::insertEntry( U32 id, const char* text, S32 index ) +{ + Entry newEntry; + newEntry.text = dStrdup( text ); + newEntry.id = id; + newEntry.active = true; + newEntry.styleId = 0; + mList.insert( &mList[index], newEntry ); + setNumRows( mList.size() ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::removeEntry( U32 id ) +{ + S32 index = findEntryById( id ); + removeEntryByIndex( index ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::removeEntryByIndex( S32 index ) +{ + if ( index < 0 || index >= mList.size() ) + return; + dFree( mList[index].text ); + mList.erase( index ); + + setNumRows( mList.size() ); + selectCell( -1, -1 ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::setEntry( U32 id, const char* text ) +{ + S32 index = findEntryById( id ); + if ( index == -1 ) + addEntry( id, text ); + else + { + dFree( mList[index].text ); + mList[index].text = dStrdup( text ); + } + setUpdate(); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::setEntryActive( U32 id, bool active ) +{ + S32 index = findEntryById( id ); + if ( index == -1 ) + return; + + if ( mList[index].active != active ) + { + mList[index].active = active; + setUpdate(); + } +} + +//------------------------------------------------------------------------------ +S32 ShellFancyTextList::findEntryById( U32 id ) +{ + for ( U32 i = 0; i < mList.size(); i++ ) + if ( mList[i].id == id ) + return i; + return -1; +} + +//------------------------------------------------------------------------------ +S32 ShellFancyTextList::findEntryByText( const char* text ) +{ + for ( U32 i = 0; i < mList.size(); i++ ) + if ( dStrcmp( mList[i].text, text ) == 0 ) + return i; + return -1; +} + +//------------------------------------------------------------------------------ +bool ShellFancyTextList::isEntryActive( U32 id ) +{ + S32 index = findEntryById( id ); + if ( index == -1 ) + return( false ); + + return( mList[index].active ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::selectRowById( U32 id ) +{ + selectCell( findEntryById( id ), 0 ); +} + +//------------------------------------------------------------------------------ +U32 ShellFancyTextList::getSelectedId() +{ + if ( mSelectedRow == -1 || mSelectedRow >= mList.size() ) + return( InvalidId ); + + return( mList[mSelectedRow].id ); +} + +//------------------------------------------------------------------------------ +U32 ShellFancyTextList::getRowId( S32 index ) +{ + if ( index == -1 || index >= mList.size() ) + return( InvalidId ); + + return( mList[index].id ); +} + +//------------------------------------------------------------------------------ +const char* ShellFancyTextList::getText( S32 index ) +{ + if ( index == -1 || index >= mList.size() ) + return( "" ); + + return( mList[index].text ); +} + +//------------------------------------------------------------------------------ +bool ShellFancyTextList::addStyleSet( U32 id, const char* fontType, U32 fontSize, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL ) +{ + // Make sure the id isn't taken already: + if ( id == 0 ) + { + Con::warnf( ConsoleLogEntry::General, "ShellFancyTextList::addStyleSet - style id 0 is reserved for the default!" ); + return( false ); + } + + if ( getStyleSet( id ) != NULL ) + { + Con::warnf( ConsoleLogEntry::General, "ShellFancyTextList::addStyleSet - style id %d used already!", id ); + return( false ); + } + + // Add the new style set: + StyleSet* newStyle; + newStyle = constructInPlace( (StyleSet*) mResourceChunker.alloc( sizeof( StyleSet ) ) ); + newStyle->fontType = StringTable->insert( fontType ); + newStyle->fontSize = fontSize; + if ( mAwake ) + newStyle->font = GFont::create( newStyle->fontType, newStyle->fontSize ); + newStyle->id = id; + newStyle->fontColor = fontColor; + newStyle->fontColorHL = fontColorHL; + newStyle->fontColorSEL = fontColorSEL; + + newStyle->next = mStyleList; + mStyleList = newStyle; + return( true ); +} + +//------------------------------------------------------------------------------ +const ShellFancyTextList::StyleSet* ShellFancyTextList::getStyleSet( U32 id ) +{ + if ( id ) + { + for ( StyleSet* walk = mStyleList; walk; walk = walk->next ) + { + if ( walk->id == id ) + return( walk ); + } + } + + return( NULL ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::setEntryStyle( S32 index, U32 styleId ) +{ + if ( index < 0 || index >= mList.size() ) + return; + + if ( mList[index].styleId != styleId ) + { + mList[index].styleId = styleId; + setUpdate(); + } +} + +//------------------------------------------------------------------------------ +U32 ShellFancyTextList::getEntryStyle( S32 index ) +{ + if ( index < 0 || index >= mList.size() ) + return( 0 ); + + return( mList[index].styleId ); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::onCellSelected( S32 row, S32 /*column*/ ) +{ + Con::executef( this, 3, "onSelect", Con::getIntArg( mList[row].id ), mList[row].text ); + if ( mConsoleCommand[0] ) + Con::evaluate( mConsoleCommand, false ); +} + +//------------------------------------------------------------------------------ +bool ShellFancyTextList::onWake() +{ + if ( !Parent::onWake() ) + return false; + + // Set the row height based on the font: + if ( mFont ) + mRowHeight = mFont->getHeight() + 3; + + // Get all of the style fonts: + for ( StyleSet* walk = mStyleList; walk; walk = walk->next ) + walk->font = GFont::create( walk->fontType, walk->fontSize ); + + return true; +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::onSleep() +{ + Parent::onSleep(); + + // Release all of the style fonts: + for ( StyleSet* walk = mStyleList; walk; walk = walk->next ) + walk->font = NULL; +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::updateList() +{ + if ( mNumRows != mList.size() ) + setNumRows( mList.size() ); + + setUpdate(); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::sort() +{ + if ( mSortColumnKey == -1 ) + return; + + mNumRows = mList.size(); + sSortColumn = findColumn( mSortColumnKey ); + sSortInc = mSortInc; + + U32 selId = getSelectedId(); + + if ( mNumRows > 1 ) + { + if ( mColumnInfoList[sSortColumn].flags.test( Column_Numeric ) ) + dQsort( (void*) &(mList[0]), mNumRows, sizeof(Entry), fancyListRowNumCompare ); + else + dQsort( (void*) &(mList[0]), mNumRows, sizeof(Entry), fancyListRowCompare ); + } + + selectRowById( selId ); + setUpdate(); +} + +//------------------------------------------------------------------------------ +void ShellFancyTextList::onRenderCell( Point2I offset, Point2I cell, bool selected, bool mouseOver ) +{ + ColumnInfo* ci = &mColumnInfoList[cell.x]; + Entry* entry = &mList[cell.y]; + + // Let the parent take care of the basics: + Parent::onRenderCell( offset, cell, selected, mouseOver ); + + // Draw the text ( if there is any ): + const char* text = getColumn( cell.x, entry->text ); + if ( text[0] ) + { + const char* temp = dStrchr( text, '\t' ); + U32 textLen = temp ? ( temp - text ) : dStrlen( text ); + char* drawText = new char[textLen + 4]; + dStrncpy( drawText, text, textLen ); + drawText[textLen] = '\0'; + + if ( ci->flags.test( Column_Icon ) ) + { + char buf[64]; + dSprintf( buf, sizeof( buf ), "gui/%s.png", drawText ); + TextureHandle tex = TextureHandle( buf, BitmapTexture ); + if ( tex ) + { + Point2I drawPt = offset; + if ( ci->width > tex.getWidth() ) + drawPt.x += ( ci->width - tex.getWidth() ) / 2; + if ( mRowHeight > tex.getHeight() ) + drawPt.y += ( mRowHeight - tex.getHeight() ) / 2; + dglDrawBitmap( tex, drawPt ); + tex = NULL; + } + } + else + { + Resource drawFont = NULL; + ColorI drawColor; + const StyleSet* style = getStyleSet( entry->styleId ); + if ( bool( style ) ) + { + drawFont = style->font; + drawColor = entry->active ? ( selected ? style->fontColorSEL : ( mouseOver ? style->fontColorHL : style->fontColor ) ) : mProfile->mFontColorNA; + } + else + { + // Fall back to the default: + drawFont = mFont; + drawColor = entry->active ? ( selected ? mProfile->mFontColorSEL : ( mouseOver ? mProfile->mFontColorHL : mProfile->mFontColor ) ) : mProfile->mFontColorNA; + } + + S32 textWidth = drawFont->getStrWidth( drawText ); + Point2I textStart; + + if ( ci->flags.test( Column_Center ) ) + textStart.x = offset.x + getMax( 4, ( ci->width - textWidth ) / 2 ); + else if ( ci->flags.test( Column_Right ) ) + textStart.x = offset.x + getMax( 4, ci->width - textWidth ); + else + textStart.x = offset.x + 4; + textStart.y = offset.y + ( ( mRowHeight - ( drawFont->getHeight() - 2 ) ) / 2 ); + dglSetBitmapModulation( drawColor ); + dglDrawText( drawFont, textStart, drawText, mProfile->mFontColors ); + delete [] drawText; + } + } +} + + diff --git a/shell/shellFancyTextList.h b/shell/shellFancyTextList.h new file mode 100644 index 0000000..3412644 --- /dev/null +++ b/shell/shellFancyTextList.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHELLFANCYTEXTLIST_H_ +#define _SHELLFANCYTEXTLIST_H_ + +#ifndef _SHELLFANCYARRAY_H_ +#include "Shell/shellFancyArray.h" +#endif + +class ShellFancyTextList : public ShellFancyArray +{ + private: + typedef ShellFancyArray Parent; + + protected: + enum + { + InvalidId = 0xFFFFFFFF + }; + + // Emulate a text list ( to some extent ): + public: + struct Entry + { + char* text; + U32 id; + bool active; + U32 styleId; + }; + + struct StyleSet + { + U32 id; + StringTableEntry fontType; + U32 fontSize; + Resource font; + ColorI fontColor; + ColorI fontColorHL; + ColorI fontColorSEL; + StyleSet* next; + + StyleSet() + { + id = 0; + fontSize = 0; + font = NULL; + fontColor.set( 0, 0, 0, 255 ); + fontColorHL.set( 0, 0, 0, 255 ); + fontColorSEL.set( 0, 0, 0, 255 ); + next = NULL; + }; + }; + + protected: + Vector mList; + DataChunker mResourceChunker; + StyleSet* mStyleList; + + const StyleSet* getStyleSet( U32 id ); + + public: + DECLARE_CONOBJECT(ShellFancyTextList); + ShellFancyTextList(); + ~ShellFancyTextList(); + + static void consoleInit(); + static void initPersistFields(); + + const char* getScriptValue(); + + U32 getNumEntries(); + void clearList(); + void addEntry( U32 id, const char* text ); + void insertEntry( U32 id, const char* text, S32 index ); + void removeEntry( U32 id ); + void removeEntryByIndex( S32 id ); + 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 ); + void selectRowById( U32 id ); + U32 getSelectedId(); + U32 getRowId( S32 index ); + const char* getText( S32 index ); + + bool addStyleSet( U32 id, const char* fontName, U32 fontSize, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL ); + void setEntryStyle( S32 index, U32 styleId ); + U32 getEntryStyle( S32 index ); + + void onCellSelected( S32 row, S32 column ); + + bool onWake(); + void onSleep(); + + void updateList(); + void sort(); + + void onRenderCell( Point2I offset, Point2I cell, bool selected, bool mouseOver ); +}; + +#endif // _SHELL_FANCYTEXTLIST_H + diff --git a/shell/shellScrollCtrl.cc b/shell/shellScrollCtrl.cc new file mode 100644 index 0000000..4f155d6 --- /dev/null +++ b/shell/shellScrollCtrl.cc @@ -0,0 +1,512 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "GUI/guiCanvas.h" +#include "console/consoleTypes.h" +#include "Shell/shellScrollCtrl.h" +#include "dgl/dgl.h" + +IMPLEMENT_CONOBJECT(ShellScrollCtrl); + +//------------------------------------------------------------------------------ +ShellScrollCtrl::ShellScrollCtrl() : GuiScrollCtrl() +{ + mGlowOffset = 4; + mBorderThickness = mGlowOffset; + + dMemset( mBarBounds, 0, sizeof( mBarBounds ) ); + mTexVBar = NULL; + mTexHBar = NULL; + + mTexVButtons = NULL; + mTexVThumb = NULL; + mTexHButtons = NULL; + mTexHThumb = NULL; + mTexCorner = NULL; + + mFieldBase = StringTable->insert( "gui/shll_field" ); + mTexLeftTop = NULL; + mTexCenterTop = NULL; + mTexRightTop = NULL; + mTexLeftCenter = NULL; + mTexCenter = NULL; + mTexRightCenter = NULL; + mTexLeftBottom = NULL; + mTexCenterBottom = NULL; + mTexRightBottom = NULL; + + mMouseOverRegion = None; +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField( "fieldBase", TypeString, Offset( mFieldBase, ShellScrollCtrl ) ); +} + +//------------------------------------------------------------------------------ +bool ShellScrollCtrl::onWake() +{ + // Set the default profile on start-up: + if ( !mProfile ) + { + SimObject *obj = Sim::findObject("NewScrollCtrlProfile"); + if ( obj ) + mProfile = dynamic_cast( obj ); + } + + // NOTE: Not using Parent here on purpose. + if ( !GuiControl::onWake() ) + return false; + + AssertFatal( size(), "ShellScrollCtrl created without content!" ); + + mBitmapBounds = new RectI[BtnCount * BtnStateCount]; + AssertFatal( mBitmapBounds, "Failed to allocate memory for bitmap array!" ); + + char buf[256]; + if ( mProfile->mBitmapBase[0] ) + { + bool result; + dSprintf( buf, sizeof( buf ), "%s_vertfield.png", mProfile->mBitmapBase ); + mTexVBar = TextureHandle( buf, BitmapKeepTexture ); + if ( mTexVBar ) + { + result = createBitmapArray( mTexVBar.getBitmap(), mBarBounds, BarStateCount, 3 ); + AssertFatal( result, "Failed to create vertical bar bitmap array for ShellScrollCtrl!" ); + } + + dSprintf( buf, sizeof( buf ), "%s_horzfield.png", mProfile->mBitmapBase ); + mTexHBar = TextureHandle( buf, BitmapKeepTexture ); + if ( mTexHBar ) + { + result = createBitmapArray( mTexHBar.getBitmap(), &mBarBounds[BarHLeft * BarStateCount], BarStateCount, 3 ); + AssertFatal( result, "Failed to create horizontal bar bitmap array for ShellScrollCtrl!" ); + } + + dSprintf( buf, sizeof( buf ), "%s_vertbuttons.png", mProfile->mBitmapBase ); + mTexVButtons = TextureHandle( buf, BitmapKeepTexture ); + if ( mTexVButtons ) + { + result = createBitmapArray( mTexVButtons.getBitmap(), mBitmapBounds, BtnStateCount, 2 ); + AssertFatal( result, "Failed to create vertical button bitmap array for ShellScrollCtrl!" ); + } + + dSprintf( buf, sizeof( buf ), "%s_vertbar.png", mProfile->mBitmapBase ); + mTexVThumb = TextureHandle( buf, BitmapKeepTexture ); + if ( mTexVThumb ) + { + result = createBitmapArray( mTexVThumb.getBitmap(), &mBitmapBounds[VThumbTop * BtnStateCount], BtnStateCount, 3 ); + AssertFatal( result, "Failed to create vertical thumb bitmap array for ShellScrollCtrl!" ); + } + + dSprintf( buf, sizeof( buf ), "%s_horzbuttons.png", mProfile->mBitmapBase ); + mTexHButtons = TextureHandle( buf, BitmapKeepTexture ); + if ( mTexHButtons ) + { + result = createBitmapArray( mTexHButtons.getBitmap(), &mBitmapBounds[LeftBtn * BtnStateCount], BtnStateCount, 2 ); + AssertFatal( result, "Failed to create horizontal button bitmap array for ShellScrollCtrl!" ); + } + + dSprintf( buf, sizeof( buf ), "%s_horzbar.png", mProfile->mBitmapBase ); + mTexHThumb = TextureHandle( buf, BitmapKeepTexture ); + if ( mTexHThumb ) + { + result = createBitmapArray( mTexHThumb.getBitmap(), &mBitmapBounds[HThumbLeft * BtnStateCount], BtnStateCount, 3 ); + AssertFatal( result, "Failed to create horizontal thumb bitmap array for ShellScrollCtrl!" ); + } + + dSprintf( buf, sizeof( buf ), "%s_scale.png", mProfile->mBitmapBase ); + mTexCorner = TextureHandle( buf, BitmapKeepTexture ); + if ( mTexCorner ) + { + result = createBitmapArray( mTexCorner.getBitmap(), &mBitmapBounds[Corner * BtnStateCount], BtnStateCount, 1 ); + AssertFatal( result, "Failed to create corner bitmap array for ShellScrollCtrl!" ); + } + } + + if ( mFieldBase[0] ) + { + dSprintf( buf, sizeof( buf ), "%s_TL.png", mFieldBase ); + mTexLeftTop = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_TM.png", mFieldBase ); + mTexCenterTop = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_TR.png", mFieldBase ); + mTexRightTop = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_ML.png", mFieldBase ); + mTexLeftCenter = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_MM.png", mFieldBase ); + mTexCenter = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_MR.png", mFieldBase ); + mTexRightCenter = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_BL.png", mFieldBase ); + mTexLeftBottom = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_BM.png", mFieldBase ); + mTexCenterBottom = TextureHandle( buf, BitmapTexture ); + dSprintf( buf, sizeof( buf ), "%s_BR.png", mFieldBase ); + mTexRightBottom = TextureHandle( buf, BitmapTexture ); + } + + //init + if ( mTexVBar && mTexVButtons && mTexVThumb ) + { + mBaseThumbSize = mBitmapBounds[VThumbTop * BtnStateCount].extent.y + + mBitmapBounds[VThumbBottom * BtnStateCount].extent.y - ( 2 * mGlowOffset ); + mScrollBarThickness = mBarBounds[BarVCenter * BarStateCount].extent.x; + mScrollBarArrowBtnLength = mBitmapBounds[UpBtn * BtnStateCount].extent.y - ( 2 * mGlowOffset ); + } + + // Ensure minimum extents: + U32 temp; + bool needResize = false; + if ( mTexLeftTop && mTexRightTop ) + { + temp = mTexLeftTop.getWidth() + mTexRightTop.getWidth() + ( 2 * mBorderThickness ); + if ( mMinExtent.x < temp ) + { + mMinExtent.x = temp; + needResize = true; + } + } + + if ( mTexLeftTop && mTexLeftBottom ) + { + temp = mTexLeftTop.getHeight() + mTexLeftBottom.getHeight() + ( 2 * mBorderThickness ); + if ( mMinExtent.y < temp ) + { + mMinExtent.y = temp; + needResize = true; + } + } + + if ( needResize ) + resize( mBounds.point, mBounds.extent ); + + computeSizes(); + + return true; +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::onSleep() +{ + Parent::onSleep(); + + mTexVBar = NULL; + mTexHBar = NULL; + + mTexVButtons = NULL; + mTexVThumb = NULL; + mTexHButtons = NULL; + mTexHThumb = NULL; + mTexCorner = NULL; + + mTexLeftTop = NULL; + mTexCenterTop = NULL; + mTexRightTop = NULL; + mTexLeftCenter = NULL; + mTexCenter = NULL; + mTexRightCenter = NULL; + mTexLeftBottom = NULL; + mTexCenterBottom = NULL; + mTexRightBottom = NULL; + + mMouseOverRegion = None; +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::onMouseUp( const GuiEvent &event ) +{ + Parent::onMouseUp( event ); + onMouseMove( event ); +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::onMouseMove( const GuiEvent &event ) +{ + Region oldRegion = mMouseOverRegion; + Point2I curMousePos = globalToLocalCoord( event.mousePoint ); + mMouseOverRegion = findHitRegion( curMousePos ); + + // Play button over sound: + if ( mActive && mMouseOverRegion != oldRegion && mProfile->mSoundButtonOver + && ( mMouseOverRegion == UpArrow || mMouseOverRegion == DownArrow || mMouseOverRegion == LeftArrow || mMouseOverRegion == RightArrow ) ) + { + F32 pan = (F32(event.mousePoint.x)/ F32(Canvas->mBounds.extent.x)*2.0f-1.0f)*0.8f; + AUDIOHANDLE handle = alxCreateSource(mProfile->mSoundButtonOver); + alxSourcef(handle, AL_PAN, pan); + alxPlay(handle); + } + + setUpdate(); +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::onMouseEnter( const GuiEvent &event ) +{ + onMouseMove( event ); +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::onMouseLeave( const GuiEvent &/*event*/ ) +{ + mMouseOverRegion = None; + setUpdate(); +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::drawBorder( const Point2I &offset, bool /*isFirstResponder*/ ) +{ + RectI r( offset, mBounds.extent ); + r.point += Point2I( mBorderThickness, mBorderThickness ); + r.extent -= Point2I( ( 2 * mBorderThickness ), ( 2 * mBorderThickness ) ); + + // Draw border: + if ( mProfile->mBorder ) + { + dglDrawRect( r, mProfile->mBorderColor ); + r.point += Point2I( 1, 1 ); + r.extent -= Point2I( 2, 2 ); + } + + // Temporary hacky focus effect: + //if ( isFirstResponder ) + //{ + // dglDrawRect( r, ColorI( 255, 255, 0 ) ); + // r.point += Point2I( 1, 1 ); + // r.extent -= Point2I( 2, 2 ); + //} + + // Draw field background: + if ( mHasVScrollBar ) + r.extent.x -= mScrollBarThickness; + if ( mHasHScrollBar ) + r.extent.y -= ( mScrollBarThickness + 1 ); + + if ( mTexLeftTop && mTexCenterTop && mTexRightTop && mTexLeftCenter && mTexCenter && mTexRightCenter + && mTexLeftBottom && mTexCenterBottom && mTexRightBottom ) + { + dglClearBitmapModulation(); + + RectI drawRect = r; + U32 stretchWidth = r.extent.x - mTexLeftTop.getWidth() - mTexRightTop.getWidth(); + U32 topEdgeHeight = mTexLeftTop.getHeight(); + if ( topEdgeHeight > ( r.extent.y - mTexLeftBottom.getHeight() ) ) + topEdgeHeight = r.extent.y - mTexLeftBottom.getHeight(); + + // Draw upper left corner: + drawRect.extent.x = mTexLeftTop.getWidth(); + drawRect.extent.y = topEdgeHeight; + dglDrawBitmapStretch( mTexLeftTop, drawRect ); + + // Draw upper center edge: + drawRect.point.x += drawRect.extent.x; + drawRect.extent.x = stretchWidth; + if ( drawRect.extent.x > 0 ) + dglDrawBitmapStretch( mTexCenterTop, drawRect ); + + // Draw upper right corner: + drawRect.point.x += drawRect.extent.x; + drawRect.extent.x = mTexRightTop.getWidth(); + dglDrawBitmapStretch( mTexRightTop, drawRect ); + + drawRect.point.x = r.point.x; + drawRect.point.y += drawRect.extent.y; + drawRect.extent.y = r.extent.y - drawRect.extent.y - mTexLeftBottom.getHeight(); + if ( drawRect.extent.y > 0 ) + { + // Draw center left edge: + drawRect.extent.x = mTexLeftCenter.getWidth(); + dglDrawBitmapStretch( mTexLeftCenter, drawRect ); + + // Draw center: + drawRect.point.x += drawRect.extent.x; + drawRect.extent.x = stretchWidth; + if ( drawRect.extent.x > 0 ) + dglDrawBitmapStretch( mTexCenter, drawRect ); + + // Draw center right edge: + drawRect.point.x += drawRect.extent.x; + drawRect.extent.x = mTexRightCenter.getWidth(); + dglDrawBitmapStretch( mTexRightCenter, drawRect ); + } + + // Draw bottom left corner: + drawRect.point.x = r.point.x; + drawRect.point.y += drawRect.extent.y; + dglDrawBitmap( mTexLeftBottom, drawRect.point ); + + // Draw bottom center edge: + drawRect.point.x += mTexLeftBottom.getWidth(); + drawRect.extent.x = stretchWidth; + drawRect.extent.y = mTexCenterBottom.getHeight(); + if ( drawRect.extent.x > 0 ) + dglDrawBitmapStretch( mTexCenterBottom, drawRect ); + + // Draw bottom right corner: + drawRect.point.x += drawRect.extent.x; + dglDrawBitmap( mTexRightBottom, drawRect.point ); + } +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::drawVScrollBar( const Point2I &offset ) +{ + if ( !mTexVBar || !mTexVButtons || !mTexVThumb ) + return; + + RectI drawRect; + drawRect.point = offset + mUpArrowRect.point - Point2I( mGlowOffset, mGlowOffset ); + dglClearBitmapModulation(); + + // Draw the up arrow button: + U32 state = mVBarEnabled ? ( mMouseOverRegion == UpArrow ? ( stateDepressed ? StatePressed : StateRollover ) : StateNormal ) : StateDisabled; + U32 bitmap = UpBtn * BtnStateCount + state; + dglDrawBitmapSR( mTexVButtons, drawRect.point, mBitmapBounds[bitmap] ); + + // Draw the scroll bar: + state = mVBarEnabled ? BarNormal : BarDisabled; + + // Draw top edge: + drawRect.point += Point2I( mGlowOffset, mGlowOffset ); + drawRect.point.y += mScrollBarArrowBtnLength; + bitmap = BarVTop * BarStateCount + state; + dglDrawBitmapSR( mTexVBar, drawRect.point, mBarBounds[bitmap] ); + + // Draw the center section: + drawRect.point.y += mBarBounds[bitmap].extent.y; + drawRect.extent.x = mScrollBarThickness; + drawRect.extent.y = mDownArrowRect.point.y - mScrollBarArrowBtnLength - mBarBounds[bitmap].extent.y - mBarBounds[BarStateCount * BarVBottom].extent.y; + if ( drawRect.extent.y > 0 ) + { + bitmap = BarVCenter * BarStateCount + state; + dglDrawBitmapStretchSR( mTexVBar, drawRect, mBarBounds[bitmap] ); + } + + // Draw the bottom edge: + drawRect.point.y += drawRect.extent.y; + bitmap = BarVBottom * BarStateCount + state; + dglDrawBitmapSR( mTexVBar, drawRect.point, mBarBounds[bitmap] ); + + // Draw the thumb (if enabled): + if ( mVBarEnabled ) + { + drawRect.point.y = offset.y + mVThumbPos; + drawRect.point -= Point2I( mGlowOffset, mGlowOffset ); + state = ( mMouseOverRegion == VertThumb ) ? ( stateDepressed ? StatePressed : StateRollover ) : StateNormal; + + // Draw top cap: + bitmap = VThumbTop * BtnStateCount + state; + dglDrawBitmapSR( mTexVThumb, drawRect.point, mBitmapBounds[bitmap] ); + + // Draw center section: + drawRect.point.y += mBitmapBounds[bitmap].extent.y; + drawRect.extent.y = mVThumbSize - mBitmapBounds[bitmap].extent.y - mBitmapBounds[VThumbBottom * BtnStateCount].extent.y + ( 2 * mGlowOffset ); + if ( drawRect.extent.y > 0 ) + { + bitmap = VThumb * BtnStateCount + state; + drawRect.extent.x = mBitmapBounds[bitmap].extent.x; + dglDrawBitmapStretchSR( mTexVThumb, drawRect, mBitmapBounds[bitmap] ); + } + + // Draw the bottom cap: + drawRect.point.y += drawRect.extent.y; + bitmap = VThumbBottom * BtnStateCount + state; + dglDrawBitmapSR( mTexVThumb, drawRect.point, mBitmapBounds[bitmap] ); + } + + // Draw the down button: + drawRect.point = offset + mDownArrowRect.point - Point2I( mGlowOffset, mGlowOffset ); + state = mVBarEnabled ? ( mMouseOverRegion == DownArrow ? ( stateDepressed ? StatePressed : StateRollover ) : StateNormal ) : StateDisabled; + bitmap = DownBtn * BtnStateCount + state; + dglDrawBitmapSR( mTexVButtons, drawRect.point, mBitmapBounds[bitmap] ); +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::drawHScrollBar( const Point2I &offset ) +{ + if ( !mTexHBar || !mTexHButtons || !mTexHThumb ) + return; + + RectI drawRect; + drawRect.point = offset + mLeftArrowRect.point - Point2I( mGlowOffset, mGlowOffset ); + dglClearBitmapModulation(); + + // Draw the left arrow button: + U32 state = mHBarEnabled ? ( mMouseOverRegion == LeftArrow ? ( stateDepressed ? StatePressed : StateRollover ) : StateNormal ) : StateDisabled; + U32 bitmap = LeftBtn * BtnStateCount + state; + dglDrawBitmapSR( mTexHButtons, drawRect.point, mBitmapBounds[bitmap] ); + + // Draw the scroll bar: + state = mHBarEnabled ? BarNormal : BarDisabled; + + // Draw left edge: + drawRect.point += Point2I( mScrollBarArrowBtnLength + mGlowOffset, mGlowOffset ); + bitmap = BarHLeft * BarStateCount + state; + dglDrawBitmapSR( mTexHBar, drawRect.point, mBarBounds[bitmap] ); + + // Draw the center section: + drawRect.point.x += mBarBounds[bitmap].extent.x; + drawRect.extent.x = mRightArrowRect.point.x - mScrollBarArrowBtnLength - mBarBounds[bitmap].extent.x - mBarBounds[BarHRight * BarStateCount].extent.x; + if ( drawRect.extent.x > 0 ) + { + drawRect.extent.y = mScrollBarThickness; + bitmap = BarHCenter * BarStateCount + state; + dglDrawBitmapStretchSR( mTexHBar, drawRect, mBarBounds[bitmap] ); + } + + // Draw the right edge: + drawRect.point.x += drawRect.extent.x; + bitmap = BarHRight * BarStateCount + state; + dglDrawBitmapSR( mTexHBar, drawRect.point, mBarBounds[bitmap] ); + + // Draw the thumb (if enabled): + if ( mHBarEnabled ) + { + drawRect.point.x = offset.x + mHThumbPos; + drawRect.point -= Point2I( mGlowOffset, mGlowOffset ); + state = ( mMouseOverRegion == HorizThumb ) ? ( stateDepressed ? StatePressed : StateRollover ) : StateNormal; + + // Draw left cap: + bitmap = HThumbLeft * BtnStateCount + state; + dglDrawBitmapSR( mTexHThumb, drawRect.point, mBitmapBounds[bitmap] ); + + // Draw center section: + drawRect.point.x += mBitmapBounds[bitmap].extent.x; + drawRect.extent.x = mHThumbSize - mBitmapBounds[bitmap].extent.x - mBitmapBounds[HThumbRight * BtnStateCount].extent.x + ( 2 * mGlowOffset ); + if ( drawRect.extent.x > 0 ) + { + bitmap = HThumb * BtnStateCount + state; + drawRect.extent.y = mBitmapBounds[bitmap].extent.y; + dglDrawBitmapStretchSR( mTexHThumb, drawRect, mBitmapBounds[bitmap] ); + } + + // Draw the right cap: + drawRect.point.x += drawRect.extent.x; + bitmap = HThumbRight * BtnStateCount + state; + dglDrawBitmapSR( mTexHThumb, drawRect.point, mBitmapBounds[bitmap] ); + } + + // Draw the right button: + drawRect.point = offset + mRightArrowRect.point - Point2I( mGlowOffset, mGlowOffset ); + state = mHBarEnabled ? ( mMouseOverRegion == RightArrow ? ( stateDepressed ? StatePressed : StateRollover ) : StateNormal ) : StateDisabled; + bitmap = RightBtn * BtnStateCount + state; + dglDrawBitmapSR( mTexHButtons, drawRect.point, mBitmapBounds[bitmap] ); +} + +//------------------------------------------------------------------------------ +void ShellScrollCtrl::drawScrollCorner( const Point2I &offset ) +{ + if ( mTexCorner ) + { + Point2I drawPos = offset + mRightArrowRect.point - Point2I( mGlowOffset, mGlowOffset ); + drawPos.x += mRightArrowRect.extent.x; + dglClearBitmapModulation(); + dglDrawBitmapSR( mTexCorner, drawPos, mBitmapBounds[Corner * BtnStateCount + StateDisabled] ); + } +} diff --git a/shell/shellScrollCtrl.h b/shell/shellScrollCtrl.h new file mode 100644 index 0000000..3eb06bf --- /dev/null +++ b/shell/shellScrollCtrl.h @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHELLSCROLLCTRL_H_ +#define _SHELLSCROLLCTRL_H_ + +#ifndef _GUISCROLLCTRL_H_ +#include "GUI/guiScrollCtrl.h" +#endif + +class ShellScrollCtrl : public GuiScrollCtrl +{ + private: + typedef GuiScrollCtrl Parent; + + protected: + enum BarIndices + { + BarVTop = 0, + BarVCenter, + BarVBottom, + BarHLeft, + BarHCenter, + BarHRight, + + BarCount + }; + + enum BarStates + { + BarNormal = 0, + BarDisabled, + + BarStateCount + }; + + RectI mBarBounds[BarCount * BarStateCount]; + TextureHandle mTexVBar; + TextureHandle mTexHBar; + + enum BtnIndices + { + UpBtn = 0, + DownBtn, + VThumbTop, + VThumb, + VThumbBottom, + LeftBtn, + RightBtn, + HThumbLeft, + HThumb, + HThumbRight, + Corner, + + BtnCount + }; + + enum BtnStates + { + StateNormal = 0, + StatePressed, + StateRollover, + StateDisabled, + + BtnStateCount + }; + + TextureHandle mTexVButtons; + TextureHandle mTexVThumb; + TextureHandle mTexHButtons; + TextureHandle mTexHThumb; + TextureHandle mTexCorner; + + public: + StringTableEntry mFieldBase; + + protected: + TextureHandle mTexLeftTop; + TextureHandle mTexCenterTop; + TextureHandle mTexRightTop; + TextureHandle mTexLeftCenter; + TextureHandle mTexCenter; + TextureHandle mTexRightCenter; + TextureHandle mTexLeftBottom; + TextureHandle mTexCenterBottom; + TextureHandle mTexRightBottom; + + Region mMouseOverRegion; + U32 mGlowOffset; + + //bool calcChildExtents( Point2I* pos, Point2I* ext ); + + public: + DECLARE_CONOBJECT(ShellScrollCtrl); + ShellScrollCtrl(); + + static void initPersistFields(); + + bool onWake(); + void onSleep(); + + U32 getGlowOffset() { return( mGlowOffset); } + + void onMouseUp( const GuiEvent &event ); + void onMouseMove( const GuiEvent &event ); + void onMouseEnter( const GuiEvent &event ); + void onMouseLeave( const GuiEvent &event ); + + void drawBorder( const Point2I &offset, bool isFirstResponder ); + void drawVScrollBar( const Point2I &offset ); + void drawHScrollBar( const Point2I &offset ); + void drawScrollCorner( const Point2I &offset ); +}; + +#endif // _SHELL_SCROLLCTRL_H diff --git a/shell/shellTextEditCtrl.cc b/shell/shellTextEditCtrl.cc new file mode 100644 index 0000000..3a5eb71 --- /dev/null +++ b/shell/shellTextEditCtrl.cc @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "shell/shellTextEditCtrl.h" +#include "console/consoleTypes.h" +#include "dgl/dgl.h" + +IMPLEMENT_CONOBJECT(ShellTextEditCtrl); + +//------------------------------------------------------------------------------ +ShellTextEditCtrl::ShellTextEditCtrl() : GuiTextEditCtrl() +{ + dMemset( mBitmapBounds, 0, sizeof( mBitmapBounds ) ); + mTexField = NULL; + mGlowOffset.set( 9, 9 ); +} + + +//------------------------------------------------------------------------------ +void ShellTextEditCtrl::initPersistFields() +{ + Parent::initPersistFields(); + addField( "glowOffset", TypePoint2I, Offset( mGlowOffset, ShellTextEditCtrl ) ); +} + + +//------------------------------------------------------------------------------ +bool ShellTextEditCtrl::onWake() +{ + // Set the default profile on start-up: + if ( !mProfile ) + { + SimObject *obj = Sim::findObject("NewTextEditProfile"); + if ( obj ) + mProfile = dynamic_cast( obj ); + } + + if ( !Parent::onWake() ) + return false; + + mTexField = mProfile->mTextureHandle; + + if ( mTexField ) + { + bool result = createBitmapArray( mTexField.getBitmap(), mBitmapBounds, StateCount, BmpCount ); + AssertFatal( result, "ShellTextEditCtrl failed to create bitmap array!" ); + + // Set minimum extents: + mMinExtent.x = mBitmapBounds[StateCount * BmpLeft].extent.x + mBitmapBounds[StateCount * BmpRight].extent.x; + mMinExtent.y = mBitmapBounds[StateCount * BmpLeft].extent.y; + resize( mBounds.point, mBounds.extent ); + } + + return true; +} + + +//------------------------------------------------------------------------------ +void ShellTextEditCtrl::onSleep() +{ + Parent::onSleep(); + mTexField = NULL; +} + + +//------------------------------------------------------------------------------ +void ShellTextEditCtrl::onRender( Point2I offset, const RectI &updateRect, GuiControl* firstResponder ) +{ + // Draw the bitmap background: + RectI drawRect( offset, mBounds.extent ); + + if ( mTexField ) + { + // Differentiate between normal and active: + U32 state; + if ( mActive ) + { + state = ( firstResponder == this ) ? StateActive : StateNormal; + dglClearBitmapModulation(); + } + else + { + state = StateNormal; + dglSetBitmapModulation( ColorI( 128, 128, 128 ) ); + } + + // Draw the left edge: + U32 bitmap = StateCount * BmpLeft + state; + dglDrawBitmapSR( mTexField, drawRect.point, mBitmapBounds[bitmap] ); + + // Draw the center section: + bitmap = StateCount * BmpCenter + state; + drawRect.point.x += mBitmapBounds[bitmap].extent.x; + drawRect.extent.x = mBounds.extent.x - mBitmapBounds[StateCount * BmpLeft].extent.x - mBitmapBounds[StateCount * BmpRight].extent.x; + drawRect.extent.y = mBitmapBounds[bitmap].extent.y; + if ( drawRect.extent.x > 0 ) + dglDrawBitmapStretchSR( mTexField, drawRect, mBitmapBounds[bitmap] ); + + // Draw the right edge: + bitmap = StateCount * BmpRight + state; + drawRect.point.x += drawRect.extent.x; + dglDrawBitmapSR( mTexField, drawRect.point, mBitmapBounds[bitmap] ); + } + + // Draw the text: + drawRect.point.y = offset.y + mGlowOffset.y; + drawRect.extent.y -= ( 2 * mGlowOffset.y ); + drawRect.point.x = offset.x + mBitmapBounds[BmpLeft].extent.x; + RectI clipRect( drawRect.point, drawRect.extent ); + if ( clipRect.intersect( updateRect ) ) + { + dglSetClipRect( clipRect ); + DrawText( drawRect, ( firstResponder == this ) ); + } + + + // Draw border (if any): + if ( mProfile->mBorder ) + dglDrawRect( RectI( offset, mBounds.extent ), mProfile->mBorderColor ); +} diff --git a/shell/shellTextEditCtrl.h b/shell/shellTextEditCtrl.h new file mode 100644 index 0000000..0f93eab --- /dev/null +++ b/shell/shellTextEditCtrl.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHELLTEXTEDITCTRL_H_ +#define _SHELLTEXTEDITCTRL_H_ + +#ifndef _GUITEXTEDITCTRL_H_ +#include "GUI/guiTextEditCtrl.h" +#endif + +class ShellTextEditCtrl : public GuiTextEditCtrl +{ + private: + typedef GuiTextEditCtrl Parent; + + protected: + enum BitmapIndices + { + BmpLeft, + BmpCenter, + BmpRight, + + BmpCount + }; + + enum BitmapStates + { + StateNormal, + StateActive, + + StateCount + }; + + RectI mBitmapBounds[BmpCount * StateCount]; + TextureHandle mTexField; + Point2I mGlowOffset; + + public: + DECLARE_CONOBJECT(ShellTextEditCtrl); + ShellTextEditCtrl(); + + static void initPersistFields(); + + bool onWake(); + void onSleep(); + + void onRender( Point2I offset, const RectI &updateRect, GuiControl *firstResponder ); +}; + +#endif // _SHELL_TEXTEDITCTRL_H diff --git a/sim/actionMap.cc b/sim/actionMap.cc new file mode 100644 index 0000000..d1b62af --- /dev/null +++ b/sim/actionMap.cc @@ -0,0 +1,1637 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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/fileStream.h" +#include "core/resManager.h" + +IMPLEMENT_CONOBJECT(ActionMap); + +// This is used for determing keys that have ascii codes for the foreign keyboards. IsAlpha doesn't work on foreign keys. +#define dIsDecentChar(c) (((char(0xa0) <= (c)) && ((c) <= char(0xff))) || (( char(0x21) <= (c)) && ((c) <= char(0x7e))) || (( char(0x91) <= (c)) && ((c) <= char(0x92)))) + +struct CodeMapping +{ + const char* pDescription; + U8 type; + U32 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 ( !ResourceManager->openFileForWrite( iostrm, ResourceManager->getBasePath(), fileName, append ? FileStream::WriteAppend : FileStream::Write ) ) + { + 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, "// Tribes 2 Input Map File\n" ); + iostrm.write( dStrlen( lineBuffer ), lineBuffer ); + } + + dSprintf(lineBuffer, 1023, "%s.delete();\n" + "new ActionMap(%s);\n", 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, ", \"%f %f\"", rNode.deadZoneBegin, rNode.deadZoneEnd); + dStrcat(lineBuffer, buff); + } + + if (rNode.flags & Node::HasScale) { + char buff[64]; + dSprintf(buff, 63, ", %f", rNode.scaleFactor); + dStrcat(lineBuffer, buff); + } + + if (rNode.flags & Node::BindCmd) { + if (rNode.makeConsoleCommand) { + dStrcat(lineBuffer, ", \""); + dStrcat(lineBuffer, rNode.makeConsoleCommand); + dStrcat(lineBuffer, "\""); + } else { + dStrcat(lineBuffer, ", \"\""); + } + if (rNode.breakConsoleCommand) { + dStrcat(lineBuffer, ", \""); + dStrcat(lineBuffer, rNode.breakConsoleCommand); + dStrcat(lineBuffer, "\""); + } + else + dStrcat(lineBuffer, ", \"\""); + } else { + dStrcat(lineBuffer, ", "); + dStrcat(lineBuffer, rNode.consoleFunction); + } + + dStrcat(lineBuffer, ");\n"); + iostrm.write(dStrlen(lineBuffer), lineBuffer); + } + } + + iostrm.close(); + } + 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, ", \"%f %f\"", rNode.deadZoneBegin, rNode.deadZoneEnd); + dStrcat(finalBuffer, buff); + } + + if (rNode.flags & Node::HasScale) { + char buff[64]; + dSprintf(buff, 63, ", %f", 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; + } + + 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?"); + + 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) +{ + // 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) { + 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; + + return pRetNode; +} + +//------------------------------------------------------------------------------ +void ActionMap::removeNode(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; + + U32 realMods = inModifiers; + if (realMods & SI_SHIFT) + realMods |= SI_SHIFT; + if (realMods & SI_CTRL) + realMods |= SI_CTRL; + if (realMods & SI_ALT) + realMods |= SI_ALT; + + for (i = 0; i < pDeviceMap->nodeMap.size(); i++) { + if (pDeviceMap->nodeMap[i].modifiers == realMods && + pDeviceMap->nodeMap[i].action == inAction) { + 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; + + for (i = 0; i < pDeviceMap->nodeMap.size(); i++) + { + if (pDeviceMap->nodeMap[i].action == KEY_ANYKEY && pDeviceMap->nodeMap[i].modifiers == realMods && dIsDecentChar(inAction)) + return &pDeviceMap->nodeMap[i]; + else 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 ) +{ + // Loop through all of the existing nodes to find the one mapped to the + // given function: + for ( U32 i = 0; i < mDeviceMaps.size(); i++ ) + { + const DeviceMap* dvcMap = mDeviceMaps[i]; + + for ( U32 j = 0; 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 ); + } + } + } + + return( false ); +} + +//------------------------------------------------------------------------------ +bool ActionMap::processUnbind(const char *device, const char *action) +{ + 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); + 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 ) +{ + U32 devMapIndex, nodeIndex; + if ( findBoundNode( command, devMapIndex, nodeIndex ) ) + { + char buffer[256]; + + const DeviceMap* deviceMap = mDeviceMaps[devMapIndex]; + char deviceBuffer[32]; + if ( !getDeviceName( deviceMap->deviceType, deviceMap->deviceInst, deviceBuffer ) ) + return( "" ); + + const Node* node = &deviceMap->nodeMap[nodeIndex]; + const char* modifierString = getModifierString( node->modifiers ); + + char keyBuffer[64]; + if ( !getKeyString( node->action, keyBuffer ) ) + return( "" ); + + dSprintf( buffer, sizeof( buffer ), "%s\t%s%s", deviceBuffer, modifierString, keyBuffer ); + + // Copy the buffer and return it: + char* returnString = Con::getReturnBuffer( dStrlen( buffer ) + 1 ); + dStrcpy( returnString, buffer ); + return( returnString ); + } + + return( "" ); +} + +//------------------------------------------------------------------------------ +// 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 ), "%f %f", 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 InputEvent* 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 { + 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; + + 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; + + switch (realModifiers & (SI_SHIFT|SI_CTRL|SI_ALT)) { + case (SI_SHIFT|SI_CTRL|SI_ALT): + return "shift-ctrl-alt "; + + case (SI_SHIFT|SI_CTRL): + return "shift-ctrl "; + + case (SI_SHIFT|SI_ALT): + return "shift-alt "; + + case (SI_CTRL|SI_ALT): + return "ctrl-alt "; + + case (SI_SHIFT): + return "shift "; + + case (SI_CTRL): + return "ctrl "; + + case (SI_ALT): + return "alt "; + + 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 = 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; + } + + // 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(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) +{ + // 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; + + 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], "%f %f", &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; + } + } + + // We have decided to only allow one bind per console function, so + // remove any existing nodes that are bound to the given function: + if ( pFnName[0] ) + { + U32 devMapIndex, nodeIndex; + while ( findBoundNode( pFnName, devMapIndex, nodeIndex ) ) + { + dFree( mDeviceMaps[devMapIndex]->nodeMap[nodeIndex].makeConsoleCommand ); + dFree( mDeviceMaps[devMapIndex]->nodeMap[nodeIndex].breakConsoleCommand ); + mDeviceMaps[devMapIndex]->nodeMap.erase( nodeIndex ); + } + } + + // 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); + + pBindNode->flags = assignedFlags; + pBindNode->deadZoneBegin = deadZoneBegin; + pBindNode->deadZoneEnd = deadZoneEnd; + pBindNode->scaleFactor = scaleFactor; + pBindNode->consoleFunction = StringTable->insert(pFnName); + + return true; +} + +//------------------------------------------------------------------------------ +bool ActionMap::processAction(const InputEvent* pEvent) +{ + 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; + } + + // 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; + + // 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); + Con::execute(2, argv); + } + // + // And enter the break into the table if this is a make event... + enterBreakEvent(pEvent, pNode); + + return true; + } else if (pEvent->action == SI_MOVE) { + if (pEvent->deviceType == MouseDeviceType) { + const Node* pNode = findNode(pEvent->deviceType, pEvent->deviceInst, + pEvent->modifier, pEvent->objType); + + 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->objType); + + 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); + Con::execute(2, argv); + + return true; + } else + { + // Joystick events... + const Node* pNode = findNode( pEvent->deviceType, pEvent->deviceInst, + pEvent->modifier, pEvent->objType ); + + 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->objType ); + + 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; + + // Ok, we're all set up, call the function. + argv[0] = pNode->consoleFunction; + argv[1] = Con::getFloatArg( value ); + Con::execute( 2, argv ); + + return true; + } + } else if (pEvent->action == SI_BREAK) { + return checkBreakTable(pEvent); + } + + return false; +} + +//------------------------------------------------------------------------------ +void ActionMap::enterBreakEvent(const InputEvent* 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].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 InputEvent* 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)) { + // Match. Issue the break event... + // + F32 value = pEvent->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; + + // 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); + Con::execute(2,argv); + } + } + else if(smBreakTable[i].breakConsoleCommand) + { + Con::evaluate(smBreakTable[i].breakConsoleCommand); + dFree(smBreakTable[i].breakConsoleCommand); + } + smBreakTable.erase(i); + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------ +bool ActionMap::handleEvent(const InputEvent* 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 InputEvent* 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); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Console stuff + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, bind, void, 5, 10, "actionMap.bind( device, action, [modifier spec, mod...], command )" ) +{ + static_cast( object )->processBind( argc - 2, argv + 2 ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, bindCmd, void, 6, 6, "actionMap.bindCmd( device, action, makeCmd, breakCmd )" ) +{ + argc; + static_cast( object )->processBindCmd( argv[2], argv[3], argv[4], argv[5] ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, unbind, void, 4, 4, "actionMap.unbind( device, action )" ) +{ + argc; + static_cast( object )->processUnbind( argv[2], argv[3] ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, save, void, 2, 4, "actionMap.save( [fileName], [append] )" ) +{ + AssertFatal( dynamic_cast( object ), "Error, how did a non-actionmap get here?" ); + ActionMap* pMap = static_cast( object ); + + const char* fileName = argc > 2 ? argv[2] : NULL; + bool append = argc > 3 ? dAtob(argv[3]) : false; + + pMap->dumpActionMap( fileName, append ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, push, void, 2, 2, "actionMap.push()" ) +{ + argc; argv; + ActionMap *pMap = static_cast( object ); + SimSet* pActionMapSet = Sim::getActiveActionMapSet(); + pActionMapSet->pushObject( pMap ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, pop, void, 2, 2, "actionMap.pop()" ) +{ + argc; argv; + ActionMap *pMap = static_cast( object ); + SimSet* pActionMapSet = Sim::getActiveActionMapSet(); + pActionMapSet->removeObject( pMap ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, getBinding, const char*, 3, 3, "actionMap.getBinding( command )" ) +{ + argc; + return( static_cast( object )->getBinding( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, getCommand, const char*, 4, 4, "actionMap.getCommand( device, action )" ) +{ + argc; + return( static_cast( object )->getCommand( argv[2], argv[3] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, isInverted, bool, 4, 4, "actionMap.isInverted( device, action )" ) +{ + argc; + return( static_cast( object )->isInverted( argv[2], argv[3] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, getScale, F32, 4, 4, "actionMap.getScale( device, action )" ) +{ + argc; + return( static_cast( object )->getScale( argv[2], argv[3] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, getDeadZone, const char*, 4, 4, "actionMap.getDeadZone( device, action )" ) +{ + argc; + return( static_cast( 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 }, + + { "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_MOVE, SI_XAXIS }, + { "yaxis", SI_MOVE, SI_YAXIS }, + { "zaxis", SI_MOVE, SI_ZAXIS }, + { "rxaxis", SI_MOVE, SI_RXAXIS }, + { "ryaxis", SI_MOVE, SI_RYAXIS }, + { "rzaxis", SI_MOVE, SI_RZAXIS }, + { "slider", SI_MOVE, 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 }, + + //-------------------------------------- MISCELLANEOUS EVENTS + // + + { "anykey", SI_KEY, KEY_ANYKEY }, + { "nomatch", SI_UNKNOWN, 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 } +}; + + +////Device Event Types +//#define SI_UNKNOWN 0x01 +//#define SI_BUTTON 0x02 +//#define SI_POV 0x03 +//#define SI_XPOV 0x04 +//#define SI_YPOV 0x05 +//#define SI_UPOV 0x06 +//#define SI_DPOV 0x07 +//#define SI_LPOV 0x08 +//#define SI_RPOV 0x09 +//#define SI_KEY 0x0A +//#define SI_XAXIS 0x0B +//#define SI_YAXIS 0x0C +//#define SI_ZAXIS 0x0D +//#define SI_RXAXIS 0x0E +//#define SI_RYAXIS 0x0F +//#define SI_RZAXIS 0x10 +//#define SI_SLIDER 0x11 diff --git a/sim/actionMap.h b/sim/actionMap.h new file mode 100644 index 0000000..18369bc --- /dev/null +++ b/sim/actionMap.h @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ACTIONMAP_H_ +#define _ACTIONMAP_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +struct InputEvent; + +struct EventDescriptor +{ + U8 flags; // Combination of any Modifiers + U8 eventType; // SI_KEY, etc. + U16 eventCode; // From event.h +}; + +class ActionMap : public SimObject +{ + typedef SimObject Parent; + + protected: + bool onAdd(); + + struct Node { + U32 modifiers; + U32 action; + + enum Flags { + Ranged = 1 << 0, + HasScale = 1 << 1, + HasDeadZone = 1 << 2, + Inverted = 1 << 3, + BindCmd = 1 << 4 + }; + + U32 flags; + F32 deadZoneBegin; + F32 deadZoneEnd; + F32 scaleFactor; + + StringTableEntry consoleFunction; + + char *makeConsoleCommand; + char *breakConsoleCommand; + }; + struct DeviceMap + { + U32 deviceType; + U32 deviceInst; + + Vector nodeMap; + DeviceMap() { + VECTOR_SET_ASSOCIATION(nodeMap); + } + ~DeviceMap(); + }; + struct BreakEntry + { + U32 deviceType; + U32 deviceInst; + U32 objInst; + 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 ); + Node* getNode(const U32 inDeviceType, const U32 inDeviceInst, + const U32 inModifiers, const U32 inAction); + + void removeNode(const U32 inDeviceType, const U32 inDeviceInst, + const U32 inModifiers, const U32 inAction); + + void enterBreakEvent(const InputEvent* pEvent, const Node* pNode); + + static const char* getModifierString(const U32 modifiers); + + public: + ActionMap(); + ~ActionMap(); + + void dumpActionMap(const char* fileName, const bool append) const; + + static bool createEventDescriptor(const char* pEventString, EventDescriptor* pDescriptor); + //static void consoleInit(); + + bool processBind(const U32 argc, const char** argv); + bool processBindCmd(const char *device, const char *action, const char *makeCmd, const char *breakCmd); + bool processUnbind(const char *device, const char *action); + + // 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 InputEvent* event ); + + bool processAction(const InputEvent*); + + static bool checkBreakTable(const InputEvent*); + static bool handleEvent(const InputEvent*); + static bool handleEventGlobal(const InputEvent*); + + static bool getDeviceTypeAndInstance(const char *device, U32 &deviceType, U32 &deviceInstance); + + DECLARE_CONOBJECT(ActionMap); +}; + +#endif // _ACTIONMAP_H_ diff --git a/sim/cannedChatDataBlock.cc b/sim/cannedChatDataBlock.cc new file mode 100644 index 0000000..7b6f75d --- /dev/null +++ b/sim/cannedChatDataBlock.cc @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Sim/cannedChatDataBlock.h" +#include "console/consoleTypes.h" + +//-------------------------------------------------------------------------- +static const char* getDataTypeCannedChatItemPtr( void* dptr, EnumTable*, BitSet32 ) +{ + CannedChatItem** obj = reinterpret_cast( dptr ); + return ( *obj ? (*obj)->getName() : "" ); +} + + +static void setDataTypeCannedChatItemPtr( void* dptr, S32 argc, const char** argv, EnumTable*, BitSet32 ) +{ + if ( argc == 1 ) + { + if ( !Sim::findObject( argv[0], *reinterpret_cast( dptr ) ) ) + Con::printf( "Object \"%s\" is not a CannedChatItem data block", argv[0] ); + } + else + Con::printf( "(TypeCannedChatItemPtr) Can't set multiple args to a single pointer." ); +} + + +//-------------------------------------------------------------------------- +IMPLEMENT_CO_DATABLOCK_V1( CannedChatItem ); +CannedChatItem::CannedChatItem() +{ + mName = NULL; + mText = NULL; + mAudioFile = NULL; + mAnimation = NULL; + mTeamOnly = false; + mPlay3D = false; +} + + +//-------------------------------------------------------------------------- +void CannedChatItem::consoleInit() +{ + addField( "name", TypeString, Offset( mName, CannedChatItem ) ); + addField( "text", TypeString, Offset( mText, CannedChatItem ) ); + addField( "audioFile", TypeString, Offset( mAudioFile, CannedChatItem ) ); + addField( "animation", TypeString, Offset( mAnimation, CannedChatItem ) ); + // more? + addField( "teamOnly", TypeBool, Offset( mTeamOnly, CannedChatItem ) ); + addField( "play3D", TypeBool, Offset( mPlay3D, CannedChatItem ) ); + Con::registerType( TypeCannedChatItemPtr, sizeof( CannedChatItem* ), getDataTypeCannedChatItemPtr, setDataTypeCannedChatItemPtr ); +} + + +//-------------------------------------------------------------------------- +void CannedChatItem::initPersistFields() +{ + Parent::initPersistFields(); +} + + +//-------------------------------------------------------------------------- +bool CannedChatItem::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + return true; +} diff --git a/sim/cannedChatDataBlock.h b/sim/cannedChatDataBlock.h new file mode 100644 index 0000000..59762fe --- /dev/null +++ b/sim/cannedChatDataBlock.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CANNEDCHATDATABLOCK_H_ +#define _CANNEDCHATDATABLOCK_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +struct CannedChatItem : public SimDataBlock +{ + typedef SimDataBlock Parent; + + StringTableEntry mName; + StringTableEntry mText; + StringTableEntry mAudioFile; + StringTableEntry mAnimation; + bool mTeamOnly; + bool mPlay3D; + // more... + + CannedChatItem(); + DECLARE_CONOBJECT( CannedChatItem ); + static void consoleInit(); + static void initPersistFields(); + virtual bool onAdd(); +}; + +#endif // _H_CANNEDCHATDATABLOCK_ diff --git a/sim/decalManager.cc b/sim/decalManager.cc new file mode 100644 index 0000000..8fe14c2 --- /dev/null +++ b/sim/decalManager.cc @@ -0,0 +1,392 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Sim/decalManager.h" +#include "PlatformWin32/platformGL.h" +#include "dgl/dgl.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "ts/tsShapeInstance.h" +#include "Core/bitStream.h" +#include "console/consoleTypes.h" + +#define DML_DIR "textures/special/" + +bool DecalManager::smDecalsOn = true; + +const U32 DecalManager::csmFreePoolBlockSize = 256; +U32 DecalManager::smMaxNumDecals = 256; +U32 DecalManager::smDecalTimeout = 5000; + +DecalManager* gDecalManager = NULL; +IMPLEMENT_CONOBJECT(DecalManager); +IMPLEMENT_CO_DATABLOCK_V1(DecalData); + +namespace { + +int QSORT_CALLBACK cmpDecalInstance(const void* p1, const void* p2) +{ + const DecalInstance** pd1 = (const DecalInstance**)p1; + const DecalInstance** pd2 = (const DecalInstance**)p2; + + return S32((*pd1)->decalData) - S32((*pd2)->decalData); +} + +} // namespace {} + + +//-------------------------------------------------------------------------- +DecalData::DecalData() +{ + sizeX = 1; + sizeY = 1; + textureName = ""; +} + +DecalData::~DecalData() +{ + if(gDecalManager) + gDecalManager->dataDeleted(this); +} + + +void DecalData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(sizeX); + stream->write(sizeY); + stream->writeString(textureName); +} + +void DecalData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&sizeX); + stream->read(&sizeY); + textureName = stream->readSTString(); +} + +bool DecalData::preload(bool server, char errorBuffer[256]) +{ + if (Parent::preload(server, errorBuffer) == false) + return false; + + if (sizeX < 0.0) { + Con::warnf("DecalData::preload: sizeX < 0"); + sizeX = 0; + } + if (sizeY < 0.0) { + Con::warnf("DecalData::preload: sizeX < 0"); + sizeY = 0; + } + if (textureName == NULL || textureName[0] == '\0') { + Con::errorf("No texture name for decal!"); + return false; + } + + if (!server) { + textureHandle = TextureHandle(textureName, MeshTexture); + if (textureHandle.getGLName() == 0) { + Con::errorf("Unable to load texture: %s for decal!", textureName); + return false; + } + } + + return true; +} + +IMPLEMENT_SETDATATYPE(DecalData) +IMPLEMENT_GETDATATYPE(DecalData) + +void DecalData::initPersistFields() +{ + Con::registerType(TypeDecalDataPtr, sizeof(DecalData*), + REF_GETDATATYPE(DecalData), + REF_SETDATATYPE(DecalData)); + + addField("sizeX", TypeF32, Offset(sizeX, DecalData)); + addField("sizeY", TypeF32, Offset(sizeY, DecalData)); + addField("textureName", TypeString, Offset(textureName, DecalData)); +} + + +DecalManager::DecalManager() +{ + mTypeMask |= DecalManagerObjectType; + + mObjBox.min.set(-1e7, -1e7, -1e7); + mObjBox.max.set( 1e7, 1e7, 1e7); + mWorldBox.min.set(-1e7, -1e7, -1e7); + mWorldBox.max.set( 1e7, 1e7, 1e7); + + mFreePool = NULL; + VECTOR_SET_ASSOCIATION(mDecalQueue); + VECTOR_SET_ASSOCIATION(mFreePoolBlocks); +} + + +DecalManager::~DecalManager() +{ + mFreePool = NULL; + for (S32 i = 0; i < mFreePoolBlocks.size(); i++) + { + delete [] mFreePoolBlocks[i]; + } + mDecalQueue.clear(); +} + + +DecalInstance* DecalManager::allocateDecalInstance() +{ + if (mFreePool == NULL) + { + // Allocate a new block of decals + mFreePoolBlocks.push_back(new DecalInstance[csmFreePoolBlockSize]); + + // Init them onto the free pool chain + DecalInstance* pNewInstances = mFreePoolBlocks.last(); + for (U32 i = 0; i < csmFreePoolBlockSize - 1; i++) + pNewInstances[i].next = &pNewInstances[i + 1]; + pNewInstances[csmFreePoolBlockSize - 1].next = NULL; + mFreePool = pNewInstances; + } + AssertFatal(mFreePool != NULL, "Error, should always have a free pool available here!"); + + DecalInstance* pRet = mFreePool; + mFreePool = pRet->next; + pRet->next = NULL; + return pRet; +} + + +void DecalManager::freeDecalInstance(DecalInstance* trash) +{ + AssertFatal(trash != NULL, "Error, no trash pointer to free!"); + + trash->next = mFreePool; + mFreePool = trash; +} + + +void DecalManager::dataDeleted(DecalData *data) +{ + for(S32 i = mDecalQueue.size() - 1; i >= 0; i--) + { + DecalInstance *inst = mDecalQueue[i]; + if(inst->decalData == data) + { + freeDecalInstance(inst); + mDecalQueue.erase(U32(i)); + } + } +} + +void DecalManager::consoleInit() +{ + Con::addVariable("$pref::decalsOn", TypeBool, &smDecalsOn); + Con::addVariable("$pref::Decal::maxNumDecals", TypeS32, &smMaxNumDecals); + Con::addVariable("$pref::Decal::decalTimeout", TypeS32, &smDecalTimeout); +} + +void DecalManager::addDecal(Point3F pos, Point3F normal, DecalData* decalData) +{ + if (smMaxNumDecals == 0) + return; + + // DMM: Rework this, should be based on time + if(mDecalQueue.size() >= smMaxNumDecals) + { + DecalInstance* holder = mDecalQueue.front(); + mDecalQueue.pop_front(); + freeDecalInstance(holder); + } + + Point3F vecX, vecY; + DecalInstance* newDecal = allocateDecalInstance(); + newDecal->decalData = decalData; + newDecal->allocTime = Platform::getVirtualMilliseconds(); + + if(mFabs(normal.z) > 0.9f) + mCross(normal, Point3F(0.0f, 1.0f, 0.0f), &vecX); + else + mCross(normal, Point3F(0.0f, 0.0f, 1.0f), &vecX); + + mCross(vecX, normal, &vecY); + normal.normalizeSafe(); + Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008)); + + vecX.normalizeSafe(); + vecY.normalizeSafe(); + + vecX *= decalData->sizeX; + vecY *= decalData->sizeY; + + newDecal->point[0] = position + vecX + vecY; + newDecal->point[1] = position + vecX - vecY; + newDecal->point[2] = position - vecX - vecY; + newDecal->point[3] = position - vecX + vecY; + + mDecalQueue.push_back(newDecal); + mQueueDirty = true; +} + +void DecalManager::addDecal(Point3F pos, Point3F rot, Point3F normal, DecalData* decalData) +{ + if(mDot(rot, normal) < 0.98) + { + // DMM: Rework this, should be based on time + if(mDecalQueue.size() >= smMaxNumDecals) + { + DecalInstance* holder = mDecalQueue.front(); + mDecalQueue.pop_front(); + freeDecalInstance(holder); + } + + Point3F vecX, vecY; + DecalInstance* newDecal = allocateDecalInstance(); + newDecal->decalData = decalData; + newDecal->allocTime = Platform::getVirtualMilliseconds(); + + mCross(rot, normal, &vecX); + mCross(normal, vecX, &vecY); + + normal.normalize(); + Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008)); + + vecX.normalize(); + vecY.normalize(); + + vecX *= decalData->sizeX; + vecY *= decalData->sizeY; + + newDecal->point[0] = position + vecX + vecY; + newDecal->point[1] = position + vecX - vecY; + newDecal->point[2] = position - vecX - vecY; + newDecal->point[3] = position - vecX + vecY; + + mDecalQueue.push_back(newDecal); + mQueueDirty = true; + } +} + +bool DecalManager::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (!smDecalsOn) return false; + + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + if (mDecalQueue.size() == 0) + return false; + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + image->sortType = SceneRenderImage::BeginSort; + state->insertRenderImage(image); + + U32 currMs = Platform::getVirtualMilliseconds(); + for (S32 i = mDecalQueue.size() - 1; i >= 0; i--) + { + U32 age = currMs - mDecalQueue[i]->allocTime; + if (age > smDecalTimeout) + { + freeDecalInstance(mDecalQueue[i]); + mDecalQueue.erase(i); + } + else if (age > ((3 * smDecalTimeout) / 4)) + { + mDecalQueue[i]->fade = 1.0f - (F32(age - ((3 * smDecalTimeout) / 4)) / F32(smDecalTimeout / 4)); + } + else + { + mDecalQueue[i]->fade = 1.0f; + } + } + + if (mQueueDirty == true) + { + // Sort the decals based on the data pointers... + dQsort(mDecalQueue.address(), + mDecalQueue.size(), + sizeof(DecalInstance*), + cmpDecalInstance); + mQueueDirty = false; + } + + return false; +} + +void DecalManager::renderObject(SceneState* state, SceneRenderImage*) +{ + if (!smDecalsOn) return; + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + state->setupBaseProjection(); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + renderDecal(); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +void DecalManager::renderDecal() +{ + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.1f); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + DecalData* pLastData = NULL; + for (S32 x = 0; x < mDecalQueue.size(); x++) + { + if (mDecalQueue[x]->decalData != pLastData) + { + glBindTexture(GL_TEXTURE_2D, mDecalQueue[x]->decalData->textureHandle.getGLName()); + pLastData = mDecalQueue[x]->decalData; + } + + glColor4f(1, 1, 1, mDecalQueue[x]->fade); + glBegin(GL_TRIANGLE_FAN); { + glTexCoord2f(0, 0); glVertex3fv(mDecalQueue[x]->point[0]); + glTexCoord2f(0, 1); glVertex3fv(mDecalQueue[x]->point[1]); + glTexCoord2f(1, 1); glVertex3fv(mDecalQueue[x]->point[2]); + glTexCoord2f(1, 0); glVertex3fv(mDecalQueue[x]->point[3]); + } glEnd(); + } + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glDisable(GL_ALPHA_TEST); +} + diff --git a/sim/decalManager.h b/sim/decalManager.h new file mode 100644 index 0000000..51a66d9 --- /dev/null +++ b/sim/decalManager.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DECALMANAGER_H_ +#define _DECALMANAGER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "Sim/sceneObject.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif + +class DecalData : public SimDataBlock +{ + typedef SimDataBlock Parent; + + //-------------------------------------- Console set variables + public: + F32 sizeX; + F32 sizeY; + StringTableEntry textureName; + + //-------------------------------------- load set variables + public: + TextureHandle textureHandle; + + public: + DecalData(); + ~DecalData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, char errorBuffer[256]); + + DECLARE_CONOBJECT(DecalData); + static void initPersistFields(); +}; + +struct DecalInstance +{ + DecalData* decalData; + Point3F point[4]; + + U32 allocTime; + F32 fade; + DecalInstance* next; +}; + +class DecalManager : public SceneObject +{ + typedef SceneObject Parent; + + Vector mDecalQueue; + bool mQueueDirty; + + static U32 smMaxNumDecals; + static U32 smDecalTimeout; + + static const U32 csmFreePoolBlockSize; + Vector mFreePoolBlocks; + DecalInstance* mFreePool; + + protected: + void renderObject(SceneState* state, SceneRenderImage*); + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + + DecalInstance* allocateDecalInstance(); + void freeDecalInstance(DecalInstance*); + + public: + DecalManager(); + ~DecalManager(); + + static void consoleInit(); + + void addDecal(Point3F pos, Point3F rot, Point3F normal, DecalData*); + void addDecal(Point3F pos, Point3F normal, DecalData*); + void dataDeleted(DecalData *data); + + void renderDecal(); + DECLARE_CONOBJECT(DecalManager); + + static bool smDecalsOn; +}; + +extern DecalManager* gDecalManager; + +#endif // _H_DecalManager diff --git a/sim/frameAllocator.cc b/sim/frameAllocator.cc new file mode 100644 index 0000000..a492004 --- /dev/null +++ b/sim/frameAllocator.cc @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Sim/frameAllocator.h" +#include "console/console.h" + +U8* FrameAllocator::smBuffer = NULL; +U32 FrameAllocator::smWaterMark = 0; +U32 FrameAllocator::smHighWaterMark = 0; + +#ifdef DEBUG +S32 sgMaxFrameAllocation = 0; + +ConsoleFunction(getMaxFrameAllocation, S32, 1,1, "getMaxFrameAllocation();") +{ + argc, argv; + return sgMaxFrameAllocation; +} + +#endif + +void FrameAllocator::init(const U32 frameSize) +{ + 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) +{ + AssertFatal(smBuffer != NULL, "Error, no buffer!"); + AssertFatal(smWaterMark + allocSize <= smHighWaterMark, "Error alloc too large, increase frame size!"); + + U8* p = &smBuffer[smWaterMark]; + smWaterMark += allocSize; + +#ifdef DEBUG + if (smWaterMark > sgMaxFrameAllocation) + sgMaxFrameAllocation = 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; +} + + + diff --git a/sim/frameAllocator.h b/sim/frameAllocator.h new file mode 100644 index 0000000..a0c54cd --- /dev/null +++ b/sim/frameAllocator.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FRAMEALLOCATOR_H_ +#define _FRAMEALLOCATOR_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif + +class FrameAllocator +{ + static U8* smBuffer; + static U32 smHighWaterMark; + static U32 smWaterMark; + + + public: + static void init(const U32 frameSize); + static void destroy(); + + static void* alloc(const U32 allocSize); + + static void setWaterMark(const U32); + static U32 getWaterMark(); + static U32 getHighWaterMark(); +}; + +#endif // _H_FRAMEALLOCATOR_ diff --git a/sim/netConnection.cc b/sim/netConnection.cc new file mode 100644 index 0000000..51da123 --- /dev/null +++ b/sim/netConnection.cc @@ -0,0 +1,1164 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "core/bitStream.h" +#include "sim/pathManager.h" +#include "core/fileStream.h" +#include "core/resManager.h" +#include "sim/pathManager.h" +#include "console/consoleTypes.h" +#include +#include "game/badWordFilter.h" + +enum { + PingTimeout = 4500, // milliseconds + DefaultPingRetryCount = 15, +}; + +NetEvent::~NetEvent() +{ +} + +//-------------------------------------------------------------------- + +class NetStringEvent : public NetEvent +{ + char *mString; + U32 mId; +public: + NetStringEvent(U32 id = 0) + { + mId = id; + mString = NULL; + } + ~NetStringEvent() + { + dFree(mString); + } + + virtual void pack(NetConnection* /*ps*/, BitStream *bstream) + { + const char *str = gNetStringTable->lookupString(mId); + bstream->writeInt(mId, NetStringTable::StringIdBitSize); + bstream->writeString(str); + } + virtual void write(NetConnection* /*ps*/, BitStream *bstream) + { + bstream->writeInt(mId, NetStringTable::StringIdBitSize); + bstream->writeString(mString); + } + virtual void unpack(NetConnection* con, BitStream *bstream) + { + char buf[256]; + mId = bstream->readInt(NetStringTable::StringIdBitSize); + bstream->readString(buf); + if(gBadWordFilter->isEnabled() && con->isServerConnection()) + gBadWordFilter->filterString(buf); + mString = dStrdup(buf); + } + virtual void process(NetConnection *connection) + { + if(!mString[0]) + return; + U32 localId; + localId = gNetStringTable->addString(mString); + connection->mapString(mId, localId); + } +#ifdef DEBUG_NET + const char *getDebugName() + { + static char buffer[512]; + dSprintf(buffer, sizeof(buffer), "%s - \"", getClassName()); + expandEscape(buffer + dStrlen(buffer), gNetStringTable->lookupString(mId)); + dStrcat(buffer, "\""); + return buffer; + } +#endif + DECLARE_CONOBJECT(NetStringEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(NetStringEvent); + +//-------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(NetConnection); +NetConnection* NetConnection::mConnectionList = NULL; +NetConnection* NetConnection::mHashTable[NetConnection::HashTableSize] = { NULL, }; +NetConnection* NetConnection::mServerConnection = NULL; +NetConnection* NetConnection::mLocalClientConnection = NULL; + +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) +{ + netAddressTableRemove(); + mNetAddress = *addr; + netAddressTableInsert(); +} + +const NetAddress *NetConnection::getNetAddress() +{ + return &mNetAddress; +} + +void NetConnection::setSequences(U32 ccs, U32 css) +{ + mClientConnectSequence = ccs; + mServerConnectSequence = css; +} + +void NetConnection::getSequences(U32 *ccs, U32 *css) +{ + *ccs = mClientConnectSequence; + *css = mServerConnectSequence; +} + +void NetConnection::setServerConnection(NetConnection *conn) +{ + mServerConnection = conn; +} + +void NetConnection::setLocalClientConnection(NetConnection *conn) +{ + mLocalClientConnection = conn; +} + + +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); +} + +void NetConnection::checkMaxRate() +{ + if(gPacketRateToServer > 32) + gPacketRateToServer = 32; + if(gPacketRateToServer < 8) + gPacketRateToServer = 8; + if(gPacketRateToClient > 32) + gPacketRateToClient = 32; + if(gPacketRateToClient < 1) + gPacketRateToClient = 1; + if(gPacketSize > 450) + gPacketSize = 450; + if(gPacketSize < 100) + gPacketSize = 100; + + gPacketUpdateDelayToServer = 1024 / gPacketRateToServer; + U32 toClientUpdateDelay = 1024 / gPacketRateToClient; + + if(mMaxRate.updateDelay != toClientUpdateDelay || mMaxRate.packetSize != gPacketSize) + { + mMaxRate.updateDelay = toClientUpdateDelay; + mMaxRate.packetSize = gPacketSize; + mMaxRate.changed = true; + } +} + +NetConnection::NetConnection(bool ghostFrom, bool ghostTo, bool sendEvents) +{ + mClientConnectSequence = 0; + mServerConnectSequence = 0; + + mSendingEvents = sendEvents; + + mSimulatedPing = 0; + mSimulatedPacketLoss = 0; +#ifdef DEBUG_NET + mLogging = false; +#endif + mIsNetworkConnection = false; + mConnectionObjectId = 0; + mLastUpdateTime = 0; + mRoundTripTime = 0; + mPacketLoss = 0; + mNextTableHash = 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: + + mGhostFrom = ghostFrom; + mGhostTo = ghostTo; + + mScopeObject = NULL; + mGhostingSequence = 0; + mGhosting = false; + mScoping = false; + + if(mGhostFrom) + { + 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; + } + else + { + mGhostArray = NULL; + mGhostRefs = NULL; + mGhostLookupTable = NULL; + } + + if(mGhostTo) + { + mLocalGhosts = new NetObject *[MaxGhostCount]; + for(S32 i = 0; i < MaxGhostCount; i++) + mLocalGhosts[i] = NULL; + } + else + mLocalGhosts = NULL; + + U32 i; + for(i = 0; i < NetStringTable::MaxStrings; i++) + mStringXLTable[i] = 0; + for(i = 0; i < NetStringTable::MaxStrings >> 3; i++) + mStringSentBitArray[i] = 0; + + mMissionPathsSent = false; + mDemoWriteStream = NULL; + mDemoReadStream = NULL; + + mPingSendCount = 0; + mPingRetryCount = DefaultPingRetryCount; + mLastPingSendTime = Platform::getVirtualMilliseconds(); +} + +NetConnection::~NetConnection() +{ + AssertFatal(mNotifyQueueHead == NULL, "Uncleared notifies remain."); + netAddressTableRemove(); + + delete[] mLocalGhosts; + delete[] mGhostLookupTable; + delete[] mGhostRefs; + delete[] mGhostArray; + if(mDemoWriteStream) + delete mDemoWriteStream; + if(mDemoReadStream) + ResourceManager->closeStream(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() +{ +} + +//-------------------------------------------------------------------------- + +ConsoleMethod(NetConnection,transmitPaths,void,2,2,"conn.transmitPaths();") +{ + argc; argv; + NetConnection *cptr = (NetConnection *) object; + + gServerPathManager->transmitPaths(cptr); + cptr->setMissionPathsSent(true); +} + +ConsoleMethod(NetConnection,clearPaths,void,2,2,"conn.clearPaths();") +{ + argc; argv; + NetConnection *cptr = (NetConnection *) object; + cptr->setMissionPathsSent(false); +} + +ConsoleMethod(NetConnection,getAddress,const char *,2,2,"conn.getAddress();") +{ + argc; argv; + NetConnection *con = (NetConnection *) object; + if(con->isLocalConnection()) + return "local"; + char *buffer = Con::getReturnBuffer(256); + Net::addressToString(con->getNetAddress(), buffer); + return buffer; +} + +ConsoleMethod(NetConnection,setSimulatedNetParams,void,4, 4,"conn.setSimulatedNetParams(packetLoss,delay);") +{ + argc; + NetConnection *con = (NetConnection *) object; + con->setSimulatedNetParams(dAtof(argv[2]), dAtoi(argv[3])); +} + +ConsoleMethod( NetConnection, getPing, S32, 2, 2, "conn.getPing()" ) +{ + argc; argv; + NetConnection* conn = (NetConnection*) object; + return( S32( conn->getRoundTripTime() ) ); +} + +ConsoleMethod( NetConnection, getPacketLoss, S32, 2, 2, "conn.getPacketLoss()" ) +{ + argc; argv; + NetConnection* conn = (NetConnection*) object; + return( S32( 100 * conn->getPacketLoss() ) ); +} + +ConsoleMethod( NetConnection, checkMaxRate, void, 2, 2, "conn.checkMaxRate()") +{ + argc; argv; + NetConnection* conn = (NetConnection*) object; + conn->checkMaxRate(); +} + +#ifdef DEBUG_NET + +ConsoleMethod( NetConnection, setLogging, void, 3, 3, "conn.setLogging(bool)") +{ + argc; + ((NetConnection *) object)->setLogging(dAtob(argv[2])); +} + +#endif + +void NetConnection::mapString(U32 remoteId, U32 localId) +{ + if(mStringXLTable[remoteId]) + gNetStringTable->removeString(mStringXLTable[remoteId]); + mStringXLTable[remoteId] = localId; +} + +void NetConnection::clearString(U32 id) +{ + mStringSentBitArray[id >> 3] &= ~(1 << (id & 0x7)); +} + +void NetConnection::checkString(U32 id) +{ + if(!id) + return; + if(!(mStringSentBitArray[id >> 3] & (1 << (id & 0x7)))) + { + mStringSentBitArray[id >> 3] |= (1 << (id & 0x7)); + postNetEvent(new NetStringEvent(id)); + } +} + +//-------------------------------------------------------------------- + +bool NetConnection::onAdd() +{ + if(!Parent::onAdd()) + return false; + mNextConnection = mConnectionList; + if(mConnectionList) + mConnectionList->mPrevConnection = this; + mConnectionList = this; + return true; +} + +void NetConnection::onRemove() +{ + if(mLocalClientConnection == this) + mLocalClientConnection = NULL; + if(mServerConnection == this) + mServerConnection = NULL; + + if(mNextConnection) + mNextConnection->mPrevConnection = mPrevConnection; + if(mPrevConnection) + mPrevConnection->mNextConnection = mNextConnection; + else + mConnectionList = mNextConnection; + while(mNotifyQueueHead) + handleNotify(false); + + if(mGhostFrom) + clearGhostInfo(); + for(U32 i = 0; i < NetStringTable::MaxStrings; i++) + if(mStringXLTable[i]) + gNetStringTable->removeString(mStringXLTable[i]); + + while(mNotifyEventList) + { + NetEvent *temp = mNotifyEventList; + mNotifyEventList = temp->mNextEvent; + + temp->notifyDelivered(this, true); + delete temp; + } + while(mUnorderedSendEventQueueHead) + { + NetEvent *temp = mUnorderedSendEventQueueHead; + mUnorderedSendEventQueueHead = temp->mNextEvent; + + temp->notifyDelivered(this, true); + delete temp; + } + while(mSendEventQueueHead) + { + NetEvent *temp = mSendEventQueueHead; + mSendEventQueueHead = temp->mNextEvent; + + temp->notifyDelivered(this, true); + delete temp; + } + Parent::onRemove(); +} + +char NetConnection::mErrorBuffer[256]; + +void NetConnection::setLastError(const char *fmt, ...) +{ + va_list argptr; + va_start(argptr, fmt); + dVsprintf(mErrorBuffer, sizeof(mErrorBuffer), fmt, argptr); + // setLastErrors assert in debug builds + AssertFatal(0, mErrorBuffer); + va_end(argptr); +} + +//-------------------------------------------------------------------- + +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(Sim::getCurrentTime(), 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[0] = 0; + 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[0]) + connectionError(mErrorBuffer); +} + +void NetConnection::connectionError(const char *errorString) +{ + errorString; +} + +//-------------------------------------------------------------------- + +void NetConnection::setRemoteConnectionObjectId(SimObjectId id) +{ + mConnectionObjectId = id; +} + +NetConnection::PacketNotify *NetConnection::allocNotify() +{ + return new PacketNotify; +} + +class NetDelayEvent : public SimEvent +{ + U8 buffer[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 *destObject) + { + ((NetConnection *) destObject)->sendPacket(&stream); + } +}; + +void NetConnection::checkPacketSend() +{ + U32 curTime = Platform::getVirtualMilliseconds(); + U32 delay = isServerConnection() ? gPacketUpdateDelayToServer : mCurRate.updateDelay; + + if(curTime < mLastUpdateTime + delay ) + return; + + 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; + } + U32 start = stream->getCurPos(); + 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; + + if(mConnectionObjectId) + { + // short circuit connection to the other side. + // handle the packet, then force a notify. + stream->setBuffer(stream->getBuffer(), stream->getPosition(), stream->getPosition()); + NetConnection *remoteConnection = (NetConnection *) Sim::findObject(mConnectionObjectId); + AssertFatal(remoteConnection, "Invalid connection objectId"); + + remoteConnection->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); + + // Write all the current paths to the stream... + gClientPathManager->dumpState(stream); + stream->validate(); + U32 start = 0; + for(U32 i = 0; i < NetStringTable::MaxStrings;) + { + for(;mStringXLTable[i] && i < NetStringTable::MaxStrings;i++) + ; + stream->write(i - start); + for(U32 j = start; j < i; j++) + stream->writeString(gNetStringTable->lookupString(mStringXLTable[j])); + start = i; + for(;!mStringXLTable[i] && i < NetStringTable::MaxStrings;i++) + ; + stream->write(i - start); + stream->validate(); + start = i; + } + start = 0; + PacketNotify *note = mNotifyQueueHead; + while(note) + { + start++; + note = note->nextPacket; + } + stream->write(start); + + eventWriteStartBlock(stream); + ghostWriteStartBlock(stream); +} + +void NetConnection::readDemoStartBlock(BitStream* stream) +{ + ConnectionProtocol::readDemoStartBlock(stream); + + stream->read(&mRoundTripTime); + stream->read(&mPacketLoss); + + // Read + gClientPathManager->readState(stream); + U32 pos = 0; + while(pos < NetStringTable::MaxStrings) + { + U32 count; + stream->read(&count); + for(;count;count--) + { + char buf[256]; + stream->readString(buf); + U32 localId = gNetStringTable->addString(buf); + mapString(pos++, localId); + } + stream->read(&count); + pos += count; + } + 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); +} + +bool NetConnection::startDemoRecord(const char *fileName) +{ + FileStream *fs = new FileStream; + + if(!ResourceManager->openFileForWrite(*fs, ResourceManager->getBasePath(), fileName)) + { + delete fs; + return false; + } + + mDemoWriteStream = fs; + mDemoWriteStream->write(mProtocolVersion); + ResizeBitStream bs; + // first write out all the strings we have from the server: + for(U32 i = 0; i < NetStringTable::MaxStrings; i++) + { + U32 strId = mStringXLTable[i]; + if(strId) + { + bs.writeFlag(true); + bs.writeInt(i, NetStringTable::StringIdBitSize); + bs.writeString(gNetStringTable->lookupString(strId)); + bs.validate(); + } + } + bs.writeFlag(false); + + // then write out the start block + writeDemoStartBlock(&bs); + U32 size = bs.getPosition() + 1; + mDemoWriteStream->write(size); + mDemoWriteStream->write(size, bs.getBuffer()); + mDemoWriteStartTime = Sim::getCurrentTime() + + getDemoTickSize() - (Sim::getCurrentTime() % getDemoTickSize()); + mDemoLastWriteTime = mDemoWriteStartTime; + return true; +} + +class DemoProcessEvent : public SimEvent +{ +public: + virtual void process(SimObject *obj) + { + ((NetConnection *) obj)->readNextDemoBlock(); + } +}; + +class DemoStartReadEvent : public SimEvent +{ +public: + virtual void process(SimObject *obj) + { + ((NetConnection *) obj)->startDemoRead(); + } +}; + +bool NetConnection::replayDemoRecord(const char *fileName) +{ + Stream *fs = ResourceManager->openStream(fileName); + if(!fs) + 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); + + while(bs.readFlag()) + { + U32 id = bs.readInt(NetStringTable::StringIdBitSize); + char buf[256]; + bs.readString(buf); + U32 localId; + localId = gNetStringTable->addString(buf); + mapString(id, localId); + } + + readDemoStartBlock(&bs); + delete[] block; + if(mDemoReadStream->getStatus() != Stream::Ok) + return false; + + Sim::postEvent(this, new DemoStartReadEvent, Sim::getTargetTime() + 1); + return true; +} + +void NetConnection::startDemoRead() +{ + mDemoRealStartTime = Platform::getRealMilliseconds(); + + U32 time; + mDemoReadStream->read(&time); + if(mDemoReadStream->getStatus() != Stream::Ok) + { + demoPlaybackComplete(); + deleteObject(); + return; + } + mDemoReadStartTime = Sim::getTargetTime(); + mDemoReadStartTime += getDemoTickSize() - (mDemoReadStartTime % getDemoTickSize()); + + Sim::postEvent(this, new DemoProcessEvent, mDemoReadStartTime + time); +} + +U32 NetConnection::getDemoTickSize() +{ + return 32; // default tick size +} + +void NetConnection::stopRecording() +{ + if(mDemoWriteStream) + { + delete mDemoWriteStream; + mDemoWriteStream = NULL; + } +} + +void NetConnection::recordBlock(U32 time, U32 type, U32 size, void *data) +{ + if(mDemoWriteStream) + { + if(time < mDemoWriteStartTime) + time = mDemoWriteStartTime; + AssertFatal(time >= mDemoLastWriteTime, "Cannot write block back in time."); + + mDemoWriteStream->write(time - mDemoWriteStartTime); + mDemoWriteStream->write(type); + mDemoWriteStream->write(size); + if(size) + mDemoWriteStream->write(size, data); + mDemoLastWriteTime = time; + } +} + +void NetConnection::handleRecordedBlock(U32 type, U32 size, void *data) +{ + switch(type) + { + case BlockTypePacket: { + BitStream bs(data, size); + processRawPacket(&bs); + break; + } + } +} + +void NetConnection::demoPlaybackComplete() +{ + mDemoRealStartTime = Platform::getRealMilliseconds() - mDemoRealStartTime; + Con::setIntVariable("$Demo::playbackTime", mDemoRealStartTime); +} + +void NetConnection::readNextDemoBlock() +{ + U8 buffer[MaxPacketDataSize]; + U32 type; + U32 size; + mDemoReadStream->read(&type); + mDemoReadStream->read(&size); + mDemoReadStream->read(size, buffer); + handleRecordedBlock(type, size, buffer); + U32 time; + mDemoReadStream->read(&time); + if(mDemoReadStream->getStatus() != Stream::Ok) + { + demoPlaybackComplete(); + deleteObject(); + } + else + Sim::postEvent(this, new DemoProcessEvent, time + mDemoReadStartTime); +} + +//-------------------------------------------------------------------- +//-------------------------------------------------------------------- + +// some handy string functions for compressing strings over a connection: +enum +{ + NullString = 0, + CString, + TagString, + Integer +}; + +void NetConnection::validateSendString(const char *str) +{ + if(U8(*str) == StringTagPrefixByte) + checkString(dAtoi(str + 1)); +} + +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), NetStringTable::StringIdBitSize); + 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); + if(gBadWordFilter->isEnabled() && isServerConnection()) + gBadWordFilter->filterString(readBuffer); + return; + case TagString: + U32 tag; + tag = stream->readInt(NetStringTable::StringIdBitSize); + readBuffer[0] = StringTagPrefixByte; + dSprintf(readBuffer+1, 1023, "%d", tag); + return; + case Integer: + bool neg; + neg = stream->readFlag(); + U32 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::clearCompression() +{ + mCompressRelative = false; +} + +void NetConnection::setCompressionPoint(const Point3F& p) +{ + mCompressRelative = true; + mCompressPoint = p; +} + +static U32 gBitCounts[4] = { + 16, 18, 20, 32 +}; + +void NetConnection::writeCompressed(BitStream* stream,const Point3F& p,F32 scale) +{ + // Same # of bits for all axis + VectorF vec; + F32 invScale = 1 / scale; + U32 type; + if(mCompressRelative) + { + 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; + } + else + type = 3; + + stream->writeInt(type, 2); + + if (type != 3) + { + type = gBitCounts[type]; + stream->writeSignedInt(S32(vec.x * invScale),type); + stream->writeSignedInt(S32(vec.y * invScale),type); + stream->writeSignedInt(S32(vec.z * invScale),type); + } + else + { + stream->write(p.x); + stream->write(p.y); + stream->write(p.z); + } +} + +void NetConnection::readCompressed(BitStream* stream,Point3F* p,F32 scale) +{ + // Same # of bits for all axis + U32 type = stream->readInt(2); + + if(type == 3) + { + stream->read(&p->x); + stream->read(&p->y); + stream->read(&p->z); + } + else + { + type = gBitCounts[type]; + p->x = stream->readSignedInt(type); + p->y = stream->readSignedInt(type); + p->z = stream->readSignedInt(type); + + p->x = mCompressPoint.x + p->x * scale; + p->y = mCompressPoint.y + p->y * scale; + p->z = mCompressPoint.z + p->z * scale; + } +} diff --git a/sim/netConnection.h b/sim/netConnection.h new file mode 100644 index 0000000..a753c12 --- /dev/null +++ b/sim/netConnection.h @@ -0,0 +1,526 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _NETCONNECTION_H_ +#define _NETCONNECTION_H_ + +#ifndef _MPOINT_H_ +#include "math/mPoint.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 + +//---------------------------------------------------------------------------- +// the sim connection encapsulates the packet stream, +// ghost manager, event manager and playerPSC of the old tribes net stuff + +class NetConnection; +class NetObject; +class BitStream; +class ResizeBitStream; +class Stream; +class Point3F; + +struct GhostInfo; +struct SubPacketRef; // defined in NetConnection subclass + +//#define DEBUG_NET + +#ifdef DEBUG_NET +#define DEBUG_LOG(x) if(mLogging){Con::printf x;} +#else +#define DEBUG_LOG(x) +#endif + +//---------------------------------------------------------------------------- + +class NetEvent : public ConsoleObject +{ + // event manager variables +public: + typedef ConsoleObject Parent; + S32 mSeqCount; + NetEvent *mNextEvent; + bool mIsPosted; + enum { + GuaranteedOrdered = 0, + Guaranteed = 1, + Unguaranteed = 2 + } mGuaranteeType; + + NetEvent() { mGuaranteeType = GuaranteedOrdered; mIsPosted = false; } + virtual ~NetEvent(); + + NetConnectionId mSourceId; +#ifdef DEBUG_NET + virtual const char *getDebugName(); +#endif + 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); +}; + + +//---------------------------------------------------------------------------- + +class NetConnection : public ConnectionProtocol, public SimGroup +{ + typedef SimGroup Parent; + +public: + struct GhostRef + { + U32 mask; + U32 ghostInfoFlags; + GhostInfo *ghost; + GhostRef *nextRef; + GhostRef *nextUpdateChain; + }; + enum { + HashTableSize = 127, + }; +public: + struct PacketNotify + { + // packet stream notify stuff: + bool rateChanged; + bool maxRateChanged; + U32 sendTime; + + NetEvent *eventList; + GhostRef *ghostList; + SubPacketRef *subList; // defined by subclass - used as desired + + PacketNotify *nextPacket; + PacketNotify(); + }; + virtual PacketNotify *allocNotify(); +//---------------------------------------------------------------- +// Connection functions +//---------------------------------------------------------------- + +private: + U32 mLastUpdateTime; + F32 mRoundTripTime; + F32 mPacketLoss; + U32 mProtocolVersion; + + U32 mSimulatedPing; + F32 mSimulatedPacketLoss; + + U32 mClientConnectSequence; + U32 mServerConnectSequence; + + struct NetRate + { + U32 updateDelay; + S32 packetSize; + bool changed; + }; + + NetRate mCurRate; + NetRate mMaxRate; + + PacketNotify *mNotifyQueueHead; + PacketNotify *mNotifyQueueTail; + + SimObjectId mConnectionObjectId; + + bool mMissionPathsSent; + + NetAddress mNetAddress; + + // timeout management stuff: + U32 mPingSendCount; + U32 mPingRetryCount; + U32 mLastPingSendTime; + + NetConnection *mNextTableHash; + static NetConnection *mHashTable[HashTableSize]; + bool mIsNetworkConnection; +protected: + static NetConnection *mServerConnection; + static NetConnection *mLocalClientConnection; + static char mErrorBuffer[256]; + +public: + static char *getErrorBuffer() { return mErrorBuffer; } +#ifdef DEBUG_NET + bool mLogging; + void setLogging(bool logging) { mLogging = logging; } +#endif + void setSimulatedNetParams(F32 packetLoss, U32 ping) + { mSimulatedPacketLoss = packetLoss; mSimulatedPing = ping; } + bool isServerConnection() { return this == mServerConnection; } + bool isLocalConnection() { return mConnectionObjectId != 0; } + bool isNetworkConnection() { return mIsNetworkConnection; } + void setNetworkConnection(bool net) { mIsNetworkConnection = net; } + // call this if the "connection" is local to this app + // short-circuits protocol layer + + void setRemoteConnectionObjectId(SimObjectId objectId); + SimObjectId getRemoteConnectionObjectId() { return mConnectionObjectId; } + void setSequences(U32 ccs, U32 css); + void getSequences(U32 *ccs, U32 *css); + + void setProtocolVersion(U32 protocol) + { mProtocolVersion = protocol; } + U32 getProtocolVersion() + { return mProtocolVersion; } + F32 getRoundTripTime() + { return mRoundTripTime; } + F32 getPacketLoss() + { return( mPacketLoss ); } + + static void setLastError(const char *fmt,...); + + static void setServerConnection(NetConnection *conn); + static void setLocalClientConnection(NetConnection *conn); + + static NetConnection *getServerConnection() { return mServerConnection; } + static NetConnection *getLocalClientConnection() { return mLocalClientConnection; } + + 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: + static NetConnection *lookup(const NetAddress *remoteAddress); + + bool checkTimeout(U32 time); // returns true if the connection timed out + + void checkPacketSend(); + + bool missionPathsSent() const { return mMissionPathsSent; } + void setMissionPathsSent(const bool s) { mMissionPathsSent = s; } + + static void consoleInit(); + + bool onAdd(); + void onRemove(); + + NetConnection(bool ghostFrom = false, bool ghostTo = false, bool sendEvents = false); + ~NetConnection(); + + DECLARE_CONOBJECT(NetConnection); +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); + +//---------------------------------------------------------------- +// event manager functions/code: +//---------------------------------------------------------------- + +private: + NetEvent *mSendEventQueueHead; + NetEvent *mSendEventQueueTail; + NetEvent *mUnorderedSendEventQueueHead; + NetEvent *mUnorderedSendEventQueueTail; + NetEvent *mWaitSeqEvents; + NetEvent *mNotifyEventList; + bool mSendingEvents; + + S32 mNextSendEventSeq; + S32 mNextRecvEventSeq; + S32 mLastAckedEventSeq; + + enum { + InvalidSendEventSeq = -1, + FirstValidSendEventSeq = 0 + }; + + 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: + bool postNetEvent(NetEvent *event); + +//---------------------------------------------------------------- +// String table functions +//---------------------------------------------------------------- + +private: + + U16 mStringXLTable[NetStringTable::MaxStrings]; + U8 mStringSentBitArray[NetStringTable::MaxStrings >> 3]; + NetConnection *mNextConnection; + NetConnection *mPrevConnection; + static NetConnection *mConnectionList; +public: + static NetConnection *getConnectionList() { return mConnectionList; } + NetConnection *getNext() { return mNextConnection; } + + void mapString(U32 remoteId, U32 localId); + void clearString(U32 id); + void checkString(U32 id); + U32 translateRemoteStringId(U32 id) { return mStringXLTable[id]; } + void validateSendString(const char *str); + void packString(BitStream *stream, const char *str); + void unpackString(BitStream *stream, char readBuffer[1024]); + +//---------------------------------------------------------------- +// ghost manager functions/code: +//---------------------------------------------------------------- + +protected: + enum + { + GhostAlwaysDone, + ReadyForNormalGhosts, + EndGhosting, + GhostAlwaysStarting, + }; + 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 + + bool mGhostFrom; // can this side ghost objects over to the other? + bool mGhostTo; // can this side accept remote ghosts? + bool mGhosting; // am I currently ghosting objects over? + bool mScoping; // am I currently allowing objects to be scoped? + U32 mGhostingSequence; // sequence number describing this ghosting session + + // mLocalGhosts pointer is NULL if mGhostTo is false + NetObject **mLocalGhosts; // local ghost for remote object + + // these pointers are NULL if mGhostFrom is false + GhostInfo *mGhostRefs; // allocated array of ghostInfos + GhostInfo **mGhostLookupTable; // table indexed by object id->GhostInfo + + 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); + +public: + enum { MaxGhostCount = 1024, GhostIdBitSize = 10, GhostLookupTableSize = 1024 }; + + virtual void doneScopingScene() {} + void setScopeObject(NetObject *object); + NetObject *getScopeObject(); + + void objectInScope(NetObject *object); + void objectLocalScopeAlways(NetObject *object); + void objectLocalClearAlways(NetObject *object); + + NetObject *resolveGhost(S32 id); + NetObject *resolveGhostParent(S32 id); + void ghostPushNonZero(GhostInfo *gi); + void ghostPushToZero(GhostInfo *gi); + void ghostPushZeroToFree(GhostInfo *gi); + inline void ghostPushFreeToZero(GhostInfo *info); + + S32 getGhostIndex(NetObject *object); + + void resetGhosting(); + void activateGhosting(); + bool isGhosting() { return mGhosting; } + + void detachObject(GhostInfo *info); + virtual void handleGhostMessage(S32 message, U32 sequence, U32 ghostCount); + void setGhostAlwaysObject(NetObject *object, U32 index); + +//---------------------------------------------------------------- +// Demo recording functions +//---------------------------------------------------------------- + +private: + Stream *mDemoWriteStream; + Stream *mDemoReadStream; + U32 mDemoWriteStartTime; + U32 mDemoReadStartTime; + U32 mDemoLastWriteTime; + + U32 mDemoRealStartTime; + +public: + enum { + BlockTypePacket, + NetConnectionBlockTypeCount + }; + bool isRecording() + { return mDemoWriteStream != NULL; } + bool isPlayingBack() + { return mDemoReadStream != NULL; } + + void recordBlock(U32 time, U32 type, U32 size, void *data); + virtual void handleRecordedBlock(U32 type, U32 size, void *data); + void readNextDemoBlock(); + + bool startDemoRecord(const char *fileName); + bool replayDemoRecord(const char *fileName); + void startDemoRead(); + void stopRecording(); + + virtual void writeDemoStartBlock(ResizeBitStream *stream); + virtual void readDemoStartBlock(BitStream *stream); + virtual U32 getDemoTickSize(); + virtual void demoPlaybackComplete(); + + +//---------------------------------------------------------------- +// Connection relative compression +//---------------------------------------------------------------- + +private: + bool mCompressRelative; + Point3F mCompressPoint; + +public: + void clearCompression(); + void setCompressionPoint(const Point3F& p); + + // Matching calls to these compression methods must, of course, + // have matching scale values. + virtual void writeCompressed(BitStream* stream,const Point3F& p,F32 scale = 0.01f); + virtual void readCompressed(BitStream* stream,Point3F* p,F32 scale = 0.01f); +}; + + +//---------------------------------------------------------------------------- + +struct GhostInfo +{ + public: // required for MSVC + + // NOTE: + // if the size of this structure changes, the + // NetConnection::getGhostIndex function MUST be changed + // to reflect. + + NetObject *obj; // the real object + U32 updateMask; // 32 bits of object info + NetConnection::GhostRef *updateChain; // chain of updates for this object in packets + GhostInfo *nextObjectRef; // next ghost ref for this object (another connection) + + GhostInfo *prevObjectRef; // prev ghost ref for this object + NetConnection *connection; + GhostInfo *nextLookupInfo; + U32 updateSkipCount; + + U32 flags; + F32 priority; + U32 index; + U32 arrayIndex; + + 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.cc b/sim/netDownload.cc new file mode 100644 index 0000000..a9556d0 --- /dev/null +++ b/sim/netDownload.cc @@ -0,0 +1,270 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "core/bitStream.h" +#include "sim/netObject.h" +#include "core/resManager.h" + +class DownloadMessageEvent : public NetEvent +{ + U32 value; + S32 message; +public: + DownloadMessageEvent(S32 msg=0, U32 val=0) + { message = msg; value = val; } + void pack(NetConnection *, BitStream *bstream) + { + bstream->write(value); + bstream->writeInt(message, 3); + } + void write(NetConnection *, BitStream *bstream) + { + bstream->write(value); + bstream->writeInt(message, 3); + } + void unpack(NetConnection *, BitStream *bstream) + { + bstream->read(&value); + message = bstream->readInt(3); + } + void process(NetConnection *ps) + { + ps->handleDownloadMessage(message, value); + } + DECLARE_CONOBJECT(DownloadMessageEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(DownloadMessageEvent); + + +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) + { + ResourceManager->closeStream(mCurrentDownloadingFile); + mCurrentDownloadingFile = NULL; + return; + } + mCurrentFileBufferOffset += len; + mCurrentDownloadingFile->read(len, buffer); + postNetEvent(new FileChunkEvent(buffer, len)); +} + +bool NetConnection::startSendingFile(const char *fileName) +{ + if(!fileName) + { + postNetEvent(new DownloadMessageEvent(SendNextRequest)); + return false; + } + mCurrentDownloadingFile = ResourceManager->openStream(fileName); + 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 + postNetEvent(new DownloadMessageEvent(FileSizeMessage, 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() && ResourceManager->find(mMissingFileList[0])) + { + 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(!ResourceManager->openFileForWrite(stream, mMissingFileList[0])) + { + setLastError("Couldn't open file downloaded by server."); + return; + } + dFree(mMissingFileList[0]); + mMissingFileList.pop_front(); + stream.write(mCurrentFileBufferSize, mCurrentFileBuffer); + stream.close(); + dFree(mCurrentFileBuffer); + mCurrentFileBuffer = NULL; + sendNextFileDownloadRequest(); + } + else + { + Con::executef(4, "onFileChunkReceived", mMissingFileList[0], Con::getIntArg(mCurrentFileBufferOffset), Con::getIntArg(mCurrentFileBufferSize)); + } +} + +void NetConnection::handleDownloadMessage(S32 message, S32 value) +{ + if((message == SendNextRequest || message == FileSizeMessage ) && !mGhostTo) + { + setLastError("Invalid packet."); + return; + } + + switch(message) + { + case SendNextRequest: + sendNextFileDownloadRequest(); + break; + case FileSizeMessage: + mCurrentFileBufferSize = value; + mCurrentFileBuffer = dRealloc(mCurrentFileBuffer, mCurrentFileBufferSize); + mCurrentFileBufferOffset = 0; + break; + } +} + diff --git a/sim/netEvent.cc b/sim/netEvent.cc new file mode 100644 index 0000000..cc21938 --- /dev/null +++ b/sim/netEvent.cc @@ -0,0 +1,355 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "core/bitStream.h" + +#define DebugChecksum 0xF00DBAAD + +void NetEvent::notifyDelivered(NetConnection *, bool) +{ +} + +void NetEvent::notifySent(NetConnection *) +{ +} + +#ifdef DEBUG_NET +const char *NetEvent::getDebugName() +{ + return getClassName(); +} +#endif + +void NetConnection::eventPacketDropped(PacketNotify *notify) +{ + NetEvent *walk = notify->eventList; + NetEvent **insertList = &mSendEventQueueHead; + NetEvent *temp; + + while(walk) + { + switch(walk->mGuaranteeType) + { + 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; + case NetEvent::Guaranteed: + temp = walk->mNextEvent; + walk->mNextEvent = mUnorderedSendEventQueueHead; + mUnorderedSendEventQueueHead = walk; + if(!walk->mNextEvent) + mUnorderedSendEventQueueTail = walk; + walk = temp; + break; + case NetEvent::Unguaranteed: + walk->notifyDelivered(this, false); + temp = walk->mNextEvent; + delete walk; + walk = temp; + } + } +} + +void NetConnection::eventPacketReceived(PacketNotify *notify) +{ + NetEvent *walk = notify->eventList; + NetEvent **noteList = &mNotifyEventList; + + while(walk) + { + NetEvent *next = walk->mNextEvent; + if(walk->mGuaranteeType != NetEvent::GuaranteedOrdered) + { + walk->notifyDelivered(this, true); + delete 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++; + NetEvent *next = mNotifyEventList->mNextEvent; + //Con::printf("EVT %d: ACK - %d", getId(), mNotifyEventList->mSeqCount); + mNotifyEventList->notifyDelivered(this, true); + delete mNotifyEventList; + mNotifyEventList = next; + } +} + +void NetConnection::eventWritePacket(BitStream *bstream, PacketNotify *notify) +{ +#ifdef DEBUG_NET + bstream->writeInt(DebugChecksum, 32); +#endif + + NetEvent *packQueueHead = NULL, *packQueueTail = NULL; + + while(mUnorderedSendEventQueueHead) + { + if(bstream->isFull()) + break; + // dequeue the first event + NetEvent *ev = mUnorderedSendEventQueueHead; + mUnorderedSendEventQueueHead = ev->mNextEvent; + U32 start = bstream->getCurPos(); + + bstream->writeFlag(true); + S32 classId = ev->getClassId(); + AssertFatal(classId >= NetEventClassFirst && classId <= NetEventClassLast, + "Out of range event class id... check simBase.h"); + bstream->writeInt(classId - NetEventClassFirst, NetEventClassBitSize); + + ev->pack(this, bstream); + DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getCurPos() - start, ev->getDebugName()) ); + +#ifdef DEBUG_NET + bstream->writeInt(classId, 10); + 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 + NetEvent *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; + + U32 start = bstream->getCurPos(); + S32 classId = ev->getClassId(); + AssertFatal(classId >= NetEventClassFirst && classId <= NetEventClassLast, + "Out of range event class id... check simBase.h"); + bstream->writeInt(classId - NetEventClassFirst, NetEventClassBitSize); + + ev->pack(this, bstream); + DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getCurPos() - start, ev->getDebugName()) ); +#ifdef DEBUG_NET + bstream->writeInt(classId, 10); + bstream->writeInt(classId ^ DebugChecksum, 32); +#endif + } + for(NetEvent *ev = packQueueHead; ev; ev = ev->mNextEvent) + ev->notifySent(this); + + notify->eventList = packQueueHead; + bstream->writeFlag(0); +} + +void NetConnection::eventReadPacket(BitStream *bstream) +{ +#ifdef DEBUG_NET + U32 sum = bstream->readInt(32); + AssertISV(sum == DebugChecksum, "Invalid checksum."); +#endif + + S32 prevSeq = -2; + NetEvent **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 classTag = bstream->readInt(NetEventClassBitSize) + NetEventClassFirst; + NetEvent *evt = (NetEvent *) ConsoleObject::create(classTag); + if(!evt) + { + setLastError("Invalid packet."); + return; + } + AbstractClassRep *rep = evt->getClassRep(); + if((rep->mClassNetClass == NetEventClassClient && !isServerConnection()) + || (rep->mClassNetClass == NetEventClassServer && isServerConnection()) ) + { + setLastError("Invalid Packet."); + return; + } + + + evt->mSourceId = getId(); + evt->unpack(this, bstream); + if(mErrorBuffer[0]) + return; +#ifdef DEBUG_NET + U32 classId = bstream->readInt(10); + U32 checksum = bstream->readInt(32); + AssertISV( (checksum ^ DebugChecksum) == (U32)classTag, + avar("unpack did not match pack for event of class %s.", + evt->getClassName()) ); +#endif + if(unguaranteedPhase) + { + evt->process(this); + delete evt; + if(mErrorBuffer[0]) + return; + continue; + } + seq |= (mNextRecvEventSeq & ~0x7F); + if(seq < mNextRecvEventSeq) + seq += 128; + + evt->mSeqCount = seq; + //Con::printf("EVT %d: RECV - %d", getId(), evt->mSeqCount); + while(*waitInsert && (*waitInsert)->mSeqCount < seq) + waitInsert = &((*waitInsert)->mNextEvent); + + evt->mNextEvent = *waitInsert; + *waitInsert = evt; + waitInsert = &(evt->mNextEvent); + } + while(mWaitSeqEvents && mWaitSeqEvents->mSeqCount == mNextRecvEventSeq) + { + mNextRecvEventSeq++; + NetEvent *temp = mWaitSeqEvents; + mWaitSeqEvents = temp->mNextEvent; + + //Con::printf("EVT %d: PROCESS - %d", getId(), temp->mSeqCount); + temp->process(this); + delete temp; + if(mErrorBuffer[0]) + return; + } +} + +bool NetConnection::postNetEvent(NetEvent *event) +{ + AssertFatal(event->mIsPosted == false, "Event cannot be posted to more than one connection."); + event->mIsPosted = true; + + if(!mSendingEvents) + { + delete event; + return false; + } + event->mNextEvent = NULL; + if(event->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(NetEvent *walk = mWaitSeqEvents; walk; walk = walk->mNextEvent) + { + stream->writeFlag(true); + S32 classId = walk->getClassId(); + stream->writeInt(classId - NetEventClassFirst, NetEventClassBitSize); + walk->write(this, stream); + stream->validate(); + } + stream->writeFlag(false); +} + +void NetConnection::eventReadStartBlock(BitStream *stream) +{ + stream->read(&mNextRecvEventSeq); + + NetEvent *lastEvent = NULL; + while(stream->readFlag()) + { + S32 classTag = stream->readInt(NetEventClassBitSize) + NetEventClassFirst; + NetEvent *evt = (NetEvent *) ConsoleObject::create(classTag); + evt->unpack(this, stream); + evt->mNextEvent = NULL; + + if(!lastEvent) + mWaitSeqEvents = evt; + else + lastEvent->mNextEvent = evt; + lastEvent = evt; + } +} diff --git a/sim/netGhost.cc b/sim/netGhost.cc new file mode 100644 index 0000000..a2e7455 --- /dev/null +++ b/sim/netGhost.cc @@ -0,0 +1,908 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "core/bitStream.h" +#include "sim/netObject.h" + +#define DebugChecksum 0xF00D + +class GhostingMessageEvent : public NetEvent +{ + U32 sequence; + S32 message; + U32 ghostCount; +public: + GhostingMessageEvent(S32 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, 11); + } + void write(NetConnection *, BitStream *bstream) + { + bstream->write(sequence); + bstream->writeInt(message, 3); + bstream->writeInt(ghostCount, 11); + } + void unpack(NetConnection *, BitStream *bstream) + { + bstream->read(&sequence); + message = bstream->readInt(3); + ghostCount = bstream->readInt(11); + } + void process(NetConnection *ps) + { + ps->handleGhostMessage(message, sequence, ghostCount); + } + DECLARE_CONOBJECT(GhostingMessageEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(GhostingMessageEvent); + +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, 10); + + NetObject *obj = (NetObject *) Sim::findObject(objectId); + if(bstream->writeFlag(obj != NULL)) + { + S32 classId = obj->getClassId(); + AssertFatal(classId >= NetObjectClassFirst && classId <= NetObjectClassLast, + "Out of range event class id... check simBase.h"); + bstream->writeInt(classId - NetObjectClassFirst, NetObjectClassBitSize); + obj->packUpdate(ps, 0xFFFFFFFF, bstream); + } + } + void write(NetConnection *ps, BitStream *bstream) + { + bstream->writeInt(ghostIndex, 10); + if(bstream->writeFlag(validObject)) + { + S32 classId = object->getClassId(); + bstream->writeInt(classId - NetObjectClassFirst, NetObjectClassBitSize); + object->packUpdate(ps, 0xFFFFFFFF, bstream); + } + } + void unpack(NetConnection *ps, BitStream *bstream) + { + ghostIndex = bstream->readInt(10); + + if(bstream->readFlag()) + { + S32 classId = bstream->readInt(NetObjectClassBitSize) + NetObjectClassFirst; + object = (NetObject *) ConsoleObject::create(classId); + if(!object) + { + ps->setLastError("Invalid packet."); + return; + } + object->mNetFlags = NetObject::IsGhost; + object->unpackUpdate(ps, bstream); + validObject = true; + } + else + { + object = new NetObject; + validObject = false; + } + } + void process(NetConnection *ps) + { + Con::executef(1, "ghostAlwaysObjectReceived"); + + ps->setGhostAlwaysObject(object, ghostIndex); + object = NULL; + } + DECLARE_CONOBJECT(GhostAlwaysObjectEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(GhostAlwaysObjectEvent); + + +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 DEBUG_NET + bstream->writeInt(DebugChecksum, 32); +#endif + + notify->ghostList = NULL; + + if(!mGhostFrom) + 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, 3); // 0-7 3 bit number + + U32 count = 0; + // + for(i = mGhostZeroUpdateIndex - 1; i >= 0 && !bstream->isFull(); i--) + { + GhostInfo *walk = mGhostArray[i]; + if(walk->flags & (GhostInfo::KillingGhost | GhostInfo::Ghosting)) + continue; + + //S32 startPos = bstream->getCurPos(); + 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); + U32 startPos = bstream->getCurPos(); + if(walk->flags & GhostInfo::NotYetGhosted) + { + S32 classId = walk->obj->getClassId(); + + AssertFatal(classId >= NetObjectClassFirst && classId <= NetObjectClassLast, "Bad ghost tag."); + + walk->flags &= ~GhostInfo::NotYetGhosted; + walk->flags |= GhostInfo::Ghosting; + upd->ghostInfoFlags = GhostInfo::Ghosting; + bstream->writeInt(classId - NetObjectClassFirst, NetObjectClassBitSize); + } +#ifdef DEBUG_NET + bstream->writeInt(walk->obj->getClassId(), 10); + bstream->writeInt(walk->obj->getClassId() ^ DebugChecksum, 16); +#endif + // update the object + U32 retMask = walk->obj->packUpdate(this, updateMask, bstream); + DEBUG_LOG(("PKLOG %d GHOST %d: %s", getId(), bstream->getCurPos() - 16 - startPos, walk->obj->getClassName())); + + AssertFatal((retMask & (~updateMask)) == 0, "Cannot set new bits in packUpdate return"); + + walk->updateMask = retMask; + if(!retMask) + ghostPushToZero(walk); + + upd->mask = updateMask & ~retMask; + + //PacketStream::getStats()->addBits(PacketStats::Send, bstream->getCurPos() - startPos, walk->obj->getPersistTag()); +#ifdef DEBUG_NET + bstream->writeInt(walk->index ^ DebugChecksum, 16); +#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 DEBUG_NET + U32 sum = bstream->readInt(32); + AssertISV(sum == DebugChecksum, "Invalid checksum."); +#endif + + if(!mGhostTo) + return; + if(!bstream->readFlag()) + return; + + S32 idSize; + idSize = bstream->readInt( 3); + idSize += 3; + + // while there's an object waiting... + NetConnection *remoteConnection = NULL; + if(mConnectionObjectId) + remoteConnection = (NetConnection *) Sim::findObject(mConnectionObjectId); + + while(bstream->readFlag()) + { + U32 index; + //S32 startPos = bstream->getCurPos(); + index = (U32) bstream->readInt(idSize); + if(bstream->readFlag()) // is this ghost being deleted? + { + AssertFatal(mLocalGhosts[index] != NULL, "Error, NULL ghost encountered."); + mLocalGhosts[index]->deleteObject(); + mLocalGhosts[index] = NULL; + } + else + { + if(!mLocalGhosts[index]) // it's a new ghost... cool + { + U32 tag; + + tag = (U32) bstream->readInt(NetObjectClassBitSize) + NetObjectClassFirst; + + NetObject *obj = (NetObject *) ConsoleObject::create(tag); + if(!obj) + { + setLastError("Invalid packet."); + return; + } + obj->mNetFlags = NetObject::IsGhost; + + // object gets initial update before adding to the manager + + obj->mNetIndex = index; + mLocalGhosts[index] = obj; +#ifdef DEBUG_NET + U32 classId = bstream->readInt(10); + U32 checksum = bstream->readInt(16); + AssertISV(mLocalGhosts[index] != NULL, "Invalid dest ghost."); + AssertISV( (checksum ^ DebugChecksum) == mLocalGhosts[index]->getClassId(), + avar("class id mismatch for dest class %s.", + mLocalGhosts[index]->getClassName()) ); +#endif + mLocalGhosts[index]->unpackUpdate(this, bstream); + + if(!obj->registerObject()) + { + if(!mErrorBuffer[0]) + setLastError("Invalid packet."); + return; + } + if(remoteConnection) + obj->mServerObject = remoteConnection->resolveGhostParent(index); + + addObject(obj); + } + else + { +#ifdef DEBUG_NET + U32 classId = bstream->readInt(10); + U32 checksum = bstream->readInt(16); + AssertISV(mLocalGhosts[index] != NULL, "Invalid dest ghost."); + AssertISV( (checksum ^ DebugChecksum) == mLocalGhosts[index]->getClassId(), + avar("class id mismatch for dest class %s.", + mLocalGhosts[index]->getClassName()) ); +#endif + mLocalGhosts[index]->unpackUpdate(this, bstream); + } + //PacketStream::getStats()->addBits(PacketStats::Receive, bstream->getCurPos() - startPos, ghostRefs[index].localGhost->getPersistTag()); +#ifdef DEBUG_NET + U32 checksum = bstream->readInt(16); + AssertISV( (checksum ^ DebugChecksum) == index, + avar("unpackUpdate did not match packUpdate for object of class %s.", + mLocalGhosts[index]->getClassName()) ); +#endif + if(mErrorBuffer[0]) + 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) +{ + 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) +{ + 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) + return; + if (obj->isScopeLocal() && !mConnectionObjectId) + 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; + return; + } + + if (mGhostFreeIndex == MaxGhostCount) + 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::handleGhostMessage(S32 message, U32 sequence, U32 ghostCount) +{ + if((message == GhostAlwaysStarting || message == GhostAlwaysDone || message == EndGhosting) && !mGhostTo) + { + setLastError("Invalid packet."); + return; + } + + S32 i; + switch(message) + { + case GhostAlwaysDone: + mGhostingSequence = sequence; + postNetEvent(new GhostingMessageEvent(ReadyForNormalGhosts, sequence)); + break; + case ReadyForNormalGhosts: + if(sequence != mGhostingSequence) + return; + 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 + for(i = 0; i < MaxGhostCount; i++) + { + if(mLocalGhosts[i]) + { + mLocalGhosts[i]->deleteObject(); + mLocalGhosts[i] = NULL; + } + } + break; + case GhostAlwaysStarting: + Con::executef(2, "ghostAlwaysStarted", Con::getIntArg(ghostCount)); + break; + } +} + +void NetConnection::activateGhosting() +{ + if(!mGhostFrom) + 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); + } + postNetEvent(new GhostingMessageEvent(GhostAlwaysStarting, mGhostingSequence, ghostAlwaysSet->size())); + for(j = mGhostZeroUpdateIndex - 1; j >= 0; j--) + { + AssertFatal((mGhostArray[j]->flags & GhostInfo::ScopeAlways) != 0, "Non-scope always in the scope always list.") + + // we may end up resending state here, but at least initial state + // will not be resent. + mGhostArray[j]->updateMask = 0; + ghostPushToZero(mGhostArray[j]); + mGhostArray[j]->flags &= ~GhostInfo::NotYetGhosted; + mGhostArray[j]->flags |= GhostInfo::ScopedEvent; + + postNetEvent(new GhostAlwaysObjectEvent(mGhostArray[j]->obj, mGhostArray[j]->index)); + } + postNetEvent(new GhostingMessageEvent(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(!mGhostFrom) + return; + // stop all ghosting activity + // send a message to the other side notifying of this + + mGhosting = false; + mScoping = false; + postNetEvent(new GhostingMessageEvent(EndGhosting, mGhostingSequence)); + mGhostingSequence++; + clearGhostInfo(); + //AssertFatal(validateGhostArray(), "Invalid ghost array!"); +} + +void NetConnection::setGhostAlwaysObject(NetObject *object, U32 index) +{ + if(!mGhostTo) + { + object->deleteObject(); + setLastError("Invalid packet."); + return; + } + + AssertFatal(mLocalGhosts[index] == NULL, "Ghost already in table!"); + object->mNetFlags = NetObject::IsGhost; + if(!object->registerObject()) + { + if(!mErrorBuffer[0]) + setLastError("Invalid packet."); + return; + } + addObject(object); + + mLocalGhosts[index] = object; +} + +//----------------------------------------------------------------------------- + +NetObject *NetConnection::resolveGhost(S32 id) +{ + return mLocalGhosts[id]; +} + +NetObject *NetConnection::resolveGhostParent(S32 id) +{ + return mGhostRefs[id].obj; +} + +S32 NetConnection::getGhostIndex(NetObject *obj) +{ + if(!mGhostFrom) + 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) +{ + stream->write(mGhostingSequence); + for(U32 i = 0; i < MaxGhostCount; i++) + { + if(mLocalGhosts[i]) + { + stream->writeFlag(true); + stream->writeInt(i, 10); + stream->writeInt(mLocalGhosts[i]->getClassId() - NetObjectClassFirst, NetObjectClassBitSize); + mLocalGhosts[i]->packUpdate(this, 0xFFFFFFFF, stream); + stream->validate(); + } + } + stream->writeFlag(false); +} + +void NetConnection::ghostReadStartBlock(BitStream *stream) +{ + stream->read(&mGhostingSequence); + while(stream->readFlag()) + { + U32 index = stream->readInt(10); + U32 tag = (U32) stream->readInt(NetObjectClassBitSize) + NetObjectClassFirst; + NetObject *obj = (NetObject *) ConsoleObject::create(tag); + if(!obj) + { + setLastError("Invalid packet."); + return; + } + obj->mNetFlags = NetObject::IsGhost; + obj->mNetIndex = index; + mLocalGhosts[index] = obj; + mLocalGhosts[index]->unpackUpdate(this, stream); + if(!obj->registerObject()) + { + if(mErrorBuffer[0]) + setLastError("Invalid packet."); + return; + } + addObject(obj); + } +} diff --git a/sim/netObject.cc b/sim/netObject.cc new file mode 100644 index 0000000..1675c19 --- /dev/null +++ b/sim/netObject.cc @@ -0,0 +1,249 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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" + +IMPLEMENT_CONOBJECT(NetObject); + +//---------------------------------------------------------------------------- +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; + } +} + +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() +{ + Vector tempV; + for(NetObject *t = mDirtyList; t; t = t->mNextDirtyList) + tempV.push_back(t); + + for(NetObject *obj = mDirtyList; obj; ) + { + NetObject *next = obj->mNextDirtyList; + U32 orMask = obj->mDirtyMaskBits; + + obj->mNextDirtyList = NULL; + obj->mPrevDirtyList = NULL; + obj->mDirtyMaskBits = 0; + + if(!obj->isDeleted() && orMask) + { + for(GhostInfo *walk = obj->mFirstObjectRef; walk; walk = walk->nextObjectRef) + { + if(!walk->updateMask) + { + walk->updateMask = orMask; + walk->connection->ghostPushNonZero(walk); + } + else + walk->updateMask |= orMask; + } + } + obj = next; + } + mDirtyList = NULL; + for(U32 i = 0; i < tempV.size(); i++) + { + AssertFatal(tempV[i]->mNextDirtyList == NULL && tempV[i]->mPrevDirtyList == NULL && tempV[i]->mDirtyMaskBits == 0, "Error in collapse"); + } +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(NetObject,scopeToClient,void,3,3,"obj.scopeToClient(%client)") +{ + 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((NetObject *) object); +} + +ConsoleMethod(NetObject,clearScopeToClient,void,3,3,"obj.clearScopeToClient(%client)") +{ + 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((NetObject *) object); +} + +ConsoleMethod(NetObject,setScopeAlways,void,2,2,"obj.scopeAlways();") +{ + argc; argv; + ((NetObject *) 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); + } + } +} + +bool NetObject::onAdd() +{ + if(mNetFlags.test(ScopeAlways)) + setScopeAlways(); + + return Parent::onAdd(); +} + +void NetObject::onRemove() +{ + while(mFirstObjectRef) + mFirstObjectRef->connection->detachObject(mFirstObjectRef); + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- + +F32 NetObject::getUpdatePriority(CameraScopeQuery*, U32, S32 updateSkips) +{ + return F32(updateSkips) * 0.1; + + //return 0; +} + +U32 NetObject::packUpdate(NetConnection*, U32, BitStream*) +{ + 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) + { + // 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(); +} + +void NetObject::consoleInit() +{ +} diff --git a/sim/netObject.h b/sim/netObject.h new file mode 100644 index 0000000..8c56299 --- /dev/null +++ b/sim/netObject.h @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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; + Point3F pos; // Pos in world space + Point3F orientation; // Vector in world space + F32 fov; // viwing angle/2 + F32 sinFov; // sin(fov/2); + F32 cosFov; // cos(fov/2); + F32 visibleDistance; +}; + +struct GhostInfo; + + +//----------------------------------------------------------------------------- + +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; + NetObject *mPrevDirtyList; + NetObject *mNextDirtyList; + U32 mDirtyMaskBits; + + static NetObject *mDirtyList; +protected: + SimObjectPtr mServerObject; + + enum NetFlag + { + IsGhost = BIT(1), // This is a ghost + ScopeAlways = BIT(6), // if set, object always ghosts to clientReps + ScopeLocal = BIT(7), // Ghost only to local client 2 + Ghostable = BIT(8), // new flag -- set if this object CAN ghost + MaxNetFlagBit = 15 + }; + BitSet32 mNetFlags; + U32 mNetIndex; // the index of this ghost in the GhostManager on the server + GhostInfo *mFirstObjectRef; +public: + NetObject(); + ~NetObject(); + + // Base class provides IO for it's members but is + // not declared as persitent. + + DECLARE_CONOBJECT(NetObject); + static void initPersistFields(); + static void consoleInit(); + static void collapseDirtyList(); + bool onAdd(); + void onRemove(); + + void setMaskBits(U32 orMask); + void clearMaskBits(U32 orMask); + + void setScopeAlways(); + + virtual F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips); + virtual U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + virtual void unpackUpdate(NetConnection *, BitStream *stream); + virtual void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo); + + U32 getNetIndex() { return mNetIndex; } + + bool isServerObject() const; + bool isClientObject() const; + + bool isGhost() const; + bool isScopeLocal() const; + bool isScopeable() const; + bool isGhostAlways() const; +}; + +//----------------------------------------------------------------------------- + +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::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); +} + +#endif diff --git a/sim/netStringTable.cc b/sim/netStringTable.cc new file mode 100644 index 0000000..60ac339 --- /dev/null +++ b/sim/netStringTable.cc @@ -0,0 +1,218 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "sim/netStringTable.h" +#include "core/stringTable.h" + +NetStringTable *gNetStringTable = NULL; + +NetStringTable::NetStringTable() +{ + firstFree = 1; + firstValid = 1; + for(U32 i = 0; i < MaxStrings; i++) + { + table[i].next = i + 1; + table[i].refCount = 0; + } + for(U32 j = 0; j < HashTableSize; j++) + hashTable[j] = 0; + allocator = new DataChunker(DataChunkerSize); +} + +NetStringTable::~NetStringTable() +{ + delete allocator; +} + +void NetStringTable::clearSentBit(U32 id) +{ + // a string has been added - loop through the connections + // and mark it as not yet sent. + for(NetConnection *conn = NetConnection::getConnectionList(); conn; conn = conn->getNext()) + conn->clearString(id); +} + +void NetStringTable::incStringRef(U32 id) +{ + AssertFatal(table[id].refCount != 0, "Cannot inc ref count from zero."); + table[id].refCount++; +} + +U32 NetStringTable::addString(const char *string) +{ + U32 hash = _StringTable::hashString(string); + U32 bucket = hash % HashTableSize; + for(U16 walk = hashTable[bucket];walk; walk = table[walk].next) + { + if(!dStrcmp(table[walk].string, string)) + { + table[walk].refCount++; + return walk; + } + } + U16 e = firstFree; + firstFree = table[e].next; + 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; + clearSentBit(e); + return e; +} + +U32 GameAddTaggedString(const char *string) +{ + return gNetStringTable->addString(string); +} + +const char *NetStringTable::lookupString(U32 id) +{ + if(table[id].refCount == 0) + return NULL; + return table[id].string; +} + +void NetStringTable::removeString(U32 id) +{ + if(--table[id].refCount) + return; + // unlink first: + U16 prev = table[id].prevLink; + U16 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(U16 *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(U16 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(U32 id, char *buf, U32 bufSize, U32 argc, const char **argv) +{ + buf[0] = StringTagPrefixByte; + dSprintf(buf + 1, bufSize - 1, "%d ", id); + + const char *string = gNetStringTable->lookupString(id); + 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, ""); + } +} + +#ifdef DEBUG +void NetStringTable::dumpToConsole() +{ + U32 count = 0; + S32 maxIndex = -1; + for ( U32 i = 0; i < MaxStrings; i++ ) + { + if ( table[i].refCount > 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 ); +} + +ConsoleFunction( dumpNetStringTable, void, 1, 1, "dumpNetStringTable()" ) +{ + argc; argv; + gNetStringTable->dumpToConsole(); +} +#endif // DEBUG diff --git a/sim/netStringTable.h b/sim/netStringTable.h new file mode 100644 index 0000000..e3beda2 --- /dev/null +++ b/sim/netStringTable.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _NETSTRINGTABLE_H_ +#define _NETSTRINGTABLE_H_ + +#ifndef _DATACHUNKER_H_ +#include "engine/core/dataChunker.h" +#endif + +class NetConnection; + +class NetStringTable +{ +public: + enum { + MaxStrings = 4096, + HashTableSize = 1287, + StringIdBitSize = 12, + DataChunkerSize = 65536 + }; +private: + struct Entry + { + char *string; + U16 refCount; + U16 next; + U16 link; + U16 prevLink; + U32 seq; + }; + U16 firstFree; + U16 firstValid; + U32 sequenceCount; + + Entry table[MaxStrings]; + U16 hashTable[HashTableSize]; + DataChunker *allocator; + +public: + NetStringTable(); + ~NetStringTable(); + + void clearSentBit(U32 id); +public: + U32 addString(const char *string); + const char *lookupString(U32 id); + void incStringRef(U32 id); + void removeString(U32 id); + void sendString(U32 id); + void repack(); + static void create(); + static void destroy(); + + static void expandString(U32 id, char *buf, U32 bufSize, U32 argc, const char **argv); + +#ifdef DEBUG + void dumpToConsole(); +#endif // DEBUG +}; + +extern NetStringTable *gNetStringTable; +#endif diff --git a/sim/pathManager.cc b/sim/pathManager.cc new file mode 100644 index 0000000..b40a1d0 --- /dev/null +++ b/sim/pathManager.cc @@ -0,0 +1,406 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Sim/pathManager.h" +#include "Sim/netConnection.h" +#include "Core/bitStream.h" +#include "interior/interiorInstance.h" +#include "Math/mathIO.h" + +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: + enum MessageType { + NewPaths, + ModifyPath + }; + + MessageType message; + U32 modifiedPath; + + Vector mPaths; + + public: + PathManagerEvent() { + VECTOR_SET_ASSOCIATION(mPaths); + } + + void pack(NetConnection*, BitStream*); + void write(NetConnection*, BitStream*); + void unpack(NetConnection*, BitStream*); + void process(NetConnection*); + + DECLARE_CONOBJECT(PathManagerEvent); +}; + +void PathManagerEvent::pack(NetConnection*, BitStream* stream) +{ + AssertFatal(mPaths.size() == 0, "Hm, bad"); + + if (stream->writeFlag(message == NewPaths) == true) { + // Write out all the new paths... + stream->write(gServerPathManager->mPaths.size()); + for (U32 i = 0; i < gServerPathManager->mPaths.size(); i++) { + stream->write(gServerPathManager->mPaths[i]->totalTime); + + stream->write(gServerPathManager->mPaths[i]->positions.size()); + for (U32 j = 0; j < gServerPathManager->mPaths[i]->positions.size(); j++) { + mathWrite(*stream, gServerPathManager->mPaths[i]->positions[j]); + stream->write(gServerPathManager->mPaths[i]->msToNext[j]); + } + } + } else { + // Write out the modified path... + stream->write(modifiedPath); + stream->write(gServerPathManager->mPaths[modifiedPath]->totalTime); + stream->write(gServerPathManager->mPaths[modifiedPath]->positions.size()); + for (U32 j = 0; j < gServerPathManager->mPaths[modifiedPath]->positions.size(); j++) { + mathWrite(*stream, gServerPathManager->mPaths[modifiedPath]->positions[j]); + stream->write(gServerPathManager->mPaths[modifiedPath]->msToNext[j]); + } + } +} + +void PathManagerEvent::write(NetConnection*, BitStream *stream) +{ + if (stream->writeFlag(message == NewPaths) == true) { + // Write out all the new paths... + stream->write(mPaths.size()); + for (U32 i = 0; i < mPaths.size(); i++) { + stream->write(mPaths[i]->totalTime); + + stream->write(mPaths[i]->positions.size()); + for (U32 j = 0; j < mPaths[i]->positions.size(); j++) { + mathWrite(*stream, mPaths[i]->positions[j]); + stream->write(mPaths[i]->msToNext[j]); + } + } + } else { + // Write out the modified path... + stream->write(modifiedPath); + stream->write(mPaths[0]->totalTime); + stream->write(mPaths[0]->positions.size()); + for (U32 j = 0; j < mPaths[0]->positions.size(); j++) { + mathWrite(*stream, mPaths[0]->positions[j]); + stream->write(mPaths[0]->msToNext[j]); + } + } +} + +void PathManagerEvent::unpack(NetConnection*, BitStream* stream) +{ + AssertFatal(mPaths.size() == 0, "Hm, bad"); + + message = stream->readFlag() ? NewPaths : ModifyPath; + + if (message == NewPaths) { + // Read in all the paths + U32 numPaths, i; + stream->read(&numPaths); + mPaths.setSize(numPaths); + for (i = 0; i < mPaths.size(); i++) + mPaths[i] = new PathManager::PathEntry; + + for (i = 0; i < mPaths.size(); i++) { + PathManager::PathEntry& rEntry = *(mPaths[i]); + + stream->read(&rEntry.totalTime); + + U32 numPoints; + stream->read(&numPoints); + rEntry.positions.setSize(numPoints); + rEntry.msToNext.setSize(numPoints); + for (U32 j = 0; j < rEntry.positions.size(); j++) { + mathRead(*stream, &rEntry.positions[j]); + stream->read(&rEntry.msToNext[j]); + } + } + } else { + // Read in the modified path... + stream->read(&modifiedPath); + AssertFatal(modifiedPath <= gClientPathManager->mPaths.size(), "Error out of bounds path!"); + + mPaths.push_back(new PathManager::PathEntry); + PathManager::PathEntry& rEntry = *(mPaths[0]); + + stream->read(&rEntry.totalTime); + + U32 numPoints; + stream->read(&numPoints); + rEntry.positions.setSize(numPoints); + rEntry.msToNext.setSize(numPoints); + for (U32 j = 0; j < rEntry.positions.size(); j++) { + mathRead(*stream, &rEntry.positions[j]); + stream->read(&rEntry.msToNext[j]); + } + } +} + +void PathManagerEvent::process(NetConnection*) +{ + if (message == NewPaths) { + // Destroy all previous paths... + U32 i; + for (i = 0; i < gClientPathManager->mPaths.size(); i++) + delete gClientPathManager->mPaths[i]; + + gClientPathManager->mPaths.setSize(mPaths.size()); + for (i = 0; i < mPaths.size(); i++) { + gClientPathManager->mPaths[i] = mPaths[i]; + mPaths[i] = NULL; + } + } else { + if (modifiedPath == gClientPathManager->mPaths.size()) { + gClientPathManager->mPaths.push_back(mPaths[0]); + mPaths[0] = NULL; + } else { + delete gClientPathManager->mPaths[modifiedPath]; + gClientPathManager->mPaths[modifiedPath] = mPaths[0]; + mPaths[0] = NULL; + } + } +} + +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() +{ + for (U32 i = 0; i < mPaths.size(); i++) + delete mPaths[i]; +} + +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& times) +{ + 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(), "Error, times and positions must match!"); + + PathEntry& rEntry = *mPaths[id]; + + rEntry.positions = positions; + rEntry.msToNext = times; + + rEntry.totalTime = 0; + for (S32 i = 0; i < S32(rEntry.msToNext.size()) - 1; 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 + PathManagerEvent* event = new PathManagerEvent; + event->message = PathManagerEvent::NewPaths; + + 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->message = PathManagerEvent::ModifyPath; + event->modifiedPath = id; + nc->postNetEvent(event); + } + } +} + +void PathManager::getPathPosition(const U32 id, + const U32 msPosition, + Point3F& rPosition) +{ + AssertFatal(isValidPath(id), "Error, this is not a valid path!"); + + // Ok, query holds our path information... + U32 ms = msPosition; + if (ms > mPaths[id]->totalTime) + ms = mPaths[id]->totalTime; + + U32 startNode = 0; + while (ms > mPaths[id]->msToNext[startNode]) { + ms -= mPaths[id]->msToNext[startNode]; + startNode++; + } + U32 endNode = (startNode + 1) % mPaths[id]->positions.size(); + + Point3F& rStart = mPaths[id]->positions[startNode]; + Point3F& rEnd = mPaths[id]->positions[endNode]; + + F32 interp = F32(ms) / F32(mPaths[id]->msToNext[startNode]); + + rPosition = (rStart * (1.0 - interp)) + (rEnd * interp); +} + + +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/sim/pathManager.h b/sim/pathManager.h new file mode 100644 index 0000000..d09a647 --- /dev/null +++ b/sim/pathManager.h @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PATHMANAGER_H_ +#define _PATHMANAGER_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MQUAT_H_ +#include "Math/mQuat.h" +#endif + +class NetConnection; +class BitStream; + +class PathManager +{ + friend class PathManagerEvent; + + private: + struct PathEntry { + U32 totalTime; + + Vector positions; + Vector msToNext; + + PathEntry() { + VECTOR_SET_ASSOCIATION(positions); + VECTOR_SET_ASSOCIATION(msToNext); + } + }; + + Vector mPaths; + + public: + enum PathType { + BackAndForth, + Looping + }; + + public: + PathManager(const bool isServer); + ~PathManager(); + + static void init(); + static void destroy(); + + //-------------------------------------- Path querying + public: + bool isValidPath(const U32 id) const; + void getPathPosition(const U32 id, const U32 msPosition, Point3F& rPosition); + 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&); + + //-------------------------------------- State dumping/reading + public: + bool dumpState(BitStream*) const; + bool readState(BitStream*); + + private: + bool mIsServer; + bool mPathsSent; +}; + +struct PathNode { + Point3F position; + QuatF rotation; + U32 msToNext; +}; + +extern PathManager* gClientPathManager; +extern PathManager* gServerPathManager; + +//-------------------------------------------------------------------------- +inline bool PathManager::isValidPath(const U32 id) const +{ + return (id < mPaths.size()) && mPaths[id]->positions.size() > 0; +} + +#endif // _H_PATHMANAGER diff --git a/sim/sceneObject.cc b/sim/sceneObject.cc new file mode 100644 index 0000000..e3906b9 --- /dev/null +++ b/sim/sceneObject.cc @@ -0,0 +1,2071 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "sim/sceneObject.h" +#include "scenegraph/sceneGraph.h" +#include "console/consoleTypes.h" +#include "collision/extrudedPolyList.h" +#include "collision/earlyOutPolyList.h" +#include "platform/profiler.h" + +#include "platform/profiler.h" +#include "interior/interior.h" +#include "interior/interiorInstance.h" +#include "terrain/terrData.h" +#include "dgl/gBitmap.h" +#include "dgl/dgl.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; + +namespace { + +// Statics used by buildPolyList methods +AbstractPolyList* sPolyList; +SphereF sBoundingSphere; +Box3F sBoundingBox; + +// Statics used by collide methods +ExtrudedPolyList sExtrudedPolyList; +Polyhedron sBoxPolyhedron; + +//-------------------------------------------------------------------------- +//-------------------------------------- Console callbacks +// +const char* cGetTransform(SimObject *obj, S32, const char **) +{ + char *returnBuffer = Con::getReturnBuffer(256); + SceneObject *ptr = (SceneObject *) obj; + const MatrixF& mat = ptr->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; +} + +const char* cGetPosition(SimObject *obj, S32, const char **) +{ + char *returnBuffer = Con::getReturnBuffer(256); + SceneObject *ptr = (SceneObject *) obj; + const MatrixF& mat = ptr->getTransform(); + Point3F pos; + mat.getColumn(3,&pos); + dSprintf(returnBuffer,256,"%g %g %g",pos.x,pos.y,pos.z); + return returnBuffer; +} + +const char *cGetForwardVector(SimObject *obj, S32, const char **) +{ + char *returnBuffer = Con::getReturnBuffer(256); + SceneObject *ptr = (SceneObject *) obj; + const MatrixF& mat = ptr->getTransform(); + Point3F dir; + mat.getColumn(1,&dir); + dSprintf(returnBuffer,256,"%g %g %g",dir.x,dir.y,dir.z); + return returnBuffer; +} + +void cSetTransform(SimObject *obj, S32, const char **argv) +{ + SceneObject *ptr = (SceneObject *) obj; + Point3F pos; + const MatrixF& tmat = ptr->getTransform(); + tmat.getColumn(3,&pos); + AngAxisF aa(tmat); + + dSscanf(argv[2],"%f %f %f %f %f %f %f", + &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); + ptr->setTransform(mat); +} + +const char* cGetScale(SimObject * obj, S32, const char **) +{ + char *returnBuffer = Con::getReturnBuffer(256); + SceneObject * sceneObj = static_cast(obj); + const VectorF & scale = sceneObj->getScale(); + dSprintf(returnBuffer, 256, "%g %g %g", + scale.x, scale.y, scale.z); + return(returnBuffer); +} + +void cSetScale(SimObject * obj, S32, const char ** argv) +{ + SceneObject * sceneObj = static_cast(obj); + VectorF scale(0.f,0.f,0.f); + dSscanf(argv[2], "%f %f %f", &scale.x, &scale.y, &scale.z); + sceneObj->setScale(scale); +} + +const char* cGetWorldBox(SimObject *obj, S32, const char**) +{ + char *returnBuffer = Con::getReturnBuffer(256); + SceneObject *ptr = (SceneObject *) obj; + const Box3F& box = ptr->getWorldBox(); + dSprintf(returnBuffer,256,"%g %g %g %g %g %g", + box.min.x, box.min.y, box.min.z, + box.max.x, box.max.y, box.max.z); + return returnBuffer; +} + +const char* cGetWorldBoxCenter(SimObject *obj, S32, const char**) +{ + char *returnBuffer = Con::getReturnBuffer(256); + SceneObject *ptr = (SceneObject *) obj; + const Box3F& box = ptr->getWorldBox(); + Point3F center; + box.getCenter(¢er); + + dSprintf(returnBuffer,256,"%g %g %g", center.x, center.y, center.z); + return returnBuffer; +} + +const char* cGetObjectBox(SimObject *obj, S32, const char**) +{ + char *returnBuffer = Con::getReturnBuffer(256); + SceneObject *ptr = (SceneObject *) obj; + const Box3F& box = ptr->getObjBox(); + dSprintf(returnBuffer,256,"%g %g %g %g %g %g", + box.min.x, box.min.y, box.min.z, + box.max.x, box.max.y, box.max.z); + return returnBuffer; +} + +// See if any objects of given types are present in box of given extent. Extent parameter is +// last since only one radius is often needed. Can change if convention clash annoys anyone... +static bool cContainerBoxEmpty(SimObject*, S32 argc, const char * * argv) +{ + Point3F center; + Point3F extent; + U32 mask = dAtoi(argv[1]); + dSscanf(argv[2], "%f %f %f", ¢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.min, VectorF(-1,0,0)); + polyList.mPlaneList[1].set(B.max, VectorF(0,1,0)); + polyList.mPlaneList[2].set(B.max, VectorF(1,0,0)); + polyList.mPlaneList[3].set(B.min, VectorF(0,-1,0)); + polyList.mPlaneList[4].set(B.min, VectorF(0,0,-1)); + polyList.mPlaneList[5].set(B.max, VectorF(0,0,1)); + + return ! gServerContainer.buildPolyList(B, mask, &polyList); +} + +void cInitContainerRadiusSearch(SimObject*, S32, const char** argv) +{ + F32 x, y, z; + dSscanf(argv[1], "%f %f %f", &x, &y, &z); + F32 r = dAtof(argv[2]); + U32 mask = dAtoi(argv[3]); + + gServerContainer.initRadiusSearch(Point3F(x, y, z), r, mask); +} + + +S32 cContainerSearchNext(SimObject*, S32, const char**) +{ + return gServerContainer.containerSearchNext(); +} + + +F32 cContainerSearchCurrDist(SimObject*, S32, const char**) +{ + return gServerContainer.containerSearchCurrDist(); +} + +F32 cContainerSearchCurrRadDamageDist(SimObject*, S32, const char**) +{ + return gServerContainer.containerSearchCurrRadDamageDist(); +} + +const char * cContainerRayCast(SimObject*, S32 argc, const char** argv) +{ + char *returnBuffer = Con::getReturnBuffer(256); + Point3F start, end; + dSscanf(argv[1], "%f %f %f", &start.x, &start.y, &start.z); + dSscanf(argv[2], "%f %f %f", &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); +} + +} // namespace {} + +// Utility method for bin insertion +void getBinRange(const F32 min, + const F32 max, + U32& minBin, + U32& maxBin) +{ + AssertFatal(max >= min, "Error, bad range! in getBinRange"); + + if ((max - min) > Container::csmTotalBinSize) { + 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! (%f, %d)", minCoord, minBin)); + + maxBin = minBin + (Container::csmNumBins - 1); + return; + } else { + F32 minCoord = mFmod(min, Container::csmTotalBinSize); + F32 maxCoord = mFmod(max, 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; + } + 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(minCoord >= 0.0 && minCoord < Container::csmTotalBinSize, "Bad minCoord"); + 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)! (%f, %d)", maxCoord, minBin)); + AssertFatal(minBin < Container::csmNumBins, avar("Error, bad clipping(max)! (%f, %d)", maxCoord, maxBin)); + + if (minCoord > maxCoord) + maxBin += Container::csmNumBins; + AssertFatal(maxBin >= minBin, "Error, min should always be less than max!"); + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- SceneObject implementation +// +LightInfo SceneObject::LightingInfo::smAmbientLight; + +SceneObject::SceneObject() +{ + mContainer = 0; + mTypeMask = DefaultObjectType; + mCollisionCount = 0; + 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; +} + +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; +} + +BSPNode *SceneObject::buildCollisionBSP(BSPTree*, const Box3F&, const SphereF&) +{ + return NULL; +} + +bool SceneObject::castRay(const Point3F&, const Point3F&, RayInfo*) +{ + return false; +} + +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.min; + const F32 * pMax = (const F32*)mObjBox.max; + + F32 maxStartTime = -1; + F32 minEndTime = 1; + F32 startTime; + F32 endTime; + + // used for getting normal + U32 hitIndex; + 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), + }; + + // + 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 +{ + AssertFatal(false, "(SceneObject::getMomentum): Should never be called, this is just a default"); + return Point3F(0, 0, 0); +} + +void SceneObject::setMomentum(const Point3F&) +{ + AssertFatal(false, "(SceneObject::setMomentum): Should never be called, this is just a default"); +} + + +F32 SceneObject::getMass() const +{ + AssertFatal(false, "(SceneObject::getMass): Should never be called, this is just a default"); + return 1.0; +} + + +bool SceneObject::displaceObject(const Point3F&) +{ + AssertFatal(false, "(SceneObject::displaceObject): Should never be called, this is just a default"); + return false; +} + + +void SceneObject::enableCollision() +{ + if (mCollisionCount) + --mCollisionCount; +} + +bool SceneObject::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + mWorldToObj = mObjToWorld; + mWorldToObj.affineInverse(); + resetWorldBox(); + + setRenderTransform(mObjToWorld); + return true; +} + +void SceneObject::addToScene() +{ + if(isClientObject()) + { + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + } + else + { + gServerContainer.addObject(this); + gServerSceneGraph->addObjectToScene(this); + } +} + +void SceneObject::onRemove() +{ + Parent::onRemove(); +} + +void SceneObject::inspectPostApply() +{ + if(isServerObject()) + setTransform(getTransform()); +} + +void SceneObject::removeFromScene() +{ + if (mSceneManager != NULL) + mSceneManager->removeObjectFromScene(this); + if (getContainer()) + getContainer()->removeObject(this); +} + +void SceneObject::setTransform(const MatrixF& mat) +{ + mObjToWorld = mWorldToObj = mat; + mWorldToObj.affineInverse(); + AssertFatal(mObjBox.isValidBox(), "Bad object box!"); + resetWorldBox(); + + if (mSceneManager != NULL && mNumCurrZones != 0) { + mSceneManager->zoneRemove(this); + mSceneManager->zoneInsert(this); + if (getContainer()) + getContainer()->checkBins(this); + } + + if(isClientObject()) + mLightingInfo.mDirty = true; + + setRenderTransform(mat); +} + +void SceneObject::setScale(const VectorF & scale) +{ + mObjScale = scale; + setTransform(MatrixF(mObjToWorld)); +} + +void SceneObject::resetWorldBox() +{ + AssertFatal(mObjBox.isValidBox(), "Bad object box!"); + mWorldBox = mObjBox; + mWorldBox.min.convolve(mObjScale); + mWorldBox.max.convolve(mObjScale); + mObjToWorld.mul(mWorldBox); +#ifdef __linux + if( !mWorldBox.isValidBox() ) { + // reset + mWorldBox.min.set( 0.0f, 0.0f, 0.0f ); + mWorldBox.max.set( 0.0f, 0.0f, 0.0f ); + } +#else + AssertFatal(mWorldBox.isValidBox(), "Bad world box!"); +#endif + + // Create mWorldSphere from mWorldBox + mWorldBox.getCenter(&mWorldSphere.center); + mWorldSphere.radius = (mWorldBox.max - mWorldSphere.center).len(); +} + +void SceneObject::setRenderTransform(const MatrixF& mat) +{ + mRenderObjToWorld = mRenderWorldToObj = mat; + mRenderWorldToObj.affineInverse(); + + AssertFatal(mObjBox.isValidBox(), "Bad object box!"); + resetRenderWorldBox(); +} + + +void SceneObject::resetRenderWorldBox() +{ + AssertFatal(mObjBox.isValidBox(), "Bad object box!"); + mRenderWorldBox = mObjBox; + mRenderWorldBox.min.convolve(mObjScale); + mRenderWorldBox.max.convolve(mObjScale); + mRenderObjToWorld.mul(mRenderWorldBox); +#ifdef __linux + if( !mRenderWorldBox.isValidBox() ) { + // reset + mRenderWorldBox.min.set( 0.0f, 0.0f, 0.0f ); + mRenderWorldBox.max.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.max - mRenderWorldSphere.center).len(); +} + + +void SceneObject::initPersistFields() +{ + Parent::initPersistFields(); + addField("position", TypeMatrixPosition, Offset(mObjToWorld, SceneObject)); + addField("rotation", TypeMatrixRotation, Offset(mObjToWorld, SceneObject)); + addField("scale", TypePoint3F, Offset(mObjScale, SceneObject)); +} + +void SceneObject::consoleInit() +{ + Con::addCommand("SceneObject", "getPosition", cGetPosition, "obj.getPosition()", 2, 2); + Con::addCommand("SceneObject", "setTransform", cSetTransform, "obj.setTransform(T)", 3, 3); + Con::addCommand("SceneObject", "getTransform", cGetTransform, "obj.getTransform()", 2, 2); + Con::addCommand("SceneObject", "setScale", cSetScale, "obj.setScale()",3, 3); + Con::addCommand("SceneObject", "getScale", cGetScale, "obj.getScale()", 2, 2); + Con::addCommand("SceneObject", "getWorldBox", cGetWorldBox, "obj.getWorldBox()", 2, 2); + Con::addCommand("SceneObject", "getWorldBoxCenter", cGetWorldBoxCenter, "obj.getWorldBoxCenter()", 2, 2); + Con::addCommand("SceneObject", "getObjectBox", cGetObjectBox, "obj.getObjectBox()", 2, 2); + Con::addCommand("SceneObject", "getForwardVector", cGetForwardVector, "obj.getForwardVector()", 2, 2); + + // Functions for searching the container database. Note that these will ONLY work + // on the server... + Con::addCommand("InitContainerRadiusSearch", cInitContainerRadiusSearch, "InitContainerRadiusSearch(\"x y z\", radius, mask)", 4, 4); + Con::addCommand("ContainerSearchNext", cContainerSearchNext, "ContainerSearchNext()", 1, 1); + Con::addCommand("ContainerSearchCurrDist", cContainerSearchCurrDist, "ContainerSearchCurrDist()", 1, 1); + Con::addCommand("ContainerSearchCurrRadDamageDist", cContainerSearchCurrRadDamageDist, "ContainerSearchCurrRadDamageDist()", 1, 1); + + Con::addCommand("ContainerRayCast", cContainerRayCast, "ContainerRayCast(\"x y z\", \"x y z\", mask, [exempt object])", 4, 5); + Con::addCommand("ContainerBoxEmpty", cContainerBoxEmpty, "ContainerBoxEmpty(Mask, Loc, Rad [,yRad, zRad]);", 4, 6); +} + +bool SceneObject::onSceneAdd(SceneGraph* pGraph) +{ + mSceneManager = pGraph; + mSceneManager->zoneInsert(this); + return true; +} + +void SceneObject::onSceneRemove() +{ + mSceneManager->zoneRemove(this); + mSceneManager = NULL; +} + +bool SceneObject::prepRenderImage(SceneState*, const U32, const U32, const bool) +{ + return false; +} + +void SceneObject::renderObject(SceneState*, SceneRenderImage*) +{ + // +} + +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 F64*, 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.min.set(-1,-1,-1); + box.max.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() +{ + 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; + + cleanupSearchVectors(); +} + +bool Container::addObject(SceneObject* obj) +{ + AssertFatal(obj->mContainer == NULL, "Adding already added object."); + obj->mContainer = this; + obj->linkAfter(&mStart); + + insertIntoBins(obj); + return true; +} + +bool Container::removeObject(SceneObject* obj) +{ + AssertFatal(obj->mContainer == this, "Trying to remove from wrong container."); + removeFromBins(obj); + + 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->min.x, pWBox->max.x, minX, maxX); + getBinRange(pWBox->min.y, pWBox->max.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) { + 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) +{ + 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) { + 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::removeFromBins(SceneObject* obj) +{ + 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); + } +} + + +void Container::checkBins(SceneObject* obj) +{ + AssertFatal(obj != NULL, "No object?"); + + if (obj->mBinRefHead == NULL) + { + insertIntoBins(obj); + 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->min.x, pWBox->max.x, minX, maxX); + getBinRange(pWBox->min.y, pWBox->max.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); + } +} + + +void Container::findObjects(const Box3F& box, U32 mask, FindCallback callback, S32 key) +{ + U32 minX, maxX, minY, maxY; + getBinRange(box.min.x, box.max.x, minX, maxX); + getBinRange(box.min.y, box.max.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)) { + (*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)) { + (*callback)(chain->object,key); + } + } + } + chain = chain->nextInBin; + } +} + + +void Container::polyhedronFindObjects(const Polyhedron& polyhedron, U32 mask, FindCallback callback, S32 key) +{ + U32 i; + Box3F box; + box.min.set(1e9, 1e9, 1e9); + box.max.set(-1e9, -1e9, -1e9); + for (i = 0; i < polyhedron.pointList.size(); i++) { + box.min.setMin(polyhedron.pointList[i]); + box.max.setMax(polyhedron.pointList[i]); + } + + U32 minX, maxX, minY, maxY; + getBinRange(box.min.x, box.max.x, minX, maxX); + getBinRange(box.min.y, box.max.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)) { + (*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)) { + (*callback)(chain->object,key); + } + } + } + chain = chain->nextInBin; + } +} + + +//---------------------------------------------------------------------------- +// 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::castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info) +{ + PROFILE_START(ContainerCastRay); + 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; + if (ptr->castRay(xformedStart, xformedEnd, &ri)) { + 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; + getBinRange(normalStart.x, normalEnd.x, minX, maxX); + getBinRange(getMin(normalStart.y, normalEnd.y), + getMax(normalStart.y, normalEnd.y), 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)) { + 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->castRay(xformedStart, xformedEnd, &ri)) { + if(ri.t < currentT) { + *info = ri; + info->point.interpolate(start, end, info->t); + currentT = ri.t; + } + } + } + } + } + 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; + if (ptr->castRay(xformedStart, xformedEnd, &ri)) { + if(ri.t < currentT) { + *info = ri; + info->point.interpolate(start, end, info->t); + currentT = ri.t; + } + } + } + } + } + chain = chain->nextInBin; + } + } + + subStartX = subEndX; + subEndX = getMin(subEndX + csmBinSize, currEndX); + } + + currStartX = currEndX; + } + } + PROFILE_END(); + return currentT != 2; +} + +// 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,S32 key) +{ + Container::CallbackInfo* info = reinterpret_cast(key); + object->buildPolyList(info->polyList,info->boundingBox,info->boundingSphere); +} + + +//---------------------------------------------------------------------------- + +bool Container::buildPolyList(const Box3F& box, U32 mask, AbstractPolyList* polyList,FindCallback callback,S32 key) +{ + CallbackInfo info; + info.boundingBox = box; + info.polyList = polyList; + info.key = key; + + // Build bounding sphere + info.boundingSphere.center = (info.boundingBox.min + info.boundingBox.max) * 0.5; + VectorF bv = box.max - info.boundingSphere.center; + info.boundingSphere.radius = bv.len(); + + sPolyList = polyList; + findObjects(box,mask,callback? callback: buildCallback,S32(&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, + S32 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.min = info.boundingBox.max = start; + info.boundingBox.min.setMin(end); + info.boundingBox.max.setMax(end); + info.boundingBox.min += box.min; + info.boundingBox.max += box.max; + info.boundingSphere.center = (info.boundingBox.min + info.boundingBox.max) * 0.5; + VectorF bv = info.boundingBox.max - 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.min.x + start.x; + point[0].y = point[3].y = point[4].y = point[7].y = box.min.y + start.y; + point[2].x = point[3].x = point[6].x = point[7].x = box.max.x + start.x; + point[1].y = point[2].y = point[5].y = point[6].y = box.max.y + start.y; + point[0].z = point[1].z = point[2].z = point[3].z = box.min.z + start.z; + point[4].z = point[5].z = point[6].z = point[7].z = box.max.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.min += queryExpansion->min; + queryBox.max += queryExpansion->max; + } + + // Query main database + findObjects(queryBox,mask,callback? callback: buildCallback,S32(&info)); + sExtrudedPolyList.adjustCollisionTime(); + return collisionList->count != 0; +} + + +//---------------------------------------------------------------------------- + +bool Container::buildCollisionList(const Polyhedron& polyhedron, + const Point3F& start, const Point3F& end, + const VectorF& velocity, + U32 mask, CollisionList* collisionList, + FindCallback callback, S32 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 min(1e10, 1e10, 1e10); + Point3F max(-1e10, -1e10, -1e10); + for (U32 i = 0; i < polyhedron.pointList.size(); i++) { + min.setMin(polyhedron.pointList[i]); + max.setMax(polyhedron.pointList[i]); + } + + info.boundingBox.min = info.boundingBox.max = Point3F(0, 0, 0); + info.boundingBox.min.setMin(vector); + info.boundingBox.max.setMax(vector); + info.boundingBox.min += min; + info.boundingBox.max += max; + info.boundingSphere.center = (info.boundingBox.min + info.boundingBox.max) * 0.5; + VectorF bv = info.boundingBox.max - 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,S32(&info)); + sExtrudedPolyList.adjustCollisionTime(); + return collisionList->count != 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.min -= Point3F(searchRadius, searchRadius, searchRadius); + queryBox.max += Point3F(searchRadius, searchRadius, searchRadius); + + SimpleQueryList queryList; + findObjects(queryBox, searchMask, SimpleQueryList::insertionCallback, S32(&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().min.x; + bMaxs = &queryList.mList[i]->getWorldBox().max.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) { + 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::containerSearchCurrRadDamageDist() +{ + 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, S32 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) +{ + MatrixF xform = mObjToWorld; + xform.setColumn(3, pos); + setTransform(xform); +} + +//-------------------------------------------------------------------------- +SceneObject::LightingInfo::LightingInfo() +{ + mUseInfo = false; + mDirty = false; + mHasLastColor = false; + + // set the colors to half white for invalid surfaces and all... + mDefaultColor.set(0.5f, 0.5f, 0.5f); + mAlarmColor.set(0.5f, 0.5f, 0.5f); + mLastColor.set(0.5f, 0.5f, 0.5f); + + mLastTime = 0; +} + +//-------------------------------------------------------------------------- +void SceneObject::installLights() +{ + // install the lights: + LightManager * lightManager = gClientSceneGraph->getLightManager(); + AssertFatal(lightManager, "SceneObject::installLights: LightManager not found"); + + ColorF ambientColor; + if(getLightingAmbientColor(&ambientColor)) + { + switch(mLightingInfo.mLightSource) + { + case LightingInfo::Interior: + { + // ambient/directional contributions + const F32 directionalFactor = 0.3f; + const F32 ambientFactor = 0.7f; + + LightInfo & light = mLightingInfo.smAmbientLight; + + light.mType = LightInfo::Ambient; + light.mDirection = VectorF(0.57735f, 0.57735f, -0.57735f); + light.mColor = ambientColor * directionalFactor; + light.mAmbient = ambientColor * ambientFactor; + + lightManager->addLight(&mLightingInfo.smAmbientLight); + lightManager->setVectorLightsEnabled(false); + + break; + } + + case LightingInfo::Terrain: + { + F32 factor = mClampF((ambientColor.red + ambientColor.green + ambientColor.blue) / 3.f, 0.f, 1.f); + lightManager->setVectorLightsAttenuation(factor); + break; + } + } + } + lightManager->installGLLights(getRenderWorldBox()); +} + +void SceneObject::uninstallLights() +{ + LightManager * lightManager = gClientSceneGraph->getLightManager(); + AssertFatal(lightManager, "SceneObject::uninstallLights: LightManager not found"); + + lightManager->removeLight(&mLightingInfo.smAmbientLight); + + // resets all ambient/vector settings + lightManager->uninstallGLLights(); +} + +//-------------------------------------------------------------------------- +// Lighting update: not used directly by sceneobject... +// - if an interior, which contains this object, moves then this value will be incorrect +bool SceneObject::getLightingAmbientColor(ColorF * col) +{ + AssertFatal(col, "SceneObject::getLightingAmbientColor: invalid color ptr"); + + const F32 cRayLength = 100.f; // down/up + const F32 cTerrainRayLength = 10.f; // height above terrain for no ambient + const F32 cColorStep = 0.2f; // amount to add per 100ms + + PROFILE_START(GetLightingAmbientColor); + + // query a new value? + if(mLightingInfo.mDirty) + { + mLightingInfo.mDirty = false; + + Point3F pos; + getRenderWorldBox().getCenter(&pos); + + // check if shadowed: + disableCollision(); + + mLightingInfo.mUseInfo = false; + mLightingInfo.mInterior = 0; + + // look up/down: + RayInfo collision; + if(gClientContainer.castRay(pos, Point3F(pos.x, pos.y, pos.z + cRayLength), InteriorObjectType, &collision)) + { + if(gClientContainer.castRay(pos, Point3F(pos.x, pos.y, pos.z - cRayLength), InteriorObjectType, &collision)) + { + // interior: + InteriorInstance * instance = dynamic_cast(collision.object); + + if((instance && (collision.face != -1))) + { + // determine the color of this point... + Interior * interior = instance->getDetailLevel(0); + AssertFatal(interior, "SceneObject::getLightingAmbientColor: invalid interior"); + AssertFatal(collision.face < interior->getSurfaceCount(), "SceneObject::getLightingAmbientColor: invalid surface"); + + const Interior::Surface & surface = interior->getSurface(collision.face); + + // get the render order + U32 currIndex = 0; + U32 last = 2; + + U32 renderIndices[512]; + + while(last < surface.windingCount) + { + // first + renderIndices[currIndex++] = last - 2; + renderIndices[currIndex++] = last - 1; + renderIndices[currIndex++] = last - 0; + last++; + + if(last == surface.windingCount) + break; + + // second + renderIndices[currIndex++] = last - 1; + renderIndices[currIndex++] = last - 2; + renderIndices[currIndex++] = last - 0; + last++; + } + + // get the winding indices, the src indices and the points + Point3F points[512]; + U32 srcIndices[512]; + for(U32 i = 0; i < currIndex; i++) + { + srcIndices[i] = surface.windingStart + renderIndices[i]; + renderIndices[i] = interior->getWinding(srcIndices[i]); + points[i] = interior->getPoint(renderIndices[i]); + } + + // project the collision point into object space + instance->getWorldTransform().mulP(collision.point); + + PlaneF plane = interior->getPlane(surface.planeIndex); + if(Interior::planeIsFlipped(surface.planeIndex)) + plane.neg(); + + // walk the tri's to find the one under collision point + S32 insideOffset = -1; + for(U32 offset = 0; offset < currIndex; offset += 3) + { + bool inside = true; + for(U32 j = 0; (j < 3) && inside; j++) + { + U32 k = (j+1) % 3; + + VectorF vec1 = points[offset + k] - points[offset + j]; + VectorF vec2 = collision.point - points[offset + j]; + + Point3F cross; + mCross(vec2, vec1, &cross); + + if(mDot(plane, cross) < 0.f) + inside = false; + } + + if(inside) + { + insideOffset = offset; + break; + } + } + + if(insideOffset != -1) + { + // box map the surface: + F32 max = 0.f; + S32 index = -1; + F32 * pNormal = (F32*)plane; + + // n, s, t: + static U32 BoxIndices[][3] = { + { 0, 1, 2 }, + { 1, 0, 2 }, + { 2, 0, 1 }, + }; + + for(U32 i = 0; i < 3; i++) + { + if(mFabs(pNormal[i]) >= mFabs(max)) + { + max = pNormal[i]; + index = i; + } + } + AssertFatal(index >= 0, "Failed to get best normal"); + + ColorF color(0.f, 0.f, 0.f, 1.f); + + // grab the points + Point3F pnts[3]; + for(U32 j = 0; j < 3; j++) + pnts[j] = points[insideOffset + j]; + + // project the collision point + F32 * pColPnt = const_cast((const F32*)collision.point); + pColPnt[BoxIndices[index][0]] = 0.f; + + // calculate both colors (default/alarm): + for(U32 i = 0; i < 2; i++) + { + Vector * vertexColors = 0; + ColorF * setColor = 0; + + // default/alarm pass: + if(i == 0) + { + vertexColors = instance->getVertexColorsNormal(0); + setColor = &mLightingInfo.mDefaultColor; + } + else if(interior->hasAlarmState()) + { + // only need the interior handle if it has an alarm state + mLightingInfo.mInterior = instance; + + vertexColors = instance->getVertexColorsAlarm(0); + setColor = &mLightingInfo.mAlarmColor; + } + else + { + mLightingInfo.mAlarmColor = mLightingInfo.mDefaultColor; + break; + } + setColor->set(0.f, 0.f, 0.f); + + // grab the source colors + ColorF srcColors[3]; + for(U32 j = 0; j < 3; j++) + srcColors[j] = (*vertexColors)[srcIndices[insideOffset + j]]; + + // handle each componant + F32 * pDestColor = const_cast(((const F32*)*setColor)); + for(U32 j = 0; j < 3; j++) + { + // project onto a color plane + for(U32 k = 0; k < 3; k++) + { + F32 * pPnt = const_cast((const F32*)pnts[k]); + F32 * pSrcCol = const_cast((const F32*)srcColors[k]); + pPnt[BoxIndices[index][0]] = pSrcCol[j]; + } + + // create a plane from the projected points + PlaneF colPlane(pnts[0], pnts[1], pnts[2]); + + // determine the color value... (distance to plane is 0) + F32 * pColPlane = const_cast((const F32 *)colPlane); + + U32 * pBoxIndices = BoxIndices[index]; + F32 val = (pColPlane[pBoxIndices[1]] * pColPnt[pBoxIndices[1]] + + pColPlane[pBoxIndices[2]] * pColPnt[pBoxIndices[2]] + colPlane.d) / + (-pColPlane[pBoxIndices[0]]); + + pDestColor[j] = mClampF(val, 0.f, 1.f); + } + } + } + } + + // use info if collided + mLightingInfo.mLightSource = LightingInfo::Interior; + mLightingInfo.mUseInfo = true; + } + } + else if(gClientContainer.castRay(pos, Point3F(pos.x, pos.y, pos.z - cTerrainRayLength), TerrainObjectType, &collision)) + { + TerrainBlock * terrain = dynamic_cast(collision.object); + + // terrain: + if(terrain) + { + // get the position of the point on the terrain + const MatrixF & mat = terrain->getTransform(); + Point3F origin; + mat.getColumn(3, &origin); + + F32 lmapSize = F32(terrain->getSquareSize()) * (F32(TerrainBlock::BlockSize) / F32(TerrainBlock::LightmapSize)); + + Point2I pos; + pos.x = (S32)mFloor((collision.point.x - origin.x) / lmapSize); + pos.y = (S32)mFloor((collision.point.y - origin.y) / lmapSize); + + U32 lmapMask = TerrainBlock::LightmapSize - 1; + pos.x &= lmapMask; + pos.y &= lmapMask; + + // grab the lightmap colors... + GBitmap * lightmap = terrain->lightMap; + AssertFatal(lightmap, "SceneObject::getAmbientLightingColor: no lightmap on terrain"); + AssertFatal((lightmap->getWidth() > pos.x) && (lightmap->getHeight() > pos.y), "SceneObject::getAmbientLightingColor: invalid lightmap coords"); + AssertFatal(lightmap->getFormat() == GBitmap::RGB5551, "SceneObject::getAmbientLightingColor: terrain lightmap is not 5551"); + + ColorI terrCol; + if(lightmap->getColor(pos.x, pos.y, terrCol)) + { + const F32 scale = 255.f / 31.f; + + // convert the color... + terrCol.red = U8(F32(terrCol.red) * scale); + terrCol.green = U8(F32(terrCol.green) * scale); + terrCol.blue = U8(F32(terrCol.blue) * scale); + + mLightingInfo.mDefaultColor = terrCol; + mLightingInfo.mLightSource = LightingInfo::Terrain; + mLightingInfo.mUseInfo = true; + } + } + } + + enableCollision(); + } + PROFILE_END(); + + // has a value? + if(mLightingInfo.mUseInfo) + { + // currently in an interior which has an alarm state? + ColorF color; + if(bool(mLightingInfo.mInterior)) + { + InteriorInstance * interior = static_cast(static_cast(mLightingInfo.mInterior)); + if(interior->inAlarmState()) + color = mLightingInfo.mAlarmColor; + else + color = mLightingInfo.mDefaultColor; + } + else + color = mLightingInfo.mDefaultColor; + + S32 time = Platform::getVirtualMilliseconds(); + + // has a previous color? + if(mLightingInfo.mHasLastColor) + { + // do each componant + F32 * pColor = const_cast((const F32 *)color); + F32 * pLastColor = const_cast((const F32 *)mLightingInfo.mLastColor); + + // cColorStep is amount added per 100ms + F32 step = (F32(time - mLightingInfo.mLastTime) / 100.f) * cColorStep; + + for(U32 i = 0; i < 3; i++) + { + if(pColor[i] > pLastColor[i]) + pColor[i] = mClampF(pLastColor[i] + step, 0.f, pColor[i]); + else if(pColor[i] < pLastColor[i]) + pColor[i] = mClampF(pLastColor[i] - step, pColor[i], 1.f); + } + } + + mLightingInfo.mHasLastColor = true; + mLightingInfo.mLastColor = color; + mLightingInfo.mLastTime = time; + + *col = color; + return(true); + } + else + mLightingInfo.mHasLastColor = false; + + return(false); +} diff --git a/sim/sceneObject.h b/sim/sceneObject.h new file mode 100644 index 0000000..4041430 --- /dev/null +++ b/sim/sceneObject.h @@ -0,0 +1,502 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENEOBJECT_H_ +#define _SCENEOBJECT_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#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 "game/objectTypes.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "scenegraph/lightManager.h" +#endif + +//-------------------------------------- Forward declarations... +class SceneObject; +class SceneGraph; +class SceneState; +class SceneRenderImage; +class Box3F; +class Point3F; +class LightManager; +class Convex; + +//---------------------------------------------------------------------------- +struct RayInfo: public Collision { + // The collision struct has object, point, normal & material. + F32 t; +}; + +//-------------------------------------------------------------------------- +// There are two indiscretions here. First is the name, which refers rather +// blatantly to the container bin system. A hygine 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. +class SceneObjectRef +{ + public: + SceneObject* object; + SceneObjectRef* nextInBin; + SceneObjectRef* prevInBin; + SceneObjectRef* nextInObj; + + U32 zone; +}; + +// A scope frustum describes a pyramid to clip new portals against. It is +// rooted at the root position of the scoping query, which is not stored +// here. +class ScopeFrustum +{ + public: + enum Constants { + TopLeft = 0, + TopRight = 1, + BottomLeft = 2, + BottomRight = 3 + }; + + Point3F frustumPoints[4]; +}; + + +//---------------------------------------------------------------------------- +class Container +{ + public: + struct Link + { + Link* next; + Link* prev; + Link(); + void unlink(); + void linkAfter(Link* ptr); + }; + + struct CallbackInfo { + AbstractPolyList* polyList; + Box3F boundingBox; + SphereF boundingSphere; + S32 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; + + public: + Container(); + ~Container(); + + // Basic database operations + typedef void (*FindCallback)(SceneObject*,S32 key); + void findObjects(U32 mask, FindCallback, S32 key = 0); + void findObjects(const Box3F& box, U32 mask, FindCallback, S32 key = 0); + void polyhedronFindObjects(const Polyhedron& polyhedron, U32 mask, + FindCallback, S32 key = 0); + + // Line intersection + bool castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info); + bool collideBox(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info); + + // Poly list + bool buildPolyList(const Box3F& box, U32 mask, AbstractPolyList*,FindCallback=0,S32 key = 0); + bool buildCollisionList(const Box3F& box, const Point3F& start, const Point3F& end, const VectorF& velocity, + U32 mask,CollisionList* collisionList,FindCallback = 0,S32 key = 0,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, S32 key = 0); + + // + 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); + + // Object searches to support console querying of the database. ONLY WORKS ON SERVER + private: + Vector*> mSearchList; + S32 mCurrSearchPos; + Point3F mSearchReferencePoint; + void cleanupSearchVectors(); + + public: + void initRadiusSearch(const Point3F& searchPoint, + const F32 searchRadius, + const U32 searchMask); + U32 containerSearchNext(); + F32 containerSearchCurrDist(); + F32 containerSearchCurrRadDamageDist(); +}; + + +//---------------------------------------------------------------------------- +// 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, S32 key); +}; + + +//---------------------------------------------------------------------------- +class SceneObject : public NetObject, public Container::Link +{ + typedef NetObject Parent; + friend class Container; + friend class SceneGraph; + friend class SceneState; + + //-------------------------------------- Public constants + public: + enum { + MaxObjectZones = 128 + }; + enum TraverseColor { + White = 0, + Grey = 1, + Black = 2 + }; + + //-------------------------------------- Public interfaces + // C'tors and D'tors + private: + SceneObject(const SceneObject&); // disallowed + public: + SceneObject(); + virtual ~SceneObject(); + + // Scripting interface + const char* scriptThis(); + + // Collision and transform related interface + public: + virtual void disableCollision(); + virtual void enableCollision(); + bool isCollisionEnabled() const { return mCollisionCount == 0; } + + virtual bool isDisplacable() const; + virtual Point3F getMomentum() const; + virtual void setMomentum(const Point3F&); + virtual F32 getMass() const; + virtual bool displaceObject(const Point3F& displaceVector); + + const MatrixF& getTransform() const { return mObjToWorld; } + const MatrixF& getWorldTransform() const { return mWorldToObj; } + const VectorF& getScale() const { return mObjScale; } + + const Box3F& getObjBox() const { return mObjBox; } + const Box3F& getWorldBox() const { return mWorldBox; } + const SphereF& getWorldSphere() const { return mWorldSphere; } + Point3F getBoxCenter() const { return (mWorldBox.min + mWorldBox.max) * 0.5f; } + + virtual void setTransform(const MatrixF&); + virtual void setScale(const VectorF & scale); + + virtual void setRenderTransform(const MatrixF&); + const MatrixF& getRenderTransform() const { return mRenderObjToWorld; } + const MatrixF& getRenderWorldTransform() const { return mRenderWorldToObj; } + const Box3F& getRenderWorldBox() const { return mRenderWorldBox; } + + virtual void buildConvex(const Box3F& box,Convex* convex); + virtual bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + virtual BSPNode* buildCollisionBSP(BSPTree *tree, 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); + + Point3F getPosition() const; + Point3F getRenderPosition() const; + void setPosition(const Point3F &pos); + + // Rendering and zone interface + public: + bool isManagingZones() const; + U32 getZoneRangeStart() const { return mZoneRangeStart; } + U32 getNumCurrZones() const { return mNumCurrZones; } + U32 getCurrZone(const U32 index) const; + + virtual bool getOverlappingZones(SceneObject* obj, U32* zones, U32* numZones); + virtual U32 getPointZone(const Point3F& p); + + virtual void renderObject(SceneState*, SceneRenderImage*); + virtual bool prepRenderImage(SceneState*, const U32 stateKey, const U32 startZone, + const bool modifyBaseZoneState = false); + virtual bool scopeObject(const Point3F& rootPosition, + const F32 rootDistance, + bool* zoneScopeState); + + void addToScene(); + void removeFromScene(); + + //-------------------------------------- Derived class interface + // Overrides + protected: + bool onAdd(); + void onRemove(); + void inspectPostApply(); + + // Overrideables + protected: + virtual bool onSceneAdd(SceneGraph*); + virtual void onSceneRemove(); + + // Portal functions. + virtual void transformModelview(const U32 portalIndex, const MatrixF& oldMV, MatrixF* newMV); + virtual void transformPosition(const U32 portalIndex, Point3F& point); + virtual bool computeNewFrustum(const U32 portalIndex, + const F64* oldFrustum, + const F64 nearPlane, + const F64 farPlane, + const RectI& oldViewport, + F64* newFrustum, + RectI& newViewport, + const bool flippedMatrix); + virtual void openPortal(const U32 portalIndex, + SceneState* pCurrState, + SceneState* pParentState); + virtual void closePortal(const U32 portalIndex, + SceneState* pCurrState, + SceneState* pParentState); + public: + virtual void getWSPortalPlane(const U32 portalIndex, PlaneF*); + + protected: + void setLastState(SceneState*, U32); + bool isLastState(SceneState*, U32) const; + void setTraverseColor(TraverseColor); + TraverseColor getTraverseColor() const; + + // lighting info: + struct LightingInfo + { + LightingInfo(); + + bool mUseInfo; + bool mDirty; + ColorF mDefaultColor; + ColorF mAlarmColor; + + SimObjectPtr mInterior; + + bool mHasLastColor; + ColorF mLastColor; + U32 mLastTime; + + static LightInfo smAmbientLight; + + enum { + Interior = 0, + Terrain, + }; + U32 mLightSource; + }; + + virtual void installLights(); + virtual void uninstallLights(); + virtual bool getLightingAmbientColor(ColorF * col); + LightingInfo mLightingInfo; + + // Transform and Collision related members + protected: + Container* mContainer; + + MatrixF mObjToWorld; // object transform + MatrixF mWorldToObj; // inverse transform + Point3F mObjScale; // object scale + + Box3F mObjBox; + Box3F mWorldBox; + SphereF mWorldSphere; + + MatrixF mRenderObjToWorld; + MatrixF mRenderWorldToObj; + Box3F mRenderWorldBox; + SphereF mRenderWorldSphere; + + void resetWorldBox(); + void resetRenderWorldBox(); + + SceneObjectRef* mZoneRefHead; + SceneObjectRef* mBinRefHead; + + U32 mBinMinX; + U32 mBinMaxX; + U32 mBinMinY; + U32 mBinMaxY; + + U32 mContainerSeqKey; + U32 getContainerSeqKey() const { return mContainerSeqKey; } + void setContainerSeqKey(const U32 key) { mContainerSeqKey = key; } + +public: + Container* getContainer() { return mContainer; } + + protected: + S32 mCollisionCount; + + public: + U32 getTypeMask() { return(mTypeMask); } + + // Rendering related members + protected: + SceneGraph* mSceneManager; + U32 mZoneRangeStart; // 0xFFFFFFFF == no zones + +// U32 mCurrZones[MaxObjectZones]; +// SceneObject* mCurrZoneOwners[MaxObjectZones]; + U32 mNumCurrZones; + + private: + TraverseColor mTraverseColor; + SceneState* mLastState; + U32 mLastStateKey; + + + // Persist and console + public: + static void initPersistFields(); + static void consoleInit(); + + DECLARE_CONOBJECT(SceneObject); +}; + + +//-------------------------------------------------------------------------- +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::setTraverseColor(TraverseColor c) +{ + mTraverseColor = c; +} + +inline SceneObject::TraverseColor SceneObject::getTraverseColor() const +{ + return mTraverseColor; +} + +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, "Error, too few object refs!"); + } + AssertFatal(walk, "Error, too few object refs!"); + + return walk->zone; +} + + +//-------------------------------------------------------------------------- +inline SceneObjectRef* Container::allocateObjectRef() +{ + if (mFreeRefPool == NULL) { + addRefPoolBlock(); + } + AssertFatal(mFreeRefPool, "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->nextInBin = NULL; + trash->prevInBin = NULL; + trash->nextInObj = mFreeRefPool; + mFreeRefPool = trash; +} + +inline void Container::findObjects(U32 mask, FindCallback callback, S32 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 // _H_SCENEOBJECT_ + diff --git a/sim/simPath.cc b/sim/simPath.cc new file mode 100644 index 0000000..9962f1c --- /dev/null +++ b/sim/simPath.cc @@ -0,0 +1,361 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "Sim/simPath.h" +#include "console/consoleTypes.h" +#include "Sim/pathManager.h" +#include "PlatformWin32/platformGL.h" +#include "dgl/dgl.h" +#include "sceneGraph/sceneState.h" +#include "Math/mathIO.h" +#include "Core/bitStream.h" + +//-------------------------------------------------------------------------- +//-------------------------------------- Console functions and cmp funcs +// +namespace { + +static ColorF cubeColors[8] = { + ColorF(0, 0, 0), + ColorF(1, 0, 0), + ColorF(0, 1, 0), + ColorF(0, 0, 1), + ColorF(1, 1, 0), + ColorF(1, 0, 1), + ColorF(0, 1, 1), + ColorF(1, 1, 1) +}; + +static 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 U32 cubeFaces[6][4] = { + { 0, 2, 6, 4 }, + { 0, 2, 3, 1 }, + { 0, 1, 5, 4 }, + { 3, 2, 6, 7 }, + { 7, 6, 4, 5 }, + { 3, 7, 5, 1 } +}; + +void wireCube(F32 size, Point3F pos) +{ + glDisable(GL_CULL_FACE); + + for(int i = 0; i < 6; i++) + { + glBegin(GL_LINE_LOOP); + for(int vert = 0; vert < 4; vert++) + { + int idx = cubeFaces[i][vert]; + glColor3f(cubeColors[idx].red, cubeColors[idx].green, cubeColors[idx].blue); + glVertex3f(cubePoints[idx].x * size + pos.x, cubePoints[idx].y * size + pos.y, cubePoints[idx].z * size + pos.z); + } + glEnd(); + } +} + + +void cPathMissionLoadDone(SimObject*, S32, const char**) +{ + // 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++) { + Path* pPath = dynamic_cast(groups[i]); + if (pPath) + pPath->finishPath(); + } +} + +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 {} + + +//-------------------------------------------------------------------------- +//-------------------------------------- Implementation +// +IMPLEMENT_CONOBJECT(Path); + +Path::Path() +{ + mPathIndex = NoPathIndex; +} + +Path::~Path() +{ + // +} + +//-------------------------------------------------------------------------- +void Path::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + + +void Path::consoleInit() +{ + Con::addCommand("pathOnMissionLoadDone", cPathMissionLoadDone, "pathOnMissionLoadDone()", 1, 1); +} + + +//-------------------------------------------------------------------------- +bool Path::onAdd() +{ + if(!Parent::onAdd()) + return false; + + return true; +} + + +void Path::onRemove() +{ + // + + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +void Path::finishPath() +{ + // If we need to, allocate a path index from the manager + if (mPathIndex == NoPathIndex) + mPathIndex = gServerPathManager->allocatePathId(); + + // Sort the markers objects into sequence order + dQsort(objectList.address(), objectList.size(), sizeof(SimObject*), cmpPathObject); + + Vector positions; + Vector times; + + 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); + times.push_back(pMarker->mMSToNext); + } + } + + // DMMTODO: Looping paths. + gServerPathManager->updatePath(mPathIndex, positions, times); +} + +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) + finishPath(); + } +} + +void Path::removeObject(SimObject* obj) +{ + bool recalc = dynamic_cast(obj) != NULL; + + Parent::removeObject(obj); + + if (mPathIndex != NoPathIndex && recalc == true) + finishPath(); +} + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +IMPLEMENT_CO_NETOBJECT_V1(Marker); +Marker::Marker() +{ + // Not ghostable unless we're editing... + //mNetFlags.set(Ghostable); + + mTypeMask = MarkerObjectType; + + mSeqNum = 0; + mMSToNext = 1000; +} + +Marker::~Marker() +{ + // +} + +//-------------------------------------------------------------------------- +void Marker::initPersistFields() +{ + Parent::initPersistFields(); + + addField("seqNum", TypeS32, Offset(mSeqNum, Marker)); + addField("msToNext", TypeS32, Offset(mMSToNext, Marker)); +} + + +void Marker::consoleInit() +{ + // +} + + +//-------------------------------------------------------------------------- +bool Marker::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox = Box3F(Point3F(-.25, -.25, -.25), Point3F(.25, .25, .25)); + resetWorldBox(); + + addToScene(); + + return true; +} + + +void Marker::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + + + +//-------------------------------------------------------------------------- +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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + state->insertRenderImage(image); + } + + return false; +} + + +void Marker::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + // Uncomment this if this is a "simple" (non-zone managing) object + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + // RENDER CODE HERE + wireCube(0.5, Point3F(0, 0, 0)); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + + +//-------------------------------------------------------------------------- +U32 Marker::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + Point3F pos; + getTransform().getColumn(3, &pos); + mathWrite(*stream, pos); + + // + + return retMask; +} + +void Marker::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + Point3F pos; + mathRead(*stream, &pos); + + MatrixF temp(true); + temp.setColumn(3, pos); + setTransform(temp); +} diff --git a/sim/simPath.h b/sim/simPath.h new file mode 100644 index 0000000..7298ccc --- /dev/null +++ b/sim/simPath.h @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMPATH_H_ +#define _SIMPATH_H_ + +#ifndef _SCENEOBJECT_H_ +#include "Sim/sceneObject.h" +#endif + +//-------------------------------------------------------------------------- +class Path : public SimGroup +{ + typedef SimGroup Parent; + + public: + enum { + NoPathIndex = 0xFFFFFFFF + }; + + + private: + U32 mPathIndex; + + protected: + bool onAdd(); + void onRemove(); + + public: + Path(); + ~Path(); + + void addObject(SimObject*); + void removeObject(SimObject*); + + void finishPath(); + U32 getPathIndex() const; + + DECLARE_CONOBJECT(Path); + static void initPersistFields(); + static void consoleInit(); +}; + + +//-------------------------------------------------------------------------- +class Marker : public SceneObject +{ + typedef SceneObject Parent; + friend class Path; + + public: + U32 mSeqNum; + U32 mMSToNext; + + // Rendering + protected: + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + protected: + bool onAdd(); + void onRemove(); + + public: + Marker(); + ~Marker(); + + DECLARE_CONOBJECT(Marker); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); +}; + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +inline U32 Path::getPathIndex() const +{ + return mPathIndex; +} + + +#endif // _H_PATH + diff --git a/targets.torque.mk b/targets.torque.mk new file mode 100644 index 0000000..da8ce75 --- /dev/null +++ b/targets.torque.mk @@ -0,0 +1,520 @@ +SOURCE.AI=\ + ai/aiConnection.cc \ +# ai/aiPlayer.cc + +SOURCE.AUDIO=\ + audio/audio.cc \ + audio/audioBuffer.cc \ + audio/audioDataBlock.cc \ + audio/audioFunctions.cc \ + +SOURCE.COLLISION=\ + collision/abstractPolyList.cc \ + collision/boxConvex.cc \ + collision/clippedPolyList.cc \ + collision/convex.cc \ + collision/depthSortList.cc \ + collision/earlyOutPolyList.cc \ + collision/extrudedPolyList.cc \ + collision/gjk.cc \ + collision/planeExtractor.cc \ + collision/polyhedron.cc \ + collision/polytope.cc + +SOURCE.CONSOLE=\ + console/compiledEval.cc \ + console/compiler.cc \ + console/console.cc \ + console/consoleFunctions.cc \ + console/consoleInternal.cc \ + console/consoleObject.cc \ + console/consoleTypes.cc \ + console/gram.cc \ + console/scan.cc \ + console/scriptObject.cc \ + console/simBase.cc \ + console/simDictionary.cc \ + console/simManager.cc \ + console/telnetConsole.cc \ + console/telnetDebugger.cc \ + console/consoleDoc.cc \ + console/typeValidators.cc + +# console/yylex.c \ +# console/yyparse.c + +SOURCE.CORE=\ + core/bitTables.cc \ + core/bitRender.cc \ + core/bitStream.cc \ + core/dataChunker.cc \ + core/dnet.cc \ + core/fileObject.cc \ + core/fileStream.cc \ + core/filterStream.cc \ + core/findMatch.cc \ + core/idGenerator.cc \ + core/memStream.cc \ + core/nStream.cc \ + core/nTypes.cc \ + core/resDictionary.cc \ + core/resManager.cc \ + core/resizeStream.cc \ + core/stringTable.cc \ + core/tVector.cc \ + core/tagDictionary.cc \ + core/zipAggregate.cc \ + core/zipHeaders.cc \ + core/zipSubStream.cc \ + core/crc.cc + +SOURCE.DGL=\ + dgl/bitmapBm8.cc \ + dgl/bitmapBmp.cc \ + dgl/bitmapGif.cc \ + dgl/bitmapJpeg.cc \ + dgl/bitmapPng.cc \ + dgl/dgl.cc \ + dgl/dglMatrix.cc \ + dgl/gBitmap.cc \ + dgl/gFont.cc \ + dgl/gPalette.cc \ + dgl/gTexManager.cc \ + dgl/lensFlare.cc \ + dgl/materialList.cc \ + dgl/materialPropertyMap.cc \ + dgl/rectClipper.cc \ + dgl/splineUtil.cc \ + dgl/stripCache.cc + +SOURCE.EDITOR=\ + editor/creator.cc \ + editor/editTSCtrl.cc \ + editor/editor.cc \ + editor/guiTerrPreviewCtrl.cc \ + editor/missionAreaEditor.cc \ + editor/terraformer.cc \ + editor/terraformerTexture.cc \ + editor/terraformerNoise.cc \ + editor/terrainActions.cc \ + editor/terrainEditor.cc \ + editor/worldEditor.cc + +SOURCE.GUI=\ + gui/guiArrayCtrl.cc \ + gui/guiAviBitmapCtrl.cc \ + gui/guiBackgroundCtrl.cc \ + gui/guiBitmapCtrl.cc \ + gui/guiBubbleTextCtrl.cc \ + gui/guiButtonBaseCtrl.cc \ + gui/guiButtonCtrl.cc \ + gui/guiCanvas.cc \ + gui/guiCheckBoxCtrl.cc \ + gui/guiChunkedBitmapCtrl.cc \ + gui/guiConsole.cc \ + gui/guiConsoleEditCtrl.cc \ + gui/guiConsoleTextCtrl.cc \ + gui/guiControl.cc \ + gui/guiControlListPopup.cc \ + gui/guiDebugger.cc \ + gui/guiEditCtrl.cc \ + gui/guiFilterCtrl.cc \ + gui/guiFrameCtrl.cc \ + gui/guiInputCtrl.cc \ + gui/guiInspector.cc \ + gui/guiMLTextCtrl.cc \ + gui/guiMLTextEditCtrl.cc \ + gui/guiMessageVectorCtrl.cc \ + gui/guiMouseEventCtrl.cc \ + gui/guiPopUpCtrl.cc \ + gui/guiProgressCtrl.cc \ + gui/guiRadioCtrl.cc \ + gui/guiScrollCtrl.cc \ + gui/guiSliderCtrl.cc \ + gui/guiTSControl.cc \ + gui/guiTextCtrl.cc \ + gui/guiTextEditCtrl.cc \ + gui/guiTextEditSliderCtrl.cc \ + gui/guiTextListCtrl.cc \ + gui/guiTreeViewCtrl.cc \ + gui/guiTypes.cc \ + gui/guiWindowCtrl.cc \ + gui/messageVector.cc \ + +# gui/guiHelpCtrl.cc \ + +SOURCE.HUD=\ + hud/hudBarDisplayCtrl.cc \ + hud/hudBezierDisplayCtrl.cc \ + hud/hudBitmapCtrl.cc \ + hud/hudClockCtrl.cc \ + hud/hudCrosshair.cc \ + hud/hudGLEx.cc \ + hud/hudHealthCtrl.cc \ + hud/hudObject.cc \ + hud/hudZoom.cc \ + hud/mBezier2D.cc + +SOURCE.INTERIOR=\ + interior/floorPlanRes.cc \ + interior/forceField.cc \ + interior/itfdump.asm \ + interior/interior.cc \ + interior/interiorCollision.cc \ + interior/interiorDebug.cc \ + interior/interiorIO.cc \ + interior/interiorInstance.cc \ + interior/interiorLMManager.cc \ + interior/interiorLightAnim.cc \ + interior/interiorRender.cc \ + interior/interiorRes.cc \ + interior/interiorResObjects.cc \ + interior/interiorSubObject.cc \ + interior/lightUpdateGrouper.cc \ + interior/mirrorSubObject.cc + +SOURCE.MATH=\ + math/mBox.cc \ + math/mConsoleFunctions.cc \ + math/mMathFn.cc \ + math/mMath_C.cc \ + math/mMatrix.cc \ + math/mPlaneTransformer.cc \ + math/mQuadPatch.cc \ + math/mQuat.cc \ + math/mRandom.cc \ + math/mSolver.cc \ + math/mSplinePatch.cc \ + math/mathTypes.cc \ + math/mathUtils.cc \ + math/mMathAMD.cc \ + math/mMathAMD_ASM.asm \ + math/mMathSSE.cc \ + math/mMathSSE_ASM.asm + +SOURCE.PLATFORM=\ + platform/gameInterface.cc \ + platform/platformAssert.cc \ + platform/platformMemory.cc \ + platform/platformRedBook.cc \ + platform/platformVideo.cc \ + platform/profiler.cc + +SOURCE.PLATFORMPPC=\ + platformPPC/ppcAudio.cc \ + platformPPC/ppcCPUInfo.cc \ + platformPPC/ppcConsole.cc \ + platformPPC/ppcFileio.cc \ + platformPPC/ppcFont.cc \ + platformPPC/ppcGL.cc \ + platformPPC/ppcInput.cc \ + platformPPC/ppcMath.cc \ + platformPPC/ppcMemory.cc \ + platformPPC/ppcNet.cc \ + platformPPC/ppcOGLVideo.cc \ + platformPPC/ppcProcessControl.cc \ + platformPPC/ppcStrings.cc \ + platformPPC/ppcTime.cc \ + platformPPC/ppcUtils.cc \ + platformPPC/ppcWindow.cc + +SOURCE.PLATFORMWIN32=\ + platformWin32/winAsmBlit.cc \ + platformWin32/winCPUInfo.cc \ + platformWin32/winConsole.cc \ + platformWin32/winD3DVideo.cc \ + platformWin32/winDInputDevice.cc \ + platformWin32/winDirectInput.cc \ + platformWin32/winFileio.cc \ + platformWin32/winFont.cc \ + platformWin32/winGL.cc \ + platformWin32/winInput.cc \ + platformWin32/winMath.cc \ + platformWin32/winMath_ASM.cc \ + platformWin32/winMemory.cc \ + platformWin32/winMutex.cc \ + platformWin32/winNet.cc \ + platformWin32/winOGLVideo.cc \ + platformWin32/winOpenAL.cc \ + platformWin32/winProcessControl.cc \ + platformWin32/winRedbook.cc \ + platformWin32/winSemaphore.cc \ + platformWin32/winStrings.cc \ + platformWin32/winThread.cc \ + platformWin32/winTime.cc \ + platformWin32/winV2Video.cc \ + platformWin32/winWindow.cc \ + +SOURCE.SIM=\ + sim/actionMap.cc \ + sim/decalManager.cc \ + sim/frameAllocator.cc \ + sim/netConnection.cc \ + sim/netEvent.cc \ + sim/netGhost.cc \ + sim/netObject.cc \ + sim/netStringTable.cc \ + sim/pathManager.cc \ + sim/sceneObject.cc \ + sim/simPath.cc + +SOURCE.GAME=\ + game/main.cc \ + game/debris.cc \ + game/debugView.cc \ + game/gameFunctions.cc \ + game/ambientAudioManager.cc \ + game/audioEmitter.cc \ + game/banList.cc \ + game/camera.cc \ + game/cameraFXMgr.cc \ + game/collisionTest.cc \ + game/explosion.cc \ + game/flyingVehicle.cc \ + game/game.cc \ + game/gameBase.cc \ + game/gameConnection.cc \ + game/gameConnectionEvents.cc \ + game/gameConnectionMoves.cc \ + game/gameProcess.cc \ + game/gameTSCtrl.cc \ + game/guiNoMouseCtrl.cc \ + game/guiPlayerView.cc \ + game/hoverVehicle.cc \ + game/httpObject.cc \ + game/item.cc \ + game/lightning.cc \ + game/missionArea.cc \ + game/missionMarker.cc \ + game/net.cc \ + game/netDispatch.cc \ + game/netTest.cc \ + game/particleEmitter.cc \ + game/particleEngine.cc \ + game/physicalZone.cc \ + game/player.cc \ + game/precipitation.cc \ + game/projectile.cc \ + game/rigid.cc \ + game/scopeAlwaysShape.cc \ + game/serverQuery.cc \ + game/shadow.cc \ + game/shapeBase.cc \ + game/shapeCollision.cc \ + game/shapeImage.cc \ + game/showTSShape.cc \ + game/sphere.cc \ + game/splash.cc \ + game/staticShape.cc \ + game/tcpObject.cc \ + game/trigger.cc \ + game/tsStatic.cc \ + game/underLava.cc \ + game/vehicle.cc \ + game/vehicleBlocker.cc \ + game/wheeledVehicle.cc \ + game/version.cc + +SOURCE.GAME.FPS=\ + game/fps/shapeNameHud.cc + +SOURCE.PLATFORMGAY=\ + platformLinux/linuxAsmBlit.cc \ + platformLinux/linuxCPUInfo.cc \ + platformLinux/linuxConsole.cc \ + platformLinux/linuxFileio.cc \ + platformLinux/linuxFont.cc \ + platformLinux/linuxGL.cc \ + platformLinux/linuxInput.cc \ + platformLinux/linuxIO.cc \ + platformLinux/linuxMath.cc \ + platformLinux/linuxMemory.cc \ + platformLinux/linuxMutex.cc \ + platformLinux/linuxNet.cc \ + platformLinux/linuxOGLVideo.cc \ + platformLinux/linuxOpenAL.cc \ + platformLinux/linuxProcessControl.cc \ + platformLinux/linuxSemaphore.cc \ + platformLinux/linuxStrings.cc \ + platformLinux/linuxThread.cc \ + platformLinux/linuxTime.cc \ + platformLinux/linuxWindow.cc + +######################################### this is dead +# platformLinux/linuxAL.cc \ +# platformLinux/linuxALStub.cc \ +# platformLinux/linuxCodeMap.cc \ +# platformLinux/linuxRedBook.cc \ +######################################### + +SOURCE.PLATFORMLINUX=\ + platformX86UNIX/x86UNIXAsmBlit.cc \ + platformX86UNIX/x86UNIXCPUInfo.cc \ + platformX86UNIX/x86UNIXConsole.cc \ + platformX86UNIX/x86UNIXFileio.cc \ + platformX86UNIX/x86UNIXFont.cc \ + platformX86UNIX/x86UNIXGL.cc \ + platformX86UNIX/x86UNIXInput.cc \ + platformX86UNIX/x86UNIXIO.cc \ + platformX86UNIX/x86UNIXMath.cc \ + platformX86UNIX/x86UNIXMemory.cc \ + platformX86UNIX/x86UNIXMutex.cc \ + platformX86UNIX/x86UNIXNet.cc \ + platformX86UNIX/x86UNIXOGLVideo.cc \ + platformX86UNIX/x86UNIXOpenAL.cc \ + platformX86UNIX/x86UNIXProcessControl.cc \ + platformX86UNIX/x86UNIXSemaphore.cc \ + platformX86UNIX/x86UNIXStrings.cc \ + platformX86UNIX/x86UNIXThread.cc \ + platformX86UNIX/x86UNIXTime.cc \ + platformX86UNIX/x86UNIXWindow.cc + +SOURCE.PLATFORMOpenBSD=\ + platformX86UNIX/x86UNIXAsmBlit.cc \ + platformX86UNIX/x86UNIXCPUInfo.cc \ + platformX86UNIX/x86UNIXConsole.cc \ + platformX86UNIX/x86UNIXFileio.cc \ + platformX86UNIX/x86UNIXFont.cc \ + platformX86UNIX/x86UNIXGL.cc \ + platformX86UNIX/x86UNIXInput.cc \ + platformX86UNIX/x86UNIXIO.cc \ + platformX86UNIX/x86UNIXMath.cc \ + platformX86UNIX/x86UNIXMemory.cc \ + platformX86UNIX/x86UNIXMutex.cc \ + platformX86UNIX/x86UNIXNet.cc \ + platformX86UNIX/x86UNIXOGLVideo.cc \ + platformX86UNIX/x86UNIXOpenAL.cc \ + platformX86UNIX/x86UNIXProcessControl.cc \ + platformX86UNIX/x86UNIXSemaphore.cc \ + platformX86UNIX/x86UNIXStrings.cc \ + platformX86UNIX/x86UNIXThread.cc \ + platformX86UNIX/x86UNIXTime.cc \ + platformX86UNIX/x86UNIXWindow.cc + +SOURCE.SCENEGRAPH=\ + sceneGraph/detailManager.cc \ + sceneGraph/lightManager.cc \ + sceneGraph/sceneGraph.cc \ + sceneGraph/sceneLighting.cc \ + sceneGraph/sceneRoot.cc \ + sceneGraph/sceneState.cc \ + sceneGraph/sceneTraversal.cc \ + sceneGraph/sgUtil.cc \ + sceneGraph/shadowVolumeBSP.cc \ + sceneGraph/windingClipper.cc + +SOURCE.TERRAIN=\ + terrain/fluidQuadTree.cc \ + terrain/fluidRender.cc \ + terrain/fluidSupport.cc \ + terrain/sky.cc \ + terrain/sun.cc \ + terrain/blender.cc \ + terrain/bvQuadTree.cc \ + terrain/terrCollision.cc \ + terrain/terrData.cc \ + terrain/terrLighting.cc \ + terrain/terrRender.cc \ + terrain/terrRender2.cc \ + terrain/waterBlock.cc + +SOURCE.TS=\ + ts/tsAnimate.cc \ + ts/tsCollision.cc \ + ts/tsDecal.cc \ + ts/tsDump.cc \ + ts/tsIntegerSet.cc \ + ts/tsLastDetail.cc \ + ts/tsMaterialList.cc \ + ts/tsMesh.cc \ + ts/tsPartInstance.cc \ + ts/tsShape.cc \ + ts/tsShapeAlloc.cc \ + ts/tsShapeConstruct.cc \ + ts/tsShapeInstance.cc \ + ts/tsShapeOldRead.cc \ + ts/tsSortedMesh.cc \ + ts/tsThread.cc \ + ts/tsTransform.cc + + +SOURCE.ENGINE =\ + $(SOURCE.COLLISION) \ + $(SOURCE.CONSOLE) \ + $(SOURCE.CORE) \ + $(SOURCE.DGL) \ + $(SOURCE.INTERIOR) \ + $(SOURCE.MATH) \ + $(SOURCE.PLATFORM) \ + $(SOURCE.PLATFORM$(OS)) \ + $(SOURCE.SCENEGRAPH) \ + $(SOURCE.SIM) \ + $(SOURCE.TERRAIN) \ + $(SOURCE.TS) + + +SOURCE.TESTAPP =\ + $(SOURCE.AI) \ + $(SOURCE.AUDIO) \ + $(SOURCE.COLLISION) \ + $(SOURCE.CONSOLE) \ + $(SOURCE.CORE) \ + $(SOURCE.DGL) \ + $(SOURCE.EDITOR) \ + $(SOURCE.GUI) \ + $(SOURCE.GAME) \ + $(SOURCE.GAME.FPS) \ + $(SOURCE.HUD) \ + $(SOURCE.INTERIOR) \ + $(SOURCE.MATH) \ + $(SOURCE.PLATFORM) \ + $(SOURCE.PLATFORM$(OS)) \ + $(SOURCE.SCENEGRAPH) \ + $(SOURCE.SIM) \ + $(SOURCE.TERRAIN) \ + $(SOURCE.TS) + + +SOURCE.TESTAPP.OBJ:=$(addprefix $(DIR.OBJ)/, $(addsuffix $O, $(basename $(SOURCE.TESTAPP))) ) +SOURCE.ENGINE.OBJ:=$(addprefix $(DIR.OBJ)/, $(addsuffix $O, $(basename $(SOURCE.ENGINE))) ) +SOURCE.ALL += $(SOURCE.TESTAPP) +targetsclean += torqueClean + + +#---------------------------------------- +torqueDemo: $(DIR.OBJ)/torqueDemo$(EXT.EXE) + +$(DIR.OBJ)/torqueDemo$(EXT.EXE): CFLAGS += -I../lib/zlib -I../lib/lungif -I../lib/lpng -I../lib/ljpeg -I- -I../lib/directx8 -I../lib/openal/$(OS) \ + -DPNG_NO_READ_TIME -DPNG_NO_WRITE_TIME \ + +$(DIR.OBJ)/torqueDemo$(EXT.EXE): LIB.PATH +=../lib/$(DIR.OBJ) \ + +$(DIR.OBJ)/torqueDemo$(EXT.EXE): LINK.LIBS.GENERAL += \ + $(PRE.LIBRARY.LIB)ljpeg$(EXT.LIB) \ + $(PRE.LIBRARY.LIB)lpng$(EXT.LIB) \ + $(PRE.LIBRARY.LIB)lungif$(EXT.LIB) \ + $(PRE.LIBRARY.LIB)zlib$(EXT.LIB) + + +$(DIR.OBJ)/torqueDemo$(EXT.EXE): dirlist $(SOURCE.TESTAPP.OBJ) + $(DO.LINK.CONSOLE.EXE) + $(CP) $(DIR.OBJ)/torqueDemo$(BUILD_SUFFIX).* ../example + + +#---------------------------------------- +$(DIR.OBJ)/engine$(EXT.LIB): CFLAGS += -I../lib/zlib -I../lib/lungif -I../lib/lpng -I../lib/ljpeg -I- -I../lib/directx8 -I../lib/openal/$(OS) \ + -DPNG_NO_READ_tIME -DPNG_NO_WRITE_TIME -DMAX_UTIL \ + +$(DIR.OBJ)/engine$(EXT.LIB): dirlist $(SOURCE.ENGINE.OBJ) + $(DO.LINK.LIB) + + +#---------------------------------------- +torqueClean: +ifneq ($(wildcard torqueDemo_DEBUG.*),) + -$(RM) torqueDemo$(BUILD_SUFFIX)* +endif +ifneq ($(wildcard torqueDemo_RELEASE.*),) + -$(RM) torqueDemo_RELEASE* +endif + diff --git a/targets.v12.mk b/targets.v12.mk new file mode 100644 index 0000000..40b9d2b --- /dev/null +++ b/targets.v12.mk @@ -0,0 +1,540 @@ +V12.AI=\ + ai/aiConnection.cc \ + ai/aiConsole.cc \ + ai/aiDebug.cc \ + ai/aiNavJetting.cc \ + ai/aiNavStep.cc \ + ai/aiObjective.cc \ + ai/aiStep.cc \ + ai/aiTask.cc \ + ai/graph.cc \ + ai/graphBase.cc \ + ai/graphBridge.cc \ + ai/graphBuildLOS.cc \ + ai/graphConjoin.cc \ + ai/graphData.cc \ + ai/graphDebug.cc \ + ai/graphDijkstra.cc \ + ai/graphFind.cc \ + ai/graphFloorPlan.cc \ + ai/graphFloorRender.cc \ + ai/graphForceField.cc \ + ai/graphGenUtils.cc \ + ai/graphGroundPlan.cc \ + ai/graphIndoors.cc \ + ai/graphIsland.cc \ + ai/graphJetting.cc \ + ai/graphLOS.cc \ + ai/graphLocate.cc \ + ai/graphMake.cc \ + ai/graphMath.cc \ + ai/graphOutdoors.cc \ + ai/graphPartition.cc \ + ai/graphPath.cc \ + ai/graphQueries.cc \ + ai/graphRender.cc \ + ai/graphSearchLOS.cc \ + ai/graphSmooth.cc \ + ai/graphSpawn.cc \ + ai/graphThreats.cc \ + ai/graphTransient.cc \ + ai/graphVolume.cc + +V12.AUDIO=\ + audio/audio.cc \ + audio/audioBuffer.cc \ + audio/audioDataBlock.cc \ + audio/audioMss.cc \ + audio/audioNet.cc \ + audio/audioThread.cc \ + audio/audioCodec.cc \ + audio/audioCodecMiles.cc \ + audio/bufferQueue.cc \ + +# audio/audioCodecGSM.cc \ + + +V12.COLLISION=\ + collision/abstractPolyList.cc \ + collision/boxConvex.cc \ + collision/clippedPolyList.cc \ + collision/convex.cc \ + collision/depthSortList.cc \ + collision/earlyOutPolyList.cc \ + collision/extrudedPolyList.cc \ + collision/gjk.cc \ + collision/planeExtractor.cc \ + collision/polyhedron.cc \ + collision/polytope.cc + +V12.CONSOLE=\ + console/compiledEval.cc \ + console/compiler.cc \ + console/console.cc \ + console/consoleFunctions.cc \ + console/consoleInternal.cc \ + console/consoleObject.cc \ + console/consoleTypes.cc \ + console/gram.cc \ + console/scan.cc \ + console/scriptObject.cc \ + console/simBase.cc \ + console/simDictionary.cc \ + console/simManager.cc \ + console/telnetConsole.cc \ + console/telnetDebugger.cc + +# console/yylex.c \ +# console/yyparse.c + +V12.CORE=\ + core/BitTables.cc \ + core/bitRender.cc \ + core/bitStream.cc \ + core/dataChunker.cc \ + core/dnet.cc \ + core/fileObject.cc \ + core/fileStream.cc \ + core/filterStream.cc \ + core/findMatch.cc \ + core/idGenerator.cc \ + core/memStream.cc \ + core/nStream.cc \ + core/nTypes.cc \ + core/resDictionary.cc \ + core/resManager.cc \ + core/resizeStream.cc \ + core/stringTable.cc \ + core/tVector.cc \ + core/tagDictionary.cc \ + core/zipAggregate.cc \ + core/zipHeaders.cc \ + core/zipSubStream.cc + +V12.CRYPT=\ + crypt/cryptMGF.cc \ + crypt/cryptRandPool.cc \ + crypt/cryptSHA1.cc + +V12.DGL=\ + dgl/bitmapBM8.cc \ + dgl/bitmapBMP.cc \ + dgl/bitmapGIF.cc \ + dgl/bitmapJpeg.cc \ + dgl/bitmapPng.cc \ + dgl/dgl.cc \ + dgl/dglMatrix.cc \ + dgl/gBitmap.cc \ + dgl/gFont.cc \ + dgl/gPalette.cc \ + dgl/gTexManager.cc \ + dgl/lensFlare.cc \ + dgl/materialList.cc \ + dgl/materialPropertyMap.cc \ + dgl/rectClipper.cc \ + dgl/splineUtil.cc \ + dgl/stripCache.cc + +V12.EDITOR=\ + editor/compTest.cc \ + editor/creator.cc \ + editor/editTSCtrl.cc \ + editor/editor.cc \ + editor/editorButtonCtrl.cc \ + editor/editorCheckboxCtrl.cc \ + editor/guiTerrPreviewCtrl.cc \ + editor/missionAreaEditor.cc \ + editor/terraformer.cc \ + editor/terraformerTexture.cc \ + editor/terraformer_noise.cc \ + editor/terrainActions.cc \ + editor/terrainEditor.cc \ + editor/worldEditor.cc + +V12.GUI=\ + gui/channelVector.cc \ + gui/guiArrayCtrl.cc \ + gui/guiAviBitmapCtrl.cc \ + gui/guiBackgroundCtrl.cc \ + gui/guiBitmapCtrl.cc \ + gui/guiBubbleTextCtrl.cc \ + gui/guiButtonCtrl.cc \ + gui/guiCanvas.cc \ + gui/guiChannelVectorCtrl.cc \ + gui/guiChatMenuTreeCtrl.cc \ + gui/guiCheckBoxCtrl.cc \ + gui/guiChunkedBitmapCtrl.cc \ + gui/guiConsole.cc \ + gui/guiConsoleEditCtrl.cc \ + gui/guiConsoleTextCtrl.cc \ + gui/guiControl.cc \ + gui/guiControlListPopup.cc \ + gui/guiDebugger.cc \ + gui/guiEditCtrl.cc \ + gui/guiFilterCtrl.cc \ + gui/guiFrameCtrl.cc \ + gui/guiInputCtrl.cc \ + gui/guiInspector.cc \ + gui/guiMLTextCtrl.cc \ + gui/guiMLTextEditCtrl.cc \ + gui/guiMessageVectorCtrl.cc \ + gui/guiMouseEventCtrl.cc \ + gui/guiPopUpCtrl.cc \ + gui/guiProgressCtrl.cc \ + gui/guiRadioCtrl.cc \ + gui/guiScrollCtrl.cc \ + gui/guiSliderCtrl.cc \ + gui/guiTSControl.cc \ + gui/guiTextCtrl.cc \ + gui/guiTextEditCtrl.cc \ + gui/guiTextEditSliderCtrl.cc \ + gui/guiTextListCtrl.cc \ + gui/guiTreeViewCtrl.cc \ + gui/guiTypes.cc \ + gui/guiVoteCtrl.cc \ + gui/guiWindowCtrl.cc \ + gui/messageVector.cc \ + +# gui/guiHelpCtrl.cc \ + +V12.HUD=\ + hud/hudCtrl.cc \ + hud/hudBitmapCtrl.cc \ + hud/hudBitmapFrameCtrl.cc \ + hud/hudClock.cc \ + hud/hudCompass.cc \ + hud/hudCrosshair.cc \ + hud/hudEnergyDamage.cc \ + +V12.INTERIOR=\ + interior/FloorPlanRes.cc \ + interior/forceField.cc \ + interior/itfdump.asm \ + interior/interior.cc \ + interior/interiorCollision.cc \ + interior/interiorDebug.cc \ + interior/interiorIO.cc \ + interior/interiorInstance.cc \ + interior/interiorLMManager.cc \ + interior/interiorLightAnim.cc \ + interior/interiorRender.cc \ + interior/interiorRes.cc \ + interior/interiorResObjects.cc \ + interior/interiorSubObject.cc \ + interior/lightUpdateGrouper.cc \ + interior/mirrorSubObject.cc + +V12.MATH=\ + math/mBox.cc \ + math/mConsoleFunctions.cc \ + math/mMathFn.cc \ + math/mMath_C.cc \ + math/mMatrix.cc \ + math/mPlaneTransformer.cc \ + math/mQuadPatch.cc \ + math/mQuat.cc \ + math/mRandom.cc \ + math/mSolver.cc \ + math/mSplinePatch.cc \ + math/mathTypes.cc \ + math/mathUtils.cc \ + math/mMathAMD.cc \ + math/mMathSSE.cc + +V12.PLATFORM=\ + platform/gameInterface.cc \ + platform/platformAssert.cc \ + platform/platformMemory.cc \ + platform/platformRedBook.cc \ + platform/platformVideo.cc \ + platform/profiler.cc + +V12.PLATFORMPPC=\ + platformPPC/ppcAudio.cc \ + platformPPC/ppcCPUInfo.cc \ + platformPPC/ppcConsole.cc \ + platformPPC/ppcFileio.cc \ + platformPPC/ppcFont.cc \ + platformPPC/ppcGL.cc \ + platformPPC/ppcInput.cc \ + platformPPC/ppcMath.cc \ + platformPPC/ppcMemory.cc \ + platformPPC/ppcNet.cc \ + platformPPC/ppcOGLVideo.cc \ + platformPPC/ppcProcessControl.cc \ + platformPPC/ppcStrings.cc \ + platformPPC/ppcTime.cc \ + platformPPC/ppcUtils.cc \ + platformPPC/ppcWindow.cc + +V12.PLATFORMWIN32=\ + platformWin32/winAsmBlit.cc \ + platformWin32/winCPUInfo.cc \ + platformWin32/winConsole.cc \ + platformWin32/winD3DVideo.cc \ + platformWin32/winDInputDevice.cc \ + platformWin32/winDirectInput.cc \ + platformWin32/winFileio.cc \ + platformWin32/winFont.cc \ + platformWin32/winGL.cc \ + platformWin32/winInput.cc \ + platformWin32/winMath.cc \ + platformWin32/winMath_ASM.cc \ + platformWin32/winMemory.cc \ + platformWin32/winMutex.cc \ + platformWin32/winNet.cc \ + platformWin32/winOGLVideo.cc \ + platformWin32/winOpenAL.cc \ + platformWin32/winProcessControl.cc \ + platformWin32/winRedbook.cc \ + platformWin32/winSemaphore.cc \ + platformWin32/winStrings.cc \ + platformWin32/winThread.cc \ + platformWin32/winTime.cc \ + platformWin32/winV2Video.cc \ + platformWin32/winWindow.cc \ + +# platformWin32/D3DGL.cc \ +# platformWin32/GLU2D3D.cc \ +# platformWin32/OpenGL2D3D.cc \ + + +V12.SHELL=\ + shell/shellFancyArray.cc \ + shell/shellFancyTextList.cc \ + shell/shellScrollCtrl.cc \ + shell/shellTextEditCtrl.cc \ + +V12.SIM=\ + sim/actionMap.cc \ + sim/cannedChatDataBlock.cc \ + sim/decalManager.cc \ + sim/frameAllocator.cc \ + sim/netConnection.cc \ + sim/netEvent.cc \ + sim/netGhost.cc \ + sim/netObject.cc \ + sim/netStringTable.cc \ + sim/pathManager.cc \ + sim/sceneObject.cc \ + sim/simPath.cc + +V12.GAME=\ + game/debris.cc \ + game/debugView.cc \ + game/gameFunctions.cc \ + game/stationFXPersonal.cc \ + game/stationFXVehicle.cc \ + game/ambientAudioManager.cc \ + game/audioEmitter.cc \ + game/badWordFilter.cc \ + game/banList.cc \ + game/bombSight.cc \ + game/camera.cc \ + game/cameraFXMgr.cc \ + game/collisionTest.cc \ + game/commanderMapIcon.cc \ + game/explosion.cc \ + game/fireballAtmosphere.cc \ + game/flyingVehicle.cc \ + game/forceFieldBare.cc \ + game/game.cc \ + game/gameBase.cc \ + game/gameConnection.cc \ + game/gameConnectionEvents.cc \ + game/gameConnectionMoves.cc \ + game/gameProcess.cc \ + game/gameTSCtrl.cc \ + game/guiNoMouseCtrl.cc \ + game/guiPlayerView.cc \ + game/guiServerBrowser.cc \ + game/hoverVehicle.cc \ + game/httpObject.cc \ + game/item.cc \ + game/lightning.cc \ + game/linearProjectile.cc \ + game/missionArea.cc \ + game/missionMarker.cc \ + game/motionBlurLine.cc \ + game/net.cc \ + game/netDispatch.cc \ + game/netTest.cc \ + game/particleEmitter.cc \ + game/particleEngine.cc \ + game/physicalZone.cc \ + game/platTest.cc \ + game/player.cc \ + game/precipitation.cc \ + game/projBomb.cc \ + game/projELF.cc \ + game/projEnergy.cc \ + game/projFlareGrenade.cc \ + game/projGrenade.cc \ + game/projLinearFlare.cc \ + game/projRepair.cc \ + game/projSeeker.cc \ + game/projShockLance.cc \ + game/projSniper.cc \ + game/projTargeting.cc \ + game/projTracer.cc \ + game/projectile.cc \ + game/rigid.cc \ + game/scopeAlwaysShape.cc \ + game/sensor.cc \ + game/serverQuery.cc \ + game/shadow.cc \ + game/shapeBase.cc \ + game/shapeCollision.cc \ + game/shapeImage.cc \ + game/shieldImpact.cc \ + game/shockwave.cc \ + game/showTSShape.cc \ + game/sphere.cc \ + game/splash.cc \ + game/staticShape.cc \ + game/targetManager.cc \ + game/tcpObject.cc \ + game/trigger.cc \ + game/tsStatic.cc \ + game/turret.cc \ + game/underLava.cc \ + game/vehicle.cc \ + game/vehicleBlocker.cc \ + game/weaponBeam.cc \ + game/wheeledVehicle.cc \ + game/version.cc + +V12.PLATFORMLINUX=\ + platformLinux/audio.cc \ + platformLinux/linuxAL.cc \ + platformLinux/linuxALStub.cc \ + platformLinux/linuxAsmBlit.cc \ + platformLinux/linuxCPUInfo.cc \ + platformLinux/linuxCodeMap.cc \ + platformLinux/linuxConsole.cc \ + platformLinux/linuxFileio.cc \ + platformLinux/linuxFont.cc \ + platformLinux/linuxGL.cc \ + platformLinux/linuxInput.cc \ + platformLinux/linuxMath.cc \ + platformLinux/linuxMemory.cc \ + platformLinux/linuxMutex.cc \ + platformLinux/linuxNet.cc \ + platformLinux/linuxOGLVideo.cc \ + platformLinux/linuxProcessControl.cc \ + platformLinux/linuxRedBook.cc \ + platformLinux/linuxSemaphore.cc \ + platformLinux/linuxStrings.cc \ + platformLinux/linuxThread.cc \ + platformLinux/linuxTime.cc \ + platformLinux/linuxWindow.cc \ + platformLinux/linuxOpenAL.cc \ + platformLinux/lokiOpenAL.cc + +V12.SCENEGRAPH=\ + sceneGraph/detailManager.cc \ + sceneGraph/lightManager.cc \ + sceneGraph/sceneGraph.cc \ + sceneGraph/sceneLighting.cc \ + sceneGraph/sceneRoot.cc \ + sceneGraph/sceneState.cc \ + sceneGraph/sceneTraversal.cc \ + sceneGraph/sgUtil.cc \ + sceneGraph/shadowVolumeBSP.cc \ + sceneGraph/windingClipper.cc + +V12.TERRAIN=\ + terrain/FluidQuadTree.cc \ + terrain/FluidRender.cc \ + terrain/FluidSupport.cc \ + terrain/Sky.cc \ + terrain/Sun.cc \ + terrain/blender.cc \ + terrain/bvQuadTree.cc \ + terrain/terrCollision.cc \ + terrain/terrData.cc \ + terrain/terrLighting.cc \ + terrain/terrRender.cc \ + terrain/terrRender2.cc \ + terrain/waterBlock.cc + +V12.TS=\ + ts/tsAnimate.cc \ + ts/tsCollision.cc \ + ts/tsDecal.cc \ + ts/tsDump.cc \ + ts/tsIntegerSet.cc \ + ts/tsLastDetail.cc \ + ts/tsMaterialList.cc \ + ts/tsMesh.cc \ + ts/tsPartInstance.cc \ + ts/tsShape.cc \ + ts/tsShapeAlloc.cc \ + ts/tsShapeConstruct.cc \ + ts/tsShapeInstance.cc \ + ts/tsShapeOldRead.cc \ + ts/tsSortedMesh.cc \ + ts/tsThread.cc \ + ts/tsTransform.cc + +V12.ALL=\ + $(V12.AI) \ + $(V12.AUDIO) \ + $(V12.COLLISION) \ + $(V12.CONSOLE) \ + $(V12.CORE) \ + $(V12.CRYPT) \ + $(V12.DGL) \ + $(V12.EDITOR) \ + $(V12.GUI) \ + $(V12.HUD) \ + $(V12.INTERIOR) \ + $(V12.MATH) \ + $(V12.PLATFORM) \ + $(V12.PLATFORMWIN32) \ + $(V12.SHELL) \ + $(V12.SIM) \ + $(V12.SCENEGRAPH) \ + $(V12.TS) \ + $(V12.TERRAIN) \ + $(V12.GAME) \ + + + + +OBJ.ALL:=$(addprefix $(DIR.OBJ)/, $(addsuffix $O, $(basename $(V12.ALL))) ) +SOURCES += $(V12.ALL) +targetsclean += v12clean + +OPENGL2D3D=glFOO +GLU2D3D=gluFOO + +$(DIR.OBJ)/v12_$(BUILD)$(EXT.EXE): CFLAGS += -I../lib/directx -I../lib/zlib -I../lib/lungif -I../lib/lpng -I../lib/ljpeg -I../lib/mss -I- -I../lib/openal/win32 \ + -DWIN32 -DUSEASSEMBLYTERRBLEND -DPNG_NO_READ_tIME -DPNG_NO_WRITE_TIME \ + -DOPENGL2D3D=\"$(OPENGL2D3D).dll\" -DGLU2D3D=\"$(GLU2D3D).dll\" \ + -DNO_MILES_OPENAL + +$(DIR.OBJ)/v12_$(BUILD)$(EXT.EXE): LIB.PATH += \ + ../lib/$(DIR.OBJ) \ + ../lib/mss \ + +$(DIR.OBJ)/v12_$(BUILD)$(EXT.EXE): LINK.LIBS.GENERAL += \ + ljpeg$(EXT.LIB) \ + lpng$(EXT.LIB) \ + lungif$(EXT.LIB) \ + zlib$(EXT.LIB) \ + Mss32$(EXT.LIB) \ + vfw32$(EXT.LIB) + +$(DIR.OBJ)/v12_$(BUILD)$(EXT.EXE): dirlist $(OBJ.ALL) + $(DO.LINK.CONSOLE.EXE) + cp $(DIR.OBJ)/v12_$(BUILD)* ../example + +v12clean: +ifneq ($(wildcard v12_DEBUG.*),) + -$(RM) v12_DEBUG* +endif +ifneq ($(wildcard v12_RELEASE.*),) + -$(RM) v12_RELEASE* +endif + diff --git a/terrain/blender.cc b/terrain/blender.cc new file mode 100644 index 0000000..3222e16 --- /dev/null +++ b/terrain/blender.cc @@ -0,0 +1,1714 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/blender.h" + + +#define USE_ALPHATAB (!BLENDER_USE_ASM) + +#define SRC_BMP_SHIFT 8 +#define DST_BMP_SHIFT 7 +#define ALPHA_SHIFT 8 +#define ALPHA_WID (1 << ALPHA_SHIFT) +#define SRC_WID (1 << SRC_BMP_SHIFT) +#define DST_WID (1 << DST_BMP_SHIFT) +#define BMP_SIZE (SRC_WID * SRC_WID) +#define DST_SIZE (DST_WID * DST_WID) +#define LIGHTMAPSHIFT 9 +#define BLOCKSHIFT SRC_BMP_SHIFT +#define BLOCKMASK ((1 << BLOCKSHIFT)-1) + + +#if BLENDER_USE_ASM + +#ifdef __MWERKS__ +#define _asm asm +#endif + +struct QWORD +{ + unsigned short a, b, c, d; +}; + +static QWORD delta_a, delta_b, delta_c, delta_d; +static QWORD alpha_a0, alpha_b0, alpha_c0, alpha_d0; +static QWORD alpha_a1, alpha_b1, alpha_c1, alpha_d1; +static QWORD alpha_a2, alpha_b2, alpha_c2, alpha_d2; +static QWORD alpha_a3, alpha_b3, alpha_c3, alpha_d3; +static QWORD ldelt_a, ldelt_b, ldelt_c, ldelt_d; +static QWORD rdelt_a, rdelt_b, rdelt_c, rdelt_d; +static QWORD zero = { 0, 0, 0, 0 }; +static QWORD redLightMask = { 0xf800, 0, 0, 0 }; +static QWORD greenLightMask = { 0x07c0, 0, 0, 0 }; +static QWORD blueLightMask = { 0x003e, 0, 0, 0 }; + +static QWORD delta2, delta3; +static QWORD rdelt_x2, ldelt_x2, leftq, rightq; +static QWORD mask_3e = { 0x3e, 0, 0, 0 }; +static QWORD mask_7c0 = { 0x07c0, 0, 0, 0 }; +static QWORD mask_f8 = { 0x00f8, 0, 0, 0 }; +static QWORD mask_f800000000 = { 0, 0, 0x00f8, 0 }; +static QWORD rdeltq, ldeltq; +static QWORD mulfact = { 0x2000, 0x0008, 0x2000, 0x0008 }; +static QWORD redblue = { 0x00f8, 0x00f8, 0x00f8, 0x00f8 }; +static QWORD green = { 0xf800, 0, 0xf800, 0 }; +static QWORD alpha = { 0x0001, 0x0001, 0, 0 }; +static QWORD mask_0000ffff0000ffff = { 0xffff, 0, 0xffff, 0 }; +static QWORD mask_00007fff00007fff = { 0x7fff, 0, 0x7fff, 0 }; +// pass a few bits of data through mem for now. +// most of them are constant for the entire blend() call. +#endif + + +static U32 lpoints[4]; +static U32 texelsPerLumelShift; +static U32 texelsPerLumel; +static U32 texelsPerLumelDiv2; +static U32 nextsrcrow; +static U32 nextdstrow; +static U32 mip0_dstrowadd; +static U32 mip1_dstrowadd; +static U32 minus1srcrowsPlus8; +static U32 srcrows_x2_MinusTPL; + + +#if BLENDER_USE_ASM + + +static void doSquare4( U32 *dst, int sq_shift, int *aoff, U32 **bmp_ptrs, + U8 **alpha_ptrs ) +{ + int iy = 1 << sq_shift; + int ix = iy >> 1; + +_asm +{ + movd mm1, sq_shift + // get alpha values for the corners of the square for each texture type. + // replicate the values into 4 words of the qwords. Also calc vertical + // stepping values for the alpha values on left and right edges. + // load alpha value into bh to mul by 256 for precision. then + // punpcklwd mm0, mm0 followed by punpckldq mm0, mm0 + // to replicate the low word into all words of mm0. + // shift down difference by sqshift to divide by pixels per square to get + // increment. + + mov esi, aoff + mov edi, alpha_ptrs + mov eax, [edi] + mov edx, eax + add eax, [esi] + xor ebx,ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq alpha_a0, mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq alpha_a1, mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq ldelt_a, mm0 + psraw mm3, mm1 + movq rdelt_a, mm3 + + mov eax, [edi+4] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq alpha_b0, mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq alpha_b1, mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq ldelt_b, mm0 + psraw mm3, mm1 + movq rdelt_b, mm3 + + mov eax, [edi+8] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq alpha_c0, mm2 + movq alpha_c2, mm0 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq alpha_c1, mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq ldelt_c, mm0 + psraw mm3, mm1 + movq rdelt_c, mm3 + + mov eax, [edi+12] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq alpha_d0, mm2 + movq alpha_d2, mm0 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq alpha_d1, mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq ldelt_d, mm0 + psraw mm3, mm1 + movq rdelt_d, mm3 + + mov esi, bmp_ptrs + mov eax, [esi] + mov ebx, [esi+4] + mov ecx, [esi+8] + mov edx, [esi+12] + + movq mm0, alpha_a1 + movq mm2, alpha_b1 + movq mm3, alpha_c1 + movq mm4, alpha_a0 + movq mm5, alpha_b0 + movq mm6, alpha_c0 + movq mm7, alpha_d0 + mov edi, dst + +yloop: + // mm1 should be sq_shift at this point + + // calculate alpha step increments...word-size steps are replicated + // to fill qword. + psubw mm0, mm4 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq delta_a, mm0 ;delta = ainc ainc ainc ainc + + psubw mm2, mm5 + psraw mm2, mm1 ;mm0 = (right-left) >> sq_shift + movq delta_b, mm2 ;delta = ainc ainc ainc ainc + + psubw mm3, mm6 + psraw mm3, mm1 ;mm0 = (right-left) >> sq_shift + movq delta_c, mm3 ;delta = ainc ainc ainc ainc + + movq mm0, alpha_d1 + psubw mm0, mm7 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq delta_d, mm0 ;delta = ainc ainc ainc ainc + + mov esi, ix + pxor mm2, mm2 + +xloop: + movq mm0, [eax] + movq mm1, mm0 + punpcklbw mm0, mm2 + pmulhw mm0, mm4 + paddw mm4, delta_a + punpckhbw mm1, mm2 + pmulhw mm1, mm4 + paddw mm4, delta_a + packuswb mm0, mm1 + + movq mm3, [ebx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm5 + paddw mm5, delta_b + punpckhbw mm1, mm2 + pmulhw mm1, mm5 + paddw mm5, delta_b + packuswb mm3, mm1 + paddb mm0, mm3 + + movq mm3, [ecx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm6 + paddw mm6, delta_c + punpckhbw mm1, mm2 + pmulhw mm1, mm6 + paddw mm6, delta_c + packuswb mm3, mm1 + paddb mm0, mm3 + + movq mm3, [edx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm7 + paddw mm7, delta_d + punpckhbw mm1, mm2 + pmulhw mm1, mm7 + paddw mm7, delta_d + packuswb mm3, mm1 + paddb mm0, mm3 + + // double result, to make up for alpha vals being signed (max = 127) + // so our math turns out a bit short, example: + // (0x7f00 * 0xff) >> 16 = 0x7e....* 2 = 252...not quite 255 + // would have been (0xff00 * 0xff) >> 16 = 0xfe = 254, + // if I could do an unsigned pmulhw... + // pmulhuw is in an intel document I found, but doesn't compile.... + paddb mm0, mm0 + + movq [edi], mm0 + + add eax, 8 + add ebx, 8 + add ecx, 8 + add edx, 8 + add edi, 8 + + dec esi + jnz xloop + + movq mm4, alpha_a0 + paddw mm4, ldelt_a + movq alpha_a0, mm4 + + movq mm5, alpha_b0 + paddw mm5, ldelt_b + movq alpha_b0, mm5 + + movq mm6, alpha_c0 + paddw mm6, ldelt_c + movq alpha_c0, mm6 + + movq mm7, alpha_d0 + paddw mm7, ldelt_d + movq alpha_d0, mm7 + + movq mm0, alpha_d1 + paddw mm0, rdelt_d + movq alpha_d1, mm0 + + movq mm2, alpha_b1 + paddw mm2, rdelt_b + movq alpha_b1, mm2 + + movq mm3, alpha_c1 + paddw mm3, rdelt_c + movq alpha_c1, mm3 + + movq mm0, alpha_a1 + paddw mm0, rdelt_a + movq alpha_a1, mm0 + + movd mm1, sq_shift // top of loop expects this + + dec iy + jnz yloop + + emms +} +} + + + + + +static void doSquare3( U32 *dst, int sq_shift, int *aoff, U32 **bmp_ptrs, + U8 **alpha_ptrs ) +{ + int iy = 1 << sq_shift; + int ix = iy >> 1; + +_asm +{ + movd mm1, sq_shift + // get alpha values for the corners of the square for each texture type. + // replicate the values into 4 words of the qwords. Also calc vertical + // stepping values for the alpha values on left and right edges. + // load alpha value into bh to mul by 256 for precision. then + // punpcklwd mm0, mm0 followed by punpckldq mm0, mm0 + // to replicate the low word into all words of mm0. + // shift down difference by sqshift to divide by pixels per square to get + // increment. + + mov esi, aoff + mov edi, alpha_ptrs + mov eax, [edi] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq alpha_a0, mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq alpha_a1, mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq ldelt_a, mm0 + psraw mm3, mm1 + movq rdelt_a, mm3 + + mov eax, [edi+4] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq alpha_b0, mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq alpha_b1, mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq ldelt_b, mm0 + psraw mm3, mm1 + movq rdelt_b, mm3 + + mov eax, [edi+8] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq alpha_c0, mm2 + movq alpha_c2, mm0 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq alpha_c1, mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq ldelt_c, mm0 + psraw mm3, mm1 + movq rdelt_c, mm3 + + mov esi, bmp_ptrs + mov eax, [esi] + mov ebx, [esi+4] + mov ecx, [esi+8] + + movq mm0, alpha_a1 + movq mm2, alpha_b1 + movq mm3, alpha_c1 + movq mm4, alpha_a0 + movq mm5, alpha_b0 + movq mm6, alpha_c0 + mov edi, dst + +yloop: + // mm1 should be sq_shift at this point + // mm0 should be alpha_a1 + // mm2 should be alpha_b1 + // mm3 should be alpha_c1 + + // calculate alpha step increments...word-size steps are replicated + // to fill qword. + psubw mm0, mm4 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq delta_a, mm0 ;delta = ainc ainc ainc ainc + + psubw mm2, mm5 + psraw mm2, mm1 ;mm0 = (right-left) >> sq_shift + movq delta_b, mm2 ;delta = ainc ainc ainc ainc + + psubw mm3, mm6 + psraw mm3, mm1 ;mm0 = (right-left) >> sq_shift + movq delta_c, mm3 ;delta = ainc ainc ainc ainc + + mov esi, ix + pxor mm2, mm2 + + + movq mm7, delta_a +xloop: + movq mm0, [eax] + movq mm1, mm0 + punpcklbw mm0, mm2 + pmulhw mm0, mm4 + paddw mm4, mm7 + punpckhbw mm1, mm2 + pmulhw mm1, mm4 + paddw mm4, mm7 + packuswb mm0, mm1 + + movq mm3, [ebx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm5 + paddw mm5, delta_b + punpckhbw mm1, mm2 + pmulhw mm1, mm5 + paddw mm5, delta_b + packuswb mm3, mm1 + paddb mm0, mm3 + + movq mm3, [ecx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm6 + paddw mm6, delta_c + punpckhbw mm1, mm2 + pmulhw mm1, mm6 + paddw mm6, delta_c + packuswb mm3, mm1 + paddb mm0, mm3 + paddb mm0, mm0 + + movq [edi], mm0 + + add eax, 8 + add ebx, 8 + add ecx, 8 + add edi, 8 + + dec esi + jnz xloop + + movq mm4, alpha_a0 + paddw mm4, ldelt_a + movq alpha_a0, mm4 + + movq mm5, alpha_b0 + paddw mm5, ldelt_b + movq alpha_b0, mm5 + + movq mm6, alpha_c0 + paddw mm6, ldelt_c + movq alpha_c0, mm6 + + movq mm2, alpha_b1 + paddw mm2, rdelt_b + movq alpha_b1, mm2 + + movq mm3, alpha_c1 + paddw mm3, rdelt_c + movq alpha_c1, mm3 + + movq mm0, alpha_a1 + paddw mm0, rdelt_a + movq alpha_a1, mm0 + + movd mm1, sq_shift // top of loop expects this + + dec iy + jnz yloop + + emms +} +} + +static void doSquare2( U32 *dst, int sq_shift, int *aoff, U32 **bmp_ptrs, + U8 **alpha_ptrs ) +{ + int iy = 1 << sq_shift; + int ix = iy >> 1; + +_asm +{ + movd mm1, sq_shift + // get alpha values for the corners of the square for each texture type. + // replicate the values into 4 words of the qwords. Also calc vertical + // stepping values for the alpha values on left and right edges. + // punpcklwd mm0, mm0 followed by punpckldq mm0, mm0 + // to replicate the low word into all words of mm0. + // shift down difference by sqshift to divide by pixels per square to get + // increment. + + mov esi, aoff + mov edi, alpha_ptrs + mov eax, [edi] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq alpha_a0, mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq alpha_a1, mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq ldelt_a, mm0 + psraw mm3, mm1 + movq rdelt_a, mm3 + + mov eax, [edi+4] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq alpha_b0, mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq alpha_b1, mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq ldelt_b, mm0 + psraw mm3, mm1 + movq rdelt_b, mm3 + + mov esi, bmp_ptrs + mov eax, [esi] + mov ebx, [esi+4] + + movq mm0, alpha_a1 + movq mm2, alpha_b1 + movq mm4, alpha_a0 + movq mm5, alpha_b0 + mov edi, dst + +yloop: + // mm1 should be sq_shift at this point + // mm0 should be alpha_a1 + // mm2 should be alpha_b1 + + // calculate alpha step increments...word-size steps are replicated + // to fill qword. + psubw mm0, mm4 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq delta_a, mm0 ;delta = ainc ainc ainc ainc + + psubw mm2, mm5 + psraw mm2, mm1 ;mm0 = (right-left) >> sq_shift + movq delta_b, mm2 ;delta = ainc ainc ainc ainc + + mov esi, ix + pxor mm2, mm2 + + movq mm6, delta_a + movq mm7, delta_b + +xloop: + movq mm0, [eax] + movq mm3, [ebx] + + movq mm1, mm0 + punpcklbw mm0, mm2 + pmulhw mm0, mm4 + paddw mm4, mm6 + punpckhbw mm1, mm2 + pmulhw mm1, mm4 + paddw mm4, mm6 + packuswb mm0, mm1 + + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm5 + paddw mm5, mm7 + punpckhbw mm1, mm2 + pmulhw mm1, mm5 + paddw mm5, mm7 + packuswb mm3, mm1 + paddb mm0, mm3 + paddb mm0, mm0 + + movq [edi], mm0 + + add edi, 8 + add eax, 8 + add ebx, 8 + + dec esi + jnz xloop + + movq mm4, alpha_a0 + paddw mm4, ldelt_a + movq alpha_a0, mm4 + + movq mm5, alpha_b0 + paddw mm5, ldelt_b + movq alpha_b0, mm5 + + movq mm2, alpha_b1 + paddw mm2, rdelt_b + movq alpha_b1, mm2 + + movq mm0, alpha_a1 + paddw mm0, rdelt_a + movq alpha_a1, mm0 + + movd mm1, sq_shift // top of loop expects this + + dec iy + jnz yloop + + emms +} +} + + + + +// This special version only works for 4x4 lumels or larger (details 2, 3, 4). +// Lights from source ptr, and Creates Mips 0, 1, and 2 in one pass. +// Mip 0 must be 128x128, all mips are 5551 format. +// Source texture is variable width (but power of 2). +static void doLumelPlus1Mip( U16 *dstmip0, U16 *dstmip1, U32 *srcptr ) +{ +_asm +{ + movd mm7, texelsPerLumelShift + + movd mm0, [lpoints] + movq mm4, mm0 + pand mm0, redLightMask + movq mm5, mm4 + pand mm4, greenLightMask + psllq mm0, 31 + pand mm5, blueLightMask + psllq mm4, 20 + paddw mm0, mm4 + psllq mm5, 9 + paddw mm0, mm5 // mm0 = 0000rrrrggggbbbb qword for lp[0] + movq leftq, mm0 + + movd mm1, [lpoints+8] // get lp2 + movq mm4, mm1 + pand mm1, redLightMask + movq mm5, mm4 + pand mm4, greenLightMask + psllq mm1, 31 + pand mm5, blueLightMask + psllq mm4, 20 + paddw mm1, mm4 + psllq mm5, 9 + paddw mm1, mm5 // mm1 = 0000rrrrggggbbbb qword for lp[2] + + psubw mm1, mm0 + psraw mm1, mm7 + movq ldeltq, mm1 + psllw mm1, 1 + movq ldelt_x2, mm1 + + + movd mm2, [lpoints+4] // get lp[1] + movq mm4, mm2 + pand mm2, redLightMask + movq mm5, mm4 + pand mm4, greenLightMask + psllq mm2, 31 + pand mm5, blueLightMask + psllq mm4, 20 + paddw mm2, mm4 + psllq mm5, 9 + paddw mm2, mm5 // mm2 = 0000rrrrggggbbbb qword for lp[1] + movq rightq, mm2 + + movd mm3, [lpoints+12] // get lp3 + movq mm4, mm3 + pand mm3, redLightMask + movq mm5, mm4 + pand mm4, greenLightMask + psllq mm3, 31 + pand mm5, blueLightMask + psllq mm4, 20 + paddw mm3, mm4 + psllq mm5, 9 + paddw mm3, mm5 // mm3 = 0000rrrrggggbbbb qword for lp[3] + + psubw mm3, mm2 + psraw mm3, mm7 + movq rdeltq, mm3 + psllw mm3, 1 + movq rdelt_x2, mm3 + + mov edi, dstmip0 + mov esi, srcptr + mov edx, dstmip1 + pxor mm6, mm6 + + mov ecx, texelsPerLumelDiv2 // yloop count + movq mm2, leftq + movq mm3, rightq + + // mm2 is left, mm3 is right +yloop: + movd mm7, texelsPerLumelShift + movq mm6, mm2 + movq mm1, mm2 // mm1 is light1 + movq mm5, mm3 + movq mm4, mm3 + + paddw mm5, rdeltq // right + rdelt + psubw mm4, mm6 // right - left + paddw mm6, ldeltq // left + ldelt + psraw mm4, mm7 + movq delta2, mm4 + + psubw mm5, mm6 // mm6 is light2 + psraw mm5, mm7 + movq delta3, mm5 + + mov ebx, texelsPerLumelDiv2 // loop count + + // do 4 source pixels per loop + // mm1 is light1 + // mm6 is light2 + pxor mm7, mm7 + +xloop: + // get first of source, col 0 and 1 + movq mm4, [esi] + add esi, nextsrcrow + movq mm5, mm4 + punpcklbw mm4, zero + pmulhw mm4, mm1 // mm1 is light factor for first row + paddw mm1, delta2 + punpckhbw mm5, zero + pmulhw mm5, mm1 + paddw mm1, delta2 + + movq mm7, [esi] + add esi, minus1srcrowsPlus8 + + movq mm0, mm4 + paddw mm0, mm5 // mm0 is the avg, for mip1[0,1] + + packuswb mm4, mm5 // put both pixels in same qword + paddw mm4, mm4 // double it, because lighting mul halved it + movq mm5, mm4 // save the original data + pand mm4, redblue // mask out all but the 5MSBits of red and blue + pmaddwd mm4, mulfact // multiply each word by + // 2^13, 2^3, 2^13, 2^3 and add results + pand mm5, green // mask out all but the 5MSBits of green + por mm4, mm5 // combine the red, green, and blue bits + psrld mm4, 6 // shift into position + packssdw mm4, zero // pack into single dword + pslld mm4, 1 // shift into final position + por mm4, alpha // add the alpha bit + + // write 2 pixels to mip0 + movd [edi], mm4 + + // get second row, cols 0 and 1 + movq mm5, mm7 + pxor mm4, mm4 + punpcklbw mm7, mm4 + pmulhw mm7, mm6 // mm6 is light factor for 2nd row + paddw mm6, delta3 + punpckhbw mm5, mm4 + pmulhw mm5, mm6 + paddw mm6, delta3 + paddw mm0, mm7 + paddw mm0, mm5 + psrlw mm0, 1 // mm0 is mip1[0,1] average + + packuswb mm7, mm5 // put both pixels in same qword + paddw mm7, mm7 // double it, because lighting mul halved it + movq mm5, mm7 // save the original data + pand mm7, redblue // mask out all but the 5MSBits of red and blue + pmaddwd mm7, mulfact // multiply each word by + // 2^13, 2^3, 2^13, 2^3 and add results + pand mm5, green // mask out all but the 5MSBits of green + por mm7, mm5 // combine the red, green, and blue bits + psrld mm7, 6 // shift into position + packssdw mm7, mm4 // pack into single dword + pslld mm7, 1 // shift into final position + por mm7, alpha // add the alpha bit + + // write 2 16-bit pixels to mip0, 2nd row + movd [edi+0x100], mm7 + + movq mm5, mm0 + movq mm4, mm0 + pand mm4, mask_f8 // red + psrlq mm5, 13 + pand mm5, mask_7c0 // green + psllq mm4, 8 + pand mm0, mask_f800000000 // blue + paddw mm5, mm4 + psrlq mm0, 34 + paddw mm0, mm5 + + // write 1 pixels to mip1 + movd eax, mm0 + mov [edx], ax + + // increment ptrs + add edx, 2 // mip1 + add edi, 4 // mip0 + + dec ebx + jnz xloop + + add esi, srcrows_x2_MinusTPL + add edx, mip1_dstrowadd + add edi, mip0_dstrowadd + paddw mm2, ldelt_x2 // mm2 is left + paddw mm3, rdelt_x2 // mm3 is right + dec ecx + jnz yloop + + emms +} +} + +static void do1x1Lumel( U16 *dstptr, U32 *srcptr ) +{ +_asm +{ + movd mm0, [lpoints] + movq mm4, mm0 + pand mm0, redLightMask + movq mm5, mm4 + pand mm4, greenLightMask + psllq mm0, 31 + pand mm5, blueLightMask + psllq mm4, 20 + paddw mm0, mm4 + psllq mm5, 9 + paddw mm0, mm5 // mm0 = 0000rrrrggggbbbb qword for lp[0] + + mov edi, dstptr + mov esi, srcptr + pxor mm6, mm6 + + movd mm4, [esi] + punpcklbw mm4, mm6 // mm6 is expected to be 0 here + pmulhw mm4, mm0 + paddw mm4, mm4 + + movq mm7, mm4 + movq mm6, mm4 + psrlq mm4, 34 + pand mm7, mask_f8 + psrlq mm6, 13 + psllq mm7, 8 + pand mm6, mask_7c0 + paddw mm4, mm7 + paddw mm4, mm6 + movd eax, mm4 + mov [edi],ax + emms +} +} + + +static void cheatmips( U16 *srcptr, U16 *dstmip0, U16 *dstmip1, int wid ) +{ +_asm +{ + mov esi, srcptr + mov edi, dstmip0 + mov edx, dstmip1 + + mov ecx, wid + shr ecx, 1 + mov eax, ecx + shr eax, 3 + shl wid, 1 + + movq mm6, mask_0000ffff0000ffff + movq mm7, mask_00007fff00007fff + +yloop: + mov ebx, eax +xloop: + movq mm0, [esi] + movq mm1, [esi+8] + movq mm2, [esi+16] + movq mm3, [esi+24] + + pand mm0, mm6 + psrlw mm1, 1 + pand mm1, mm7 + psrlw mm0, 1 + pand mm2, mm6 + psrlw mm3, 1 + pand mm3, mm7 + psrlw mm2, 1 + packssdw mm0, mm1 + packssdw mm2, mm3 + psllw mm0, 1 // mip1, qw 0 + movq [edi], mm0 + psllw mm2, 1 // mip1, qw 1 + movq [edi+8], mm2 + + test ecx, 1 + jnz nomip2 + + movq mm1, mm0 + movq mm3, mm2 + pand mm1, mm6 + psrlw mm3, 1 + pand mm3, mm7 + psrlw mm1, 1 + packssdw mm1, mm3 + psllw mm1, 1 // mip2, qw 0 + movq [edx], mm1 + add edx, 8 +nomip2: + + add esi, 32 + add edi, 16 + + dec ebx + jnz xloop + + add esi, wid + dec ecx + jnz yloop + + emms +} +} + + +static void cheatmips4x4( U16 *srcptr, U16 *dstmip0, U16 *dstmip1 ) +{ +_asm +{ + mov esi, srcptr + mov edi, dstmip0 + mov edx, dstmip1 + + movq mm0, [esi] + movq mm1, [esi+16] + pand mm0, mask_0000ffff0000ffff + psrlw mm1, 1 + pand mm1, mask_00007fff00007fff + psrlw mm0, 1 + packssdw mm0, mm1 + psllw mm0, 1 // mip1, qw 0 + movq [edi], mm0 + + movd eax, mm0 + mov [edx], ax + + emms +} +} +#endif + +#if USE_ALPHATAB +static U8 alphaTable[16384]; +#endif + +#if PAULS_TEST_CODE +static unsigned char grid_mats[256][256]; +#endif + + +// old C extruder +static void extrude5551( const U16 *srcMip, U16 *mip, U32 height, U32 width ) +{ + const U16 *src = srcMip; + U16 *dst = 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; + } +} + + +// Take first mip in array, and extrude rest into other entries of array +// i.e. power is 7 for 128x128, but there should be power+1 entries in the +// array. +static void extrude( U16 **mips, U32 power ) +{ + U32 width = 1 << (power - 1); + + for ( U32 i = 0; i < power; i++ ) + { + extrude5551( mips[i], mips[i+1], width, width ); + width >>= 1; + } +} + + + +// level is between 2 (high detail) and 5 (low detail) inclusive; +// x and y are in alpha sized squares (not tex squares) +// lmap is light map data, format is 5551 +// destmips is a list of 16-bit 5551 rgb buffers for the result. +void Blender::blend( int x, int y, int level, const U16 *lmap, U16 **destmips ) +{ + int sq_shift = 7 - level; + int squareShift = sq_shift; + int double_sq_shift = sq_shift << 1; + int squareCount = 1 << (DST_BMP_SHIFT - sq_shift); + int miplevel = level - 2; // 0 at high detail, 3 at low detail + U32 squareSize = 1 << sq_shift; + + U32 lumelsPerSquareShift = (LIGHTMAPSHIFT - BLOCKSHIFT); + U32 lumelsPerSquare = 1 << lumelsPerSquareShift; + texelsPerLumelShift = squareShift - lumelsPerSquareShift; + texelsPerLumel = 1 << texelsPerLumelShift; + texelsPerLumelDiv2 = texelsPerLumel >> 1; + U32 textureSizeShift = 7; + U32 lightMapSizeMask = (1 << LIGHTMAPSHIFT) - 1; + U32 tystep = (1 << textureSizeShift); + U32 stystep = (1 << squareShift); + U32 txstep = texelsPerLumel; + + nextsrcrow = ((stystep) << 2); + nextdstrow = ((tystep) << 1); + +#if BLENDER_USE_ASM + U32 *bmpptrs[4]; + U8 *alphaptrs[4]; + mip0_dstrowadd = (nextdstrow << 1) - (texelsPerLumel << 1); + mip1_dstrowadd = (nextdstrow >> 1) - (texelsPerLumel); + minus1srcrowsPlus8 = 8 - nextsrcrow; + srcrows_x2_MinusTPL = (nextsrcrow << 1) - (texelsPerLumel << 2); +#endif + + for ( int sy = 0; sy < squareCount; sy++ ) + { + int yp = (y + sy) & BLOCKMASK; + int yp1 = (yp + 1) & BLOCKMASK; + int py = yp & 7; + int yp_shifted = yp << ALPHA_SHIFT; + int yp1_shifted = yp1 << ALPHA_SHIFT; + int sy_shifted = sy << DST_BMP_SHIFT; + int py_shifted = py << (SRC_BMP_SHIFT - miplevel + sq_shift); + int mip_off = miplevel * num_src_bmps; + + for ( int sx = 0; sx < squareCount; sx++ ) + { + int aoffs[4]; + int xp = (x + sx) & BLOCKMASK; + int xp1 = (xp + 1) & BLOCKMASK; + int px = xp & 7; + aoffs[0] = (yp_shifted + xp); + aoffs[1] = (yp_shifted + xp1); + aoffs[2] = (yp1_shifted + xp); + aoffs[3] = (yp1_shifted + xp1); + U32 *dstptr = blendbuffer; + + int bmpoff = py_shifted + (px << double_sq_shift); +#if BLENDER_USE_ASM + int cnt = 0; + + U32 gridflags = GRIDFLAGS( xp, yp ); + + for ( int i = 0; i < num_src_bmps; i++ ) + if ( gridflags & (MATERIALSTART << i) ) + { + alphaptrs[ cnt ] = alpha_data[ i ]; + bmpptrs[ cnt++ ] = bmpdata[ mip_off + i ] + bmpoff; + + if ( cnt == 4 ) + break; + } + + switch( cnt ) + { + case 1: + // don't copy the square over...just leave it and tell + // lighting code to use src bmp as the source instead of + // the blend_buffer; + dstptr = bmpptrs[ 0 ]; + break; + case 2: + doSquare2( dstptr, sq_shift, aoffs, bmpptrs, alphaptrs ); + break; + case 3: + doSquare3( dstptr, sq_shift, aoffs, bmpptrs, alphaptrs ); + break; + case 4: + doSquare4( dstptr, sq_shift, aoffs, bmpptrs, alphaptrs ); + break; + } +#else + bool cleared = false; + + U32 gridflags = GRIDFLAGS( xp, yp ); + + for ( int i = 0; i < num_src_bmps; i++ ) + if ( gridflags & (MATERIALSTART << i) ) + { + U32 a0 = U32( alpha_data[ i ][ aoffs[0] ] ) << 8; + U32 a1 = U32( alpha_data[ i ][ aoffs[1] ] ) << 8; + U32 a2 = U32( alpha_data[ i ][ aoffs[2] ] ) << 8; + U32 a3 = U32( alpha_data[ i ][ aoffs[3] ] ) << 8; + + U32 ldelt = (a2 - a0) >> squareShift; + U32 rdelt = (a3 - a1) >> squareShift; + + U32 left = a0; + U32 right = a1; + U8 *sourcePtr = (U8 *)(bmpdata[ mip_off + i ] + bmpoff); + U8 *addr1 = (U8 *)dstptr; + + if (!cleared) + { + for(U32 iy = 0; iy < squareSize; iy++) + { + U32 delt = (right - left) >> squareShift; + U32 astart = left; + U8 *addr = addr1; + for(U32 ix = 0; ix < squareSize; ix++) + { + U32 index = (astart >> 2) & 0x3F00; + + addr[0] = alphaTable[index | sourcePtr[0]]; + addr[1] = alphaTable[index | sourcePtr[1]]; + addr[2] = alphaTable[index | sourcePtr[2]]; + addr += 4; + sourcePtr += 4; + astart += delt; + } + left += ldelt; + right += rdelt; + addr1 += 1 << (squareShift + 2); + } + cleared = true; + } + else + { + for(U32 iy = 0; iy < squareSize; iy++) + { + U32 delt = (right - left) >> squareShift; + U32 astart = left; + U8 *addr = addr1; + for(U32 ix = 0; ix < squareSize; ix++) + { + U32 index = (astart >> 2) & 0x3F00; + + addr[0] += alphaTable[index | sourcePtr[0]]; + addr[1] += alphaTable[index | sourcePtr[1]]; + addr[2] += alphaTable[index | sourcePtr[2]]; + addr += 4; + sourcePtr += 4; + astart += delt; + } + left += ldelt; + right += rdelt; + addr1 += 1 << (squareShift + 2); + } + } + } +#endif + + // copy in the lighting info + U32 lxstart = xp << lumelsPerSquareShift; + U32 lystart = yp << lumelsPerSquareShift; + + U32 txstart = sx << squareShift; + U32 tystart = sy << squareShift; + + U32 tstart = 0, ststart = 0; + U32 idy = (tystart << DST_BMP_SHIFT); + U16 *bits0 = &destmips[0][ idy + txstart ]; + U16 *bits1 = &destmips[1][ (idy >> 2) + (txstart >> 1) ]; + U16 *bits2 = &destmips[2][ (idy >> 4) + (txstart >> 2) ]; + + U32 ly = lystart; + for(U32 iy = 0; iy < lumelsPerSquare; iy++) + { + U32 lynext = (ly + 1) & lightMapSizeMask; + + U32 lx = lxstart; + U32 txwalk = 0, stxwalk = 0; + + for(U32 ix = 0; ix < lumelsPerSquare; ix++) + { + U32 lxnext = (lx + 1) & lightMapSizeMask; + U32 twalk = tstart + txwalk; + U32 stwalk = ststart + stxwalk; + + lpoints[0] = (U32)lmap[lx + (ly << LIGHTMAPSHIFT)]; + lpoints[1] = (U32)lmap[lxnext + (ly << LIGHTMAPSHIFT)]; + lpoints[2] = (U32)lmap[lx + (lynext << LIGHTMAPSHIFT)]; + lpoints[3] = (U32)lmap[lxnext + (lynext << LIGHTMAPSHIFT)]; + +#if BLENDER_USE_ASM + if ( texelsPerLumel > 1 ) + { + doLumelPlus1Mip( &bits0[ twalk ], + &bits1[ (tstart >> 2) + (txwalk >> 1) ], + &dstptr[ stwalk ] ); + } + else + do1x1Lumel( &bits0[ twalk ], &dstptr[ stwalk ] ); +#else + U32 col[3][4]; + + U32 i; + for(i = 0; i < 4; i++) + { + col[0][i] = (lpoints[i] >> 11) << 11; + col[1][i] = ((lpoints[i] >> 6) & 0x1f) << 11; + col[2][i] = ((lpoints[i] >> 1) & 0x1f) << 11; + } + + U32 ldelt[3]; + U32 rdelt[3]; + U32 left[3]; + U32 right[3]; + + for(i = 0; i < 3; i++) + { + ldelt[i] = (col[i][2] - col[i][0]) >> texelsPerLumelShift; + rdelt[i] = (col[i][3] - col[i][1]) >> texelsPerLumelShift; + + left[i] = col[i][0]; + right[i] = col[i][1]; + } + + for(U32 ty = 0; ty < texelsPerLumel; ty++) + { + U32 delt[3]; + U32 start[3]; + + for(i = 0; i < 3; i++) + { + delt[i] = (right[i] - left[i]) >> texelsPerLumelShift; + start[i] = left[i]; + } + + U16 *dstbits = &bits0[ twalk ]; + U8 *srcbits = (U8 *)&dstptr[stwalk]; + + for(U32 tx = 0; tx < texelsPerLumel; tx++) + { + U16 dstcol[3]; + for(i = 0; i < 3; i++) + { + U32 index = (start[i] >> 2) & 0x3F00; + dstcol[i] = alphaTable[index | srcbits[i]]; + start[i] += delt[i]; + } + U16 packed_col = (dstcol[0]>>3)<<11; + packed_col += (dstcol[1]>>3)<<6; + packed_col += (dstcol[2]>>3)<<1; + *dstbits++ = packed_col; + srcbits += 4; + } + + for(i = 0; i < 3; i++) + { + left[i] += ldelt[i]; + right[i] += rdelt[i]; + } + + + twalk += tystep; + stwalk += stystep; + } +#endif + txwalk += txstep; + stxwalk += txstep; + lx = lxnext; + } + ly = lynext; + tstart += (tystep << texelsPerLumelShift); + ststart += (stystep << texelsPerLumelShift); + } + } + } + +#if BLENDER_USE_ASM + if ( texelsPerLumel == 1) + extrude( destmips, DST_BMP_SHIFT ); + else + { + cheatmips( destmips[1], destmips[2], destmips[3], 64 ); + cheatmips( destmips[3], destmips[4], destmips[5], 16 ); + cheatmips4x4( destmips[5], destmips[6], destmips[7] ); + } +#else + extrude( destmips, DST_BMP_SHIFT ); +#endif +} + + +#define MAX_SQ_SHIFT 5 +#define NUM_BLOCKS (1 << (SRC_BMP_SHIFT - MAX_SQ_SHIFT)) + + +void Blender::addSourceTexture( int bmp_type, const U8 **bmps ) +{ + int sqwid = 1 << MAX_SQ_SHIFT; + int mipwid = SRC_WID; + + for ( int j = 0; j < num_mip_levels; j++ ) + { + U32 *dst = bmpdata[ j * num_src_bmps + bmp_type ]; + + // copy the bmp data over, changing the format so each block + // is contiguous. + for ( int row = 0; row < NUM_BLOCKS; row++ ) + { + const U8 *srcptr = bmps[ j ] + row * mipwid * sqwid * 3; + + for ( int col = 0; col < NUM_BLOCKS; col++, srcptr += (sqwid * 3) ) + for ( int py = 0; py < sqwid; py++ ) + { + const U8 *src2 = srcptr + py * mipwid * 3; + + for ( int px = 0; px < sqwid; px++, src2 += 3 ) + *dst++ = src2[0] + (U32(src2[1]) << 8) + (U32(src2[2]) << 16); + } + } + + sqwid >>= 1; + mipwid >>= 1; + } +} + + + +#define BLEND_BUFFER_SIZE (1 << (MAX_SQ_SHIFT * 2)) +#define CACHE_ROUND_SHIFT 12 +#define CACHE_ROUND_ADJUST ((1 << CACHE_ROUND_SHIFT) - 1) +#define CACHE_ROUND_MASK (~CACHE_ROUND_ADJUST) +#define DWORD_STAGGER 0// 256 + +static U32 *round_to_cache_start( U32 *ptr ) +{ + return ( (U32 *) ((int(ptr) + CACHE_ROUND_ADJUST) & CACHE_ROUND_MASK) ); +} + + +Blender::Blender( int num_src, int num_mips, U8 **alphas ) +{ + int bmps_size = BLEND_BUFFER_SIZE; // blending buffer (1 square) + int mip_size = BMP_SIZE; + int i, j; + + alpha_data = new U8*[num_src]; + for (i = 0; i < num_src; i++) + alpha_data[i] = alphas[i]; + + num_src_bmps = num_src; + num_mip_levels = num_mips; + + bmpdata = new U32*[ num_src * num_mips ]; + + for ( i = 0; i < num_mips; i++ ) + { + bmps_size += (mip_size + DWORD_STAGGER) * num_src; + mip_size >>= 2; + } + + bmp_alloc_ptr = new U32[ bmps_size + CACHE_ROUND_ADJUST ]; + U32 *bmps = round_to_cache_start( bmp_alloc_ptr ); + + // buffer that we'll be blending into, and lighting out of. + blendbuffer = bmps; + + U32 *curbmp = blendbuffer + BLEND_BUFFER_SIZE; + int bmp_size = BMP_SIZE; + int bmpnum = 0; + + // initialize pointers into buffer for source textures. + for ( j = 0; j < num_mips; j++ ) + { + for ( i = 0; i < num_src; i++ ) + { + bmpdata[ bmpnum ] = curbmp; + U32 *bptr = curbmp; + + curbmp += (bmp_size + DWORD_STAGGER); + bmpnum++; + } + + bmp_size >>= 2; + } + + +#if PAULS_TEST_CODE + // now zip through the alpha grids, and setup our material bits. + // only needed for test code, as real code can use tribes' grid. + int kbit = 1; + + for ( int k = 0; k < num_src; k++, kbit <<= 1 ) + { + for ( i = 0; i < ALPHA_WID; i++ ) + for ( j = 0; j < ALPHA_WID; j++ ) + { + int i2 = (i + 1) & 0xff; + int j2 = (j + 1) & 0xff; + + if ( !k ) + grid_mats[i][j] = 0; + + if ( alphas[k][i*ALPHA_WID+j] || alphas[k][i2*ALPHA_WID+j] || + alphas[k][i*ALPHA_WID+j2] || alphas[k][i2*ALPHA_WID+j2] ) + grid_mats[i][j] |= kbit; + } + } + + // make sure all squares have at least one bit set, so that it + // at least fills in with black. + for ( i = 0; i < ALPHA_WID; i++ ) + for ( j = 0; j < ALPHA_WID; j++ ) + if ( !grid_mats[i][j] ) + grid_mats[i][j] = 1; +#endif + +#if USE_ALPHATAB + // build alpha blending table for C versions... + for (i = 0; i < 16384; i++) + { + U32 pix = i & 0xFF; + U32 alpha = i >> 8; + U32 val = U32( pix * (F32(alpha) / 63.f) ); + alphaTable[i] = val; + } +#endif +} + +Blender::~Blender() +{ + if ( bmp_alloc_ptr ) + delete [] bmp_alloc_ptr; + + if ( bmpdata ) + delete [] bmpdata; + + if ( alpha_data ) + delete [] alpha_data; +} + diff --git a/terrain/blender.h b/terrain/blender.h new file mode 100644 index 0000000..2adb2ee --- /dev/null +++ b/terrain/blender.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BLENDER_H_ +#define _BLENDER_H_ + +#define PAULS_TEST_CODE 0 + + +#if PAULS_TEST_CODE + +typedef unsigned char U8; +typedef unsigned short U16; +typedef unsigned int U32; +typedef float F32; + +#define GRIDFLAGS( x, y ) (grid_mats[ yp ][ xp ]) +#define MATERIALSTART 1 +#define BLENDER_USE_ASM 1 + +#else + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TERRDATA_H_ +#include "terrain/terrData.h" +#endif +#ifndef _TERRRENDER_H_ +#include "terrain/terrRender.h" +#endif + +#ifdef USEASSEMBLYTERRBLEND +#define BLENDER_USE_ASM 1 +#else +#define BLENDER_USE_ASM 0 +#endif + +#define GRIDFLAGS( x, y ) (TerrainRender::mCurrentBlock->findSquare( 0, Point2I( xp, yp ) )->flags) +#define MATERIALSTART (GridSquare::MaterialStart) + +#endif + + + +class Blender +{ + // pointer to big buffer of source textures and mipmaps + U32 *bmp_alloc_ptr; + + // One square buffer used for blending... + U32 *blendbuffer; + + // List of pointers into bmp buffer. Grouped by mip level, + // so first X pointers are textures 0-X, mip 0. + U32 **bmpdata; + + // List of pointers to alpha data for the bmp types. + U8 **alpha_data; + + // Number of bmp types + int num_src_bmps; + + // Mip levels (including top detail) for each bmp type + int num_mip_levels; + +public: + // mips_per_bmp should include top level (always >= 1) + // alphadata is 8 bit 256x256 + Blender( int num_bmp_types, int mips_per_bmp, U8 **alphadata ); + ~Blender(); + + // blends into 5551 format. X and Y are in blocks (same resolution as + // alpha table, i.e. at high detail, there are 4x4 blocks covered by + // the 128x128 destination bmp. + // lightmap should be 16 bit 512x512. + // destmips is an array of pointers to the bitmap and it's mips to be + // filled in by this function + void blend( int x, int y, int level, const U16 *lightmap, U16 **destmips ); + + // bmps is an array of pointers to the bitmap and it's mips + // highest detail first. Should be in 24bit format. + // Call this once per bmp type. It copies the bmp into it's own format, + // so you can then delete your versions of the bmp and mips. + void addSourceTexture( int bmp_type, const U8 **bmps ); +}; + +#endif diff --git a/terrain/blender_asm.asm b/terrain/blender_asm.asm new file mode 100644 index 0000000..002d117 --- /dev/null +++ b/terrain/blender_asm.asm @@ -0,0 +1,1351 @@ +; syntax: export_fn +%macro export_fn 1 + %ifdef LINUX + ; No underscore needed for ELF object files + global %1 + %1: + %else + global _%1 + _%1: + %endif +%endmacro + +%macro global_var 1 + %ifdef LINUX + global %1 + %else + global _%1 + %define %1 _%1 + %endif +%endmacro + +%macro extern_var 1 + %ifdef LINUX + extern %1 + %else + extern _%1 + %define %1 _%1 + %endif +%endmacro + +; push registers +%macro prologue 0 + push ebp + mov ebp, esp ; set up ebp for parameter access +; pushad ; + push ebx + push esi + push edi +%endmacro + +; pop registers +%macro epilogue 0 + pop edi + pop esi + pop ebx +; popad + pop ebp + +%endmacro + +segment .data + +ix dd 0 +iy dd 0 + +; times 2 dd == 64 bits == sizeof(QWORD) in blender.cc +delta_a times 2 dd 0 +delta_b times 2 dd 0 +delta_c times 2 dd 0 +delta_d times 2 dd 0 + +alpha_a0 times 2 dd 0 +alpha_b0 times 2 dd 0 +alpha_c0 times 2 dd 0 +alpha_d0 times 2 dd 0 + +alpha_a1 times 2 dd 0 +alpha_b1 times 2 dd 0 +alpha_c1 times 2 dd 0 +alpha_d1 times 2 dd 0 + +alpha_c2 times 2 dd 0 +alpha_d2 times 2 dd 0 + +ldelt_a times 2 dd 0 +ldelt_b times 2 dd 0 +ldelt_c times 2 dd 0 +ldelt_d times 2 dd 0 + +rdelt_a times 2 dd 0 +rdelt_b times 2 dd 0 +rdelt_c times 2 dd 0 +rdelt_d times 2 dd 0 + +zero times 2 dd 0 + +redLightMask dw 0xf800, 0, 0, 0 +greenLightMask dw 0x07c0, 0, 0, 0 +blueLightMask dw 0x003e, 0, 0, 0 + +delta2 times 2 dd 0 +delta3 times 2 dd 0 +rdelt_x2 times 2 dd 0 +ldelt_x2 times 2 dd 0 + +leftq times 2 dd 0 +rightq times 2 dd 0 + +rdeltq times 2 dd 0 +ldeltq times 2 dd 0 + +mulfact dw 0x2000, 0x0008, 0x2000, 0x0008 +redblue dw 0x00f8, 0x00f8, 0x00f8, 0x00f8 +green dw 0xf800, 0, 0xf800, 0 +alpha dw 0x0001, 0x0001, 0, 0 + +mask_7c0 dw 0x07c0, 0, 0, 0 +mask_f8 dw 0x00f8, 0, 0, 0 +mask_f800000000 dw 0, 0, 0x00f8, 0 +mask_0000ffff0000ffff dw 0xffff, 0, 0xffff, 0 +mask_00007fff00007fff dw 0x7fff, 0, 0x7fff, 0 + +; externs for global variables declared in blender.cc +extern_var lumels + +; declare global variables +global_var sTargetTexelsPerLumel_log2 +global_var sTargetTexelsPerLumel +global_var sTargetTexelsPerLumelDiv2 +global_var nextsrcrow +global_var nextdstrow +global_var mip0_dstrowadd +global_var mip1_dstrowadd +global_var minus1srcrowsPlus8 +global_var srcrows_x2_MinusTPL + +; define global variables +sTargetTexelsPerLumel_log2 dd 0 +sTargetTexelsPerLumel dd 0 +sTargetTexelsPerLumelDiv2 dd 0 + +nextsrcrow dd 0 +nextdstrow dd 0 +mip0_dstrowadd dd 0 +mip1_dstrowadd dd 0 +minus1srcrowsPlus8 dd 0 +srcrows_x2_MinusTPL dd 0 + + +segment .text + +; parameter accessors for all of the doSquareX functions +%define dst [ebp+8] +%define sq_shift [ebp+12] +%define aoff [ebp+16] +%define bmp_ptrs [ebp+20] +%define alpha_ptrs [ebp+24] + +; void doSquare4( +; U32 *dst, +; int sq_shift, +; const int *aoff, +; const U32 *const *bmp_ptrs, +; const U8 *const *alpha_ptrs ); +export_fn doSquare4 + + prologue + + ; init iy + mov eax, 1 + mov cl, sq_shift + shl eax, cl + mov dword [iy], eax + + ; init ix + shr eax, 1 + mov dword [ix], eax + + movd mm1, sq_shift + ; get alpha values for the corners of the square for each texture type. + ; replicate the values into 4 words of the qwords. Also calc vertical + ; stepping values for the alpha values on left and right edges. + ; load alpha value into bh to mul by 256 for precision. then + ; punpcklwd mm0, mm0 followed by punpckldq mm0, mm0 + ; to replicate the low word into all words of mm0. + ; shift down difference by sqshift to divide by pixels per square to get + ; increment. + + mov esi, aoff + mov edi, alpha_ptrs + mov eax, [edi] + mov edx, eax + add eax, [esi] + xor ebx,ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_a0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_a1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_a], mm0 + psraw mm3, mm1 + movq [rdelt_a], mm3 + + mov eax, [edi+4] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_b0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_b1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_b], mm0 + psraw mm3, mm1 + movq [rdelt_b], mm3 + + mov eax, [edi+8] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_c0], mm2 + movq [alpha_c2], mm0 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_c1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_c], mm0 + psraw mm3, mm1 + movq [rdelt_c], mm3 + + mov eax, [edi+12] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_d0], mm2 + movq [alpha_d2], mm0 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_d1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_d], mm0 + psraw mm3, mm1 + movq [rdelt_d], mm3 + + mov esi, bmp_ptrs + mov eax, [esi] + mov ebx, [esi+4] + mov ecx, [esi+8] + mov edx, [esi+12] + + movq mm0, [alpha_a1] + movq mm2, [alpha_b1] + movq mm3, [alpha_c1] + movq mm4, [alpha_a0] + movq mm5, [alpha_b0] + movq mm6, [alpha_c0] + movq mm7, [alpha_d0] + mov edi, dst + +yloop4: + ; mm1 should be sq_shift at this point + + ; calculate alpha step increments...word-size steps are replicated + ; to fill qword. + psubw mm0, mm4 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_a], mm0 ;delta = ainc ainc ainc ainc + + psubw mm2, mm5 + psraw mm2, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_b], mm2 ;delta = ainc ainc ainc ainc + + psubw mm3, mm6 + psraw mm3, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_c], mm3 ;delta = ainc ainc ainc ainc + + movq mm0, [alpha_d1] + psubw mm0, mm7 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_d], mm0 ;delta = ainc ainc ainc ainc + + mov esi, [ix] + pxor mm2, mm2 + +xloop4: + movq mm0, [eax] + movq mm1, mm0 + punpcklbw mm0, mm2 + pmulhw mm0, mm4 + paddw mm4, [delta_a] + punpckhbw mm1, mm2 + pmulhw mm1, mm4 + paddw mm4, [delta_a] + packuswb mm0, mm1 + + movq mm3, [ebx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm5 + paddw mm5, [delta_b] + punpckhbw mm1, mm2 + pmulhw mm1, mm5 + paddw mm5, [delta_b] + packuswb mm3, mm1 + paddb mm0, mm3 + + movq mm3, [ecx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm6 + paddw mm6, [delta_c] + punpckhbw mm1, mm2 + pmulhw mm1, mm6 + paddw mm6, [delta_c] + packuswb mm3, mm1 + paddb mm0, mm3 + + movq mm3, [edx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm7 + paddw mm7, [delta_d] + punpckhbw mm1, mm2 + pmulhw mm1, mm7 + paddw mm7, [delta_d] + packuswb mm3, mm1 + paddb mm0, mm3 + + ; double result, to make up for alpha vals being signed (max = 127) + ; so our math turns out a bit short, example: + ; (0x7f00 * 0xff) >> 16 = 0x7e....* 2 = 252...not quite 255 + ; would have been (0xff00 * 0xff) >> 16 = 0xfe = 254, + ; if I could do an unsigned pmulhw... + ; pmulhuw is in an intel document I found, but doesn't compile.... + paddb mm0, mm0 + + movq [edi], mm0 + + add eax, 8 + add ebx, 8 + add ecx, 8 + add edx, 8 + add edi, 8 + + dec esi + jnz near xloop4 + + movq mm4, [alpha_a0] + paddw mm4, [ldelt_a] + movq [alpha_a0], mm4 + + movq mm5, [alpha_b0] + paddw mm5, [ldelt_b] + movq [alpha_b0], mm5 + + movq mm6, [alpha_c0] + paddw mm6, [ldelt_c] + movq [alpha_c0], mm6 + + movq mm7, [alpha_d0] + paddw mm7, [ldelt_d] + movq [alpha_d0], mm7 + + movq mm0, [alpha_d1] + paddw mm0, [rdelt_d] + movq [alpha_d1], mm0 + + movq mm2, [alpha_b1] + paddw mm2, [rdelt_b] + movq [alpha_b1], mm2 + + movq mm3, [alpha_c1] + paddw mm3, [rdelt_c] + movq [alpha_c1], mm3 + + movq mm0, [alpha_a1] + paddw mm0, [rdelt_a] + movq [alpha_a1], mm0 + + movd mm1, sq_shift ; top of loop expects this + + dec dword [iy] + jnz near yloop4 + + emms + + epilogue + + ret + + + +; void doSquare3( +; U32 *dst, +; int sq_shift, +; const int *aoff, +; const U32 *const *bmp_ptrs, +; const U8 *const *alpha_ptrs ); +export_fn doSquare3 + + prologue + + ; init iy + mov eax, 1 + mov cl, sq_shift + shl eax, cl + mov dword [iy], eax + + ; init ix + shr eax, 1 + mov dword [ix], eax + + movd mm1, sq_shift + ; get alpha values for the corners of the square for each texture type. + ; replicate the values into 4 words of the qwords. Also calc vertical + ; stepping values for the alpha values on left and right edges. + ; load alpha value into bh to mul by 256 for precision. then + ; punpcklwd mm0, mm0 followed by punpckldq mm0, mm0 + ; to replicate the low word into all words of mm0. + ; shift down difference by sqshift to divide by pixels per square to get + ; increment. + + mov esi, aoff + mov edi, alpha_ptrs + mov eax, [edi] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_a0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_a1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_a], mm0 + psraw mm3, mm1 + movq [rdelt_a], mm3 + + mov eax, [edi+4] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_b0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_b1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_b], mm0 + psraw mm3, mm1 + movq [rdelt_b], mm3 + + mov eax, [edi+8] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_c0], mm2 + movq [alpha_c2], mm0 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_c1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_c], mm0 + psraw mm3, mm1 + movq [rdelt_c], mm3 + + mov esi, bmp_ptrs + mov eax, [esi] + mov ebx, [esi+4] + mov ecx, [esi+8] + + movq mm0, [alpha_a1] + movq mm2, [alpha_b1] + movq mm3, [alpha_c1] + movq mm4, [alpha_a0] + movq mm5, [alpha_b0] + movq mm6, [alpha_c0] + mov edi, dst + +yloop3: + ; mm1 should be sq_shift at this point + ; mm0 should be alpha_a1 + ; mm2 should be alpha_b1 + ; mm3 should be alpha_c1 + + ; calculate alpha step increments...word-size steps are replicated + ; to fill qword. + psubw mm0, mm4 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_a], mm0 ;delta = ainc ainc ainc ainc + + psubw mm2, mm5 + psraw mm2, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_b], mm2 ;delta = ainc ainc ainc ainc + + psubw mm3, mm6 + psraw mm3, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_c], mm3 ;delta = ainc ainc ainc ainc + + mov esi, [ix] + pxor mm2, mm2 + + + movq mm7, [delta_a] +xloop3: + movq mm0, [eax] + movq mm1, mm0 + punpcklbw mm0, mm2 + pmulhw mm0, mm4 + paddw mm4, mm7 + punpckhbw mm1, mm2 + pmulhw mm1, mm4 + paddw mm4, mm7 + packuswb mm0, mm1 + + movq mm3, [ebx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm5 + paddw mm5, [delta_b] + punpckhbw mm1, mm2 + pmulhw mm1, mm5 + paddw mm5, [delta_b] + packuswb mm3, mm1 + paddb mm0, mm3 + + movq mm3, [ecx] + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm6 + paddw mm6, [delta_c] + punpckhbw mm1, mm2 + pmulhw mm1, mm6 + paddw mm6, [delta_c] + packuswb mm3, mm1 + paddb mm0, mm3 + paddb mm0, mm0 + + movq [edi], mm0 + + add eax, 8 + add ebx, 8 + add ecx, 8 + add edi, 8 + + dec esi + jnz near xloop3 + + movq mm4, [alpha_a0] + paddw mm4, [ldelt_a] + movq [alpha_a0], mm4 + + movq mm5, [alpha_b0] + paddw mm5, [ldelt_b] + movq [alpha_b0], mm5 + + movq mm6, [alpha_c0] + paddw mm6, [ldelt_c] + movq [alpha_c0], mm6 + + movq mm2, [alpha_b1] + paddw mm2, [rdelt_b] + movq [alpha_b1], mm2 + + movq mm3, [alpha_c1] + paddw mm3, [rdelt_c] + movq [alpha_c1], mm3 + + movq mm0, [alpha_a1] + paddw mm0, [rdelt_a] + movq [alpha_a1], mm0 + + movd mm1, sq_shift ; top of loop expects this + + dec dword [iy] + jnz near yloop3 + + emms + + epilogue + + ret + + + +; void doSquare2( +; U32 *dst, +; int sq_shift, +; const int *aoff, +; const U32 *const *bmp_ptrs, +; const U8 *const *alpha_ptrs ); +export_fn doSquare2 + + prologue + + ; init iy + mov eax, 1 + mov cl, sq_shift + shl eax, cl + mov dword [iy], eax + + ; init ix + shr eax, 1 + mov dword [ix], eax + + movd mm1, sq_shift + ; get alpha values for the corners of the square for each texture type. + ; replicate the values into 4 words of the qwords. Also calc vertical + ; stepping values for the alpha values on left and right edges. + ; punpcklwd mm0, mm0 followed by punpckldq mm0, mm0 + ; to replicate the low word into all words of mm0. + ; shift down difference by sqshift to divide by pixels per square to get + ; increment. + + mov esi, aoff + mov edi, alpha_ptrs + mov eax, [edi] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_a0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_a1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_a], mm0 + psraw mm3, mm1 + movq [rdelt_a], mm3 + + mov eax, [edi+4] + mov edx, eax + add eax, [esi] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm2, ebx + punpcklwd mm2, mm2 + add eax, [esi+8] + punpckldq mm2, mm2 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm0, ebx + punpcklwd mm0, mm0 + punpckldq mm0, mm0 + movq [alpha_b0], mm2 + psubw mm0, mm2 + add eax, [esi+4] + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + mov eax, edx + movd mm4, ebx + punpcklwd mm4, mm4 + add eax, [esi+12] + punpckldq mm4, mm4 + xor ebx, ebx + mov bl, [eax] + shl ebx, 7 + movd mm3, ebx + movq [alpha_b1], mm4 + punpcklwd mm3, mm3 + punpckldq mm3, mm3 + psraw mm0, mm1 + psubw mm3, mm4 + movq [ldelt_b], mm0 + psraw mm3, mm1 + movq [rdelt_b], mm3 + + mov esi, bmp_ptrs + mov eax, [esi] + mov ebx, [esi+4] + + movq mm0, [alpha_a1] + movq mm2, [alpha_b1] + movq mm4, [alpha_a0] + movq mm5, [alpha_b0] + mov edi, dst + +yloop2: + ; mm1 should be sq_shift at this point + ; mm0 should be alpha_a1 + ; mm2 should be alpha_b1 + + ; calculate alpha step increments...word-size steps are replicated + ; to fill qword. + psubw mm0, mm4 + psraw mm0, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_a], mm0 ;delta = ainc ainc ainc ainc + + psubw mm2, mm5 + psraw mm2, mm1 ;mm0 = (right-left) >> sq_shift + movq [delta_b], mm2 ;delta = ainc ainc ainc ainc + + mov esi, [ix] + pxor mm2, mm2 + + movq mm6, [delta_a] + movq mm7, [delta_b] + +xloop2: + movq mm0, [eax] + movq mm3, [ebx] + + movq mm1, mm0 + punpcklbw mm0, mm2 + pmulhw mm0, mm4 + paddw mm4, mm6 + punpckhbw mm1, mm2 + pmulhw mm1, mm4 + paddw mm4, mm6 + packuswb mm0, mm1 + + movq mm1, mm3 + punpcklbw mm3, mm2 + pmulhw mm3, mm5 + paddw mm5, mm7 + punpckhbw mm1, mm2 + pmulhw mm1, mm5 + paddw mm5, mm7 + packuswb mm3, mm1 + paddb mm0, mm3 + paddb mm0, mm0 + + movq [edi], mm0 + + add edi, 8 + add eax, 8 + add ebx, 8 + + dec esi + jnz xloop2 + + movq mm4, [alpha_a0] + paddw mm4, [ldelt_a] + movq [alpha_a0], mm4 + + movq mm5, [alpha_b0] + paddw mm5, [ldelt_b] + movq [alpha_b0], mm5 + + movq mm2, [alpha_b1] + paddw mm2, [rdelt_b] + movq [alpha_b1], mm2 + + movq mm0, [alpha_a1] + paddw mm0, [rdelt_a] + movq [alpha_a1], mm0 + + movd mm1, sq_shift ; top of loop expects this + + dec dword [iy] + jnz near yloop2 + + emms + + epilogue + + ret + + + +; params for doLumelPlus1Mip +%define dstmip0 [ebp+8] +%define dstmip1 [ebp+12] +%define srcptr [ebp+16] + +; void doLumelPlus1Mip( U16 *dstmip0, U16 *dstmip1, const U32 *srcptr ); +export_fn doLumelPlus1Mip + + prologue + + movd mm7, [sTargetTexelsPerLumel_log2] + + movd mm0, [lumels] + movq mm4, mm0 + pand mm0, [redLightMask] + movq mm5, mm4 + pand mm4, [greenLightMask] + psllq mm0, 31 + pand mm5, [blueLightMask] + psllq mm4, 20 + paddw mm0, mm4 + psllq mm5, 9 + paddw mm0, mm5 ; mm0 = 0000rrrrggggbbbb qword for lp[0] + movq [leftq], mm0 + + movd mm1, [lumels+8] ; get lp2 + movq mm4, mm1 + pand mm1, [redLightMask] + movq mm5, mm4 + pand mm4, [greenLightMask] + psllq mm1, 31 + pand mm5, [blueLightMask] + psllq mm4, 20 + paddw mm1, mm4 + psllq mm5, 9 + paddw mm1, mm5 ; mm1 = 0000rrrrggggbbbb qword for lp[2] + + psubw mm1, mm0 + psraw mm1, mm7 + movq [ldeltq], mm1 + psllw mm1, 1 + movq [ldelt_x2], mm1 + + + movd mm2, [lumels+4] ; get lp[1] + movq mm4, mm2 + pand mm2, [redLightMask] + movq mm5, mm4 + pand mm4, [greenLightMask] + psllq mm2, 31 + pand mm5, [blueLightMask] + psllq mm4, 20 + paddw mm2, mm4 + psllq mm5, 9 + paddw mm2, mm5 ; mm2 = 0000rrrrggggbbbb qword for lp[1] + movq [rightq], mm2 + + movd mm3, [lumels+12] ; get lp3 + movq mm4, mm3 + pand mm3, [redLightMask] + movq mm5, mm4 + pand mm4, [greenLightMask] + psllq mm3, 31 + pand mm5, [blueLightMask] + psllq mm4, 20 + paddw mm3, mm4 + psllq mm5, 9 + paddw mm3, mm5 ; mm3 = 0000rrrrggggbbbb qword for lp[3] + + psubw mm3, mm2 + psraw mm3, mm7 + movq [rdeltq], mm3 + psllw mm3, 1 + movq [rdelt_x2], mm3 + + mov edi, dstmip0 + mov esi, srcptr + mov edx, dstmip1 + pxor mm6, mm6 + + mov ecx, [sTargetTexelsPerLumelDiv2] ; yloop count + movq mm2, [leftq] + movq mm3, [rightq] + + ; mm2 is left, mm3 is right +yloop_dlpm: + movd mm7, [sTargetTexelsPerLumel_log2] + movq mm6, mm2 + movq mm1, mm2 ; mm1 is light1 + movq mm5, mm3 + movq mm4, mm3 + + paddw mm5, [rdeltq] ; right + rdelt + psubw mm4, mm6 ; right - left + paddw mm6, [ldeltq] ; left + ldelt + psraw mm4, mm7 + movq [delta2], mm4 + + psubw mm5, mm6 ; mm6 is light2 + psraw mm5, mm7 + movq [delta3], mm5 + + mov ebx, [sTargetTexelsPerLumelDiv2] ; loop count + + ; do 4 source pixels per loop + ; mm1 is light1 + ; mm6 is light2 + pxor mm7, mm7 + +xloop_dlpm: + ; get first of source, col 0 and 1 + movq mm4, [esi] + add esi, [nextsrcrow] + movq mm5, mm4 + punpcklbw mm4, [zero] + pmulhw mm4, mm1 ; mm1 is light factor for first row + paddw mm1, [delta2] + punpckhbw mm5, [zero] + pmulhw mm5, mm1 + paddw mm1, [delta2] + + movq mm7, [esi] + add esi, [minus1srcrowsPlus8] + + movq mm0, mm4 + paddw mm0, mm5 ; mm0 is the avg, for mip1[0,1] + + packuswb mm4, mm5 ; put both pixels in same qword + paddw mm4, mm4 ; double it, because lighting mul halved it + movq mm5, mm4 ; save the original data + pand mm4, [redblue] ; mask out all but the 5MSBits of red and blue + pmaddwd mm4, [mulfact] ; multiply each word by + ; 2^13, 2^3, 2^13, 2^3 and add results + pand mm5, [green] ; mask out all but the 5MSBits of green + por mm4, mm5 ; combine the red, green, and blue bits + psrld mm4, 6 ; shift into position + packssdw mm4, [zero] ; pack into single dword + pslld mm4, 1 ; shift into final position + por mm4, [alpha] ; add the alpha bit + + ; write 2 pixels to mip0 + movd [edi], mm4 + + ; get second row, cols 0 and 1 + movq mm5, mm7 + pxor mm4, mm4 + punpcklbw mm7, mm4 + pmulhw mm7, mm6 ; mm6 is light factor for 2nd row + paddw mm6, [delta3] + punpckhbw mm5, mm4 + pmulhw mm5, mm6 + paddw mm6, [delta3] + paddw mm0, mm7 + paddw mm0, mm5 + psrlw mm0, 1 ; mm0 is mip1[0,1] average + + packuswb mm7, mm5 ; put both pixels in same qword + paddw mm7, mm7 ; double it, because lighting mul halved it + movq mm5, mm7 ; save the original data + pand mm7, [redblue] ; mask out all but the 5MSBits of red and blue + pmaddwd mm7, [mulfact] ; multiply each word by + ; 2^13, 2^3, 2^13, 2^3 and add results + pand mm5, [green] ; mask out all but the 5MSBits of green + por mm7, mm5 ; combine the red, green, and blue bits + psrld mm7, 6 ; shift into position + packssdw mm7, mm4 ; pack into single dword + pslld mm7, 1 ; shift into final position + por mm7, [alpha] ; add the alpha bit + + ; write 2 16-bit pixels to mip0, 2nd row + movd [edi+0x100], mm7 + + movq mm5, mm0 + movq mm4, mm0 + pand mm4, [mask_f8] ; red + psrlq mm5, 13 + pand mm5, [mask_7c0] ; green + psllq mm4, 8 + pand mm0, [mask_f800000000] ; blue + paddw mm5, mm4 + psrlq mm0, 34 + paddw mm0, mm5 + + ; write 1 pixels to mip1 + movd eax, mm0 + mov [edx], ax + + ; increment ptrs + add edx, 2 ; mip1 + add edi, 4 ; mip0 + + dec ebx + jnz near xloop_dlpm + + add esi, [srcrows_x2_MinusTPL] + add edx, [mip1_dstrowadd] + add edi, [mip0_dstrowadd] + paddw mm2, [ldelt_x2] ; mm2 is left + paddw mm3, [rdelt_x2] ; mm3 is right + dec ecx + jnz near yloop_dlpm + + emms + + epilogue + + ret + + + +; params for do1x1Lumel +%define dstptr [ebp+8] +%define srcptr [ebp+12] + +; void do1x1Lumel( U16 *dstptr, const U32 *srcptr ); +export_fn do1x1Lumel + + prologue + + movd mm0, [lumels] + movq mm4, mm0 + pand mm0, [redLightMask] + movq mm5, mm4 + pand mm4, [greenLightMask] + psllq mm0, 31 + pand mm5, [blueLightMask] + psllq mm4, 20 + paddw mm0, mm4 + psllq mm5, 9 + paddw mm0, mm5 ; mm0 = 0000rrrrggggbbbb qword for lp[0] + + mov edi, dstptr + mov esi, srcptr + pxor mm6, mm6 + + movd mm4, [esi] + punpcklbw mm4, mm6 ; mm6 is expected to be 0 here + pmulhw mm4, mm0 + paddw mm4, mm4 + + movq mm7, mm4 + movq mm6, mm4 + psrlq mm4, 34 + pand mm7, [mask_f8] + psrlq mm6, 13 + psllq mm7, 8 + pand mm6, [mask_7c0] + paddw mm4, mm7 + paddw mm4, mm6 + movd eax, mm4 + mov [edi],ax + emms + + epilogue + + ret + +; params for cheatmips +%define srcptr [ebp+8] +%define dstmip0 [ebp+12] +%define dstmip1 [ebp+16] +%define wid [ebp+20] + +; void cheatmips( U16 *srcptr, U16 *dstmip0, U16 *dstmip1, int wid ); +export_fn cheatmips + + prologue + + mov esi, srcptr + mov edi, dstmip0 + mov edx, dstmip1 + + mov ecx, wid + shr ecx, 1 + mov eax, ecx + shr eax, 3 + shl dword wid, 1 + + movq mm6, [mask_0000ffff0000ffff] + movq mm7, [mask_00007fff00007fff] + +yloop_cm: + mov ebx, eax +xloop_cm: + movq mm0, [esi] + movq mm1, [esi+8] + movq mm2, [esi+16] + movq mm3, [esi+24] + + pand mm0, mm6 + psrlw mm1, 1 + pand mm1, mm7 + psrlw mm0, 1 + pand mm2, mm6 + psrlw mm3, 1 + pand mm3, mm7 + psrlw mm2, 1 + packssdw mm0, mm1 + packssdw mm2, mm3 + psllw mm0, 1 ;mip1, qw 0 + movq [edi], mm0 + psllw mm2, 1 ;mip1, qw 1 + movq [edi+8], mm2 + + test ecx, 1 + jnz nomip2 + + movq mm1, mm0 + movq mm3, mm2 + pand mm1, mm6 + psrlw mm3, 1 + pand mm3, mm7 + psrlw mm1, 1 + packssdw mm1, mm3 + psllw mm1, 1 ;mip2, qw 0 + movq [edx], mm1 + add edx, 8 +nomip2: + + add esi, 32 + add edi, 16 + + dec ebx + jnz xloop_cm + + add esi, wid + dec ecx + jnz near yloop_cm + + emms + + epilogue + + ret + +; params for cheatmips4x4 +%define srcptr [ebp+8] +%define dstmip0 [ebp+12] +%define dstmip1 [ebp+16] + +; void cheatmips4x4( U16 *srcptr, U16 *dstmip0, U16 *dstmip1 ); +export_fn cheatmips4x4 + + prologue + + mov esi, srcptr + mov edi, dstmip0 + mov edx, dstmip1 + + movq mm0, [esi] + movq mm1, [esi+16] + pand mm0, [mask_0000ffff0000ffff] + psrlw mm1, 1 + pand mm1, [mask_00007fff00007fff] + psrlw mm0, 1 + packssdw mm0, mm1 + psllw mm0, 1 ; mip1, qw 0 + movq [edi], mm0 + + movd eax, mm0 + mov [edx], ax + + emms + + epilogue + + ret diff --git a/terrain/bvQuadTree.cc b/terrain/bvQuadTree.cc new file mode 100644 index 0000000..102df9b --- /dev/null +++ b/terrain/bvQuadTree.cc @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/****************************************************************************** + * FILENAME: D:\Tribes\darkstar\terrain\bvQuadTree.cc + * + * DESCRIPTION: + * + * CREATED: 1/17/00 4:07:04 PM + * + * BY: PeteW + ******************************************************************************/ + +#include "terrain/bvQuadTree.h" +#include "console/console.h" + +BVQuadTree::BVQuadTree(BitVector *bv) +{ + if (bv != NULL) + init(*bv); + else + { + BitVector localBV; + localBV.setSize(4); + localBV.set(); + init(localBV); + } +} + +BVQuadTree::~BVQuadTree() +{ + while (mQTHierarchy.size() > 0) + { + delete mQTHierarchy.last(); + mQTHierarchy.pop_back(); + } +} + +bool BVQuadTree::isSet(const Point2F &pos, S32 level) const +{ + AssertFatal(pos.x >= 0. && pos.x <= 1., "BVQuadTree::isSet: x must be in range [0,1]"); + AssertFatal(pos.y >= 0. && pos.y <= 1., "BVQuadTree::isSet: y must be in range [0,1]"); + AssertFatal(level >= 0, "BVQuadTree:isSet: level must be greater than or equal to zero"); + + if (level >= mQTHierarchy.size()) + // force to be within resolution of QT + level = mQTHierarchy.size() - 1; + if (level < 0) + level = 0; + + F32 dimension = F32(1 << level); + U32 offset = U32((F32(U32(pos.y * dimension)) + pos.x) * dimension); + return(mQTHierarchy[level]->test(offset)); +} + +bool BVQuadTree::isClear(const Point2F &pos, S32 level) const +{ + AssertFatal(pos.x >= 0. && pos.x <= 1., "BVQuadTree::isClear: x must be in range [0,1]"); + AssertFatal(pos.y >= 0. && pos.y <= 1., "BVQuadTree::isClear: y must be in range [0,1]"); + AssertFatal(level >= 0, "BVQuadTree:isClear: level must be greater than or equal to zero"); + + if (level >= mQTHierarchy.size()) + // force to be within resolution of QT + level = mQTHierarchy.size() - 1; + if (level < 0) + level = 0; + + F32 dimension = F32(1 << level); + U32 offset = U32((F32(U32(pos.y * dimension)) + pos.x) * dimension); + return(mQTHierarchy[level]->test(offset) == false); +} + +/* Initialize the quadtree with the provided bit vector. Note that the bit vector + * denotes data at each corner of the quadtree cell. Hence the dimension for the + * deepest quadtree level must be 1 less than that of the bit vector. + */ +void BVQuadTree::init(const BitVector &bv) +{ + while (mQTHierarchy.size() > 0) + { + delete mQTHierarchy.last(); + mQTHierarchy.pop_back(); + } + + // get the width/height of the square bit vector + U32 bvDim = (U32)mSqrt((F32)bv.getSize()); + U32 qtDim = bvDim - 1; // here's where we correct dimension... + + AssertFatal(((mSqrt((F32)bv.getSize()) - 1) == (F32)qtDim) && (isPow2(qtDim) == true), "BVQuadTree::init: bit vector size must be power of 4"); + + // find the power of two we're starting at + mResolution = qtDim; + U32 level = 0; + while ((1 << (level + 1)) <= qtDim) + level++; + BitVector *initBV = new BitVector; + + AssertFatal(initBV != NULL, "BVQuadTree::init: failed to allocate highest detail bit vector"); + + initBV->setSize(qtDim * qtDim); + initBV->clear(); + for (S32 i = 0; i < qtDim; i++) + for (S32 j = 0; j < qtDim; j++) + { + S32 k = i * bvDim + j; + if (bv.test(k) || bv.test(k + 1) || bv.test(k + bvDim) || bv.test(k + bvDim + 1)) + initBV->set(i * qtDim + j); + } + mQTHierarchy.push_back(initBV); + if (level > 0) + buildHierarchy(level - 1); +} + +#ifdef BV_QUADTREE_DEBUG +void BVQuadTree::dump() const +{ + char str[256]; + U32 strlen; + for (U32 i = 0; i < mQTHierarchy.size(); i++) + { + U32 dimension = 1 << i; + Con::printf("level %d:", i); + for (U32 y = 0; y < dimension; y++) + { + U32 yOffset = y * dimension; + str[0] = '\0'; + for (U32 x = 0; x < dimension; x++) + { + U32 offset = yOffset + x; + strlen = dStrlen(str); + if (strlen < 252) + dSprintf(str + strlen, 256 - strlen, mQTHierarchy[i]->isSet(offset) ? "1 " : "0 "); + else + { + dSprintf(str + strlen, 256 - strlen, "..."); + break; + } + } + Con::printf("%s", str); + } + } +} +#endif + +void BVQuadTree::buildHierarchy(U32 level) +{ + BitVector *priorBV = mQTHierarchy.first(); + BitVector *levelBV = new BitVector; + + AssertFatal(levelBV != NULL, "BVQuadTree::buildHierarchy: failed to allocate bit vector"); + + U32 levelDim = (1 << level); + levelBV->setSize(levelDim * levelDim); + levelBV->clear(); + U32 yOffset, offset, yPriorOffset, priorOffset; + // COULD THIS BE DONE WITH A SINGLE LOOP? + for (U32 y = 0; y < levelDim; y++) + { + yOffset = y * levelDim; + yPriorOffset = yOffset << 2; + for (U32 x = 0; x < levelDim; x++) + { + offset = yOffset + x; + priorOffset = yPriorOffset + (x << 1); + if (priorBV->test(priorOffset) || priorBV->test(priorOffset + 1) || + priorBV->test(priorOffset + (levelDim << 1)) || + priorBV->test(priorOffset + (levelDim << 1) + 1)) + levelBV->set(offset); + } + } + mQTHierarchy.push_front(levelBV); + if (level > 0) + buildHierarchy(level - 1); +} diff --git a/terrain/bvQuadTree.h b/terrain/bvQuadTree.h new file mode 100644 index 0000000..c74f9eb --- /dev/null +++ b/terrain/bvQuadTree.h @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +/****************************************************************************** + * FILENAME: D:\Tribes\darkstar\terrain\bvQuadTree.h + * + * DESCRIPTION: + * + * CREATED: 1/17/00 4:02:53 PM + * + * BY: PeteW + ******************************************************************************/ + +#ifndef _BVQUADTREE_H_ +#define _BVQUADTREE_H_ + +//#define BV_QUADTREE_DEBUG + +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif +#ifndef _BITVECTOR_H_ +#include "Core/bitVector.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif + +class BVQuadTree +{ +protected: + VectorPtr mQTHierarchy; + U32 mResolution; +public: + BVQuadTree(BitVector *bv = NULL); + ~BVQuadTree(); + + bool isSet(const Point2F &pos, S32 level) const; + bool isClear(const Point2F &pos, S32 level) const; + + void init(const BitVector &bv); +#ifdef BV_QUADTREE_DEBUG + void dump() const; +#endif + U32 countLevels() const { return(mQTHierarchy.size()); } +protected: + void buildHierarchy(U32 level); +private: + BVQuadTree(const BVQuadTree &); + BVQuadTree& operator=(const BVQuadTree &); +}; + +#endif diff --git a/terrain/fluid.h b/terrain/fluid.h new file mode 100644 index 0000000..dd6242b --- /dev/null +++ b/terrain/fluid.h @@ -0,0 +1,326 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FLUID_H_ +#define _FLUID_H_ + +//============================================================================== +// TO DO LIST +//============================================================================== +// - ARB support? +// - Optimize the fog given water is horizontal +// - New fog system +// - Attempt to reject fully fogger low LOD blocks +//============================================================================== +// - Designate specular map? +// - Turn off specular? (for lava) +//============================================================================== + +//============================================================================== +// ASSUMPTIONS: +// - A single repetition of the terrain (or a "rep") is 256x256 squares. +// - A terrain square is 8 meters on a side. +// - The fluid can NOT be rotated on any axis. +// - The "anchor point" for the fluid can be on any discrete square corner. +// - The size of the fluid is constrained to be either a multiple of 4 or 8 +// squares. +//============================================================================== +// DISCUSSION: +// - If the overall size of the fluid is less than a quarter of a terrain rep, +// then the fluid will use a "double resolution" mode. Thus, when the fluid +// is sufficiently small, it gets more detailed. +// - The fluid renders blocks in one of two sizes, LOW and HIGH. +// - Block are 8 squares in normal resolution mode, and 4 in high. +// - A quad tree is used to quickly cull down to the blocks. +// - Reject and accept masks are used by the quad tree. +//============================================================================== + +//============================================================================== +// INCLUDES +//============================================================================== + +#ifndef _TYPES_H_ +#include "Platform/types.h" +#endif +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif + +//============================================================================== +// DEFINES +//============================================================================== + +#define s32 S32 +#define u32 U32 +#define s16 S16 +#define u16 U16 +#define s8 S8 +#define u8 U8 +#define byte U8 +#define f32 F32 + +#define PI (3.141592653589793238462643f) + +#define SECONDS ((f32)(Platform::getVirtualMilliseconds()) * 0.001f) +#define MALLOC dMalloc +#define REALLOC dRealloc +#define FREE dFree +#define MEMSET dMemset +#define TWO_PI (PI * 2.0f) +#define FMOD(a,b) (f32)fmod( a, b ) +#define LENGTH3(a,b,c) (f32)sqrt( (a)*(a) + (b)*(b) + (c)*(c) ) +#define SINE(a) (float)sin( a ) +#define COSINE(a) (float)cos( a ) + +#define DISTANCE(x,y,z) LENGTH3( (x)-m_Eye.X, (y)-m_Eye.Y, (z)-m_Eye.Z ) + +#define ASSERT(exp) + +//============================================================================== +// TYPES +//============================================================================== +// +// Order for [4] element arrays... +// +// 2----3 Y +// | | | +// | | | +// 0----1 0----X +// +//============================================================================== + +class fluid +{ +//------------------------------------------------------------------------------ +// Public Types +// +public: + typedef f32 compute_fog_fn( f32 DeltaZ, f32 D ); + +//------------------------------------------------------------------------------ +// Public Functions +// +public: + fluid ( void ); + ~fluid (); + + // + // Render (in FluidRender.cpp): + // + void Render ( bool& EyeSubmerged ); + + // + // Setup per frame (in FluidSupport.cpp): + // + void SetEyePosition ( f32 X, f32 Y, f32 Z ); + void SetFrustrumPlanes ( f32* pFrustrumPlanes ); + + // + // Setup at initialization (in FluidSupport.cpp): + // + void SetInfo ( f32& X0, + f32& Y0, + f32& SizeX, + f32& SizeY, + f32 SurfaceZ, + f32 WaveAmplitude, + f32& Opacity, + f32& EnvMapIntensity, + s32 RemoveWetEdges ); + + void SetTerrainData ( u16* pTerrainData ); + + void SetTextures ( TextureHandle Base, + TextureHandle EnvMap ); + + void SetLightMapTexture ( TextureHandle LightMapTexture ); + + void SetFogParameters ( f32 R, f32 G, f32 B, f32 VisibleDistance ); + void SetFogFn ( compute_fog_fn* pFogFn ); + + // + // Run time interrogation (in FluidSupport.cpp): + // + s32 IsFluidAtXY ( f32 X, f32 Y ) const; + +//------------------------------------------------------------------------------ +// Private Types +// +private: + struct plane { f32 A, B, C, D; }; + struct rgba { f32 R, G, B, A; }; + struct xyz { f32 X, Y, Z; }; + struct uv { f32 U, V; }; + + struct node + { + s32 Level; + s32 MaskIndexX, MaskIndexY; + s32 BlockX0, BlockY0; // World Block + f32 X0, Y0; // World Position + f32 X1, Y1; // World Position + byte ClipBits[4]; + }; + + struct block + { + f32 X0, Y0; + f32 X1, Y1; + f32 Distance[4]; // Distance from eye + f32 LOD [4]; // Level of detail + }; + + // Rendering phases: + // Phase 1 - Base texture (two passes of cross faded textures) + // Phase 2 - Shadow map + // Phase 3 - Environment map / Specular + // Phase 4 - Fog + + struct vertex + { + xyz XYZ; // All phases - Position + rgba RGBA1a; // Phase 1a - Base alpha, first pass + rgba RGBA1b; // Phase 1b - Base alpha, second pass + uv UV3; // Phase 3 - EnvMap UV + rgba RGBA4; // Phase 4 - Fog Color/Alpha + }; + +//------------------------------------------------------------------------------ +// Private Variables +// +private: + s32 m_SquareX0, m_SquareY0; // Anchor in terrain squares + s32 m_SquaresInX, m_SquaresInY; // Number of squares in fluid region + s32 m_BlocksInX, m_BlocksInY; // Number of blocks in fluid region + f32 m_SurfaceZ; // Altitude of fluid surface + s32 m_RemoveWetEdges; // Dry fill all edges of the fluid block + s32 m_HighResMode; // Blocks are 4x4 (high res) or 8x8 squares (normal) + plane m_Plane[6]; // Frustrum clip planes: 0=T 1=B 2=L 3=R 4=N 5=F + xyz m_Eye; + f32 m_Seconds; + f32 m_BaseSeconds; + rgba m_FogColor; + f32 m_VisibleDistance; + f32 m_Opacity; + f32 m_EnvMapIntensity; + f32 m_WaveAmplitude; + f32 m_WaveFactor; + u16* m_pTerrain; // 256x256 data for the terrain + + TextureHandle m_BaseTexture; + TextureHandle m_EnvMapTexture; + TextureHandle m_LightMapTexture; + + f32 m_Step[5]; // [0] = 0 + // [1] = 1/4 block step + // [2] = 1/2 block step + // [3] = 3/4 block step + // [4] = 1 block step + + compute_fog_fn* m_pFogFn; + + // Bit masks to trivially accept or reject the progressive levels for the + // quad tree recursion. No need for an accept mask at the highest level + // since it is just the exact opposite of the reject mask at that level. + + static s32 m_MaskOffset[6]; // Offset for given level into masks. + + byte m_RejectMask[ 1 + 1 + 2 + 8 + 32 + 128 ]; + byte m_AcceptMask[ 1 + 1 + 2 + 8 + 32 ]; + + // + // Shared among instances of fluid. + // + + static vertex* m_pVertex; + static s32 m_VAllocated; + static s32 m_VUsed; + + static s16* m_pIndex; + static s16* m_pINext; + static s16 m_IOffset; + static s32 m_IAllocated; + static s32 m_IUsed; + + static s32 m_Instances; + + // + // Debug variables. + // +public: + s32 m_ShowWire; + s32 m_ShowNodes; + s32 m_ShowBlocks; + s32 m_ShowBaseA; + s32 m_ShowBaseB; + s32 m_ShowLightMap; + s32 m_ShowEnvMap; + s32 m_ShowFog; + +//------------------------------------------------------------------------------ +// Private Functions +// +private: + + // + // Functions in FluidSupport.cpp: + // + s32 GetAcceptBit ( s32 Level, s32 IndexX, s32 IndexY ) const; + s32 GetRejectBit ( s32 Level, s32 IndexX, s32 IndexY ) const; + void SetAcceptBit ( s32 Level, s32 IndexX, s32 IndexY, s32 Value ); + void SetRejectBit ( s32 Level, s32 IndexX, s32 IndexY, s32 Value ); + void BuildLowerMasks ( void ); + void RebuildMasks ( void ); + void FloodFill ( u8* pGrid, s32 x, s32 y, s32 SizeX, s32 SizeY ); + + // + // Functions in FluidQuadTree.cpp + // + void RunQuadTree ( bool& EyeSubmerged ); + + f32 ComputeLOD ( f32 Distance ); + byte ComputeClipBits ( f32 X, f32 Y, f32 Z ); + + void ProcessNode ( node& Node ); + void ProcessBlock ( block& Block ); + + void ProcessBlockLODHigh ( block& Block ); + void ProcessBlockLODMorph ( block& Block ); + void ProcessBlockLODTrans ( block& Block ); + void ProcessBlockLODLow ( block& Block ); + + void SetupVert ( f32 X, f32 Y, f32 Distance, vertex* pV ); + + void InterpolateVerts ( vertex* pV0, + vertex* pV1, + vertex* pV2, + vertex* pV3, + vertex* pV4, + f32 LOD0, + f32 LOD4 ); + + void InterpolateVert ( vertex* pV0, + vertex* pV1, + vertex* pV2, + f32 LOD ); + +// void BuildFogTable ( void ); + + void ReleaseVertexMemory ( void ); + vertex* AcquireVertices ( s32 Count ); + void AddTriangleIndices ( s16 I1, s16 I2, s16 I3 ); +}; + +//============================================================================== +#endif // FLUID_HPP +//============================================================================== diff --git a/terrain/fluidQuadTree.cc b/terrain/fluidQuadTree.cc new file mode 100644 index 0000000..5bec610 --- /dev/null +++ b/terrain/fluidQuadTree.cc @@ -0,0 +1,1060 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/Fluid.h" +#include "dgl/dgl.h" + +//============================================================================== +// VARIABLES +//============================================================================== + +fluid::vertex* fluid::m_pVertex = NULL; +s32 fluid::m_VAllocated = 0; +s32 fluid::m_VUsed = 0; + +s16* fluid::m_pIndex = NULL; +s16* fluid::m_pINext = NULL; +s16 fluid::m_IOffset = 0; +s32 fluid::m_IAllocated = 0; +s32 fluid::m_IUsed = 0; + +static f32 sSurfaceAtEye; +static f32 sFogZ; +//atic f32 sFogTable[64]; +//atic f32 sFogAccessFactor; + +//============================================================================== +// FUNCTIONS +//============================================================================== + +#define L0 150.0f +#define L1 75.0f + +f32 fluid::ComputeLOD( f32 Distance ) +{ + f32 LOD; + if( Distance > L0 ) return( 0.0f ); + if( Distance < L1 ) return( 1.0f ); + LOD = (L0 - Distance) / (L0-L1); + return( LOD ); +} + +#undef L0 +#undef L1 + +//============================================================================== +// Frustrum clip planes: 0=T 1=B 2=L 3=R 4=N 5=F + +byte fluid::ComputeClipBits( f32 X, f32 Y, f32 Z ) +{ + byte Result = 0x00; + s32 i; + + for( i = 0; i < 6; i++ ) + if( ((X * m_Plane[i].A) + (Y * m_Plane[i].B) + (Z * m_Plane[i].C)) < -m_Plane[i].D ) + Result |= (1< m_VAllocated ) + { + m_VAllocated += 100; + m_pVertex = (vertex*)REALLOC( m_pVertex, m_VAllocated * sizeof(vertex) ); + ASSERT( m_pVertex ); + } + + pResult = m_pVertex + m_VUsed; + m_IOffset = m_VUsed; + m_VUsed += Count; + + return( pResult ); +} + +//============================================================================== + +void fluid::AddTriangleIndices( s16 I1, s16 I2, s16 I3 ) +{ + if( m_IAllocated == 0 ) + { + m_IAllocated = 100; + m_IUsed = 0; + m_pIndex = (s16*)MALLOC( m_IAllocated * sizeof(s16) ); + m_pINext = m_pIndex; + ASSERT( m_pIndex ); + } + + if( m_IUsed+3 > m_IAllocated ) + { + s32 Next = m_pINext - m_pIndex; + m_IAllocated += 100; + m_pIndex = (s16*)REALLOC( m_pIndex, m_IAllocated * sizeof(s16) ); + m_pINext = m_pIndex + Next; + ASSERT( m_pIndex ); + } + + m_IUsed += 3; + + *m_pINext = m_IOffset + I1; m_pINext++; + *m_pINext = m_IOffset + I2; m_pINext++; + *m_pINext = m_IOffset + I3; m_pINext++; +} + +//============================================================================== + +void fluid::ReleaseVertexMemory( void ) +{ + if( m_pVertex ) + { + FREE( m_pVertex ); + m_pVertex = NULL; + m_VAllocated = 0; + m_VUsed = 0; + } + + if( m_pIndex ) + { + FREE( m_pIndex ); + m_pINext = NULL; + m_pIndex = NULL; + m_IAllocated = 0; + m_IUsed = 0; + } +} + +//============================================================================== +/* +void fluid::BuildFogTable( void ) +{ + f32 Distance; + f32 Delta; + f32 Z; + s32 i; + + Z = m_SurfaceZ - m_Eye.Z; + Delta = m_VisibleDistance / 64.0f; + Distance = 0.0f; + + for( i = 0; i < 64; i++ ) + { + Distance += Delta; + sFogTable[i] = m_pFogFn( Distance, Z ); + } + + // And, create a multiplier we will use to access the table. + sFogAccessFactor = 64.0f / m_VisibleDistance; +} +*/ +//============================================================================== + +void fluid::RunQuadTree( bool& EyeSubmerged ) +{ + node Stack[ 32 ]; + s32 Top = -1; + s32 i, j; + s32 I, J; + s32 BlocksPerRep = m_HighResMode ? 64 : 32; + + // Build a fog sample table. +// BuildFogTable(); + + // We will use a single 'altitude' for all fog (regardless of waves). + sFogZ = m_SurfaceZ - m_Eye.Z; + + // Determine where fluid surface is directly above (or above) the eye. + sSurfaceAtEye = m_SurfaceZ + (SINE( (m_Eye.X * 0.05f) + m_Seconds ) + + SINE( (m_Eye.Y * 0.05f) + m_Seconds )) * m_WaveFactor; + + // Set a flag to be returned to the caller. + EyeSubmerged = (m_Eye.Z < sSurfaceAtEye) && IsFluidAtXY( m_Eye.X, m_Eye.Y ); + + // Prepare to accumulate vertices and indices. + m_VUsed = 0; + m_IUsed = 0; + m_pINext = m_pIndex; + + // Determine what "rep" of the terrain we are in. + I = (s32)(m_Eye.X / 2048.0f); + J = (s32)(m_Eye.Y / 2048.0f); + if( m_Eye.X < 0.0f ) I--; + if( m_Eye.Y < 0.0f ) J--; + + // Push 9 reps of the fluid onto the stack. + for( j = J-1; j <= J+1; j++ ) + for( i = I-1; i <= I+1; i++ ) + { + // New stack node. + Top++; + + // Set up the level based masking. + Stack[Top].Level = 0; + Stack[Top].MaskIndexX = 0; + Stack[Top].MaskIndexY = 0; + + // Set up "block" coordinates and factor in the rep of the terrain. + Stack[Top].BlockX0 = (i * BlocksPerRep); + Stack[Top].BlockY0 = (j * BlocksPerRep); + + // Note that the MaskIndexX and the BlockX0 are similar, but not the + // same. The BlockX0 is affected by the rep of the terrain, whereas + // MaskIndexX is not. Note that neither takes into consideration + // the offset of the fluid within a terrain rep. + + // Compute world coordinates. Account for fluid offset within terrain. + Stack[Top].X0 = (Stack[Top].BlockX0 * m_Step[4]) + (m_SquareX0 * 8.0f); + Stack[Top].Y0 = (Stack[Top].BlockY0 * m_Step[4]) + (m_SquareY0 * 8.0f); + Stack[Top].X1 = Stack[Top].X0 + 2048.0f; + Stack[Top].Y1 = Stack[Top].Y0 + 2048.0f; + + // Compute clip bits for each corner. + Stack[Top].ClipBits[0] = ComputeClipBits( Stack[Top].X0, Stack[Top].Y0, m_SurfaceZ ); + Stack[Top].ClipBits[1] = ComputeClipBits( Stack[Top].X1, Stack[Top].Y0, m_SurfaceZ ); + Stack[Top].ClipBits[2] = ComputeClipBits( Stack[Top].X0, Stack[Top].Y1, m_SurfaceZ ); + Stack[Top].ClipBits[3] = ComputeClipBits( Stack[Top].X1, Stack[Top].Y1, m_SurfaceZ ); + } + + // + // Let the "recursion" begin. + // + + while( Top >= 0 ) + { + node& Node = Stack[Top]; + + ASSERT( Top < 32 ); + ASSERT( Node.MaskIndexX < (1 << Node.Level) ); + ASSERT( Node.MaskIndexY < (1 << Node.Level) ); + + // Attempt to trivially reject the whole node. See if the clip bits + // indicate that all 4 verts are outside one of the frustrum planes. + if( Node.ClipBits[0] & + Node.ClipBits[1] & + Node.ClipBits[2] & + Node.ClipBits[3] ) + { + Top--; + continue; + } + + // Attempt to trivially reject the whole node based on the mask bits. + if( GetRejectBit( Node.Level, Node.MaskIndexX, Node.MaskIndexY ) ) + { + Top--; + continue; + } + + // If we have reached level 5 (the node is a single block), then + // accept it. + if( Node.Level == 5 ) + { + ProcessNode( Node ); + Top--; + continue; + } + + // Attempt to trivially accept the whole node. It must pass BOTH the + // clip bits and the accept mask. + if( ((Node.ClipBits[0] | Node.ClipBits[1] | + Node.ClipBits[2] | Node.ClipBits[3]) == 0x00) && + GetAcceptBit( Node.Level, Node.MaskIndexX, Node.MaskIndexY ) ) + { + ProcessNode( Node ); + Top--; + continue; + } + + // If we are here, then we need to subdivide the node. We will break + // the node into 4 equal parts. + // + // *-----* *--@--* + // | | | 2| 3| + // | Top | becomes... @--@--@ + // | | | 0| 1| + // *-----* *--@--* + // + { + node& Node0 = Stack[Top+0]; + node& Node1 = Stack[Top+1]; + node& Node2 = Stack[Top+2]; + node& Node3 = Stack[Top+3]; + s32 Blocks; + + // We're going to copy the original node onto all of the new nodes + // in a moment. Before we do that, we might as well make some + // changes to the original node that will need to be made in all + // of the new nodes anyways. (Node and Node0 are the same thing.) + + Node.Level += 1; + Node.MaskIndexX = Node.MaskIndexX << 1; + Node.MaskIndexY = Node.MaskIndexY << 1; + + // The number of blocks across a new node is needed repeatedly + // below. Go ahead and compute it now. + + Blocks = (32 >> Node.Level); + + // Now, just copy everything from the original node into all of + // the new nodes. + + Node3 = Node2 = Node1 = Node0; + + // Update Node 0. + + Node0.X1 = Node0.X0 + (Blocks * m_Step[4]); + Node0.Y1 = Node0.Y0 + (Blocks * m_Step[4]); + Node0.ClipBits[1] = ComputeClipBits( Node0.X1, Node0.Y0, m_SurfaceZ ); + Node0.ClipBits[2] = ComputeClipBits( Node0.X0, Node0.Y1, m_SurfaceZ ); + Node0.ClipBits[3] = ComputeClipBits( Node0.X1, Node0.Y1, m_SurfaceZ ); + + // Update Node 1. Take advantage of updated Node 0. + + Node1.MaskIndexX += 1; + Node1.BlockX0 += Blocks; + Node1.X0 = Node0.X1; + Node1.Y1 = Node0.Y1; + Node1.ClipBits[0] = Node0.ClipBits[1]; + Node1.ClipBits[2] = Node0.ClipBits[3]; + Node1.ClipBits[3] = ComputeClipBits( Node1.X1, Node1.Y1, m_SurfaceZ ); + + // Update Node 2. Take advantage of updated Node 0. + + Node2.MaskIndexY += 1; + Node2.BlockY0 += Blocks; + Node2.X1 = Node0.X1; + Node2.Y0 = Node0.Y1; + Node2.ClipBits[0] = Node0.ClipBits[2]; + Node2.ClipBits[1] = Node0.ClipBits[3]; + Node2.ClipBits[3] = ComputeClipBits( Node2.X1, Node2.Y1, m_SurfaceZ ); + + // Update Node 3. Take advantage of updated Nodes 0, 1, and 2. + + Node3.MaskIndexX += 1; + Node3.MaskIndexY += 1; + Node3.BlockX0 += Blocks; + Node3.BlockY0 += Blocks; + Node3.X0 = Node0.X1; + Node3.Y0 = Node0.Y1; + Node3.ClipBits[0] = Node0.ClipBits[3]; + Node3.ClipBits[1] = Node1.ClipBits[3]; + Node3.ClipBits[2] = Node2.ClipBits[3]; + + Top += 3; + } + } +} + +//============================================================================== + +void fluid::ProcessNode( node& Node ) +{ + // Quick debug render. + if( m_ShowNodes ) + { + glDisable ( GL_TEXTURE_2D ); + glDisable ( GL_DEPTH_TEST ); + glEnable ( GL_BLEND ); + glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE ); + glColor4f ( 0.5f, 0.5f, 0.0f, 0.5f ); + glBegin ( GL_QUADS ); + glVertex3f ( Node.X0, Node.Y0, m_SurfaceZ ); + glVertex3f ( Node.X1, Node.Y0, m_SurfaceZ ); + glVertex3f ( Node.X1, Node.Y1, m_SurfaceZ ); + glVertex3f ( Node.X0, Node.Y1, m_SurfaceZ ); + glEnd (); + glEnable ( GL_DEPTH_TEST ); + glColor4f ( 0.8f, 0.8f, 0.3f, 0.8f ); + glBegin ( GL_QUADS ); + glVertex3f ( Node.X0, Node.Y0, m_SurfaceZ ); + glVertex3f ( Node.X1, Node.Y0, m_SurfaceZ ); + glVertex3f ( Node.X1, Node.Y1, m_SurfaceZ ); + glVertex3f ( Node.X0, Node.Y1, m_SurfaceZ ); + glEnd (); + } + + // For each node, render all the blocks it contains. + + s32 X, Y; + s32 Blocks; + + Blocks = (32 >> Node.Level); + + for( Y = 0; Y < Blocks; Y++ ) + for( X = 0; X < Blocks; X++ ) + { + block Block; + + Block.X0 = Node.X0 + X * m_Step[4]; + Block.Y0 = Node.Y0 + Y * m_Step[4]; + Block.X1 = Block.X0 + m_Step[4]; + Block.Y1 = Block.Y0 + m_Step[4]; + + Block.Distance[0] = DISTANCE( Block.X0, Block.Y0, m_SurfaceZ ); + Block.Distance[1] = DISTANCE( Block.X1, Block.Y0, m_SurfaceZ ); + Block.Distance[2] = DISTANCE( Block.X0, Block.Y1, m_SurfaceZ ); + Block.Distance[3] = DISTANCE( Block.X1, Block.Y1, m_SurfaceZ ); + + Block.LOD[0] = ComputeLOD( Block.Distance[0] ); + Block.LOD[1] = ComputeLOD( Block.Distance[1] ); + Block.LOD[2] = ComputeLOD( Block.Distance[2] ); + Block.LOD[3] = ComputeLOD( Block.Distance[3] ); + + ProcessBlock( Block ); + } +} + +//============================================================================== + +void fluid::ProcessBlock( block& Block ) +{ + // Quick debug render. + if( m_ShowBlocks ) + { + glDisable ( GL_TEXTURE_2D ); + glDisable ( GL_DEPTH_TEST ); + glEnable ( GL_BLEND ); + glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE ); + glColor4f ( 0.0f, 0.0f, 0.0f, 0.5f ); + glBegin ( GL_QUADS ); + glVertex3f ( Block.X0, Block.Y0, m_SurfaceZ ); + glVertex3f ( Block.X1, Block.Y0, m_SurfaceZ ); + glVertex3f ( Block.X1, Block.Y1, m_SurfaceZ ); + glVertex3f ( Block.X0, Block.Y1, m_SurfaceZ ); + glEnd (); + glEnable ( GL_DEPTH_TEST ); + glColor4f ( 0.3f, 0.3f, 0.3f, 0.8f ); + glBegin ( GL_QUADS ); + glVertex3f ( Block.X0, Block.Y0, m_SurfaceZ ); + glVertex3f ( Block.X1, Block.Y0, m_SurfaceZ ); + glVertex3f ( Block.X1, Block.Y1, m_SurfaceZ ); + glVertex3f ( Block.X0, Block.Y1, m_SurfaceZ ); + glEnd (); + } + + if( (Block.LOD[0] > 0.0f) && + (Block.LOD[1] > 0.0f) && + (Block.LOD[2] > 0.0f) && + (Block.LOD[3] > 0.0f) ) + { + if( (Block.LOD[0] == 1.0f) && + (Block.LOD[1] == 1.0f) && + (Block.LOD[2] == 1.0f) && + (Block.LOD[3] == 1.0f) ) + { + // LOD on ALL verts is ONE! + ProcessBlockLODHigh( Block ); + } + else + { + // LOD on ALL verts is NON-ZERO, but it is NOT ONE on ALL verts! + ProcessBlockLODMorph( Block ); + } + } + else + { + if( (Block.LOD[0] > 0.0f) || + (Block.LOD[1] > 0.0f) || + (Block.LOD[2] > 0.0f) || + (Block.LOD[3] > 0.0f) ) + { + // LOD on SOME verts is NON-ZERO, and on SOME it is ZERO! + ProcessBlockLODTrans( Block ); + } + else + { + // LOD on ALL verts is ZERO! + ProcessBlockLODLow( Block ); + } + } +} + +//============================================================================== + +static s16 HighLODIndices[96] = +{ + 0, 1, 6, 6, 5, 0, // +Y + 2, 7, 6, 6, 1, 2, // 20--21--22--23--24 + 2, 3, 8, 8, 7, 2, // | \ | / | \ | / | + 4, 9, 8, 8, 3, 4, // 15--16--17--18--19 + // | / | \ | / | \ | + 6, 11, 10, 10, 5, 6, // 10--11--12--13--14 + 6, 7, 12, 12, 11, 6, // | \ | / | \ | / | + 8, 13, 12, 12, 7, 8, // 05--06--07--08--09 + 8, 9, 14, 14, 13, 8, // | / | \ | / | \ | + // [00]-01--02--03--04 +X + 10, 11, 16, 16, 15, 10, // + 12, 17, 16, 16, 11, 12, + 12, 13, 18, 18, 17, 12, + 14, 19, 18, 18, 13, 14, + + 16, 21, 20, 20, 15, 16, + 16, 17, 22, 22, 21, 16, + 18, 23, 22, 22, 17, 18, + 18, 19, 24, 24, 23, 18, +}; + +//============================================================================== + +void fluid::ProcessBlockLODHigh( block& Block ) +{ + vertex* pV; + s32 X, Y, i; + f32 x, y; + s16* pIndex = HighLODIndices; + f32 Distance; + + // Get vetices. + pV = AcquireVertices( 25 ); + + // Put data in the corner verts. + SetupVert( Block.X0, Block.Y0, Block.Distance[0], pV + 0 ); + SetupVert( Block.X1, Block.Y0, Block.Distance[1], pV + 4 ); + SetupVert( Block.X0, Block.Y1, Block.Distance[2], pV + 20 ); + SetupVert( Block.X1, Block.Y1, Block.Distance[3], pV + 24 ); + + // Put data in all verts but the corners. + i = 0; + for( Y = 0; Y < 5; Y++ ) + for( X = 0; X < 5; X++ ) + { + if( (i != 0) && (i != 4) && (i != 20) && (i != 24) ) + { + x = Block.X0 + m_Step[X]; + y = Block.Y0 + m_Step[Y]; + Distance = DISTANCE( x, y, m_SurfaceZ ); + SetupVert( x, y, Distance, pV+i ); + } + + i++; + } + + // Add the triangle indices. + for( i = 0; i < 32; i++ ) + { + AddTriangleIndices( *(pIndex+0), *(pIndex+1), *(pIndex+2) ); + pIndex += 3; + } +} + +//============================================================================== + +void fluid::ProcessBlockLODMorph( block& Block ) +{ + vertex* pV; + s32 X, Y, i; + f32 x, y; + s16* pIndex = HighLODIndices; + f32 Distance; + f32 LOD; + f32 MiddleX = Block.X0 + m_Step[2]; + f32 MiddleY = Block.Y0 + m_Step[2]; + f32 MiddleD = DISTANCE( MiddleX, MiddleY, m_SurfaceZ ); + + // Get vetices. + pV = AcquireVertices( 25 ); + + // Put data in the corner verts. + SetupVert( Block.X0, Block.Y0, Block.Distance[0], pV + 0 ); + SetupVert( Block.X1, Block.Y0, Block.Distance[1], pV + 4 ); + SetupVert( Block.X0, Block.Y1, Block.Distance[2], pV + 20 ); + SetupVert( Block.X1, Block.Y1, Block.Distance[3], pV + 24 ); + + // Put data in all verts but the corners. + i = 0; + for( Y = 0; Y < 5; Y++ ) + for( X = 0; X < 5; X++ ) + { + if( (i != 0) && (i != 4) && (i != 20) && (i != 24) ) + { + x = Block.X0 + m_Step[X]; + y = Block.Y0 + m_Step[Y]; + Distance = DISTANCE( x, y, m_SurfaceZ ); + SetupVert( x, y, Distance, pV+i ); + } + + i++; + } + + // Get LOD values for the center AND the centers of each edge. + LOD = Block.LOD[1] < Block.LOD[0] ? Block.LOD[1] : Block.LOD[0]; + LOD = Block.LOD[2] < LOD ? Block.LOD[2] : LOD; + LOD = Block.LOD[3] < LOD ? Block.LOD[3] : LOD; + + // Interpolate all of the edges. + InterpolateVerts( pV + 0, pV + 1, pV + 2, pV + 3, pV + 4, Block.LOD[0], Block.LOD[1] ); + InterpolateVerts( pV + 4, pV + 9, pV + 14, pV + 19, pV + 24, Block.LOD[1], Block.LOD[3] ); + InterpolateVerts( pV + 24, pV + 23, pV + 22, pV + 21, pV + 20, Block.LOD[3], Block.LOD[2] ); + InterpolateVerts( pV + 20, pV + 15, pV + 10, pV + 5, pV + 0, Block.LOD[2], Block.LOD[0] ); + + // Now interpolate from the center out to get the remaining 8 verts. + InterpolateVert( pV + 12, pV + 6, pV + 0, LOD ); + InterpolateVert( pV + 12, pV + 7, pV + 2, LOD ); + InterpolateVert( pV + 12, pV + 8, pV + 4, LOD ); + InterpolateVert( pV + 12, pV + 13, pV + 14, LOD ); + InterpolateVert( pV + 12, pV + 18, pV + 24, LOD ); + InterpolateVert( pV + 12, pV + 17, pV + 22, LOD ); + InterpolateVert( pV + 12, pV + 16, pV + 20, LOD ); + InterpolateVert( pV + 12, pV + 11, pV + 10, LOD ); + + // Add the triangle indices. + for( i = 0; i < 32; i++ ) + { + AddTriangleIndices( *(pIndex+0), *(pIndex+1), *(pIndex+2) ); + pIndex += 3; + } +} + +//============================================================================== + +void fluid::ProcessBlockLODTrans( block& Block ) +{ + vertex* pV; + s32 Verts; + s32 I; + f32 X, Y; + f32 Distance; + f32 MiddleX = Block.X0 + m_Step[2]; + f32 MiddleY = Block.Y0 + m_Step[2]; + f32 MiddleD = DISTANCE( MiddleX, MiddleY, m_SurfaceZ ); + + // Determine how many verts we need. + Verts = 5; + if( (Block.LOD[0] > 0.0f) && (Block.LOD[0] > 0.0f) ) Verts += 3; + if( (Block.LOD[1] > 0.0f) && (Block.LOD[3] > 0.0f) ) Verts += 3; + if( (Block.LOD[3] > 0.0f) && (Block.LOD[2] > 0.0f) ) Verts += 3; + if( (Block.LOD[2] > 0.0f) && (Block.LOD[0] > 0.0f) ) Verts += 3; + + // Get vetices. + pV = AcquireVertices( Verts ); + + // Build the corner and center vertices. + SetupVert( Block.X0, Block.Y0, Block.Distance[0], pV+0 ); + SetupVert( Block.X1, Block.Y0, Block.Distance[1], pV+1 ); + SetupVert( Block.X0, Block.Y1, Block.Distance[2], pV+2 ); + SetupVert( Block.X1, Block.Y1, Block.Distance[3], pV+3 ); + SetupVert( MiddleX, MiddleY, MiddleD, pV+4 ); + + I = 5; + + //------------------------------------------------------ + + if( (Block.LOD[0] > 0.0f) && (Block.LOD[1] > 0.0f) ) + { + X = Block.X0 + m_Step[1]; + Distance = DISTANCE( X, Block.Y0, m_SurfaceZ ); + SetupVert( X, Block.Y0, Distance, pV+I+0 ); + + X = Block.X0 + m_Step[2]; + Distance = DISTANCE( X, Block.Y0, m_SurfaceZ ); + SetupVert( X, Block.Y0, Distance, pV+I+1 ); + + X = Block.X0 + m_Step[3]; + Distance = DISTANCE( X, Block.Y0, m_SurfaceZ ); + SetupVert( X, Block.Y0, Distance, pV+I+2 ); + + InterpolateVerts( pV+0, pV+I+0, pV+I+1, pV+I+2, pV+1, Block.LOD[0], Block.LOD[1] ); + AddTriangleIndices( 4, 0, I+0 ); + AddTriangleIndices( 4, I+0, I+1 ); + AddTriangleIndices( 4, I+1, I+2 ); + AddTriangleIndices( 4, I+2, 1 ); + I += 3; + } + else + { + AddTriangleIndices( 4, 0, 1 ); + } + + //------------------------------------------------------ + + if( (Block.LOD[1] > 0.0f) && (Block.LOD[3] > 0.0f) ) + { + Y = Block.Y0 + m_Step[1]; + Distance = DISTANCE( Block.X1, Y, m_SurfaceZ ); + SetupVert( Block.X1, Y, Distance, pV+I+0 ); + + Y = Block.Y0 + m_Step[2]; + Distance = DISTANCE( Block.X1, Y, m_SurfaceZ ); + SetupVert( Block.X1, Y, Distance, pV+I+1 ); + + Y = Block.Y0 + m_Step[3]; + Distance = DISTANCE( Block.X1, Y, m_SurfaceZ ); + SetupVert( Block.X1, Y, Distance, pV+I+2 ); + + InterpolateVerts( pV+1, pV+I+0, pV+I+1, pV+I+2, pV+3, Block.LOD[1], Block.LOD[3] ); + AddTriangleIndices( 4, 1, I+0 ); + AddTriangleIndices( 4, I+0, I+1 ); + AddTriangleIndices( 4, I+1, I+2 ); + AddTriangleIndices( 4, I+2, 3 ); + I += 3; + } + else + { + AddTriangleIndices( 4, 1, 3 ); + } + + //------------------------------------------------------ + + if( (Block.LOD[3] > 0.0f) && (Block.LOD[2] > 0.0f) ) + { + X = Block.X1 - m_Step[1]; + Distance = DISTANCE( X, Block.Y1, m_SurfaceZ ); + SetupVert( X, Block.Y1, Distance, pV+I+0 ); + + X = Block.X1 - m_Step[2]; + Distance = DISTANCE( X, Block.Y1, m_SurfaceZ ); + SetupVert( X, Block.Y1, Distance, pV+I+1 ); + + X = Block.X1 - m_Step[3]; + Distance = DISTANCE( X, Block.Y1, m_SurfaceZ ); + SetupVert( X, Block.Y1, Distance, pV+I+2 ); + + InterpolateVerts( pV+3, pV+I+0, pV+I+1, pV+I+2, pV+2, Block.LOD[3], Block.LOD[2] ); + AddTriangleIndices( 4, 3, I+0 ); + AddTriangleIndices( 4, I+0, I+1 ); + AddTriangleIndices( 4, I+1, I+2 ); + AddTriangleIndices( 4, I+2, 2 ); + I += 3; + } + else + { + AddTriangleIndices( 4, 3, 2 ); + } + + //------------------------------------------------------ + + if( (Block.LOD[2] > 0.0f) && (Block.LOD[0] > 0.0f) ) + { + Y = Block.Y1 - m_Step[1]; + Distance = DISTANCE( Block.X0, Y, m_SurfaceZ ); + SetupVert( Block.X0, Y, Distance, pV+I+0 ); + + Y = Block.Y1 - m_Step[2]; + Distance = DISTANCE( Block.X0, Y, m_SurfaceZ ); + SetupVert( Block.X0, Y, Distance, pV+I+1 ); + + Y = Block.Y1 - m_Step[3]; + Distance = DISTANCE( Block.X0, Y, m_SurfaceZ ); + SetupVert( Block.X0, Y, Distance, pV+I+2 ); + + InterpolateVerts( pV+2, pV+I+0, pV+I+1, pV+I+2, pV+0, Block.LOD[2], Block.LOD[0] ); + AddTriangleIndices( 4, 2, I+0 ); + AddTriangleIndices( 4, I+0, I+1 ); + AddTriangleIndices( 4, I+1, I+2 ); + AddTriangleIndices( 4, I+2, 0 ); + I += 3; + } + else + { + AddTriangleIndices( 4, 2, 0 ); + } + + //------------------------------------------------------ +} + +//============================================================================== +// +// [2]-----[3] +// |\ 3 /| +// | \ / | +// |4 [4] 2| Rendered as four triangles. +// | / \ | +// |/ 1 \| +// [0]-----[1] +// + +void fluid::ProcessBlockLODLow( block& Block ) +{ + vertex* pV; + f32 MiddleX = Block.X0 + m_Step[2]; + f32 MiddleY = Block.Y0 + m_Step[2]; + f32 MiddleD = DISTANCE( MiddleX, MiddleY, m_SurfaceZ ); + + // Get vetices. + pV = AcquireVertices( 5 ); + + // Put data in the verts. + SetupVert( Block.X0, Block.Y0, Block.Distance[0], pV+0 ); + SetupVert( Block.X1, Block.Y0, Block.Distance[1], pV+1 ); + SetupVert( Block.X0, Block.Y1, Block.Distance[2], pV+2 ); + SetupVert( Block.X1, Block.Y1, Block.Distance[3], pV+3 ); + SetupVert( MiddleX, MiddleY, MiddleD, pV+4 ); + +//--Attempt to reject if all fogged. + + // Add the triangle indices. + AddTriangleIndices( 0, 1, 4 ); + AddTriangleIndices( 1, 3, 4 ); + AddTriangleIndices( 3, 2, 4 ); + AddTriangleIndices( 2, 0, 4 ); + + // Quick debug render. +#if 0 + if( 0 ) + { + glDisable ( GL_TEXTURE_2D ); + glBegin ( GL_QUADS ); + glColor4f ( Block.LOD[0], 1.0f, Block.LOD[3], 1.0f ); + glVertex3f ( pV[0].XYZ.X, pV[0].XYZ.Y, pV[0].XYZ.Z ); + glVertex3f ( pV[1].XYZ.X, pV[1].XYZ.Y, pV[1].XYZ.Z ); + glVertex3f ( pV[4].XYZ.X, pV[4].XYZ.Y, pV[4].XYZ.Z ); + glVertex3f ( pV[2].XYZ.X, pV[2].XYZ.Y, pV[2].XYZ.Z ); + glVertex3f ( pV[4].XYZ.X, pV[4].XYZ.Y, pV[4].XYZ.Z ); + glVertex3f ( pV[1].XYZ.X, pV[1].XYZ.Y, pV[1].XYZ.Z ); + glVertex3f ( pV[3].XYZ.X, pV[3].XYZ.Y, pV[3].XYZ.Z ); + glVertex3f ( pV[2].XYZ.X, pV[2].XYZ.Y, pV[2].XYZ.Z ); + glEnd (); + } +#endif +} + +//============================================================================== + +void fluid::SetupVert( f32 X, f32 Y, f32 Distance, vertex* pV ) +{ + f32 Z; + + // + // Compute a Z value. + // + { + f32 WarpZ, WarpD0, WarpD1; + f32 WarpFactor; + f32 Delta; + + Delta = SINE( (X * 0.05f) + m_Seconds ) + + SINE( (Y * 0.05f) + m_Seconds ); + + Z = m_SurfaceZ + Delta * m_WaveFactor; + + // When the camera is close to the fluid surface, warp the surface to + // prevent the fluid from intersecting the camera glass. + + WarpD0 = m_Step[2]; + if( Distance < WarpD0 ) + { + WarpD1 = m_Step[1]; + + if( m_Eye.Z > sSurfaceAtEye ) + { + WarpZ = m_Eye.Z - 0.25f; + if( Z > WarpZ ) + { + if( Distance < WarpD1 ) + { + // Close enough to completely warp the vert. + Z = WarpZ; + } + else + { + // Range is between WarpD0 and WarpD1. Interpolate. + WarpFactor = ((WarpD0 - Distance) / WarpD1); + Z = (WarpZ * WarpFactor) + (Z * (1.0f-WarpFactor)); + } + } + } + else + { + WarpZ = m_Eye.Z + 0.25f; + if( Z < WarpZ ) + { + if( Distance < WarpD1 ) + { + // Close enough to completely warp the vert. + Z = WarpZ; + } + else + { + // Range is between WarpD0 and WarpD1. Interpolate. + WarpFactor = ((WarpD0 - Distance) / WarpD1); + Z = (WarpZ * WarpFactor) + (Z * (1.0f-WarpFactor)); + } + } + } + } + } + + // These fields are easy! + + pV->XYZ.X = X; + pV->XYZ.Y = Y; + pV->XYZ.Z = Z; + pV->RGBA4.R = m_FogColor.R; + pV->RGBA4.G = m_FogColor.G; + pV->RGBA4.B = m_FogColor.B; + pV->RGBA4.A = m_pFogFn( Distance, sFogZ ); +/* + pV->RGBA4.A = Distance < m_VisibleDistance + ? sFogTable[ (s32)(Distance * sFogAccessFactor) ] + : 1.0f; +*/ + + // + // Compute the UV for the environment map / specular pass. + // + { + xyz Vector; + + // The reflection vector is the "eye to point" vector, but with a positive + // Z value. + + Vector.X = X - m_Eye.X; + Vector.Y = Y - m_Eye.Y; + Vector.Z = Z - m_Eye.Z; + + if( Vector.Z < 0.0f ) + Vector.Z = -Vector.Z; + + if( Vector.Z < 0.001f ) + Vector.Z = 0.001f; + + // The UV values are simply the XY values from the normalized reflection + // vector. + + if( Distance < 0.001f ) + { + pV->UV3.U = 0.0f; + pV->UV3.V = 0.0f; + } + else + { + // The standard UV reflection mapping tends to over emphasize the + // outer edges of the environment map. Try to "adjust" the values + // to get more efficient use of the map. Oh, and normalize, too. + + f32 Value = (Distance - Vector.Z) / (Distance * Distance); + + pV->UV3.U = Vector.X * Value; + pV->UV3.V = Vector.Y * Value; + } + + // Convert from [+1,-1] to [0,1]. + pV->UV3.U = pV->UV3.U * 0.5f + 0.5f; + pV->UV3.V = pV->UV3.V * 0.5f + 0.5f; + + // Now, based on the XY, tweak the UV in some liquid manner. + + volatile static f32 Q1 = 150.00f; + volatile static f32 Q2 = 2.00f; + volatile static f32 Q3 = 0.01f; + + f32 A1 = COSINE( ((X / Q1) + (m_Seconds / Q2)) * 6.00f ); + f32 A2 = SINE ( ((Y / Q1) + (m_Seconds / Q2)) * 6.28f ); + + pV->UV3.U += A1 * Q3; + pV->UV3.V += A2 * Q3; + + f32 Swing = (A1+A2) * 0.15f + 0.5f; + + pV->RGBA1a.A = ((1.0f-Swing) * m_Opacity) / (1.0f - (Swing * m_Opacity)); + pV->RGBA1b.A = Swing * m_Opacity; + + pV->RGBA1a.R = pV->RGBA1a.G = pV->RGBA1a.B = 1.0f; + pV->RGBA1b.R = pV->RGBA1b.G = pV->RGBA1b.B = 1.0f; + } +} + +//============================================================================== + +void fluid::InterpolateVerts( vertex* pV0, + vertex* pV1, + vertex* pV2, + vertex* pV3, + vertex* pV4, + f32 LOD0, + f32 LOD4 ) +{ + f32 DeltaLOD = (LOD4 - LOD0 ) * 0.25f; + f32 DeltaZ = (pV4->XYZ.Z - pV0->XYZ.Z) * 0.25f; + f32 DeltaU = (pV4->UV3.U - pV0->UV3.U) * 0.25f; + f32 DeltaV = (pV4->UV3.V - pV0->UV3.V) * 0.25f; + + f32 Z = pV0->XYZ.Z; + uv UV = pV0->UV3; + + f32 LODa = LOD0; + f32 LODb = 1.0f; + + //-------------------------------------------------------------------------- + // Compute interpolated low detail data. + Z += DeltaZ; + UV.U += DeltaU; + UV.V += DeltaV; + LODa += DeltaLOD; + LODb = 1.0f - LODa; + + // Interpolate based on LOD. + pV1->UV3.U = (pV1->UV3.U * LODa) + (UV.U * LODb); + pV1->UV3.V = (pV1->UV3.V * LODa) + (UV.V * LODb); + pV1->XYZ.Z = (pV1->XYZ.Z * LODa) + (Z * LODb); + //-------------------------------------------------------------------------- + + //-------------------------------------------------------------------------- + // Compute complete interpolated low detail data. + Z += DeltaZ; + UV.U += DeltaU; + UV.V += DeltaV; + LODa += DeltaLOD; + LODb = 1.0f - LODa; + + // Interpolate based on LOD. + pV2->UV3.U = (pV2->UV3.U * LODa) + (UV.U * LODb); + pV2->UV3.V = (pV2->UV3.V * LODa) + (UV.V * LODb); + pV2->XYZ.Z = (pV2->XYZ.Z * LODa) + (Z * LODb); + //-------------------------------------------------------------------------- + + //-------------------------------------------------------------------------- + // Compute complete interpolated low detail data. + Z += DeltaZ; + UV.U += DeltaU; + UV.V += DeltaV; + LODa += DeltaLOD; + LODb = 1.0f - LODa; + + // Interpolate based on LOD. + pV3->UV3.U = (pV3->UV3.U * LODa) + (UV.U * LODb); + pV3->UV3.V = (pV3->UV3.V * LODa) + (UV.V * LODb); + pV3->XYZ.Z = (pV3->XYZ.Z * LODa) + (Z * LODb); + //-------------------------------------------------------------------------- +} + +//============================================================================== + +void fluid::InterpolateVert( vertex* pV0, + vertex* pV1, + vertex* pV2, + f32 LODa ) +{ + f32 Z = (pV2->XYZ.Z + pV0->XYZ.Z) * 0.5f; + f32 U = (pV2->UV3.U + pV0->UV3.U) * 0.5f; + f32 V = (pV2->UV3.V + pV0->UV3.V) * 0.5f; + f32 LODb = 1.0f - LODa; + + //-------------------------------------------------------------------------- + // Interpolate based on LOD. + pV1->UV3.U = (pV1->UV3.U * LODa) + (U * LODb); + pV1->UV3.V = (pV1->UV3.V * LODa) + (V * LODb); + pV1->XYZ.Z = (pV1->XYZ.Z * LODa) + (Z * LODb); + //-------------------------------------------------------------------------- +} + +//============================================================================== + + diff --git a/terrain/fluidRender.cc b/terrain/fluidRender.cc new file mode 100644 index 0000000..b8abade --- /dev/null +++ b/terrain/fluidRender.cc @@ -0,0 +1,249 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/Fluid.h" +#include "dgl/dgl.h" + +//============================================================================== +// FUNCTIONS +//============================================================================== + +void fluid::Render( bool& EyeSubmerged ) +{ + f32 BaseDriftX, BaseDriftY; + + f32 Q1 = 1.0f / 48.0f; // This just looks good. + f32 Q2 = 1.0f / 2048.0f; // This is the size of the terrain. + + f32 SBase[] = { Q1, 0, 0, 0 }; + f32 TBase[] = { 0, Q1, 0, 0 }; + + f32 SLMap[] = { Q2, 0, 0, 0 }; + f32 TLMap[] = { 0, Q2, 0, 0 }; + + // Several attributes in the fluid vary over time. Get a definitive time + // reading now to be used throughout this render pass. + m_Seconds = SECONDS - m_BaseSeconds; + + // Based on the view frustrum, accumulate the list of triangles that + // comprise the fluid surface for this render pass. + RunQuadTree( EyeSubmerged ); + + // Quick debug render. +#if 0 + if( 0 ) + { + s32 i; + for( i = 0; i < m_IUsed / 3; i++ ) + { + glDisable ( GL_TEXTURE_2D ); + glEnable ( GL_BLEND ); + glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glBegin ( GL_TRIANGLES ); + glColor4f ( 0.0f, 1.0f, 0.0f, 0.5f ); + glVertex3f ( m_pVertex[m_pIndex[i*3+0]].XYZ.X, m_pVertex[m_pIndex[i*3+0]].XYZ.Y, m_pVertex[m_pIndex[i*3+0]].XYZ.Z ); + glVertex3f ( m_pVertex[m_pIndex[i*3+1]].XYZ.X, m_pVertex[m_pIndex[i*3+1]].XYZ.Y, m_pVertex[m_pIndex[i*3+1]].XYZ.Z ); + glVertex3f ( m_pVertex[m_pIndex[i*3+2]].XYZ.X, m_pVertex[m_pIndex[i*3+2]].XYZ.Y, m_pVertex[m_pIndex[i*3+2]].XYZ.Z ); + glEnd (); + } + } +#endif + + // + // We need to compute some time dependant values before we start rendering. + // + + // Base texture drift. + { + #define BASE_DRIFT_CYCLE_TIME 8.0f + #define BASE_DRIFT_RATE 0.02f + #define BASE_DRIFT_SCALAR 0.03f + + f32 Phase = FMOD( m_Seconds * (TWO_PI/BASE_DRIFT_CYCLE_TIME), TWO_PI ); + + BaseDriftX = m_Seconds * BASE_DRIFT_RATE; + BaseDriftY = COSINE( Phase ) * BASE_DRIFT_SCALAR; + } + + //-------------------------------------------------------------------------- + //-- + //-- Let's rock. + //-- + + //-------------------------------------------------------------------------- + //-- Debug - wire + + if( m_ShowWire ) + { + glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE ); + glEnableClientState ( GL_VERTEX_ARRAY ); + glVertexPointer ( 3, GL_FLOAT, sizeof(vertex), &(m_pVertex[0].XYZ) ); + glDisable ( GL_TEXTURE_2D ); + glDisable ( GL_DEPTH_TEST ); + glEnable ( GL_BLEND ); + glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glColor4f ( 0.5f, 0.5f, 0.5f, 0.5f ); + glDrawElements ( GL_TRIANGLES, m_IUsed, GL_UNSIGNED_SHORT, m_pIndex ); + glDisable ( GL_BLEND ); + glEnable ( GL_DEPTH_TEST ); + glColor4f ( 1.0f, 1.0f, 0.0f, 1.0f ); + glDrawElements ( GL_TRIANGLES, m_IUsed, GL_UNSIGNED_SHORT, m_pIndex ); + glPolygonMode ( GL_FRONT_AND_BACK, GL_FILL ); + } + + //-------------------------------------------------------------------------- + //-- Initializations for Phase 1 - base textures + + glPolygonMode ( GL_FRONT_AND_BACK, GL_FILL ); + + glEnableClientState ( GL_VERTEX_ARRAY ); + glVertexPointer ( 3, GL_FLOAT, sizeof(vertex), &(m_pVertex[0].XYZ) ); + + glEnable ( GL_TEXTURE_2D ); + glEnable ( GL_BLEND ); + + glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + glMatrixMode ( GL_TEXTURE ); + glPushMatrix (); + glLoadIdentity (); + + glEnableClientState ( GL_COLOR_ARRAY ); + + glEnable ( GL_TEXTURE_GEN_S ); + glEnable ( GL_TEXTURE_GEN_T ); + glTexGeni ( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + glTexGeni ( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + glTexGenfv ( GL_S, GL_OBJECT_PLANE, SBase ); + glTexGenfv ( GL_T, GL_OBJECT_PLANE, TBase ); + + glBindTexture ( GL_TEXTURE_2D, m_BaseTexture.getGLName() ); + + //-------------------------------------------------------------------------- + //-- Initializations for Phase 1a - first base texture + + glRotatef ( 30.0f, 0.0f, 0.0f, 1.0f ); + glColorPointer ( 4, GL_FLOAT, sizeof(vertex), &(m_pVertex[0].RGBA1a) ); + + glDepthMask(GL_FALSE); + + //-------------------------------------------------------------------------- + //-- Render Phase 1a - first base texture + + if( m_ShowBaseA ) + { + glDrawElements ( GL_TRIANGLES, m_IUsed, GL_UNSIGNED_SHORT, m_pIndex ); + } + + //-------------------------------------------------------------------------- + //-- Initializations for Phase 1b - second base texture + + glRotatef ( 30.0f, 0.0f, 0.0f, 1.0f ); + glTranslatef ( BaseDriftX, BaseDriftY, 0.0f ); + glColorPointer ( 4, GL_FLOAT, sizeof(vertex), &(m_pVertex[0].RGBA1b) ); + + //-------------------------------------------------------------------------- + //-- Render Phase 1b - first base texture + + if( m_ShowBaseB ) + { + glDrawElements ( GL_TRIANGLES, m_IUsed, GL_UNSIGNED_SHORT, m_pIndex ); + } + + //-------------------------------------------------------------------------- + //-- Cleanup from Phase 1 - base textures + + glDisableClientState( GL_COLOR_ARRAY ); + glPopMatrix (); + + //-------------------------------------------------------------------------- + //-- Initializations for Phase 2 - light map + + // glColor4f ( 1.0f, 1.0f, 1.0f, 1.0f ); + // glBindTexture ( GL_TEXTURE_2D, m_LightMapTexture.getGLName() ); + + // glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + // glBlendFunc ( GL_DST_COLOR, GL_ZERO ); + + // glMatrixMode ( GL_TEXTURE ); + // glPushMatrix (); + // glLoadIdentity (); + + // glTexGenfv ( GL_S, GL_OBJECT_PLANE, SLMap ); + // glTexGenfv ( GL_T, GL_OBJECT_PLANE, TLMap ); + + //-------------------------------------------------------------------------- + //-- Render Phase 2 - light map + + // if( m_ShowLightMap ) + // { + // glDrawElements ( GL_TRIANGLES, m_IUsed, GL_UNSIGNED_SHORT, m_pIndex ); + // } + + //-------------------------------------------------------------------------- + //-- Cleanup from Phase 2 - light map + + // glPopMatrix (); + glMatrixMode ( GL_MODELVIEW ); + + glDisable ( GL_TEXTURE_GEN_S ); + glDisable ( GL_TEXTURE_GEN_T ); + + //-------------------------------------------------------------------------- + //-- Initializations for Phase 3 - environment map + + glEnableClientState ( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer ( 2, GL_FLOAT, sizeof(vertex), &(m_pVertex[0].UV3) ); + + glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glBlendFunc ( GL_SRC_ALPHA, GL_ONE ); + + glColor4f ( 1.0f, 1.0f, 1.0f, m_EnvMapIntensity ); + glBindTexture ( GL_TEXTURE_2D, m_EnvMapTexture.getGLName() ); + + //-------------------------------------------------------------------------- + //-- Render Phase 3 - environment map / specular + + if( m_ShowEnvMap ) + { + glDrawElements ( GL_TRIANGLES, m_IUsed, GL_UNSIGNED_SHORT, m_pIndex ); + } + + //-------------------------------------------------------------------------- + //-- Initializations for Phase 4 - fog + + glDisable ( GL_TEXTURE_2D ); + glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + glEnableClientState ( GL_COLOR_ARRAY ); + glColorPointer ( 4, GL_FLOAT, sizeof(vertex), &(m_pVertex[0].RGBA4) ); + + glDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + //-------------------------------------------------------------------------- + //-- Render Phase 4 - fog + + if( m_ShowFog ) + { + glDrawElements ( GL_TRIANGLES, m_IUsed, GL_UNSIGNED_SHORT, m_pIndex ); + } + + //-------------------------------------------------------------------------- + //-- Cleanup from all Phases + + glDepthMask(GL_TRUE); + + glDisable ( GL_BLEND ); + glDisableClientState( GL_VERTEX_ARRAY ); + glDisableClientState( GL_COLOR_ARRAY ); + + //-------------------------------------------------------------------------- + //-- We're done with the rendering. +} + +//============================================================================== diff --git a/terrain/fluidSupport.cc b/terrain/fluidSupport.cc new file mode 100644 index 0000000..afbaa67 --- /dev/null +++ b/terrain/fluidSupport.cc @@ -0,0 +1,607 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/Fluid.h" +#include "dgl/dgl.h" +#include + +//============================================================================== +// VARIABLES +//============================================================================== + +s32 fluid::m_Instances = 0; +s32 fluid::m_MaskOffset[6] = { 0, 1, 2, 4, 12, 44 }; + +//============================================================================== +// FUNCTIONS +//============================================================================== + +fluid::fluid( void ) +{ + m_Instances += 1; + + // Fill out fields with a stable, if useless, state. + m_SquareX0 = 0; + m_SquareY0 = 0; + m_SquaresInX = 4; + m_SquaresInY = 4; + m_BlocksInX = 1; + m_BlocksInY = 1; + m_HighResMode = 1; + m_RemoveWetEdges = 0; + m_SurfaceZ = 0.0f; + m_WaveAmplitude = 0.0f; + m_Opacity = 0.0f; + m_EnvMapIntensity = 1.0f; + m_BaseSeconds = SECONDS; + m_pTerrain = NULL; + + // Set values on the debug flags. + m_ShowWire = 0; + m_ShowBlocks = 0; + m_ShowNodes = 0; + m_ShowBaseA = 1; + m_ShowBaseB = 1; + m_ShowLightMap = 1; + m_ShowEnvMap = 1; + m_ShowFog = 1; +} + +//============================================================================== + +fluid::~fluid() +{ + m_Instances -= 1; + + if( m_Instances == 0 ) + { + ReleaseVertexMemory(); + } +} + +//============================================================================== + +s32 fluid::GetRejectBit( s32 Level, s32 IndexX, s32 IndexY ) const +{ + s32 BitNumber = (IndexY << Level) + IndexX; + s32 ByteNumber = m_MaskOffset[Level] + (BitNumber >> 3); + byte Byte = m_RejectMask[ ByteNumber ]; + s32 Bit = (Byte >> (BitNumber & 0x07)) & 0x01; + + return( Bit ); +} + +//============================================================================== + +s32 fluid::GetAcceptBit( s32 Level, s32 IndexX, s32 IndexY ) const +{ + if( Level == 5 ) + return( !GetRejectBit( 5, IndexX, IndexY ) ); + + s32 BitNumber = (IndexY << Level) + IndexX; + s32 ByteNumber = m_MaskOffset[Level] + (BitNumber >> 3); + byte Byte = m_AcceptMask[ ByteNumber ]; + s32 Bit = (Byte >> (BitNumber & 0x07)) & 0x01; + + return( Bit ); +} + +//============================================================================== + +void fluid::SetRejectBit( s32 Level, s32 IndexX, s32 IndexY, s32 Value ) +{ + s32 BitNumber = (IndexY << Level) + IndexX; + s32 ByteNumber = m_MaskOffset[Level] + (BitNumber >> 3); + byte Byte = 1 << (BitNumber & 0x07); + + if( Value ) m_RejectMask[ ByteNumber ] |= Byte; + else m_RejectMask[ ByteNumber ] &= ~Byte; +} + +//============================================================================== + +void fluid::SetAcceptBit( s32 Level, s32 IndexX, s32 IndexY, s32 Value ) +{ + if( Level == 5 ) + SetRejectBit( 5, IndexX, IndexY, !Value ); + + s32 BitNumber = (IndexY << Level) + IndexX; + s32 ByteNumber = m_MaskOffset[Level] + (BitNumber >> 3); + byte Byte = 1 << (BitNumber & 0x07); + + if( Value ) m_AcceptMask[ ByteNumber ] |= Byte; + else m_AcceptMask[ ByteNumber ] &= ~Byte; +} + +//============================================================================== + +void fluid::BuildLowerMasks( void ) +{ + s32 X, Y; + + // Initially, at all non-level-5 mask levels, we want to both accept and + // reject everything. Then, we'll go back and correct it all. + + MEMSET( m_AcceptMask, 0xFF, 1+1+2+8+32 ); + MEMSET( m_RejectMask, 0xFF, 1+1+2+8+32 ); + + // Now, for each entry in the level 5 mask, push its implications down + // through all the other levels. + + for( Y = 0; Y < 32; Y++ ) + for( X = 0; X < 32; X++ ) + { + if( GetRejectBit( 5, X, Y ) ) + { + // The block is set for reject. + // We cannot accept it on the lower levels. + SetAcceptBit( 4, X>>1, Y>>1, 0 ); + SetAcceptBit( 3, X>>2, Y>>2, 0 ); + SetAcceptBit( 2, X>>3, Y>>3, 0 ); + SetAcceptBit( 1, X>>4, Y>>4, 0 ); + SetAcceptBit( 0, X>>5, Y>>5, 0 ); + } + else + { + // The block is set for accept. + // We cannot reject it on the lower levels. + SetRejectBit( 4, X>>1, Y>>1, 0 ); + SetRejectBit( 3, X>>2, Y>>2, 0 ); + SetRejectBit( 2, X>>3, Y>>3, 0 ); + SetRejectBit( 1, X>>4, Y>>4, 0 ); + SetRejectBit( 0, X>>5, Y>>5, 0 ); + } + } +} + +//============================================================================== + +struct fill_segment +{ + s32 Y; + s32 X0, X1; + s32 DY; // +1 or -1 +}; + +#define STACK_SIZE 50 + +//------------------------------------------------------------------------------ +#define PUSH(y,x0,x1,dy) \ +{ \ + if( ((y+(dy)) >= 0) && ((y+(dy)) < SizeY) ) \ + { \ + if( Count < STACK_SIZE ) \ + { \ + Stack[Count].Y = y; \ + Stack[Count].X0 = x0; \ + Stack[Count].X1 = x1; \ + Stack[Count].DY = dy; \ + Count++; \ + } \ + else \ + { \ + FloodFill( pGrid, x0, y, SizeX, SizeY ); \ + } \ + } \ +} +//------------------------------------------------------------------------------ +#define POP(y,x0,x1,dy) \ +{ \ + Count--; \ + Y = Stack[Count].Y + Stack[Count].DY; \ + X0 = Stack[Count].X0; \ + X1 = Stack[Count].X1; \ + DY = Stack[Count].DY; \ +} +//------------------------------------------------------------------------------ + +void fluid::FloodFill( u8* pGrid, s32 x, s32 y, s32 SizeX, s32 SizeY ) +{ + fill_segment Stack[ STACK_SIZE ]; + s32 Count = 0; + s32 X, Y, X0, X1, DY, Left; + u8* p; + + if( !pGrid[ (y*SizeX) + x ] ) + return; + + PUSH( y, x, x, 1 ); // Needed in a few cases. + PUSH( y+1, x, x, -1 ); // Primary seed point. Popped first. + + while( Count > 0 ) + { + POP( Y, X0, X1, DY ); + + // A span in y=(Y-DY) for X0<=x<=X1 was previously filled. Now consider + // adjacent entries in y=Y. + + // Clear going towards decreasing X. + X = X0; + p = &pGrid[ (Y*SizeX) + X ]; + while( (X >= 0) && *p ) + { + *p = 0; + X--; + p--; + } + + if( X >= X0 ) + goto Skip; + + Left = X + 1; + if( Left < X0 ) + PUSH( Y, Left, X0-1, -DY ) + + X = X0 + 1; + + do + { + // Clear going towards increasing X. + p = &pGrid[ (Y*SizeX) + X ]; + while( (X < SizeX) && *p ) + { + *p = 0; + X++; + p++; + } + + PUSH( Y, Left, X-1, DY ); + if( X > X1+1 ) + PUSH( Y, X1+1, X-1, -DY ); + +Skip: X++; + p = &pGrid[ (Y*SizeX) + X ]; + while( (X <= X1) && !(*p) ) + { + X++; + p++; + } + Left = X; + } + while( X <= X1 ); + } +} + +//============================================================================== + +void fluid::RebuildMasks( void ) +{ + u8* pGrid; + u8* pG; // Traveling grid pointer + s32 GridSize; + s32 X, Y; + s32 x, y; + s32 i; // Index + + s32 SquaresPerBlock = m_HighResMode ? 4 : 8; + s32 ShiftPerBlock = m_HighResMode ? 2 : 3; + + // + // We need a grid to classify all terrain data points which are within the + // fluid area. We will use this grid to reject underground blocks, reject + // "wet" edges if requested, and to dry fill where requested. + // + + GridSize = (m_SquaresInX+1) * (m_SquaresInY+1); + pGrid = (u8*)MALLOC( GridSize ); + + // Classify each point as above or below ground. + + if( m_pTerrain ) + { + u16 FluidLevel = (u16)((m_SurfaceZ + (m_WaveAmplitude/2.0f)) * 32.0f); + + pG = pGrid; + for( Y = 0; Y < m_SquaresInY+1; Y++ ) + for( X = 0; X < m_SquaresInX+1; X++ ) + { + i = (((m_SquareY0+Y) & 255) << 8) + ((m_SquareX0+X) & 255); + *pG = (u8)(FluidLevel > m_pTerrain[i]); + pG++; + } + } + + // If requested, "dry up" all edges which "protrude" into the air. + + if( m_RemoveWetEdges && m_pTerrain ) + { + for( X = 0; X < m_SquaresInX+1; X++ ) + { + FloodFill( pGrid, X, 0 , m_SquaresInX+1, m_SquaresInY+1 ); + FloodFill( pGrid, X, m_SquaresInY, m_SquaresInX+1, m_SquaresInY+1 ); + } + + for( Y = 0; Y < m_SquaresInY+1; Y++ ) + { + FloodFill( pGrid, 0 , Y, m_SquaresInX+1, m_SquaresInY+1 ); + FloodFill( pGrid, m_SquaresInX, Y, m_SquaresInX+1, m_SquaresInY+1 ); + } + } + + // Time to build the masks. First, reject everything! We will work on the + // level 5 reject mask. (Level 5 is the most detailed mask, and there is no + // accept mask at that level.) + + MEMSET( m_RejectMask + m_MaskOffset[5], 0xFF, 128 ); + + // Any block which as useful points left in the grid is to be kept. + + for( Y = 0; Y < m_BlocksInY; Y++ ) + for( X = 0; X < m_BlocksInX; X++ ) + { + s32 Accept = 0; + + // If ANY point in the block is acceptable, then accept the whole block. + + for( y = 0; y <= SquaresPerBlock; y++ ) + for( x = 0; x <= SquaresPerBlock; x++ ) + { + s32 GridX = (X << ShiftPerBlock) + x; + s32 GridY = (Y << ShiftPerBlock) + y; + + i = (GridY * (m_SquaresInX+1)) + GridX; + + if( pGrid[i] ) + { + Accept = 1; + goto BailOut; + } + } + + BailOut: + + if( Accept ) + SetRejectBit( 5, X, Y, 0 ); + } + + FREE( pGrid ); + BuildLowerMasks(); +} + +//============================================================================== + +void fluid::SetInfo( f32& X0, + f32& Y0, + f32& SizeX, + f32& SizeY, + f32 SurfaceZ, + f32 WaveAmplitude, + f32& Opacity, + f32& EnvMapIntensity, + s32 RemoveWetEdges ) +{ + // Constrain the range of parameters. + if( Opacity > 1.0f ) Opacity = 1.0f; + if( Opacity < 0.0f ) Opacity = 0.0f; + if( EnvMapIntensity > 1.0f ) EnvMapIntensity = 1.0f; + if( EnvMapIntensity < 0.0f ) EnvMapIntensity = 0.0f; + + // Get the easy stuff first. + m_SurfaceZ = SurfaceZ; + m_WaveAmplitude = WaveAmplitude; + m_RemoveWetEdges = RemoveWetEdges; + m_Opacity = Opacity; + m_EnvMapIntensity = EnvMapIntensity; + + m_WaveFactor = m_WaveAmplitude * 0.25f; + + // Place the "min" corner. + m_SquareX0 = (s32)((X0 / 8.0f) + 0.5f); + m_SquareY0 = (s32)((Y0 / 8.0f) + 0.5f); + + // Constrain the range of values. + if( m_SquareX0 < 0.0f ) m_SquareX0 = 0; + if( m_SquareY0 < 0.0f ) m_SquareY0 = 0; + if( m_SquareX0 > 2040.0f ) m_SquareX0 = 2040; + if( m_SquareY0 > 2040.0f ) m_SquareY0 = 2040; + + // Decide on the size of the block. + m_SquaresInX = (s32)((SizeX / 8.0f) + 0.5f); + m_SquaresInY = (s32)((SizeY / 8.0f) + 0.5f); + + // + // If the fluid is meant to cover less than 1/4 of a terrain rep, then we + // will enter "High Resolution Mode". The fluid will cover the same area + // as specified, but it will have twice the vertex resolution in each + // direction. So, on "small lakes", we get better memory utilization, + // better terrain fitting, and so on. + // + + if( (m_SquaresInX <= 128) && (m_SquaresInY <= 128) ) + { + // High Resolution Mode! + m_HighResMode = 1; + + // A Block is now 4x4 terrain squares. And the number of squares in + // the fluid must be a multiple of 4 so we get whole blocks. + + m_SquaresInX = (m_SquaresInX + 3) & ~0x03; + m_SquaresInY = (m_SquaresInY + 3) & ~0x03; + + // Constrain the range of values. + if( m_SquaresInX <= 0 ) m_SquaresInX = 4; + if( m_SquaresInY <= 0 ) m_SquaresInY = 4; + + m_BlocksInX = m_SquaresInX >> 2; + m_BlocksInY = m_SquaresInY >> 2; + } + else + { + // Normal resolution. + m_HighResMode = 0; + + // A Block is now 8x8 terrain squares. And the number of squares in + // the fluid must be a multiple of 8 so we get whole blocks. + + m_SquaresInX = (m_SquaresInX + 7) & ~0x07; + m_SquaresInY = (m_SquaresInY + 7) & ~0x07; + + // Constrain the range of values. + if( m_SquaresInX > 256 ) m_SquaresInX = 256; + if( m_SquaresInY > 256 ) m_SquaresInY = 256; + if( m_SquaresInX <= 0 ) m_SquaresInX = 8; + if( m_SquaresInY <= 0 ) m_SquaresInY = 8; + + m_BlocksInX = m_SquaresInX >> 3; + m_BlocksInY = m_SquaresInY >> 3; + } + + // Set some internal values for later usage. + if( m_HighResMode ) + { + m_Step[4] = 32.0f; + m_Step[3] = 24.0f; + m_Step[2] = 16.0f; + m_Step[1] = 8.0f; + m_Step[0] = 0.0f; + } + else + { + m_Step[4] = 64.0f; + m_Step[3] = 48.0f; + m_Step[2] = 32.0f; + m_Step[1] = 16.0f; + m_Step[0] = 0.0f; + } + + // Set values back into parameters for caller. + X0 = m_SquareX0 * 8.0f; + Y0 = m_SquareY0 * 8.0f; + SizeX = m_SquaresInX * 8.0f; + SizeY = m_SquaresInY * 8.0f; + + // Recompute our masks. + RebuildMasks(); +} + +//============================================================================== + +void fluid::SetTerrainData( u16* pTerrainData ) +{ + m_pTerrain = pTerrainData; + RebuildMasks(); +} + +//============================================================================== + +void fluid::SetEyePosition( f32 X, f32 Y, f32 Z ) +{ + m_Eye.X = X; + m_Eye.Y = Y; + m_Eye.Z = Z; +} + +//============================================================================== +// Frustrum clip planes: 0=T 1=B 2=L 3=R 4=N 5=F + +void fluid::SetFrustrumPlanes( f32* pFrustrumPlanes ) +{ + f32 BackOff = m_WaveAmplitude * 0.5f; + + m_Plane[0].A = pFrustrumPlanes[ 0]; + m_Plane[0].B = pFrustrumPlanes[ 1]; + m_Plane[0].C = pFrustrumPlanes[ 2]; + m_Plane[0].D = pFrustrumPlanes[ 3] + BackOff; + + m_Plane[1].A = pFrustrumPlanes[ 4]; + m_Plane[1].B = pFrustrumPlanes[ 5]; + m_Plane[1].C = pFrustrumPlanes[ 6]; + m_Plane[1].D = pFrustrumPlanes[ 7] + BackOff; + + m_Plane[2].A = pFrustrumPlanes[ 8]; + m_Plane[2].B = pFrustrumPlanes[ 9]; + m_Plane[2].C = pFrustrumPlanes[10]; + m_Plane[2].D = pFrustrumPlanes[11] + BackOff; + + m_Plane[3].A = pFrustrumPlanes[12]; + m_Plane[3].B = pFrustrumPlanes[13]; + m_Plane[3].C = pFrustrumPlanes[14]; + m_Plane[3].D = pFrustrumPlanes[15] + BackOff; + + m_Plane[4].A = pFrustrumPlanes[16]; + m_Plane[4].B = pFrustrumPlanes[17]; + m_Plane[4].C = pFrustrumPlanes[18]; + m_Plane[4].D = pFrustrumPlanes[19] + BackOff; + + m_Plane[5].A = pFrustrumPlanes[20]; + m_Plane[5].B = pFrustrumPlanes[21]; + m_Plane[5].C = pFrustrumPlanes[22]; + m_Plane[5].D = pFrustrumPlanes[23]; +} + +//============================================================================== + +void fluid::SetTextures( TextureHandle Base, + TextureHandle EnvMap ) +{ + m_BaseTexture = Base; + m_EnvMapTexture = EnvMap; +} + +//============================================================================== + +void fluid::SetLightMapTexture( TextureHandle LightMapTexture ) +{ + m_LightMapTexture = LightMapTexture; +} + +//============================================================================== + +void fluid::SetFogParameters( f32 R, f32 G, f32 B, f32 VisibleDistance ) +{ + m_FogColor.R = R; + m_FogColor.G = G; + m_FogColor.B = B; + m_FogColor.A = 1.0f; + m_VisibleDistance = VisibleDistance; +} + +//============================================================================== + +void fluid::SetFogFn( compute_fog_fn* pFogFn ) +{ + m_pFogFn = pFogFn; +} + +//============================================================================== + +s32 fluid::IsFluidAtXY( f32 X, f32 Y ) const +{ + s32 x, y; + s32 ShiftPerBlock = m_HighResMode ? 5 : 6; + + // + // Convert fluid space (X,Y) to block (x,y). Use the accept mask. Note + // that the masks are anchored at the min point rather than terrain (0,0). + // + + // Convert to integer. + x = (s32)X; + y = (s32)Y; + + // Compensate for min point offset. + x -= (m_SquareX0 << 3); + y -= (m_SquareY0 << 3); + + // We only want points in the range [0,2048). + x &= 2047; + y &= 2047; + + // Convert to block coordinate. + x >>= ShiftPerBlock; + y >>= ShiftPerBlock; + + // When we are in high res mode, there are "virtually" 64 blocks per terrain + // along a particular axis. But only the first 32 of them are used. + if( x >= 32 ) return( 0 ); + if( y >= 32 ) return( 0 ); + + // Consult mask. + return( GetAcceptBit( 5, x, y ) ); +} + +//============================================================================== diff --git a/terrain/sky.cc b/terrain/sky.cc new file mode 100644 index 0000000..9e9c972 --- /dev/null +++ b/terrain/sky.cc @@ -0,0 +1,1788 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/Sky.h" +#include "math/mMath.h" +#include "dgl/dgl.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/bitStream.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sceneState.h" +#include "terrain/terrData.h" +#include "sim/sceneObject.h" +#include "game/particleEngine.h" +#include "math/mathIO.h" +#include "scenegraph/windingClipper.h" +#include "platform/profiler.h" + +#define HORIZON 0.0f +#define OFFSET_HEIGHT 60.0f +#define FOG_BAN_DETAIL 8 +#define RAD (2 * M_PI) +#define DML_DIR "textures/" + + +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); + + mVisibleDistance = 250; + mFogDistance = 250; + mSkyTexturesOn = true; + mRenderBoxBottom = false; + mNumFogVolumes = 0; + mFogLine = 0.0f; + mRealFog = 0; + mFogColor.set(128, 128, 128); + mSolidFillColor.set(0.0, 1.0, 0,0); + mWindVelocity.set(1.f, 0.f, 0.f); + mWindDir.set(0.f, 0.f); + mEffectPrecip = false; + + mLastVisDisMod = -1; + + for(U32 j = 0; j < MaxFogVolumes; j++) + { + mFogVolumes[j].visibleDistance = -1; + mFogVolumes[j].percentage = 1.0f; + + mFogVolumes[j].color.red = mFogColor.red; + mFogVolumes[j].color.green = mFogColor.green; + mFogVolumes[j].color.blue = mFogColor.blue; + } + + for(S32 i = 0; i < MAX_NUM_LAYERS; ++i) + { + mCloudText[i] = ""; + mCloudSpeed[i] = 0.0001 * (1 + (i * 1)); + mCloudHeight[i] = 0; + } + + mStormCloudData.state = isDone; + mStormCloudData.speed = 0.0f; + mStormCloudData.time = 0.0f; + + mStormFogData.time = 0.0f; + mStormFogData.current = 0; + mStormFogData.lastTime = 0; + mStormFogData.startTime = 0; + mStormFogData.endPercentage = -1.0f; + for(S32 x = 0; x < MaxFogVolumes; ++x) + { + mStormFogData.volume[x].state = isDone; + mStormFogData.volume[x].speed = 0.0f; + mStormFogData.volume[x].endPercentage = 1.0f; + } + mFogTime = 0.0f; + mFogVolume = -1; + mStormCloudsOn = true; + mStormFogOn = false; + mSetFog = true; + + mLastForce16Bit = false; + mLastForcePaletted = false; +} + +//------------------------------------------------------------------------------ +Sky::~Sky() +{ + +} + +//--------------------------------------------------------------------------- +bool Sky::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.min.set(-1e9, -1e9, -1e9); + mObjBox.max.set( 1e9, 1e9, 1e9); + resetWorldBox(); + for(mNumFogVolumes = 0; mNumFogVolumes < MaxFogVolumes; mNumFogVolumes++) + if(mFogVolumes[mNumFogVolumes].visibleDistance == -1 || mFogVolumes[mNumFogVolumes].visibleDistance == 0) + break; + + if(isClientObject()) + initSkyData(); + else + { + setWindVelocity(mWindVelocity); + assignName("Sky"); + } + + addToScene(); + setVisibility(); + 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.05f); + mCloudLayer[i].setSpeed(mWindDir * mCloudSpeed[i]); + mCloudLayer[i].setPoints(); + } + setVisibility(); +} + +//--------------------------------------------------------------------------- +void Sky::setVisibility() +{ + if(mSceneManager) + { + extern bool sgForce16BitTexture; + extern bool sgForcePalettedTexture; + mRealFogColor.red = S32(mCeil(mFogColor.red * 255.0f)); + mRealFogColor.green = S32(mCeil(mFogColor.green * 255.0f)); + mRealFogColor.blue = S32(mCeil(mFogColor.blue * 255.0f)); + mRealFogColor.alpha = 255; + if(sgForce16BitTexture) + { + U8 temp = (mRealFogColor.red >> 4) & 0xF; + mRealFogColor.red = (temp << 4) | temp; + + temp = (mRealFogColor.green >> 4) & 0xF; + mRealFogColor.green = (temp << 4) | temp; + + temp = (mRealFogColor.blue >> 4) & 0xF; + mRealFogColor.blue = (temp << 4) | temp; + + mSceneManager->setFogColor((ColorF)mRealFogColor); + } + else + mSceneManager->setFogColor(mFogColor); + + mRealSkyColor.red = S32(mSolidFillColor.red * 255.0f); + mRealSkyColor.green = S32(mSolidFillColor.green * 255.0f); + mRealSkyColor.blue = S32(mSolidFillColor.blue * 255.0f); + + mSceneManager->setFogDistance(mFogDistance); + mSceneManager->setVisibleDistance(mVisibleDistance); + mSceneManager->setFogVolumes(mNumFogVolumes, mFogVolumes); + mLastForce16Bit = sgForce16BitTexture; + mLastForcePaletted = sgForcePalettedTexture; + } +} + +//--------------------------------------------------------------------------- +void Sky::setWindVelocity(const Point3F & vel) +{ + mWindVelocity = vel; + ParticleEngine::setWindVelocity(vel); + if(isServerObject()) + setMaskBits(WindMask); +} + +Point3F Sky::getWindVelocity() +{ + return(mWindVelocity); +} + +//--------------------------------------------------------------------------- +void Sky::onRemove() +{ + removeFromScene(); + Parent::onRemove(); +} + +//--------------------------------------------------------------------------- +static void cStormCloudsOn(SimObject *obj, S32, const char **argv) +{ + Sky *ctrl = static_cast(obj); + ctrl->stormCloudsOn(dAtoi(argv[2]), dAtof(argv[3])); +} + +static void cStormFogOn(SimObject *obj, S32, const char **argv) +{ + Sky *ctrl = static_cast(obj); + ctrl->stormFogOn(dAtof(argv[2]), dAtof(argv[3])); +} + +static void cStormRealFog(SimObject *obj, S32, const char **argv) +{ + Sky *ctrl = static_cast(obj); + ctrl->stormRealFog(dAtoi(argv[2]), dAtof(argv[3]), dAtof(argv[4]), dAtof(argv[5])); +} + +static const char * cGetWindVelocity(SimObject *obj, S32, const char**) +{ + Sky * sky = static_cast(obj); + char * retBuf = Con::getReturnBuffer(128); + + Point3F vel = sky->getWindVelocity(); + dSprintf(retBuf, 128, "%f %f %f", vel.x, vel.y, vel.z); + return(retBuf); +} + +static void cSetWindVelocity(SimObject *obj, S32, const char** argv) +{ + Sky * sky = static_cast(obj); + if(sky->isClientObject()) + return; + + Point3F vel(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4])); + sky->setWindVelocity(vel); +} + +static void cStormCloudsShow(SimObject *obj, S32, const char **argv) +{ + Sky *ctrl = static_cast(obj); + ctrl->stormCloudsShow(dAtob(argv[2])); +} + +static void cStormFogShow(SimObject *obj, S32, const char **argv) +{ + Sky *ctrl = static_cast(obj); + ctrl->stormFogShow(dAtob(argv[2])); +} + +//--------------------------------------------------------------------------- +void Sky::consoleInit() +{ +#ifdef 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); + + Con::addCommand("Sky", "stormClouds", cStormCloudsOn, "sky.stormCloudsOn(0 or 1,Time)", 4, 4); + Con::addCommand("Sky", "stormFog", cStormFogOn, "sky.stormFogOn(Percentage <0 to 1>, Time)", 4, 4); + Con::addCommand("Sky", "realFog", cStormRealFog, "sky.realFog(0 or 1 , max, min, speed)", 6, 6); + Con::addCommand("Sky", "getWindVelocity", cGetWindVelocity, "sky.getWindVelocity()", 2, 2); + Con::addCommand("Sky", "setWindVelocity", cSetWindVelocity, "sky.setWindVelocity(x, y, z)", 5, 5); + Con::addCommand("Sky", "stormCloudsShow", cStormCloudsShow, "sky.stormCloudsShow(bool)", 3, 3); + Con::addCommand("Sky", "stormFogShow", cStormFogShow, "sky.stormFogShow(bool)", 3, 3); +} + +//--------------------------------------------------------------------------- +bool Sky::processArguments(S32, const char**) +{ + return true; +} + +//--------------------------------------------------------------------------- +void Sky::initPersistFields() +{ + Parent::initPersistFields(); + addField("cloudText", TypeString, Offset(mCloudText,Sky),MAX_NUM_LAYERS); + 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)); + addField("visibleDistance", TypeF32, Offset(mVisibleDistance, Sky)); + addField("useSkyTextures", TypeBool, Offset(mSkyTexturesOn, Sky)); + addField("renderBottomTexture", TypeBool, Offset(mRenderBoxBottom, Sky)); + addField("SkySolidColor", TypeColorF, Offset(mSolidFillColor, Sky)); + addField("fogDistance", TypeF32, Offset(mFogDistance, Sky)); + addField("fogColor", TypeColorF, Offset(mFogColor, Sky)); + addField("fogVolume1", TypePoint3F, Offset(mFogVolumes[0], Sky)); + addField("fogVolume2", TypePoint3F, Offset(mFogVolumes[1], Sky)); + addField("fogVolume3", TypePoint3F, Offset(mFogVolumes[2], Sky)); + addField("materialList", TypeString, Offset(mMaterialListName,Sky)); + addField("windVelocity", TypePoint3F, Offset(mWindVelocity, Sky)); + addField("windEffectPrecipitation", TypeBool, Offset(mEffectPrecip, Sky)); + addField("fogVolumeColor1", TypeColorF, Offset(mFogVolumes[0].color, Sky)); + addField("fogVolumeColor2", TypeColorF, Offset(mFogVolumes[1].color, Sky)); + addField("fogVolumeColor3", TypeColorF, Offset(mFogVolumes[2].color, Sky)); +} + +//--------------------------------------------------------------------------- +void Sky::unpackUpdate(NetConnection *, BitStream *stream) +{ + if(stream->readFlag()) + { + mMaterialListName = stream->readSTString(); + stream->read(&mFogColor.red); + stream->read(&mFogColor.green); + stream->read(&mFogColor.blue); + stream->read(&mNumFogVolumes); + stream->read(&mSkyTexturesOn); + stream->read(&mRenderBoxBottom); + stream->read(&mSolidFillColor.red); + stream->read(&mSolidFillColor.green); + stream->read(&mSolidFillColor.blue); + stream->read(&mEffectPrecip); + + U32 i; + for(i = 0; i < mNumFogVolumes; i++) + { + stream->read(&mFogVolumes[i].visibleDistance); + stream->read(&mFogVolumes[i].minHeight); + stream->read(&mFogVolumes[i].maxHeight); + + stream->read(&mFogVolumes[i].color.red); + stream->read(&mFogVolumes[i].color.green); + stream->read(&mFogVolumes[i].color.blue); + } + for(i = 0; i < MAX_NUM_LAYERS; i++) + { + mCloudText[i] = stream->readSTString(); + stream->read(&mCloudHeight[i]); + stream->read(&mCloudSpeed[i]); + } + initSkyData(); + Point3F vel; + if(mathRead(*stream, &vel)) + setWindVelocity(vel); + + stream->read(&mFogVolume); + if(stream->readFlag()) + { + U32 state, stormTimeDiff; + stream->read(&mFogPercentage); + stream->read(&mStormFogData.time); + stream->read(&state); + stream->read(&stormTimeDiff); + stream->read(&mStormFogData.endPercentage); + mStormFogData.state = SkyState(state); + if(mStormFogData.time) + { + Con::printf("Server Storm Time: %u",stormTimeDiff); + Con::printf("Get Current Time: %u",Sim::getCurrentTime()); + mStormFogOn = true; + mStormFogData.lastTime = Sim::getCurrentTime() - stormTimeDiff; + Con::printf("READ OFFSET: %f", F32(Sim::getCurrentTime() - mStormFogData.lastTime) / 32.0f); + for(S32 x = 0; x < mNumFogVolumes; ++x) + { + mFogVolumes[x].percentage = mFogPercentage; + mStormFogData.volume[x].endPercentage = mStormFogData.endPercentage; + mStormFogData.volume[x].speed = (mStormFogData.endPercentage - mFogVolumes[x].percentage) / ((mStormFogData.time * 32.0f) / (F32)mNumFogVolumes); + mStormFogData.volume[x].state = mStormFogData.state; + if(mStormFogData.volume[x].state == comingIn) + mStormFogData.current = 0; + else + mStormFogData.current = mNumFogVolumes-1; + } + } + } + } + + if(stream->readFlag()) + stream->read(&mStormCloudsOn); + + if(stream->readFlag()) + { + stream->read(&mStormFogOn); + if(!mStormFogOn) + { + for(S32 x = 0; x < mNumFogVolumes; x++) + mFogVolumes[x].percentage = 0.0f; + mSetFog = true; + } + } + + if(stream->readFlag()) + { + stream->read(&mVisibleDistance); + stream->read(&mFogDistance); + initSkyData(); + } + if(stream->readFlag()) + { + U32 state; + stream->read(&state); + mStormCloudData.state = SkyState(state); + stream->read(&mStormCloudData.time); + + if(mStormCloudData.time > 0.0f) + { + mStormCloudData.speed = ((mRadius * 2) * F32(mNumCloudLayers + 1)) / (mStormCloudData.time * 32.0f); + if(mNumCloudLayers) + mStormCloudData.fadeSpeed = 1.0f / (((mStormCloudData.time * 32.0f) / F32(mNumCloudLayers + 1)) / mNumCloudLayers); + startStorm(); + } + } + if(stream->readFlag()) + { + stream->read(&mFogPercentage); + stream->read(&mStormFogData.time); + stream->read(&mFogVolume); + if(mStormFogData.time) + { + mStormFogData.lastTime = Sim::getCurrentTime(); + startStormFog(); + } + } + if(stream->readFlag()) + { + stream->read(&mRealFog); + stream->read(&mRealFogMax); + stream->read(&mRealFogMin); + stream->read(&mRealFogSpeed); + if(mRealFog) + { + for(S32 x = 0; x < mNumFogVolumes; ++x) + { + mStormFogData.volume[x].lastPercentage = mRealFogMax; + mStormFogData.volume[x].endPercentage = mRealFogMin; + mStormFogData.volume[x].speed = -(((mNumFogVolumes-x)*(mNumFogVolumes-x)) * mRealFogSpeed); + } + F32 save = mStormFogData.volume[0].speed; + mStormFogData.volume[0].speed = mStormFogData.volume[1].speed; + mStormFogData.volume[1].speed = save; + } + } + + if(stream->readFlag()) + { + Point3F vel; + if(mathRead(*stream, &vel)) + setWindVelocity(vel); + } +} + +//--------------------------------------------------------------------------- +U32 Sky::packUpdate(NetConnection *, U32 mask, BitStream *stream) +{ + if(stream->writeFlag(mask & InitMask)) + { + stream->writeString(mMaterialListName); + stream->write(mFogColor.red); + stream->write(mFogColor.green); + stream->write(mFogColor.blue); + stream->write(mNumFogVolumes); + stream->write(mSkyTexturesOn); + stream->write(mRenderBoxBottom); + stream->write(mSolidFillColor.red); + stream->write(mSolidFillColor.green); + stream->write(mSolidFillColor.blue); + stream->write(mEffectPrecip); + + U32 i; + for(i = 0; i < mNumFogVolumes; i++) + { + stream->write(mFogVolumes[i].visibleDistance); + stream->write(mFogVolumes[i].minHeight); + stream->write(mFogVolumes[i].maxHeight); + + stream->write(mFogVolumes[i].color.red); + stream->write(mFogVolumes[i].color.green); + stream->write(mFogVolumes[i].color.blue); + } + for(i = 0; i < MAX_NUM_LAYERS; i++) + { + stream->writeString(mCloudText[i]); + stream->write(mCloudHeight[i]); + stream->write(mCloudSpeed[i]); + } + mathWrite(*stream, mWindVelocity); + + stream->write(mFogVolume); + U32 currentTime = Sim::getCurrentTime(); + U32 stormTimeDiff = currentTime - mStormFogData.startTime; + if(stream->writeFlag(F32(stormTimeDiff) / 1000.0f < mStormFogData.time)) + { + stream->write(mFogPercentage); + stream->write(mStormFogData.time); + stream->write(U32(mStormFogData.state)); + stream->write(stormTimeDiff); + stream->write(mStormFogData.endPercentage); + Con::printf("WRITE OFFSET: %f", F32(stormTimeDiff) / 32.0f); + } + } + + if(stream->writeFlag(mask & StormCloudsOnMask)) + stream->write(mStormCloudsOn); + + if(stream->writeFlag(mask & StormFogOnMask && !(mask & InitMask))) + stream->write(mStormFogOn); + + if(stream->writeFlag(mask & VisibilityMask)) + { + stream->write(mVisibleDistance); + stream->write(mFogDistance); + } + + if(stream->writeFlag(mask & StormCloudMask)) + { + stream->write(U32(mStormCloudData.state)); + stream->write(mStormCloudData.time); + } + + if(stream->writeFlag(mask & StormFogMask && !(mask & InitMask)) ) + { + stream->write(mStormFogData.endPercentage); + stream->write(mStormFogData.time); + stream->write(mFogVolume); + mStormFogData.startTime = Sim::getCurrentTime(); + } + + if(stream->writeFlag(mask & StormRealFogMask)) + { + stream->write(mRealFog); + stream->write(mRealFogMax); + stream->write(mRealFogMin); + stream->write(mRealFogSpeed); + } + if(stream->writeFlag(mask & WindMask)) + mathWrite(*stream, mWindVelocity); + return 0; +} + +//--------------------------------------------------------------------------- +void Sky::inspectPostApply() +{ + for(mNumFogVolumes = 0; mNumFogVolumes < MaxFogVolumes; mNumFogVolumes++) + if(mFogVolumes[mNumFogVolumes].visibleDistance == -1 || mFogVolumes[mNumFogVolumes].visibleDistance == 0) + break; + setMaskBits(InitMask | VisibilityMask); +} + +//--------------------------------------------------------------------------- +void Sky::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + extern bool sgForce16BitTexture; + extern bool sgForcePalettedTexture; + + if(mLastForce16Bit != sgForce16BitTexture || mLastForcePaletted != sgForcePalettedTexture) + setVisibility(); + + RectI viewport; + + dglGetViewport(&viewport); + + // Clear the objects viewport to the fog color. This is something of a dirty trick, + // since we want an identity projection matrix here... + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + + state->setupObjectProjection(this); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + glColor3ub(U8(mRealFogColor.red), U8(mRealFogColor.green), U8(mRealFogColor.blue)); + glBegin(GL_TRIANGLE_FAN); + glVertex3f(-1, -1, 1); + glVertex3f(-1, 1, 1); + glVertex3f( 1, 1, 1); + glVertex3f( 1, -1, 1); + glEnd(); + glPopMatrix(); + + // On input. Finalize the projection matrix... + state->setupObjectProjection(this); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + Point3F camPos = state->getCameraPosition(); + glTranslatef(camPos.x,camPos.y,camPos.z); + + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE); + glDepthMask(GL_FALSE); + + render(state); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + dglSetViewport(viewport); + + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//--------------------------------------------------------------------------- +bool Sky::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 startZone, const bool modifyBaseState) +{ + AssertFatal(modifyBaseState == false, "Error, should never be called with this parameter set"); + AssertFatal(startZone == 0xFFFFFFFF, "Error, startZone should indicate -1"); + + 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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->sortType = SceneRenderImage::Sky; + state->insertRenderImage(image); + } + + 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}; + F32 depthInFog = 0.0f; + Point3F camPos; + S32 index=0; + + if(gClientSceneGraph) + { + F32 currentVisDis = gClientSceneGraph->getVisibleDistanceMod(); + if(mLastVisDisMod != currentVisDis) + { + calcPoints(); + for(S32 i = 0; i < MAX_NUM_LAYERS; ++i) + mCloudLayer[i].setPoints(); + + mLastVisDisMod = currentVisDis; + } + } + if(mNumFogVolumes) + { + camPos = state->getCameraPosition(); + depthInFog = -(camPos.z - mFogLine); + } + // Calculats alpha values and ban heights + if(depthInFog > 0.0f) + calcAlphas_Heights(camPos.z, banHeights, alphaBan, depthInFog); + else // Not in fog so setup default values + { + alphaBan[0] = 0.0f; + alphaBan[1] = 0.0f; + banHeights[0] = HORIZON; + banHeights[1] = banHeights[0] + OFFSET_HEIGHT; + } + + // if lower ban is at top of box then no cliping plan 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 || mNumFogVolumes == 0) + renderSkyBox(banHeights[0], alphaBan[1]); + + // if completly fogged out then no need to render + if(alphaBan[1] < 1.0f || depthInFog < 0.0f) + { + if(smCloudsOn && mStormCloudsOn && smSkyOn) + { + F32 ang = mAtan(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); + } + Point3F banPoints[2][MAX_BAN_POINTS]; + Point3F cornerPoints[MAX_BAN_POINTS]; + + // Calculate upper, lower, and corner ban points + calcBans(banHeights, banPoints, cornerPoints); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Renders the side, top, and corner bans + renderBans(alphaBan, banHeights, banPoints, cornerPoints); + + glDisable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + } + + if(mSetFog) + { + mSceneManager->setFogVolumes(mNumFogVolumes, mFogVolumes); + mSetFog = false; + } + if(mStormFogOn && mStormFogData.volume[mStormFogData.current].state != isDone) + updateFog(); + if(mStormFogOn && mRealFog) + updateRealFog(); + PROFILE_END(); +} + +//--------------------------------------------------------------------------- +void Sky::calcAlphas_Heights(F32 zCamPos, F32 *banHeights, + F32 *alphaBan, F32 depthInFog) +{ + F32 sideA, lower = 0.0f, upper = 0.0f; + F32 visValue, visDis = 0.0f; + F32 visRatio, ratioVal, setVis; + + visDis = setVis = mFogVolumes[mNumFogVolumes-1].visibleDistance; + for(S32 x = 0; x < mNumFogVolumes-1; ++x) + { + if(mFogVolumes[x].visibleDistance < setVis) + { + visValue = (zCamPos < mFogVolumes[x].minHeight) ? + mFogVolumes[x].maxHeight - mFogVolumes[x].minHeight : + mFogVolumes[x].maxHeight - zCamPos; + if(visValue > 0.0f) + { + ratioVal = setVis / mFogVolumes[x].visibleDistance; + visRatio = visValue / mFogVolumes[x].visibleDistance; + visDis -= (mFogVolumes[x].visibleDistance * visRatio) * ratioVal; + } + } + } + + //Calculate upper Height + if(visDis > 0.0f) + upper = (mSkyBoxPt.x*depthInFog)/(visDis * 0.2f); + + banHeights[1] = mSpherePt.z; + + if(upper < mSpherePt.z) + { + banHeights[1] = upper; + if(banHeights[1] < OFFSET_HEIGHT) + banHeights[1] = OFFSET_HEIGHT + HORIZON; + } + + if(visDis > depthInFog) + { + sideA = mSqrt((visDis*visDis)-(depthInFog*depthInFog)); + lower = (mSkyBoxPt.x*depthInFog)/sideA; + + //Calculate lower Height + banHeights[0] = mSpherePt.z; + if(lower < mSpherePt.z) + banHeights[0] = lower; + + if(banHeights[0] == mSpherePt.z && banHeights[1] == mSpherePt.z) + { + F32 temp = ((lower - mSpherePt.z) * (sideA/depthInFog)); + if(temp <= mSkyBoxPt.x) + alphaBan[1] = temp/mSkyBoxPt.x; + else + alphaBan[1] = 1.0f; + + alphaBan[0] = 1.0f; + } + else + { + alphaBan[0] = banHeights[0]/mSpherePt.z; + alphaBan[1] = 0.0f; + } + } + else + { + alphaBan[1] = alphaBan[0] = 1.0f; + banHeights[0] = banHeights[1] = mSpherePt.z; + } + banHeights[0] *= mFogVolumes[0].percentage; + banHeights[1] *= mFogVolumes[0].percentage; + + if(banHeights[1] < OFFSET_HEIGHT) + banHeights[1] = OFFSET_HEIGHT + HORIZON; +} + +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) +{ + for(S32 x = 0; x < 4; ++x) + texCoords[x].set(mTexCoord[x].x, mTexCoord[x].y); + S32 length = mFabs(mPoints[index].z) + mFabs(mPoints[index + 5].z); + F32 per = (mPoints[index].z - renderPoints[3].z) / length; + + texCoords[3].y = texCoords[2].y = per; +} + +//--------------------------------------------------------------------------- +void Sky::renderSkyBox(F32 lowerBanHeight, F32 alphaBanUpper) +{ + S32 side, index=0, val; + U32 numPoints; + Point3F renderPoints[4]; + Point2F texCoords[4]; + if(!mSkyTexturesOn || !smSkyOn) + { + glDisable(GL_TEXTURE_2D); + glColor3ub(mRealSkyColor.red, mRealSkyColor.green, mRealSkyColor.blue); + } + for(side = 0; side < ((mRenderBoxBottom) ? 6 : 5); ++side) + { + if((lowerBanHeight != mSpherePt.z || (side == 4 && alphaBanUpper < 1.0f)) && mSkyHandle[side]) + { + glBindTexture(GL_TEXTURE_2D,mSkyHandle[side].getGLName()); + if(side < 4) + { + numPoints = 4; + setRenderPoints(renderPoints, index); + sgUtil_clipToPlane(renderPoints, numPoints, PlaneF(0.0f, 0.0f, 1.0f, -lowerBanHeight)); + if(numPoints) + { + calcTexCoords(texCoords, renderPoints, index); + glBegin(GL_QUADS); + glTexCoord2f(texCoords[0].x, texCoords[0].y); + glVertex3f(renderPoints[0].x, renderPoints[0].y, renderPoints[0].z); + glTexCoord2f(texCoords[1].x, texCoords[1].y); + glVertex3f(renderPoints[1].x, renderPoints[1].y, renderPoints[1].z); + glTexCoord2f(texCoords[3].x, texCoords[3].y); + glVertex3f(renderPoints[2].x, renderPoints[2].y, renderPoints[2].z); + glTexCoord2f(texCoords[2].x, texCoords[2].y); + glVertex3f(renderPoints[3].x, renderPoints[3].y, renderPoints[3].z); + glEnd(); + } + ++index; + } + else + { + index = 3; + val = -1; + if(side == 5) + { + index = 5; + val = 1; + } + glBegin(GL_QUADS); + glTexCoord2f(mTexCoord[0].x, mTexCoord[0].y); + glVertex3f(mPoints[index].x, mPoints[index].y, mPoints[index].z); + glTexCoord2f(mTexCoord[1].x, mTexCoord[1].y); + glVertex3f(mPoints[index+(1*val)].x, mPoints[index+(1*val)].y, mPoints[index+(1*val)].z); + glTexCoord2f(mTexCoord[3].x, mTexCoord[3].y); + glVertex3f(mPoints[index+(2*val)].x, mPoints[index+(2*val)].y, mPoints[index+(2*val)].z); + glTexCoord2f(mTexCoord[2].x, mTexCoord[2].y); + glVertex3f(mPoints[index+(3*val)].x, mPoints[index+(3*val)].y, mPoints[index+(3*val)].z); + glEnd(); + } + + } + } + if(!mSkyTexturesOn) + glEnable(GL_TEXTURE_2D); +} + +//--------------------------------------------------------------------------- +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); + } + height = banHeights[1]; + value = banHeights[1] / mSkyBoxPt.x; + mulVal = -(mSqrt(1-(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(F32 *alphaBan, F32 *banHeights, Point3F banPoints[][MAX_BAN_POINTS], Point3F *cornerPoints) +{ + S32 side, x, index = 0; + F32 angle; + U8 UalphaIn = U8(alphaBan[1]*255); + U8 UalphaOut = U8(alphaBan[0]*255); + + //Renders the side bans + if(banHeights[0] < mSpherePt.z) + { + glBegin(GL_TRIANGLE_STRIP); + for(x=0;x mFogPercentage) ? goingOut : comingIn; + mStormFogData.volume[x].endPercentage = mFogPercentage; + if(mStormFogData.volume[x].state == comingIn) + mStormFogData.current = 0; + else + mStormFogData.current = mNumFogVolumes-1; + } + else if(mFogVolume < mNumFogVolumes) + { + mStormFogData.volume[mFogVolume].speed = (mFogPercentage - mFogVolumes[mFogVolume].percentage) / ((mStormFogData.time * 32.0f) / (F32)mNumFogVolumes); + mStormFogData.volume[mFogVolume].state = (mFogVolumes[mFogVolume].percentage > mFogPercentage) ? goingOut : comingIn; + mStormFogData.volume[mFogVolume].endPercentage = mFogPercentage; + mStormFogData.current = mFogVolume; + } +} + +//--------------------------------------------------------------------------- +void Sky::updateFog() +{ + F32 overFlow, offset = 1.0f; + U32 currentTime = Sim::getCurrentTime(); + + if(mStormFogData.lastTime != 0) + offset = F32(currentTime - mStormFogData.lastTime) / 32.0f; + Con::printf("OFFSET: %f", offset); + mStormFogData.lastTime = currentTime; + + mFogVolumes[mStormFogData.current].percentage += (mStormFogData.volume[mStormFogData.current].speed * offset); + do + { + Con::printf("CURRENT: %d PERCENTAGE: %f TIME: %u",mStormFogData.current, mFogVolumes[mStormFogData.current].percentage, currentTime); + overFlow = 0.0f; + if(mStormFogData.volume[mStormFogData.current].state == comingIn && mFogVolumes[mStormFogData.current].percentage >= mStormFogData.volume[mStormFogData.current].endPercentage) + { + overFlow = mFogVolumes[mStormFogData.current].percentage - mStormFogData.volume[mStormFogData.current].endPercentage; + mFogVolumes[mStormFogData.current].percentage = mStormFogData.volume[mStormFogData.current].endPercentage; + mStormFogData.volume[mStormFogData.current].state = isDone; + if(++mStormFogData.current >= mNumFogVolumes) + { + mStormFogData.current -= 1; + mStormFogData.lastTime = 0; + mStormFogOn = false; + Con::printf("FOG IS DONE"); + } + else + mFogVolumes[mStormFogData.current].percentage += overFlow; + } + else if(mStormFogData.volume[mStormFogData.current].state == goingOut && mFogVolumes[mStormFogData.current].percentage <= mStormFogData.volume[mStormFogData.current].endPercentage) + { + overFlow = mStormFogData.volume[mStormFogData.current].endPercentage - mFogVolumes[mStormFogData.current].percentage; + mFogVolumes[mStormFogData.current].percentage = mStormFogData.volume[mStormFogData.current].endPercentage; + mStormFogData.volume[mStormFogData.current].state = isDone; + if(--mStormFogData.current < 0) + { + mStormFogData.current += 1; + mStormFogData.lastTime = 0; + mStormFogOn = false; + Con::printf("FOG IS DONE"); + } + else + mFogVolumes[mStormFogData.current].percentage -= overFlow; + } + } while(overFlow > 0.0f && mStormFogOn); +// if(mStormFogData.volume[mStormFogData.current].state != done) + mSceneManager->setFogVolumes(mNumFogVolumes, mFogVolumes); +} + +//--------------------------------------------------------------------------- +void Sky::updateRealFog() +{ + for(S32 x = 0; x < mNumFogVolumes; ++x) + { + mFogVolumes[x].percentage += mStormFogData.volume[x].speed; + if((mStormFogData.volume[x].speed < 0.0f && mFogVolumes[x].percentage <= mStormFogData.volume[x].endPercentage) || + (mStormFogData.volume[x].speed > 0.0f && mFogVolumes[x].percentage >= mStormFogData.volume[x].endPercentage)) + { + mFogVolumes[x].percentage = mStormFogData.volume[x].endPercentage; + F32 save = mStormFogData.volume[x].lastPercentage; + mStormFogData.volume[x].lastPercentage = mStormFogData.volume[x].endPercentage; + mStormFogData.volume[x].endPercentage = save; + + mStormFogData.volume[x].speed *= -1; + } + } + mSceneManager->setFogVolumes(mNumFogVolumes, mFogVolumes); +} + +//--------------------------------------------------------------------------- +void Sky::calcPoints() +{ + S32 x, y, xval = 1, yval = -1, zval = 1; + F32 textureDem; + + F32 visDisMod = mVisibleDistance; + if(gClientSceneGraph) + visDisMod = gClientSceneGraph->getVisibleDistanceMod(); + mRadius = visDisMod * 0.95f; + + Cloud::setRadius(mRadius); + + loadDml(); + + 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; + } + + mFogLine = 0.0f; + for(x = 0; x < mNumFogVolumes; ++x) + mFogLine = (mFogVolumes[x].maxHeight > mFogLine) ? mFogVolumes[x].maxHeight : mFogLine; + + textureDem = 512; + if(mSkyHandle[0]) + textureDem = mSkyHandle[0].getWidth(); + + for(y = 0; y < 2 ; ++y) + for(x = 0; x < 2 ; ++x) + { + mTexCoord[x+(y*2)].set(x,y); + mTexCoord[x+(y*2)] *= (textureDem-1.0f)/textureDem; + mTexCoord[x+(y*2)] += Point2F(0.5 / textureDem, 0.5 / textureDem); + } + + mSpherePt = mSkyBoxPt = mPoints[1]; + mSpherePt.set(mSpherePt.x,0.0f,mSpherePt.z); + mSpherePt.normalize(mSkyBoxPt.x); + mTopCenterPt.set(0.0f,0.0f,mSkyBoxPt.z); +} + +//--------------------------------------------------------------------------- +void Sky::loadDml() +{ + S32 x; + char dmlName[256]; + mNumCloudLayers = 0; + dStrcpy(dmlName, DML_DIR); + dStrcat(dmlName, mMaterialListName); + Stream *stream = ResourceManager->openStream(dmlName); + mMaterialList.read(*stream); + ResourceManager->closeStream(stream); + mMaterialList.load(SkyTexture); + for(x = 0; x < 6; ++x) + { + mMaterialList.getMaterial(x).setClamp(true); + mSkyHandle[x] = mMaterialList.getMaterial(x); + } + for(x = 0; x < mMaterialList.size() - CloudMaterialOffset; ++x, ++mNumCloudLayers) + { + mMaterialList.getMaterial(x + CloudMaterialOffset).setClamp(false); + mCloudLayer[x].setTexture(mMaterialList.getMaterial(x+CloudMaterialOffset)); + } +} + +//--------------------------------------------------------------------------- +void Sky::updateVisibility() +{ + setVisibility(); + + setMaskBits(VisibilityMask); +} + +//--------------------------------------------------------------------------- +void Sky::stormCloudsOn(S32 state, F32 time) +{ + mStormCloudData.state = (state) ? comingIn : goingOut; + + mStormCloudData.time = time; + setMaskBits(StormCloudMask); +} + +//--------------------------------------------------------------------------- +void Sky::stormFogOn(F32 percentage, F32 time) +{ + mStormFogData.time = time; + + if(mStormFogData.endPercentage >= 0.0f) + { + mStormFogData.state = (mStormFogData.endPercentage > percentage) ? goingOut : comingIn; + mFogPercentage = mStormFogData.endPercentage; + } + else + mStormFogData.state = (mFogPercentage > percentage) ? goingOut : comingIn; + mStormFogData.endPercentage = percentage; + + setMaskBits(StormFogMask); +} + +//--------------------------------------------------------------------------- +void Sky::stormRealFog(S32 value, F32 max, F32 min, F32 speed) +{ + mRealFog = value; + mRealFogMax = max; + mRealFogMin = min; + mRealFogSpeed = speed; + setMaskBits(StormRealFogMask); +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +// Cloud Code +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +Cloud::Cloud() +{ + mDown=5; + mOver=1; + mBaseOffset.set(0, 0); + mTextureScale.set(1, 1); + mCenterHeight=0.5; + mInnerHeight=0.45; + mEdgeHeight=0.4; + 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(TextureHandle textHand) +{ + if(textHand) + { + mCloudHandle = textHand; + AssertFatal(bool(mCloudHandle) != false, "Error, couldn't load cloud layer bitmap"); + } +} + +//--------------------------------------------------------------------------- +void Cloud::setSpeed(Point2F setSpeed) +{ + mSpeed = setSpeed; +} + +//--------------------------------------------------------------------------- +void Cloud::setPoints() +{ + S32 x, y; + F32 xyDiff = (mRadius - -mRadius)/4; + F32 cDis = mRadius*mCenterHeight, + upDis = mRadius*mInnerHeight, + edgeZ = mRadius*mEdgeHeight; + + 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]); + + //Used to modify the corners of the cloud layers to make them one plane.. + 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) +{ + 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) + { + glBindTexture(GL_TEXTURE_2D, mCloudHandle.getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + 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(!outlineOn) + glBegin(GL_TRIANGLE_FAN); + else + glBegin(GL_LINE_LOOP); + + for(k = 0; k < numPoints; ++k) + { + glColor4f(1.0,1.0,1.0, renderAlpha[k]*renderSAlpha[k]); + glTexCoord2f(renderTexPoints[k].x,renderTexPoints[k].y); + glVertex3f(renderPoints[k].x,renderPoints[k].y,renderPoints[k].z); + } + glEnd(); + + ++start; + } + } + glDisable(GL_BLEND); +} + +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) +{ + float tempX, tempY; + float 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/terrain/sky.h b/terrain/sky.h new file mode 100644 index 0000000..23718da --- /dev/null +++ b/terrain/sky.h @@ -0,0 +1,261 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SKY_H_ +#define _SKY_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif +#ifndef _SCENESTATE_H_ +#include "scenegraph/sceneState.h" +#endif +#ifndef _SCENEGRAPH_H_ +#include "scenegraph/sceneGraph.h" +#endif +#ifndef _MPOINT_H_ +#include "math/mPoint.h" +#endif +#ifndef _MATERIALLIST_H_ +#include "dgl/materialList.h" +#endif +#ifndef _GAMEBASE_H_ +#include "game/gameBase.h" +#endif + +#define MAX_NUM_LAYERS 3 +#define MAX_BAN_POINTS 20 + +class SceneGraph; +class SceneState; +class SceneRenderImage; + +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; + +typedef struct +{ + SkyState state; + F32 speed; + F32 endPercentage; + F32 lastPercentage; +}StormFogVolume; + +typedef struct +{ + SkyState state; + U32 startTime; + F32 endPercentage; + F32 time; + S32 current; + U32 lastTime; + StormFogVolume volume[MaxFogVolumes]; +}StormFogData; + +//--------------------------------------------------------------------------- +class Cloud +{ + private: + Point3F mPoints[25]; + Point2F mSpeed; + F32 mCenterHeight, mInnerHeight, mEdgeHeight; + F32 mAlpha[25]; + S32 mDown, mOver; + static F32 mRadius; + F32 mLastTime, mOffset; + Point2F mBaseOffset, mTexCoords[25], mTextureScale; + TextureHandle mCloudHandle; + + Point2F alphaCenter; + Point2F stormUpdate; + F32 stormAlpha[25]; + F32 mAlphaSave[25]; + + static StormInfo mGStormData; + public: + Cloud(); + ~Cloud(); + void setPoints(); + void setHeights(F32 cHeight, F32 iHeight, F32 eHeight); + void setTexture(TextureHandle); + void setSpeed(Point2F); + 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; + StormFogData mStormFogData; + TextureHandle mSkyHandle[6]; + StringTableEntry mCloudText[MAX_NUM_LAYERS]; + F32 mCloudHeight[MAX_NUM_LAYERS]; + F32 mCloudSpeed[MAX_NUM_LAYERS]; + Cloud mCloudLayer[MAX_NUM_LAYERS]; + F32 mRadius; + Point3F mPoints[10]; + Point2F mTexCoord[4]; + StringTableEntry mMaterialListName; + + Point3F mSkyBoxPt; + Point3F mTopCenterPt; + Point3F mSpherePt; + ColorI mRealFogColor; + ColorI mRealSkyColor; + + MaterialList mMaterialList; + ColorF mFogColor; + bool mSkyTexturesOn; + bool mRenderBoxBottom; + ColorF mSolidFillColor; + F32 mFogDistance; + F32 mVisibleDistance; + U32 mNumFogVolumes; + FogVolume mFogVolumes[MaxFogVolumes]; + F32 mFogLine; + F32 mFogTime; + F32 mFogPercentage; + S32 mFogVolume; + S32 mRealFog; + F32 mRealFogMax; + F32 mRealFogMin; + F32 mRealFogSpeed; + + bool mLastForce16Bit; + bool mLastForcePaletted; + + SkyState mFogState; + + S32 mNumCloudLayers; + Point3F mWindVelocity; + + F32 mLastVisDisMod; + + static bool smCloudsOn; + static bool smCloudOutlineOn; + static bool smSkyOn; + static S32 smNumCloudsOn; + + bool mStormCloudsOn; + bool mStormFogOn; + bool mSetFog; + + void calcPoints(); + protected: + bool onAdd(); + void onRemove(); + + void renderObject(SceneState*, SceneRenderImage*); + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void render(SceneState *state); + void calcAlphas_Heights(F32 zCamPos, F32 *banHeights, F32 *alphaBan, F32 DepthInFog); + void renderSkyBox(F32 lowerBanHeight, F32 alphaIn); + void calcBans(F32 *banHeights, Point3F banPoints[][MAX_BAN_POINTS], Point3F *cornerPoints); + void renderBans(F32 *alphaBan, F32 *banHeights, Point3F banPoints[][MAX_BAN_POINTS], Point3F *cornerPoints); + void inspectPostApply(); + void startStorm(); + void setVisibility(); + void initSkyData(); + void loadDml(); + void updateFog(); + void updateRealFog(); + void startStormFog(); + void setRenderPoints(Point3F* renderPoints, S32 index); + void calcTexCoords(Point2F* texCoords, Point3F* renderPoints, S32 index); + public: + bool mEffectPrecip; + Point2F mWindDir; + enum NetMaskBits { + InitMask = BIT(0), + VisibilityMask = BIT(1), + StormCloudMask = BIT(2), + StormFogMask = BIT(3), + StormRealFogMask = BIT(4), + WindMask = BIT(5), + StormCloudsOnMask = BIT(6), + StormFogOnMask = BIT(7) + }; + enum Constants { + EnvMapMaterialOffset = 6, + CloudMaterialOffset = 7 + }; + + Sky(); + ~Sky(); + + F32 getVisibleDistance() const { return mVisibleDistance; } + + void stormCloudsShow(bool); + void stormFogShow(bool); + void stormCloudsOn(S32 state, F32 time); + void stormFogOn(F32 percentage, F32 time); + void stormRealFog(S32 value, F32 max, F32 min, F32 speed); + + void setWindVelocity(const Point3F &); + Point3F getWindVelocity(); + + TextureHandle getEnvironmentMap() { return mMaterialList.getMaterial(EnvMapMaterialOffset); } + + DECLARE_CONOBJECT(Sky); + static void initPersistFields(); + static void consoleInit(); + bool processArguments(S32 argc, const char **argv); + + void unpackUpdate(NetConnection *, BitStream *stream); + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + + void updateVisibility(); +}; + + + + +#endif diff --git a/terrain/sun.cc b/terrain/sun.cc new file mode 100644 index 0000000..bc8d9f4 --- /dev/null +++ b/terrain/sun.cc @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/Sun.h" +#include "console/objectTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "Core/bitStream.h" +#include "console/consoleTypes.h" +#include "terrain/terrData.h" +#include "dgl/gBitmap.h" +#include "Math/mathIO.h" + +IMPLEMENT_CO_NETOBJECT_V1(Sun); + +//----------------------------------------------------------------------------- + +Sun::Sun() +{ + mNetFlags.set(Ghostable | ScopeAlways); + mTypeMask = EnvironmentObjectType; + + mLight.mType = LightInfo::Vector; + mLight.mDirection.set(0.f, 0.707f, -0.707f); + mLight.mColor.set(0.7f, 0.7f, 0.7f); + mLight.mAmbient.set(0.3f, 0.3f, 0.3f); +} + +//----------------------------------------------------------------------------- + +void Sun::conformLight() +{ + mLight.mDirection.normalize(); + mLight.mColor.clamp(); + mLight.mAmbient.clamp(); +} + +//----------------------------------------------------------------------------- + +bool Sun::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + if(isClientObject()) + Sim::getLightSet()->addObject(this); + else + conformLight(); + + return(true); +} + +void Sun::registerLights(LightManager * lightManager, bool) +{ + lightManager->addLight(&mLight); +} + +//----------------------------------------------------------------------------- + +void Sun::inspectPostApply() +{ + conformLight(); + setMaskBits(UpdateMask); +} + +void Sun::unpackUpdate(NetConnection *, BitStream * stream) +{ + if(stream->readFlag()) + { + // direction -> color -> ambient + mathRead(*stream, &mLight.mDirection); + + stream->read(&mLight.mColor.red); + stream->read(&mLight.mColor.green); + stream->read(&mLight.mColor.blue); + stream->read(&mLight.mColor.alpha); + + stream->read(&mLight.mAmbient.red); + stream->read(&mLight.mAmbient.green); + stream->read(&mLight.mAmbient.blue); + stream->read(&mLight.mAmbient.alpha); + } +} + +U32 Sun::packUpdate(NetConnection *, U32 mask, BitStream * stream) +{ + if(stream->writeFlag(mask & UpdateMask)) + { + // direction -> color -> ambient + mathWrite(*stream, mLight.mDirection); + + stream->write(mLight.mColor.red); + stream->write(mLight.mColor.green); + stream->write(mLight.mColor.blue); + stream->write(mLight.mColor.alpha); + + stream->write(mLight.mAmbient.red); + stream->write(mLight.mAmbient.green); + stream->write(mLight.mAmbient.blue); + stream->write(mLight.mAmbient.alpha); + } + return(0); +} + +//----------------------------------------------------------------------------- + +void Sun::initPersistFields() +{ + Parent::initPersistFields(); + addField("direction", TypePoint3F, Offset(mLight.mDirection, Sun)); + addField("color", TypeColorF, Offset(mLight.mColor, Sun)); + addField("ambient", TypeColorF, Offset(mLight.mAmbient, Sun)); +} diff --git a/terrain/sun.h b/terrain/sun.h new file mode 100644 index 0000000..fae0d9f --- /dev/null +++ b/terrain/sun.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SUN_H_ +#define _SUN_H_ + +#ifndef _NETOBJECT_H_ +#include "Sim/netObject.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "sceneGraph/lightManager.h" +#endif + +class Sun : public NetObject +{ + private: + typedef NetObject Parent; + + LightInfo mLight; + + void conformLight(); + + public: + + Sun(); + + // SimObject + bool onAdd(); + void registerLights(LightManager *, bool); + + // + void inspectPostApply(); + + static void initPersistFields(); + + // NetObject + enum NetMaskBits { + UpdateMask = BIT(0) + }; + + void unpackUpdate(NetConnection *, BitStream * stream); + U32 packUpdate(NetConnection *, U32 mask, BitStream * stream); + + DECLARE_CONOBJECT(Sun); +}; + +#endif diff --git a/terrain/terrCollision.cc b/terrain/terrCollision.cc new file mode 100644 index 0000000..6f74ef8 --- /dev/null +++ b/terrain/terrCollision.cc @@ -0,0 +1,1018 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/terrData.h" +#include "dgl/dgl.h" +#include "Editor/editor.h" + +const F32 TerrainThickness = 0.5f; +static const U32 MaxExtent = 100; +#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 }, + { 1, 0,1 }, + { 3, 0,1,1,2,2,0 }, + { 0 }, + { 1, 0,1 }, + { 0 }, + { 2, 0,1,0,2 }, + { 1, 0,1 }, + { 3, 0,1,1,2,2,0 }, + { 2, 0,1,1,2 }, + { 5, 0,1,1,2,2,3,3,0,0,2 }, +}; + +S32 sEdgeList135[16][11] = { + { 0 }, + { 0 }, + { 0 }, + { 1, 0,1 }, + { 0 }, + { 0 }, + { 1, 0,1 }, + { 2, 0,1,1,2 }, + { 1, 0,1 }, + { 1, 0,1 }, + { 1, 0,1 }, + { 3, 0,1,1,2,2,0 }, + { 1, 0,1 }, + { 2, 0,2,1,2 }, + { 3, 0,1,1,2,2,0 }, + { 5, 0,1,1,2,2,3,3,0,1,3 }, +}; + + +// 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 }, +}; + + +//---------------------------------------------------------------------------- + +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 = 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(); + + // + if (box.max.z < -TerrainThickness || box.min.z > fixedToFloat(gridMap[BlockShift]->maxHeight)) + 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.min.x / squareSize ); + S32 xEnd = (S32)mCeil ( osBox.max.x / squareSize ); + S32 yStart = (S32)mFloor( osBox.min.y / squareSize ); + S32 yEnd = (S32)mCeil ( osBox.max.y / squareSize ); + S32 xExt = xEnd - xStart; + if (xExt > MaxExtent) + xExt = MaxExtent; + + mHeightMax = floatToFixed(osBox.max.z); + mHeightMin = (osBox.min.z < 0)? 0: floatToFixed(osBox.min.z); + + for (S32 y = yStart; y < yEnd; y++) { + S32 yi = y & BlockMask; + + // + for (S32 x = xStart; x < xEnd; x++) { + S32 xi = x & BlockMask; + GridSquare *gs = findSquare(0, Point2I(xi, yi)); + + // holes only in the primary terrain block + if (((gs->flags & GridSquare::Empty) && x == xi && y == yi) || + gs->minHeight > mHeightMax || gs->maxHeight < mHeightMin) + 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 = getMaterial(xi,yi)->index; + cp->box.min.set(x * squareSize,y * squareSize,fixedToFloat(gs->minHeight)); + cp->box.max.x = cp->box.min.x + squareSize; + cp->box.max.y = cp->box.min.y + squareSize; + cp->box.max.z = fixedToFloat(gs->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 = (x + dx) * squareSize; + pos->y = (y + dy) * squareSize; + pos->z = fixedToFloat(getHeight(xi + dx, yi + dy)); + } + + // Build normals, then split into two Convex objects if the + // square is concave + if ((cp->split45 = gs->flags & GridSquare::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&) +{ + if (box.max.z < -TerrainThickness || box.min.z > fixedToFloat(gridMap[BlockShift]->maxHeight)) + return false; + + // 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"); + + // Setup collision state data + polyList->setTransform(&getTransform(), getScale()); + polyList->setObject(this); + + S32 xStart = (S32)mFloor( osBox.min.x / squareSize ); + S32 xEnd = (S32)mCeil ( osBox.max.x / squareSize ); + S32 yStart = (S32)mFloor( osBox.min.y / squareSize ); + S32 yEnd = (S32)mCeil ( osBox.max.y / squareSize ); + S32 xExt = xEnd - xStart; + if (xExt > MaxExtent) + xExt = MaxExtent; + + mHeightMax = floatToFixed(osBox.max.z); + mHeightMin = (osBox.min.z < 0)? 0: floatToFixed(osBox.min.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); + + 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; + GridSquare *gs = findSquare(0, Point2I(xi, yi)); + + // holes only in the primary terrain block + if (((gs->flags & GridSquare::Empty) && x == xi && y == yi) || + gs->minHeight > mHeightMax || gs->maxHeight < mHeightMin) + 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 = (x + dx) * squareSize; + pos.y = (y + dy) * squareSize; + pos.z = fixedToFloat(getHeight(xi + dx, yi + dy)); + *vp = polyList->addPoint(pos); + } + vi[i] = *vp; + } + + U32* vp = &vi[0]; + if (!(gs->flags & GridSquare::Split45)) + vi[4] = vi[0], vp++; + + U32 material = getMaterial(xi,yi)->index; + 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; +} + + +//---------------------------------------------------------------------------- + +BSPNode *TerrainBlock::buildCollisionBSP(BSPTree *tree, const Box3F &box, const SphereF& /*sphere*/) +{ + S32 xStart = (S32)mFloor( box.min.x / squareSize ); + S32 xEnd = (S32)mCeil ( box.max.x / squareSize ); + S32 yStart = (S32)mFloor( box.min.y / squareSize ); + S32 yEnd = (S32)mCeil ( box.max.y / squareSize ); + + mTree = tree; + + S32 yExt = yEnd - yStart; + S32 xExt = xEnd - xStart; + + S32 maxPlaneCount = yExt - 1 + (xExt - 1) * yExt + xExt * yExt * 4; + + if (box.max.z < -TerrainThickness || box.min.z > fixedToFloat(gridMap[BlockShift]->maxHeight)) + return 0; + mHeightMax = floatToFixed(box.max.z); + mHeightMin = (box.min.z < 0)? 0: floatToFixed(box.min.z); + + BSPNode *backTree = buildXTree(yStart, xStart, xEnd); + + for(S32 y = yStart + 1; y < yEnd; y++) + { + BSPNode *branch = mTree->alloc(); + branch->plane.setXZ(y * squareSize); + + branch->frontNode = buildXTree(y, xStart, xEnd); + branch->backNode = backTree; + + if(branch->frontNode == branch->backNode) + { + AssertFatal(!branch->frontNode, "TerrainBlock::Internal Error: Bad equal nodes."); + backTree = branch->frontNode; + } + else + backTree = branch; + } + return backTree; +} + + +BSPNode *TerrainBlock::buildXTree(S32 y, S32 xStart, S32 xEnd) +{ + BSPNode *backTree = buildSquareTree(y, xStart); + for(S32 x = xStart + 1; x < xEnd; x++) + { + BSPNode *branch = mTree->alloc(); + branch->plane.setYZ(x * squareSize); + branch->frontNode = buildSquareTree(y, x); + branch->backNode = backTree; + + if(branch->frontNode == branch->backNode) + { + AssertFatal(!branch->frontNode, "TerrainBlock::Internal Error: Bad equal nodes."); + backTree = branch->frontNode; + } + else + backTree = branch; + } + return backTree; +} + + +BSPNode *TerrainBlock::buildSquareTree(S32 y, S32 x) +{ + // coords are in, mask off for heightMap lookup: + S32 xi = x & BlockMask; + S32 yi = y & BlockMask; + + GridSquare *gs = findSquare(0, Point2I(xi, yi)); + + // trivial check on min/max heights: + // we compare > on height max so we can add thickness to the terrain. + + // holes only in the primary terrain block + if (((gs->flags & GridSquare::Empty) && x == xi && y == yi) || + gs->minHeight > mHeightMax || gs->maxHeight < mHeightMin) + return 0; + + F32 zBottomLeft = fixedToFloat(getHeight(xi, yi)); + F32 zBottomRight = fixedToFloat(getHeight(xi + 1, yi)); + F32 zTopLeft = fixedToFloat(getHeight(xi, yi + 1)); + F32 zTopRight = fixedToFloat(getHeight(xi + 1, yi + 1)); + + // trivial check failed - construct planes. + + Point3F normal1, normal2, normal3; + Point3F squarePos, testPoint; + + // test point is point on p2 that is not on the line from p1 to p2. + + if(gs->flags & GridSquare::Split45) + { + squarePos.set(x * squareSize, y * squareSize, zBottomLeft); + normal1.set(zBottomLeft - zBottomRight, zBottomRight - zTopRight, squareSize); + normal2.set(zTopLeft - zTopRight, zBottomLeft - zTopLeft, squareSize); + normal3.set(-squareSize,squareSize,0); + testPoint.set(x * squareSize, (y+1) * squareSize, zTopLeft); + } + else + { + squarePos.set((x + 1) * squareSize, y * squareSize, zBottomRight); + normal1.set(zTopLeft - zTopRight, zBottomRight - zTopRight, squareSize); + normal2.set(zBottomLeft - zBottomRight, zBottomLeft - zTopLeft, squareSize); + normal3.set(-squareSize,-squareSize,0); + testPoint.set(x * squareSize, y * squareSize, zBottomLeft); + } + + // Solid terrain node with material type + BSPNode* solidNode = mTree->alloc(); + solidNode->material = getMaterial(xi,yi)->index; + solidNode->frontNode = solidNode->backNode = 0; + + // Make plane 1 node: + BSPNode *p1Node = mTree->alloc(); + p1Node->plane.set(squarePos, normal1); + p1Node->frontNode = 0; + p1Node->backNode = solidNode; + + F32 dist = p1Node->plane.distToPlane(testPoint); + if(dist != 0) + { + // Square is not flat, we need to build more nodes + // Make plane 2 node: + BSPNode *p2Node = mTree->alloc(); + p2Node->plane.set(squarePos, normal2); + p2Node->frontNode = 0; + p2Node->backNode = solidNode; + + // Make vertical split node: + BSPNode *psNode = mTree->alloc(); + psNode->plane.set(squarePos, normal3); + psNode->frontNode = p2Node; + psNode->backNode = p1Node; + + p1Node = psNode; + } + + // Add in a bottom plane if we extend past the bottom of the terrain. + if(gs->minHeight >= mHeightMin - TerrainThickness) + { + BSPNode *belowNode = mTree->alloc(); + belowNode->plane.setXY(fixedToFloat(gs->minHeight) - TerrainThickness); + belowNode->plane.invert(); + belowNode->frontNode = 0; + belowNode->backNode = p1Node; + return belowNode; + } + return p1Node; +} + + +//---------------------------------------------------------------------------- + +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; + +static void drawLineTest(F32 startT, F32 endT, bool collide) +{ + glEnable(GL_DEPTH_TEST); + if(lineCount & 1) + { + if(collide) + glColor3f(1, 0, 0); + else + glColor3f(1, 1, 1); + } + else + { + if(collide) + glColor3f(0, 1, 0); + else + glColor3f(0, 0, 0); + } + lineCount++; + glBegin(GL_LINES); + Point3F pt; + pt.interpolate(lineStart, lineEnd, startT); + glVertex3fv(pt); + pt.interpolate(lineStart, lineEnd, endT); + glVertex3fv(pt); + Point3F ptUp = pt; + ptUp.z += 4; + glVertex3fv(pt); + glVertex3fv(ptUp); + + glEnd(); +} + +bool TerrainBlock::castRay(const Point3F &start, const Point3F &end, RayInfo *info) +{ + return castRayI(start, end, info, false); +} + +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 / F32(squareSize * BlockSquareWidth); + + 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; + } + F32 startT = 0; + for(;;) + { + F32 nextXInt = calcInterceptX(pStart.x, invDeltaX, blockX + (dx == 1)); + F32 nextYInt = calcInterceptY(pStart.y, invDeltaY, 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), BlockShift, invDeltaX, invDeltaY, startT, intersectT, info, collideEmpty)) { + info->normal.z *= BlockSquareWidth * squareSize; + 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, Point2I aBlockPos, U32 aLevel, F32 invDeltaX, F32 invDeltaY, F32 aStartT, F32 aEndT, RayInfo *info, bool collideEmpty) +{ + F32 invBlockSize = 1 / F32(BlockSquareWidth); + + static TerrLOSStackNode stack[BlockShift * 3 + 1]; + U32 stackSize = 1; + + stack[0].startT = aStartT; + stack[0].endT = aEndT; + stack[0].blockPos = aBlockPos; + stack[0].level = aLevel; + + while(stackSize--) + { + TerrLOSStackNode *sn = stack + stackSize; + U32 level = sn->level; + F32 startT = sn->startT; + F32 endT = sn->endT; + Point2I blockPos = sn->blockPos; + + GridSquare *sq = findSquare(level, Point2I(blockPos.x & BlockMask, blockPos.y & BlockMask)); + + 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) + { + //drawLineTest(startT, sn->endT, false); + continue; + } + F32 maxHeight = fixedToFloat(sq->maxHeight); + if(startZ >= maxHeight && endZ >= maxHeight) + { + //drawLineTest(startT, endT, false); + continue; + } + if (!collideEmpty && (sq->flags & GridSquare::Empty) && + blockPos.x == (blockPos.x & BlockMask) && blockPos.y == (blockPos.y & BlockMask)) + { + //drawLineTest(startT, endT, false); + continue; + } + if(level == 0) + { + F32 xs = blockPos.x * invBlockSize; + F32 ys = blockPos.y * invBlockSize; + + F32 zBottomLeft = fixedToFloat(getHeight(blockPos.x, blockPos.y)); + F32 zBottomRight= fixedToFloat(getHeight(blockPos.x + 1, blockPos.y)); + F32 zTopLeft = fixedToFloat(getHeight(blockPos.x, blockPos.y + 1)); + F32 zTopRight = fixedToFloat(getHeight(blockPos.x + 1, blockPos.y + 1)); + + PlaneF p1, p2; + PlaneF divider; + Point3F planePoint; + + if(sq->flags & GridSquare::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 squareWidth = 1 << level; + 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/terrData.cc b/terrain/terrData.cc new file mode 100644 index 0000000..3b24be2 --- /dev/null +++ b/terrain/terrData.cc @@ -0,0 +1,1420 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/terrData.h" +#include "dgl/gBitmap.h" +#include "math/mMath.h" +#include "console/consoleTypes.h" +#include "core/fileStream.h" +#include "terrain/terrRender.h" +#include "dgl/materialList.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sceneState.h" +#include "platformWIN32/platformGL.h" +#include "dgl/dgl.h" +#include "core/bitStream.h" +#include "dgl/gTexManager.h" +#include "math/mathIO.h" +#include "terrain/blender.h" +#include "sim/netConnection.h" +#include "dgl/materialPropertyMap.h" + + +extern bool gDGLRender; + + +IMPLEMENT_CO_NETOBJECT_V1(TerrainBlock); + +namespace { + +void terrainTextureEventCB(const U32 eventCode, const U32 userData) +{ + TerrainBlock* pTerr = reinterpret_cast(userData); + pTerr->processTextureEvent(eventCode); +} + +Point3F sgTexGenS; +Point3F sgTexGenT; +Point3F sgLMGenS; +Point3F sgLMGenT; + +} // namespace {} + + + +//-------------------------------------- +TerrainBlock::TerrainBlock() +{ + squareSize = 8; + + mBlender = NULL; + lightMap = 0; + + for(S32 i = BlockShift; i >= 0; i--) + gridMap[i] = NULL; + + heightMap = NULL; + materialMap = NULL; + mBaseMaterialMap = NULL; + mMaterialFileName = NULL; + mTypeMask = TerrainObjectType | StaticObjectType | StaticRenderedObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + mCollideEmpty = false; + mDetailTextureName = NULL; + mCRC = 0; + flagMap = 0; + mVertexBuffer = -1; +} + + +//-------------------------------------- +TerrainBlock::~TerrainBlock() +{ + delete lightMap; +} + + +//-------------------------------------- +void TerrainBlock::setFile(Resource terr) +{ + mFile = terr; + for(U32 i = 0; i <= BlockShift; i++) + gridMap[i] = mFile->mGridMap[i]; + + mBaseMaterialMap = mFile->mBaseMaterialMap; + mMaterialFileName= mFile->mMaterialFileName; + mChunkMap = mFile->mChunkMap; + materialMap = mFile->mMaterialMap; + heightMap = mFile->mHeightMap; + flagMap = mFile->mFlagMap; +} + + +//-------------------------------------------------------------------------- +bool TerrainBlock::save(const char *filename) +{ + return mFile->save(filename); +} + + +//-------------------------------------- +static U16 calcDev(PlaneF &pl, Point3F &pt) +{ + F32 z = (pl.d + pl.x * pt.x + pl.y * pt.y) / -pl.z; + F32 diff = z - pt.z; + if(diff < 0) + diff = -diff; + + if(diff > 0xFFFF) + return 0xFFFF; + else + return U16(diff); +} + +static U16 Umax(U16 u1, U16 u2) +{ + return u1 > u2 ? u1 : u2; +} + +//------------------------------------------------------------------------------ + +bool TerrainBlock::unpackEmptySquares() +{ + U32 size = BlockSquareWidth * BlockSquareWidth; + + U32 i; + for(i = 0; i < size; i++) + materialMap[i].flags &= ~Material::Empty; + + // walk the pairs + for(i = 0; i < mEmptySquareRuns.size(); i++) + { + U32 offset = mEmptySquareRuns[i] & 0xffff; + U32 run = U32(mEmptySquareRuns[i]) >> 16; + + // + for(U32 j = 0; j < run; j++) + { + if((offset+j) >= size) + { + Con::errorf(ConsoleLogEntry::General, "TerrainBlock::unpackEmpties: invalid entry."); + return(false); + } + materialMap[offset+j].flags |= Material::Empty; + } + } + + rebuildEmptyFlags(); + return(true); +} + +void TerrainBlock::packEmptySquares() +{ + AssertFatal(isServerObject(), "TerrainBlock::packEmptySquares: client!"); + mEmptySquareRuns.clear(); + + // walk the map + U32 run = 0; + U32 offset; + + U32 size = BlockSquareWidth * BlockSquareWidth; + for(U32 i = 0; i < size; i++) + { + if(materialMap[i].flags & Material::Empty) + { + if(!run) + offset = i; + run++; + } + else if(run) + { + mEmptySquareRuns.push_back((run << 16) | offset); + run = 0; + + // cap it + if(mEmptySquareRuns.size() == MaxEmptyRunPairs) + break; + } + } + + // + if(run && mEmptySquareRuns.size() != MaxEmptyRunPairs) + mEmptySquareRuns.push_back((run << 16) | offset); + + if(mEmptySquareRuns.size() == MaxEmptyRunPairs) + Con::warnf(ConsoleLogEntry::General, "TerrainBlock::packEmptySquares: More than %d run pairs encountered. Extras will not persist.", MaxEmptyRunPairs); + + // + mNetFlags |= EmptyMask; +} + +//------------------------------------------------------------------------------ + +void TerrainBlock::rebuildEmptyFlags() +{ + // rebuild entire maps empty flags! + for(U32 y = 0; y < TerrainBlock::ChunkSquareWidth; y++) + { + for(U32 x = 0; x < TerrainBlock::ChunkSquareWidth; x++) + { + GridChunk &gc = *(mChunkMap + x + (y << TerrainBlock::ChunkShift)); + gc.emptyFlags = 0; + U32 sx = x << TerrainBlock::ChunkDownShift; + U32 sy = y << TerrainBlock::ChunkDownShift; + for(U32 j = 0; j < 4; j++) + { + for(U32 i = 0; i < 4; i++) + { + TerrainBlock::Material *mat = getMaterial(sx + i, sy + j); + if(mat->flags & TerrainBlock::Material::Empty) + gc.emptyFlags |= (1 << (j * 4 + i)); + } + } + } + } + + for(S32 i = BlockShift; i >= 0; i--) + { + S32 squareCount = 1 << (BlockShift - i); + S32 squareSize = (TerrainBlock::BlockSize) / squareCount; + + for(S32 squareX = 0; squareX < squareCount; squareX++) + { + for(S32 squareY = 0; squareY < squareCount; squareY++) + { + GridSquare *parent = NULL; + if(i < BlockShift) + parent = findSquare(i+1, Point2I(squareX * squareSize, squareY * squareSize)); + bool empty = true; + + for(S32 sizeX = 0; empty && sizeX <= squareSize; sizeX++) + { + for(S32 sizeY = 0; empty && sizeY <= squareSize; sizeY++) + { + S32 x = squareX * squareSize + sizeX; + S32 y = squareY * squareSize + sizeY; + + if(sizeX != squareSize && sizeY != squareSize) + { + TerrainBlock::Material *mat = getMaterial(x, y); + if(!(mat->flags & TerrainBlock::Material::Empty)) + empty = false; + } + } + } + GridSquare *sq = findSquare(i, Point2I(squareX * squareSize, squareY * squareSize)); + if(empty) + sq->flags |= GridSquare::Empty; + else + sq->flags &= ~GridSquare::Empty; + } + } + } +} + +//------------------------------------------------------------------------------ + +void TerrainBlock::setHeight(const Point2I & pos, float height) +{ + // set the height + U16 ht = floatToFixed(height); + *((U16*)getHeightAddress(pos.x, pos.y)) = ht; + + // update the chunk deviance + Point2I chunkPos(pos.x >> ChunkDownShift, pos.y >> ChunkDownShift); + buildChunkDeviance(chunkPos.x, chunkPos.y); + + // update neighbors + U32 chunkMask = ChunkSize - 1; + U32 chunkSquareMask = ChunkSquareWidth - 1; + + for(S32 x = 0; x < ChunkSize; x += chunkMask) + { + bool xEdge = (pos.x & chunkMask) == x; + if(xEdge) + buildChunkDeviance((x ? (chunkPos.x + 1) : (chunkPos.x - 1)) & chunkSquareMask, chunkPos.y); + + for(S32 y = 0; y < ChunkSize; y += chunkMask) + { + bool yEdge = (pos.y & chunkMask) == y; + + if(yEdge) + { + buildChunkDeviance(chunkPos.x, (y ? (chunkPos.y + 1) : (chunkPos.y - 1)) & chunkSquareMask); + + if(xEdge) + buildChunkDeviance((x ? (chunkPos.x + 1) : (chunkPos.x - 1)) & chunkSquareMask, + (y ? (chunkPos.y + 1) : (chunkPos.y - 1)) & chunkSquareMask); + } + } + } + + Point3F p1, p2, p3, p4; + PlaneF pl1, pl2, pl3, pl4; + + p1.set(0, 0, getHeight(pos.x, pos.y)); + p2.set(0, 1, getHeight(pos.x, pos.y + 1)); + p3.set(1, 1, getHeight(pos.x + 1, pos.y + 1)); + p4.set(1, 0, getHeight(pos.x + 1, pos.y)); + + pl1.set(p1, p2, p3); + pl2.set(p1, p3, p4); + pl3.set(p1, p2, p4); + pl4.set(p2, p3, p4); + + GridSquare * gs = findSquare(0, pos); + + U16 mindev45 = 0; + U16 mindev135 = 0; + + for(U32 sizeX = 0; sizeX <= 1; sizeX++) + for(U32 sizeY = 0; sizeY <= 1; sizeY++) + { + U32 x = pos.x + sizeX; + U32 y = pos.y + sizeY; + U16 ht = getHeight(x, y); + + if(ht < gs->minHeight) + gs->minHeight = ht; + if(ht > gs->maxHeight) + gs->maxHeight = ht; + + Point3F pt(sizeX, sizeY, fixedToFloat(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) == 0) + dev = calcDev(pl3, pt); + else if((sizeX + sizeY) > 1) + dev = calcDev(pl4, pt); + else + dev = Umax(calcDev(pl3, pt), calcDev(pl4, pt)); + + if(dev > mindev135) + mindev135 = dev; + } + + // + gs->flags &= GridSquare::Empty; + + if(((pos.x ^ pos.y) & 1) == 0) + { + gs->flags |= GridSquare::Split45; + gs->heightDeviance = mindev45; + } + else + gs->heightDeviance = mindev135; + + // + for(U32 i = 1; i <= TerrainBlock::BlockShift; i++) + { + GridSquare * parent = findSquare(i, pos); + + if(parent->minHeight > gs->minHeight) + parent->minHeight = gs->minHeight; + if(parent->maxHeight < gs->maxHeight) + parent->maxHeight = gs->maxHeight; + if(parent->heightDeviance < gs->heightDeviance) + parent->heightDeviance = gs->heightDeviance; + } +} + + +//-------------------------------------- +bool TerrainBlock::getHeight(const Point2F & pos, float * height) +{ + float invSquareSize = 1.0f / (float)squareSize; + float xp = pos.x * invSquareSize; + float yp = pos.y * invSquareSize; + int x = (S32)mFloor(xp); + int y = (S32)mFloor(yp); + xp -= (float)x; + yp -= (float)y; + x &= BlockMask; + y &= BlockMask; + GridSquare * gs = findSquare(0, Point2I(x,y)); + + if (gs->flags & GridSquare::Empty) + return false; + + float zBottomLeft = fixedToFloat(getHeight(x, y)); + float zBottomRight = fixedToFloat(getHeight(x + 1, y)); + float zTopLeft = fixedToFloat(getHeight(x, y + 1)); + float zTopRight = fixedToFloat(getHeight(x + 1, y + 1)); + + if(gs->flags & GridSquare::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) +{ + float invSquareSize = 1.0f / (float)squareSize; + float xp = pos.x * invSquareSize; + float yp = pos.y * invSquareSize; + int x = (S32)mFloor(xp); + int y = (S32)mFloor(yp); + xp -= (float)x; + yp -= (float)y; + x &= BlockMask; + y &= BlockMask; + GridSquare * gs = findSquare(0, Point2I(x,y)); + + if (gs->flags & GridSquare::Empty) + return false; + + float zBottomLeft = fixedToFloat(getHeight(x, y)); + float zBottomRight = fixedToFloat(getHeight(x + 1, y)); + float zTopLeft = fixedToFloat(getHeight(x, y + 1)); + float zTopRight = fixedToFloat(getHeight(x + 1, y + 1)); + + if(gs->flags & GridSquare::Split45) + { + if (xp>yp) + // bottom half + normal->set(zBottomLeft-zBottomRight,zBottomRight-zTopRight,squareSize); + else + // top half + normal->set(zTopLeft-zTopRight,zBottomLeft-zTopLeft,squareSize); + } + else + { + if (1.0f-xp>yp) + // bottom half + normal->set(zBottomLeft-zBottomRight,zBottomLeft-zTopLeft,squareSize); + else + // top half + normal->set(zTopLeft-zTopRight,zBottomRight-zTopRight,squareSize); + } + if (normalize) + normal->normalize(); + return true; +} + +bool TerrainBlock::getNormalAndHeight(const Point2F & pos, Point3F * normal, F32 * height, bool normalize) +{ + float invSquareSize = 1.0f / (float)squareSize; + float xp = pos.x * invSquareSize; + float yp = pos.y * invSquareSize; + int x = (S32)mFloor(xp); + int y = (S32)mFloor(yp); + xp -= (float)x; + yp -= (float)y; + x &= BlockMask; + y &= BlockMask; + GridSquare * gs = findSquare(0, Point2I(x,y)); + + if (gs->flags & GridSquare::Empty) + return false; + + float zBottomLeft = fixedToFloat(getHeight(x, y)); + float zBottomRight = fixedToFloat(getHeight(x + 1, y)); + float zTopLeft = fixedToFloat(getHeight(x, y + 1)); + float zTopRight = fixedToFloat(getHeight(x + 1, y + 1)); + + if(gs->flags & GridSquare::Split45) + { + if (xp>yp) + { + // bottom half + normal->set(zBottomLeft-zBottomRight,zBottomRight-zTopRight,squareSize); + *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight); + } + else + { + // top half + normal->set(zTopLeft-zTopRight,zBottomLeft-zTopLeft,squareSize); + *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft); + } + } + else + { + if (1.0f-xp>yp) + { + // bottom half + normal->set(zBottomLeft-zBottomRight,zBottomLeft-zTopLeft,squareSize); + *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft); + } + else + { + // top half + normal->set(zTopLeft-zTopRight,zBottomRight-zTopRight,squareSize); + *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight); + } + } + if (normalize) + normal->normalize(); + return true; +} + +//-------------------------------------- + + +//-------------------------------------- +void TerrainBlock::setBaseMaterials(S32 argc, const char *argv[]) +{ + for (S32 i = 0; i < argc; i++) + mMaterialFileName[i] = StringTable->insert(argv[i]); + for (S32 j = argc; j < MaterialGroups; j++) + mMaterialFileName[j] = NULL; +} + +//------------------------------------------------------------------------------ + +//-------------------------------------- + +void TerrainBlock::buildMipMap() +{ +// TerrainRender::mCurrentBlock = this; +// for(S32 i = 0; i < 16; i++) +// { +// AllocatedTexture tex; +// tex.x = (i & 3) * 64; +// tex.y = (i >> 2) * 64; +// tex.level = 6; +// tex.mipLevel = 7; + +// TerrainRender::buildBlendMap(&tex); +// baseTextures[i] = tex.handle; +// } +} + +//-------------------------------------- + +bool TerrainBlock::buildMaterialMap() +{ + TerrainRender::flushCache(); +#if 0 + if (1) + { + for (i = 0; i < MaterialGroups; i++) + { + if(!mMaterialFileName[i] || !*mMaterialFileName[i] || !getMaterialAlphaMap(i)) + break; + char fileBuf[256]; + dSprintf(fileBuf, sizeof(fileBuf), "terrain/%s", mMaterialFileName[i]); + mBaseMaterials[i] = TextureHandle(fileBuf, MeshTexture); + if(!mBaseMaterials[i]) + return false; + U8 *amap = getMaterialAlphaMap(i); + GBitmap *bmp = new GBitmap(256, 256, false, GBitmap::Luminance); + U8 *dest = bmp->getWritableBits(); + dMemcpy(dest, amap, 256 * 256); + mAlphaMaterials[i] = TextureHandle(NULL, bmp); + } + if(i == 0) + return false; + } +#endif + if (initMMXBlender() == false) + return false; + if (gDGLRender) + buildMipMap(); + + return true; +} + +bool TerrainBlock::initMMXBlender() +{ + // DMMNOTE: come back to this + delete mBlender; + mBlender = NULL; + + char fileBuf[256]; + + U32 validMaterials = 0; + S32 i; + for (i = 0; i < MaterialGroups; i++) { + if (mMaterialFileName[i] && *mMaterialFileName[i]) + validMaterials++; + else + break; + } + AssertFatal(validMaterials != 0, "Error, must have SOME materials here!"); + + // Submit alphamaps + U8* alphaMaterials[MaterialGroups]; + dMemset(alphaMaterials, 0, sizeof(alphaMaterials)); + for (i = 0; i < validMaterials; i++) { + if (getMaterialAlphaMap(i) == NULL) { + AssertFatal(getMaterialAlphaMap(i) != NULL, "Error, need an alpha map here!"); + return false; + } + alphaMaterials[i] = getMaterialAlphaMap(i); + } + + mBlender = new Blender(validMaterials, 5, alphaMaterials); + + // Ok, we have validMaterials set correctly + for(i = 0; i < validMaterials; i++) { + AssertFatal(mMaterialFileName[i] && *mMaterialFileName[i], "Error, something wacky here"); + StringTableEntry fn = mMaterialFileName[i]; + if(!dStrncmp(fn, "terrain.", 8)) + fn += 8; + dSprintf(fileBuf, sizeof(fileBuf), "textures/terrain/%s.png", fn); + + // DMM + Stream* pMaterialStream = ResourceManager->openStream(fileBuf); + if (pMaterialStream == NULL) + pMaterialStream = ResourceManager->openStream("textures/terrain/Default.png"); + AssertFatal(pMaterialStream != NULL, "Error, must have a stream at this point!"); + + GBitmap* pBitmap = new GBitmap; + if (pBitmap->readPNG(*pMaterialStream) == false) { + AssertFatal(false, "Must be a png at this point!"); + return false; + } + ResourceManager->closeStream(pMaterialStream); + pBitmap->extrudeMipLevels(); + + // Submit as material for group i + const U8* pMipmaps[5]; + for (U32 j = 0; j < 5; j++) + pMipmaps[j] = pBitmap->getBits(j); + + mBlender->addSourceTexture(i, pMipmaps); + + delete pBitmap; + } + + return true; +} + +//------------------------------------------------------------------------------ + +void TerrainBlock::refreshMaterialLists() +{ +} + +//------------------------------------------------------------------------------ + +void TerrainBlock::onEditorEnable() +{ + // need to load in the material base material lists + if(isClientObject()) + refreshMaterialLists(); +} + +void TerrainBlock::onEditorDisable() +{ + +} + +//------------------------------------------------------------------------------ + +bool TerrainBlock::onAdd() +{ + if(!Parent::onAdd()) + return false; + + setPosition(Point3F(-squareSize * (BlockSize >> 1), -squareSize * (BlockSize >> 1), 0)); + + char fileBuf[256]; + dSprintf(fileBuf, sizeof(fileBuf), "terrains/%s", mTerrFileName); + + Resource terr = ResourceManager->load(fileBuf, true); + if(!bool(terr)) + { + if(isClientObject()) + NetConnection::setLastError("You are missing a file needed to play this mission: %s", mTerrFileName); + return false; + } + + setFile(terr); + char nameBuff[256] = "terrain/"; + MaterialPropertyMap* pMatMap = static_cast(Sim::findObject("MaterialPropertyMap")); + + StringTableEntry fn = mMaterialFileName[0]; + if(!dStrncmp(fn, "terrain.", 8)) + fn += 8; + + dStrcat(nameBuff,fn); + mMPMIndex[0] = pMatMap->getIndexFromName(nameBuff); + + mObjBox.min.set(-1e8, -1e8, -1e8); + mObjBox.max.set( 1e8, 1e8, 1e8); + resetWorldBox(); + setRenderTransform(mObjToWorld); + + if (isClientObject()) + { + if(mCRC != terr.getCRC()) + { + NetConnection::setLastError("Your terrain file doesn't match the version that is running on the server."); + return false; + } + + if(mDetailTextureName && mDetailTextureName[0]) + mDetailTextureHandle = TextureHandle(mDetailTextureName, DetailTexture); + + lightMap = new GBitmap(LightmapSize, LightmapSize, false, GBitmap::RGB5551); + + if (!buildMaterialMap()) + return false; + + mTextureCallbackKey = TextureManager::registerEventCallback(terrainTextureEventCB, U32(this)); + + mDynLightTexture = TextureHandle("special/lightFalloffMono", BitmapTexture, true); + + if (dglDoesSupportVertexBuffer()) + mVertexBuffer = glAllocateVertexBufferEXT(VertexBufferSize,GL_TRIBESMTVFMT_EXT,true); + else + mVertexBuffer = -1; + } + else + mCRC = terr.getCRC(); + + addToScene(); + + if(!unpackEmptySquares()) + return(false); + + return true; +} + +//-------------------------------------- +void TerrainBlock::onRemove() +{ + delete mBlender; + mBlender = NULL; + + removeFromScene(); + + if (mVertexBuffer != -1) + { + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(mVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + mVertexBuffer = -1; + } + + TerrainRender::flushCache(); + TextureManager::unregisterEventCallback(mTextureCallbackKey); + Parent::onRemove(); +} + + +//-------------------------------------- +void TerrainBlock::processTextureEvent(const U32 eventCode) +{ + if (eventCode == TextureManager::BeginZombification) { + // Delete any textures we have built from the dmls... + TerrainRender::flushCache(); + for(S32 i = 0; i < 16; i++) + baseTextures[i] = NULL; + if (mVertexBuffer != -1) + { + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(mVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + mVertexBuffer = -1; + } + } else if (eventCode == TextureManager::CacheResurrected) { + buildMipMap(); + if (dglDoesSupportVertexBuffer()) + mVertexBuffer = glAllocateVertexBufferEXT(VertexBufferSize,GL_TRIBESMTVFMT_EXT,true); + } +} + +//-------------------------------------------------------------------------- +bool TerrainBlock::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... + bool render = false; + if (state->isTerrainOverridden() == false) + render = state->isObjectRendered(this); + else + render = true; + + if (render == true) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->sortType = SceneRenderImage::Terrain; + state->insertRenderImage(image); + } + + return false; +} + +void TerrainBlock::buildChunkDeviance(S32 x, S32 y) +{ + mFile->buildChunkDeviance(x, y); +} + +void TerrainBlock::buildGridMap() +{ + mFile->buildGridMap(); +} + +//------------------------------------------------------------------------------ +void TerrainBlock::setTransform(const MatrixF & mat) +{ + Parent::setTransform(mat); + + // Since the terrain is a static object, it's render transform changes 1 to 1 + // with it's collision transform + setRenderTransform(mat); +} + +void TerrainBlock::renderObject(SceneState* state, SceneRenderImage*) +{ + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); + + RectI viewport; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + dglGetViewport(&viewport); + + if (state->isTerrainOverridden() == false) { + state->setupObjectProjection(this); + } else { + state->setupBaseProjection(); + } + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + dglMultMatrix(&mRenderObjToWorld); + glScalef(mObjScale.x, mObjScale.y, mObjScale.z); + + TerrainRender::renderBlock(this, state); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + dglSetViewport(viewport); + + dglSetCanonicalState(); + AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); +} + +//-------------------------------------- +void TerrainBlock::initPersistFields() +{ + Parent::initPersistFields(); + addField("detailTexture", TypeString, Offset(mDetailTextureName, TerrainBlock)); + addField("terrainFile", TypeString, Offset(mTerrFileName, TerrainBlock)); + addField("squareSize", TypeS32, Offset(squareSize, TerrainBlock)); + addField("emptySquares", TypeS32Vector, Offset(mEmptySquareRuns, TerrainBlock)); + removeField("position"); +} + +//-------------------------------------- +U32 TerrainBlock::packUpdate(NetConnection *, U32 mask, BitStream *stream) +{ + if(stream->writeFlag(mask & InitMask)) + { + stream->write(mCRC); + stream->writeString(mTerrFileName); + stream->writeString(mDetailTextureName); + stream->write(squareSize); + + // write out the empty rle vector + stream->write(mEmptySquareRuns.size()); + for(U32 i = 0; i < mEmptySquareRuns.size(); i++) + stream->write(mEmptySquareRuns[i]); + } + else // normal update + { + if(stream->writeFlag(mask & EmptyMask)) + { + // write out the empty rle vector + stream->write(mEmptySquareRuns.size()); + for(U32 i = 0; i < mEmptySquareRuns.size(); i++) + stream->write(mEmptySquareRuns[i]); + } + } + + return 0; +} + +void TerrainBlock::unpackUpdate(NetConnection *, BitStream *stream) +{ + if(stream->readFlag()) // init + { + stream->read(&mCRC); + mTerrFileName = stream->readSTString(); + mDetailTextureName = stream->readSTString(); + stream->read(&squareSize); + + // read in the empty rle + U32 size; + stream->read(&size); + mEmptySquareRuns.setSize(size); + for(U32 i = 0; i < size; i++) + stream->read(&mEmptySquareRuns[i]); + } + else // normal update + { + if(stream->readFlag()) // empty + { + // read in the empty rle + U32 size; + stream->read(&size); + mEmptySquareRuns.setSize(size); + for(U32 i = 0; i < size; i++) + stream->read(&mEmptySquareRuns[i]); + + // + if(materialMap) + unpackEmptySquares(); + } + } +} + +//-------------------------------------- +static void cMakeTestTerrain(SimObject*, S32 argc, const char **argv) +{ + TerrainFile *file = new TerrainFile; + S32 nMaterialTypes; + argc -= 2; + if (argc) + { + nMaterialTypes = argc; + for (S32 i=0; imMaterialFileName[i] = StringTable->insert(filename); + } + } + else + { + nMaterialTypes = 1; + file->mMaterialFileName[0] = StringTable->insert("Default"); + } + + // create circular cone in the middle of the map: + S32 i, j; + for(i = 0; i < TerrainBlock::BlockSize; i++) + { + for(j = 0; j < TerrainBlock::BlockSize; j++) + { + S32 x = i & 0x7f; + S32 y = j & 0x7f; + + F32 dist = mSqrt((64 - x) * (64 - x) + (64 - y) * (64 - y)); + dist /= 64.0f; + + if(dist > 1) + dist = 1; + + U32 offset = i + (j << TerrainBlock::BlockShift); + file->mHeightMap[offset] = (U16)((1 - dist) * (1 - dist) * 20000); + file->mBaseMaterialMap[offset] = 0; + } + } + + char filename[256]; + char *ext; + dSprintf(filename, sizeof(filename), "base/terrains/%s", argv[1]); + ext = dStrrchr(filename, '.'); + if (!ext || dStricmp(ext, ".ter") != 0) + dStrcat(filename, ".ter"); + file->save(filename); + + delete file; +} + + +//-------------------------------------- +static bool cTerrain_save(SimObject *obj, S32, const char **argv) +{ + char filename[256]; + char *ext; + dSprintf(filename, sizeof(filename), "base/terrains/%s", argv[2]); + ext = dStrrchr(filename, '.'); + if (!ext || dStricmp(ext, ".ter") != 0) + dStrcat(filename, ".ter"); + return static_cast(obj)->save(filename); +} + +static F32 cTerrainHeight(SimObject *, S32, const char **argv) +{ + Point2F pos; + F32 height = 0.0f; + dSscanf(argv[1],"%f %f",&pos.x,&pos.y); + TerrainBlock * terrain = dynamic_cast(Sim::findObject("Terrain")); + if(terrain) + if(terrain->isServerObject()) + { + Point3F offset; + terrain->getTransform().getColumn(3, &offset); + pos -= Point2F(offset.x, offset.y); + terrain->getHeight(pos, &height); + } + return height; +} + +//-------------------------------------- +void TerrainBlock::consoleInit() +{ + Con::addCommand("makeTestTerrain", cMakeTestTerrain, "makeTestTerrain(fileName, {dml1, dml2, ...dml8} );", 2, 10); + Con::addCommand("TerrainBlock", "save", cTerrain_save, "TerrainBlock.save(fileName);", 3, 3); + Con::addCommand("getTerrainHeight", cTerrainHeight, "getTerrainHeight(pos);", 2, 2); +} + +void TerrainBlock::flushCache() +{ + TerrainRender::flushCache(); + +} + +//-------------------------------------- +TerrainFile::TerrainFile() +{ + for(S32 i=0; i < TerrainBlock::MaterialGroups; i++) + { + mMaterialFileName[i] = NULL; + mMaterialAlphaMap[i] = NULL; + } +} + +TerrainFile::~TerrainFile() +{ + for(S32 i=0; i < TerrainBlock::MaterialGroups; i++) { + delete[] mMaterialAlphaMap[i]; + mMaterialAlphaMap[i] = NULL; + } +} + + +//-------------------------------------- +bool TerrainFile::save(const char *filename) +{ + FileStream writeFile; + if(!writeFile.open(filename, FileStream::Write)) + return(false); + + // write the VERSION and HeightField + writeFile.write((U8)FILE_VERSION); + for (S32 i=0; i < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); i++) + writeFile.write(mHeightMap[i]); + + // write the material group map, after merging the flags... + TerrainBlock::Material * materialMap = (TerrainBlock::Material*)mMaterialMap; + for (S32 j=0; j < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); j++) + { + U8 val = mBaseMaterialMap[j]; + val |= materialMap[j].flags & TerrainBlock::Material::PersistMask; + writeFile.write(val); + } + + // write the MaterialList Info + S32 k; + for(k=0; k < TerrainBlock::MaterialGroups; k++) + writeFile.writeString(mMaterialFileName[k]); + + for(k=0; k < TerrainBlock::MaterialGroups; k++) { + if(mMaterialFileName[k] && mMaterialFileName[k][0]) { + AssertFatal(mMaterialAlphaMap[k] != NULL, "Error, must have a material map here!"); + writeFile.write(TerrainBlock::BlockSize * TerrainBlock::BlockSize, mMaterialAlphaMap[k]); + } + } + + return (writeFile.getStatus() == FileStream::Ok); +} + +//-------------------------------------- + +void TerrainFile::heightDevLine(U32 p1x, U32 p1y, U32 p2x, U32 p2y, U32 pmx, U32 pmy, U16 *devPtr) +{ + S32 h1 = getHeight(p1x, p1y); + S32 h2 = getHeight(p2x, p2y); + S32 hm = getHeight(pmx, pmy); + S32 dev = ((h2 + h1) >> 1) - hm; + if(dev < 0) + dev = -dev; + if(dev > *devPtr) + *devPtr = dev; +} + +void TerrainFile::buildChunkDeviance(S32 x, S32 y) +{ + GridChunk &gc = *(mChunkMap + x + (y << TerrainBlock::ChunkShift)); + gc.emptyFlags = 0; + U32 sx = x << TerrainBlock::ChunkDownShift; + U32 sy = y << TerrainBlock::ChunkDownShift; + + gc.heightDeviance[0] = 0; + + heightDevLine(sx, sy, sx + 2, sy, sx + 1, sy, &gc.heightDeviance[0]); + heightDevLine(sx + 2, sy, sx + 4, sy, sx + 3, sy, &gc.heightDeviance[0]); + + heightDevLine(sx, sy + 2, sx + 2, sy + 2, sx + 1, sy + 2, &gc.heightDeviance[0]); + heightDevLine(sx + 2, sy + 2, sx + 4, sy + 2, sx + 3, sy + 2, &gc.heightDeviance[0]); + + heightDevLine(sx, sy + 4, sx + 2, sy + 4, sx + 1, sy + 4, &gc.heightDeviance[0]); + heightDevLine(sx + 2, sy + 4, sx + 4, sy + 4, sx + 3, sy + 4, &gc.heightDeviance[0]); + + heightDevLine(sx, sy, sx, sy + 2, sx, sy + 1, &gc.heightDeviance[0]); + heightDevLine(sx + 2, sy, sx + 2, sy + 2, sx + 2, sy + 1, &gc.heightDeviance[0]); + heightDevLine(sx + 4, sy, sx + 4, sy + 2, sx + 4, sy + 1, &gc.heightDeviance[0]); + + heightDevLine(sx, sy + 2, sx, sy + 4, sx, sy + 3, &gc.heightDeviance[0]); + heightDevLine(sx + 2, sy + 2, sx + 2, sy + 4, sx + 2, sy + 3, &gc.heightDeviance[0]); + heightDevLine(sx + 4, sy + 2, sx + 4, sy + 4, sx + 4, sy + 3, &gc.heightDeviance[0]); + + gc.heightDeviance[1] = gc.heightDeviance[0]; + + heightDevLine(sx, sy, sx + 2, sy + 2, sx + 1, sy + 1, &gc.heightDeviance[1]); + heightDevLine(sx + 2, sy, sx, sy + 2, sx + 1, sy + 1, &gc.heightDeviance[1]); + + heightDevLine(sx + 2, sy, sx + 4, sy + 2, sx + 3, sy + 1, &gc.heightDeviance[1]); + heightDevLine(sx + 2, sy + 2, sx + 4, sy, sx + 3, sy + 1, &gc.heightDeviance[1]); + + heightDevLine(sx, sy + 2, sx + 2, sy + 4, sx + 1, sy + 3, &gc.heightDeviance[1]); + heightDevLine(sx + 2, sy + 4, sx, sy + 2, sx + 1, sy + 3, &gc.heightDeviance[1]); + + heightDevLine(sx + 2, sy + 2, sx + 4, sy + 4, sx + 3, sy + 3, &gc.heightDeviance[1]); + heightDevLine(sx + 2, sy + 4, sx + 4, sy + 2, sx + 3, sy + 3, &gc.heightDeviance[1]); + + gc.heightDeviance[2] = gc.heightDeviance[1]; + + heightDevLine(sx, sy, sx + 4, sy, sx + 2, sy, &gc.heightDeviance[2]); + heightDevLine(sx, sy + 4, sx + 4, sy + 4, sx + 2, sy + 4, &gc.heightDeviance[2]); + heightDevLine(sx, sy, sx, sy + 4, sx, sy + 2, &gc.heightDeviance[2]); + heightDevLine(sx + 4, sy, sx + 4, sy + 4, sx + 4, sy + 2, &gc.heightDeviance[2]); + + for(U32 j = 0; j < 4; j++) + { + for(U32 i = 0; i < 4; i++) + { + TerrainBlock::Material *mat = getMaterial(sx + i, sy + j); + if(mat->flags & TerrainBlock::Material::Empty) + gc.emptyFlags |= (1 << (j * 4 + i)); + } + } +} + +void TerrainFile::buildGridMap() +{ + S32 y; + for(y = 0; y < TerrainBlock::ChunkSquareWidth; y++) + for(U32 x = 0; x < TerrainBlock::ChunkSquareWidth; x++) + buildChunkDeviance(x, y); + + GridSquare * gs = mGridMapBase; + S32 i; + for(i = TerrainBlock::BlockShift; i >= 0; i--) + { + mGridMap[i] = gs; + gs += 1 << (2 * (TerrainBlock::BlockShift - i)); + } + for(i = TerrainBlock::BlockShift; i >= 0; i--) + { + S32 squareCount = 1 << (TerrainBlock::BlockShift - i); + S32 squareSize = (TerrainBlock::BlockSize) / 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; + + Point3F p1, p2, p3, p4; + + // determine max error for both possible splits. + PlaneF pl1, pl2, pl3, pl4; + + p1.set(0, 0, getHeight(squareX * squareSize, squareY * squareSize)); + p2.set(0, squareSize, getHeight(squareX * squareSize, squareY * squareSize + squareSize)); + p3.set(squareSize, squareSize, getHeight(squareX * squareSize + squareSize, squareY * squareSize + squareSize)); + p4.set(squareSize, 0, getHeight(squareX * squareSize + squareSize, squareY * squareSize)); + + // pl1, pl2 = split45, pl3, pl4 = split135 + pl1.set(p1, p2, p3); + pl2.set(p1, p3, p4); + pl3.set(p1, p2, p4); + pl4.set(p2, p3, p4); + bool parentSplit45 = false; + GridSquare *parent = NULL; + if(i < TerrainBlock::BlockShift) + { + GridSquare *parent; + parent = findSquare(i+1, Point2I(squareX * squareSize, squareY * squareSize)); + parentSplit45 = parent->flags & GridSquare::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) + { + TerrainBlock::Material *mat = getMaterial(x, y); + if(!(mat->flags & TerrainBlock::Material::Empty)) + empty = false; + else + hasEmpty = true; + } + U16 ht = getHeight(x, y); + + if(ht < min) + min = ht; + if(ht > max) + max = ht; + Point3F pt(sizeX, sizeY, 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; + } + } + GridSquare *sq = findSquare(i, Point2I(squareX * squareSize, squareY * squareSize)); + sq->minHeight = min; + sq->maxHeight = max; + + sq->flags = empty ? GridSquare::Empty : 0; + if(hasEmpty) + sq->flags |= GridSquare::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 |= GridSquare::Split45; + sq->heightDeviance = mindev45; + } + else + sq->heightDeviance = mindev135; + if(parent) + if(parent->heightDeviance < sq->heightDeviance) + parent->heightDeviance = sq->heightDeviance; + } + } + } + for (y = 0; y < TerrainBlock::BlockSize; y++) + { + for (S32 x=0; x < TerrainBlock::BlockSize; x++) + { + GridSquare *sq = findSquare(0, Point2I(x, y)); + S32 xpl = (x + 1) & TerrainBlock::BlockMask; + S32 ypl = (y + 1) & TerrainBlock::BlockMask; + for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++) + { + if (mMaterialAlphaMap[i] != NULL) { + U32 mapVal = (mMaterialAlphaMap[i][(y << TerrainBlock::BlockShift) + x] + + mMaterialAlphaMap[i][(ypl << TerrainBlock::BlockShift) + x] + + mMaterialAlphaMap[i][(ypl << TerrainBlock::BlockShift) + xpl] + + mMaterialAlphaMap[i][(y << TerrainBlock::BlockShift) + xpl]); + if(mapVal) + sq->flags |= (GridSquare::MaterialStart << i); + } + } + } + } + for (y = 0; y < TerrainBlock::BlockSize; y += 2) + { + for (S32 x=0; x < TerrainBlock::BlockSize; 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); + } + } + GridSquare *s = findSquare(1, Point2I(0, 0)); + U16 *dflags = mFlagMap; + U16 *eflags = mFlagMap + TerrainBlock::FlagMapWidth * TerrainBlock::FlagMapWidth; + + for(;dflags != eflags;s++,dflags++) + *dflags = s->flags; + +} + +//-------------------------------------- +ResourceInstance *constructTerrainFile(Stream &stream) +{ + U8 version; + stream.read(&version); + if (version > TerrainFile::FILE_VERSION) + return NULL; + + if (version != TerrainFile::FILE_VERSION) { + Con::errorf(" ****************************************************"); + Con::errorf(" ****************************************************"); + Con::errorf(" ****************************************************"); + Con::errorf(" PLEASE RESAVE THE TERRAIN FILE FOR THIS MISSION! THANKS!"); + Con::errorf(" ****************************************************"); + Con::errorf(" ****************************************************"); + Con::errorf(" ****************************************************"); + } + + TerrainFile* ret = new TerrainFile; + // read the HeightField + for (S32 i=0; i < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); i++) + stream.read(&ret->mHeightMap[i]); + + // read the material group map and flags... + dMemset(ret->mMaterialMap, 0, sizeof(ret->mMaterialMap)); + TerrainBlock::Material * materialMap = (TerrainBlock::Material*)ret->mMaterialMap; + + AssertFatal(!(TerrainBlock::Material::PersistMask & TerrainFile::MATERIAL_GROUP_MASK), + "Doh! We have flag clobberage..."); + + for (S32 j=0; j < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); j++) + { + U8 val; + stream.read(&val); + + // + ret->mBaseMaterialMap[j] = val & TerrainFile::MATERIAL_GROUP_MASK; + materialMap[j].flags = val & TerrainBlock::Material::PersistMask; + } + + // read the MaterialList Info + for(S32 k=0; k < TerrainBlock::MaterialGroups; k++) + ret->mMaterialFileName[k] = stream.readSTString(); + + if(version == 1) + { + for(S32 j = 0; j < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); j++) { + if (ret->mMaterialAlphaMap[ret->mBaseMaterialMap[j]] == NULL) { + ret->mMaterialAlphaMap[ret->mBaseMaterialMap[j]] = new U8[TerrainBlock::BlockSize * TerrainBlock::BlockSize]; + dMemset(ret->mMaterialAlphaMap[ret->mBaseMaterialMap[j]], 0, TerrainBlock::BlockSize * TerrainBlock::BlockSize); + } + + ret->mMaterialAlphaMap[ret->mBaseMaterialMap[j]][j] = 255; + } + } + else + { + for(S32 k=0; k < TerrainBlock::MaterialGroups; k++) { + if(ret->mMaterialFileName[k] && ret->mMaterialFileName[k][0]) { + AssertFatal(ret->mMaterialAlphaMap[k] == NULL, "Bad assumption. There should be no alpha map at this point..."); + ret->mMaterialAlphaMap[k] = new U8[TerrainBlock::BlockSize * TerrainBlock::BlockSize]; + stream.read(TerrainBlock::BlockSize * TerrainBlock::BlockSize, ret->mMaterialAlphaMap[k]); + } + } + } + + ret->buildGridMap(); + return ret; +} + +void TerrainBlock::setBaseMaterial(U32 /*x*/, U32 /*y*/, U8 /*matGroup*/) +{ + +} + diff --git a/terrain/terrData.h b/terrain/terrData.h new file mode 100644 index 0000000..972ec30 --- /dev/null +++ b/terrain/terrData.h @@ -0,0 +1,414 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRDATA_H_ +#define _TERRDATA_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MPOINT_H_ +#include "math/mPoint.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif +#ifndef _RESMANAGER_H_ +#include "core/resManager.h" +#endif +#ifndef _MATERIALLIST_H_ +#include "dgl/materialList.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif + + +class GBitmap; +class TerrainFile; +class TerrainBlock; +class ColorF; +class Blender; + + +//-------------------------------------------------------------------------- + +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() { mType = TerrainConvexType; } + 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 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 GridSquare +{ + U16 minHeight; + U16 maxHeight; + U16 heightDeviance; + U16 flags; + enum { + Split45 = 1, + Empty = 2, + HasEmpty = 4, + MaterialShift = 3, + MaterialStart = 8, + Material0 = 8, + Material1 = 16, + Material2 = 32, + Material3 = 64, + }; +}; + +struct GridChunk +{ + U16 heightDeviance[3]; // levels 0-1, 1-2, 2 + U16 emptyFlags; +}; + + +//-------------------------------------------------------------------------- +class TerrainBlock : public SceneObject +{ + typedef SceneObject Parent; + + public: + 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; + }; + enum { + BlockSize = 256, + BlockShift = 8, + LightmapSize = 512, + LightmapShift = 9, + ChunkSquareWidth = 64, + ChunkSize = 4, + ChunkDownShift = 2, + ChunkShift = BlockShift - ChunkDownShift, + BlockSquareWidth = 256, + SquareMaxPoints = 1024, + BlockMask = 255, + GridMapSize = 0x15555, + FlagMapWidth = 128, // flags map is for 2x2 squares + FlagMapMask = 127, + MaxMipLevel = 6, + NumBaseTextures = 16, + MaterialGroups = 8, + MaxEmptyRunPairs = 100 + }; + enum UpdateMaskBits { + InitMask = 1, + VisibilityMask = 2, + EmptyMask = 4, + }; + + Blender* mBlender; + + TextureHandle baseTextures[NumBaseTextures]; + TextureHandle mBaseMaterials[MaterialGroups]; + TextureHandle mAlphaMaterials[MaterialGroups]; + + GBitmap *lightMap; + StringTableEntry *mMaterialFileName; // array from the file + + TextureHandle mDynLightTexture; + + U8 *mBaseMaterialMap; + Material *materialMap; + // fixed point height values + U16 *heightMap; + U16 *flagMap; + StringTableEntry mDetailTextureName; + TextureHandle mDetailTextureHandle; + + StringTableEntry mTerrFileName; + Vector mEmptySquareRuns; + + U32 mTextureCallbackKey; + void processTextureEvent(const U32 eventCode); + + S32 mMPMIndex[10]; + + S32 mVertexBuffer; + + private: + Resource mFile; + GridSquare *gridMap[BlockShift+1]; + GridChunk *mChunkMap; + U32 mCRC; + + public: + + TerrainBlock(); + ~TerrainBlock(); + void buildChunkDeviance(S32 x, S32 y); + void buildGridMap(); + U32 getCRC() { return(mCRC); } + + bool onAdd(); + void onRemove(); + + void refreshMaterialLists(); + void onEditorEnable(); + void onEditorDisable(); + + void rebuildEmptyFlags(); + bool unpackEmptySquares(); + void packEmptySquares(); + + TextureHandle getDetailTextureHandle(); + Material *getMaterial(U32 x, U32 y); + GridSquare *findSquare(U32 level, Point2I pos); + GridSquare *findSquare(U32 level, S32 x, S32 y); + GridChunk *findChunk(Point2I pos); + + void setHeight(const Point2I & pos, float height); + U16 getHeight(U32 x, U32 y) { return heightMap[(x & BlockMask) + ((y & BlockMask) << BlockShift)]; } + U16 *getHeightAddress(U32 x, U32 y) { return &heightMap[(x & BlockMask) + ((y & BlockMask) << BlockShift)]; } + void setBaseMaterial(U32 x, U32 y, U8 matGroup); + + U8 *getMaterialAlphaMap(U32 matIndex); + U8* getBaseMaterialAddress(U32 x, U32 y); + U8 getBaseMaterial(U32 x, U32 y); + U16 *getFlagMapPtr(S32 x, S32 y); + + // a more useful getHeight for the public... + bool getHeight(const Point2F & pos, F32 * height); + bool getNormal(const Point2F & pos, Point3F * normal, bool normalize = true); + bool getNormalAndHeight(const Point2F & pos, Point3F * normal, F32 * height, bool normalize = true); + + // 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));} + + private: + S32 squareSize; + + public: + void setFile(Resource file); + bool save(const char* filename); + + static void flushCache(); + void relight(const ColorF &lightColor, const ColorF &ambient, const Point3F &lightDir); + + S32 getSquareSize(); + + //-------------------------------------- + // SceneGraph functions... + + protected: + void setTransform(const MatrixF&); + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(SceneState*, SceneRenderImage*); + + //-------------------------------------- + // collision info + private: + BSPTree *mTree; + S32 mHeightMin; + S32 mHeightMax; + public: + void buildConvex(const Box3F& box,Convex* convex); + bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + BSPNode *buildCollisionBSP(BSPTree *tree, 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, Point2I blockPos, U32 level, F32 invDeltaX, F32 invDeltaY, F32 startT, F32 endT, RayInfo *info, bool); + private: + BSPNode *buildSquareTree(S32 y, S32 x); + BSPNode *buildXTree(S32 y, S32 xStart, S32 xEnd); + + public: + bool buildMaterialMap(); + void buildMipMap(); + + void setBaseMaterials(S32 argc, const char *argv[]); +// bool loadBaseMaterials(); + bool initMMXBlender(); + + // private helper + private: + bool mCollideEmpty; + + public: + DECLARE_CONOBJECT(TerrainBlock); + static void initPersistFields(); + static void consoleInit(); + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); +}; + + + +//-------------------------------------- +class TerrainFile : public ResourceInstance +{ + public: + enum Constants { + FILE_VERSION = 2, + MATERIAL_GROUP_MASK = 0x7 + }; + + TerrainFile(); + ~TerrainFile(); + U16 mHeightMap[TerrainBlock::BlockSize * TerrainBlock::BlockSize]; + U8 mBaseMaterialMap[TerrainBlock::BlockSize * TerrainBlock::BlockSize]; + GridSquare mGridMapBase[TerrainBlock::GridMapSize]; + GridSquare *mGridMap[TerrainBlock::BlockShift+1]; + GridChunk mChunkMap[TerrainBlock::ChunkSquareWidth * TerrainBlock::ChunkSquareWidth]; + U16 mFlagMap[TerrainBlock::FlagMapWidth * TerrainBlock::FlagMapWidth]; + + TerrainBlock::Material mMaterialMap[TerrainBlock::BlockSquareWidth * TerrainBlock::BlockSquareWidth]; + + // DMMNOTE: This loads all the alpha maps, whether or not they are used. Possible to + // restrict to only the used versions? + StringTableEntry mMaterialFileName[TerrainBlock::MaterialGroups]; + U8* mMaterialAlphaMap[TerrainBlock::MaterialGroups]; + + bool save(const char *filename); + void buildChunkDeviance(S32 x, S32 y); + void buildGridMap(); + void heightDevLine(U32 p1x, U32 p1y, U32 p2x, U32 p2y, U32 pmx, U32 pmy, U16 *devPtr); + inline GridSquare *findSquare(U32 level, Point2I pos) + { + return mGridMap[level] + (pos.x >> level) + ((pos.y>>level) << (TerrainBlock::BlockShift - level)); + } + inline U16 getHeight(U32 x, U32 y) + { + return mHeightMap[(x & TerrainBlock::BlockMask) + ((y & TerrainBlock::BlockMask) << TerrainBlock::BlockShift)]; + } + inline TerrainBlock::Material *getMaterial(U32 x, U32 y) + { + return &mMaterialMap[(x & TerrainBlock::BlockMask) + ((y & TerrainBlock::BlockMask) << TerrainBlock::BlockShift)]; + } +}; + + + +//-------------------------------------------------------------------------- +inline U16 *TerrainBlock::getFlagMapPtr(S32 x, S32 y) +{ + return flagMap + ((x >> 1) & TerrainBlock::FlagMapMask) + + ((y >> 1) & TerrainBlock::FlagMapMask) * TerrainBlock::FlagMapWidth; +} + +inline GridSquare *TerrainBlock::findSquare(U32 level, S32 x, S32 y) +{ + return gridMap[level] + ((x & TerrainBlock::BlockMask) >> level) + (((y & TerrainBlock::BlockMask) >> level) << (TerrainBlock::BlockShift - level)); +} + +inline GridSquare *TerrainBlock::findSquare(U32 level, Point2I pos) +{ + return gridMap[level] + (pos.x >> level) + ((pos.y>>level) << (BlockShift - level)); +} + +inline GridChunk *TerrainBlock::findChunk(Point2I pos) +{ + return mChunkMap + (pos.x >> ChunkDownShift) + ((pos.y>>ChunkDownShift) << ChunkShift); +} + +inline TerrainBlock::Material *TerrainBlock::getMaterial(U32 x, U32 y) +{ + return materialMap + x + (y << BlockShift); +} + +inline TextureHandle TerrainBlock::getDetailTextureHandle() +{ + return mDetailTextureHandle; +} + +inline S32 TerrainBlock::getSquareSize() +{ + return squareSize; +} + +inline U8 TerrainBlock::getBaseMaterial(U32 x, U32 y) +{ + return mBaseMaterialMap[(x & BlockMask) + ((y & BlockMask) << BlockShift)]; +} + +inline U8* TerrainBlock::getBaseMaterialAddress(U32 x, U32 y) +{ + return &mBaseMaterialMap[(x & BlockMask) + ((y & BlockMask) << BlockShift)]; +} + +inline U8* TerrainBlock::getMaterialAlphaMap(U32 matIndex) +{ + if (mFile->mMaterialAlphaMap[matIndex] == NULL) { + mFile->mMaterialAlphaMap[matIndex] = new U8[TerrainBlock::BlockSize * TerrainBlock::BlockSize]; + dMemset(mFile->mMaterialAlphaMap[matIndex], 0, TerrainBlock::BlockSize * TerrainBlock::BlockSize); + } + + return mFile->mMaterialAlphaMap[matIndex]; +} + +// 11.5 fixed point - gives us a height range from 0->2048 in 1/32 inc + +inline F32 fixedToFloat(U16 val) +{ + return F32(val) * 0.03125f; +} + +inline U16 floatToFixed(F32 val) +{ + return U16(val * 32.0); +} + +extern ResourceInstance *constructTerrainFile(Stream &stream); + +#endif diff --git a/terrain/terrLighting.cc b/terrain/terrLighting.cc new file mode 100644 index 0000000..f1d7b81 --- /dev/null +++ b/terrain/terrLighting.cc @@ -0,0 +1,766 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/terrData.h" +#include "Math/mMath.h" +#include "dgl/dgl.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/gBitmap.h" +#include "dgl/gTexManager.h" +#include "terrain/terrRender.h" + +//void TerrainRender::setupColorAdder(F32 rSquared, F32 r, F32 g, F32 b) +//{ +// mCurrentR = r; +// mCurrentG = g; +// mCurrentB = b; +// mCurrentRSq = rSquared; +//} +// +//void TerrainRender::addColor(Color &c, F32 dSquared) +//{ +// if(dSquared > mCurrentRSq) +// return; +// F32 scale = 255.0f * ( 1 - (dSquared / mCurrentRSq) ); +// c.r += (S32)(mCurrentR * scale); +// c.g += (S32)(mCurrentG * scale); +// c.b += (S32)(mCurrentB * scale); +//} +// +//inline U16 TerrainRender::convertColorBack(Color &c) +//{ +// U16 ret; +// if(c.r > 255) +// ret = 0xF800; +// else +// ret = (c.r >> 3) << 11; +// if(c.g > 255) +// ret |= 0x07E0; +// else +// ret |= (c.g >> 2) << 5; +// +// if(c.b > 255) +// ret |= 0x001F; +// else +// ret |= c.b >> 3; +// +// return ret; +//} +// +//inline void TerrainRender::convertColor(Color &c, U16 lix) +//{ +// S32 r = lix >> 11; +// S32 g = (lix >> 5) & 0x3F; +// S32 b = lix & 0x1F; +// +// c.r = (r << 3) | ( r >> 2); +// c.g = (g << 2) | ( g >> 4); +// c.b = (b << 3) | ( b >> 2); +//} +// +//void TerrainRender::lightExpandHorz(F32 x, F32 ySquared, F32 dx, S32 j, S32 i, S32 shift) +//{ +// S32 size = 1 << shift; +// F32 scale = 1 / F32(size); +// +// F32 z = mLMABuffer[j][i].z; +// F32 dz = (mLMABuffer[j][i+size].z - z) * scale; +// +// for(S32 count = 1; count < size; count++) +// { +// x += dx; +// z += dz; +// +// addColor(mLMABuffer[j][i + count], x * x + z * z + ySquared); +// } +//} +// +//void TerrainRender::lightExpandVert(F32 y, F32 xSquared, F32 dy, S32 j, S32 i, S32 shift) +//{ +// S32 size = 1 << shift; +// F32 scale = 1 / F32(size); +// +// F32 z = mLMABuffer[j][i].z; +// F32 dz = (mLMABuffer[j+size][i].z - z) * scale; +// +// for(S32 count = 1; count < size; count++) +// { +// y += dy; +// z += dz; +// +// mLMABuffer[j+count][i].z = z; +// addColor(mLMABuffer[j+count][i], y * y + z * z + xSquared); +// } +//} +// +//void TerrainRender::lmExpandHorz(S32 y, S32 x, S32 shift) +//{ +// S32 size = 1 << shift; +// Color d; +// Color c = mLMABuffer[y][x]; +// d.r = (mLMABuffer[y][x+size].r - c.r) >> shift; +// d.g = (mLMABuffer[y][x+size].g - c.g) >> shift; +// d.b = (mLMABuffer[y][x+size].b - c.b) >> shift; +// +// for(S32 i = 1; i < size; i++) +// { +// c.r += d.r; +// c.g += d.g; +// c.b += d.b; +// +// mLMABuffer[y][x+i] = c; +// } +//} +// +//void TerrainRender::lmExpandVert(S32 y, S32 x, S32 shift) +//{ +// S32 size = 1 << shift; +// Color d; +// Color c = mLMABuffer[y][x]; +// d.r = (mLMABuffer[y+size][x].r - c.r) >> shift; +// d.g = (mLMABuffer[y+size][x].g - c.g) >> shift; +// d.b = (mLMABuffer[y+size][x].b - c.b) >> shift; +// +// for(S32 i = 1; i < size; i++) +// { +// c.r += d.r; +// c.g += d.g; +// c.b += d.b; +// +// mLMABuffer[y+i][x] = c; +// } +//} + +//void TerrainRender::expandDynamicLight(TerrLightInfo &tl, S32 x, S32 y, S32 level) +//{ +// S32 i, j; +// setupColorAdder(tl.radiusSquared, tl.r, tl.g, tl.b); +// +// if(DynamicLightMapShift - level <= 0) +// { +// S32 step = 1 << (level - DynamicLightMapShift); +// S32 count = DynamicLightMapSize; +// for(j = 0; j <= count; j++) +// { +// for(i = 0; i <= count; i++) +// { +// F32 xp = (x + i*step) * mSquareSize - tl.pos.x; +// F32 yp = (y + j*step) * mSquareSize - tl.pos.y; +// F32 zp = fixedToFloat(mCurrentBlock->getHeight((x + i) & TerrainBlock::BlockMask, +// (y + j) & TerrainBlock::BlockMask)) - tl.pos.z; +// +// addColor(mLMABuffer[j][i], xp * xp + yp * yp + zp * zp); +// } +// } +// } +// else +// { +// S32 delt = DynamicLightMapShift - level; +// S32 size = 1 << delt; +// S32 count = 1 << level; +// +// F32 delta = mSquareSize / F32(size); +// +// for(j = 0; j <= count; j++) +// { +// for(i = 0; i <= count; i++) +// { +// F32 xp = (x + i) * mSquareSize - tl.pos.x; +// F32 yp = (y + j) * mSquareSize - tl.pos.y; +// F32 zp = fixedToFloat(mCurrentBlock->getHeight((x + i) & TerrainBlock::BlockMask, (y + j) & TerrainBlock::BlockMask)) - tl.pos.z; +// mLMABuffer[j << delt][i << delt].z = zp; +// +// addColor(mLMABuffer[j << delt][i << delt], xp * xp + yp * yp + zp * zp); +// if(i != 0) +// lightExpandHorz(xp - mSquareSize, yp * yp, delta, j << delt, (i - 1) << delt, delt); +// if(j != 0) +// lightExpandVert(yp - mSquareSize, xp * xp, delta, (j-1) << delt, i << delt, delt); +// if(i != 0 && j != 0) +// { +// F32 xp = (x + i - 1) * mSquareSize - tl.pos.x; +// for(S32 k = 1; k < size; k++) +// { +// F32 yp = (y + j - 1) * mSquareSize + k * delta - tl.pos.y; +// lightExpandHorz(xp, yp * yp, delta, ((j - 1) << delt) + k, (i-1) << delt, delt); +// } +// } +// } +// } +// } +//} + +//void TerrainRender::expandLightMap(S32 /*x*/, S32 /*y*/, S32 /*level*/) +//{ +// S32 i, j; +// x &= TerrainBlock::BlockMask; +// y &= TerrainBlock::BlockMask; +// GBitmap *lightMap = mCurrentBlock->lightMapHandle.getBitmap(); +// if(DynamicLightMapShift - level <= 0) +// { +// S32 step = (level - DynamicLightMapShift); +// S32 count = DynamicLightMapSize; +// S32 lightMask = lightMap->getWidth() - 1; +// +// for(j = 0; j <= count; j++) +// { +// for(i = 0; i <= count; i++) +// { +// U16 lix = * ((U16 *) lightMap-> +// getAddress( (x + (i << step)) & lightMask, +// (y + (j << step)) & lightMask ) ); +// convertColor(mLMABuffer[j][i], lix); +// } +// } +// } +// else +// { +// S32 delt = DynamicLightMapShift - level; +// S32 size = 1 << delt; +// S32 count = 1 << level; +// +// for(j = 0; j <= count; j++) +// { +// U16 *pixPtr = (U16 *) lightMap->getAddress(x, y + j); +// for(i = 0; i <= count; i++) +// { +// convertColor(mLMABuffer[j << delt][i << delt], *pixPtr++); +// if(i != 0) +// lmExpandHorz(j << delt, (i-1) << delt, delt); +// if(j != 0) +// lmExpandVert((j-1) << delt, i << delt, delt); +// if(i != 0 && j != 0) +// { +// for(S32 k = 1; k < size; k++) +// lmExpandHorz(((j-1) << delt) + k, (i-1) << delt, delt); +// } +// } +// } +// } +//} + +U32 TerrainRender::TestSquareLights(GridSquare *sq, S32 level, Point2I pos, U32 lightMask) +{ + U32 retMask = 0; + F32 blockX = pos.x * mSquareSize + mBlockPos.x; + F32 blockY = pos.y * mSquareSize + mBlockPos.y; + F32 blockZ = fixedToFloat(sq->minHeight); + + F32 blockSize = mSquareSize * (1 << level); + F32 blockHeight = fixedToFloat(sq->maxHeight - sq->minHeight); + + Point3F vec; + + for(S32 i = 0; (lightMask >> i) != 0; i++) + { + + if(lightMask & (1 << i)) + { + Point3F *pos = &mTerrainLights[i].pos; + // test the visibility of this light to box + // find closest point on box to light and test + + if(pos->z < blockZ) + vec.z = blockZ - pos->z; + else if(pos->z > blockZ + blockHeight) + vec.z = pos->z - (blockZ + blockHeight); + else + vec.z = 0; + + if(pos->x < blockX) + vec.x = blockX - pos->x; + else if(pos->x > blockX + blockSize) + vec.x = pos->x - (blockX + blockSize); + else + vec.x = 0; + + if(pos->y < blockY) + vec.y = blockY - pos->y; + else if(pos->y > blockY + blockSize) + vec.y = pos->y - (blockY + blockSize); + else + vec.y = 0; + F32 dist = vec.len(); + if(dist < mTerrainLights[i].radius) + retMask |= (1 << i); + } + } + return retMask; +} + +//jff tmp +#include "sceneGraph/lightManager.h" +#include "sceneGraph/sceneGraph.h" + +void TerrainRender::buildLightArray() +{ + static Vector lights; + + // + lights.clear(); + gClientSceneGraph->getLightManager()->getBestLights(mClipPlane, mNumClipPlanes, mCamPos, lights, MaxTerrainLights); + + // create terrain lights from these... + U32 curIndex = 0; + for(U32 i = 0; i < lights.size(); i++) + { + if(lights[i]->mType != LightInfo::Point) + continue; + + // set the 'fo + TerrLightInfo & info = mTerrainLights[curIndex++]; + + mCurrentBlock->getWorldTransform().mulP(lights[i]->mPos, &info.pos); + info.radius = lights[i]->mRadius; //jff + info.radiusSquared = info.radius * info.radius; + + // + info.r = lights[i]->mColor.red; + info.g = lights[i]->mColor.green; + info.b = lights[i]->mColor.blue; + + Point3F dVec = mCamPos - lights[i]->mPos; + info.distSquared = mDot(dVec, dVec); + } + + mDynamicLightCount = curIndex; + + /*const TMat3F & mat = mCamera->getTOW(); + TSSceneLighting * sceneLights = trs.renderContext->getLights(); + SphereF instSphere ( mat.p, 1000000.0f ); + sceneLights->prepare ( instSphere, mat ); + + S32 lightCount = 0; + + TSSceneLighting::iterator ptr; + for ( ptr = sceneLights->begin(); ptr != sceneLights->end() && lightCount < MaxTerrainLights ; ptr++ ) + { + TSLight *tsl = *ptr; + if(tsl->fLight.fType == TS::Light::LightPoint && !tsl->isStaticLight()) + { + mTerrainLights[lightCount].pos = tsl->fLight.fPosition; + Point3F dVec = mCamPos - mTerrainLights[lightCount].pos; + + mTerrainLights[lightCount].distSquared = m_dot(dVec, dVec); + mTerrainLights[lightCount].radius = tsl->fLight.fRange; + mTerrainLights[lightCount].radiusSquared = tsl->fLight.fRange * tsl->fLight.fRange; + mTerrainLights[lightCount].r = tsl->fLight.fRed; + mTerrainLights[lightCount].g = tsl->fLight.fGreen; + mTerrainLights[lightCount].b = tsl->fLight.fBlue; + lightCount++; + } + } + trs.dynamicLightCount = min(lightCount, (S32)MaxVisibleLights); + */ +} + +static U16 convertColor(ColorF color) +{ + if(color.red > 1) + color.red = 1; + if(color.green > 1) + color.green = 1; + if(color.blue > 1) + color.blue = 1; + + return (U32(color.red * 31) << 11) | + (U32(color.green * 31) << 6) | + (U32(color.blue * 31) << 1) | 1; +} + +void TerrainBlock::relight(const ColorF &lightColor, const ColorF &ambient, const Point3F &lightDir) +{ + if(lightDir.x == 0 && lightDir.y == 0) + return; + + if(!lightMap) + return; + + S32 generateLevel = Con::getIntVariable("$pref::sceneLighting::terrainGenerateLevel", 0); + generateLevel = mClamp(generateLevel, 0, 4); + + U32 generateDim = TerrainBlock::LightmapSize << generateLevel; + U32 generateShift = TerrainBlock::LightmapShift + generateLevel; + U32 generateMask = generateDim - 1; + + F32 zStep; + F32 frac; + + Point2I blockColStep; + Point2I blockRowStep; + Point2I blockFirstPos; + Point2I lmapFirstPos; + + F32 terrainDim = F32(getSquareSize()) * F32(TerrainBlock::BlockSize); + F32 stepSize = F32(getSquareSize()) / F32(generateDim / TerrainBlock::BlockSize); + + if(mFabs(lightDir.x) >= mFabs(lightDir.y)) + { + if(lightDir.x > 0) + { + zStep = lightDir.z / lightDir.x; + frac = lightDir.y / lightDir.x; + + blockColStep.set(1, 0); + blockRowStep.set(0, 1); + blockFirstPos.set(0, 0); + lmapFirstPos.set(0, 0); + } + else + { + zStep = -lightDir.z / lightDir.x; + frac = -lightDir.y / lightDir.x; + + blockColStep.set(-1, 0); + blockRowStep.set(0, 1); + blockFirstPos.set(255, 0); + lmapFirstPos.set(TerrainBlock::LightmapSize-1, 0); + } + } + else + { + if(lightDir.y > 0) + { + zStep = lightDir.z / lightDir.y; + frac = lightDir.x / lightDir.y; + + blockColStep.set(0, 1); + blockRowStep.set(1, 0); + blockFirstPos.set(0, 0); + lmapFirstPos.set(0, 0); + } + else + { + zStep = -lightDir.z / lightDir.y; + frac = -lightDir.x / lightDir.y; + + blockColStep.set(0, -1); + blockRowStep.set(1, 0); + blockFirstPos.set(0, 255); + lmapFirstPos.set(0, TerrainBlock::LightmapSize-1); + } + } + zStep *= stepSize; + + F32 * heightArray = new F32[generateDim]; + + S32 fracStep = -1; + if(frac < 0) + { + fracStep = 1; + frac = -frac; + } + + F32 * nextHeightArray = new F32[generateDim]; + F32 oneMinusFrac = 1 - frac; + + U32 blockShift = generateShift - TerrainBlock::BlockShift; + U32 lightmapShift = generateShift - TerrainBlock::LightmapShift; + + U32 blockStep = 1 << blockShift; + U32 blockMask = (1 << blockShift) - 1; + U32 lightmapMask = (1 << lightmapShift) - 1; + + Point2I bp = blockFirstPos; + F32 terrainHeights[2][TerrainBlock::BlockSize]; + U32 i; + + // get first set of heights + for(i = 0; i < TerrainBlock::BlockSize; i++, bp += blockRowStep) + terrainHeights[0][i] = fixedToFloat(getHeight(bp.x, bp.y)); + + // get second set of heights + bp = blockFirstPos + blockColStep; + for(i = 0; i < TerrainBlock::BlockSize; i++, bp += blockRowStep) + terrainHeights[1][i] = fixedToFloat(getHeight(bp.x, bp.y)); + + F32 * pTerrainHeights[2]; + pTerrainHeights[0] = static_cast(terrainHeights[0]); + pTerrainHeights[1] = static_cast(terrainHeights[1]); + + F32 heightStep = 1.f / blockStep; + + F32 terrainZRowStep[2][TerrainBlock::BlockSize]; + F32 terrainZColStep[TerrainBlock::BlockSize]; + + // fill in the row steps + for(i = 0; i < TerrainBlock::BlockSize; i++) + { + terrainZRowStep[0][i] = (terrainHeights[0][(i+1) & TerrainBlock::BlockMask] - terrainHeights[0][i]) * heightStep; + terrainZRowStep[1][i] = (terrainHeights[1][(i+1) & TerrainBlock::BlockMask] - terrainHeights[1][i]) * heightStep; + terrainZColStep[i] = (terrainHeights[1][i] - terrainHeights[0][i]) * heightStep; + } + + // get first row of process heights + for(i = 0; i < generateDim; i++) + { + U32 bi = i >> blockShift; + heightArray[i] = terrainHeights[0][bi] + (i & blockMask) * terrainZRowStep[0][bi]; + } + + bp = blockFirstPos; + if(generateDim == TerrainBlock::BlockSize) + bp += blockColStep; + + // generate the initial run + U32 x, y; + for(x = 1; x < generateDim; x++) + { + U32 xmask = x & blockMask; + + // generate new height step rows? + if(!xmask) + { + F32 * tmp = pTerrainHeights[0]; + pTerrainHeights[0] = pTerrainHeights[1]; + pTerrainHeights[1] = tmp; + + bp += blockColStep; + + Point2I bwalk = bp; + for(i = 0; i < TerrainBlock::BlockSize; i++, bwalk += blockRowStep) + pTerrainHeights[1][i] = fixedToFloat(getHeight(bwalk.x, bwalk.y)); + + // fill in the row steps + for(i = 0; i < TerrainBlock::BlockSize; i++) + { + terrainZRowStep[0][i] = (pTerrainHeights[0][(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[0][i]) * heightStep; + terrainZRowStep[1][i] = (pTerrainHeights[1][(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[1][i]) * heightStep; + terrainZColStep[i] = (pTerrainHeights[1][i] - pTerrainHeights[0][i]) * heightStep; + } + } + + Point2I bwalk = bp - blockRowStep; + for(y = 0; y < generateDim; y++) + { + U32 ymask = y & blockMask; + if(!ymask) + bwalk += blockRowStep; + + U32 bi = y >> blockShift; + U32 binext = (bi + 1) & TerrainBlock::BlockMask; + + F32 height; + + // 135? + if((bwalk.x ^ bwalk.y) & 1) + { + U32 xsub = blockStep - xmask; + if(xsub > ymask) // bottom + height = pTerrainHeights[0][bi] + xmask * terrainZColStep[bi] + + ymask * terrainZRowStep[0][bi]; + else // top + height = pTerrainHeights[1][bi] - xsub * terrainZColStep[binext] + + ymask * terrainZRowStep[1][bi]; + } + else + { + if(xmask > ymask) // bottom + height = pTerrainHeights[0][bi] + xmask * terrainZColStep[bi] + + ymask * terrainZRowStep[1][bi]; + else // top + height = pTerrainHeights[0][bi] + xmask * terrainZColStep[binext] + + ymask * terrainZRowStep[0][bi]; + } + + F32 intHeight = heightArray[y] * oneMinusFrac + heightArray[(y + fracStep) & generateMask] * frac + zStep; + nextHeightArray[y] = getMax(height, intHeight); + } + + // swap the height rows + for(y = 0; y < generateDim; y++) + heightArray[y] = nextHeightArray[y]; + } + + F32 squareSize = getSquareSize(); + F32 squaredSquareSize = squareSize * squareSize; + F32 lexelDim = squareSize * F32(TerrainBlock::BlockSize) / F32(TerrainBlock::LightmapSize); + + // calculate normal runs + Point3F normals[2][TerrainBlock::BlockSize]; + Point3F * pNormals[2]; + + pNormals[0] = static_cast(normals[0]); + pNormals[1] = static_cast(normals[1]); + + // calculate the normal lookup table + F32 * normTable = new F32 [blockStep * blockStep * 4]; + + Point2F corners[4] = { + Point2F(0.f, 0.f), + Point2F(1.f, 0.f), + Point2F(1.f, 1.f), + Point2F(0.f, 1.f) + }; + + U32 idx = 0; + F32 step = 1.f / blockStep; + F32 halfStep = step / 2.f; + Point2F pos(halfStep, halfStep); + + // fill it + for(x = 0; x < blockStep; x++, pos.x += step, pos.y = halfStep) + for(y = 0; y < blockStep; y++, pos.y += step) + for(U32 i = 0; i < 4; i++, idx++) + normTable[idx] = 1.f - getMin(Point2F(pos - corners[i]).len(), 1.f); + + // fill first column + bp = blockFirstPos; + for(x = 0; x < TerrainBlock::BlockSize; x++) + { + terrainHeights[0][x] = fixedToFloat(getHeight(bp.x, bp.y)); + Point2F pos(bp.x * squareSize, bp.y * squareSize); + getNormal(pos, &pNormals[1][x]); + bp += blockRowStep; + } + + // get swapped on first pass + pTerrainHeights[0] = static_cast(terrainHeights[1]); + pTerrainHeights[1] = static_cast(terrainHeights[0]); + + ColorF colors[TerrainBlock::LightmapSize]; + + F32 ratio = F32(1 << lightmapShift); + F32 inverseRatioSquared = 1.f / (ratio * ratio); + + // walk it... + bp = blockFirstPos - blockColStep; + Point2I lp = lmapFirstPos - blockColStep; + + for(x = 0; x < generateDim; x++) + { + U32 xmask = x & blockMask; + + // process lightmap? + if(!(x & lightmapMask)) + { + dMemset(colors, 0, sizeof(ColorF) * TerrainBlock::LightmapSize); + lp += blockColStep; + } + + // generate new runs? + if(!xmask) + { + bp += blockColStep; + + // do the normals + Point3F * temp = pNormals[0]; + pNormals[0] = pNormals[1]; + pNormals[1] = temp; + + // fill the row + Point2I bwalk = bp + blockColStep; + for(U32 i = 0; i < TerrainBlock::BlockSize; i++) + { + Point2F pos(bwalk.x * squareSize, bwalk.y * squareSize); + getNormal(pos, &pNormals[1][i]); + bwalk += blockRowStep; + } + + // do the heights + F32 * tmp = pTerrainHeights[0]; + pTerrainHeights[0] = pTerrainHeights[1]; + pTerrainHeights[1] = tmp; + + bwalk = bp + blockColStep; + for(i = 0; i < TerrainBlock::BlockSize; i++, bwalk += blockRowStep) + pTerrainHeights[1][i] = fixedToFloat(getHeight(bwalk.x, bwalk.y)); + + // fill in the row steps + for(i = 0; i < TerrainBlock::BlockSize; i++) + { + terrainZRowStep[0][i] = (pTerrainHeights[0][(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[0][i]) * heightStep; + terrainZRowStep[1][i] = (pTerrainHeights[1][(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[1][i]) * heightStep; + terrainZColStep[i] = (pTerrainHeights[1][i] - pTerrainHeights[0][i]) * heightStep; + } + } + + Point2I bwalk = bp - blockRowStep; + for(y = 0; y < generateDim; y++) + { + U32 ymask = y & blockMask; + if(!ymask) + bwalk += blockRowStep; + + U32 bi = y >> blockShift; + U32 binext = (bi + 1) & TerrainBlock::BlockMask; + + F32 height; + + // 135? + if((bwalk.x ^ bwalk.y) & 1) + { + U32 xsub = blockStep - xmask; + if(xsub > ymask) // bottom + height = pTerrainHeights[0][bi] + xmask * terrainZColStep[bi] + + ymask * terrainZRowStep[0][bi]; + else // top + height = pTerrainHeights[1][bi] - xsub * terrainZColStep[binext] + + ymask * terrainZRowStep[1][bi]; + } + else + { + if(xmask > ymask) // bottom + height = pTerrainHeights[0][bi] + xmask * terrainZColStep[bi] + + ymask * terrainZRowStep[1][bi]; + else // top + height = pTerrainHeights[0][bi] + xmask * terrainZColStep[binext] + + ymask * terrainZRowStep[0][bi]; + } + + F32 intHeight = heightArray[y] * oneMinusFrac + heightArray[(y + fracStep) & generateMask] * frac + zStep; + + ColorF & col = colors[y >> lightmapShift]; + + // non shadowed? + if(height >= intHeight) + { + U32 idx = (xmask + (ymask << blockShift)) << 2; + + Point3F normal; + normal = pNormals[0][bi] * normTable[idx++]; + normal += pNormals[0][binext] * normTable[idx++]; + normal += pNormals[1][binext] * normTable[idx++]; + normal += pNormals[1][bi] * normTable[idx]; + normal.normalize(); + + nextHeightArray[y] = height; + F32 colorScale = mDot(normal, lightDir); + if(colorScale >= 0) + col += ambient; + else + col += (ambient + lightColor * -colorScale); + } + else + { + nextHeightArray[y] = intHeight; + col += ambient; + } + } + + for(y = 0; y < generateDim; y++) + heightArray[y] = nextHeightArray[y]; + + // do some lighting stuff? + if(!((x+1) & lightmapMask)) + { + Point2I lwalk = lp; + U32 mask = TerrainBlock::LightmapSize - 1; + for(U32 i = 0; i < TerrainBlock::LightmapSize; i++) + { + U16 * ptr = (U16*)lightMap->getAddress(lp.x & mask, lp.y & mask); + *ptr = convertColor(colors[i]); + lp += blockRowStep; + } + } + } + + delete [] normTable; + delete [] heightArray; + delete [] nextHeightArray; +} + diff --git a/terrain/terrRender.cc b/terrain/terrRender.cc new file mode 100644 index 0000000..ecb87a5 --- /dev/null +++ b/terrain/terrRender.cc @@ -0,0 +1,2298 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/terrData.h" +#include "math/mMath.h" +#include "dgl/dgl.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/gBitmap.h" +#include "terrain/terrRender.h" +#include "dgl/materialList.h" +#include "scenegraph/sceneState.h" +#include "terrain/waterBlock.h" +#include "terrain/blender.h" +#include "sim/frameAllocator.h" +#include "scenegraph/sceneGraph.h" +#include "scenegraph/sgUtil.h" +#include "platform/profiler.h" + +struct LightTriangle { + ColorF color; + + Point2F texco1; + Point3F point1; + Point2F texco2; + Point3F point2; + Point2F texco3; + Point3F point3; + + LightTriangle* next; + U32 flags; // 0 if inactive +}; + +static LightTriangle* sgCurrLightTris = NULL; + + +GBitmap* TerrainRender::mBlendBitmap = NULL; + +S32 TerrainRender::mTextureMinSquareSize; + +bool TerrainRender::mEnableTerrainDetails = true; +bool TerrainRender::mEnableTerrainDynLights = true; + +U32 TerrainRender::mNumClipPlanes = 4; +MatrixF TerrainRender::mCameraToObject; + +AllocatedTexture TerrainRender::mTextureFrameListHead; +AllocatedTexture TerrainRender::mTextureFrameListTail; +AllocatedTexture TerrainRender::mTextureFreeListHead; +AllocatedTexture TerrainRender::mTextureFreeListTail; +AllocatedTexture TerrainRender::mTextureFreeBigListHead; +AllocatedTexture TerrainRender::mTextureFreeBigListTail; +AllocatedTexture *TerrainRender::mTextureGrid[AllocatedTextureCount]; +AllocatedTexture **TerrainRender::mTextureGridPtr[5]; +AllocatedTexture *TerrainRender::mCurrentTexture = NULL; +#ifdef __linux // Texture slop isn't necessary on Linux +U32 TerrainRender::mTextureSlopSize = 512; +#else +U32 TerrainRender::mTextureSlopSize = 220; +#endif + +static bool sgTextureFreeListPrimed = false; +static U32 sgFreeListPrimeCount = 32; +Vector TerrainRender::mTextureFreeList(__FILE__, __LINE__); + +SceneState* TerrainRender::mSceneState; + +TerrainBlock* TerrainRender::mCurrentBlock; +S32 TerrainRender::mSquareSize; +F32 TerrainRender::mScreenSize; +U32 TerrainRender::mFrameIndex; + +Point2F TerrainRender::mBlockPos; +Point2I TerrainRender::mBlockOffset; +Point2I TerrainRender::mTerrainOffset; +PlaneF TerrainRender::mClipPlane[MaxClipPlanes]; +Point3F TerrainRender::mCamPos; + +TextureHandle* TerrainRender::mGrainyTexture = NULL; +U32 TerrainRender::mDynamicLightCount; + +F32 TerrainRender::mPixelError; + +TerrLightInfo TerrainRender::mTerrainLights[MaxTerrainLights]; +bool TerrainRender::mRenderingCommander = false; + + +F32 TerrainRender::mScreenError; +F32 TerrainRender::mMinSquareSize; +F32 TerrainRender::mFarDistance; +S32 TerrainRender::mDynamicTextureCount; +S32 TerrainRender::mTextureSpaceUsed; +S32 TerrainRender::mLevelZeroCount; +S32 TerrainRender::mFullMipCount; +S32 TerrainRender::mStaticTextureCount; +S32 TerrainRender::mUnusedTextureCount; +S32 TerrainRender::mStaticTSU; +S32 sgFogRejectedBlocks = 0; +S32 maxTerrPoints = 100; + +ColorF TerrainRender::mFogColor; + +bool TerrainRender::mRenderOutline; + +U32 TerrainRender::mMaterialCount; +void (*TerrainRender::transformPoint)(U32,U32,U32) = NULL; +S32 TerrainRender::mSquareSeqAdd[256]; +U32 TerrainRender::mNewGenTextureCount; + +namespace { + +Point4F sgTexGenS; +Point4F sgTexGenT; +Point4F sgLMGenS; +Point4F sgLMGenT; + +} // namespace {} + + +static S32 getPower(S32 x) +{ + // Returns 2^n (the highest bit). + S32 i = 0; + if (x) + do + i++; + while (x >>= 1); + return i; +} + + +void TerrainRender::init() +{ + S32 i; + mTextureMinSquareSize = 0; + + mFrameIndex = 0; + + mScreenError = 4; + mScreenSize = 45; + mMinSquareSize = 4; + mFarDistance = 500; + mRenderOutline = false; + mTextureFrameListHead.next = &mTextureFrameListTail; + mTextureFrameListHead.previous = NULL; + mTextureFrameListTail.next = NULL; + mTextureFrameListTail.previous = &mTextureFrameListHead; + + mTextureFreeListHead.next = &mTextureFreeListTail; + mTextureFreeListHead.previous = NULL; + mTextureFreeListTail.next = NULL; + mTextureFreeListTail.previous = &mTextureFreeListHead; + + mTextureFreeBigListHead.next = &mTextureFreeBigListTail; + mTextureFreeBigListHead.previous = NULL; + mTextureFreeBigListTail.next = NULL; + mTextureFreeBigListTail.previous = &mTextureFreeBigListHead; + + for(i = 0; i < AllocatedTextureCount; i++) + mTextureGrid[i] = 0; + + mTextureGridPtr[0] = mTextureGrid; + mTextureGridPtr[1] = mTextureGrid + 4096; + mTextureGridPtr[2] = mTextureGrid + 4096 + 1024; + mTextureGridPtr[3] = mTextureGrid + 4096 + 1024 + 256; + mTextureGridPtr[4] = mTextureGrid + 4096 + 1024 + 256 + 64; + + for(i = 0; i < 256; i++) + { + mSquareSeqAdd[i] = 0; + for(S32 val = 0; val < 9; val++) + { + if(i & (1 << val)) + mSquareSeqAdd[i] += (1 << val) * (1 << val); + } + } + + mBlendBitmap = new GBitmap(128, 128, true, GBitmap::RGB5551); + Con::addVariable("maxTerrPoints", TypeS32, &maxTerrPoints); + Con::addVariable("T2::renderOutline", TypeBool, &mRenderOutline); + + Con::addVariable("T2::dynamicTextureCount", TypeS32, &mDynamicTextureCount); + Con::addVariable("T2::textureSpaceUsed", TypeS32, &mTextureSpaceUsed); + Con::addVariable("T2::unusedTextureCount", TypeS32, &mUnusedTextureCount); + + Con::addVariable("T2::FogRejections", TypeS32, &sgFogRejectedBlocks); + + Con::addVariable("T2::staticTextureCount", TypeS32, &mStaticTextureCount); + Con::addVariable("T2::levelZeroCount", TypeS32, &mLevelZeroCount); + Con::addVariable("T2::fullMipCount", TypeS32, &mFullMipCount); + Con::addVariable("T2::staticTSU", TypeS32, &mStaticTSU); + + Con::addVariable("screenSize", TypeF32, &mScreenSize); + Con::addVariable("farDistance", TypeF32, &mFarDistance); + Con::addVariable("pref::Terrain::texDetail", TypeS32, &mTextureMinSquareSize); + Con::addVariable("pref::Terrain::enableDetails", TypeBool, &mEnableTerrainDetails); + Con::addVariable("pref::Terrain::dynamicLights", TypeBool, &mEnableTerrainDynLights); + Con::addVariable("pref::Terrain::screenError", TypeF32, &mScreenError); + Con::addVariable("pref::Terrain::textureCacheSize", TypeS32, &mTextureSlopSize); +} + +//enum { +// AllocBlockSize = 1024 * 1024, +//}; +// +//U8 mAllocBuffer[AllocBlockSize]; +//U32 mAllocPos; + +void TerrainRender::shutdown() +{ + delete mBlendBitmap; + mBlendBitmap = NULL; + flushCache(); +} + +void TerrainRender::buildClippingPlanes(bool flipClipPlanes) +{ + F64 frustumParam[6]; + dglGetFrustum(&frustumParam[0], &frustumParam[1], + &frustumParam[2], &frustumParam[3], + &frustumParam[4], &frustumParam[5]); + + Point3F osCamPoint(0, 0, 0); + mCameraToObject.mulP(osCamPoint); + sgComputeOSFrustumPlanes(frustumParam, + mCameraToObject, + osCamPoint, + mClipPlane[4], + mClipPlane[0], + mClipPlane[1], + mClipPlane[2], + mClipPlane[3]); + // no need + mNumClipPlanes = 4; + // near plane is needed as well... + //PlaneF p(0, 1, 0, -frustumParam[4]); + //mTransformPlane(mCameraToObject, Point3F(1,1,1), p, &mClipPlane[0]); + + if (flipClipPlanes) { + mClipPlane[0].neg(); + mClipPlane[1].neg(); + mClipPlane[2].neg(); + mClipPlane[3].neg(); + mClipPlane[4].neg(); + mClipPlane[5].neg(); + } +} + +S32 TerrainRender::TestSquareVisibility(Point3F &min, Point3F &max, S32 mask, F32 expand) +{ + S32 retMask = 0; + Point3F minPoint, maxPoint; + for(S32 i = 0; i < mNumClipPlanes; i++) + { + if(mask & (1 << i)) + { + if(mClipPlane[i].x > 0) + { + maxPoint.x = max.x; + minPoint.x = min.x; + } + else + { + maxPoint.x = min.x; + minPoint.x = max.x; + } + if(mClipPlane[i].y > 0) + { + maxPoint.y = max.y; + minPoint.y = min.y; + } + else + { + maxPoint.y = min.y; + minPoint.y = max.y; + } + if(mClipPlane[i].z > 0) + { + maxPoint.z = max.z; + minPoint.z = min.z; + } + else + { + maxPoint.z = min.z; + minPoint.z = max.z; + } + F32 maxDot = mDot(maxPoint, mClipPlane[i]); + F32 minDot = mDot(minPoint, mClipPlane[i]); + F32 planeD = mClipPlane[i].d; + if(maxDot <= -(planeD + expand)) + return -1; + if(minDot <= -planeD) + retMask |= (1 << i); + } + } + return retMask; +} + +ChunkCornerPoint *TerrainRender::allocInitialPoint(Point3F pos) +{ + + ChunkCornerPoint *ret = (ChunkCornerPoint *) FrameAllocator::alloc(sizeof(ChunkCornerPoint)); + ret->x = pos.x; + ret->y = pos.y; + ret->z = pos.z; + ret->distance = (*ret - mCamPos).len(); + ret->xfIndex = 0; + return ret; +} + +ChunkCornerPoint *TerrainRender::allocPoint(Point2I pos) +{ + ChunkCornerPoint *ret = (ChunkCornerPoint *) FrameAllocator::alloc(sizeof(ChunkCornerPoint)); + ret->x = pos.x * mSquareSize + mBlockPos.x; + ret->y = pos.y * mSquareSize + mBlockPos.y; + ret->z = fixedToFloat(mCurrentBlock->getHeight(pos.x, pos.y)); + ret->distance = (*ret - mCamPos).len(); + gClientSceneGraph->getFogCoordPair(ret->distance, ret->z, ret->fogRed, ret->fogGreen); + + ret->xfIndex = 0; + return ret; +} + +void TerrainRender::allocRenderEdges(U32 edgeCount, EdgeParent **dest, bool renderEdge) +{ + if(renderEdge) + { + for(U32 i = 0; i < edgeCount; i++) + { + ChunkEdge *edge = (ChunkEdge *) FrameAllocator::alloc(sizeof(ChunkEdge)); + edge->c1 = NULL; + edge->c2 = NULL; + edge->xfIndex = 0; + dest[i] = edge; + } + } + else + { + for(U32 i = 0; i < edgeCount; i++) + { + ChunkScanEdge *edge = (ChunkScanEdge *) FrameAllocator::alloc(sizeof(ChunkScanEdge)); + edge->mp = NULL; + dest[i] = edge; + } + } +} + +void TerrainRender::subdivideChunkEdge(ChunkScanEdge *e, Point2I pos, bool chunkEdge) +{ + if(!e->mp) + { + allocRenderEdges(2, &e->e1, chunkEdge); + e->mp = allocPoint(pos); + + e->e1->p1 = e->p1; + e->e1->p2 = e->mp; + e->e2->p1 = e->mp; + e->e2->p2 = e->p2; + } +} + +F32 TerrainRender::getSquareDistance(const Point3F& minPoint, const Point3F& maxPoint, F32* zDiff) +{ + Point3F vec; + if(mCamPos.z < minPoint.z) + vec.z = minPoint.z - mCamPos.z; + else if(mCamPos.z > maxPoint.z) + vec.z = maxPoint.z - mCamPos.z; + else + vec.z = 0; + + if(mCamPos.x < minPoint.x) + vec.x = minPoint.x - mCamPos.x; + else if(mCamPos.x > maxPoint.x) + vec.x = mCamPos.x - maxPoint.x; + else + vec.x = 0; + + if(mCamPos.y < minPoint.y) + vec.y = minPoint.y - mCamPos.y; + else if(mCamPos.y > maxPoint.y) + vec.y = mCamPos.y - maxPoint.y; + else + vec.y = 0; + + *zDiff = vec.z; + + return vec.len(); +} + +void TerrainRender::emitTerrChunk(SquareStackNode *n, F32 squareDistance, U32 lightMask, bool farClip, bool drawDetails) +{ + //if(n->pos.x || n->pos.y) + // return; + GridChunk *gc = mCurrentBlock->findChunk(n->pos); + EmitChunk *chunk = (EmitChunk *) FrameAllocator::alloc(sizeof(EmitChunk)); + chunk->x = n->pos.x + mBlockOffset.x + mTerrainOffset.x; + chunk->y = n->pos.y + mBlockOffset.y + mTerrainOffset.y; + chunk->gridX = n->pos.x; + chunk->gridY = n->pos.y; + chunk->lightMask = lightMask; + + chunk->next = mCurrentTexture->list; + mCurrentTexture->list = chunk; + + if(mRenderingCommander) + return; + + chunk->edge[0] = (ChunkEdge *) n->top; + chunk->edge[1] = (ChunkEdge *) n->right; + chunk->edge[2] = (ChunkEdge *) n->bottom; + chunk->edge[3] = (ChunkEdge *) n->left; + + chunk->edge[0]->c2 = chunk; + chunk->edge[1]->c1 = chunk; + chunk->edge[2]->c1 = chunk; + chunk->edge[3]->c2 = chunk; + + + // holes only in the primary terrain block + if (gc->emptyFlags && mBlockPos.x == 0 && mBlockPos.y == 0) + chunk->emptyFlags = gc->emptyFlags; + else + chunk->emptyFlags = 0; + + S32 subDivLevel; + F32 growFactor = 0; + + F32 minSubdivideDistance = 1000000; + chunk->clip = farClip; + + chunk->renderDetails = drawDetails; + + if(squareDistance < 1) + subDivLevel = -1; + else + { + for(subDivLevel = 2; subDivLevel >= 0; subDivLevel--) + { + F32 subdivideDistance = fixedToFloat(gc->heightDeviance[subDivLevel]) / mPixelError; + if(subdivideDistance > minSubdivideDistance) + subdivideDistance = minSubdivideDistance; + + if(squareDistance >= subdivideDistance) + break; + F32 clampDistance = subdivideDistance * 0.75; + if(squareDistance > clampDistance) + { + growFactor = (squareDistance - clampDistance) / (0.25 * subdivideDistance); + subDivLevel--; + break; + } + minSubdivideDistance = clampDistance; + } + } + chunk->subDivLevel = subDivLevel; + chunk->growFactor = growFactor; +} + +void TerrainRender::textureRecurse(SquareStackNode *stack) +{ + S32 curStackSize = 1; + Point3F minPoint, maxPoint; + F32 squareDistance; + + while(curStackSize) + { + SquareStackNode *n = stack + curStackSize - 1; + // see if it's visible + Point2I pos = n->pos; + S32 squareSz = mSquareSize << n->level; + GridSquare *sq = mCurrentBlock->findSquare(n->level, pos); + + minPoint.set(mSquareSize * pos.x + mBlockPos.x, + mSquareSize * pos.y + mBlockPos.y, + fixedToFloat(sq->minHeight)); + maxPoint.set(minPoint.x + (mSquareSize << n->level), + minPoint.y + (mSquareSize << n->level), + fixedToFloat(sq->maxHeight)); + + F32 zDiff; + squareDistance = getSquareDistance(minPoint, maxPoint, &zDiff); + + // holes only in the primary terrain block + if (squareDistance >= mFarDistance || + ((sq->flags & GridSquare::Empty) && mBlockPos.x == 0 && mBlockPos.y == 0)) + { + curStackSize--; + continue; + } + // first check the level - if its 3 or less, we have to just make a bitmap: + // level 3 == 8x8 square - 8x8 * 16x16 == 128x128 + S32 mipLevel = 7; + if(n->level > 6) + goto norecalloc; + if(n->level > mTextureMinSquareSize + 2) + { + // get the mip level of the square and see if we're in range + if(squareDistance > 0.001) + { + S32 size = S32(dglProjectRadius(squareDistance + (squareSz >> 1), squareSz)); + mipLevel = getPower(size * 0.75); + //if(n->level >= 6) + //{ + // if(mipLevel - n->level > 2) + // goto norecalloc; + //} + //else + if(mipLevel > 7) // too big for this square + goto norecalloc; + } + else + goto norecalloc; + } + + allocTerrTexture(n->pos, n->level, mipLevel, false, squareDistance); + curStackSize--; + continue; +norecalloc: + // split it up: + S32 nextLevel = n->level - 1; + for(S32 i = 0; i < 4; i++) + n[i].level = nextLevel; + + S32 squareHalfSize = 1 << nextLevel; + + // push in reverse order of processing. + n[3].pos = pos; + n[2].pos.set(pos.x + squareHalfSize, pos.y); + n[1].pos.set(pos.x, pos.y + squareHalfSize); + n[0].pos.set(pos.x + squareHalfSize, pos.y + squareHalfSize); + curStackSize += 3; + } +} + + + +void TerrainRender::processCurrentBlock2(SceneState*, EdgeParent *topEdge, EdgeParent *rightEdge, EdgeParent *bottomEdge, EdgeParent *leftEdge) +{ + SquareStackNode stack[TerrainBlock::BlockShift*4]; + Point3F minPoint, maxPoint; + + stack[0].level = TerrainBlock::BlockShift; + stack[0].clipFlags = ((1 << mNumClipPlanes) - 1) | FarSphereMask; // test all the planes + stack[0].pos.set(0,0); + stack[0].top = topEdge; + stack[0].right = rightEdge; + stack[0].bottom = bottomEdge; + stack[0].left = leftEdge; + stack[0].lightMask = (1 << mDynamicLightCount) - 1; // test all the lights + stack[0].texAllocated = false; + + Vector *posFog = mSceneState->getPosFogBands(); + Vector *negFog = mSceneState->getNegFogBands(); + bool clipAbove = posFog->size() > 0 && (*posFog)[0].isFog == false; + bool clipBelow = negFog->size() > 0 && (*negFog)[0].isFog == false; + bool clipOn = posFog->size() > 0 && (*posFog)[0].isFog == true; + + if(posFog->size() != 0 || negFog->size() != 0) + stack[0].clipFlags |= FogPlaneBoxMask; + + S32 curStackSize = 1; + F32 squareDistance; + + //F32 zeroFullMipDistance = + //8 = (squareSize / (dist + squareSize * .5) ) * worldToScreenScale; + //(dist + squareSize * .5) * 8 = squareSize * wts; + //dist = (squareSize * wts) / (1 << mipLevel) - squareSize * .5 + + F32 worldToScreenScale = dglProjectRadius(1,1); + F32 zeroFullMipDistance = (mSquareSize * worldToScreenScale) / (1 << 3) - (mSquareSize >> 1); + F32 zeroSmallMipDistance = (mSquareSize * worldToScreenScale) / (1 << 6) - (mSquareSize >> 1); + F32 zeroDetailDistance = (mSquareSize * worldToScreenScale) / (1 << 6) - (mSquareSize >> 1); + + while(curStackSize) + { + SquareStackNode *n = stack + curStackSize - 1; + // see if it's visible + GridSquare *sq = mCurrentBlock->findSquare(n->level, n->pos); + + minPoint.set(mSquareSize * n->pos.x + mBlockPos.x, + mSquareSize * n->pos.y + mBlockPos.y, + fixedToFloat(sq->minHeight)); + maxPoint.set(minPoint.x + (mSquareSize << n->level), + minPoint.y + (mSquareSize << n->level), + fixedToFloat(sq->maxHeight)); + + // holes only in the primary terrain block + if ((sq->flags & GridSquare::Empty) && mBlockPos.x == 0 && mBlockPos.y == 0) + { + curStackSize--; + continue; + } + + F32 zDiff; + squareDistance = getSquareDistance(minPoint, maxPoint, &zDiff); + + S32 nextClipFlags = 0; + + if(n->clipFlags) + { + if(n->clipFlags & FogPlaneBoxMask) + { + F32 camZ = mCamPos.z; + bool boxBelow = camZ > maxPoint.z; + bool boxAbove = camZ < minPoint.z; + bool boxOn = !(boxAbove || boxBelow); + if( clipOn || + (clipAbove && boxAbove && (maxPoint.z - camZ > (*posFog)[0].cap)) || + (clipBelow && boxBelow && (camZ - minPoint.z > (*negFog)[0].cap)) || + (boxOn && (( clipAbove && maxPoint.z - camZ > (*posFog)[0].cap ) || + ( clipBelow && camZ - minPoint.z > (*negFog)[0].cap )))) + { + if(boxBelow && !mSceneState->isBoxFogVisible(squareDistance, maxPoint.z, minPoint.z)) + { + // only clip out terrain below the camera + // otherwise the sky can show through + // which SUCKS! + curStackSize--; + continue; + } + nextClipFlags |= FogPlaneBoxMask; + } + } + if(n->clipFlags & FarSphereMask) + { + if(squareDistance >= mFarDistance) + { + curStackSize--; + continue; + } + + S32 squareSz = mSquareSize << n->level; + if(squareDistance + maxPoint.z - minPoint.z + squareSz + squareSz > mFarDistance) + nextClipFlags |= FarSphereMask; + } + + // zDelta for screen error height deviance. + F32 zDelta = squareDistance * mPixelError; + minPoint.z -= zDelta; + maxPoint.z += zDelta; + + nextClipFlags |= TestSquareVisibility(minPoint, maxPoint, n->clipFlags, mSquareSize); + if(nextClipFlags == -1) + { + //if(!n->texAllocated) + // textureRecurse(n); + + // trivially rejected, so pop it off the stack + curStackSize--; + continue; + } + } + if(!n->texAllocated) + { + S32 squareSz = mSquareSize << n->level; + // first check the level - if its 3 or less, we have to just make a bitmap: + // level 3 == 8x8 square - 8x8 * 16x16 == 128x128 + if(n->level > 6) + goto notexalloc; + + S32 mipLevel = 7; + if(!mRenderingCommander) + { + if(n->level > mTextureMinSquareSize + 2) + { + // get the mip level of the square and see if we're in range + if(squareDistance > 0.001) + { + S32 size = S32(dglProjectRadius(squareDistance + (squareSz >> 1), squareSz)); + mipLevel = getPower(size * 0.75); + if(mipLevel > 7) // too big for this square + goto notexalloc; + } + else + goto notexalloc; + } + } + allocTerrTexture(n->pos, n->level, mipLevel, true, squareDistance); + n->texAllocated = true; + if(mRenderingCommander) // level == 6 + { + emitTerrChunk(n, 0, 0, 0, 0); + curStackSize--; + continue; + } + } +notexalloc: + if(n->lightMask) + n->lightMask = TestSquareLights(sq, n->level, n->pos, n->lightMask); + + if(n->level == 2) + { + AssertFatal(n->texAllocated, "Invalid texture index."); + + bool drawDetails = false; + if (mEnableTerrainDetails && squareDistance < zeroDetailDistance) + drawDetails = true; + + emitTerrChunk(n, squareDistance, n->lightMask, nextClipFlags & FarSphereMask, drawDetails); + curStackSize--; + continue; + } + bool allocChunkEdges = (n->level == 3); + + Point2I pos = n->pos; + + ChunkScanEdge *top = (ChunkScanEdge *) n->top; + ChunkScanEdge *right = (ChunkScanEdge *) n->right; + ChunkScanEdge *bottom = (ChunkScanEdge *) n->bottom; + ChunkScanEdge *left = (ChunkScanEdge *) n->left; + + // subdivide this square and throw it on the stack + S32 squareOneSize = 1 << n->level; + S32 squareHalfSize = squareOneSize >> 1; + + ChunkCornerPoint *midPoint = allocPoint(Point2I(pos.x + squareHalfSize, pos.y + squareHalfSize)); + S32 nextLevel = n->level - 1; + + subdivideChunkEdge(top, Point2I(pos.x + squareHalfSize, pos.y + squareOneSize), allocChunkEdges); + subdivideChunkEdge(right, Point2I(pos.x + squareOneSize, pos.y + squareHalfSize), allocChunkEdges); + subdivideChunkEdge(bottom, Point2I(pos.x + squareHalfSize, pos.y), allocChunkEdges); + subdivideChunkEdge(left, Point2I(pos.x, pos.y + squareHalfSize), allocChunkEdges); + + // cross edges go top, right, bottom, left + EdgeParent *crossEdges[4]; + allocRenderEdges(4, crossEdges, allocChunkEdges); + crossEdges[0]->p1 = top->mp; + crossEdges[0]->p2 = midPoint; + crossEdges[1]->p1 = midPoint; + crossEdges[1]->p2 = right->mp; + crossEdges[2]->p1 = midPoint; + crossEdges[2]->p2 = bottom->mp; + crossEdges[3]->p1 = left->mp; + crossEdges[3]->p2 = midPoint; + + n->level = nextLevel; + n->clipFlags = nextClipFlags; + + for(S32 i = 1; i < 4; i++) + { + n[i].level = nextLevel; + n[i].clipFlags = nextClipFlags; + n[i].lightMask = n->lightMask; + n[i].texAllocated = n->texAllocated; + } + // push in reverse order of processing. + n[3].pos = pos; + n[3].top = crossEdges[3]; + n[3].right = crossEdges[2]; + n[3].bottom = bottom->e1; + n[3].left = left->e2; + + n[2].pos.set(pos.x + squareHalfSize, pos.y); + n[2].top = crossEdges[1]; + n[2].right = right->e2; + n[2].bottom = bottom->e2; + n[2].left = crossEdges[2]; + + n[1].pos.set(pos.x, pos.y + squareHalfSize); + n[1].top = top->e1; + n[1].right = crossEdges[0]; + n[1].bottom = crossEdges[3]; + n[1].left = left->e1; + + n[0].pos.set(pos.x + squareHalfSize, pos.y + squareHalfSize); + n[0].top = top->e2; + n[0].right = right->e1; + n[0].bottom = crossEdges[1]; + n[0].left = crossEdges[0]; + + curStackSize += 3; + } +} + + + + + +//--------------------------------------------------------------- +//--------------------------------------------------------------- +// Root block render function +//--------------------------------------------------------------- +//--------------------------------------------------------------- + +void TerrainRender::fixEdge(ChunkEdge *edge, S32 x, S32 y, S32 dx, S32 dy) +{ + S32 minLevel, maxLevel; + F32 growFactor; + + if(edge->c1) + { + minLevel = edge->c1->subDivLevel; + maxLevel = edge->c1->subDivLevel; + growFactor = edge->c1->growFactor; + if(edge->c2) + { + if(edge->c2->subDivLevel < minLevel) + minLevel = edge->c2->subDivLevel; + else if(edge->c2->subDivLevel > maxLevel) + { + maxLevel = edge->c2->subDivLevel; + growFactor = edge->c2->growFactor; + } + else if(edge->c2->growFactor > growFactor) + growFactor = edge->c2->growFactor; + } + } + else + { + minLevel = maxLevel = edge->c2->subDivLevel; + growFactor = edge->c2->growFactor; + } + if(minLevel == 2) + { + edge->pointCount = 0; + return; + } + + // get the mid heights + EdgePoint *pmid = &edge->pt[1]; + ChunkCornerPoint *p1 = edge->p1; + ChunkCornerPoint *p2 = edge->p2; + + pmid->x = (p1->x + p2->x) * 0.5; + pmid->y = (p1->y + p2->y) * 0.5; + + if(maxLevel == 2) + { + // pure interp + pmid->z = (p1->z + p2->z) * 0.5; + pmid->distance = (*pmid - mCamPos).len(); + pmid->fogRed = (p1->fogRed + p2->fogRed) * 0.5; + pmid->fogGreen = (p1->fogGreen + p2->fogGreen) * 0.5; + + if(minLevel >= 0) + { + edge->pointCount = 1; + return; + } + } + else + { + pmid->z = fixedToFloat(mCurrentBlock->getHeight(x + dx + dx, y + dy + dy)); + if(maxLevel == 1) // interp the z and haze + pmid->z = pmid->z + growFactor * (((p1->z + p2->z) * 0.5) - pmid->z); + + pmid->distance = (*pmid - mCamPos).len(); + gClientSceneGraph->getFogCoordPair(pmid->distance, pmid->z, pmid->fogRed, pmid->fogGreen); + + if(maxLevel == 1) // interp the z and haze + { + pmid->fogRed = pmid->fogRed + growFactor * (((p1->fogRed + p2->fogRed) * 0.5) - pmid->fogRed); + pmid->fogGreen = pmid->fogGreen + growFactor * (((p1->fogGreen + p2->fogGreen) * 0.5) - pmid->fogGreen); + } + if(minLevel >= 0) + { + edge->pointCount = 1; + return; + } + } + // last case - minLevel == -1, midPoint calc'd + edge->pointCount = 3; + EdgePoint *pm1 = &edge->pt[0]; + EdgePoint *pm2 = &edge->pt[2]; + + pm1->x = (p1->x + pmid->x) * 0.5; + pm1->y = (p1->y + pmid->y) * 0.5; + pm2->x = (p2->x + pmid->x) * 0.5; + pm2->y = (p2->y + pmid->y) * 0.5; + + if(maxLevel != -1) + { + // clamp it: + pm1->z = (p1->z + pmid->z) * 0.5; + pm1->distance = (*pm1 - mCamPos).len(); + pm1->fogRed = (p1->fogRed + pmid->fogRed) * 0.5; + pm1->fogGreen = (p1->fogGreen + pmid->fogGreen) * 0.5; + + pm2->z = (p2->z + pmid->z) * 0.5; + pm2->distance = (*pm2 - mCamPos).len(); + pm2->fogRed = (p2->fogRed + pmid->fogRed) * 0.5; + pm2->fogGreen = (p2->fogGreen + pmid->fogGreen) * 0.5; + return; + } + // compute the real deals: + pm1->z = fixedToFloat(mCurrentBlock->getHeight(x + dx, y + dy)); + pm2->z = fixedToFloat(mCurrentBlock->getHeight(x + dx + dx + dx, y + dy + dy + dy)); + + if(growFactor) + { + pm1->z = pm1->z + growFactor * (((p1->z + pmid->z) * 0.5) - pm1->z); + pm2->z = pm2->z + growFactor * (((p2->z + pmid->z) * 0.5) - pm2->z); + } + pm1->distance = (*pm1 - mCamPos).len(); + gClientSceneGraph->getFogCoordPair(pm1->distance, pm1->z, pm1->fogRed, pm1->fogGreen); + + pm2->distance = (*pm2 - mCamPos).len(); + gClientSceneGraph->getFogCoordPair(pm2->distance, pm2->z, pm2->fogRed, pm2->fogGreen); + + if(growFactor) + { + pm1->fogRed = pm1->fogRed + growFactor * (((p1->fogRed + pmid->fogRed) * 0.5) - pm1->fogRed); + pm1->fogGreen = pm1->fogGreen + growFactor * (((p1->fogGreen + pmid->fogGreen) * 0.5) - pm1->fogGreen); + + pm2->fogRed = pm2->fogRed + growFactor * (((p2->fogRed + pmid->fogRed) * 0.5) - pm2->fogRed); + pm2->fogGreen = pm2->fogGreen + growFactor * (((p2->fogGreen + pmid->fogGreen) * 0.5) - pm2->fogGreen); + } +} + +EdgePoint *mXFVertices = NULL; +U16 *mXFIndexBuffer; +U16 *mXFIndexPtr; +U32 mXFIndexCount; + +U32 mXFPointCount; +U32 mXFIndex; + +inline U32 clipPoint(EdgePoint *p1, EdgePoint *p2, F32 dist) +{ + F32 frac = (dist - p1->distance) / (p2->distance - p1->distance); + U32 clipIndex = mXFPointCount++; + + EdgePoint *ip = mXFVertices + clipIndex; + ip->x = p2->x * frac + p1->x * (1 - frac); + ip->y = p2->y * frac + p1->y * (1 - frac); + ip->z = p2->z * frac + p1->z * (1 - frac); + ip->fogRed = p2->fogRed * frac + p1->fogRed * (1 - frac); + ip->fogGreen = p2->fogGreen * frac + p1->fogGreen * (1 - frac); + + ip->distance = dist; + return clipIndex; +} + +void TerrainRender::clip(U32 indexStart) +{ + static U16 dest[16 * 3 * 2]; + + + U32 vertexCount = mXFIndexBuffer[indexStart + 1]; + U32 centerPoint = mXFIndexBuffer[indexStart + 2]; + U16 *source = mXFIndexBuffer + indexStart + 3; + EdgePoint *center = mXFVertices + centerPoint; + + U32 destIndex = 0; + + if(mXFVertices[centerPoint].distance > mFarDistance) + { + // loop through all the tris and clip em: + // there are vertexCount - 1 triangles. + EdgePoint *p1 = mXFVertices + source[0]; + bool p1out = p1->distance >= mFarDistance; + U32 p1idx = source[0]; + U32 clip1; + if(!p1out) + clip1 = clipPoint(p1, center, mFarDistance); + for(U32 i = 0; i < vertexCount - 2; i++) + { + U32 p2idx = source[i+1]; + EdgePoint *p2 = mXFVertices + p2idx; + bool p2out = p2->distance >= mFarDistance; + if(!p2out) + { + U32 clip2 = clipPoint(p2, center, mFarDistance); + if(p1out) + { + // p2 is the only "in" point: + dest[destIndex++] = p2idx; + dest[destIndex++] = clip2; + dest[destIndex++] = clipPoint(p1, p2, mFarDistance); + } + else + { + dest[destIndex++] = clip2; + dest[destIndex++] = clip1; + dest[destIndex++] = p1idx; + dest[destIndex++] = p2idx; + dest[destIndex++] = clip2; + dest[destIndex++] = p1idx; + } + clip1 = clip2; + } + else if(!p1out) + { + dest[destIndex++] = p1idx; + dest[destIndex++] = clipPoint(p1, p2, mFarDistance); + dest[destIndex++] = clip1; + } + p1idx = p2idx; + p1out = p2out; + p1 = p2; + } + if(destIndex) + { + // copy this in.. + mXFIndexBuffer[indexStart] = GL_TRIANGLES; + mXFIndexBuffer[indexStart + 1] = destIndex; + for(U32 i = 0; i < destIndex; i++) + mXFIndexBuffer[indexStart + i + 2] = dest[i]; + mXFIndexCount = destIndex + indexStart + 2; + } + else + mXFIndexCount = indexStart; + } + else + { + EdgePoint *prev = mXFVertices + source[0]; + bool prevIn = prev->distance <= mFarDistance; + U32 i; + + for(i = 1; i < vertexCount - 1; i++) + { + EdgePoint *pt = mXFVertices + source[i]; + bool curIn = pt->distance <= mFarDistance; + + if((curIn && !prevIn) || (!curIn && prevIn)) + dest[destIndex++] = clipPoint(pt, prev, mFarDistance); + if(curIn) + dest[destIndex++] = source[i]; + else + dest[destIndex++] = clipPoint(pt, center, mFarDistance); + prev = pt; + prevIn = curIn; + } + for(i = 0; i < destIndex; i++) + mXFIndexBuffer[indexStart + i + 3] = dest[i]; + mXFIndexBuffer[indexStart + destIndex + 3] = dest[0]; + mXFIndexBuffer[indexStart + 1] = destIndex + 2; + mXFIndexCount = indexStart + destIndex + 4; + } +} + +inline U32 TerrainRender::constructPoint(S32 x, S32 y) +{ + U32 ret = mXFPointCount++; + EdgePoint *pt = mXFVertices + ret; + + pt->x = x * mSquareSize; + pt->y = y * mSquareSize; + pt->z = fixedToFloat(mCurrentBlock->getHeight(x, y)); + F32 dx = pt->x - mCamPos.x, dy = pt->y - mCamPos.y; + + pt->distance = (*pt - mCamPos).len(); + + gClientSceneGraph->getFogCoordPair(pt->distance, pt->z, pt->fogRed, pt->fogGreen); + + return ret; +} + +inline U32 TerrainRender::interpPoint(U32 p1, U32 p2, S32 x, S32 y, F32 growFactor) +{ + U32 ret = mXFPointCount++; + EdgePoint *pt = mXFVertices + ret; + + pt->x = x * mSquareSize; + pt->y = y * mSquareSize; + pt->z = fixedToFloat(mCurrentBlock->getHeight(x, y)); + pt->z = pt->z + growFactor * (((mXFVertices[p1].z + mXFVertices[p2].z) * 0.5) - pt->z); + + pt->distance = (*pt - mCamPos).len(); + + gClientSceneGraph->getFogCoordPair(pt->distance, pt->z, pt->fogRed, pt->fogGreen); + +// pt->fogRed = pt->fogRed + growFactor * (((mXFVertices[p1].fogRed + mXFVertices[p2].fogRed) * 0.5) - pt->fogRed); +// pt->fogGreen = pt->fogGreen + growFactor * (((mXFVertices[p1].fogGreen + mXFVertices[p2].fogGreen) * 0.5) - pt->fogGreen); + + pt->distance = (*pt - mCamPos).len(); + return ret; +} + +inline void TerrainRender::addEdge(ChunkEdge *edge) +{ + if(edge->pointCount == 1) + { + edge->pointIndex = mXFPointCount; + mXFVertices[mXFPointCount++] = * ((EdgePoint *) &edge->pt[1]); + } + else if(edge->pointCount == 3) + { + edge->pointIndex = mXFPointCount; + mXFVertices[mXFPointCount++] = *((EdgePoint *) &edge->pt[0]); + mXFVertices[mXFPointCount++] = *((EdgePoint *) &edge->pt[1]); + mXFVertices[mXFPointCount++] = *((EdgePoint *) &edge->pt[2]); + } + edge->xfIndex = mXFIndex; +} + +inline void emitTri(U32 i1, U32 i2, U32 i3) +{ + mXFIndexBuffer[mXFIndexCount] = i1; + mXFIndexBuffer[mXFIndexCount + 1] = i2; + mXFIndexBuffer[mXFIndexCount + 2] = i3; + mXFIndexCount += 3; +} + +inline U32 emitCornerPoint(ChunkCornerPoint *p) +{ + if(p->xfIndex != mXFIndex) + { + p->pointIndex = mXFPointCount; + p->xfIndex = mXFIndex; + mXFVertices[mXFPointCount++] = *((EdgePoint *) p); + } + return p->pointIndex; +} + +void buildLightTri(LightTriangle* pTri, TerrLightInfo* pInfo) +{ + // Get the plane normal + Point3F normal; + mCross((pTri->point1 - pTri->point2), (pTri->point3 - pTri->point2), &normal); + if (normal.lenSquared() < 1e-7) + { + pTri->flags = 0; + return; + } + + + PlaneF plane(pTri->point2, normal); // Assumes that mPlane.h normalizes incoming point + + Point3F centerPoint; + F32 d = plane.distToPlane(pInfo->pos); + centerPoint = pInfo->pos - plane * d; + d = mFabs(d); + if (d >= pInfo->radius) { + pTri->flags = 0; + return; + } + + F32 mr = mSqrt(pInfo->radiusSquared - d*d); + + Point3F normalS; + Point3F normalT; + mCross(plane, Point3F(0, 1, 0), &normalS); + mCross(plane, normalS, &normalT); + PlaneF splane(centerPoint, normalS); // Assumes that mPlane.h normalizes incoming point + PlaneF tplane(centerPoint, normalT); // Assumes that mPlane.h normalizes incoming point + + pTri->color.red = pInfo->r; + pTri->color.green = pInfo->g; + pTri->color.blue = pInfo->b; + pTri->color.alpha = (pInfo->radius - d) / pInfo->radius; + + pTri->texco1.set(((splane.distToPlane(pTri->point1) / mr) + 1.0) / 2.0, + ((tplane.distToPlane(pTri->point1) / mr) + 1.0) / 2.0); + pTri->texco2.set(((splane.distToPlane(pTri->point2) / mr) + 1.0) / 2.0, + ((tplane.distToPlane(pTri->point2) / mr) + 1.0) / 2.0); + pTri->texco3.set(((splane.distToPlane(pTri->point3) / mr) + 1.0) / 2.0, + ((tplane.distToPlane(pTri->point3) / mr) + 1.0) / 2.0); + + pTri->flags = 1; +} + +void TerrainRender::renderChunkCommander(EmitChunk *chunk) +{ + U32 ll = mXFPointCount; + for(U32 y = 0; y <= 64; y += 4) + for(U32 x = (y & 4) ? 4 : 0; x <= 64; x += 8) + constructPoint(chunk->x + x,chunk->y + y); + + for(U32 y = 0; y < 8; y++) + { + for(U32 x = 0; x < 8; x++) + { + U16 *ib = mXFIndexBuffer + mXFIndexCount; + ib[0] = GL_TRIANGLE_FAN; + ib[1] = 6; + ib[2] = ll + 9; + ib[3] = ll; + ib[4] = ll + 17; + ib[5] = ll + 18; + ib[6] = ll + 1; + ib[7] = ll; + mXFIndexCount += 8; + ll++; + } + ll += 9; + } +} + +void TerrainRender::renderChunkOutline(EmitChunk *chunk) +{ + U32 startXFIndex = mXFIndexCount; + + ChunkEdge *e0 = chunk->edge[0]; + ChunkEdge *e1 = chunk->edge[1]; + ChunkEdge *e2 = chunk->edge[2]; + ChunkEdge *e3 = chunk->edge[3]; + + if(e0->xfIndex != mXFIndex) + { + if(!e0->xfIndex) + fixEdge(e0, chunk->x, chunk->y + 4, 1, 0); + addEdge(e0); + } + if(e1->xfIndex != mXFIndex) + { + if(!e1->xfIndex) + fixEdge(e1, chunk->x + 4, chunk->y + 4, 0, -1); + addEdge(e1); + } + if(e2->xfIndex != mXFIndex) + { + if(!e2->xfIndex) + fixEdge(e2, chunk->x, chunk->y, 1, 0); + addEdge(e2); + } + if(e3->xfIndex != mXFIndex) + { + if(!e3->xfIndex) + fixEdge(e3, chunk->x, chunk->y + 4, 0, -1); + addEdge(e3); + } + U32 p0 = emitCornerPoint(e0->p1); + U32 p1 = emitCornerPoint(e0->p2); + U32 p2 = emitCornerPoint(e2->p2); + U32 p3 = emitCornerPoint(e2->p1); + + // build the interior points: + U32 ip0 = constructPoint(chunk->x + 2, chunk->y + 2); + F32 growFactor = chunk->growFactor; + + if(chunk->subDivLevel >= 1) + { + // just emit the fan for the whole square: + S32 i; + mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; + U32 indexStart = mXFIndexCount++; + mXFIndexBuffer[mXFIndexCount++] = ip0; + + mXFIndexBuffer[mXFIndexCount++] = p0; + for(i = 0; i < e0->pointCount; i++) + mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + i; + + mXFIndexBuffer[mXFIndexCount++] = p1; + for(i = 0; i < e1->pointCount; i++) + mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + i; + + mXFIndexBuffer[mXFIndexCount++] = p2; + for(i = e2->pointCount - 1; i >= 0; i--) + mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + i; + + mXFIndexBuffer[mXFIndexCount++] = p3; + for(i = e3->pointCount - 1; i >= 0; i--) + mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + i; + + mXFIndexBuffer[mXFIndexCount++] = p0; + + mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; + if(chunk->clip) + clip(indexStart - 1); + } + else + { + if(chunk->subDivLevel == 0) + { + U32 ip1 = interpPoint(p0, ip0, chunk->x + 1, chunk->y + 3, growFactor); + U32 ip2 = interpPoint(p1, ip0, chunk->x + 3, chunk->y + 3, growFactor); + U32 ip3 = interpPoint(p2, ip0, chunk->x + 3, chunk->y + 1, growFactor); + U32 ip4 = interpPoint(p3, ip0, chunk->x + 1, chunk->y + 1, growFactor); + // emit the 4 fans: + + U32 indexStart; + + if((chunk->emptyFlags & CornerEmpty_0_1) != CornerEmpty_0_1) + { + mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; + indexStart = mXFIndexCount++; + + mXFIndexBuffer[mXFIndexCount++] = ip1; + mXFIndexBuffer[mXFIndexCount++] = p0; + if(e0->pointCount == 1) + mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex; + else // has to be 3: + { + mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex; + mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + 1; + } + mXFIndexBuffer[mXFIndexCount++] = ip0; + if(e3->pointCount == 1) + mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex; + else + { + mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + 1; + mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex; + } + mXFIndexBuffer[mXFIndexCount++] = p0; + mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; + if(chunk->clip) + clip(indexStart - 1); + } + + if((chunk->emptyFlags & CornerEmpty_1_1) != CornerEmpty_1_1) + { + mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; + indexStart = mXFIndexCount++; + mXFIndexBuffer[mXFIndexCount++] = ip2; + mXFIndexBuffer[mXFIndexCount++] = p1; + if(e1->pointCount == 1) + mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex; + else + { + mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex; + mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + 1; + } + mXFIndexBuffer[mXFIndexCount++] = ip0; + if(e0->pointCount == 1) + mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex; + else + { + mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + 1; + mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + 2; + } + mXFIndexBuffer[mXFIndexCount++] = p1; + mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; + if(chunk->clip) + clip(indexStart - 1); + } + + if((chunk->emptyFlags & CornerEmpty_1_0) != CornerEmpty_1_0) + { + mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; + indexStart = mXFIndexCount++; + mXFIndexBuffer[mXFIndexCount++] = ip3; + mXFIndexBuffer[mXFIndexCount++] = p2; + if(e2->pointCount == 1) + mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex; + else + { + mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + 2; + mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + 1; + } + mXFIndexBuffer[mXFIndexCount++] = ip0; + if(e1->pointCount == 1) + mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex; + else + { + mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + 1; + mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + 2; + } + mXFIndexBuffer[mXFIndexCount++] = p2; + mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; + if(chunk->clip) + clip(indexStart - 1); + } + + if((chunk->emptyFlags & CornerEmpty_0_0) != CornerEmpty_0_0) + { + mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; + indexStart = mXFIndexCount++; + mXFIndexBuffer[mXFIndexCount++] = ip4; + mXFIndexBuffer[mXFIndexCount++] = p3; + if(e3->pointCount == 1) + mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex; + else + { + mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + 2; + mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + 1; + } + mXFIndexBuffer[mXFIndexCount++] = ip0; + if(e2->pointCount == 1) + mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex; + else + { + mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + 1; + mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex; + } + mXFIndexBuffer[mXFIndexCount++] = p3; + mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; + if(chunk->clip) + clip(indexStart - 1); + } + } + else + { + // subDiv == -1 + U32 ip1 = constructPoint(chunk->x + 1, chunk->y + 3); + U32 ip2 = constructPoint(chunk->x + 3, chunk->y + 3); + U32 ip3 = constructPoint(chunk->x + 3, chunk->y + 1); + U32 ip4 = constructPoint(chunk->x + 1, chunk->y + 1); + U32 ip5 = interpPoint(e0->pointIndex + 1, ip0, chunk->x + 2, chunk->y + 3, growFactor); + U32 ip6 = interpPoint(e1->pointIndex + 1, ip0, chunk->x + 3, chunk->y + 2, growFactor); + U32 ip7 = interpPoint(e2->pointIndex + 1, ip0, chunk->x + 2, chunk->y + 1, growFactor); + U32 ip8 = interpPoint(e3->pointIndex + 1, ip0, chunk->x + 1, chunk->y + 2, growFactor); + + // now do the squares: + U16 *ib; + + if(chunk->emptyFlags & CornerEmpty_0_1) + { + if((chunk->emptyFlags & CornerEmpty_0_1) != CornerEmpty_0_1) + { + mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES; + U32 indexStart = mXFIndexCount++; + if(!(chunk->emptyFlags & SquareEmpty_0_3)) + { + emitTri(ip1, e3->pointIndex, p0); + emitTri(ip1, p0, e0->pointIndex); + } + if(!(chunk->emptyFlags & SquareEmpty_1_3)) + { + emitTri(ip1, e0->pointIndex, e0->pointIndex + 1); + emitTri(ip1, e0->pointIndex + 1, ip5); + } + if(!(chunk->emptyFlags & SquareEmpty_1_2)) + { + emitTri(ip1, ip5, ip0); + emitTri(ip1, ip0, ip8); + } + if(!(chunk->emptyFlags & SquareEmpty_0_2)) + { + emitTri(ip1, ip8, e3->pointIndex + 1); + emitTri(ip1, e3->pointIndex + 1, e3->pointIndex); + } + mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; + } + } + else + { + ib = mXFIndexBuffer + mXFIndexCount + 1; + ib[-1] = GL_TRIANGLE_FAN; + ib[0] = 10; + ib[1] = ip1; + ib[2] = p0; + ib[3] = e0->pointIndex; + ib[4] = e0->pointIndex + 1; + ib[5] = ip5; + ib[6] = ip0; + ib[7] = ip8; + ib[8] = e3->pointIndex + 1; + ib[9] = e3->pointIndex; + ib[10] = ib[2]; + + mXFIndexCount += 12; + if(chunk->clip) + clip(mXFIndexCount - 12); + } + + if(chunk->emptyFlags & CornerEmpty_1_1) + { + if((chunk->emptyFlags & CornerEmpty_1_1) != CornerEmpty_1_1) + { + mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES; + U32 indexStart = mXFIndexCount++; + + if(!(chunk->emptyFlags & SquareEmpty_3_3)) + { + emitTri(ip2, e0->pointIndex + 2, p1); + emitTri(ip2, p1, e1->pointIndex); + } + if(!(chunk->emptyFlags & SquareEmpty_3_2)) + { + emitTri(ip2, e1->pointIndex, e1->pointIndex + 1); + emitTri(ip2, e1->pointIndex + 1, ip6); + } + if(!(chunk->emptyFlags & SquareEmpty_2_2)) + { + emitTri(ip2, ip6, ip0); + emitTri(ip2, ip0, ip5); + } + if(!(chunk->emptyFlags & SquareEmpty_2_3)) + { + emitTri(ip2, ip5, e0->pointIndex + 1); + emitTri(ip2, e0->pointIndex + 1, e0->pointIndex + 2); + } + mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; + } + } + else + { + ib = mXFIndexBuffer + mXFIndexCount + 1; + ib[-1] = GL_TRIANGLE_FAN; + ib[0] = 10; + ib[1] = ip2; + ib[2] = p1; + ib[3] = e1->pointIndex; + ib[4] = e1->pointIndex + 1; + ib[5] = ip6; + ib[6] = ip0; + ib[7] = ip5; + ib[8] = e0->pointIndex + 1; + ib[9] = e0->pointIndex + 2; + ib[10] = ib[2]; + + mXFIndexCount += 12; + if(chunk->clip) + clip(mXFIndexCount - 12); + } + + if(chunk->emptyFlags & CornerEmpty_1_0) + { + if((chunk->emptyFlags & CornerEmpty_1_0) != CornerEmpty_1_0) + { + mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES; + U32 indexStart = mXFIndexCount++; + + if(!(chunk->emptyFlags & SquareEmpty_3_0)) + { + emitTri(ip3, e1->pointIndex + 2, p2); + emitTri(ip3, p2, e2->pointIndex + 2); + } + if(!(chunk->emptyFlags & SquareEmpty_2_0)) + { + emitTri(ip3, e2->pointIndex + 2, e2->pointIndex + 1); + emitTri(ip3, e2->pointIndex + 1, ip7); + } + if(!(chunk->emptyFlags & SquareEmpty_2_1)) + { + emitTri(ip3, ip7, ip0); + emitTri(ip3, ip0, ip6); + } + if(!(chunk->emptyFlags & SquareEmpty_3_1)) + { + emitTri(ip3, ip6, e1->pointIndex + 1); + emitTri(ip3, e1->pointIndex + 1, e1->pointIndex + 2); + } + mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; + } + } + else + { + ib = mXFIndexBuffer + mXFIndexCount + 1; + ib[-1] = GL_TRIANGLE_FAN; + ib[0] = 10; + ib[1] = ip3; + ib[2] = p2; + ib[3] = e2->pointIndex + 2; + ib[4] = e2->pointIndex + 1; + ib[5] = ip7; + ib[6] = ip0; + ib[7] = ip6; + ib[8] = e1->pointIndex + 1; + ib[9] = e1->pointIndex + 2; + ib[10] = ib[2]; + + mXFIndexCount += 12; + if(chunk->clip) + clip(mXFIndexCount - 12); + } + + if(chunk->emptyFlags & CornerEmpty_0_0) + { + if((chunk->emptyFlags & CornerEmpty_0_0) != CornerEmpty_0_0) + { + mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES; + U32 indexStart = mXFIndexCount++; + + if(!(chunk->emptyFlags & SquareEmpty_0_0)) + { + emitTri(ip4, e2->pointIndex, p3); + emitTri(ip4, p3, e3->pointIndex + 2); + } + if(!(chunk->emptyFlags & SquareEmpty_0_1)) + { + emitTri(ip4, e3->pointIndex + 2, e3->pointIndex + 1); + emitTri(ip4, e3->pointIndex + 1, ip8); + } + if(!(chunk->emptyFlags & SquareEmpty_1_1)) + { + emitTri(ip4, ip8, ip0); + emitTri(ip4, ip0, ip7); + } + if(!(chunk->emptyFlags & SquareEmpty_1_0)) + { + emitTri(ip4, ip7, e2->pointIndex + 1); + emitTri(ip4, e2->pointIndex + 1, e2->pointIndex); + } + mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; + } + } + else + { + ib = mXFIndexBuffer + mXFIndexCount + 1; + ib[-1] = GL_TRIANGLE_FAN; + ib[0] = 10; + ib[1] = ip4; + ib[2] = p3; + ib[3] = e3->pointIndex + 2; + ib[4] = e3->pointIndex + 1; + ib[5] = ip8; + ib[6] = ip0; + ib[7] = ip7; + ib[8] = e2->pointIndex + 1; + ib[9] = e2->pointIndex; + ib[10] = ib[2]; + + mXFIndexCount += 12; + if(chunk->clip) + clip(mXFIndexCount - 12); + } + + } + } + + // Do dynamic lighting here... + if (mEnableTerrainDynLights && chunk->lightMask != 0) { + for (U32 i = 0; i < 32; i++) { + if ((chunk->lightMask & (1 << i)) == 0) + continue; + + for (U32 start = startXFIndex; start < mXFIndexCount; start++) { + if (mXFIndexBuffer[start] == GL_TRIANGLE_FAN) { + start++; + U32 count = mXFIndexBuffer[start]; + U32 triCount = count - 2; + + LightTriangle* lightTris = (LightTriangle*)FrameAllocator::alloc(sizeof(LightTriangle) * triCount); + U32 j; + for (j = 0; j < (triCount-1); j++) + lightTris[j].next = &lightTris[j+1]; + lightTris[triCount-1].next = sgCurrLightTris; + sgCurrLightTris = lightTris; + + // Copy out tri data here... + for (j = 0; j < triCount; j++) { + lightTris[j].point1 = mXFVertices[mXFIndexBuffer[start + 1 + 0]]; + lightTris[j].point2 = mXFVertices[mXFIndexBuffer[start + 1 + j + 1]]; + lightTris[j].point3 = mXFVertices[mXFIndexBuffer[start + 1 + j + 2]]; + + buildLightTri(&lightTris[j], &mTerrainLights[i]); + } + + start += count; + } else { + AssertFatal(mXFIndexBuffer[start] == GL_TRIANGLES, "Error, bad start code!"); + start++; + U32 count = mXFIndexBuffer[start]; + AssertFatal((count / 3) * 3 == count, "Error, not divisible by 3!"); + + U32 triCount = count/3; + LightTriangle* lightTris = (LightTriangle*)FrameAllocator::alloc(sizeof(LightTriangle) * triCount); + U32 j; + for (j = 0; j < (triCount-1); j++) + lightTris[j].next = &lightTris[j+1]; + lightTris[triCount-1].next = sgCurrLightTris; + sgCurrLightTris = lightTris; + + // Copy out tri data here... + for (j = 0; j < triCount; j++) { + lightTris[j].point1 = mXFVertices[mXFIndexBuffer[start + 1 + (j*3) + 0]]; + lightTris[j].point2 = mXFVertices[mXFIndexBuffer[start + 1 + (j*3) + 1]]; + lightTris[j].point3 = mXFVertices[mXFIndexBuffer[start + 1 + (j*3) + 2]]; + + buildLightTri(&lightTris[j], &mTerrainLights[i]); + } + + start += count; + } + } + } + } +} + +void TerrainRender::drawTriFan(U32 vCount, U32 *indexBuffer) +{ + glBegin(GL_LINES); + U32 cur = vCount - 1; + for(U32 i = 1; i < vCount; i++) + { + glArrayElement(indexBuffer[0]); + glArrayElement(indexBuffer[cur]); + glArrayElement(indexBuffer[cur]); + glArrayElement(indexBuffer[i]); + cur = i; + } + glEnd(); +} + +void TerrainRender::renderXFCache() +{ + U32 count = 0; + + while (count < mXFIndexCount) + { + U32 mode = mXFIndexBuffer[count]; + U32 vertexCount = mXFIndexBuffer[count + 1]; + glDrawElements(mode, vertexCount, GL_UNSIGNED_SHORT, mXFIndexBuffer + count + 2); + count += vertexCount + 2; + } +} + +void doTexGens(Point4F &s, Point4F &t) +{ + EdgePoint *pt = mXFVertices; + EdgePoint *last = pt + mXFPointCount; + for(;pt < last; pt++) + { + pt->haze = pt->x * s.x + pt->y * s.y + pt->z * s.z + s.w; + pt->distance = pt->x * t.x + pt->y * t.y + pt->z * t.z + t.w; + } +} + +void TerrainRender::renderBlock(TerrainBlock *block, SceneState *state) +{ + PROFILE_START(TerrainRender); + PROFILE_START(TerrainRenderSetup); + dglSetRenderPrimType(1); + U32 storedWaterMark = FrameAllocator::getWaterMark(); + + if (sgTextureFreeListPrimed == false) { + sgTextureFreeListPrimed = true; + AssertFatal(mTextureFreeList.size() == 0, "Error, unprimed free list should always be size 0"); + + mTextureFreeList.setSize(sgFreeListPrimeCount); + for (U32 i = 0; i < sgFreeListPrimeCount; i++) { + constructInPlace(&mTextureFreeList[i]); + mTextureFreeList[i] = TextureHandle((const char*)NULL, mBlendBitmap, TerrainTexture, true); + } + } + + mFrameIndex++; + mSceneState = state; + mFarDistance = state->getVisibleDistance(); + + dglGetModelview(&mCameraToObject); + + mCameraToObject.inverse(); + + mCameraToObject.getColumn(3, &mCamPos); + mFogColor = state->getFogColor(); + + TextureHandle hazeTexture = gClientSceneGraph->getFogTexture(); + mCurrentBlock = block; + + Point4F detTexGenS = Point4F(1, 0, 0, 0); + Point4F detTexGenT = Point4F(0, 1, 0, 0); + if (mEnableTerrainDetails && mCurrentBlock->mDetailTextureHandle.getGLName() != 0) { + detTexGenS.x *= 62.0 / mCurrentBlock->mDetailTextureHandle.getWidth(); + detTexGenT.y *= 62.0 / mCurrentBlock->mDetailTextureHandle.getHeight(); + detTexGenS.w = -(S32) (mCamPos.x*detTexGenS.x); + detTexGenT.w = -(S32) (mCamPos.y*detTexGenT.y); + } + + mSquareSize = block->getSquareSize(); + + mNewGenTextureCount = 0; + + // compute pixelError + if(mScreenError >= 0.001) + mPixelError = 1 / dglProjectRadius(mScreenError, 1); + else + mPixelError = 0.000001; + + buildClippingPlanes(state->mFlipCull); + buildLightArray(); + + mDynamicTextureCount = 0; + mTextureSpaceUsed = 0; + mUnusedTextureCount = 0; + mStaticTextureCount = 0; + mLevelZeroCount = 0; + mFullMipCount = 0; + mStaticTSU = 0; + + F32 worldToScreenScale = dglProjectRadius(1,1); + F32 zeroDetailDistance = (mSquareSize * worldToScreenScale) / (1 << 6) - (mSquareSize >> 1); + + F32 blockSize = mSquareSize * TerrainBlock::BlockSquareWidth; + S32 xStart = (S32)mFloor( (mCamPos.x - mFarDistance) / blockSize ); + S32 xEnd = (S32)mCeil ( (mCamPos.x + mFarDistance) / blockSize ); + S32 yStart = (S32)mFloor( (mCamPos.y - mFarDistance) / blockSize ); + S32 yEnd = (S32)mCeil ( (mCamPos.y + mFarDistance) / blockSize ); + S32 xExt = (S32)(xEnd - xStart); + S32 yExt = (S32)(yEnd - yStart); + +#if 0 + { + SquareStackNode2 *st = (SquareStackNode2 *) + FrameAllocator::alloc(sizeof(SquareStackNode2) * + (xExt * yExt + TerrainBlock::BlockShift*4) ); + for(S32 y = 0; y < yExt; y++) + { + for(S32 x = 0; x < xExt; x++) + { + SquareStackNode2 *s = st + x + y * xExt; + + s->pos.set((xStart + x) * blockSize, + (yStart + y) * blockSize); + s->level = TerrainBlock::BlockShift; + s->clipFlags = ClipPlaneMask | FarSphereMask; // test all the planes + s->lightMask = (1 << mDynamicLightCount) - 1; // test all the lights + } + } + processBlockStack(st, xExt * yExt); + render2(); + FrameAllocator::setWaterMark(storedWaterMark); + return; + } +#endif + +// mSquareOrigin.x = xStart; +// mSquareOrigin.y = yStart; + PROFILE_END(); + PROFILE_START(TerrainRenderRecurse); + F32 height = fixedToFloat(block->getHeight(0,0)); + + EdgeParent **bottomEdges = (EdgeParent **) FrameAllocator::alloc(sizeof(ChunkScanEdge *) * xExt); + TerrainRender::allocRenderEdges(xExt, bottomEdges, false); + ChunkCornerPoint *prevCorner = TerrainRender::allocInitialPoint(Point3F(xStart * blockSize, yStart * blockSize, height)); + + mTerrainOffset.set(xStart * TerrainBlock::BlockSquareWidth, yStart * TerrainBlock::BlockSquareWidth); + + for(S32 x = 0; x < xExt; x++) + { + bottomEdges[x]->p1 = prevCorner; + prevCorner = TerrainRender::allocInitialPoint(Point3F((xStart + x ) * blockSize, yStart * blockSize, height)); + bottomEdges[x]->p2 = prevCorner; + } + for(S32 y = 0; y < yExt; y++) + { + // allocate the left edge: + EdgeParent *left; + TerrainRender::allocRenderEdges(1, &left, false); + left->p1 = TerrainRender::allocInitialPoint(Point3F(xStart * blockSize, (yStart + y + 1) * blockSize, height)); + left->p2 = bottomEdges[0]->p1; + for(S32 x = 0; x < xExt; x++) + { + EdgeParent *right; + TerrainRender::allocRenderEdges(1, &right, false); + right->p1 = TerrainRender::allocInitialPoint(Point3F((xStart + x + 1) * blockSize, (yStart + 1) * blockSize, height)); + right->p2 = bottomEdges[x]->p2; + EdgeParent *top; + TerrainRender::allocRenderEdges(1, &top, false); + top->p1 = left->p1; + top->p2 = right->p1; + + mBlockOffset.set(x << TerrainBlock::BlockShift, + y << TerrainBlock::BlockShift); + + mBlockPos.set((xStart + x) * blockSize, + (yStart + y) * blockSize); + + sgFogRejectedBlocks = 0; + TerrainRender::processCurrentBlock2(state, top, right, bottomEdges[x], left); + left = right; + bottomEdges[x] = top; + } + } + U32 slop = 0; + AllocatedTexture *fwalk = mTextureFreeListHead.next; + while(fwalk != &mTextureFreeListTail && slop < mTextureSlopSize) + { + fwalk = fwalk->next; + slop++; + } + while(fwalk != &mTextureFreeListTail) + { + AllocatedTexture *next = fwalk->next; + fwalk->unlink(); + + S32 x = (fwalk->x & TerrainBlock::BlockMask) >> fwalk->level; + S32 y = (fwalk->y & TerrainBlock::BlockMask) >> fwalk->level; + mTextureGridPtr[fwalk->level - 2][x + (y << (8 - fwalk->level))] = NULL; + + freeTerrTexture(fwalk); + fwalk = next; + } + + PROFILE_END(); + PROFILE_START(TerrainRenderEmit); + + bool lockArrays = dglDoesSupportCompiledVertexArray() && dglDoesSupportARBMultitexture(); + bool multitextureFog = dglDoesSupportARBMultitexture(); + bool vertexBuffer = dglDoesSupportVertexBuffer() && (block->mVertexBuffer != -1); + + glFrontFace(GL_CW); + glEnable(GL_CULL_FACE); + glEnableClientState(GL_VERTEX_ARRAY); + + if(!vertexBuffer) + mXFVertices = (EdgePoint *) FrameAllocator::alloc(sizeof(EdgePoint) * VertexBufferSize); + + glVertexPointer(3, GL_FLOAT, sizeof(EdgePoint), mXFVertices); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + + // see if we should setup hazing on the second TMU + // if not, we'll have to 2-pass the terrain... + // and that ain't gonna be fast no matter how ya slice it. + + if(multitextureFog) + { + // Hazing + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glActiveTextureARB(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, hazeTexture.getGLName()); //handle + + //if (gOpenGLNoEnvColor) + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + //else + //{ + // glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND); + // glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mFogColor); + //} + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->fogRed); + + // Base textures + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glActiveTextureARB(GL_TEXTURE0_ARB); + } + else + glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->fogRed); + + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->haze); + +// glEnable(GL_TEXTURE_GEN_S); +// glEnable(GL_TEXTURE_GEN_T); +// glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); +// glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + + glDisable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + mXFIndex = 0; + sgCurrLightTris = NULL; + AllocatedTexture *walk; + mXFIndexBuffer = (U16 *) FrameAllocator::alloc(sizeof(U16) * 64 * 64 * 4); + + while((walk = mTextureFrameListHead.next) != &mTextureFrameListTail) + { + walk->unlink(); + if(walk->level != 6) + walk->linkAfter(&mTextureFreeListHead); + else + walk->linkAfter(&mTextureFreeBigListHead); + mStaticTextureCount++; + if(!walk->handle) + buildBlendMap(walk); + AllocatedTexture *step = walk; + while(step) + { + // loop through the list and draw all the squares: + F32 invLevel = 1 / F32(mSquareSize << step->level); + sgTexGenS.set(invLevel, 0, 0, -(step->x >> step->level)); + sgTexGenT.set(0, invLevel, 0, -(step->y >> step->level)); + while(step->list) + { + mXFPointCount = 0; + mXFIndex++; + mXFIndexPtr = mXFIndexBuffer; + mXFIndexCount = 0; + + PROFILE_START(TerrainRenderLock); + if (vertexBuffer) + mXFVertices = (EdgePoint *) glLockVertexBufferEXT(block->mVertexBuffer, VertexBufferSize); + PROFILE_END(); + + PROFILE_START(TerrainRenderBuild); + + bool renderDetails = false; + EmitChunk *sq; + for(sq = step->list; sq && mXFPointCount < maxTerrPoints; sq = sq->next) + { + if(mRenderingCommander) + { + renderChunkCommander(sq); + } + else + { + renderDetails |= sq->renderDetails && mEnableTerrainDetails; + renderChunkOutline(sq); + } + AssertFatal(mXFPointCount <= VertexBufferSize, "Invalid point count."); + AssertFatal(mXFIndexCount <= 64 * 64 * 4, "Index count sucks."); + } + step->list = sq; + + if (mXFIndexCount && + renderDetails && mCurrentBlock->mDetailTextureHandle.getGLName() != 0) + { + for (U32 a = 0; a < mXFPointCount; a++) + { + EdgePoint& rPoint = mXFVertices[a]; + + F32 factor = state->getHazeAndFog(rPoint.distance, rPoint.z); + F32 distFactor = (zeroDetailDistance - rPoint.distance) / zeroDetailDistance; + if (distFactor < 0.0) + distFactor = 0.0; + else if (distFactor > 1.0) + distFactor = 1.0f; + + U8 c = U8((((1.0 - factor) * distFactor) * 255.0f) + 0.5f); + + rPoint.detailColor.red = c; + rPoint.detailColor.green = c; + rPoint.detailColor.blue = c; + rPoint.detailColor.alpha = c; + } + } + + PROFILE_END(); + PROFILE_START(TerrainRenderUnlock); + if (vertexBuffer) + glUnlockVertexBufferEXT(block->mVertexBuffer); + PROFILE_END(); + + if(mXFIndexCount) + { + PROFILE_START(TerrainRenderBind); + glBindTexture(GL_TEXTURE_2D, walk->handle.getGLName()); //handle + //glTexGenfv(GL_S, GL_OBJECT_PLANE, sgTexGenS); + //glTexGenfv(GL_T, GL_OBJECT_PLANE, sgTexGenT); + doTexGens(sgTexGenS, sgTexGenT); + + PROFILE_END(); + PROFILE_START(TerrainRenderSetVertexBuffer); + + if (vertexBuffer) + glSetVertexBufferEXT(block->mVertexBuffer); + if (lockArrays) + glLockArraysEXT(0, mXFPointCount); + // lock the array + PROFILE_END(); + PROFILE_START(TerrainRenderXFRender); + renderXFCache(); + PROFILE_END(); + if(!multitextureFog) + { + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor4fv(mFogColor); + glBindTexture(GL_TEXTURE_2D, hazeTexture.getGLName()); //handle + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mFogColor); + + glEnable(GL_BLEND); + renderXFCache(); + glDisable(GL_BLEND); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glEnable(GL_TEXTURE_GEN_S); + glEnable(GL_TEXTURE_GEN_T); + } + + if (renderDetails && mCurrentBlock->mDetailTextureHandle.getGLName() != 0) + { + PROFILE_START(TerrainRenderDetails); + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(EdgePoint), &mXFVertices->detailColor); + glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mDetailTextureHandle.getGLName()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + if (vertexBuffer) + mXFVertices = (EdgePoint *) glLockVertexBufferEXT(block->mVertexBuffer, VertexBufferSize); + doTexGens(detTexGenS, detTexGenT); + if (vertexBuffer) + glUnlockVertexBufferEXT(block->mVertexBuffer); + //glTexGenfv(GL_S, GL_OBJECT_PLANE, detTexGenS); + //glTexGenfv(GL_T, GL_OBJECT_PLANE, detTexGenT); + + glEnable(GL_BLEND); + glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); + if(multitextureFog) + { + glActiveTextureARB(GL_TEXTURE1_ARB); + glDisable(GL_TEXTURE_2D); + renderXFCache(); + glEnable(GL_TEXTURE_2D); + glActiveTextureARB(GL_TEXTURE0_ARB); + } + else + { + renderXFCache(); + } + glDisable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisableClientState(GL_COLOR_ARRAY); + PROFILE_END(); + } + if (lockArrays) + glUnlockArraysEXT(); + } + } + AllocatedTexture *nextStep = step->nextLink; + step->list = 0; + step->nextLink = 0; + if(step != walk) + delete step; + step = nextStep; + } + } + + if(multitextureFog) + { + glActiveTextureARB(GL_TEXTURE1_ARB); + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glActiveTextureARB(GL_TEXTURE0_ARB); + glClientActiveTextureARB(GL_TEXTURE0_ARB); + } + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +// glDisable(GL_TEXTURE_GEN_S); +// glDisable(GL_TEXTURE_GEN_T); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glDisableClientState(GL_VERTEX_ARRAY); + + if (mEnableTerrainDynLights && sgCurrLightTris) { + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mDynLightTexture.getGLName()); + LightTriangle* walk = sgCurrLightTris; + + glBegin(GL_TRIANGLES); + while (walk) { + if (walk->flags) { + glColor4fv(walk->color); + glTexCoord2fv(walk->texco1); + glVertex3fv(walk->point1); + glColor4fv(walk->color); + glTexCoord2fv(walk->texco2); + glVertex3fv(walk->point2); + glColor4fv(walk->color); + glTexCoord2fv(walk->texco3); + glVertex3fv(walk->point3); + } + walk = walk->next; + } + glEnd(); + + glDisable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + } + + glDisable(GL_CULL_FACE); + + FrameAllocator::setWaterMark(storedWaterMark); + dglSetRenderPrimType(0); + PROFILE_END(); + PROFILE_END(); +} + + + +//--------------------------------------------------------------- + +//--------------------------------------------------------------- +// Texture allocation / free list management +//--------------------------------------------------------------- + +void TerrainRender::flushCache() +{ + for(S32 i = 0; i < AllocatedTextureCount; i++) + mTextureGrid[i] = 0; + + for(S32 i = 0; i < mTextureFreeList.size(); i++) + mTextureFreeList[i] = NULL; + mTextureFreeList.clear(); + AllocatedTexture *tex; + + while( (tex = mTextureFreeListHead.next) != &mTextureFreeListTail) + { + tex->unlink(); + delete tex; + } + while( (tex = mTextureFreeBigListHead.next) != &mTextureFreeBigListTail) + { + tex->unlink(); + delete tex; + } +} + +//--------------------------------------------------------------- + +void TerrainRender::freeTerrTexture(AllocatedTexture *texture) +{ + if(texture->handle) + { + mTextureFreeList.increment(); + constructInPlace(&mTextureFreeList.last()); + mTextureFreeList.last() = texture->handle; + } + delete texture; +} + +//--------------------------------------------------------------- + +void TerrainRender::allocTerrTexture(Point2I pos, U32 level, U32 mipLevel, bool vis, F32 distance) +{ + S32 blockX = mBlockOffset.x + mTerrainOffset.x; + S32 blockY = mBlockOffset.y + mTerrainOffset.y; + + S32 x = pos.x + blockX; + S32 y = pos.y + blockY; + + S32 px = (pos.x & TerrainBlock::BlockMask) >> level; + S32 py = (pos.y & TerrainBlock::BlockMask) >> level; + + AssertFatal(level >= 2 && level <= 6, "Invalid level"); + + AllocatedTexture *cur = mTextureGridPtr[level - 2][px + (py << (8 - level))]; + + if(!cur) + { + cur = new AllocatedTexture; + + cur->list = NULL; + cur->mipLevel = mipLevel; + cur->x = x; + cur->y = y; + cur->level = level; + cur->nextLink = NULL; + } + else + { + AssertFatal(cur->level == level, "Invalid block for this level."); + if(cur->list && (cur->x != x || cur->y != y)) + { + // see if the texture is already in the list... + AllocatedTexture *walk = cur->nextLink; + while(walk && walk->x != x && walk->y != y) + walk = walk->nextLink; + if(walk) + { + mCurrentTexture = walk; + return; + } + AllocatedTexture *tail = new AllocatedTexture; + tail->list = NULL; + tail->x = x; + tail->y = y; + tail->level = level; + tail->nextLink = cur->nextLink; + tail->distance = distance; + cur->nextLink = tail; + mCurrentTexture = tail; + return; + } + else + { + cur->x = x; + cur->y = y; + cur->unlink(); + } + } + + cur->linkAfter(&mTextureFrameListHead); + cur->distance = distance; + + mCurrentTexture = cur; + + mTextureGridPtr[level - 2][px + (py << (8 - level))] = cur; +} + +void TerrainRender::buildBlendMap(AllocatedTexture *tex) +{ + PROFILE_START(TerrainRenderBuildBlendMap); + GBitmap *bmp = mBlendBitmap; + S32 x = tex->x; + S32 y = tex->y; + S32 level = tex->level; + + AssertFatal(mCurrentBlock->lightMap->getFormat() == GBitmap::RGB5551, "Error, lightmap must be 5551"); + AssertFatal(bmp->getFormat() == GBitmap::RGB5551, "Error, destination must be 565"); + AssertFatal(bmp->getWidth() == 128 && bmp->getHeight() == 128, "Error, bitmaps must be 128 high and wide for the terrain"); + AssertFatal(mCurrentBlock->lightMap->getWidth() == 512 && mCurrentBlock->lightMap->getHeight() == 512, + "Fast blender requires a 512 lightmap!"); + + U16* mips[9]; + for (U32 i = 0; i < bmp->getNumMipLevels(); i++) + mips[i] = (U16*)bmp->getWritableBits(i); + + mCurrentBlock->mBlender->blend(x, y, level, (const U16*)mCurrentBlock->lightMap->getBits(), mips); + + mDynamicTextureCount++; + if(mTextureFreeList.size()) + { + tex->handle = mTextureFreeList.last(); + mTextureFreeList.last() = NULL; + mTextureFreeList.decrement(); + tex->handle.refresh(mBlendBitmap); + } + else + { + tex->handle = TextureHandle((const char*)NULL, mBlendBitmap, TerrainTexture, true); + } + PROFILE_END(); +} diff --git a/terrain/terrRender.h b/terrain/terrRender.h new file mode 100644 index 0000000..c90f847 --- /dev/null +++ b/terrain/terrRender.h @@ -0,0 +1,342 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRRENDER_H_ +#define _TERRRENDER_H_ + +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _WATERBLOCK_H_ +#include "terrain/waterBlock.h" +#endif + +struct EmitChunk; + +struct AllocatedTexture { + U32 level; + S32 x, y; + F32 distance; + EmitChunk *list; + TextureHandle handle; + AllocatedTexture *next; + AllocatedTexture *previous; + AllocatedTexture *nextLink; + U32 mipLevel; + + AllocatedTexture() + { + next = previous = NULL; + } + inline void unlink() + { + AssertFatal(next && previous, "Invalid unlink."); + next->previous = previous; + previous->next = next; + next = previous = NULL; + } + inline void linkAfter(AllocatedTexture *t) + { + AssertFatal(next == NULL && previous == NULL, "Cannot link a non-null next & prev"); + + next = t->next; + previous = t; + t->next->previous = this; + t->next = this; + } +}; + +struct Render2Point : public Point3F +{ + F32 d; +// ColorI color; +}; + +struct EdgePoint : public Point3F +{ + ColorI detailColor; + F32 haze; + F32 distance; + F32 fogRed; + F32 fogGreen; +}; + +struct ChunkCornerPoint : public EdgePoint +{ + U32 pointIndex; + U32 xfIndex; +}; + +struct EdgeParent +{ + ChunkCornerPoint *p1, *p2; +}; + +struct ChunkScanEdge : public EdgeParent +{ + ChunkCornerPoint *mp; + EdgeParent *e1, *e2; +}; + +struct ChunkEdge : public EdgeParent +{ + U32 xfIndex; + U32 pointIndex; + U32 pointCount; + + EdgePoint pt[3]; + EmitChunk *c1, *c2; +}; + +struct EmitChunk +{ + ChunkEdge *edge[4]; + S32 subDivLevel; + F32 growFactor; + S32 x, y; + S32 gridX, gridY; + U32 emptyFlags; + bool clip; + U32 lightMask; + EmitChunk *next; + bool renderDetails; +}; + +struct SquareStackNode2 +{ + U32 clipFlags; + U32 lightMask; + Point2I pos; + U32 level; + bool texAllocated; +}; + +struct SquareStackNode +{ + U32 clipFlags; + U32 lightMask; + Point2I pos; + U32 level; + bool texAllocated; + EdgeParent *top, *right, *bottom, *left; +}; + +struct TerrLightInfo +{ + Point3F pos; // world position + F32 radius; // radius of the light + F32 radiusSquared; // radius^2 + F32 r, g, b; + + F32 distSquared; // distance to camera +}; + +//struct ScanEdge +//{ +// U16 p1, p2, mp; +// U16 firstSubEdge; // two sub edges for each edge, mp = InvalidPointIndex if none +//}; + +enum EmptyFlags { + SquareEmpty_0_0 = (1 << 0), + SquareEmpty_1_0 = (1 << 1), + SquareEmpty_2_0 = (1 << 2), + SquareEmpty_3_0 = (1 << 3), + SquareEmpty_0_1 = (1 << 4), + SquareEmpty_1_1 = (1 << 5), + SquareEmpty_2_1 = (1 << 6), + SquareEmpty_3_1 = (1 << 7), + SquareEmpty_0_2 = (1 << 8), + SquareEmpty_1_2 = (1 << 9), + SquareEmpty_2_2 = (1 << 10), + SquareEmpty_3_2 = (1 << 11), + SquareEmpty_0_3 = (1 << 12), + SquareEmpty_1_3 = (1 << 13), + SquareEmpty_2_3 = (1 << 14), + SquareEmpty_3_3 = (1 << 15), + CornerEmpty_0_0 = SquareEmpty_0_0 | SquareEmpty_1_0 | SquareEmpty_0_1 | SquareEmpty_1_1, + CornerEmpty_1_0 = SquareEmpty_2_0 | SquareEmpty_3_0 | SquareEmpty_2_1 | SquareEmpty_3_1, + CornerEmpty_0_1 = SquareEmpty_0_2 | SquareEmpty_1_2 | SquareEmpty_0_3 | SquareEmpty_1_3, + CornerEmpty_1_1 = SquareEmpty_2_2 | SquareEmpty_3_2 | SquareEmpty_2_3 | SquareEmpty_3_3, +}; + +struct RenderPoint : public Point3F +{ + F32 dist; + F32 haze; // also used as grow factor +}; + +enum { + MaxClipPlanes = 8, // left, right, top, bottom - don't need far tho... + MaxTerrainMaterials = 256, + + EdgeStackSize = 1024, // value for water/terrain edge stack size. + MaxWaves = 8, + MaxDetailLevel = 9, + MaxMipLevel = 8, + MaxTerrainLights = 64, + MaxVisibleLights = 31, + ClipPlaneMask = (1 << MaxClipPlanes) - 1, + FarSphereMask = 0x80000000, + FogPlaneBoxMask = 0x40000000, + VertexBufferSize = 65 * 65 + 1000, + AllocatedTextureCount = 16 + 64 + 256 + 1024 + 4096, + + SmallMipLevel = 6 +}; + +struct Color +{ + S32 r, g, b; + F32 z; +}; + +class SceneState; + +struct TerrainRender +{ + static MatrixF mCameraToObject; + static AllocatedTexture mTextureFrameListHead; + static AllocatedTexture mTextureFrameListTail; + static AllocatedTexture mTextureFreeListHead; + static AllocatedTexture mTextureFreeListTail; + static AllocatedTexture mTextureFreeBigListHead; + static AllocatedTexture mTextureFreeBigListTail; + static U32 mTextureSlopSize; + static Vector mTextureFreeList; + static S32 mTextureMinSquareSize; + + static SceneState *mSceneState; + static AllocatedTexture *mCurrentTexture; + + static TerrainBlock *mCurrentBlock; + static S32 mSquareSize; + static F32 mScreenSize; + static U32 mFrameIndex; + static U32 mNumClipPlanes; + static AllocatedTexture *mTextureGrid[AllocatedTextureCount]; + static AllocatedTexture **mTextureGridPtr[5]; + + static Point2F mBlockPos; + static Point2I mBlockOffset; + static Point2I mTerrainOffset; + + static PlaneF mClipPlane[MaxClipPlanes]; + static Point3F mCamPos; + static TextureHandle* mGrainyTexture; + static U32 mDynamicLightCount; + static bool mEnableTerrainDetails; + static bool mEnableTerrainDynLights; + + static F32 mPixelError; + + static TerrLightInfo mTerrainLights[MaxTerrainLights]; + static F32 mScreenError; + static F32 mMinSquareSize; + static F32 mFarDistance; + static S32 mDynamicTextureCount; + static S32 mTextureSpaceUsed; + static S32 mLevelZeroCount; + static S32 mFullMipCount; + static S32 mStaticTextureCount; + static S32 mUnusedTextureCount; + static S32 mStaticTSU; + static S32 mSquareSeqAdd[256]; + static U32 mNewGenTextureCount; + static F32 mInvFarDistance; + static F32 mInvHeightRange; + static U32 mMipCap; + static bool mRenderingCommander; + + static ColorF mFogColor; + + static bool mRenderOutline; + static U32 mMaterialCount; + + static GBitmap* mBlendBitmap; + + static void (*transformPoint)(U32 point, U32 p1, U32 p2); + + static void init(); + static void shutdown(); + +// static void allocReset(); +// static void* alloc(U32 byteSize); + static void allocRenderEdges(U32 edgeCount, EdgeParent **dest, bool renderEdge); + static void subdivideChunkEdge(ChunkScanEdge *e, Point2I pos, bool chunkEdge); + static void processCurrentBlock2(SceneState* state, EdgeParent *topEdge, EdgeParent *rightEdge, EdgeParent *bottomEdge, EdgeParent *leftEdge); + static ChunkCornerPoint *allocInitialPoint(Point3F pos); + static ChunkCornerPoint *allocPoint(Point2I pos); + static void emitTerrChunk(SquareStackNode *n, F32 squareDistance, U32 lightMask, bool farClip, bool useDetails); + static void renderChunkOutline(EmitChunk *chunk); + static void renderChunkCommander(EmitChunk *chunk); + static void fixEdge(ChunkEdge *edge, S32 x, S32 y, S32 dx, S32 dy); + static void drawTriFan(U32 vCount, U32 *indexBuffer); + static U32 constructPoint(S32 x, S32 y); + static U32 interpPoint(U32 p1, U32 p2, S32 x, S32 y, F32 growFactor); + static void addEdge(ChunkEdge *edge); + static void clipStart(EdgePoint *pt, U32 index); + static void clipInsert(EdgePoint *pt, U32 index); + static void clipEnd(); + static void clip(U32 triFanStart); + + static F32 getScreenError() { return(mScreenError); } + static void setScreenError(F32 error) { mScreenError = error; } + + static void flushCache(); + static void transformTerrainPoint(U32 point, U32 p1, U32 p2); + + static U8 getClipFlags(Point2F &pos, U8 clipMask); + + static void allocTerrTexture(Point2I pos, U32 level, U32 mipLevel, bool vis, F32 distance); + static void freeTerrTexture(AllocatedTexture *texture); + static void buildBlendMap(AllocatedTexture *texture); + + static U32 TestSquareLights(GridSquare *sq, S32 level, Point2I pos, U32 lightMask); + static S32 TestSquareVisibility(Point3F &min, Point3F &max, S32 clipMask, F32 expand); + + static S32 allocEdges(S32 count); + static void clampEdge(S32 edge); + static void subdivideEdge(S32 edge, Point2I pos); + static F32 getSquareDistance(const Point3F& minPoint, const Point3F& maxPoint, + F32* zDiff); + static bool subdivideSquare(GridSquare *sq, S32 level, Point2I pos); + + static void emitTerrSquare(SquareStackNode *n, U32 flags); + static void emitFullMipSquare(SquareStackNode *n, U32 flags); + static void emitNonzeroSquare(SquareStackNode *n, U32 flags, U32 mipLevel); + static void emitLevelZeroSquare(SquareStackNode *n, U32 flags, U32 material, U32 mipLevel); + + static void buildTextureCoor(); + static void renderCurrentBlock(S32 firstSquare, S32 lastSquare); + + static void textureRecurse(SquareStackNode *n); + static void processCurrentBlock(S32 topEdge, S32 rightEdge, S32 bottomEdge, S32 leftEdge); + static void buildLightArray(); + static void buildClippingPlanes(bool flipClipPlanes); + static void buildDetailTable(); + + static void renderXFCache(); + static void renderBlock(TerrainBlock *, SceneState *state); + + static void textureRecurse2(SquareStackNode2 *n); + static void tesselate0(SquareStackNode2 *n, U32 flags); + static void tesselate1(SquareStackNode2 *n); + static void tesselate2(SquareStackNode2 *n); + static void tesselate3(SquareStackNode2 *n); + static void farclip(SquareStackNode2 *n, U32 flags); + static void render2(); + static void processBlockStack(SquareStackNode2 *stack, S32 curStackSize); + +protected: + static void doesWaterBlockSubmergeCamera(SceneObject *sceneObj, S32 key); +}; + +#endif diff --git a/terrain/terrRender2.cc b/terrain/terrRender2.cc new file mode 100644 index 0000000..c284bf6 --- /dev/null +++ b/terrain/terrRender2.cc @@ -0,0 +1,655 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/terrData.h" +#include "Math/mMath.h" +#include "dgl/dgl.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "dgl/gBitmap.h" +#include "terrain/terrRender.h" +#include "dgl/materialList.h" +#include "sceneGraph/sceneState.h" +#include "terrain/waterBlock.h" +#include "terrain/blender.h" +#include "Sim/frameAllocator.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sgUtil.h" + +Render2Point gEmitPoints2[65536]; +U16 gIndexArray2[131000]; +Render2Point *mEmitPoints2; +U16 *mIndexArray2; +U32 mCurrPoint2; +U32 mPrimQ[16384]; + +struct PrimitiveList +{ + enum { + MaxCount = 256, + }; + U32 count; + U16 *primList[MaxCount]; + PrimitiveList *next; + static void alloc(PrimitiveList **ph) + { + PrimitiveList *next = *ph; + *ph = (PrimitiveList *) FrameAllocator::alloc(sizeof(PrimitiveList)); + (*ph)->count = 0; + (*ph)->next = next; + } +}; + +PrimitiveList *blendList[TerrainBlock::MaterialGroups]; +PrimitiveList *baseList[TerrainBlock::MaterialGroups]; + + +inline void addPrimitive(U16 *start, PrimitiveList **lp) +{ + if(!(*lp) || ( (*lp)->count == PrimitiveList::MaxCount) ) + PrimitiveList::alloc(lp); + (*lp)->primList[(*lp)->count++] = start; +} +#if 0 +void addPrimitives(U32 flags, U16 *start) +{ + if(flags & GridSquare::Material0) + { + addPrimitive(start, &baseList[0]); + goto testBlend1; + } + if(flags & GridSquare::Material1) + { + addPrimitive(start, &baseList[1]); + goto testBlend2; + } + if(flags & GridSquare::Material2) + { + addPrimitive(start, &baseList[2]); + goto testBlend3; + } + if(flags & GridSquare::Material3) + addPrimitive(start, &baseList[3]); + return; +testBlend1: + if(flags & GridSquare::Material1) + addPrimitive(start, &blendList[1]); +testBlend2: + if(flags & GridSquare::Material2) + addPrimitive(start, &blendList[2]); +testBlend3: + if(flags & GridSquare::Material3) + addPrimitive(start, &blendList[3]); +} +#else +#define addPrimitives(x,y) +#endif + +void TerrainRender::tesselate3(SquareStackNode2 *n) +{ + // generate 9x9 array of points, and emit the 16 fans for the block + U32 i = 0, x, y; + Render2Point * ep = mEmitPoints2 + mCurrPoint2; + F32 xp, yp, step = mSquareSize; + + yp = n->pos.y * mSquareSize + mBlockPos.y; + F32 xs = n->pos.x * mSquareSize + mBlockPos.x; + + U16 *flp = mCurrentBlock->getFlagMapPtr(n->pos.x, n->pos.y); + + for(y = 0; y < 9; y++) + { + xp = xs; + U16 *hp = mCurrentBlock->getHeightAddress(n->pos.x, n->pos.y + y); + for(x = 0; x < 8; x++) + { + ep->x = xp; + ep->y = yp; + ep->z = fixedToFloat(*hp++); + ep++; + xp += step; + } + ep->x = xp; + ep->y = yp; + ep->z = fixedToFloat(mCurrentBlock->getHeight(n->pos.x + x, n->pos.y + y)); + ep++; + if((~y) & 1) + { + ep[-9].d = (ep[-9] - mCamPos).len(); + ep[-7].d = (ep[-7] - mCamPos).len(); + ep[-5].d = (ep[-5] - mCamPos).len(); + ep[-3].d = (ep[-3] - mCamPos).len(); + ep[-1].d = (ep[-1] - mCamPos).len(); + ep[-8].d = (ep[-9].d + ep[-7].d) * 0.5; + ep[-6].d = (ep[-7].d + ep[-5].d) * 0.5; + ep[-4].d = (ep[-5].d + ep[-3].d) * 0.5; + ep[-2].d = (ep[-3].d + ep[-1].d) * 0.5; + if(y > 1) + { + Render2Point *r1 = ep - 27; + Render2Point *r2 = ep - 18; + Render2Point *r3 = ep - 9; + r2[0].d = (r1[0].d + r3[0].d) * 0.5; + r2[2].d = (r1[2].d + r3[2].d) * 0.5; + r2[4].d = (r1[4].d + r3[4].d) * 0.5; + r2[6].d = (r1[6].d + r3[6].d) * 0.5; + r2[8].d = (r1[8].d + r3[8].d) * 0.5; + + r2[1].d = (r2[0].d + r2[2].d) * 0.5; + r2[3].d = (r2[2].d + r2[4].d) * 0.5; + r2[5].d = (r2[4].d + r2[6].d) * 0.5; + r2[7].d = (r2[6].d + r2[8].d) * 0.5; + } + } + yp += step; + } + for(y = 0; y < 4; y++) + { + for(x = 0; x < 4; x++) + { + addPrimitives(*flp, mIndexArray2); + flp++; + + mIndexArray2[0] = GL_TRIANGLE_FAN; + mIndexArray2[1] = 10; + mIndexArray2[2] = mCurrPoint2 + 10; + mIndexArray2[3] = mCurrPoint2 + 18; + mIndexArray2[4] = mCurrPoint2 + 19; + mIndexArray2[5] = mCurrPoint2 + 20; + mIndexArray2[6] = mCurrPoint2 + 11; + mIndexArray2[7] = mCurrPoint2 + 2; + mIndexArray2[8] = mCurrPoint2 + 1; + mIndexArray2[9] = mCurrPoint2; + mIndexArray2[10] = mCurrPoint2 + 9; + mIndexArray2[11] = mCurrPoint2 + 18; + mIndexArray2 += 12; + mCurrPoint2 += 2; + } + flp += TerrainBlock::FlagMapWidth - 4; + mCurrPoint2 += 10; + } + mCurrPoint2 += 9; +} + +void TerrainRender::tesselate2(SquareStackNode2 *n) +{ + U32 i = 0, x, y; + Render2Point * ep = mEmitPoints2 + mCurrPoint2; + F32 xp, yp, step = mSquareSize; + F32 xs = n->pos.x * mSquareSize + mBlockPos.x; + + yp = n->pos.y * mSquareSize + mBlockPos.y; + U16 *flp = mCurrentBlock->getFlagMapPtr(n->pos.x, n->pos.y); + + for(y = 0; y < 5; y++) + { + xp = xs; + for(x = 0; x < 5; x++) + { + ep->x = xp; + ep->y = yp; + ep->z = fixedToFloat(mCurrentBlock->getHeight(n->pos.x + x, n->pos.y + y)); + ep->d = (*ep - mCamPos).len(); + ep++; + xp += step; + } + yp += step; + } + for(y = 0; y < 2; y++) + { + for(x = 0; x < 2; x++) + { + addPrimitives(*flp, mIndexArray2); + flp++; + + mIndexArray2[0] = GL_TRIANGLE_FAN; + mIndexArray2[1] = 10; + mIndexArray2[2] = mCurrPoint2 + 6; + mIndexArray2[3] = mCurrPoint2 + 10; + mIndexArray2[4] = mCurrPoint2 + 11; + mIndexArray2[5] = mCurrPoint2 + 12; + mIndexArray2[6] = mCurrPoint2 + 7; + mIndexArray2[7] = mCurrPoint2 + 2; + mIndexArray2[8] = mCurrPoint2 + 1; + mIndexArray2[9] = mCurrPoint2; + mIndexArray2[10] = mCurrPoint2 + 5; + mIndexArray2[11] = mCurrPoint2 + 10; + mIndexArray2 += 12; + mCurrPoint2 += 2; + } + mCurrPoint2 += 6; + flp += TerrainBlock::FlagMapWidth - 2; + } + mCurrPoint2 += 5; +} + +void TerrainRender::tesselate1(SquareStackNode2 *n) +{ + U32 i = 0, x, y; + Render2Point * ep = mEmitPoints2 + mCurrPoint2; + F32 xp, yp, step = mSquareSize; + F32 xs = n->pos.x * mSquareSize + mBlockPos.x; + + yp = n->pos.y * mSquareSize + mBlockPos.y; + + for(y = 0; y < 3; y++) + { + xp = xs; + for(x = 0; x < 3; x++) + { + ep->x = xp; + ep->y = yp; + ep->z = fixedToFloat(mCurrentBlock->getHeight(n->pos.x + x, n->pos.y + y)); + ep->d = (*ep - mCamPos).len(); + ep++; + xp += step; + } + yp += step; + } + addPrimitives( *(mCurrentBlock->getFlagMapPtr(n->pos.x, n->pos.y)), mIndexArray2); + mIndexArray2[0] = GL_TRIANGLE_FAN; + mIndexArray2[1] = 10; + mIndexArray2[2] = mCurrPoint2 + 4; + mIndexArray2[3] = mCurrPoint2 + 6; + mIndexArray2[4] = mCurrPoint2 + 7; + mIndexArray2[5] = mCurrPoint2 + 8; + mIndexArray2[6] = mCurrPoint2 + 5; + mIndexArray2[7] = mCurrPoint2 + 2; + mIndexArray2[8] = mCurrPoint2 + 1; + mIndexArray2[9] = mCurrPoint2; + mIndexArray2[10] = mCurrPoint2 + 3; + mIndexArray2[11] = mCurrPoint2 + 6; + mIndexArray2 += 12; + mCurrPoint2 += 9; +} + +void TerrainRender::tesselate0(SquareStackNode2 *n, U32 flags) +{ +} + +void TerrainRender::farclip(SquareStackNode2 *n, U32 flags) +{ +} + +void TerrainRender::render2() +{ + glFrontFace(GL_CW); + glEnable(GL_CULL_FACE); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(Render2Point), mEmitPoints2); + TextureHandle hazeTexture = gClientSceneGraph->getFogTexture(); + + F32 invLevel = 1 / F32(mSquareSize << 2); + Point4F texGenS = Point4F(invLevel, 0, 0, 0); + Point4F texGenT = Point4F(0, invLevel, 0, 0); + glColor4f(1,1,1,1); + + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glActiveTextureARB(GL_TEXTURE0_ARB); + glEnable(GL_TEXTURE_2D); + glEnable(GL_TEXTURE_GEN_S); + glEnable(GL_TEXTURE_GEN_T); + glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glTexGenfv(GL_S, GL_OBJECT_PLANE, texGenS); + glTexGenfv(GL_T, GL_OBJECT_PLANE, texGenT); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + invLevel = 1 / F32(mSquareSize << 8); + texGenS = Point4F(invLevel, 0, 0, 0); + texGenT = Point4F(0, invLevel, 0, 0); + + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glActiveTextureARB(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + glEnable(GL_TEXTURE_GEN_S); + glEnable(GL_TEXTURE_GEN_T); + glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glTexGenfv(GL_S, GL_OBJECT_PLANE, texGenS); + glTexGenfv(GL_T, GL_OBJECT_PLANE, texGenT); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glBlendFunc(GL_ONE, GL_ONE); + + glLockArraysEXT(0, mCurrPoint2); + + //glEnableClientState(GL_TEXTURE_COORD_ARRAY); + //glTexCoordPointer(2, GL_FLOAT, sizeof(Render2Point), &mEmitPoints2->z); + //glMatrixMode(GL_TEXTURE_MATRIX); + + for(S32 i = 0; i < TerrainBlock::MaterialGroups; i++) + { + TextureHandle t = mCurrentBlock->mBaseMaterials[i]; + TextureHandle a = mCurrentBlock->mAlphaMaterials[i]; + + if(!bool(t)) + break; + + glActiveTextureARB(GL_TEXTURE0_ARB); + glBindTexture(GL_TEXTURE_2D, t.getGLName()); + glActiveTextureARB(GL_TEXTURE1_ARB); + glBindTexture(GL_TEXTURE_2D, a.getGLName()); + + glDisable(GL_BLEND); + //glBlendFunc(GL_SRC_ALPHA, GL_ZERO); + for(PrimitiveList *walk = baseList[i]; walk; walk = walk->next) + for(S32 j = 0; j < walk->count; j++) + glDrawElements(walk->primList[j][0], walk->primList[j][1], GL_UNSIGNED_SHORT, walk->primList[j] + 2); + + glEnable(GL_BLEND); + for(PrimitiveList *walk = blendList[i]; walk; walk = walk->next) + for(S32 j = 0; j < walk->count; j++) + glDrawElements(walk->primList[j][0], walk->primList[j][1], GL_UNSIGNED_SHORT, walk->primList[j] + 2); + } + glUnlockArraysEXT(); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glClientActiveTextureARB(GL_TEXTURE1_ARB); + glActiveTextureARB(GL_TEXTURE1_ARB); + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + glDisable(GL_TEXTURE_2D); + + // last pass is the haze pass + glClientActiveTextureARB(GL_TEXTURE0_ARB); + glActiveTextureARB(GL_TEXTURE0_ARB); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + glBindTexture(GL_TEXTURE_2D, hazeTexture.getGLName()); + + + static F32 texMatrix[16] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }; + // get the height range from the root of the block map + GridSquare *sq = mCurrentBlock->findSquare(TerrainBlock::BlockShift, Point2I(0,0)); + + F32 heightOffset = fixedToFloat(sq->minHeight); + F32 heightRange = fixedToFloat(sq->maxHeight) - heightOffset; + texMatrix[4] = - 1 / mFarDistance; + texMatrix[12] = 1; + texMatrix[1] = 1 / heightRange; + texMatrix[13] = -heightOffset / heightRange; + glMatrixMode(GL_TEXTURE); + glLoadMatrixf(texMatrix); + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(Render2Point), &(mEmitPoints2[0].z)); + glLockArraysEXT(0, mCurrPoint2); + + U16 *ar = gIndexArray2; + while(ar != mIndexArray2) + { + glDrawElements(ar[0], ar[1], GL_UNSIGNED_SHORT, ar + 2); + ar += ar[1] + 2; + } + glUnlockArraysEXT(); + + glLoadIdentity(); + + glDisable(GL_BLEND); + //glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDisable(GL_TEXTURE_2D); + + glDisable(GL_CULL_FACE); +} + +static S32 getPower(S32 x) +{ + // Returns 2^n (the highest bit). + S32 i = 0; + if (x) + do + i++; + while (x >>= 1); + return i; +} + +void TerrainRender::textureRecurse2(SquareStackNode2 *stack) +{ + S32 curStackSize = 1; + Point3F minPoint, maxPoint; + F32 squareDistance; + + while(curStackSize) + { + SquareStackNode2 *n = stack + curStackSize - 1; + // see if it's visible + Point2I pos = n->pos; + S32 squareSz = mSquareSize << n->level; + GridSquare *sq = mCurrentBlock->findSquare(n->level, pos); + + minPoint.set(mSquareSize * pos.x + mBlockPos.x, + mSquareSize * pos.y + mBlockPos.y, + fixedToFloat(sq->minHeight)); + maxPoint.set(minPoint.x + (mSquareSize << n->level), + minPoint.y + (mSquareSize << n->level), + fixedToFloat(sq->maxHeight)); + + F32 zDiff; + squareDistance = getSquareDistance(minPoint, maxPoint, &zDiff); + + // holes only in the primary terrain block + if (squareDistance >= mFarDistance || + ((sq->flags & GridSquare::Empty) && mBlockPos.x == 0 && mBlockPos.y == 0)) + { + curStackSize--; + continue; + } + // first check the level - if its 3 or less, we have to just make a bitmap: + // level 3 == 8x8 square - 8x8 * 16x16 == 128x128 + S32 mipLevel = 7; + if(n->level > 6) + goto norecalloc; + if(n->level != mTextureMinSquareSize) + { + // get the mip level of the square and see if we're in range + if(squareDistance > 0.001) + { + S32 size = S32(dglProjectRadius(squareDistance + (squareSz >> 1), squareSz)); + mipLevel = getPower(size * 0.75); + //if(n->level >= 6) + //{ + // if(mipLevel - n->level > 2) + // goto norecalloc; + //} + //else + if(mipLevel > 7) // too big for this square + goto norecalloc; + } + else + goto norecalloc; + } + + allocTerrTexture(n->pos, n->level, mipLevel, false, squareDistance); + curStackSize--; + continue; +norecalloc: + // split it up: + S32 nextLevel = n->level - 1; + for(S32 i = 0; i < 4; i++) + n[i].level = nextLevel; + + S32 squareHalfSize = 1 << nextLevel; + + // push in reverse order of processing. + n[3].pos = pos; + n[2].pos.set(pos.x + squareHalfSize, pos.y); + n[1].pos.set(pos.x, pos.y + squareHalfSize); + n[0].pos.set(pos.x + squareHalfSize, pos.y + squareHalfSize); + curStackSize += 3; + } +} + + +void TerrainRender::processBlockStack(SquareStackNode2 *stack, S32 curStackSize) +{ + Point3F minPoint, maxPoint; + F32 squareDistance; + mCurrPoint2 = 0; + mIndexArray2 = gIndexArray2; + mEmitPoints2 = gEmitPoints2; + U32 i; + + for(i = 0; i < TerrainBlock::MaterialGroups; i++) + { + blendList[i] = 0; + baseList[i] = 0; + } + for(i = 0; i < curStackSize; i++) + stack[i].texAllocated = false; + + F32 worldToScreenScale = dglProjectRadius(1,1); + F32 zeroFullMipDistance = (mSquareSize * worldToScreenScale) / (1 << 3) - (mSquareSize >> 1); + F32 zeroSmallMipDistance = (mSquareSize * worldToScreenScale) / (1 << 6) - (mSquareSize >> 1); + F32 zeroDetailDistance = (mSquareSize * worldToScreenScale) / (1 << 6) - (mSquareSize >> 1); + + while(curStackSize) + { + SquareStackNode2 *n = stack + curStackSize - 1; + // see if it's visible + GridSquare *sq = mCurrentBlock->findSquare(n->level, n->pos.x, n->pos.y); + + minPoint.set(mSquareSize * n->pos.x + mBlockPos.x, + mSquareSize * n->pos.y + mBlockPos.y, + fixedToFloat(sq->minHeight)); + maxPoint.set(minPoint.x + (mSquareSize << n->level), + minPoint.y + (mSquareSize << n->level), + fixedToFloat(sq->maxHeight)); + + // holes only in the primary terrain block + if ((sq->flags & GridSquare::Empty) && mBlockPos.x == 0 && mBlockPos.y == 0) + { + curStackSize--; + continue; + } + + F32 zDiff; + S32 nextClipFlags = 0; + squareDistance = getSquareDistance(minPoint, maxPoint, &zDiff); + + if(n->clipFlags) + { + if(n->clipFlags & FarSphereMask) + { + if(squareDistance >= mFarDistance) + { + curStackSize--; + continue; + } + + S32 squareSz = mSquareSize << n->level; + if(squareDistance + maxPoint.z - minPoint.z + squareSz + squareSz > mFarDistance) + nextClipFlags |= FarSphereMask; + } + nextClipFlags |= TestSquareVisibility(minPoint, maxPoint, n->clipFlags, mSquareSize); + if(nextClipFlags == -1) + { + //if(!n->texAllocated) + // textureRecurse(n); + // trivially rejected, so pop it off the stack + curStackSize--; + continue; + } + } + + if(!n->texAllocated) + { + S32 squareSz = mSquareSize << n->level; + // first check the level - if its 3 or less, we have to just make a bitmap: + // level 3 == 8x8 square - 8x8 * 16x16 == 128x128 + if(n->level > 6) + goto notexalloc; + + S32 mipLevel = 7; + if(n->level != mTextureMinSquareSize) + { + // get the mip level of the square and see if we're in range + if(squareDistance > 0.001) + { + S32 size = S32(dglProjectRadius(squareDistance + (squareSz >> 1), squareSz)); + mipLevel = getPower(size * 0.75); + if(mipLevel > 7) // too big for this square + goto notexalloc; + } + else + goto notexalloc; + } + allocTerrTexture(n->pos, n->level, mipLevel, true, squareDistance); + n->texAllocated = true; + } +notexalloc: + + if(n->lightMask) + n->lightMask = TestSquareLights(sq, n->level, n->pos, n->lightMask); + + if(n->level <= 3) + { + bool noHoles = !(sq->flags & GridSquare::HasEmpty); + if(n->level == 3) + { + if(nextClipFlags == 0 && noHoles) + { + tesselate3(n); + curStackSize--; + continue; + } + } + else + { + if(!(nextClipFlags & FarSphereMask) && noHoles) + { + if(n->level == 2) + tesselate2(n); + else if(n->level == 1) + tesselate1(n); + else + tesselate0(n, sq->flags); + curStackSize--; + continue; + } + } + if(n->level == 0) + { + // it's gotta be far clipped... + farclip(n, sq->flags); + curStackSize--; + continue; + } + } + Point2I pos = n->pos; + // subdivide this square and throw it on the stack + S32 nextLevel = n->level - 1; + S32 squareHalfSize = 1 << nextLevel; + + n->level = nextLevel; + n->clipFlags = nextClipFlags; + + for(S32 i = 1; i < 4; i++) + { + n[i].level = nextLevel; + n[i].clipFlags = nextClipFlags; + n[i].lightMask = n->lightMask; + } + // push in reverse order of processing. + n[3].pos = pos; + n[2].pos.set(pos.x + squareHalfSize, pos.y); + n[1].pos.set(pos.x, pos.y + squareHalfSize); + n[0].pos.set(pos.x + squareHalfSize, pos.y + squareHalfSize); + curStackSize += 3; + } +} + diff --git a/terrain/waterBlock.cc b/terrain/waterBlock.cc new file mode 100644 index 0000000..1caf82a --- /dev/null +++ b/terrain/waterBlock.cc @@ -0,0 +1,724 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/waterBlock.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "Core/bitStream.h" +#include "Math/mBox.h" +#include "dgl/dgl.h" +#include "Core/color.h" +#include "terrain/terrData.h" +#include "terrain/terrRender.h" +#include "Math/mathIO.h" +#include "sceneGraph/sgUtil.h" +#include "audio/audioDataBlock.h" + +//============================================================================== + +IMPLEMENT_CO_NETOBJECT_V1(WaterBlock); + +//============================================================================== + +bool WaterBlock::mCameraSubmerged = false; +U32 WaterBlock::mSubmergedType = 0; +TextureHandle WaterBlock::mSubmergeTexture[WC_NUM_SUBMERGE_TEX]; + +//============================================================================== +// I know this is a bit of a hack. I've done this in order to avoid a coupling +// between the fluid and the rest of the system. +// +static SceneState* pSceneState = NULL; + +static F32 FogFunction( F32 Distance, F32 DeltaZ ) +{ + return( pSceneState->getHazeAndFog( Distance, DeltaZ ) ); +} + +//============================================================================== + +WaterBlock::WaterBlock() +{ + mNetFlags.set(Ghostable | ScopeAlways); + mTypeMask = WaterObjectType; + + mObjBox.min.set( 0, 0, 0 ); + mObjBox.max.set( 1, 1, 1 ); + + mLiquidType = eOceanWater; + mDensity = 1; + mViscosity = 15; + mWaveMagnitude = 1.0f; + mSurfaceTexture = TextureHandle(); + mSurfaceOpacity = 0.75f; + mEnvMapTexture = TextureHandle(); + mEnvMapIntensity = 1.0f; + mRemoveWetEdges = true; + mAudioEnvironment = 0; + + // lets be good little programmers and initialize our data! + mSurfaceName = NULL; + mEnvMapName = NULL; + + dMemset( mSubmergeName, 0, sizeof( mSubmergeName ) ); + + mpTerrain = NULL; + mSurfaceZ = 0.0f; + + mFluid.SetFogFn( FogFunction ); +} + +//============================================================================== + +WaterBlock::~WaterBlock() +{ +} + +//============================================================================== + +void WaterBlock::SnagTerrain( SceneObject* sceneObj, S32 key ) +{ + WaterBlock* pWater = (WaterBlock*)key; + pWater->mpTerrain = dynamic_cast(sceneObj); +} + +//============================================================================== + +void WaterBlock::UpdateFluidRegion( void ) +{ + MatrixF M; + Point3F P; + + P = mObjToWorld.getPosition(); + P.x += 1024.0f; + P.y += 1024.0f; + + mSurfaceZ = P.z + mObjScale.z; + + mFluid.SetInfo( P.x, P.y, + mObjScale.x, mObjScale.y, + mSurfaceZ, + mWaveMagnitude, + mSurfaceOpacity, + mEnvMapIntensity, + mRemoveWetEdges ); + + P.x -= 1024.0f; + P.y -= 1024.0f; + + M.identity(); + M.setPosition( P ); + + Parent::setTransform( M ); + + resetWorldBox(); + + if( isServerObject() ) + setMaskBits(1); +} + +//============================================================================== + +bool WaterBlock::onAdd() +{ + if( !isClientObject() ) + { + // Make sure that the Fluid has had a chance to tweak the values. + UpdateFluidRegion(); + } + + if( !Parent::onAdd() ) + return false; + + if(!mRemoveWetEdges) + { + mObjBox.min.set(-1e4, -1e4, 0); + mObjBox.max.set( 1e4, 1e4, 1); + } + + resetWorldBox(); + addToScene(); + + if( isClientObject() ) + { + // load textures + mSurfaceTexture = TextureHandle( mSurfaceName, MeshTexture ); + mEnvMapTexture = TextureHandle( mEnvMapName, MeshTexture ); + + for( int i=0; ifindObjects( mWorldBox, (U32)TerrainObjectType, SnagTerrain, (S32)this ); + if( mpTerrain ) + mFluid.SetTerrainData( mpTerrain->heightMap ); + } + + return( true ); + } + return( false ); +} + +//============================================================================== +// The incoming matrix transforms the water into the WORLD. This includes any +// offset in the terrain, so the translation can be negative. We need to get +// the translation to be expressed in "terrain square" terms. The terrain +// offset is always -1024,-1024. + +void WaterBlock::setTransform( const MatrixF &mat ) +{ + mObjToWorld = mat; + UpdateFluidRegion(); +} + +//============================================================================== + +void WaterBlock::setScale( const VectorF & scale ) +{ + mObjScale = scale; + UpdateFluidRegion(); +} + +//============================================================================== + +bool WaterBlock::prepRenderImage( SceneState* state, + const U32 stateKey, + const U32, + const bool ) +{ + // Attempt to get the terrain. + if( (mpTerrain == NULL) && (mContainer != NULL) ) + { + mContainer->findObjects( mWorldBox, (U32)TerrainObjectType, SnagTerrain, (S32)this ); + if( mpTerrain ) + mFluid.SetTerrainData( mpTerrain->heightMap ); + } + + + 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)) { + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + image->isTranslucent = true; + + image->sortType = SceneRenderImage::Plane; + image->plane = PlaneF(0, 0, 1, -mSurfaceZ); + image->poly[0] = Point3F(mObjBox.min.x, mObjBox.min.y, 1); + image->poly[1] = Point3F(mObjBox.min.x, mObjBox.max.y, 1); + image->poly[2] = Point3F(mObjBox.max.x, mObjBox.max.y, 1); + image->poly[3] = Point3F(mObjBox.max.x, mObjBox.min.y, 1); + for (U32 i = 0; i < 4; i++) + { + image->poly[i].convolve(mObjScale); + getTransform().mulP(image->poly[i]); + } + + // Calc the area of this poly + Point3F intermed; + mCross(image->poly[2] - image->poly[0], image->poly[3] - image->poly[1], &intermed); + image->polyArea = intermed.len() * 0.5; + + state->insertRenderImage(image); + } + + return false; +} + +//============================================================================== + +void WaterBlock::renderObject( SceneState* state, SceneRenderImage* ) +{ + AssertFatal( dglIsInCanonicalState(), + "Error, GL not in canonical state on entry" ); + + RectI viewport; + Point3F Eye; // Camera in water space. + bool CameraSubmergedFlag = false; + + dglGetViewport ( &viewport ); + glMatrixMode ( GL_PROJECTION ); + glPushMatrix (); + state->setupObjectProjection( this ); + +/**** + // Debug assist. + // Render a wire outline around the base of the water block. + if( 0 ) + { + glMatrixMode ( GL_MODELVIEW ); + glPushMatrix (); + dglMultMatrix ( &mObjToWorld ); + + F32 X0 = 0; + F32 Y0 = 0; + F32 X1 = mObjScale.x; + F32 Y1 = mObjScale.y; + F32 Z = 0; + + glDisable ( GL_TEXTURE_2D ); + glDisable ( GL_DEPTH_TEST ); + glEnable ( GL_BLEND ); + glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE ); + glColor4f ( 0.6f, 0.6f, 0.0f, 0.5f ); + glBegin ( GL_QUADS ); + + glVertex3f ( X0, Y0, Z ); + glVertex3f ( X1, Y0, Z ); + glVertex3f ( X1, Y1, Z ); + glVertex3f ( X0, Y1, Z ); + + glEnd (); + glEnable ( GL_DEPTH_TEST ); + glColor4f ( 0.8f, 0.8f, 0.8f, 0.8f ); + glBegin ( GL_QUADS ); + + glVertex3f ( X0, Y0, Z ); + glVertex3f ( X1, Y0, Z ); + glVertex3f ( X1, Y1, Z ); + glVertex3f ( X0, Y1, Z ); + + glEnd (); + glPolygonMode ( GL_FRONT_AND_BACK, GL_FILL ); + + glPopMatrix (); + glMatrixMode ( GL_MODELVIEW ); + } +****/ + + // Handle the fluid. + { + // The water block lives in terrain space which is -1024,-1024. + // To get into world space, we just add 1024,1024. + // The fluid lives in world space. + + Point3F W2Lv( 1024.0f, 1024.0f, 0.0f ); // World to Local vector + Point3F L2Wv( -1024.0f, -1024.0f, 0.0f ); // Local to World vector + MatrixF L2Wm; // Local to World matrix + + L2Wm.identity(); + L2Wm.setPosition( L2Wv ); + + glMatrixMode ( GL_MODELVIEW ); + glPushMatrix (); + dglMultMatrix ( &L2Wm ); + + + // We need the eye in water space. + { + Eye = state->getCameraPosition() + W2Lv; + mFluid.SetEyePosition( Eye.x, Eye.y, Eye.z ); + } + + // We need the frustrum in water space. + { + MatrixF L2Cm; + dglGetModelview( &L2Cm ); + L2Cm.inverse(); + + Point3F Dummy; + Dummy = L2Cm.getPosition(); + + F64 frustumParam[6]; + dglGetFrustum(&frustumParam[0], &frustumParam[1], + &frustumParam[2], &frustumParam[3], + &frustumParam[4], &frustumParam[5]); + + sgComputeOSFrustumPlanes(frustumParam, + L2Cm, + Dummy, + mClipPlane[1], + mClipPlane[2], + mClipPlane[3], + mClipPlane[4], + mClipPlane[5]); + + // near plane is needed as well... + PlaneF p(0, 1, 0, -frustumParam[4]); + mTransformPlane(L2Cm, Point3F(1,1,1), p, &mClipPlane[0]); + +// F64 left, right, bottom, top, near, far; +// MatrixF L2Cm; + +// dglGetFrustum( &left, &right, &bottom, &top, &near, &far ); + +// far = state->getVisibleDistance(); + +// Point3F Origin ( 0, 0, 0 ); +// Point3F FarOrigin ( 0, far, 0 ); +// Point3F UpperLeft ( left, near, top ); +// Point3F UpperRight ( right, near, top ); +// Point3F LowerLeft ( left, near, bottom ); +// Point3F LowerRight ( right, near, bottom ); + +// dglGetModelview( &L2Cm ); +// L2Cm.inverse(); + +// Point3F Dummy; +// Dummy = L2Cm.getPosition(); + +// L2Cm.mulP( Origin ); +// L2Cm.mulP( FarOrigin ); +// L2Cm.mulP( UpperLeft ); +// L2Cm.mulP( UpperRight ); +// L2Cm.mulP( LowerLeft ); +// L2Cm.mulP( LowerRight ); + +// // Frustrum clip planes: 0=T 1=B 2=L 3=R 4=N 5=F + +// mClipPlane[0].set( UpperLeft, Origin, UpperRight ); +// mClipPlane[1].set( LowerRight, Origin, LowerLeft ); +// mClipPlane[2].set( LowerLeft, Origin, UpperLeft ); +// mClipPlane[3].set( UpperRight, Origin, LowerRight ); +// mClipPlane[4].set( UpperRight, UpperLeft, LowerRight ); + +// // far plane is reverse of near plane vector +// mClipPlane[5].x = -mClipPlane[4].x; +// mClipPlane[5].y = -mClipPlane[4].y; +// mClipPlane[5].z = -mClipPlane[4].z; +// mClipPlane[5].d = -mClipPlane[4].d + far; + + mFluid.SetFrustrumPlanes( (F32*)mClipPlane ); + } + + // Fog stuff. + { + pSceneState = state; + ColorF FogColor = state->getFogColor(); + mFluid.SetFogParameters( FogColor.red, + FogColor.green, + FogColor.blue, + state->getVisibleDistance() ); + } + + // And RENDER! + { + mFluid.Render( CameraSubmergedFlag ); + } + + // Clean up. + glPopMatrix (); + glMatrixMode ( GL_MODELVIEW ); + } + + // + // And now the closing ceremonies... + // + glMatrixMode ( GL_PROJECTION ); + glPopMatrix (); + glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + dglSetViewport ( viewport ); + + // + // Oh yes. We have to set some state information for the scene... + // + + if( CameraSubmergedFlag ) + { + mCameraSubmerged = true; + mSubmergedType = mLiquidType; + } + + AssertFatal( dglIsInCanonicalState(), + "Error, GL not in canonical state on exit" ); +} + +//============================================================================== + +void WaterBlock::inspectPostApply() +{ + resetWorldBox(); + setMaskBits(1); +} + +//============================================================================== + +static EnumTable::Enums gLiquidTypeEnums[] = +{ + { WaterBlock::eWater, "Water" }, + { WaterBlock::eOceanWater, "OceanWater" }, + { WaterBlock::eRiverWater, "RiverWater" }, + { WaterBlock::eStagnantWater, "StagnantWater" }, + { WaterBlock::eLava, "Lava" }, + { WaterBlock::eHotLava, "HotLava" }, + { WaterBlock::eCrustyLava, "CrustyLava" }, + { WaterBlock::eQuicksand, "Quicksand" } +}; +static EnumTable gLiquidTypeTable( 8, gLiquidTypeEnums ); + +//------------------------------------------------------------------------------ + +void WaterBlock::initPersistFields() +{ + Parent::initPersistFields(); + + addField( "liquidType", TypeEnum, Offset( mLiquidType, WaterBlock ), 1, &gLiquidTypeTable ); + addField( "density", TypeF32, Offset( mDensity, WaterBlock ) ); + addField( "viscosity", TypeF32, Offset( mViscosity, WaterBlock ) ); + addField( "waveMagnitude", TypeF32, Offset( mWaveMagnitude, WaterBlock ) ); + addField( "surfaceTexture", TypeString, Offset( mSurfaceName, WaterBlock ) ); + addField( "surfaceOpacity", TypeF32, Offset( mSurfaceOpacity, WaterBlock ) ); + addField( "envMapTexture", TypeString, Offset( mEnvMapName, WaterBlock ) ); + addField( "envMapIntensity", TypeF32, Offset( mEnvMapIntensity, WaterBlock ) ); + addField( "submergeTexture", TypeString, Offset( mSubmergeName, WaterBlock ), WC_NUM_SUBMERGE_TEX ); + addField( "removeWetEdges", TypeBool, Offset( mRemoveWetEdges, WaterBlock ) ); + addField( "audioEnvironment", TypeAudioEnvironmentPtr, Offset( mAudioEnvironment, WaterBlock ) ); +} + +//============================================================================== + +void WaterBlock::cToggleWireFrame( SimObject* obj, S32, const char** ) +{ + WaterBlock* pWaterBlock = static_cast(obj); + if( pWaterBlock ) + { + pWaterBlock->mFluid.m_ShowWire = !(pWaterBlock->mFluid.m_ShowWire); + if( pWaterBlock->mFluid.m_ShowWire ) + Con::printf( "WaterBlock wire frame ENABLED" ); + else + Con::printf( "WaterBlock wire frame DISABLED" ); + } +} + +//============================================================================== + +void WaterBlock::consoleInit() +{ + Con::addCommand( "WaterBlock", "toggleWireFrame", cToggleWireFrame, "waterBlock.toggleWireFrame()", 2, 2 ); +} + +//============================================================================== + +U32 WaterBlock::packUpdate( NetConnection* c, U32 mask, BitStream* stream ) +{ + U32 retMask = Parent::packUpdate( c, mask, stream ); + + // No masking in here now. + // There's not too much data, and it doesn't change during normal game play. + + stream->writeAffineTransform( mObjToWorld ); + mathWrite( *stream, mObjScale ); + + stream->writeString( mSurfaceName ); + stream->writeString( mEnvMapName ); + + for( int i=0; iwriteString( mSubmergeName[i] ); + } + + stream->write( (S32)mLiquidType ); + stream->write( mDensity ); + stream->write( mViscosity ); + stream->write( mWaveMagnitude ); + stream->write( mSurfaceOpacity ); + stream->write( mEnvMapIntensity ); + stream->write( mRemoveWetEdges ); + + // audio environment: + if(stream->writeFlag(mAudioEnvironment)) + stream->writeRangedU32(mAudioEnvironment->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + return( retMask ); +} + +//============================================================================== + +void WaterBlock::unpackUpdate( NetConnection* c, BitStream* stream ) +{ + Parent::unpackUpdate( c, stream ); + + U32 LiquidType; + + // No masking in here now. + // There's not too much data, and it doesn't change during normal game play. + + stream->readAffineTransform( &mObjToWorld ); + mathRead( *stream, &mObjScale ); + + mSurfaceName = stream->readSTString(); + mEnvMapName = stream->readSTString(); + + for( int i=0; ireadSTString(); + } + + + stream->read( &LiquidType ); + stream->read( &mDensity ); + stream->read( &mViscosity ); + stream->read( &mWaveMagnitude ); + stream->read( &mSurfaceOpacity ); + stream->read( &mEnvMapIntensity ); + stream->read( &mRemoveWetEdges ); + + // audio environment: + if(stream->readFlag()) + { + U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + mAudioEnvironment = dynamic_cast(Sim::findObject(profileId)); + } + else + mAudioEnvironment = 0; + + mLiquidType = (EWaterType)LiquidType; + + UpdateFluidRegion(); + + if( !isProperlyAdded() ) + return; + + resetWorldBox(); +} + +//============================================================================== +// This method can take a point in world space, or water block space. The default +// assumes pos is in world space, and therefore must transform it to waterblock +// space. + +bool WaterBlock::isPointSubmerged(const Point3F &pos, bool worldSpace) const +{ + return( isPointSubmergedSimple( pos, worldSpace ) ); +} + +//============================================================================== + +bool WaterBlock::isPointSubmergedSimple(const Point3F &pos, bool worldSpace) const +{ + Point3F Pos = pos; + + if( Pos.z > mSurfaceZ ) + return( false ); + + if( worldSpace ) + { + Pos.x += 1024.0f; + Pos.y += 1024.0f; + } + + return( mFluid.IsFluidAtXY( Pos.x, Pos.y ) ); +} + +//============================================================================== + +bool WaterBlock::castRay( const Point3F& start, const Point3F& end, RayInfo* info ) +{ + F32 t, x, y, X, Y; + Point3F Pos; + + // + // Looks like the incoming points are in parametric object space. Great. + // + + // The water surface is 1.0. Bail if the ray does not cross the surface. + + if( (start.z > 1.0f) && (end.z > 1.0f) ) return( false ); + if( (start.z < 1.0f) && (end.z < 1.0f) ) return( false ); + + // The ray crosses the surface plane. Find out where. + + t = (start.z - 1.0f) / (start.z - end.z); + x = start.x + (end.x - start.x) * t; + y = start.y + (end.y - start.y) * t; + + Pos = mObjToWorld.getPosition(); + + X = (x * mObjScale.x) + Pos.x + 1024.0f; + Y = (y * mObjScale.y) + Pos.y + 1024.0f; + + if( mFluid.IsFluidAtXY( X, Y ) ) + { + info->t = t; + info->point.x = x; + info->point.y = y; + info->point.z = 1.0f; + info->normal.x = 0.0f; + info->normal.y = 0.0f; + info->normal.z = 1.0f; + info->object = this; + info->material = 0; + return( true ); + } + + // Hmm. Guess we missed! + return( false ); +} + +//============================================================================== + +bool WaterBlock::isWater( U32 liquidType ) +{ + EWaterType wType = EWaterType( liquidType ); + return( wType == eWater || + wType == eOceanWater || + wType == eRiverWater || + wType == eStagnantWater ); +} + +//============================================================================== + +bool WaterBlock::isLava( U32 liquidType ) +{ + EWaterType wType = EWaterType( liquidType ); + return( wType == eLava || + wType == eHotLava || + wType == eCrustyLava ); +} + +//============================================================================== + +bool WaterBlock::isQuicksand( U32 liquidType ) +{ + EWaterType wType = EWaterType( liquidType ); + return( wType == eQuicksand ); +} + +//============================================================================== diff --git a/terrain/waterBlock.h b/terrain/waterBlock.h new file mode 100644 index 0000000..bcc16aa --- /dev/null +++ b/terrain/waterBlock.h @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WATERBLOCK_H_ +#define _WATERBLOCK_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "Sim/sceneObject.h" +#endif +#ifndef _GTEXMANAGER_H_ +#include "dgl/gTexManager.h" +#endif +#ifndef _COLOR_H_ +#include "Core/color.h" +#endif +#ifndef _TERRDATA_H_ +#include "terrain/terrData.h" +#endif + +#ifndef _FLUID_H_ +#include "terrain/Fluid.h" +#endif + +//============================================================================== +class AudioEnvironment; + +class WaterBlock : public SceneObject +{ + typedef SceneObject Parent; + +public: + + enum EWaterType + { + eWater = 0, + eOceanWater = 1, + eRiverWater = 2, + eStagnantWater = 3, + eLava = 4, + eHotLava = 5, + eCrustyLava = 6, + eQuicksand = 7, + }; + + enum WaterConst + { + WC_NUM_SUBMERGE_TEX = 2, + }; + +private: + + fluid mFluid; + + TextureHandle mSurfaceTexture; + TextureHandle mEnvMapTexture; + PlaneF mClipPlane[6]; // Frustrum clip planes: 0=T 1=B 2=L 3=R 4=N 5=F + TerrainBlock* mpTerrain; // Terrain block + F32 mSurfaceZ; + + // Fields exposed to the editor. + EWaterType mLiquidType; // Water? Lava? What? + F32 mDensity; + F32 mViscosity; + F32 mWaveMagnitude; + StringTableEntry mSurfaceName; + F32 mSurfaceOpacity; + StringTableEntry mEnvMapName; + F32 mEnvMapIntensity; + StringTableEntry mSubmergeName[WC_NUM_SUBMERGE_TEX]; + bool mRemoveWetEdges; + AudioEnvironment * mAudioEnvironment; + + TextureHandle mLocalSubmergeTexture[WC_NUM_SUBMERGE_TEX]; + + static TextureHandle mSubmergeTexture[WC_NUM_SUBMERGE_TEX]; + +public: + + WaterBlock(); + ~WaterBlock(); + + bool onAdd ( void ); + void onRemove ( void ); + bool onSceneAdd ( SceneGraph* pGraph ); + void setTransform ( const MatrixF& mat ); + void setScale ( const VectorF& scale ); + bool prepRenderImage ( SceneState*, const U32, const U32, const bool ); + void renderObject ( SceneState*, SceneRenderImage* ); + void inspectPostApply ( void ); + F32 getSurfaceHeight ( void ) { return mSurfaceZ; } + + void UpdateFluidRegion ( void ); + static void SnagTerrain ( SceneObject* sceneObj, S32 key ); + + static void cToggleWireFrame( SimObject*, S32, const char** ); + + DECLARE_CONOBJECT(WaterBlock); + + static void initPersistFields(); + static void consoleInit(); + +// bool setLiquidType(EWaterFlag liquidType); + EWaterType getLiquidType() const { return mLiquidType; } + static bool isWater ( U32 liquidType ); + static bool isLava ( U32 liquidType ); + static bool isQuicksand ( U32 liquidType ); + + F32 getViscosity() const { return mViscosity; } + F32 getDensity() const { return mDensity; } + + U32 packUpdate ( NetConnection*, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection*, BitStream *stream ); + + bool isPointSubmerged ( const Point3F &pos, bool worldSpace = true ) const; + bool isPointSubmergedSimple( const Point3F &pos, bool worldSpace = true ) const; + + AudioEnvironment * getAudioEnvironment() { return(mAudioEnvironment); } + + static bool mCameraSubmerged; + static U32 mSubmergedType; + + static TextureHandle getSubmergeTexture( U32 index ){ return mSubmergeTexture[index]; } + +protected: + bool castRay( const Point3F& start, const Point3F& end, RayInfo* info ); +}; + +#endif diff --git a/ts/tsAnimate.cc b/ts/tsAnimate.cc new file mode 100644 index 0000000..bbdb305 --- /dev/null +++ b/ts/tsAnimate.cc @@ -0,0 +1,1099 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, 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; + + // 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; isequence->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->sequence->rotationMatters); + tranBeenSet.takeAway(th->sequence->translationMatters); + scaleBeenSet.takeAway(th->sequence->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; isequence->rotationMatters.start(); + end = b; + for (nodeIndex=start; nodeIndexsequence->rotationMatters.next(nodeIndex), j++) + { + // skip nodes outside of this detail + if (nodeIndexgetRotation(*th->sequence,th->keyNum1,j,&q1); + mShape->getRotation(*th->sequence,th->keyNum2,j,&q2); + TSTransform::interpolate(q1,q2,th->keyPos,&smNodeCurrentRotations[nodeIndex]); + rotBeenSet.set(nodeIndex); + smRotationThreads[nodeIndex] = th; + } + } + + j=0; + start = th->sequence->translationMatters.start(); + end = b; + for (nodeIndex=start; nodeIndexsequence->translationMatters.next(nodeIndex), j++) + { + if (nodeIndexgetTranslation(*th->sequence,th->keyNum1,j); + const Point3F & p2 = mShape->getTranslation(*th->sequence,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); + + // compute transforms + for (i=a; 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]->sequence->rotationMatters.test(nodeIndex)) + { + thread = mTransitionThreads[i]; + break; + } + } + AssertFatal(thread,"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]->sequence->translationMatters.test(nodeIndex)) + { + thread = mTransitionThreads[i]; + break; + } + } + AssertFatal(thread,"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]->sequence->scaleMatters.test(nodeIndex)) + { + thread = mTransitionThreads[i]; + break; + } + } + AssertFatal(thread,"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; isequence->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->sequence->animatesAlignedScale()) + code += 1; + else if (thread->sequence->animatesArbitraryScale()) + code += 2; + if (animatesAlignedScale()) + code +=3; + if (animatesArbitraryScale()) + code += 6; + + F32 uniformScale; + Point3F alignedScale; + TSScale arbitraryScale; + for (S32 nodeIndex=start; nodeIndexsequence->scaleMatters.next(nodeIndex), j++) + { + if (nodeIndex uniform + case 1: // uniform -> aligned + case 2: // uniform -> arbitrary + { + F32 s1 = mShape->getUniformScale(*thread->sequence,thread->keyNum1,j); + F32 s2 = mShape->getUniformScale(*thread->sequence,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->sequence,thread->keyNum1,j); + const Point3F & s2 = mShape->getAlignedScale(*thread->sequence,thread->keyNum2,j); + TSTransform::interpolate(s1,s2,thread->keyPos,&alignedScale); + break; + } + case 8: // arbitrary -> arbitary + { + TSScale s1,s2; + mShape->getArbitraryScale(*thread->sequence,thread->keyNum1,j,&s1); + mShape->getArbitraryScale(*thread->sequence,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->sequence,th->keyNum1,offset); + const Point3F & p2 = mShape->getTranslation(*th->sequence,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->sequence->translationMatters; + nodeMatters.overlap(thread->sequence->rotationMatters); + nodeMatters.overlap(thread->sequence->scaleMatters); + S32 start = nodeMatters.start(); + S32 end = b; + for (S32 nodeIndex=start; nodeIndexsequence->rotationMatters.test(nodeIndex)) + jrot++; + if (thread->sequence->translationMatters.test(nodeIndex)) + jtrans++; + if (thread->sequence->scaleMatters.test(nodeIndex)) + jscale++; + continue; + } + + MatrixF mat(true); + if (thread->sequence->rotationMatters.test(nodeIndex)) + { + QuatF q1,q2; + mShape->getRotation(*thread->sequence,thread->keyNum1,jrot,&q1); + mShape->getRotation(*thread->sequence,thread->keyNum2,jrot,&q2); + QuatF quat; + TSTransform::interpolate(q1,q2,thread->keyPos,&quat); + TSTransform::setMatrix(quat,&mat); + jrot++; + } + + if (thread->sequence->translationMatters.test(nodeIndex)) + { + const Point3F & p1 = mShape->getTranslation(*thread->sequence,thread->keyNum1,jtrans); + const Point3F & p2 = mShape->getTranslation(*thread->sequence,thread->keyNum2,jtrans); + Point3F p; + TSTransform::interpolate(p1,p2,thread->keyPos,&p); + mat.setColumn(3,p); + jtrans++; + } + + if (thread->sequence->scaleMatters.test(nodeIndex)) + { + if (thread->sequence->animatesUniformScale()) + { + F32 s1 = mShape->getUniformScale(*thread->sequence,thread->keyNum1,jscale); + F32 s2 = mShape->getUniformScale(*thread->sequence,thread->keyNum2,jscale); + F32 scale = TSTransform::interpolate(s1,s2,thread->keyPos); + TSTransform::applyScale(scale,&mat); + } + else if (animatesAlignedScale()) + { + Point3F s1 = mShape->getAlignedScale(*thread->sequence,thread->keyNum1,jscale); + Point3F s2 = mShape->getAlignedScale(*thread->sequence,thread->keyNum2,jscale); + Point3F scale; + TSTransform::interpolate(s1,s2,thread->keyPos,&scale); + TSTransform::applyScale(scale,&mat); + } + else + { + TSScale s1,s2; + mShape->getArbitraryScale(*thread->sequence,thread->keyNum1,jscale,&s1); + mShape->getArbitraryScale(*thread->sequence,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; isequence->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->sequence->duration + th->sequence->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; isequence->visMatters); + + // set defaults + S32 a = mShape->subShapeFirstObject[ss]; + S32 b = a + mShape->subShapeNumObjects[ss]; + for (i=a; iobjectStates[i].vis; + + // go through each thread and set visibility on those objects that + // are not set yet and are controlled by that thread + for (i=0; isequence->frameMatters; + objectMatters.overlap(th->sequence->matFrameMatters); + objectMatters.overlap(th->sequence->visMatters); + + // skip to beginining of this sub-shape + S32 j=0; + S32 start = objectMatters.start(); + S32 end = b; + for (S32 objectIndex = start; objectIndexsequence->visMatters.test(objectIndex)) + { + F32 state1 = mShape->getObjectState(*th->sequence,th->keyNum1,j).vis; + F32 state2 = mShape->getObjectState(*th->sequence,th->keyNum2,j).vis; + if ((state1-state2) * (state1-state2) > 0.99f) + // goes from 0 to 1 -- discreet jump + mMeshObjects[objectIndex].visible = th->keyPos<0.5f ? state1 : state2; + else + // interpolate between keyframes when visibility change is gradual + mMeshObjects[objectIndex].visible = (1.0f-th->keyPos) * state1 + th->keyPos * state2; + + // record change so that later threads don't over-write us... + beenSet.set(objectIndex); + } + } + } +} + +void TSShapeInstance::animateFrame(S32 ss) +{ + S32 i; + if (!mMeshObjects.size()) + return; + + // find out who needs default values set + TSIntegerSet beenSet; + beenSet.setAll(mMeshObjects.size()); + for (i=0; isequence->frameMatters); + + // set defaults + S32 a = mShape->subShapeFirstObject[ss]; + S32 b = a + mShape->subShapeNumObjects[ss]; + for (i=a; iobjectStates[i].frameIndex; + + // go through each thread and set frame on those objects that + // are not set yet and are controlled by that thread + for (i=0; isequence->frameMatters; + objectMatters.overlap(th->sequence->matFrameMatters); + objectMatters.overlap(th->sequence->visMatters); + + // skip to beginining of this sub-shape + S32 j=0; + S32 start = objectMatters.start(); + S32 end = b; + for (S32 objectIndex = start; objectIndexsequence->frameMatters.test(objectIndex)) + { + S32 key = (th->keyPos<0.5f) ? th->keyNum1 : th->keyNum2; + mMeshObjects[objectIndex].frame = mShape->getObjectState(*th->sequence,key,j).frameIndex; + + // record change so that later threads don't over-write us... + beenSet.set(objectIndex); + } + } + } +} + +void TSShapeInstance::animateMatFrame(S32 ss) +{ + S32 i; + if (!mMeshObjects.size()) + return; + + // find out who needs default values set + TSIntegerSet beenSet; + beenSet.setAll(mMeshObjects.size()); + for (i=0; isequence->matFrameMatters); + + // set defaults + S32 a = mShape->subShapeFirstObject[ss]; + S32 b = a + mShape->subShapeNumObjects[ss]; + for (i=a; iobjectStates[i].matFrameIndex; + + // go through each thread and set matFrame on those objects that + // are not set yet and are controlled by that thread + for (i=0; isequence->frameMatters; + objectMatters.overlap(th->sequence->matFrameMatters); + objectMatters.overlap(th->sequence->visMatters); + + // skip to beginining of this sub-shape + S32 j=0; + S32 start = objectMatters.start(); + S32 end = b; + for (S32 objectIndex = start; objectIndexsequence->matFrameMatters.test(objectIndex)) + { + S32 key = (th->keyPos<0.5f) ? th->keyNum1 : th->keyNum2; + mMeshObjects[objectIndex].matFrame = mShape->getObjectState(*th->sequence,key,j).matFrameIndex; + + // record change so that later threads don't over-write us... + beenSet.set(objectIndex); + } + } + } +} + +void TSShapeInstance::animateDecals(S32 ss) +{ + S32 i; + if (!mDecalObjects.size()) + return; + + // find out who needs default values set + TSIntegerSet beenSet; + beenSet.setAll(mDecalObjects.size()); + for (i=0; isequence->decalMatters); + + // set defaults + S32 a = mShape->subShapeFirstDecal[ss]; + S32 b = a + mShape->subShapeNumDecals[ss]; + for (i=a; idecalStates[i].frameIndex; + + // go through each thread and set frame on those decals that + // are not set yet and are controlled by that thread + for (i=0; isequence->decalMatters.start(); + S32 end = b; + for (S32 decalIndex=start; decalIndexsequence->decalMatters.next(decalIndex), j++) + { + if (!beenSet.test(decalIndex)) + { + S32 key = (th->keyPos<0.5f) ? th->keyNum1 : th->keyNum2; + mDecalObjects[decalIndex].frame = mShape->getDecalState(*th->sequence,key,j).frameIndex; + + // record change so that later threads don't over-write us... + beenSet.set(decalIndex); + } + } + } +} + +//------------------------------------------------------------------------------------- +// Animate (and initialize detail levels) +//------------------------------------------------------------------------------------- + +void TSShapeInstance::animate() +{ + animate(mCurrentDetailLevel); +} + +void TSShapeInstance::animate(S32 dl) +{ + if (dl==-1) + // nothing to do + return; + + S32 ss = mShape->details[dl].subShapeNum; + + // this is a billboard detail... + if (ss<0) + return; + + U32 dirtyFlags = mDirtyFlags[ss]; + + if (dirtyFlags & ThreadDirty) + { + sortThreads(mThreadList); + sortThreads(mTransitionThreads); + } + + // animate ifl's? + if (dirtyFlags & IflDirty) + animateIfls(); + + // animate nodes? + if (dirtyFlags & TransformDirty) + animateNodes(ss); + + // animate objects? + if (dirtyFlags & VisDirty) + animateVisibility(ss); + if (dirtyFlags & FrameDirty) + animateFrame(ss); + if (dirtyFlags & MatFrameDirty) + animateMatFrame(ss); + + // animate decals? + if (dirtyFlags & DecalDirty) + animateDecals(ss); + + mDirtyFlags[ss] = 0; +} + +void TSShapeInstance::animateNodeSubtrees(bool forceFull) +{ + // animate all the nodes for all the detail levels... + + if (forceFull) + // force transforms to animate + setDirty(TransformDirty); + + for (S32 i=0; isubShapeNumNodes.size(); i++) + { + if (mDirtyFlags[i] & TransformDirty) + { + animateNodes(i); + mDirtyFlags[i] &= ~TransformDirty; + } + } +} + +void TSShapeInstance::animateSubtrees(bool forceFull) +{ + // animate all the subtrees + + if (forceFull) + // force full animate + setDirty(AllDirtyMask); + + for (S32 i=0; isubShapeNumNodes.size(); i++) + { + if (mDirtyFlags[i] & TransformDirty) + { + animate(i); + mDirtyFlags[i] = 0; + } + } +} + +void TSShapeInstance::addPath(TSThread * gt, float start, float end, MatrixF * mat) +{ + // never get here while in transition... + AssertFatal(!gt->transitionData.inTransition,"TSShapeInstance::addPath"); + + if (!mat) + mat = &mGroundTransform; + + MatrixF startInvM; + gt->getGround(start,&startInvM); + startInvM.inverse(); + + MatrixF endM; + gt->getGround(end,&endM); + + MatrixF addM; + addM.mul(endM,startInvM); + endM.mul(addM,*mat); + *mat = endM; +} + +bool TSShapeInstance::initGround() +{ + for (S32 i=0; itransitionData.inTransition && th->sequence->numGroundFrames>0) + { + mGroundThread = th; + return true; + } + } + return false; +} + +void TSShapeInstance::animateGround() +{ + mGroundTransform.identity(); + + // pick thread which controlls ground transform + // if we haven't already... + if (!mGroundThread && !initGround()) + return; + + S32 & loop = mGroundThread->path.loop; + float & start = mGroundThread->path.start; + float & end = mGroundThread->path.end; + + // accumulate path transform + if (loop>0) + { + addPath(mGroundThread,start,1.0f); + while (--loop) + addPath(mGroundThread,0.0f,1.0f); + addPath(mGroundThread,0.0f,end); + } + else if (loop<0) + { + addPath(mGroundThread,start,0.0f); + while (++loop) + addPath(mGroundThread,1.0f,0.0f); + addPath(mGroundThread,1.0f,end); + } + else + addPath(mGroundThread,start,end); + start = end; // in case user tries to animateGround twice +} + +void TSShapeInstance::deltaGround(TSThread * thread, F32 start, F32 end, MatrixF * mat) +{ + if (!mat) + mat = &mGroundTransform; + + mat->identity(); + if (thread->transitionData.inTransition) + return; + + F32 invDuration = 1.0f / thread->getDuration(); + start *= invDuration; + end *= invDuration; + + addPath(thread,start,end,mat); +} + +// Simple case of above- get ground delta at given position in unit range +void TSShapeInstance::deltaGround1(TSThread * thread, F32 start, F32 end, MatrixF& mat) +{ + mat.identity(); + if (thread->transitionData.inTransition) + return; + addPath(thread, start, end, &mat); +} + +void TSShapeInstance::setTriggerState(U32 stateNum, bool on) +{ + AssertFatal(stateNum<=32 && stateNum>0,"TSShapeInstance::setTriggerState: state index out of range"); + + stateNum--; // stateNum externally 1..32, internally 0..31 + U32 bit = 1 << stateNum; + if (on) + mTriggerStates |= bit; + else + mTriggerStates &= ~bit; +} + +void TSShapeInstance::setTriggerStateBit(U32 stateBit, bool on) +{ + if (on) + mTriggerStates |= stateBit; + else + mTriggerStates &= ~stateBit; +} + +bool TSShapeInstance::getTriggerState(U32 stateNum, bool clearState) +{ + AssertFatal(stateNum<=32 && stateNum>0,"TSShapeInstance::getTriggerState: state index out of range"); + + stateNum--; // stateNum externally 1..32, internally 0..31 + U32 bit = 1 << stateNum; + bool ret = mTriggerStates & bit; + if (clearState) + mTriggerStates &= ~bit; + return ret; +} + +void TSShapeInstance::setNodeAnimationState(S32 nodeIndex, U32 animationState) +{ + AssertFatal((animationState & ~(MaskNodeAll|MaskNodeHandsOff|MaskNodeCallback)) == 0,"TSShapeInstance::setNodeAnimationState (1)"); + + // hands-off & callback takes precedance + if (animationState & MaskNodeHandsOff) + animationState = MaskNodeHandsOff | MaskNodeBlend; + else if (animationState & MaskNodeCallback) + animationState = MaskNodeCallback | MaskNodeBlend; + + // if we're not changing anything then get out of here now + if (animationState == getNodeAnimationState(nodeIndex)) + return; + + // feel so dirty... + setDirty(AllDirtyMask); + + if (animationState & MaskNodeAllButBlend) + { + if (animationState & MaskNodeRotation) + mMaskRotationNodes.set(nodeIndex); + if (animationState & MaskNodePosX) + mMaskPosXNodes.set(nodeIndex); + if (animationState & MaskNodePosY) + mMaskPosYNodes.set(nodeIndex); + if (animationState & MaskNodePosZ) + mMaskPosZNodes.set(nodeIndex); + } + else + { + // no masking clear out all the masking lists + mMaskRotationNodes.clear(nodeIndex); + mMaskPosXNodes.clear(nodeIndex); + mMaskPosYNodes.clear(nodeIndex); + mMaskPosZNodes.clear(nodeIndex); + } + + if (animationState & MaskNodeBlend) + mDisableBlendNodes.set(nodeIndex); + else + mDisableBlendNodes.clear(nodeIndex); + + if (animationState & MaskNodeHandsOff) + mHandsOffNodes.set(nodeIndex); + else + mHandsOffNodes.clear(nodeIndex); + + if (animationState & MaskNodeCallback) + mCallbackNodes.set(nodeIndex); + else + mCallbackNodes.clear(nodeIndex); +} + +U32 TSShapeInstance::getNodeAnimationState(S32 nodeIndex) +{ + U32 ret = 0; + if (mMaskRotationNodes.test(nodeIndex)) + ret |= MaskNodeRotation; + if (mMaskPosXNodes.test(nodeIndex)) + ret |= MaskNodePosX; + if (mMaskPosYNodes.test(nodeIndex)) + ret |= MaskNodePosY; + if (mMaskPosZNodes.test(nodeIndex)) + ret |= MaskNodePosZ; + if (mDisableBlendNodes.test(nodeIndex)) + ret |= MaskNodeBlend; + if (mHandsOffNodes.test(nodeIndex)) + ret |= MaskNodeHandsOff; + if (mCallbackNodes.test(nodeIndex)) + ret |= MaskNodeCallback; + return ret; +} diff --git a/ts/tsCollision.cc b/ts/tsCollision.cc new file mode 100644 index 0000000..6c82614 --- /dev/null +++ b/ts/tsCollision.cc @@ -0,0 +1,374 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShapeInstance.h" +#include "Sim/sceneObject.h" + +//------------------------------------------------------------------------------------- +// Collision methods +//------------------------------------------------------------------------------------- + +bool TSShapeInstance::buildPolyList(AbstractPolyList * polyList, S32 dl) +{ + // if dl==-1, nothing to do + if (dl==-1) + return false; + + AssertFatal(dl>=0 && dldetails.size(),"TSShapeInstance::buildPolyList"); + + // get subshape and object detail + const TSDetail * detail = &mShape->details[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + // set up static data + setStatics(dl); + + // nothing emitted yet... + bool emitted = false; + U32 surfaceKey = 0; + + S32 start = mShape->subShapeFirstObject[ss]; + S32 end = mShape->subShapeNumObjects[ss] + start; + if (startgetTransform(&initialMat,&initialScale); + + // set up for first object's node + MatrixF mat; + MatrixF scaleMat(true); + F32* p = scaleMat; + p[0] = initialScale.x; + p[5] = initialScale.y; + p[10] = initialScale.z; + MatrixF * previousMat = mMeshObjects[start].getTransform(); + mat.mul(initialMat,scaleMat); + mat.mul(*previousMat); + polyList->setTransform(&mat,Point3F(1, 1, 1)); + + // run through objects and collide + for (S32 i=start; igetTransform() != previousMat) + { + // different node from before, set up for this node + previousMat = mesh->getTransform(); + mat.mul(initialMat,scaleMat); + mat.mul(*previousMat); + polyList->setTransform(&mat,Point3F(1, 1, 1)); + } + // collide... + emitted |= mesh->buildPolyList(od,polyList,surfaceKey); + } + + // restore original transform... + polyList->setTransform(&initialMat,initialScale); + } + + clearStatics(); + + return emitted; +} + +bool TSShapeInstance::getFeatures(const MatrixF& mat, const Point3F& n, ConvexFeature* cf, S32 dl) +{ + // if dl==-1, nothing to do + if (dl==-1) + return false; + + AssertFatal(dl>=0 && dldetails.size(),"TSShapeInstance::buildPolyList"); + + // get subshape and object detail + const TSDetail * detail = &mShape->details[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + // set up static data + setStatics(dl); + + // nothing emitted yet... + bool emitted = false; + U32 surfaceKey = 0; + + S32 start = mShape->subShapeFirstObject[ss]; + S32 end = mShape->subShapeNumObjects[ss] + start; + if (startgetTransform() != previousMat) { + previousMat = mesh->getTransform(); + final.mul(mat, *previousMat); + } + emitted |= mesh->getFeatures(od, final, n, cf, surfaceKey); + } + } + + clearStatics(); + + return emitted; +} + +bool TSShapeInstance::castRay(const Point3F & a, const Point3F & b, RayInfo * rayInfo, S32 dl) +{ + // if dl==-1, nothing to do + if (dl==-1) + return false; + + AssertFatal(dl>=0 && dldetails.size(),"TSShapeInstance::buildPolyList"); + + // get subshape and object detail + const TSDetail * detail = &mShape->details[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + // set up static data + setStatics(dl); + + S32 start = mShape->subShapeFirstObject[ss]; + S32 end = mShape->subShapeNumObjects[ss] + start; + RayInfo saveRay; + saveRay.t = 1.0f; + const MatrixF * saveMat = NULL; + bool found = false; + if (startgetTransform() != previousMat) + { + // different node from before, set up for this node + previousMat = mesh->getTransform(); + mat = *previousMat; + mat.inverse(); + mat.mulP(a,&ta); + mat.mulP(b,&tb); + } + // collide... + if (mesh->castRay(od,ta,tb,rayInfo)) + { + if (!rayInfo) + { + clearStatics(); + return true; + } + if (rayInfo->t <= saveRay.t) + { + saveRay = *rayInfo; + saveMat = previousMat; + } + found = true; + } + } + } + + // collide with any skins for this detail level... + // TODO: if ever... + + // finalize the deal... + if (found) + { + *rayInfo = saveRay; + saveMat->mulV(rayInfo->normal); + rayInfo->point = b-a; + rayInfo->point *= rayInfo->t; + rayInfo->point += a; + } + clearStatics(); + return found; +} + +Point3F TSShapeInstance::support(const Point3F & v, S32 dl) +{ + // if dl==-1, nothing to do + AssertFatal(dl != -1, "Error, should never try to collide with a non-existant detail level!"); + AssertFatal(dl>=0 && dldetails.size(),"TSShapeInstance::support"); + + // get subshape and object detail + const TSDetail * detail = &mShape->details[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + // set up static data + setStatics(dl); + + S32 start = mShape->subShapeFirstObject[ss]; + S32 end = mShape->subShapeNumObjects[ss] + start; + const MatrixF * saveMat = NULL; + bool found = false; + + F32 currMaxDP = -1e9f; + Point3F currSupport = Point3F(0, 0, 0); + MatrixF * previousMat = NULL; + MatrixF mat; + if (startgetMesh(od); + if (physMesh && mesh->visible > 0.01f) { + // collide... + F32 saveMDP = currMaxDP; + + if (mesh->getTransform() != previousMat) + { + // different node from before, set up for this node + previousMat = mesh->getTransform(); + mat = *previousMat; + mat.inverse(); + } + mat.mulV(v, &va); + physMesh->support(mesh->frame, va, &currMaxDP, &currSupport); + } + } + } + + clearStatics(); + + if (currMaxDP != -1e9f) { + previousMat->mulP(currSupport); + return currSupport; + } + else { + return Point3F(0, 0, 0); + } +} + +void TSShapeInstance::computeBounds(S32 dl, Box3F & bounds) +{ + // if dl==-1, nothing to do + if (dl==-1) + return; + + AssertFatal(dl>=0 && dldetails.size(),"TSShapeInstance::computeBounds"); + + // get subshape and object detail + const TSDetail * detail = &mShape->details[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + // set up static data + setStatics(dl); + + S32 start = mShape->subShapeFirstObject[ss]; + S32 end = mShape->subShapeNumObjects[ss] + start; + + // run through objects and updating bounds as we go + bounds.min.set( 10E30, 10E30, 10E30); + bounds.max.set(-10E30,-10E30,-10E30); + Box3F box; + for (S32 i=start; igetMesh(od)) + { + mesh->getMesh(od)->computeBounds(*mesh->getTransform(),box); + bounds.min.setMin(box.min); + bounds.max.setMax(box.max); + } + } + + clearStatics(); +} + +//------------------------------------------------------------------------------------- +// Object (MeshObjectInstance & PluginObjectInstance) collision methods +//------------------------------------------------------------------------------------- + +bool TSShapeInstance::ObjectInstance::buildPolyList(S32 objectDetail, AbstractPolyList * polyList, U32 & surfaceKey) +{ + objectDetail,polyList,surfaceKey; + AssertFatal(0,"TSShapeInstance::ObjectInstance::buildPolyList: no default method."); + return false; +} + +bool TSShapeInstance::ObjectInstance::getFeatures(S32 objectDetail, const MatrixF& mat, const Point3F& n, ConvexFeature* cf, U32& surfaceKey) +{ + objectDetail,mat, n, cf, surfaceKey; + AssertFatal(0,"TSShapeInstance::ObjectInstance::buildPolyList: no default method."); + return false; +} + +void TSShapeInstance::ObjectInstance::support(S32, const Point3F&, F32*, Point3F*) +{ + AssertFatal(0,"TSShapeInstance::ObjectInstance::supprt: no default method."); +} + +bool TSShapeInstance::ObjectInstance::castRay(S32 objectDetail, const Point3F & start, const Point3F & end, RayInfo * rayInfo) +{ + objectDetail,start,end,rayInfo; + AssertFatal(0,"TSShapeInstance::ObjectInstance::castRay: no default method."); + return false; +} + +bool TSShapeInstance::MeshObjectInstance::buildPolyList(S32 objectDetail, AbstractPolyList * polyList, U32 & surfaceKey) +{ + TSMesh * mesh = getMesh(objectDetail); + if (mesh && visible>0.01f) + return mesh->buildPolyList(frame,polyList,surfaceKey); + return false; +} + +bool TSShapeInstance::MeshObjectInstance::getFeatures(S32 objectDetail, const MatrixF& mat, const Point3F& n, ConvexFeature* cf, U32& surfaceKey) +{ + TSMesh* mesh = getMesh(objectDetail); + if (mesh && visible > 0.01f) + return mesh->getFeatures(frame, mat, n, cf, surfaceKey); + return false; +} + +void TSShapeInstance::MeshObjectInstance::support(S32 objectDetail, const Point3F& v, F32* currMaxDP, Point3F* currSupport) +{ + TSMesh* mesh = getMesh(objectDetail); + if (mesh && visible > 0.01f) + mesh->support(frame, v, currMaxDP, currSupport); +} + + +bool TSShapeInstance::MeshObjectInstance::castRay(S32 objectDetail, const Point3F & start, const Point3F & end, RayInfo * rayInfo) +{ + TSMesh * mesh = getMesh(objectDetail); + if (mesh && visible>0.01f) + return mesh->castRay(frame,start,end,rayInfo); + return false; +} + + + + + + diff --git a/ts/tsDecal.cc b/ts/tsDecal.cc new file mode 100644 index 0000000..5fe2050 --- /dev/null +++ b/ts/tsDecal.cc @@ -0,0 +1,249 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsDecal.h" +#include "dgl/dgl.h" +#include "math/mMath.h" +#include "math/mathIO.h" +#include "ts/tsShapeInstance.h" +#include "sim/frameAllocator.h" + +// Not worth the effort, much less the effort to comment, but if the draw types +// are consecutive use addition rather than a table to go from index to command value... +#if ((GL_TRIANGLES+1==GL_TRIANGLE_STRIP) && (GL_TRIANGLE_STRIP+1==GL_TRIANGLE_FAN)) + #define getDrawType(a) (GL_TRIANGLES+(a)) +#else + static U32 drawTypes[] = { GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN }; + #define getDrawType(a) (drawTypes[a]) +#endif + +// temp variables for saving and restoring parameters during render +static S32 decalSaveFogMethod; +static F32 decalSaveAlphaAlways; +static F32 gSaveFadeVal; + +// from tsmesh +extern void forceFaceCamera(); +extern void forceFaceCameraZAxis(); + +void TSDecalMesh::render(S32 frame, S32 decalFrame, TSMaterialList * materials) +{ + if (targetMesh == NULL || texgenS.empty() || texgenT.empty() || indices.empty()) + return; + + // we need to face the camera just like our target does + if (targetMesh->getFlags(TSMesh::Billboard)) + { + if (targetMesh->getFlags(TSMesh::BillboardZAxis)) + forceFaceCameraZAxis(); + else + forceFaceCamera(); + } + + S32 firstVert = targetMesh->vertsPerFrame * frame; + + // generate texture coords + S32 sz = indices.size(); + Point4F s = texgenS[decalFrame]; + Point4F t = texgenT[decalFrame]; + Point3F * verts = &targetMesh->verts[firstVert]; + U32 waterMark = FrameAllocator::getWaterMark(); + F32 * ptr = (F32*)FrameAllocator::alloc(sizeof(F32)*2*targetMesh->vertsPerFrame); + S16 * idx = (S16*)indices.address(); + S16 * idxEnd = idx + indices.size(); + for (; idxx * s.x + v->y * s.y + v->z * s.z + s.w; + tv.y = v->x * t.x + v->y * t.y + v->z * t.z + t.w; + } + + // set up vertex arrays + glVertexPointer(3,GL_FLOAT,0,verts); + glNormalPointer(GL_FLOAT,0,targetMesh->getNormals(firstVert)); + glTexCoordPointer(2,GL_FLOAT,0,ptr); + + // NOTE: we don't lock these arrays since decals tend to use only a small subset of the vertices + + // material change? We only need to do this once. + if ( (TSShapeInstance::smRenderData.materialIndex ^ materialIndex) & (TSDrawPrimitive::MaterialMask|TSDrawPrimitive::NoMaterial)) + TSMesh::setMaterial(materialIndex,materials); + + S32 i; + S32 a = startPrimitive[decalFrame]; + S32 b = decalFrame+1>30); + glDrawElements(drawType,draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]); + } + + // return frameAllocator memory + FrameAllocator::setWaterMark(waterMark); +} + +void TSDecalMesh::initDecalMaterials() +{ + TSShapeInstance::smRenderData.materialFlags = TSMaterialList::S_Wrap | TSMaterialList::T_Wrap; + TSShapeInstance::smRenderData.materialIndex = TSDrawPrimitive::NoMaterial; + TSShapeInstance::smRenderData.environmentMapMethod = TSShapeInstance::NO_ENVIRONMENT_MAP; + TSShapeInstance::smRenderData.detailMapMethod = TSShapeInstance::NO_DETAIL_MAP; + TSShapeInstance::smRenderData.baseTE = 0; + + // adjust for fading + gSaveFadeVal = TSMesh::getOverrideFade(); + TSMesh::setOverrideFade( 1.0f ); + TSShapeInstance::smRenderData.vertexAlpha.always = gSaveFadeVal; + // setMaterial will end up changing vertex color if needed... + + // draw one-sided... + glEnable(GL_CULL_FACE); + glFrontFace(GL_CW); + + // enable vertex arrays... + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + // bias depth values to fight z-fighting + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-2,-2); + + // when blending we modulate and draw using src_alpha, 1-src_alpha... + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + + // but we don't blend by default... + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + + // this should be off by default, but we'll end up turning it on asap... + glDisable(GL_TEXTURE_2D); + + // fog the decals (or rather, don't...we'll fog the whole shape later if + // we are 2-passing it, otherwise we just want to fade the decal out)... + decalSaveFogMethod = TSShapeInstance::smRenderData.fogMethod; + if (decalSaveFogMethod != TSShapeInstance::NO_FOG) + decalSaveAlphaAlways = TSShapeInstance::smRenderData.alwaysAlphaValue; + TSShapeInstance::smRenderData.fogMethod = TSShapeInstance::FOG_TWO_PASS; // decal will be faded (since decal is translucent) +} + +void TSDecalMesh::resetDecalMaterials() +{ + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glDisable(GL_POLYGON_OFFSET_FILL); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + + glDisable(GL_CULL_FACE); + + TSMesh::setOverrideFade( gSaveFadeVal ); + + // fog the decals (or rather, don't...we'll fog the whole shape later if + // we are 2-passing it, otherwise we just want to fade the decal out)... + if (decalSaveFogMethod != TSShapeInstance::NO_FOG) + TSShapeInstance::smRenderData.alwaysAlphaValue = decalSaveAlphaAlways; + TSShapeInstance::smRenderData.fogMethod = decalSaveFogMethod; +} + +//----------------------------------------------------- +// TSDecalMesh assembly/dissembly methods +// used for transfer to/from memory buffers +//----------------------------------------------------- + +#define alloc TSShape::alloc + +void TSDecalMesh::assemble(bool) +{ + if (TSShape::smReadVersion<20) + { + // read empty mesh...decals used to be derived from meshes + alloc.checkGuard(); + alloc.getPointer32(15); + } + + S32 sz = alloc.get32(); + S32 * ptr32 = alloc.copyToShape32(0); // get current shape address w/o doing anything + for (S32 i=0; i=19) + { + ptr32 = alloc.copyToShape32(sz*4); + texgenS.set(ptr32,startPrimitive.size()); + ptr32 = alloc.copyToShape32(sz*4); + texgenT.set(ptr32,startPrimitive.size()); + } + else + { + texgenS.set(NULL,0); + texgenT.set(NULL,0); + } + + materialIndex = alloc.get32(); + + alloc.checkGuard(); +} + +void TSDecalMesh::disassemble() +{ + alloc.set32(primitives.size()); + for (S32 i=0; i primitives; + ToolVector indices; + + // indexed by decal frame... + ToolVector startPrimitive; + ToolVector texgenS; + ToolVector texgenT; + + // We only allow 1 material per decal... + S32 materialIndex; + + // override render function + void render(S32 frame, S32 decalFrame, TSMaterialList *); + + void disassemble(); + void assemble(bool skip); + + static void initDecalMaterials(); + static void resetDecalMaterials(); +}; + + +#endif + diff --git a/ts/tsDump.cc b/ts/tsDump.cc new file mode 100644 index 0000000..100034b --- /dev/null +++ b/ts/tsDump.cc @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShapeInstance.h" + +//------------------------------------------------------------------------------------- +// Dump shape structure: +//------------------------------------------------------------------------------------- + +#define dumpLine(buffer) {str = buffer; stream.write(dStrlen(str),str);} + +void TSShapeInstance::dumpDecals(Stream & stream, S32 indent, MeshObjectInstance * mesh) +{ + // search for any decals + const char * str; + for (S32 i=0; itargetObject != mesh) + continue; + + // we have a decal + + // get name + const char *decalName = ""; + if (decal->decalObject->nameIndex!=-1) + decalName = mShape->getName(decal->decalObject->nameIndex); + + // indent... + char buf[1024]; + dMemset(buf,' ',indent); + buf[indent] = '\0'; + dumpLine(buf); + + // dump object name + dumpLine(avar(" Decal named \"%s\" on above object.\r\n",decalName)); + } +} + +void TSShapeInstance::dumpNode(Stream & stream ,S32 level, S32 nodeIndex, Vector & detailSizes) +{ + if (nodeIndex<0) + return; + + S32 i; + const char * str; + char space[256]; + for (i = 0; i < level*3; i++) + space[i] = ' '; + space[level*3] = '\0'; + + const char *nodeName = ""; + const TSShape::Node & node = mShape->nodes[nodeIndex]; + if (node.nameIndex != -1) + nodeName = mShape->getName(node.nameIndex); + dumpLine(avar("%s%s", space, nodeName)); + + // find all the objects that hang off this node... + Vector objectList; + for (i=0; iobject->nameIndex!=-1) + objectName = mShape->getName(obj->object->nameIndex); + + // more spaces if this is the second object on this node + if (spaceCount>0) + { + char buf[1024]; + dMemset(buf,' ',spaceCount); + buf[spaceCount] = '\0'; + dumpLine(buf); + } + + // dump object name + dumpLine(avar(" --> Object %s with following details: ",objectName)); + + // dump object detail levels + for (S32 k=0; kobject->numMeshes; k++) + { + S32 f = obj->object->startMeshIndex; + if (mShape->meshes[f+k]) + dumpLine(avar(" %i",detailSizes[k])); + } + + dumpLine("\r\n"); + + // how many spaces should we prepend if we have another object on this node + if (spaceCount<0) + spaceCount = dStrlen(space) + dStrlen(nodeName); + + // dump any decals + dumpDecals(stream, spaceCount+3, obj); + } + + // search for children + for (S32 k=nodeIndex+1; knodes.size(); k++) + { + if (mShape->nodes[k].parentIndex == nodeIndex) + // this is our child + dumpNode(stream, level+1, k, detailSizes); + } +} + +void TSShapeInstance::dump(Stream & stream) +{ + S32 i,j,ss,od,sz; + const char * name; + const char * str; + + dumpLine("\r\nShape Hierarchy:\r\n"); + + dumpLine("\r\n Details:\r\n"); + + for (i=0; idetails.size(); i++) + { + const TSDetail & detail = mShape->details[i]; + name = mShape->getName(detail.nameIndex); + ss = detail.subShapeNum; + od = detail.objectDetailNum; + sz = (S32)detail.size; + dumpLine(avar(" %s, Subtree %i, objectDetail %i, size %i\r\n",name,ss,od,sz)); + } + + dumpLine("\r\n Subtrees:\r\n"); + + for (i=0; isubShapeFirstNode.size(); i++) + { + S32 a = mShape->subShapeFirstNode[i]; + S32 b = a + mShape->subShapeNumNodes[i]; + dumpLine(avar(" Subtree %i\r\n",i)); + + // compute detail sizes for each subshape + Vector detailSizes; + for (S32 l=0;ldetails.size(); l++) + { + if (mShape->details[l].subShapeNum==i) + detailSizes.push_back(mShape->details[l].size); + } + + for (j=a; jnodes[j]; + // if the node has a parent, it'll get dumped via the parent + if (node.parentIndex<0) + dumpNode(stream,3,j,detailSizes); + } + } + + bool foundSkin = false; + for (i=0; iobjects.size(); i++) + { + if (mShape->objects[i].nodeIndex<0) // must be a skin + { + if (!foundSkin) + dumpLine("\r\n Skins:\r\n"); + foundSkin=true; + const char * skinName = ""; + S32 nameIndex = mShape->objects[i].nameIndex; + if (nameIndex>=0) + skinName = mShape->getName(nameIndex); + dumpLine(avar(" Skin %s with following details: ",skinName)); + for (S32 num=0; numobjects[i].numMeshes; num++) + { + if (mShape->meshes[num]) + dumpLine(avar(" %i",(S32)mShape->details[num].size)); + } + dumpLine("\r\n"); + } + } + if (foundSkin) + dumpLine("\r\n"); + + dumpLine("\r\n Sequences:\r\n"); + for (i = 0; i < mShape->sequences.size(); i++) + { + const char *name = "(none)"; + if (mShape->sequences[i].nameIndex != -1) + name = mShape->getName(mShape->sequences[i].nameIndex); + dumpLine(avar(" %3d: %s\r\n",i,name)); + } + + if (mShape->materialList) + { + TSMaterialList * ml = mShape->materialList; + dumpLine("\r\n Material list:\r\n"); + for (i=0; i< ml->getMaterialCount(); i++) + { + U32 flags = ml->getFlags(i); + const char * name = ml->getMaterialName(i); + dumpLine(avar(" material #%i: \"%s\"%s.",i,name ? ml->getMaterialName(i) : "",flags&(TSMaterialList::S_Wrap|TSMaterialList::T_Wrap) ? "" : " not tiled")); + if (flags & TSMaterialList::IflMaterial) + dumpLine(" Place holder for ifl."); + if (flags & TSMaterialList::IflFrame) + dumpLine(" Ifl frame."); + if (flags & TSMaterialList::DetailMapOnly) + dumpLine(" Used as a detail map."); + if (flags & TSMaterialList::BumpMapOnly) + dumpLine(" Used as a bump map."); + if (flags & TSMaterialList::ReflectanceMapOnly) + dumpLine(" Used as a reflectance map."); + if (flags & TSMaterialList::Translucent) + { + if (flags & TSMaterialList::Additive) + dumpLine(" Additive-translucent.") + else if (flags & TSMaterialList::Subtractive) + dumpLine(" Subtractive-translucent.") + else + dumpLine(" Translucent.") + } + dumpLine("\r\n"); + } + } +} + diff --git a/ts/tsIntegerSet.cc b/ts/tsIntegerSet.cc new file mode 100644 index 0000000..e48a47d --- /dev/null +++ b/ts/tsIntegerSet.cc @@ -0,0 +1,222 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsIntegerSet.h" +#include "Platform/platform.h" +#include "Core/stream.h" + +#define SETUPTO(upto) ( ((1<<(upto&31))-1)*2+1 ) // careful not to shift more than 31 times + +void TSIntegerSet::clearAll(S32 upto) +{ + AssertFatal(upto<=MAX_TS_SET_SIZE,"TSIntegerSet::clearAll: out of range"); + + dMemset(bits,0,(upto>>5)*4); + if (upto&31) + bits[upto>>5] &= ~SETUPTO(upto); +} + +void TSIntegerSet::setAll(S32 upto) +{ + AssertFatal(upto<=MAX_TS_SET_SIZE,"TSIntegerSet::setAll: out of range"); + + dMemset(bits,0xFFFFFFFF,(upto>>5)*4); + if (upto&31) + bits[upto>>5] |= SETUPTO(upto); +} + +bool TSIntegerSet::testAll(S32 upto) const +{ + AssertFatal(upto<=MAX_TS_SET_SIZE,"TSIntegerSet::testAll: out of range"); + S32 i; + for (i=0; i<(upto>>5); i++) + if (bits[i]) + return true; + if (upto&31) + return (bits[upto>>5] & SETUPTO(upto)) != 0; + return false; +} + +void TSIntegerSet::intersect(const TSIntegerSet & otherSet) +{ + for (S32 i=0; i=0; i--) + { + // search for set bit one dword at a time + U32 dword = bits[i]; + if (bits[i]) + { + // got dword, now search one byte at a time + S32 j = 31; + U32 mask = 0xFF000000; + do + { + if (dword&mask) + { + // got byte, now one bit at a time + U32 bit = mask & ~(mask>>1); // grabs the highest bit + do + { + if (dword&bit) + return (i<<5)+j+1; + j--; + bit >>= 1; + } while (1); + } + mask >>= 8; + j -= 8; + } while (1); + } + } + + return 0; +} + +void TSIntegerSet::next(S32 & i) const +{ + i++; + U32 idx = i>>5; + U32 bit = 1 << (i&31); + U32 dword = bits[idx] & ~(bit-1); + while (dword==0) + { + i = (i+32) & ~31; + if (i>=MAX_TS_SET_SIZE) + return; + dword=bits[++idx]; + bit = 1; + } + dword = bits[idx]; + while ( (bit & dword) == 0) + { + bit <<= 1; + i++; + } +} + +/* Or would one byte at a time be better... +void TSIntegerSet::next(S32 & i) +{ + U32 idx = i>>3; + U8 bit = 1 << (i&7); + U8 byte = ((U8*)bits)[idx] & ~(bit*2-1); + while (byte==0) + { + i = (i+8) & ~7; + if (i>=MAX_TS_SET_SIZE) + return; + byte=((U8*)bits)[++idx]; + bit = 1; + } + byte = ((U8*)bits)[idx]; + while (bit & byte == 0) + { + bit <<= 1; + i++; + } +} +*/ + +void TSIntegerSet::copy(const TSIntegerSet & otherSet) +{ + dMemcpy(bits,otherSet.bits,MAX_TS_SET_DWORDS*4); +} + +TSIntegerSet::TSIntegerSet() +{ + clearAll(); +} + +TSIntegerSet::TSIntegerSet(const TSIntegerSet & otherSet) +{ + copy(otherSet); +} + +void TSIntegerSet::read(Stream * s) +{ + clearAll(); + + S32 numInts; + s->read(&numInts); // don't care about this + + S32 sz; + s->read(&sz); + AssertFatal(sz<=MAX_TS_SET_DWORDS,"TSIntegerSet:: set too large...increase max set size and re-compile"); + s->read(4*sz,(U8*)bits); + for (S32 i=0; iwrite((S32)0); // don't do this anymore, keep in to avoid versioning + S32 i,sz=0; + for (i=0; iwrite(sz); + for (i=0; iwrite(bits[i]); +} + diff --git a/ts/tsIntegerSet.h b/ts/tsIntegerSet.h new file mode 100644 index 0000000..6a5995a --- /dev/null +++ b/ts/tsIntegerSet.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSINTEGERSET_H_ +#define _TSINTEGERSET_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + + +#ifdef MAX_UTIL +#define MAX_TS_SET_DWORDS 32 +#else +#define MAX_TS_SET_DWORDS 6 +#endif + +#define MAX_TS_SET_SIZE (32*MAX_TS_SET_DWORDS) + +class Stream; + +class TSIntegerSet +{ + U32 bits[MAX_TS_SET_DWORDS]; + +public: + + void clear(S32 index); + void set(S32 index); + bool test(S32 index) const; + + void clearAll(S32 upto = MAX_TS_SET_SIZE); + void setAll(S32 upto = MAX_TS_SET_SIZE); + bool testAll(S32 upto = MAX_TS_SET_SIZE) const; + + void intersect(const TSIntegerSet&); + void overlap(const TSIntegerSet&); + void difference(const TSIntegerSet&); + void takeAway(const TSIntegerSet&); + + void copy(const TSIntegerSet&); + + void operator=(const TSIntegerSet& otherSet) { copy(otherSet); } + + S32 start() const; + S32 end() const; + void next(S32 & i) const; + + void read(Stream *); + void write(Stream *); + + TSIntegerSet(); + TSIntegerSet(const TSIntegerSet&); +}; + +inline void TSIntegerSet::clear(S32 index) +{ + AssertFatal(index>=0 && index>5] &= ~(1 << (index & 31)); +} + +inline void TSIntegerSet::set(S32 index) +{ + AssertFatal(index>=0 && index>5] |= 1 << (index & 31); +} + +inline bool TSIntegerSet::test(S32 index) const +{ + AssertFatal(index>=0 && index>5] & (1 << (index & 31)); +} + +#endif diff --git a/ts/tsLastDetail.cc b/ts/tsLastDetail.cc new file mode 100644 index 0000000..a13f845 --- /dev/null +++ b/ts/tsLastDetail.cc @@ -0,0 +1,356 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsLastDetail.h" + +#include "dgl/dgl.h" +#include "ts/tsShape.h" +#include "ts/tsShapeInstance.h" + +Point2F TSLastDetail::smTVerts[4] = { Point2F(0,1), Point2F(1,1), Point2F(1,0), Point2F(0,0) }; +Point3F TSLastDetail::smNorms[4] = { Point3F(0,-1,0), Point3F(0,-1,0), Point3F(0,-1,0), Point3F(0,-1,0) }; + + +TSLastDetail::TSLastDetail(TSShape * shape, + U32 numEquatorSteps, + U32 numPolarSteps, + F32 polarAngle, + bool includePoles, + S32 dl, S32 dim) +{ + VECTOR_SET_ASSOCIATION(mBitmaps); + VECTOR_SET_ASSOCIATION(mTextures); + + mNumEquatorSteps = numEquatorSteps; + mNumPolarSteps = numPolarSteps; + mPolarAngle = polarAngle; + mIncludePoles = includePoles; + + F32 equatorStepSize = M_2PI / (F32) numEquatorSteps; + F32 polarStepSize = numPolarSteps>0 ? (0.5f * M_PI - polarAngle) / (F32)numPolarSteps : 0.0f; + + S32 i; + F32 rotZ = 0; + for (i=0; i0 ? polarAngle - 0.5f * M_PI : 0.0f; + for (S32 j=0; j<2*numPolarSteps+1; j++) + { + MatrixF angMat; + angMat.mul(MatrixF(EulerF(0,0,-M_PI+rotZ)),MatrixF(EulerF(rotX,0,0))); + mBitmaps.push_back(TSShapeInstance::snapshot(shape,dim,dim,true,angMat,dl,1.0f,true)); + rotX += polarStepSize; + } + rotZ += equatorStepSize; + } + if (includePoles) + { + MatrixF m1( EulerF( M_PI / 2, 0, 0 ) ); + MatrixF m2( EulerF( -M_PI / 2, 0, 0 ) ); + mBitmaps.push_back(TSShapeInstance::snapshot(shape,dim,dim,true,m1,dl,1.0f,true)); + mBitmaps.push_back(TSShapeInstance::snapshot(shape,dim,dim,true,m2,dl,1.0f,true)); + } + + mTextures.setSize(mBitmaps.size()); + for (i=0; iradius,0, shape->radius); + mPoints[1].set( shape->radius,0, shape->radius); + mPoints[2].set( shape->radius,0,-shape->radius); + mPoints[3].set(-shape->radius,0,-shape->radius); + + mCenter = shape->center; +} + +TSLastDetail::~TSLastDetail() +{ + for (S32 i=0; i=0 && rotXM_PI-mPolarAngle)) + { + mBitmapIndex = mNumEquatorSteps * (2*mNumPolarSteps+1); + if (rotX>mPolarAngle) + mBitmapIndex++; +// mRotY = mAtan(y.y,x.y); + mRotY = mAtan(m[5],m[4]); + } + else + { + F32 equatorStepSize = M_2PI / (F32) mNumEquatorSteps; + F32 polarStepSize = mNumPolarSteps>0 ? (0.5f * M_PI - mPolarAngle) / (F32) mNumPolarSteps : 0.0f; + rotZ = 0.999f * (mAtan(dotX,dotY) + M_PI); // the 0.99f makes sure we are in range + AssertFatal(rotZ>=0 && rotZ0) + mBitmapIndex = (U32)(mBitmapIndex + ((rotX-mPolarAngle) / polarStepSize)); +// mRotY = mAtan(z.x,z.z); + mRotY = mAtan(m[2],m[10]); + } +} + +void TSLastDetail::render(F32 alpha, bool drawFog) +{ + glPushMatrix(); + + // get camera matrix, adjust for shape center + MatrixF mat; + Point3F p,center; + dglGetModelview(&mat); + mat.getColumn(3,&p); + mat.mulV(mCenter,¢er); + p += center; + mat.setColumn(3,p); + + Point3F ones( 1, 1, 1 ); + chooseView(mat,ones); + + // following is a quicker version of mat.set(EulerF(0,rotY,0)); + // note: we assume mat[12]=1 and mat[3]=mat[7]=mat[11]=0 to start with + F32 * m = (F32*)mat; // because [] operator isn't implemented on MatrixF, so it finds mat[0] ambiguous (const) + AssertFatal(mFabs(m[15]-1.0f)<0.01f && mFabs(m[14])<0.01f && mFabs(m[13])<0.01f && mFabs(m[12])<0.01f,"TSLastDetail::render"); + if (mRotY*mRotY>0.0001f) + { + m[0] = m[10] = mCos(mRotY); + m[2] = mSin(mRotY); m[8] = -m[2]; + m[1] = m[4] = m[6] = m[9] = 0.0f; + m[5] = 1.0f; + } + else + { + m[0] = m[5] = m[10] = 1.0f; + m[1] = m[2] = m[4] = m[6] = m[8] = m[9] = 0.0f; + } + + dglLoadMatrix(&mat); + if (TSShapeInstance::smRenderData.objectScale) + glScalef(TSShapeInstance::smRenderData.objectScale->x,TSShapeInstance::smRenderData.objectScale->y,TSShapeInstance::smRenderData.objectScale->z); + + if (!drawFog) + renderNoFog(alpha); + else + { + if (dglDoesSupportTextureEnvCombine() && dglDoesSupportARBMultitexture()) + renderFog_MultiCombine(alpha); +// else if (dglDoesSupportTextureEnvCombine()) +// renderFog_Combine(alpha); + else + renderNoFog(alpha * TSShapeInstance::smRenderData.fogColor.w); + } + + glPopMatrix(); +} + +void TSLastDetail::renderNoFog(F32 alpha) +{ + // set up arrays + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3,GL_FLOAT,0,mPoints); + glTexCoordPointer(2,GL_FLOAT,0,smTVerts); + glNormalPointer(GL_FLOAT,0,smNorms); + + glDepthMask(GL_FALSE); + + // light the material + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,Point4F(1,1,1,alpha)); + + // additive transparency + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + + // texture + glEnable(GL_TEXTURE_2D); + + if (TSShapeInstance::smRenderData.useOverride == false) + glBindTexture(GL_TEXTURE_2D, mTextures[mBitmapIndex]->getGLName()); + else + glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.override.getGLName()); + + // set color state + glColor4f(1,1,1,1); + + glDrawArrays(GL_TRIANGLE_FAN,0,4); + + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); +} + +void TSLastDetail::renderFog_Combine(F32 alpha) +{ + // set up arrays + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3,GL_FLOAT,0,mPoints); + glTexCoordPointer(2,GL_FLOAT,0,smTVerts); + glNormalPointer(GL_FLOAT,0,smNorms); + + glDepthMask(GL_FALSE); + + // light the material + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,Point4F(1,1,1,alpha)); + glColor4f(1,1,1,1); + + // additive transparency + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + + // texture + glEnable(GL_TEXTURE_2D); + if (TSShapeInstance::smRenderData.useOverride == false) + glBindTexture(GL_TEXTURE_2D, mTextures[mBitmapIndex]->getGLName()); + else + glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.override.getGLName()); + + glDrawArrays(GL_TRIANGLE_FAN,0,4); + + // now render the fog + + glDisableClientState(GL_NORMAL_ARRAY); + + bool wasLit = glIsEnabled(GL_LIGHTING); + glDisable(GL_LIGHTING); + glDepthMask(GL_TRUE); + + glColor4fv(TSShapeInstance::smRenderData.fogColor); + + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_EXT); + // color comes from constant color... + glTexEnvi(GL_TEXTURE_ENV,GL_COMBINE_RGB_EXT,GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE0_RGB_EXT,GL_PRIMARY_COLOR_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND0_RGB_EXT,GL_SRC_COLOR); + // alpha is product of texture and constant alpha... + glTexEnvi(GL_TEXTURE_ENV,GL_COMBINE_ALPHA_EXT,GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE0_ALPHA_EXT,GL_PRIMARY_COLOR_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND0_ALPHA_EXT,GL_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE1_ALPHA_EXT,GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND1_ALPHA_EXT,GL_SRC_ALPHA); + + glDrawArrays(GL_TRIANGLE_FAN,0,4); + + // restore state... + if (wasLit) + glEnable(GL_LIGHTING); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); +} + +void TSLastDetail::renderFog_MultiCombine(F32 alpha) +{ + // set up materials + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3,GL_FLOAT,0,mPoints); + glTexCoordPointer(2,GL_FLOAT,0,smTVerts); + glNormalPointer(GL_FLOAT,0,smNorms); + + // set color state + glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,Point4F(1,1,1,alpha)); + glColor4f(1,1,1,1); + + // first TE applies lighting to billboard texture + glEnable(GL_TEXTURE_2D); + if (TSShapeInstance::smRenderData.useOverride == false) + glBindTexture(GL_TEXTURE_2D, mTextures[mBitmapIndex]->getGLName()); + else + glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.override.getGLName()); + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + + // second TE applies fog to the result + glActiveTextureARB(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + glTexEnvfv(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR,TSShapeInstance::smRenderData.fogColor); + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_COMBINE_RGB_EXT,GL_INTERPOLATE_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE0_RGB_EXT,GL_CONSTANT_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND0_RGB_EXT,GL_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE1_RGB_EXT,GL_PREVIOUS_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND1_RGB_EXT,GL_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE2_RGB_EXT,GL_CONSTANT_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND2_RGB_EXT,GL_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV,GL_COMBINE_ALPHA_EXT,GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE0_ALPHA_EXT,GL_PREVIOUS_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND0_ALPHA_EXT,GL_SRC_ALPHA); + + // additive transparency + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_FALSE); + + // render billboard+fog + glDrawArrays(GL_TRIANGLE_FAN,0,4); + + // restore state... + glDisable(GL_TEXTURE_2D); + glActiveTextureARB(GL_TEXTURE0_ARB); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glDisable(GL_TEXTURE_2D); +} + + + + diff --git a/ts/tsLastDetail.h b/ts/tsLastDetail.h new file mode 100644 index 0000000..e6ec30b --- /dev/null +++ b/ts/tsLastDetail.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSLASTDETAIL_H_ +#define _TSLASTDETAIL_H_ + +#ifndef _MATHTYPES_H_ +#include "Math/mathTypes.h" +#endif +#ifndef _MPOINT_H_ +#include "Math/mPoint.h" +#endif +#ifndef _MMATRIX_H_ +#include "Math/mMatrix.h" +#endif +#ifndef _TVECTOR_H_ +#include "Core/tVector.h" +#endif + +class TSShape; +class GBitmap; +class TextureHandle; + +class TSLastDetail +{ + U32 mNumEquatorSteps; // number steps around the equator of the globe + U32 mNumPolarSteps; // number of steps to go from equator to each polar region (0 means equator only) + F32 mPolarAngle; // angle in radians of sub-polar regions + bool mIncludePoles; // include poles in snapshot? + + Point3F mCenter; + + // remember these values so we can save some work next time we render... + U32 mBitmapIndex; + F32 mRotY; + + Vector mBitmaps; + Vector mTextures; + + Point3F mPoints[4]; // always draw poly defined by these points... + static Point3F smNorms[4]; + static Point2F smTVerts[4]; + + public: + + TSLastDetail(TSShape * shape, U32 numEquatorSteps, U32 numPolarSteps, F32 polarAngle, bool includePoles, S32 dl, S32 dim); + ~TSLastDetail(); + + void render(F32 alpha, bool drawFog); + void renderNoFog(F32 alpha); + void renderFog_Combine(F32 alpha); + void renderFog_MultiCombine(F32 alpha); + void chooseView(const MatrixF &, const Point3F & scale); +}; + + +#endif // _TS_LAST_DETAIL + + diff --git a/ts/tsMaterialList.cc b/ts/tsMaterialList.cc new file mode 100644 index 0000000..517a7fe --- /dev/null +++ b/ts/tsMaterialList.cc @@ -0,0 +1,323 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShape.h" + +// look for all ts materials in this subdirectory of TextureManager::csmTexturePrefix directory +const char* TSMaterialList::csmTSTexturePrefix = "skins/"; +// prepend this to materials of pre version 16 shapes before prepending the above... +const char* TSMaterialList::csmOldTSTexturePrefix = ""; +// prepend this to ifl materials +const char* TSMaterialList::csmIFLTexturePrefix = "skins/"; + + +TSMaterialList::TSMaterialList(U32 materialCount, + const char **materialNames, + const U32 * materialFlags, + const U32 * reflectanceMaps, + const U32 * bumpMaps, + const U32 * detailMaps, + const F32 * detailScales, + const F32 * reflectionAmounts) + : MaterialList(materialCount,materialNames), + mNamesTransformed(false) +{ + VECTOR_SET_ASSOCIATION(mFlags); + VECTOR_SET_ASSOCIATION(mReflectanceMaps); + VECTOR_SET_ASSOCIATION(mBumpMaps); + VECTOR_SET_ASSOCIATION(mDetailMaps); + VECTOR_SET_ASSOCIATION(mDetailScales); + VECTOR_SET_ASSOCIATION(mReflectionAmounts); + + allocate(getMaterialCount()); + + dMemcpy(mFlags.address(),materialFlags,getMaterialCount()*sizeof(U32)); + dMemcpy(mReflectanceMaps.address(),reflectanceMaps,getMaterialCount()*sizeof(U32)); + dMemcpy(mBumpMaps.address(),bumpMaps,getMaterialCount()*sizeof(U32)); + dMemcpy(mDetailMaps.address(),detailMaps,getMaterialCount()*sizeof(U32)); + dMemcpy(mDetailScales.address(),detailScales,getMaterialCount()*sizeof(F32)); + dMemcpy(mReflectionAmounts.address(),reflectionAmounts,getMaterialCount()*sizeof(F32)); +} + +TSMaterialList::TSMaterialList() + : mNamesTransformed(false) +{ + VECTOR_SET_ASSOCIATION(mFlags); + VECTOR_SET_ASSOCIATION(mReflectanceMaps); + VECTOR_SET_ASSOCIATION(mBumpMaps); + VECTOR_SET_ASSOCIATION(mDetailMaps); + VECTOR_SET_ASSOCIATION(mDetailScales); + VECTOR_SET_ASSOCIATION(mReflectionAmounts); +} + +TSMaterialList::TSMaterialList(const TSMaterialList* pCopy) + : MaterialList(pCopy) +{ + VECTOR_SET_ASSOCIATION(mFlags); + VECTOR_SET_ASSOCIATION(mReflectanceMaps); + VECTOR_SET_ASSOCIATION(mBumpMaps); + VECTOR_SET_ASSOCIATION(mDetailMaps); + VECTOR_SET_ASSOCIATION(mDetailScales); + VECTOR_SET_ASSOCIATION(mReflectionAmounts); + + mFlags = pCopy->mFlags; + mReflectanceMaps = pCopy->mReflectanceMaps; + mBumpMaps = pCopy->mBumpMaps; + mDetailMaps = pCopy->mDetailMaps; + mDetailScales = pCopy->mDetailScales; + mReflectionAmounts = pCopy->mReflectionAmounts; + mNamesTransformed = pCopy->mNamesTransformed; +} + +TSMaterialList::~TSMaterialList() +{ + free(); +} + +void TSMaterialList::tsmlTransform() +{ + if (mNamesTransformed) + return; + mNamesTransformed = true; + + for (U32 i = 0; i < mMaterialNames.size(); i++) + { + char* pName = mMaterialNames[i]; + if (pName == NULL) + continue; + + // fix slashes that face the wrong way + S32 len = dStrlen(pName); + for (U32 j=0; j11) + { + for (i=0; i20) + { + for (i=0; i TSMesh::smVertsList; +Vector TSMesh::smNormsList; +Vector TSMesh::smEncodedNormsList; +Vector TSMesh::smTVertsList; +Vector TSMesh::smDataCopied; + +Vector TSMesh::smSaveVerts; +Vector TSMesh::smSaveNorms; +Vector TSMesh::smSaveTVerts; + +Vector TSSkinMesh::smInitTransformList; +Vector TSSkinMesh::smVertexIndexList; +Vector TSSkinMesh::smBoneIndexList; +Vector TSSkinMesh::smWeightList; +Vector TSSkinMesh::smNodeIndexList; + +Vector gNormalStore; + +F32 TSMesh::overrideFadeVal = 1.0; + +bool TSMesh::smUseTriangles = false; // convert all primitives to triangle lists on load +bool TSMesh::smUseOneStrip = false; // join triangle strips into one long strip on load +S32 TSMesh::smMinStripSize = 1; // smallest number of _faces_ allowed per strip (all else put in tri list) +bool TSMesh::smUseEncodedNormals = false; + +// quick function to force object to face camera -- currently throws out roll :( +void forceFaceCamera() +{ + MatrixF mat; + Point4F p; + + dglGetModelview(&mat); + mat.getColumn(3,&p); + mat.identity(); + mat.setColumn(3,p); + dglLoadMatrix(&mat); + if (TSShapeInstance::smRenderData.objectScale) + glScalef(TSShapeInstance::smRenderData.objectScale->x, + TSShapeInstance::smRenderData.objectScale->y, + TSShapeInstance::smRenderData.objectScale->z); +} + +void forceFaceCameraZAxis() +{ + MatrixF mat; + dglGetModelview(&mat); + Point3F z; + mat.getColumn(2,&z); // this is where the z-axis goes, keep it here but reset x and y + Point3F x,y; + if (mFabs(z.y) < 0.99f) + { + // mCross(Point3F(0,1,0),tAxis,&x); + x.set(z.z,0,-z.x); + x.normalize(); + mCross(z,x,&y); + } + else + { + // mCross(z,Point3F(1,0,0),&y); + y.set(0,z.z,-z.y); + y.normalize(); + mCross(y,z,&x); + } + mat.setColumn(0,x); + mat.setColumn(1,y); + mat.setColumn(2,z); + dglLoadMatrix(&mat); + if (TSShapeInstance::smRenderData.objectScale) + glScalef(TSShapeInstance::smRenderData.objectScale->x,TSShapeInstance::smRenderData.objectScale->y,TSShapeInstance::smRenderData.objectScale->z); +} + +void TSMesh::saveMergeVerts() +{ + S32 startMerge = vertsPerFrame - mergeIndices.size(); + S32 j; + for (j=0; j>30); + + glDrawElements(drawType,draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]); + PROFILE_END(); + } + + // unlock... + if (gGLState.suppLockedArrays) + glUnlockArraysEXT(); +} + +void TSMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials) +{ + if( vertsPerFrame <= 0 ) { + return; + } + + S32 firstVert = vertsPerFrame * frame; + S32 firstTVert = vertsPerFrame * matFrame; + + if (getFlags(Billboard)) + { + if (getFlags(BillboardZAxis)) + forceFaceCameraZAxis(); + else + forceFaceCamera(); + } + + const Point3F * normals = getNormals(firstVert); + saveMergeNormals(); // verts & tverts saved and restored on tsshapeinstance::setStatics + + // set up vertex arrays -- already enabled in TSShapeInstance::render + glVertexPointer(3,GL_FLOAT,0,&verts[firstVert]); + glNormalPointer(GL_FLOAT,0,normals); + glTexCoordPointer(2,GL_FLOAT,0,&tverts[firstTVert]); + if (TSShapeInstance::smRenderData.detailMapMethod == TSShapeInstance::DETAIL_MAP_MULTI_1 || + TSShapeInstance::smRenderData.detailMapMethod == TSShapeInstance::DETAIL_MAP_MULTI_2) + { + glClientActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.detailMapTE); + glTexCoordPointer(2,GL_FLOAT,0,&tverts[firstTVert]); + glClientActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + } + + // lock... + if (gGLState.suppLockedArrays) + glLockArraysEXT(0,vertsPerFrame); + + for (S32 i=0; i>30); + + glDrawElements(drawType,draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]); + } + + // unlock... + if (gGLState.suppLockedArrays) + glUnlockArraysEXT(); + + restoreMergeNormals(); +} + +const Point3F * TSMesh::getNormals(S32 firstVert) +{ + if (getFlags(UseEncodedNormals)) + { + gNormalStore.setSize(vertsPerFrame); + for (S32 i=0; igetGLName()); + glActiveTextureARB(GL_TEXTURE0_ARB + baseTE); + } + else if (fogMethod == TSShapeInstance::FOG_MULTI_1_TEXGEN) + { + // set up fog texure unit + glActiveTextureARB(GL_TEXTURE0_ARB + fogTE); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); + + // set up fog map + glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.fogMapHandle->getGLName()); + + // set up texgen equations + glTexGeni(GL_S,GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glTexGeni(GL_T,GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glEnable(GL_TEXTURE_GEN_S); + glEnable(GL_TEXTURE_GEN_T); + glTexGenfv(GL_S,GL_OBJECT_PLANE,&TSShapeInstance::smRenderData.fogTexGenS.x); + glTexGenfv(GL_T,GL_OBJECT_PLANE,&TSShapeInstance::smRenderData.fogTexGenT.x); + + // return to base TE + glActiveTextureARB(GL_TEXTURE0_ARB + baseTE); + } + + //---------------------------------- + // set up texture enviroment for base texture + + TSShapeInstance::smRenderData.materialFlags = TSMaterialList::S_Wrap | TSMaterialList::T_Wrap; + TSShapeInstance::smRenderData.materialIndex = TSDrawPrimitive::NoMaterial; + + // draw one-sided... + glEnable(GL_CULL_FACE); + glFrontFace(GL_CW); + + // enable vertex arrays... + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + // when blending we modulate and draw using src_alpha, 1-src_alpha... + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + + // we modulate in order to apply lighting... + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + + // but we don't blend by default... + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + + // lighting? + TSShapeInstance::smRenderData.lightingOn = glIsEnabled(GL_LIGHTING); + + // set vertex color (if emapping, vertexColor.w holds emapAlpha) + TSShapeInstance::smRenderData.vertexAlpha.set(); + Point4F vertexColor(1,1,1,TSShapeInstance::smRenderData.vertexAlpha.current); + glColor4fv(vertexColor); + glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,vertexColor); + + // this should be off by default, but we'll end up turning it on asap... + glDisable(GL_TEXTURE_2D); +} + +void TSMesh::resetMaterials() +{ + // restore gl state changes made for dmap + if (TSShapeInstance::smRenderData.detailMapMethod==TSShapeInstance::DETAIL_MAP_MULTI_1 || + TSShapeInstance::smRenderData.detailMapMethod==TSShapeInstance::DETAIL_MAP_MULTI_2) + { + // set detail maps texture unit + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.detailMapTE); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_TEXTURE_2D); + + // may have changed texture matrix of one of detail-texture texture-environment + if (mFabs(TSShapeInstance::smRenderData.detailTextureScale-1.0f) > 0.001f) + { + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + } + + if (TSShapeInstance::smRenderData.detailMapMethod==TSShapeInstance::DETAIL_MAP_MULTI_2) + { + glTexEnvfv(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR,Point4F(0,0,0,0)); + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.detailMapTE+1); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_TEXTURE_2D); + glTexEnvf(GL_TEXTURE_ENV,GL_RGB_SCALE_EXT,1.0f); + } + else + glTexEnvf(GL_TEXTURE_ENV,GL_RGB_SCALE_EXT,1.0f); + + // disable tcoord's on detail map's TE + glClientActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.detailMapTE); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glClientActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + } + + // restore gl state changes made for emap + if (TSShapeInstance::smRenderData.environmentMapMethod==TSShapeInstance::ENVIRONMENT_MAP_MULTI_1) + { + // set second texure unit (TE #1) + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.environmentMapTE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_TEXTURE_2D); + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + } + else if (TSShapeInstance::smRenderData.environmentMapMethod==TSShapeInstance::ENVIRONMENT_MAP_MULTI_3) + { + // set second texure unit (TE #1) + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.environmentMapTE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_TEXTURE_2D); + + // set third texure unit (TE #2) + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.environmentMapTE + 1); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_TEXTURE_2D); + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + + // set fourth texure unit (TE #3) + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.environmentMapTE + 2); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_TEXTURE_2D); + } + + if (TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_MULTI_1 || TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_MULTI_1_TEXGEN) + { + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.fogTE); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + glDisable(GL_TEXTURE_2D); + } + + // restore gl state changes made for base texture + if (dglDoesSupportARBMultitexture()) + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + + // don't cull by default... + glDisable(GL_CULL_FACE); + + // if lights were on when we started, make sure they're on when we leave + if (TSShapeInstance::smRenderData.lightingOn && !glIsEnabled(GL_LIGHTING)) + glEnable(GL_LIGHTING); + + // baseTE != 0? + if (TSShapeInstance::smRenderData.baseTE!=0) + { + glActiveTextureARB(GL_TEXTURE0_ARB); + glClientActiveTextureARB(GL_TEXTURE0_ARB); + } + + // restore cloak shifting + if (TSShapeInstance::smRenderData.textureMatrixPushed) + { + glMatrixMode(GL_TEXTURE); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + TSShapeInstance::smRenderData.textureMatrixPushed = false; + } +} + +// set up materials for mesh rendering +// keeps track of flags via TSShapeInstance::smRenderData.materialFlags and only changes what needs to be changed +// keeps track of material index via TSShapeInstance::smRenderData.materialIndex +void TSMesh::setMaterial(S32 matIndex, TSMaterialList* materials) +{ + if ((matIndex|TSShapeInstance::smRenderData.materialIndex) & TSDrawPrimitive::NoMaterial) + { + if (matIndex & TSDrawPrimitive::NoMaterial) + { + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + TSShapeInstance::smRenderData.materialIndex = matIndex; + TSShapeInstance::smRenderData.materialFlags &= ~TSMaterialList::Translucent; + return; + } + glEnable(GL_TEXTURE_2D); + } + + matIndex &= TSDrawPrimitive::MaterialMask; + + U32 flags = materials->getFlags(matIndex); + U32 bareFlags = flags; + if (TSShapeInstance::smRenderData.alwaysAlpha || TSShapeInstance::smRenderData.fadeSet) + flags |= TSMaterialList::Translucent; + U32 deltaFlags = flags ^ TSShapeInstance::smRenderData.materialFlags; + if (TSShapeInstance::smRenderData.environmentMapMethod!=TSShapeInstance::ENVIRONMENT_MAP_MULTI_1) + deltaFlags &= ~TSMaterialList::NeverEnvMap; + + // update flags and material index... + TSShapeInstance::smRenderData.materialFlags = flags; + TSShapeInstance::smRenderData.materialIndex = matIndex; + + if (TSShapeInstance::smRenderData.useOverride == false || bareFlags & TSMaterialList::Translucent) + { + TextureHandle & tex = materials->getMaterial(matIndex); + glBindTexture(GL_TEXTURE_2D, tex.getGLName()); + } + else + glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.override.getGLName()); + + // anything change...? + if (deltaFlags) + { + if (deltaFlags & TSMaterialList::NeverEnvMap && !TSShapeInstance::smRenderData.useOverride ) + { + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.environmentMapTE); + if (bareFlags & TSMaterialList::NeverEnvMap) + glDisable(GL_TEXTURE_2D); + else + glEnable(GL_TEXTURE_2D); + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + } + if (flags & TSMaterialList::Translucent) + { + if (TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_TWO_PASS || TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_TWO_PASS_TEXGEN) + { + TSShapeInstance::smRenderData.vertexAlpha.fog = 1.0f - TSShapeInstance::smRenderData.fogColor.w; + } + else if ((TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_MULTI_1 || + TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_MULTI_1_TEXGEN) && + flags & (TSMaterialList::Additive|TSMaterialList::Subtractive)) + { + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.fogTE); + glDisable(GL_TEXTURE_2D); + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + TSShapeInstance::smRenderData.vertexAlpha.fog = 1.0f - TSShapeInstance::smRenderData.fogColor.w; + } + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + } + else + { + if (TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_TWO_PASS || TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_TWO_PASS_TEXGEN) + { + TSShapeInstance::smRenderData.vertexAlpha.fog = 1.0f; + } + else if ((TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_MULTI_1 || + TSShapeInstance::smRenderData.fogMethod == TSShapeInstance::FOG_MULTI_1_TEXGEN) && + flags & (TSMaterialList::Additive|TSMaterialList::Subtractive)) + { + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.fogTE); + glEnable(GL_TEXTURE_2D); + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + TSShapeInstance::smRenderData.vertexAlpha.fog = 1.0f; + } + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + } + if (deltaFlags & (TSMaterialList::Additive|TSMaterialList::Subtractive)) + { + if (flags & TSMaterialList::Additive) + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + else if (flags & TSMaterialList::Subtractive) + glBlendFunc(GL_ZERO,GL_ONE_MINUS_SRC_COLOR); + else + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + } + if (deltaFlags & TSMaterialList::SelfIlluminating) + { + if (TSShapeInstance::smRenderData.detailMapMethod==TSShapeInstance::DETAIL_MAP_MULTI_2) + { + // special case: lighting done on different TE than texture... + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE + 1); + if (flags & TSMaterialList::SelfIlluminating) + { + // modulate... + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + } + else + { + // we need to use the combine extension...we modulate by primary color rather than fragment color + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_COMBINE_RGB_EXT,GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE0_RGB_EXT,GL_PREVIOUS_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND0_RGB_EXT,GL_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE1_RGB_EXT,GL_PRIMARY_COLOR_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND1_RGB_EXT,GL_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV,GL_COMBINE_ALPHA_EXT,GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE0_ALPHA_EXT,GL_PREVIOUS_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND0_ALPHA_EXT,GL_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE1_ALPHA_EXT,GL_PRIMARY_COLOR_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND1_ALPHA_EXT,GL_SRC_ALPHA); + } + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + } + // turn off lights if self-illuminating (or back on if not) + if (flags & TSMaterialList::SelfIlluminating || !TSShapeInstance::smRenderData.lightingOn) + glDisable(GL_LIGHTING); + else + glEnable(GL_LIGHTING); + } + } + + // gotta set these every time since present value depends on texture not gl state + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,flags & TSMaterialList::S_Wrap ? GL_REPEAT : GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,flags & TSMaterialList::T_Wrap ? GL_REPEAT : GL_CLAMP); + + // emap texture... + if (TSShapeInstance::smRenderData.environmentMapMethod==TSShapeInstance::ENVIRONMENT_MAP_MULTI_3) + { + // set emap's texture unit... + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.environmentMapTE + 2); + + if (TSShapeInstance::smRenderData.useOverride == false || flags & TSMaterialList::Translucent) + { + TextureHandle & tex = materials->getMaterial(matIndex); + glBindTexture(GL_TEXTURE_2D, tex.getGLName()); + } + else + glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.override.getGLName()); + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.environmentMapTE); + glBindTexture(GL_TEXTURE_2D, materials->getReflectionMap(matIndex)->getGLName()); + + // set default texture unit... + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + } + + // dmap texture... + if (TSShapeInstance::smRenderData.detailMapMethod==TSShapeInstance::DETAIL_MAP_MULTI_1 || + TSShapeInstance::smRenderData.detailMapMethod==TSShapeInstance::DETAIL_MAP_MULTI_2) + { + // set detail map's texture unit... + glActiveTextureARB(GL_TEXTURE0_ARB+TSShapeInstance::smRenderData.detailMapTE); + + TextureHandle * detailMap = materials->getDetailMap(matIndex); + if (detailMap) + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D,detailMap->getGLName()); + + // has texture scale changed? + F32 tscale = materials->getDetailMapScale(matIndex); + if (mFabs(tscale-TSShapeInstance::smRenderData.detailTextureScale) > 0.001f) + { + // yes, update scale + TSShapeInstance::smRenderData.detailTextureScale = tscale; + glMatrixMode(GL_TEXTURE); + MatrixF scaleMat; + scaleMat.identity(); + for (S32 i=0; i<15; i++) + ((F32*)scaleMat)[i] *= tscale; + dglLoadMatrix(&scaleMat); + glMatrixMode(GL_MODELVIEW); + } + } + else + glDisable(GL_TEXTURE_2D); + + // set default texture unit... + glActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + } + + // translucent materials shouldn't get cloak shifting + // 1: pushed -> pushed + // 2: pushed -> not pushed + // 3: not pushed -> pushed + // 4: not pushed -> not pushed + if (TSShapeInstance::smRenderData.textureMatrixPushed) + { + if (TSShapeInstance::smRenderData.useOverride && bareFlags & TSMaterialList::Translucent) + { + // Leave it alone + } + else + { + // Pop it off + glMatrixMode(GL_TEXTURE); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + TSShapeInstance::smRenderData.textureMatrixPushed = false; + } + } + else + { + if (TSShapeInstance::smRenderData.useOverride && bareFlags & TSMaterialList::Translucent) + { + // Push it up + glMatrixMode(GL_TEXTURE); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + TSShapeInstance::smRenderData.textureMatrixPushed = true; + } + else + { + // leave it alone + } + } + + // handle environment map + if (flags & TSMaterialList::NeverEnvMap) + TSShapeInstance::smRenderData.vertexAlpha.emap = 1.0f; + else + TSShapeInstance::smRenderData.vertexAlpha.emap = TSShapeInstance::smRenderData.environmentMapAlpha * materials->getReflectionAmount(matIndex); + // handle vertex alpha + if (TSShapeInstance::smRenderData.vertexAlpha.set()) + { + Point4F v(1,1,1,TSShapeInstance::smRenderData.vertexAlpha.current); + glColor4fv(v); + glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,v); + } + + // set up fade + if( overrideFadeVal < 1.0f && dglDoesSupportTextureEnvCombine() ) + { + S32 & emapTE = TSShapeInstance::smRenderData.environmentMapTE; + S32 & baseTE = TSShapeInstance::smRenderData.baseTE; + + if( TSShapeInstance::smRenderData.environmentMapMethod == TSShapeInstance::ENVIRONMENT_MAP_MULTI_1 ) + { + glActiveTextureARB(GL_TEXTURE0_ARB + emapTE); + glDisable(GL_TEXTURE_2D); + } + + glActiveTextureARB(GL_TEXTURE0_ARB + baseTE); + + glEnable( GL_BLEND ); + + if( TSShapeInstance::smRenderData.materialFlags & TSMaterialList::Translucent ) + { + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + } + else + { + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + + + ColorF curColor; + glGetFloatv( GL_FOG_COLOR, (GLfloat*)&curColor ); + curColor.alpha = overrideFadeVal; + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, curColor); + + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_COMBINE_ALPHA_EXT,GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE0_ALPHA_EXT,GL_CONSTANT_EXT); + glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND0_ALPHA_EXT,GL_SRC_ALPHA); + + + } +} + +void TSMesh::setFade(F32 fadeValue) +{ + TSShapeInstance::smRenderData.vertexAlpha.vis = fadeValue; + if (TSShapeInstance::smRenderData.vertexAlpha.set()) + { + Point4F v(1,1,1,TSShapeInstance::smRenderData.vertexAlpha.current); + glColor4fv(v); + glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,v); + } + TSShapeInstance::smRenderData.fadeSet = true; +} + +void TSMesh::clearFade() +{ + setFade(1.0f); + TSShapeInstance::smRenderData.fadeSet = false; +} + +//----------------------------------------------------- +// TSMesh render environment map (2-pass) methods +//----------------------------------------------------- + +void TSMesh::renderEnvironmentMap(S32 frame, S32 matFrame, TSMaterialList * materials) +{ + matFrame; + + // most gl states assumed to be all set up... + // if we're here, then we're drawing environment map in two passes... + + S32 firstVert = vertsPerFrame * frame; + + // set up vertex arrays -- already enabled in TSShapeInstance::render + glVertexPointer(3,GL_FLOAT,0,&verts[firstVert]); + glNormalPointer(GL_FLOAT,0,&norms[firstVert]); + + // lock... + if (gGLState.suppLockedArrays) + glLockArraysEXT(0,vertsPerFrame); + + S32 matIndex = -1; + + for (S32 i=0; igetFlags(matIndex) & TSMaterialList::NeverEnvMap) + continue; + TSShapeInstance::smRenderData.vertexAlpha.emap = + TSShapeInstance::smRenderData.environmentMapAlpha * materials->getReflectionAmount(matIndex); + glBindTexture(GL_TEXTURE_2D, materials->getReflectionMap(matIndex)->getGLName()); + if (TSShapeInstance::smRenderData.vertexAlpha.set()) + { + Point4F v(1,1,1,TSShapeInstance::smRenderData.vertexAlpha.current); + glColor4fv(v); + glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,v); + } + } + } + glDrawElements(getDrawType(draw.matIndex>>30),draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]); + } + + // unlock... + if (gGLState.suppLockedArrays) + glUnlockArraysEXT(); +} + +void TSMesh::initEnvironmentMapMaterials() +{ + // set up gl environment + + // TE0 + glActiveTextureARB(GL_TEXTURE0_ARB); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glDepthMask(GL_TRUE); + glFrontFace(GL_CW); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); + + // TE1 + glActiveTextureARB(GL_TEXTURE1_ARB); + + glEnable(GL_TEXTURE_2D); + glTexGeni(GL_S,GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); + glTexGeni(GL_T,GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); + glEnable(GL_TEXTURE_GEN_S); + glEnable(GL_TEXTURE_GEN_T); + glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.environmentMapGLName); + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE); // should leave alpha alone since emap has no alpha + + // TE0 again + glActiveTextureARB(GL_TEXTURE0_ARB); + + TSShapeInstance::smRenderData.vertexAlpha.init(); + TSShapeInstance::smRenderData.vertexAlpha.emap = TSShapeInstance::smRenderData.environmentMapAlpha; + TSShapeInstance::smRenderData.vertexAlpha.always = TSShapeInstance::smRenderData.alwaysAlphaValue; + TSShapeInstance::smRenderData.vertexAlpha.set(); + glColor4fv(Point4F(1.0f,1.0f,1.0f,TSShapeInstance::smRenderData.vertexAlpha.current)); +} + +void TSMesh::resetEnvironmentMapMaterials() +{ + // restore texture environmnet 0 + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glDisable(GL_CULL_FACE); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + // restore texture environment 1 + glActiveTextureARB(GL_TEXTURE1_ARB); + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + glDisable(GL_TEXTURE_2D); + + glActiveTextureARB(GL_TEXTURE0_ARB); +} + +//----------------------------------------------------- +// TSMesh render detail map (2 pass) methods +//----------------------------------------------------- + +void TSMesh::renderDetailMap(S32 frame, S32 matFrame, TSMaterialList * materials) +{ + // most gl states assumed to be all set up... + // if we're here, then we're drawing detail map in two passes... + + S32 firstVert = vertsPerFrame * frame; + S32 firstTVert = vertsPerFrame * matFrame; + + // set up vertex arrays -- already enabled in TSShapeInstance::render + glVertexPointer(3,GL_FLOAT,0,&verts[firstVert]); + glNormalPointer(GL_FLOAT,0,&norms[firstVert]); + glTexCoordPointer(2,GL_FLOAT,0,&tverts[firstTVert]); + + // lock... + if (gGLState.suppLockedArrays) + glLockArraysEXT(0,vertsPerFrame); + + S32 matIndex = -1; + + for (S32 i=0; igetDetailMap(matIndex); + if (detailMap) + glBindTexture(GL_TEXTURE_2D,detailMap->getGLName()); + else + continue; + TSShapeInstance::smRenderData.detailMapTE = 1; + F32 tscale = materials->getDetailMapScale(matIndex); + if (mFabs(tscale-TSShapeInstance::smRenderData.detailTextureScale) > 0.001f) + { + // yes, update scale + TSShapeInstance::smRenderData.detailTextureScale = tscale; + glMatrixMode(GL_TEXTURE); + MatrixF scaleMat; + scaleMat.identity(); + for (S32 i=0; i<15; i++) + ((F32*)scaleMat)[i] *= tscale; + dglLoadMatrix(&scaleMat); + glMatrixMode(GL_MODELVIEW); + } + } + } + if (TSShapeInstance::smRenderData.detailMapTE) + glDrawElements(getDrawType(draw.matIndex>>30),draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]); + } + + // unlock... + if (gGLState.suppLockedArrays) + glUnlockArraysEXT(); +} + +void TSMesh::initDetailMapMaterials() +{ + TSShapeInstance::smRenderData.detailTextureScale = 1.0f; + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glEnable(GL_CULL_FACE); + glFrontFace(GL_CW); + glDepthMask(GL_TRUE); + glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + // set texture environment color and fragment color + // so that c=1-k/2 and f=k/2, where k is 1-detailMapAlpha + // result is that distance from 0.5 is reduced as + // detailMapAlpha goes to 0... + F32 k = 0.5f * (1.0f - TSShapeInstance::smRenderData.detailMapAlpha); + Point4F TEColor(1.0f-k,1.0f-k,1.0f-k,1.0f); + glTexEnvfv(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR,TEColor); + Point4F fColor(k,k,k,1.0f); + glColor4fv(fColor); + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_BLEND); + + glDisable(GL_LIGHTING); +} + +void TSMesh::resetDetailMapMaterials() +{ + glEnable(GL_LIGHTING); + + if (mFabs(TSShapeInstance::smRenderData.detailTextureScale-1.0f)>0.001f) + { + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + } + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glDepthMask(GL_TRUE); + glDisable(GL_CULL_FACE); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glColor4fv(Point4F(1,1,1,1)); + glTexEnvfv(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR,Point4F(0,0,0,0)); + glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); +} + +//----------------------------------------------------- +// TSMesh renderShadow +//----------------------------------------------------- + +Vector gShadowVerts(__FILE__, __LINE__); + +void shadowTransform(const MatrixF & mat, Point3F * v, Point3F * vend, S32 max) +{ + const F32 * m = (const F32*)mat; + F32 ax = m[0]; + F32 ay = m[1]; + F32 az = m[2]; + F32 at = m[3]; + F32 bx = m[8]; + F32 by = m[9]; + F32 bz = m[10]; + F32 bt = m[11]; + + Point2I * dest = gShadowVerts.address(); + S32 val; + + while (v < vend) + { + val = (S32)(ax*v->x + ay*v->y + az*v->z + at); + dest->x = val<0 ? 0 : (val>max ? max : val); + + val = (S32)(bx*v->x + by*v->y + bz*v->z + bt); + dest->y = val<0 ? 0 : (val>max ? max : val); + + dest++; v++; + } +} + +void TSMesh::renderShadow(S32 frame, const MatrixF & mat, S32 dim, U32 * bits, TSMaterialList * materials) +{ + if (!vertsPerFrame || !primitives.size()) + return; + + if (!(primitives[0].matIndex & TSDrawPrimitive::NoMaterial) && (TSMaterialList::Translucent & materials->getFlags(primitives[0].matIndex & TSDrawPrimitive::MaterialMask))) + // if no material...it would be nice to just exit...but may have some real polys back there... + return; + + AssertFatal(primitives[0].matIndex & TSDrawPrimitive::Indexed,"TSMesh::renderShadow: indexed polys only"); + + gShadowVerts.setSize(vertsPerFrame); + Point3F * vstart = &verts[frame*vertsPerFrame]; + Point3F * vend = vstart + vertsPerFrame; + + // result placed into gShadowVerts + shadowTransform(mat,vstart,vend,dim-1); + + // pick correct bit render routine...we assume all strips or all triangles + if ( (primitives[0].matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Strip) + BitRender::render_strips((U8*)primitives.address(),primitives.size(),sizeof(TSDrawPrimitive),indices.address(),gShadowVerts.address(),dim,bits); + else if ( (primitives[0].matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles) + BitRender::render_tris((U8*)primitives.address(),primitives.size(),sizeof(TSDrawPrimitive),indices.address(),gShadowVerts.address(),dim,bits); + else + AssertFatal(0,"TSMesh::renderShadow: strips or triangles only...how'd you get in here."); +} + + +//----------------------------------------------------- +// TSMesh render fog +//----------------------------------------------------- +void TSMesh::renderFog(S32 frame, TSMaterialList* materials) +{ + if (getFlags(Billboard)) + { + if (getFlags(BillboardZAxis)) + forceFaceCameraZAxis(); + else + forceFaceCamera(); + } + + S32 firstVert = vertsPerFrame * frame; + + // set up vertex arrays -- already enabled in TSShapeInstance::render + glVertexPointer(3,GL_FLOAT,0,&verts[firstVert]); + + // lock... + if (gGLState.suppLockedArrays) + glLockArraysEXT(0,vertsPerFrame); + + for (S32 i=0; igetFlags(primitives[i].matIndex & TSDrawPrimitive::MaterialMask) & (TSMaterialList::Translucent | TSMaterialList::Additive)) + continue; + + glDrawElements(getDrawType(draw.matIndex>>30),draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]); + } + + // unlock... + if (gGLState.suppLockedArrays) + glUnlockArraysEXT(); +} + +//----------------------------------------------------- +// TSMesh collision methods +//----------------------------------------------------- + +bool TSMesh::buildPolyList(S32 frame, AbstractPolyList * polyList, U32 & surfaceKey) +{ + S32 firstVert = vertsPerFrame * frame, i, base; + + // add the verts... + if (vertsPerFrame) + { + base = polyList->addPoint(verts[firstVert]); + for (i=1; iaddPoint(verts[i+firstVert]); + } + + // add the polys... + for (i=0; ibegin(material,surfaceKey++); + polyList->vertex(idx0); + polyList->vertex(idx1); + polyList->vertex(idx2); + polyList->plane(idx0,idx1,idx2); + polyList->end(); + j += 3; + } + } + else + { + AssertFatal((draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Strip,"TSMesh::buildPolyList (2)"); + + U32 idx0 = base + indices[start + 0]; + U32 idx1; + U32 idx2 = base + indices[start + 1]; + U32 * nextIdx = &idx1; + for (S32 j=2; jbegin(material,surfaceKey++); + polyList->vertex(idx0); + polyList->vertex(idx1); + polyList->vertex(idx2); + polyList->plane(idx0,idx1,idx2); + polyList->end(); + } + } + } + return true; +} + +bool TSMesh::getFeatures(S32 frame, const MatrixF& mat, const Point3F& /*n*/, ConvexFeature* cf, U32& /*surfaceKey*/) +{ + // DMM NOTE! Do not change without talking to Dave Moore. ShapeBase assumes that + // this will return ALL information from the mesh. + S32 firstVert = vertsPerFrame * frame; + S32 i; + S32 base = cf->mVertexList.size(); + + for (i = 0; i < vertsPerFrame; i++) { + cf->mVertexList.increment(); + mat.mulP(verts[firstVert + i], &cf->mVertexList.last()); + } + + // add the polys... + for (i=0; i < primitives.size(); i++) + { + TSDrawPrimitive & draw = primitives[i]; + U32 start = draw.start; + + AssertFatal(draw.matIndex & TSDrawPrimitive::Indexed,"TSMesh::buildPolyList (1)"); + + U32 material = draw.matIndex & TSDrawPrimitive::MaterialMask; + + // gonna depend on what kind of primitive it is... + if ( (draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles) + { + for (S32 j=0; jmVertexList[base + indices[start + j + 0]], + cf->mVertexList[base + indices[start + j + 1]], + cf->mVertexList[base + indices[start + j + 2]]); + + cf->mFaceList.increment(); + cf->mFaceList.last().normal = plane; + + cf->mFaceList.last().vertex[0] = base + indices[start + j + 0]; + cf->mFaceList.last().vertex[1] = base + indices[start + j + 1]; + cf->mFaceList.last().vertex[2] = base + indices[start + j + 2]; + + for (U32 l = 0; l < 3; l++) { + U32 newEdge0, newEdge1; + U32 zero = base + indices[start + j + l]; + U32 one = base + indices[start + j + ((l+1)%3)]; + newEdge0 = getMin(zero, one); + newEdge1 = getMax(zero, one); + bool found = false; + for (U32 k = 0; k < cf->mEdgeList.size(); k++) { + if (cf->mEdgeList[k].vertex[0] == newEdge0 && + cf->mEdgeList[k].vertex[1] == newEdge1) { + found = true; + break; + } + } + if (!found) { + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = newEdge0; + cf->mEdgeList.last().vertex[1] = newEdge1; + } + } + } + } + else + { + AssertFatal((draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Strip,"TSMesh::buildPolyList (2)"); + + U32 idx0 = base + indices[start + 0]; + U32 idx1; + U32 idx2 = base + indices[start + 1]; + U32 * nextIdx = &idx1; + for (S32 j=2; jmVertexList[idx0], + cf->mVertexList[idx1], + cf->mVertexList[idx2]); + + cf->mFaceList.increment(); + cf->mFaceList.last().normal = plane; + + cf->mFaceList.last().vertex[0] = idx0; + cf->mFaceList.last().vertex[1] = idx1; + cf->mFaceList.last().vertex[2] = idx2; + + U32 newEdge0, newEdge1; + newEdge0 = getMin(idx0, idx1); + newEdge1 = getMax(idx0, idx1); + bool found = false; + U32 k; + for (k = 0; k < cf->mEdgeList.size(); k++) { + if (cf->mEdgeList[k].vertex[0] == newEdge0 && + cf->mEdgeList[k].vertex[1] == newEdge1) { + found = true; + break; + } + } + if (!found) { + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = newEdge0; + cf->mEdgeList.last().vertex[1] = newEdge1; + } + + newEdge0 = getMin(idx1, idx2); + newEdge1 = getMax(idx1, idx2); + found = false; + for (k = 0; k < cf->mEdgeList.size(); k++) { + if (cf->mEdgeList[k].vertex[0] == newEdge0 && + cf->mEdgeList[k].vertex[1] == newEdge1) { + found = true; + break; + } + } + if (!found) { + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = newEdge0; + cf->mEdgeList.last().vertex[1] = newEdge1; + } + + newEdge0 = getMin(idx0, idx2); + newEdge1 = getMax(idx0, idx2); + found = false; + for (k = 0; k < cf->mEdgeList.size(); k++) { + if (cf->mEdgeList[k].vertex[0] == newEdge0 && + cf->mEdgeList[k].vertex[1] == newEdge1) { + found = true; + break; + } + } + if (!found) { + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = newEdge0; + cf->mEdgeList.last().vertex[1] = newEdge1; + } + } + } + } + + return false; +} + + +void TSMesh::support(S32 frame, const Point3F& v, F32* currMaxDP, Point3F* currSupport) +{ + if (vertsPerFrame == 0) + return; + + U32 waterMark = FrameAllocator::getWaterMark(); + F32* pDots = (F32*)FrameAllocator::alloc(sizeof(F32) * vertsPerFrame); + + S32 firstVert = vertsPerFrame * frame; + m_point3F_bulk_dot(&v.x, + &verts[firstVert].x, + vertsPerFrame, + sizeof(Point3F), + pDots); + + F32 localdp = *currMaxDP; + S32 index = -1; + + for (S32 i = 0; i < vertsPerFrame; i++) + { + if (pDots[i] > localdp) + { + localdp = pDots[i]; + index = i; + } + } + + FrameAllocator::setWaterMark(waterMark); + + if (index != -1) + { + *currMaxDP = localdp; + *currSupport = verts[index + firstVert]; + } +} + +bool TSMesh::castRay(S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo) +{ + if (planeNormals.empty()) + // if haven't done it yet... + buildConvexHull(); + + // Keep track of startTime and endTime. They start out at just under 0 and just over 1, respectively. + // As we check against each plane, prune start and end times back to represent current intersection of + // line with all the planes (or rather with all the half-spaces defined by the planes). + // But, instead of explicitly keeping track of startTime and endTime, keep track as numerator and denominator + // so that we can avoid as many divisions as possible. + + // F32 startTime = -0.01f; + F32 startNum = -0.01f; + F32 startDen = 1.00f; + // F32 endTime = 1.01f; + F32 endNum = 1.01f; + F32 endDen = 1.00f; + + S32 curPlane = 0; + U32 curMaterial = 0; + bool found = false; + + // the following block of code is an optimization... + // it isn't necessary if the longer version of the main loop is used + bool tmpFound; + S32 tmpPlane; + F32 sgn = -1.0f; + F32 * pnum = &startNum; + F32 * pden = &startDen; + S32 * pplane = &curPlane; + bool * pfound = &found; + + S32 startPlane = frame * planesPerFrame; + for (S32 i=startPlane; i0.0f) + { + // same side of the plane...which side -- dot==0 considered inside + if (dot1>0.0f) + // start and end outside of this plane, no collision + return false; + // start and end inside plane, continue + continue; + } + + AssertFatal(dot1/(dot1-dot2)>=0.0f && dot1/(dot1-dot2)<=1.0f,"TSMesh::castRay (1)"); + + // find intersection (time) with this plane... + // F32 time = dot1 / (dot1-dot2); + F32 num = mFabs(dot1); + F32 den = mFabs(dot1-dot2); + + // the following block of code is an optimized version... + // this can be commented out and the following block of code used instead + // if debugging a problem in this code, that should probably be done + // if you want to see how this works, look at the following block of code, + // not this one... + // Note that this does not get optimized appropriately...it is included this way + // as an idea for future optimization. + if (sgn*dot1>=0) + { + sgn *= -1.0f; + pnum = (F32*) ((U32)pnum ^ (U32)&endNum ^ (U32)&startNum); + pden = (F32*) ((U32)pden ^ (U32)&endDen ^ (U32)&startDen); + pplane = (S32*) ((U32)pplane ^ (U32)&tmpPlane ^ (U32)&curPlane); + pfound = (bool*) ((U32)pfound ^ (U32)&tmpFound ^ (U32)&found); + } + bool noCollision; + if (num * *pden * sgn < *pnum * den * sgn && !(noCollision=num*endDen*sgnstartNum*den) // if (time>startTime) +// { +// if (num*endDen>endNum*den) //if (time>endTime) +// // no intersection of line and hull +// return false; +// // startTime = time; +// startNum = num; +// startDen = den; +// curPlane = i; +// curMaterial = planeMaterials[i-startPlane]; +// found = true; +// } +// // else, no need to do anything, just continue (we've been more inside than this) +// } + } + + // setup rayInfo + if (found && rayInfo) + { + rayInfo->t = (F32)startNum/(F32)startDen; // finally divide... + rayInfo->normal = planeNormals[curPlane]; + rayInfo->material = curMaterial; + return true; + } + else if (found) + return true; + + // only way to get here is if start is inside hull... + // we could return null and just plug in garbage for the material and normal... + return false; +} + +bool TSMesh::addToHull(U32 idx0, U32 idx1, U32 idx2) +{ + Point3F normal; + mCross(verts[idx2]-verts[idx0],verts[idx1]-verts[idx0],&normal); + if (mDot(normal,normal)<0.001f) + { + mCross(verts[idx0]-verts[idx1],verts[idx2]-verts[idx1],&normal); + if (mDot(normal,normal)<0.001f) + { + mCross(verts[idx1]-verts[idx2],verts[idx0]-verts[idx2],&normal); + if (mDot(normal,normal)<0.001f) + return false; + } + } + normal.normalize(); + F32 k = mDot(normal,verts[idx0]); + for (S32 i=0; i0.99f && mFabs(k-planeConstants[i])<0.01f) + // this is a repeat... + return false; + } + // new plane, add it to the list... + planeNormals.push_back(normal); + planeConstants.push_back(k); + return true; +} + +bool TSMesh::buildConvexHull() +{ + // already done, return without error + if (planeNormals.size()) + return true; + + bool error = false; + + // should probably only have 1 frame, but just in case... + planesPerFrame = 0; + S32 frame, i, j; + for (frame=0; frame planeNormals.size() ) + { + // we're short, duplicate last plane till we match + U32 sz = planeNormals.size(); + planeNormals.increment(); + planeNormals.last() = planeNormals[sz-1]; + planeConstants.increment(); + planeConstants.last() = planeConstants[sz-1]; + } + while ( (frame+1) * planesPerFrame < planeNormals.size() ) + { + // harsh -- last frame has more than other frames + // duplicate last plane in each frame + for (S32 k=frame-1; k>=0; k--) + { + planeNormals.insert(k*planesPerFrame+planesPerFrame); + planeNormals[k*planesPerFrame+planesPerFrame] = planeNormals[k*planesPerFrame+planesPerFrame-1]; + planeConstants.insert(k*planesPerFrame+planesPerFrame); + planeConstants[k*planesPerFrame+planesPerFrame] = planeConstants[k*planesPerFrame+planesPerFrame-1]; + if (k==0) + { + planeMaterials.increment(); + planeMaterials.last() = planeMaterials[planeMaterials.size()-2]; + } + } + planesPerFrame++; + } + } + AssertFatal((frame+1) * planesPerFrame == planeNormals.size(),"TSMesh::buildConvexHull (3)"); + } + return !error; +} + +//----------------------------------------------------- +// TSMesh bounds methods +//----------------------------------------------------- + +void TSMesh::computeBounds() +{ + MatrixF mat(true); + computeBounds(mat,mBounds,-1,&mCenter,&mRadius); +} + +void TSMesh::computeBounds(MatrixF & transform, Box3F & bounds, S32 frame, Point3F * center, F32 * radius) +{ + if (frame<0) + computeBounds(verts.address(),verts.size(),transform,bounds,center,radius); + else + computeBounds(verts.address() + frame * vertsPerFrame,vertsPerFrame,transform,bounds,center,radius); +} + +void TSMesh::computeBounds(Point3F * v, S32 numVerts, MatrixF & transform, Box3F & bounds, Point3F * center, F32 * radius) +{ + if (!numVerts) + { + bounds.min.set(0,0,0); + bounds.max.set(0,0,0); + if (center) + center->set(0,0,0); + if (radius) + *radius = 0; + return; + } + + S32 i; + Point3F p; + transform.mulP(*v,&bounds.min); + bounds.max = bounds.min; + for (i=0; ix = 0.5f * (bounds.min.x + bounds.max.x); + center->y = 0.5f * (bounds.min.y + bounds.max.y); + center->z = 0.5f * (bounds.min.z + bounds.max.z); + if (radius) + { + *radius = 0.0f; + for (i=0; i gBoneTransforms; +Vector gSkinVerts; +Vector gSkinNorms; + +void TSSkinMesh::updateSkin() +{ + // set arrays + gBoneTransforms.setSize(nodeIndex.size()); +#ifdef MAX_UTIL + verts.setSize(initialVerts.size()); + norms.setSize(initialVerts.size()); +#else + if (encodedNorms.size()) + { + // we co-opt responsibility for updating encoded normals from mesh + gNormalStore.setSize(vertsPerFrame); + for (S32 i=0; i0.01f) + norms[i] *= 1.0f/mSqrt(len2); + } +} + +void TSSkinMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials) +{ + // update verts and normals... + updateSkin(); + + // render... + Parent::render(frame,matFrame,materials); +} + +bool TSSkinMesh::buildPolyList(S32 frame, AbstractPolyList * polyList, U32 & surfaceKey) +{ + // update verts and normals... + updateSkin(); + + // render... + return Parent::buildPolyList(frame,polyList,surfaceKey); +} + +bool TSSkinMesh::castRay(S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo) +{ + frame,start,end,rayInfo; + return false; +} + +bool TSSkinMesh::buildConvexHull() +{ + return false; // no error, but we don't do anything either... +} + +void TSSkinMesh::computeBounds(MatrixF & transform, Box3F & bounds, S32 frame, Point3F * center, F32 * radius) +{ + frame; + TSMesh::computeBounds(initialVerts.address(),initialVerts.size(),transform,bounds,center,radius); +} + +//----------------------------------------------------- +// encoded normals +//----------------------------------------------------- + +const Point3F TSMesh::smU8ToNormalTable[] = +{ + Point3F( 0.565061f, -0.270644f, -0.779396f ), + Point3F( -0.309804f, -0.731114f, 0.607860f ), + Point3F( -0.867412f, 0.472957f, 0.154619f ), + Point3F( -0.757488f, 0.498188f, -0.421925f ), + Point3F( 0.306834f, -0.915340f, 0.260778f ), + Point3F( 0.098754f, 0.639153f, -0.762713f ), + Point3F( 0.713706f, -0.558862f, -0.422252f ), + Point3F( -0.890431f, -0.407603f, -0.202466f ), + Point3F( 0.848050f, -0.487612f, -0.207475f ), + Point3F( -0.232226f, 0.776855f, 0.585293f ), + Point3F( -0.940195f, 0.304490f, -0.152706f ), + Point3F( 0.602019f, -0.491878f, -0.628991f ), + Point3F( -0.096835f, -0.494354f, -0.863850f ), + Point3F( 0.026630f, -0.323659f, -0.945799f ), + Point3F( 0.019208f, 0.909386f, 0.415510f ), + Point3F( 0.854440f, 0.491730f, 0.167731f ), + Point3F( -0.418835f, 0.866521f, -0.271512f ), + Point3F( 0.465024f, 0.409667f, 0.784809f ), + Point3F( -0.674391f, -0.691087f, -0.259992f ), + Point3F( 0.303858f, -0.869270f, -0.389922f ), + Point3F( 0.991333f, 0.090061f, -0.095640f ), + Point3F( -0.275924f, -0.369550f, 0.887298f ), + Point3F( 0.426545f, -0.465962f, 0.775202f ), + Point3F( -0.482741f, -0.873278f, -0.065920f ), + Point3F( 0.063616f, 0.932012f, -0.356800f ), + Point3F( 0.624786f, -0.061315f, 0.778385f ), + Point3F( -0.530300f, 0.416850f, 0.738253f ), + Point3F( 0.312144f, -0.757028f, -0.573999f ), + Point3F( 0.399288f, -0.587091f, -0.704197f ), + Point3F( -0.132698f, 0.482877f, 0.865576f ), + Point3F( 0.950966f, 0.306530f, 0.041268f ), + Point3F( -0.015923f, -0.144300f, 0.989406f ), + Point3F( -0.407522f, -0.854193f, 0.322925f ), + Point3F( -0.932398f, 0.220464f, 0.286408f ), + Point3F( 0.477509f, 0.876580f, 0.059936f ), + Point3F( 0.337133f, 0.932606f, -0.128796f ), + Point3F( -0.638117f, 0.199338f, 0.743687f ), + Point3F( -0.677454f, 0.445349f, 0.585423f ), + Point3F( -0.446715f, 0.889059f, -0.100099f ), + Point3F( -0.410024f, 0.909168f, 0.072759f ), + Point3F( 0.708462f, 0.702103f, -0.071641f ), + Point3F( -0.048801f, -0.903683f, -0.425411f ), + Point3F( -0.513681f, -0.646901f, 0.563606f ), + Point3F( -0.080022f, 0.000676f, -0.996793f ), + Point3F( 0.066966f, -0.991150f, -0.114615f ), + Point3F( -0.245220f, 0.639318f, -0.728793f ), + Point3F( 0.250978f, 0.855979f, 0.452006f ), + Point3F( -0.123547f, 0.982443f, -0.139791f ), + Point3F( -0.794825f, 0.030254f, -0.606084f ), + Point3F( -0.772905f, 0.547941f, 0.319967f ), + Point3F( 0.916347f, 0.369614f, -0.153928f ), + Point3F( -0.388203f, 0.105395f, 0.915527f ), + Point3F( -0.700468f, -0.709334f, 0.078677f ), + Point3F( -0.816193f, 0.390455f, 0.425880f ), + Point3F( -0.043007f, 0.769222f, -0.637533f ), + Point3F( 0.911444f, 0.113150f, 0.395560f ), + Point3F( 0.845801f, 0.156091f, -0.510153f ), + Point3F( 0.829801f, -0.029340f, 0.557287f ), + Point3F( 0.259529f, 0.416263f, 0.871418f ), + Point3F( 0.231128f, -0.845982f, 0.480515f ), + Point3F( -0.626203f, -0.646168f, 0.436277f ), + Point3F( -0.197047f, -0.065791f, 0.978184f ), + Point3F( -0.255692f, -0.637488f, -0.726794f ), + Point3F( 0.530662f, -0.844385f, -0.073567f ), + Point3F( -0.779887f, 0.617067f, -0.104899f ), + Point3F( 0.739908f, 0.113984f, 0.662982f ), + Point3F( -0.218801f, 0.930194f, -0.294729f ), + Point3F( -0.374231f, 0.818666f, 0.435589f ), + Point3F( -0.720250f, -0.028285f, 0.693137f ), + Point3F( 0.075389f, 0.415049f, 0.906670f ), + Point3F( -0.539724f, -0.106620f, 0.835063f ), + Point3F( -0.452612f, -0.754669f, -0.474991f ), + Point3F( 0.682822f, 0.581234f, -0.442629f ), + Point3F( 0.002435f, -0.618462f, -0.785811f ), + Point3F( -0.397631f, 0.110766f, -0.910835f ), + Point3F( 0.133935f, -0.985438f, 0.104754f ), + Point3F( 0.759098f, -0.608004f, 0.232595f ), + Point3F( -0.825239f, -0.256087f, 0.503388f ), + Point3F( 0.101693f, -0.565568f, 0.818408f ), + Point3F( 0.386377f, 0.793546f, -0.470104f ), + Point3F( -0.520516f, -0.840690f, 0.149346f ), + Point3F( -0.784549f, -0.479672f, 0.392935f ), + Point3F( -0.325322f, -0.927581f, -0.183735f ), + Point3F( -0.069294f, -0.428541f, 0.900861f ), + Point3F( 0.993354f, -0.115023f, -0.004288f ), + Point3F( -0.123896f, -0.700568f, 0.702747f ), + Point3F( -0.438031f, -0.120880f, -0.890795f ), + Point3F( 0.063314f, 0.813233f, 0.578484f ), + Point3F( 0.322045f, 0.889086f, -0.325289f ), + Point3F( -0.133521f, 0.875063f, -0.465228f ), + Point3F( 0.637155f, 0.564814f, 0.524422f ), + Point3F( 0.260092f, -0.669353f, 0.695930f ), + Point3F( 0.953195f, 0.040485f, -0.299634f ), + Point3F( -0.840665f, -0.076509f, 0.536124f ), + Point3F( -0.971350f, 0.202093f, 0.125047f ), + Point3F( -0.804307f, -0.396312f, -0.442749f ), + Point3F( -0.936746f, 0.069572f, 0.343027f ), + Point3F( 0.426545f, -0.465962f, 0.775202f ), + Point3F( 0.794542f, -0.227450f, 0.563000f ), + Point3F( -0.892172f, 0.091169f, -0.442399f ), + Point3F( -0.312654f, 0.541264f, 0.780564f ), + Point3F( 0.590603f, -0.735618f, -0.331743f ), + Point3F( -0.098040f, -0.986713f, 0.129558f ), + Point3F( 0.569646f, 0.283078f, -0.771603f ), + Point3F( 0.431051f, -0.407385f, -0.805129f ), + Point3F( -0.162087f, -0.938749f, -0.304104f ), + Point3F( 0.241533f, -0.359509f, 0.901341f ), + Point3F( -0.576191f, 0.614939f, 0.538380f ), + Point3F( -0.025110f, 0.085740f, 0.996001f ), + Point3F( -0.352693f, -0.198168f, 0.914515f ), + Point3F( -0.604577f, 0.700711f, 0.378802f ), + Point3F( 0.465024f, 0.409667f, 0.784809f ), + Point3F( -0.254684f, -0.030474f, -0.966544f ), + Point3F( -0.604789f, 0.791809f, 0.085259f ), + Point3F( -0.705147f, -0.399298f, 0.585943f ), + Point3F( 0.185691f, 0.017236f, -0.982457f ), + Point3F( 0.044588f, 0.973094f, 0.226052f ), + Point3F( -0.405463f, 0.642367f, 0.650357f ), + Point3F( -0.563959f, 0.599136f, -0.568319f ), + Point3F( 0.367162f, -0.072253f, -0.927347f ), + Point3F( 0.960429f, -0.213570f, -0.178783f ), + Point3F( -0.192629f, 0.906005f, 0.376893f ), + Point3F( -0.199718f, -0.359865f, -0.911378f ), + Point3F( 0.485072f, 0.121233f, -0.866030f ), + Point3F( 0.467163f, -0.874294f, 0.131792f ), + Point3F( -0.638953f, -0.716603f, 0.279677f ), + Point3F( -0.622710f, 0.047813f, -0.780990f ), + Point3F( 0.828724f, -0.054433f, -0.557004f ), + Point3F( 0.130241f, 0.991080f, 0.028245f ), + Point3F( 0.310995f, -0.950076f, -0.025242f ), + Point3F( 0.818118f, 0.275336f, 0.504850f ), + Point3F( 0.676328f, 0.387023f, 0.626733f ), + Point3F( -0.100433f, 0.495114f, -0.863004f ), + Point3F( -0.949609f, -0.240681f, -0.200786f ), + Point3F( -0.102610f, 0.261831f, -0.959644f ), + Point3F( -0.845732f, -0.493136f, 0.203850f ), + Point3F( 0.672617f, -0.738838f, 0.041290f ), + Point3F( 0.380465f, 0.875938f, 0.296613f ), + Point3F( -0.811223f, 0.262027f, -0.522742f ), + Point3F( -0.074423f, -0.775670f, -0.626736f ), + Point3F( -0.286499f, 0.755850f, -0.588735f ), + Point3F( 0.291182f, -0.276189f, -0.915933f ), + Point3F( -0.638117f, 0.199338f, 0.743687f ), + Point3F( 0.439922f, -0.864433f, -0.243359f ), + Point3F( 0.177649f, 0.206919f, 0.962094f ), + Point3F( 0.277107f, 0.948521f, 0.153361f ), + Point3F( 0.507629f, 0.661918f, -0.551523f ), + Point3F( -0.503110f, -0.579308f, -0.641313f ), + Point3F( 0.600522f, 0.736495f, -0.311364f ), + Point3F( -0.691096f, -0.715301f, -0.103592f ), + Point3F( -0.041083f, -0.858497f, 0.511171f ), + Point3F( 0.207773f, -0.480062f, -0.852274f ), + Point3F( 0.795719f, 0.464614f, 0.388543f ), + Point3F( -0.100433f, 0.495114f, -0.863004f ), + Point3F( 0.703249f, 0.065157f, -0.707951f ), + Point3F( -0.324171f, -0.941112f, 0.096024f ), + Point3F( -0.134933f, -0.940212f, 0.312722f ), + Point3F( -0.438240f, 0.752088f, -0.492249f ), + Point3F( 0.964762f, -0.198855f, 0.172311f ), + Point3F( -0.831799f, 0.196807f, 0.519015f ), + Point3F( -0.508008f, 0.819902f, 0.263986f ), + Point3F( 0.471075f, -0.001146f, 0.882092f ), + Point3F( 0.919512f, 0.246162f, -0.306435f ), + Point3F( -0.960050f, 0.279828f, -0.001187f ), + Point3F( 0.110232f, -0.847535f, -0.519165f ), + Point3F( 0.208229f, 0.697360f, 0.685806f ), + Point3F( -0.199680f, -0.560621f, 0.803637f ), + Point3F( 0.170135f, -0.679985f, -0.713214f ), + Point3F( 0.758371f, -0.494907f, 0.424195f ), + Point3F( 0.077734f, -0.755978f, 0.649965f ), + Point3F( 0.612831f, -0.672475f, 0.414987f ), + Point3F( 0.142776f, 0.836698f, -0.528726f ), + Point3F( -0.765185f, 0.635778f, 0.101382f ), + Point3F( 0.669873f, -0.419737f, 0.612447f ), + Point3F( 0.593549f, 0.194879f, 0.780847f ), + Point3F( 0.646930f, 0.752173f, 0.125368f ), + Point3F( 0.837721f, 0.545266f, -0.030127f ), + Point3F( 0.541505f, 0.768070f, 0.341820f ), + Point3F( 0.760679f, -0.365715f, -0.536301f ), + Point3F( 0.381516f, 0.640377f, 0.666605f ), + Point3F( 0.565794f, -0.072415f, -0.821361f ), + Point3F( -0.466072f, -0.401588f, 0.788356f ), + Point3F( 0.987146f, 0.096290f, 0.127560f ), + Point3F( 0.509709f, -0.688886f, -0.515396f ), + Point3F( -0.135132f, -0.988046f, -0.074192f ), + Point3F( 0.600499f, 0.476471f, -0.642166f ), + Point3F( -0.732326f, -0.275320f, -0.622815f ), + Point3F( -0.881141f, -0.470404f, 0.048078f ), + Point3F( 0.051548f, 0.601042f, 0.797553f ), + Point3F( 0.402027f, -0.763183f, 0.505891f ), + Point3F( 0.404233f, -0.208288f, 0.890624f ), + Point3F( -0.311793f, 0.343843f, 0.885752f ), + Point3F( 0.098132f, -0.937014f, 0.335223f ), + Point3F( 0.537158f, 0.830585f, -0.146936f ), + Point3F( 0.725277f, 0.298172f, -0.620538f ), + Point3F( -0.882025f, 0.342976f, -0.323110f ), + Point3F( -0.668829f, 0.424296f, -0.610443f ), + Point3F( -0.408835f, -0.476442f, -0.778368f ), + Point3F( 0.809472f, 0.397249f, -0.432375f ), + Point3F( -0.909184f, -0.205938f, -0.361903f ), + Point3F( 0.866930f, -0.347934f, -0.356895f ), + Point3F( 0.911660f, -0.141281f, -0.385897f ), + Point3F( -0.431404f, -0.844074f, -0.318480f ), + Point3F( -0.950593f, -0.073496f, 0.301614f ), + Point3F( -0.719716f, 0.626915f, -0.298305f ), + Point3F( -0.779887f, 0.617067f, -0.104899f ), + Point3F( -0.475899f, -0.542630f, 0.692151f ), + Point3F( 0.081952f, -0.157248f, -0.984153f ), + Point3F( 0.923990f, -0.381662f, -0.024025f ), + Point3F( -0.957998f, 0.120979f, -0.260008f ), + Point3F( 0.306601f, 0.227975f, -0.924134f ), + Point3F( -0.141244f, 0.989182f, 0.039601f ), + Point3F( 0.077097f, 0.186288f, -0.979466f ), + Point3F( -0.630407f, -0.259801f, 0.731499f ), + Point3F( 0.718150f, 0.637408f, 0.279233f ), + Point3F( 0.340946f, 0.110494f, 0.933567f ), + Point3F( -0.396671f, 0.503020f, -0.767869f ), + Point3F( 0.636943f, -0.245005f, 0.730942f ), + Point3F( -0.849605f, -0.518660f, -0.095724f ), + Point3F( -0.388203f, 0.105395f, 0.915527f ), + Point3F( -0.280671f, -0.776541f, -0.564099f ), + Point3F( -0.601680f, 0.215451f, -0.769131f ), + Point3F( -0.660112f, -0.632371f, -0.405412f ), + Point3F( 0.921096f, 0.284072f, 0.266242f ), + Point3F( 0.074850f, -0.300846f, 0.950731f ), + Point3F( 0.943952f, -0.067062f, 0.323198f ), + Point3F( -0.917838f, -0.254589f, 0.304561f ), + Point3F( 0.889843f, -0.409008f, 0.202219f ), + Point3F( -0.565849f, 0.753721f, -0.334246f ), + Point3F( 0.791460f, 0.555918f, -0.254060f ), + Point3F( 0.261936f, 0.703590f, -0.660568f ), + Point3F( -0.234406f, 0.952084f, 0.196444f ), + Point3F( 0.111205f, 0.979492f, -0.168014f ), + Point3F( -0.869844f, -0.109095f, -0.481113f ), + Point3F( -0.337728f, -0.269701f, -0.901777f ), + Point3F( 0.366793f, 0.408875f, -0.835634f ), + Point3F( -0.098749f, 0.261316f, 0.960189f ), + Point3F( -0.272379f, -0.847100f, 0.456324f ), + Point3F( -0.319506f, 0.287444f, -0.902935f ), + Point3F( 0.873383f, -0.294109f, 0.388203f ), + Point3F( -0.088950f, 0.710450f, 0.698104f ), + Point3F( 0.551238f, -0.786552f, 0.278340f ), + Point3F( 0.724436f, -0.663575f, -0.186712f ), + Point3F( 0.529741f, -0.606539f, 0.592861f ), + Point3F( -0.949743f, -0.282514f, 0.134809f ), + Point3F( 0.155047f, 0.419442f, -0.894443f ), + Point3F( -0.562653f, -0.329139f, -0.758346f ), + Point3F( 0.816407f, -0.576953f, 0.024576f ), + Point3F( 0.178550f, -0.950242f, -0.255266f ), + Point3F( 0.479571f, 0.706691f, 0.520192f ), + Point3F( 0.391687f, 0.559884f, -0.730145f ), + Point3F( 0.724872f, -0.205570f, -0.657496f ), + Point3F( -0.663196f, -0.517587f, -0.540624f ), + Point3F( -0.660054f, -0.122486f, -0.741165f ), + Point3F( -0.531989f, 0.374711f, -0.759328f ), + Point3F( 0.194979f, -0.059120f, 0.979024f ) +}; + +U8 TSMesh::encodeNormal(const Point3F & normal) +{ + U8 bestIndex=0; + F32 bestDot=-10E30f; + for (U32 i=0; i<256; i++) + { + F32 dot = mDot(normal,smU8ToNormalTable[i]); + if (dot>bestDot) + { + bestIndex = i; + bestDot = dot; + } + } + return bestIndex; +} + +//----------------------------------------------------- +// TSMesh assemble from/ dissemble to memory buffer +//----------------------------------------------------- + +#define alloc TSShape::alloc + +TSMesh * TSMesh::assembleMesh(U32 meshType, bool skip) +{ + static TSMesh tempStandardMesh; + static TSSkinMesh tempSkinMesh; + static TSDecalMesh tempDecalMesh; + static TSSortedMesh tempSortedMesh; + + bool justSize = skip || !alloc.allocShape32(0); // if this returns NULL, we're just sizing memory block + + // a little funny business because we pretend decals are derived from meshes + S32 * ret = NULL; + TSMesh * mesh = NULL; + TSDecalMesh * decal = NULL; + + if (justSize) + { + switch (meshType) + { + case StandardMeshType : + { + ret = (S32*)&tempStandardMesh; + mesh = &tempStandardMesh; + alloc.allocShape32(sizeof(TSMesh)>>2); + break; + } + case SkinMeshType : + { + ret = (S32*)&tempSkinMesh; + mesh = &tempSkinMesh; + alloc.allocShape32(sizeof(TSSkinMesh)>>2); + break; + } + case DecalMeshType : + { + ret = (S32*)&tempDecalMesh; + decal = &tempDecalMesh; + alloc.allocShape32(sizeof(TSDecalMesh)>>2); + break; + } + case SortedMeshType : + { + ret = (S32*)&tempSortedMesh; + mesh = &tempSortedMesh; + alloc.allocShape32(sizeof(TSSortedMesh)>>2); + break; + } + } + } + else + { + switch (meshType) + { + case StandardMeshType : + { + ret = alloc.allocShape32(sizeof(TSMesh)>>2); + constructInPlace((TSMesh*)ret); + mesh = (TSMesh*)ret; + break; + } + case SkinMeshType : + { + ret = alloc.allocShape32(sizeof(TSSkinMesh)>>2); + constructInPlace((TSSkinMesh*)ret); + mesh = (TSSkinMesh*)ret; + break; + } + case DecalMeshType : + { + ret = alloc.allocShape32(sizeof(TSDecalMesh)>>2); + constructInPlace((TSDecalMesh*)ret); + decal = (TSDecalMesh*)ret; + break; + } + case SortedMeshType : + { + ret = alloc.allocShape32(sizeof(TSSortedMesh)>>2); + constructInPlace((TSSortedMesh*)ret); + mesh = (TSSortedMesh*)ret; + break; + } + } + } + + alloc.setSkipMode(skip); + + if (mesh) + mesh->assemble(skip); + if (decal) + decal->assemble(skip); + + alloc.setSkipMode(false); + + return (TSMesh*)ret; +} + +void TSMesh::convertToTris(S16 * primitiveDataIn, S32 * primitiveMatIn, S16 * indicesIn, + S32 numPrimIn, S32 & numPrimOut, S32 & numIndicesOut, + S32 * primitivesOut, S16 * indicesOut) +{ + S32 prevMaterial = -99999; + TSDrawPrimitive * newDraw = NULL; + numPrimOut=0; + numIndicesOut=0; + for (S32 i=0; istart = numIndicesOut; + newDraw->numElements = 0; + newDraw->matIndex = newMat | TSDrawPrimitive::Triangles; + } + numPrimOut++; + prevMaterial = newMat; + } + U16 start = primitiveDataIn[i*2]; + U16 numElements = primitiveDataIn[i*2+1]; + + // gonna depend on what kind of primitive it is... + if ( (primitiveMatIn[i] & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles) + { + for (S32 j=0; jnumElements += 3; + numIndicesOut += 3; + } + } + else + { + U32 idx0 = indicesIn[start + 0]; + U32 idx1; + U32 idx2 = indicesIn[start + 1]; + U32 * nextIdx = &idx1; + for (S32 j=2; jnumElements += 3; + numIndicesOut += 3; + } + } + } +} + +void unwindStrip(S16 * indices, S32 numElements, Vector & triIndices) +{ + U32 idx0 = indices[0]; + U32 idx1; + U32 idx2 = indices[1]; + U32 * nextIdx = &idx1; + for (S32 j=2; j triIndices; + S32 curDrawOut = 0; + numPrimOut=0; + numIndicesOut=0; + for (S32 i=0; istart = numIndicesOut; + newTris->numElements = triIndices.size(); + dMemcpy(&indicesOut[numIndicesOut],triIndices.address(),triIndices.size()*sizeof(U16)); + } + numIndicesOut += triIndices.size(); + triIndices.clear(); + newTris = NULL; + } + + if (primitivesOut) + { + newDraw = (TSDrawPrimitive*) &primitivesOut[numPrimOut*2]; + newDraw->start = numIndicesOut; + newDraw->numElements = 0; + newDraw->matIndex = newMat; + } + numPrimOut++; + curDrawOut = 0; + prevMaterial = newMat; + } + U16 start = primitiveDataIn[i*2]; + U16 numElements = primitiveDataIn[i*2+1]; + + // gonna depend on what kind of primitive it is... + // from above we know it's the same kind as the one we're building... + if ( (primitiveMatIn[i] & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles) + { + // triangles primitive...add to it + for (S32 j=0; jnumElements += 3; + numIndicesOut += 3; + } + } + else + { + // strip primitive... + // if numElements less than smSmallestStripSize, add to triangles... + if (numElementsmatIndex = newMat; + newTris->matIndex &= ~(TSDrawPrimitive::Triangles|TSDrawPrimitive::Strip); + newTris->matIndex |= TSDrawPrimitive::Triangles; + } + numPrimOut++; + } + unwindStrip(indicesIn+start,numElements,triIndices); + } + else + { + // strip primitive...add to it + if (indicesOut) + { + if (curDrawOut&1) + { + indicesOut[numIndicesOut+0] = indicesOut[numIndicesOut-1]; + indicesOut[numIndicesOut+1] = indicesOut[numIndicesOut-1]; + indicesOut[numIndicesOut+2] = indicesIn[start]; + dMemcpy(indicesOut+numIndicesOut+3,indicesIn+start,2*numElements); + } + else if (curDrawOut) + { + indicesOut[numIndicesOut+0] = indicesOut[numIndicesOut-1]; + indicesOut[numIndicesOut+1] = indicesIn[start]; + dMemcpy(indicesOut+numIndicesOut+2,indicesIn+start,2*numElements); + } + else + dMemcpy(indicesOut+numIndicesOut,indicesIn+start,2*numElements); + } + S32 added = numElements; + added += curDrawOut ? (curDrawOut&1 ? 3 : 2) : 0; + if (newDraw) + newDraw->numElements += added; + numIndicesOut += added; + curDrawOut += added; + } + } + } + // spit out tris before leaving + // before adding the new primitive, transfer triangle indices + if (triIndices.size()) + { + if (newTris && indicesOut) + { + newTris->start = numIndicesOut; + newTris->numElements = triIndices.size(); + dMemcpy(&indicesOut[numIndicesOut],triIndices.address(),triIndices.size()*sizeof(U16)); + } + numIndicesOut += triIndices.size(); + triIndices.clear(); + newTris = NULL; + } +} + +// this method does none of the converting that the above methods do, except that small strips are converted +// to triangle lists... +void TSMesh::leaveAsMultipleStrips(S16 * primitiveDataIn, S32 * primitiveMatIn, S16 * indicesIn, S32 numPrimIn, + S32 & numPrimOut, S32 & numIndicesOut, + S32 * primitivesOut, S16 * indicesOut) +{ + S32 prevMaterial = -99999; + TSDrawPrimitive * newDraw = NULL; + Vector triIndices; + numPrimOut=0; + numIndicesOut=0; + for (S32 i=0; imatIndex = prevMaterial; + newTris->matIndex &= ~(TSDrawPrimitive::Triangles|TSDrawPrimitive::Strip); + newTris->matIndex |= TSDrawPrimitive::Triangles; + newTris->start = numIndicesOut; + newTris->numElements = triIndices.size(); + dMemcpy(&indicesOut[numIndicesOut],triIndices.address(),triIndices.size()*sizeof(U16)); + } + numPrimOut++; + numIndicesOut += triIndices.size(); + triIndices.clear(); + } + + // this is a little convoluted because this code was adapted from convertToSingleStrip + // but we will need a new primitive only if it is a triangle primitive coming in + // or we have more elements than the min strip size... + if ( (primitiveMatIn[i] & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles || numElements>=smMinStripSize+2) + { + if (primitivesOut) + { + newDraw = (TSDrawPrimitive*) &primitivesOut[numPrimOut*2]; + newDraw->start = numIndicesOut; + newDraw->numElements = 0; + newDraw->matIndex = newMat; + } + numPrimOut++; + } + prevMaterial = newMat; + + // gonna depend on what kind of primitive it is... + // from above we know it's the same kind as the one we're building... + if ( (primitiveMatIn[i] & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles) + { + // triangles primitive...add to it + for (S32 j=0; jnumElements += 3; + numIndicesOut += 3; + } + } + else + { + // strip primitive... + // if numElements less than smSmallestStripSize, add to triangles... + if (numElementsnumElements = numElements; + numIndicesOut += numElements; + } + } + } + // spit out tris before leaving + if (triIndices.size()) + { + // material just changed and we have triangles lying around + // add primitive and indices for triangles and clear triIndices + if (indicesOut) + { + TSDrawPrimitive * newTris = (TSDrawPrimitive*) &primitivesOut[numPrimOut*2]; + newTris->matIndex = prevMaterial; + newTris->matIndex &= ~(TSDrawPrimitive::Triangles|TSDrawPrimitive::Strip); + newTris->matIndex |= TSDrawPrimitive::Triangles; + newTris->start = numIndicesOut; + newTris->numElements = triIndices.size(); + dMemcpy(&indicesOut[numIndicesOut],triIndices.address(),triIndices.size()*sizeof(U16)); + } + numPrimOut++; + numIndicesOut += triIndices.size(); + triIndices.clear(); + } +} + +// This method retrieves data that is shared (or possibly shared) between different meshes. +// This adds an extra step to the copying of data from the memory buffer to the shape data buffer. +// If we have no parentMesh, then we either return a pointer to the data in the memory buffer +// (in the case that we skip this mesh) or copy the data into the shape data buffer and return +// that pointer (in the case that we don't skip this mesh). +// If we do have a parent mesh, then we return a pointer to the data in the shape buffer, +// copying the data in there ourselves if our parent didn't already do it (i.e., if it was skipped). +S32 * TSMesh::getSharedData32(S32 parentMesh, S32 size, S32 ** source, bool skip) +{ + S32 * ptr; + if(parentMesh<0) + ptr = skip ? alloc.getPointer32(size) : alloc.copyToShape32(size); + else + { + ptr = source[parentMesh]; + // if we skipped the previous mesh (and we're not skipping this one) then + // we still need to copy points into the shape... + if (!smDataCopied[parentMesh] && !skip) + { + S32 * tmp = ptr; + ptr = alloc.allocShape32(size); + if (ptr && tmp) + dMemcpy(ptr,tmp,size*sizeof(S32)); + } + } + return ptr; +} + +S8 * TSMesh::getSharedData8(S32 parentMesh, S32 size, S8 ** source, bool skip) +{ + S8 * ptr; + if(parentMesh<0) + ptr = skip ? alloc.getPointer8(size) : alloc.copyToShape8(size); + else + { + ptr = source[parentMesh]; + // if we skipped the previous mesh (and we're not skipping this one) then + // we still need to copy points into the shape... + if (!smDataCopied[parentMesh] && !skip) + { + S8 * tmp = ptr; + ptr = alloc.allocShape8(size); + if (ptr && tmp) + dMemcpy(ptr,tmp,size*sizeof(S32)); + } + } + return ptr; +} + +void TSMesh::assemble(bool skip) +{ + alloc.checkGuard(); + + numFrames = alloc.get32(); + numMatFrames = alloc.get32(); + parentMesh = alloc.get32(); + alloc.get32((S32*)&mBounds,6); + alloc.get32((S32*)&mCenter,3); + mRadius = (F32)alloc.get32(); + + S32 numVerts = alloc.get32(); + S32 * ptr32 = getSharedData32(parentMesh,3*numVerts,(S32**)smVertsList.address(),skip); + verts.set((Point3F*)ptr32,numVerts); + + S32 numTVerts = alloc.get32(); + ptr32 = getSharedData32(parentMesh,2*numTVerts,(S32**)smTVertsList.address(),skip); + tverts.set((Point2F*)ptr32,numTVerts); + + S8 * ptr8; + if (TSShape::smReadVersion>21 && TSMesh::smUseEncodedNormals) + { + // we have encoded normals and we want to use them... + + if (parentMesh<0) + alloc.getPointer32(numVerts*3); // advance past norms, don't use + norms.set(NULL,0); + + ptr8 = getSharedData8(parentMesh,numVerts,(S8**)smEncodedNormsList.address(),skip); + encodedNorms.set(ptr8,numVerts); + } + else if (TSShape::smReadVersion>21) + { + // we have encoded normals but we don't want to use them... + + ptr32 = getSharedData32(parentMesh,3*numVerts,(S32**)smNormsList.address(),skip); + norms.set((Point3F*)ptr32,numVerts); + + if (parentMesh<0) + alloc.getPointer8(numVerts); // advance past encoded normls, don't use + encodedNorms.set(NULL,0); + } + else + { + // no encoded normals... + + ptr32 = getSharedData32(parentMesh,3*numVerts,(S32**)smNormsList.address(),skip); + norms.set((Point3F*)ptr32,numVerts); + encodedNorms.set(NULL,0); + } + + // copy the primitives and indices...how we do this depends on what + // form we want them in when copied...just get pointers to data for now + S32 szPrim = alloc.get32(); + S16 * prim16 = alloc.getPointer16(szPrim*2); + S32 * prim32 = alloc.getPointer32(szPrim); + S32 szInd = alloc.get32(); + S16 * ind16 = alloc.getPointer16(szInd); + + // count then copy... + S32 cpyPrim = szPrim, cpyInd = szInd; + if (smUseTriangles) + convertToTris(prim16,prim32,ind16,szPrim,cpyPrim,cpyInd,NULL,NULL); + else if (smUseOneStrip) + convertToSingleStrip(prim16,prim32,ind16,szPrim,cpyPrim,cpyInd,NULL,NULL); + else + leaveAsMultipleStrips(prim16,prim32,ind16,szPrim,cpyPrim,cpyInd,NULL,NULL); + ptr32 = alloc.allocShape32(2*cpyPrim); + S16 * ptr16 = alloc.allocShape16(cpyInd); + alloc.align32(); + S32 chkPrim = szPrim, chkInd = szInd; + if (smUseTriangles) + convertToTris(prim16,prim32,ind16,szPrim,chkPrim,chkInd,ptr32,ptr16); + else if (smUseOneStrip) + convertToSingleStrip(prim16,prim32,ind16,szPrim,chkPrim,chkInd,ptr32,ptr16); + else + leaveAsMultipleStrips(prim16,prim32,ind16,szPrim,chkPrim,chkInd,ptr32,ptr16); + AssertFatal(chkPrim==cpyPrim && chkInd==cpyInd,"TSMesh::primitive conversion"); + primitives.set(ptr32,cpyPrim); + indices.set(ptr16,cpyInd); + + S32 sz = alloc.get32(); + ptr16 = alloc.copyToShape16(sz); + alloc.align32(); + mergeIndices.set(ptr16,sz); + + vertsPerFrame = alloc.get32(); + U32 flags = (U32)alloc.get32(); + if (encodedNorms.size()) + flags |= UseEncodedNormals; + setFlags(flags); + + alloc.checkGuard(); + + if (alloc.allocShape32(0) && TSShape::smReadVersion<19) + // only do this if we copied the data... + computeBounds(); +} + +void TSMesh::disassemble() +{ + alloc.setGuard(); + + alloc.set32(numFrames); + alloc.set32(numMatFrames); + alloc.set32(parentMesh); + alloc.copyToBuffer32((S32*)&mBounds,6); + alloc.copyToBuffer32((S32*)&mCenter,3); + alloc.set32((S32)mRadius); + + // verts... + alloc.set32(verts.size()); + if (parentMesh<0) + // if no parent mesh, then save off our verts + alloc.copyToBuffer32((S32*)verts.address(),3*verts.size()); + + // tverts... + alloc.set32(tverts.size()); + if (parentMesh<0) + // if no parent mesh, then save off our tverts + alloc.copyToBuffer32((S32*)tverts.address(),2*tverts.size()); + + // norms... + if (parentMesh<0) + // if no parent mesh, then save off our norms + alloc.copyToBuffer32((S32*)norms.address(),3*norms.size()); // norms.size()==verts.size() or error... + + // encoded norms... + if (parentMesh<0) + { + // if no parent mesh, compute encoded normals and copy over + for (S32 i=0; i21 && TSMesh::smUseEncodedNormals) + { + // we have encoded normals and we want to use them... + if (parentMesh<0) + alloc.getPointer32(numVerts*3); // advance past norms, don't use + initialNorms.set(NULL,0); + + ptr8 = getSharedData8(parentMesh,numVerts,(S8**)smEncodedNormsList.address(),skip); + encodedNorms.set(ptr8,numVerts); + // Note: we don't set the encoded normals flag because we handle them in updateSkin and + // hide the fact that we are using them from base class (TSMesh) + } + else if (TSShape::smReadVersion>21) + { + // we have encoded normals but we don't want to use them... + ptr32 = getSharedData32(parentMesh,3*numVerts,(S32**)smNormsList.address(),skip); + initialNorms.set((Point3F*)ptr32,numVerts); + + if (parentMesh<0) + alloc.getPointer8(numVerts); // advance past encoded normls, don't use + encodedNorms.set(NULL,0); + } + else + { + // no encoded normals... + ptr32 = getSharedData32(parentMesh,3*numVerts,(S32**)smNormsList.address(),skip); + initialNorms.set((Point3F*)ptr32,numVerts); + encodedNorms.set(NULL,0); + } + + sz = alloc.get32(); + ptr32 = getSharedData32(parentMesh,16*sz,(S32**)smInitTransformList.address(),skip); + initialTransforms.set(ptr32,sz); + + sz = alloc.get32(); + ptr32 = getSharedData32(parentMesh,sz,(S32**)smVertexIndexList.address(),skip); + vertexIndex.set(ptr32,sz); + + ptr32 = getSharedData32(parentMesh,sz,(S32**)smBoneIndexList.address(),skip); + boneIndex.set(ptr32,sz); + + ptr32 = getSharedData32(parentMesh,sz,(S32**)smWeightList.address(),skip); + weight.set((F32*)ptr32,sz); + + sz = alloc.get32(); + ptr32 = getSharedData32(parentMesh,sz,(S32**)smNodeIndexList.address(),skip); + nodeIndex.set(ptr32,sz); + + alloc.checkGuard(); + + if (alloc.allocShape32(0) && TSShape::smReadVersion<19) + // only do this if we copied the data... + TSMesh::computeBounds(); +} + +void TSSkinMesh::disassemble() +{ + TSMesh::disassemble(); + + alloc.set32(initialVerts.size()); + // if we have no parent mesh, then save off our verts & norms + if (parentMesh<0) + { + alloc.copyToBuffer32((S32*)initialVerts.address(),3*initialVerts.size()); + + // no longer do this here...let tsmesh handle this + alloc.copyToBuffer32((S32*)initialNorms.address(),3*initialNorms.size()); + + // if no parent mesh, compute encoded normals and copy over + for (S32 i=0; i class ToolVector +{ + public: + A * addr; + S32 sz; + S32 size() const { return sz; } + bool empty() const { return sz==0; } + A & operator[](S32 idx) { return addr[idx]; } + A const & operator[](S32 idx) const { return addr[idx]; } + A * address() { return addr; } + void set(void * _addr, S32 _sz) { addr = (A*)_addr; sz = _sz; } +}; +#endif + +class TSMaterialList; +class TSShapeInstance; +struct RayInfo; +class ConvexFeature; + +struct TSDrawPrimitive +{ + enum + { + Triangles = 0 << 30, // bits 30 and 31 index element type + Strip = 1 << 30, // bits 30 and 31 index element type + Fan = 2 << 30, // bits 30 and 31 index element type + Indexed = 1 << 29, // use glDrawElements if indexed, glDrawArrays o.w. + NoMaterial = 1 << 28, // set if no material (i.e., texture missing) + MaterialMask = ~(Strip|Fan|Triangles|Indexed|NoMaterial), + TypeMask = Strip|Fan|Triangles + }; + + S16 start; + S16 numElements; + S32 matIndex; // holds material index & element type (see above enum) +}; + +class TSMesh +{ + protected: + U32 meshType; + Box3F mBounds; + Point3F mCenter; + F32 mRadius; + + static F32 overrideFadeVal; + + public: + + enum + { + // types... + StandardMeshType = 0, + SkinMeshType = 1, + DecalMeshType = 2, + SortedMeshType = 3, + NullMeshType = 4, + TypeMask = StandardMeshType|SkinMeshType|DecalMeshType|SortedMeshType|NullMeshType, + + // flags (stored with meshType)... + Billboard = 1 << 31, HasDetailTexture = 1 << 30, + BillboardZAxis = 1 << 29, UseEncodedNormals = 1 << 28, + FlagMask = Billboard|BillboardZAxis|HasDetailTexture|UseEncodedNormals + }; + + U32 getMeshType() { return meshType & TypeMask; } + void setFlags(U32 flag) { meshType |= flag; } + void clearFlags(U32 flag) { meshType &= ~flag; } + U32 getFlags(U32 flag = 0xFFFFFFFF) { return meshType & flag; } + + const Point3F * getNormals(S32 firstVert); + + S32 parentMesh; // index into shapes mesh list + S32 numFrames; + S32 numMatFrames; + S32 vertsPerFrame; + + ToolVector verts; + ToolVector norms; + ToolVector tverts; + ToolVector primitives; + ToolVector encodedNorms; + ToolVector indices; + ToolVector mergeIndices; // the last so many verts merge with these + // verts to form the next detail level + // NOT IMPLEMENTED YET + + // billboard data + Point3F billboardAxis; + + // convex hull data + Vector planeNormals; + Vector planeConstants; + Vector planeMaterials; + S32 planesPerFrame; + S32 vbOffset; + U32 mergeBufferStart; + + // render methods... + virtual void fillVB(S32 vb, S32 frame, S32 matFrame, TSMaterialList *materials); + virtual void morphVB(S32 vb, S32 morph, S32 frame, S32 matFrame, TSMaterialList *materials); + virtual void renderVB(S32 frame, S32 matFrame, TSMaterialList *materials); + virtual void render(S32 frame, S32 matFrame, TSMaterialList *); + void renderShadow(S32 frame, const MatrixF & mat, S32 dim, U32 * bits, TSMaterialList *); + void renderEnvironmentMap(S32 frame, S32 matFrame, TSMaterialList *); + void renderDetailMap(S32 frame, S32 matFrame, TSMaterialList *); + void renderFog(S32 frame, TSMaterialList* materials); + + // material methods... + static void initMaterials(); + static void resetMaterials(); + static void initEnvironmentMapMaterials(); + static void resetEnvironmentMapMaterials(); + static void initDetailMapMaterials(); + static void resetDetailMapMaterials(); + static void setMaterial(S32 matIndex, TSMaterialList *); + static void setFade(F32 fadeValue); + static void clearFade(); + static void setOverrideFade(F32 fadeValue){ overrideFadeVal = fadeValue; } + static F32 getOverrideFade(){ return overrideFadeVal; } + + // collision methods... + virtual bool buildPolyList(S32 frame, AbstractPolyList * polyList, U32 & surfaceKey); + virtual bool getFeatures(S32 frame, const MatrixF&, const VectorF&, ConvexFeature*, U32& surfaceKey); + virtual void support(S32 frame, const Point3F& v, F32* currMaxDP, Point3F* currSupport); + virtual bool castRay(S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo); + virtual bool buildConvexHull(); // returns false if not convex (still builds planes) + bool addToHull(U32 idx0, U32 idx1, U32 idx2); + + // calculate and get bounding information... + void computeBounds(); + virtual void computeBounds(MatrixF & transform, Box3F & bounds, S32 frame = 0, Point3F * center = NULL, F32 * radius = NULL); + void computeBounds(Point3F *, S32 numVerts, MatrixF & transform, Box3F & bounds, Point3F * center, F32 * radius); + Box3F & getBounds() { return mBounds; } + Point3F & getCenter() { return mCenter; } + F32 getRadius() { return mRadius; } + virtual S32 getNumPolys(); + + U8 encodeNormal(const Point3F & normal); + const Point3F & decodeNormal(U8 ncode); + + void saveMergeVerts(); // called by shapeinstance in setStatics + void restoreMergeVerts(); // called by shapeinstance in clearStatics + void saveMergeNormals(); // called by mesh at start of render (decals don't bother) + void restoreMergeNormals(); // called by mesh at end of render + + // persist methods... + virtual void assemble(bool skip); + static TSMesh * assembleMesh(U32 meshType, bool skip); + virtual void disassemble(); + + // on load...optionally convert primitives to other form + static bool smUseTriangles; + static bool smUseOneStrip; + static S32 smMinStripSize; + static bool smUseEncodedNormals; + + // convert primitives on load... + void convertToTris(S16 * primitiveDataIn, S32 * primitiveMatIn, S16 * indicesIn, + S32 numPrimIn, S32 & numPrimOut, S32 & numIndicesOut, + S32 * primitivesOut, S16 * indicesOut); + void convertToSingleStrip(S16 * primitiveDataIn, S32 * primitiveMatIn, S16 * indicesIn, + S32 numPrimIn, S32 & numPrimOut, S32 & numIndicesOut, + S32 * primitivesOut, S16 * indicesOut); + void leaveAsMultipleStrips(S16 * primitiveDataIn, S32 * primitiveMatIn, S16 * indicesIn, + S32 numPrimIn, S32 & numPrimOut, S32 & numIndicesOut, + S32 * primitivesOut, S16 * indicesOut); + + // methods used during assembly to share vertexand other info + // between meshes (and for skipping detail levels on load) + S32 * getSharedData32(S32 parentMesh, S32 size, S32 ** source, bool skip); + S8 * getSharedData8 (S32 parentMesh, S32 size, S8 ** source, bool skip); + + // variables used during assembly (for skipping mesh detail levels + // on load and for sharing verts between meshes) + static Vector smVertsList; + static Vector smNormsList; + static Vector smEncodedNormsList; + static Vector smTVertsList; + static Vector smDataCopied; + + static Vector smSaveVerts; + static Vector smSaveNorms; + static Vector smSaveTVerts; + + static const Point3F smU8ToNormalTable[]; + + + TSMesh() : meshType(StandardMeshType) { + VECTOR_SET_ASSOCIATION(planeNormals); + VECTOR_SET_ASSOCIATION(planeConstants); + VECTOR_SET_ASSOCIATION(planeMaterials); + parentMesh = -1; + } + virtual ~TSMesh(); +}; + +inline const Point3F & TSMesh::decodeNormal(U8 ncode) { return smU8ToNormalTable[ncode]; } + +class TSSkinMesh : public TSMesh +{ +public: + typedef TSMesh Parent; + + // vectors that define the vertex, weight, bone tuples + ToolVector weight; + ToolVector boneIndex; + ToolVector vertexIndex; + + // vectors indexed by bone number + ToolVector nodeIndex; + ToolVector initialTransforms; + + // initial values of verts and normals + // these get transformed into initial bone space, + // from there into world space relative to current bone + // pos, and then weighted by bone weights... + ToolVector initialVerts; + ToolVector initialNorms; + + // set verts and normals... + void updateSkin(); + + // render methods.. + void render(S32 frame, S32 matFrame, TSMaterialList *); + + // collision methods... + bool buildPolyList(S32 frame, AbstractPolyList * polyList, U32 & surfaceKey); + bool castRay(S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo); + bool buildConvexHull(); // does nothing, skins don't use this + + void computeBounds(MatrixF & transform, Box3F & bounds, S32 frame, Point3F * center, F32 * radius); + + // persist methods... + void assemble(bool skip); + void disassemble(); + + // variables used during assembly (for skipping mesh detail levels + // on load and for sharing verts between meshes) + static Vector smInitTransformList; + static Vector smVertexIndexList; + static Vector smBoneIndexList; + static Vector smWeightList; + static Vector smNodeIndexList; + + TSSkinMesh() { meshType = SkinMeshType; } +}; + +#endif diff --git a/ts/tsPartInstance.cc b/ts/tsPartInstance.cc new file mode 100644 index 0000000..15fd070 --- /dev/null +++ b/ts/tsPartInstance.cc @@ -0,0 +1,476 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsPartInstance.h" +#include "dgl/dgl.h" +#include "Math/mMath.h" + +//------------------------------------------------------------------------------------- +// Constructors +//------------------------------------------------------------------------------------- + +MRandomR250 TSPartInstance::smRandom; + +TSPartInstance::TSPartInstance(TSShapeInstance * sourceShape) +{ + VECTOR_SET_ASSOCIATION(mMeshObjects); + VECTOR_SET_ASSOCIATION(mDecalObjects); + + init(sourceShape); +} + +TSPartInstance::TSPartInstance(TSShapeInstance * sourceShape, S32 objectIndex) +{ + init(sourceShape); + addObject(objectIndex); +} + +void TSPartInstance::init(TSShapeInstance * sourceShape) +{ + mSourceShape = sourceShape; + mSizeCutoffs = NULL; + mPolyCount = NULL; + mNumDetails = 0; + mCurrentObjectDetail = 0; + mCurrentIntraDL = 1.0f; + mData = 0; +} + +TSPartInstance::~TSPartInstance() +{ + delete [] mPolyCount; +} + +//------------------------------------------------------------------------------------- +// Methods for updating PartInstances +//------------------------------------------------------------------------------------- + +void TSPartInstance::addObject(S32 objectIndex) +{ + if (mSourceShape->mMeshObjects[objectIndex].visible<0.01f) + // not visible, don't bother + return; + + mMeshObjects.push_back(&mSourceShape->mMeshObjects[objectIndex]); + + // add any decals that are currently on? + S32 decalIndex = mSourceShape->getShape()->objects[objectIndex].firstDecal; + while (decalIndex>=0) + { + if (mSourceShape->mDecalObjects[decalIndex].frame>=0) + mDecalObjects.push_back(&mSourceShape->mDecalObjects[decalIndex]); + decalIndex = mSourceShape->mDecalObjects[decalIndex].decalObject->nextSibling; + } +} + +void TSPartInstance::updateBounds() +{ + mSourceShape->setStatics(); + + // run through meshes and brute force it? + Box3F bounds; + mBounds.min.set( 10E30, 10E30, 10E30); + mBounds.max.set(-10E30,-10E30,-10E30); + for (S32 i=0; igetMesh(0)) + mMeshObjects[i]->getMesh(0)->computeBounds(*mMeshObjects[i]->getTransform(),bounds,mMeshObjects[i]->frame); + mBounds.min.setMin(bounds.min); + mBounds.max.setMax(bounds.max); + } + mCenter = mBounds.min + mBounds.max; + mCenter *= 0.5f; + Point3F r = mBounds.max-mCenter; + mRadius = mSqrt(mDot(r,r)); +} + +//------------------------------------------------------------------------------------- +// Methods for breaking shapes into pieces +//------------------------------------------------------------------------------------- + +void TSPartInstance::breakShape(TSShapeInstance * shape, S32 subShape, Vector & partList, F32 * probShatter, F32 * probBreak, S32 probDepth) +{ + AssertFatal(subShape>=0 && subShapemShape->subShapeFirstNode.size(),"TSPartInstance::breakShape: subShape out of range."); + + S32 start = shape->mShape->subShapeFirstNode[subShape]; + + TSPartInstance::breakShape(shape, NULL, start, partList, probShatter, probBreak, probDepth); + + // update bounds (and get rid of empty parts) + for (S32 i=0; imMeshObjects.size()) + partList[i]->updateBounds(); + else + { + partList.erase(i); + i--; + } + } +} + +void TSPartInstance::breakShape(TSShapeInstance * shape, TSPartInstance * currentPart, S32 currentNode, Vector & partList, F32 * probShatter, F32 * probBreak, S32 probDepth) +{ + AssertFatal( !probDepth || (probShatter && probBreak),"TSPartInstance::breakShape: probabilities improperly specified."); + + const TSShape::Node * node = &shape->mShape->nodes[currentNode]; + S32 object = node->firstObject; + S32 child = node->firstChild; + + // copy off probabilities and update probability lists for next level + F32 ps = probShatter ? *probShatter : 1.0f; + F32 pb = probBreak ? *probBreak : 1.0f; + if (probDepth>1 && probShatter && probBreak) + { + probShatter++; + probBreak++; + probDepth--; + } + + // what to do...depending on how the die roll, we can: + // a) shatter the shape at this level -- meaning we make a part out of each object on this node and + // we make parts out of all the children (perhaps breaking them up further still) + // b) break the shape off at this level -- meaning we make a part out of the intact piece from here + // on down (again, we might break the result further as we iterate through the nodes...what breaking + // the shape really does is separate this piece from the parent piece). + // c) add this piece to the parent -- meaning all objects on this node are added to the parent, and children + // are also added (but children will be recursively sent through this routine, so if a parent gets option + // (c) and the child option (a) or (b), then the child will be ripped from the parents grasp. Cruel + // people us coders are. + // Note: (a) is the only way that two objects on the same node can be separated...that is why both + // option a and option b are needed. + if (!probShatter || smRandom.randF() < ps) + { + // option a -- shatter the shape at this level + + // iterate through the objects, make part out of each one + while (object>=0) + { + partList.increment(); + partList.last() = new TSPartInstance(shape,object); + object = shape->mShape->objects[object].nextSibling; + } + + // iterate through the child nodes, call ourselves on each one with currentPart = NULL + while (child>=0) + { + TSPartInstance::breakShape(shape,NULL,child,partList,probShatter,probBreak,probDepth); + child = shape->mShape->nodes[child].nextSibling; + } + + return; + } + + if (!probBreak || smRandom.randF() < pb) + // option b -- break the shape off at this level + currentPart = NULL; // fall through to option C + + // option c -- add this piece to the parent + + if (!currentPart) + { + currentPart = new TSPartInstance(shape); + partList.push_back(currentPart); + } + + // iterate through objects, add to currentPart + while (object>=0) + { + currentPart->addObject(object); + object = shape->mShape->objects[object].nextSibling; + } + + // iterate through child nodes, call ourselves on each one with currentPart as is + while (child>=0) + { + TSPartInstance::breakShape(shape,currentPart,child,partList,probShatter,probBreak,probDepth); + child = shape->mShape->nodes[child].nextSibling; + } +} + +//------------------------------------------------------------------------------------- +// render methods -- we use TSShapeInstance code as much as possible +// issues: setupTexturing expects a detail level, we give it an object detail level +//------------------------------------------------------------------------------------- + +void TSPartInstance::render(S32 od, const Point3F * objectScale) +{ + S32 i; + + // + mSourceShape->setStatics(0,mCurrentIntraDL,objectScale); + mSourceShape->setupTexturing(od,mCurrentIntraDL); // use of od here is a bit sketchy...but should be ok + TSMesh::initMaterials(); + + // render mesh objects + TSShapeInstance::smRenderData.currentTransform = NULL; + for (i=0; irender(od,mSourceShape->getMaterialList()); + + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + + // restore gl state + TSMesh::resetMaterials(); + + // render detail maps using a second pass? + if (mSourceShape->twoPassDetailMap()) + renderDetailMap(od); + + // render environment map using second pass? + if (mSourceShape->twoPassEnvironmentMap()) + renderEnvironmentMap(od); + + // set up gl environment for decals... + TSDecalMesh::initDecalMaterials(); + + // render decals... + TSShapeInstance::smRenderData.currentTransform = NULL; + for (i=0; irender(od,mSourceShape->mMaterialList); + + // if we have a matrix pushed, pop it now + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + + // restore gl state + TSDecalMesh::resetDecalMaterials(); + + // render fog if 2-passing it + if (mSourceShape->twoPassFog()) + renderFog(od); + + mSourceShape->clearStatics(); +} + +void TSPartInstance::renderDetailMap(S32 od) +{ + AssertFatal(TSShapeInstance::smRenderData.detailMapMethod==TSShapeInstance::DETAIL_MAP_TWO_PASS,"TSPartInstance::renderDetailMap"); + + // set up gl environment for the detail map + TSMesh::initDetailMapMaterials(); + + // run through objects and render detail maps + TSShapeInstance::smRenderData.currentTransform = NULL; + for (S32 i=0; irenderDetailMap(od,mSourceShape->mMaterialList); + + // if we have a matrix pushed, pop it now + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + + // restore gl state + TSMesh::resetDetailMapMaterials(); +} + +void TSPartInstance::renderEnvironmentMap(S32 od) +{ + AssertFatal((TextureObject*)mSourceShape->mEnvironmentMap,"TSPartInstance::renderEnvironmentMap (1)"); + AssertFatal(mSourceShape->mEnvironmentMapOn,"TSPartInstance::renderEnvironmentMap (2)"); + AssertFatal(dglDoesSupportARBMultitexture(),"TSPartInstance::renderEnvironmentMap (3)"); + AssertFatal(TSShapeInstance::smRenderData.environmentMapMethod==TSShapeInstance::ENVIRONMENT_MAP_TWO_PASS,"TSPartInstance::renderEnvironmentMap (4)"); + + // set up gl environment for emap... + TSMesh::initEnvironmentMapMaterials(); + + // run through objects and render + S32 i; + TSShapeInstance::smRenderData.currentTransform = NULL; + for (i=0; irenderEnvironmentMap(od,mSourceShape->getMaterialList()); + + // if we have a matrix pushed, pop it now + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + + // restore gl state + TSMesh::resetEnvironmentMapMaterials(); +} + +void TSPartInstance::renderFog(S32 od) +{ + AssertFatal(TSShapeInstance::smRenderData.fogMethod==TSShapeInstance::FOG_TWO_PASS,"TSPartInstance::renderFog"); + + // just one fog color per shape... + bool wasLit = glIsEnabled(GL_LIGHTING); + glDisable(GL_LIGHTING); + glColor4fv(TSShapeInstance::smRenderData.fogColor); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnableClientState(GL_VERTEX_ARRAY); + // texture should be disabled already... + + TSShapeInstance::smRenderData.currentTransform = NULL; + for (S32 i=0; irenderFog(od, mSourceShape->getMaterialList()); + + // if we have a marix pushed, pop it now + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + + // reset gl state + glDisable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ZERO); + glDisableClientState(GL_VERTEX_ARRAY); + if (wasLit) + glEnable(GL_LIGHTING); +} + +//------------------------------------------------------------------------------------- +// Detail selection +// 2 methods: +// method 1: use source shapes detail levels... +// method 2: pass in our own table... +// In either case, you can compute the pixel size on your own or let open gl do it. +// If you want to use method 2, you have to call setDetailData sometime before selecting detail +//------------------------------------------------------------------------------------- + +void TSPartInstance::setDetailData(F32 * sizeCutoffs, S32 numDetails) +{ + if (mSizeCutoffs == sizeCutoffs && mNumDetails==numDetails) + return; + + mSizeCutoffs = sizeCutoffs; + mNumDetails = numDetails; + delete [] mPolyCount; + mPolyCount = NULL; +} + +void TSPartInstance::selectCurrentDetail(bool ignoreScale) +{ + if (mSizeCutoffs) + { + selectCurrentDetail(mSizeCutoffs,mNumDetails,ignoreScale); + return; + } + + mSourceShape->selectCurrentDetail(ignoreScale); + mCurrentObjectDetail = mSourceShape->getCurrentDetail(); + mCurrentIntraDL = mSourceShape->getCurrentIntraDetail(); +} + +void TSPartInstance::selectCurrentDetail(F32 pixelSize) +{ + if (mSizeCutoffs) + { + selectCurrentDetail(pixelSize,mSizeCutoffs,mNumDetails); + return; + } + + mSourceShape->selectCurrentDetail(pixelSize); + mCurrentObjectDetail = mSourceShape->getCurrentDetail(); + mCurrentIntraDL = mSourceShape->getCurrentIntraDetail(); +} + +void TSPartInstance::selectCurrentDetail2(F32 adjustedDist) +{ + if (mSizeCutoffs) + { + F32 pixelSize = dglProjectRadius(adjustedDist,mSourceShape->getShape()->radius) * dglGetPixelScale() * TSShapeInstance::smDetailAdjust; + selectCurrentDetail(pixelSize,mSizeCutoffs,mNumDetails); + return; + } + + mSourceShape->selectCurrentDetail2(adjustedDist); + mCurrentObjectDetail = mSourceShape->getCurrentDetail(); + mCurrentIntraDL = mSourceShape->getCurrentIntraDetail(); +} + +void TSPartInstance::selectCurrentDetail(F32 * sizeCutoffs, S32 numDetails, bool ignoreScale) +{ + // compute pixel size + MatrixF toCam; + Point3F p; + dglGetModelview(&toCam); + toCam.mulP(mCenter,&p); + F32 dist = mDot(p,p); + F32 scale = 1.0f; + if (!ignoreScale) + { + // any scale? + Point3F x,y,z; + toCam.getRow(0,&x); + toCam.getRow(1,&y); + toCam.getRow(2,&z); + F32 scalex = mDot(x,x); + F32 scaley = mDot(y,y); + F32 scalez = mDot(z,z); + scale = scalex; + if (scaley > scale) + scale = scaley; + if (scalez > scale) + scale = scalez; + } + dist /= scale; + dist = mSqrt(dist); + + F32 pixelRadius = dglProjectRadius(dist,mRadius) * dglGetPixelScale() * TSShapeInstance::smDetailAdjust; + + selectCurrentDetail(pixelRadius,sizeCutoffs,numDetails); +} + +void TSPartInstance::selectCurrentDetail(F32 pixelSize, F32 * sizeCutoffs, S32 numDetails) +{ + mCurrentObjectDetail = 0; + while (numDetails) + { + if (pixelSize > *sizeCutoffs) + return; + mCurrentObjectDetail++; + numDetails--; + sizeCutoffs++; + } + mCurrentObjectDetail = -1; +} + +//------------------------------------------------------------------------------------- +// Detail query methods...complicated because there are two ways that detail information +// can be determined...1) using source shape, or 2) using mSizeCutoffs +//------------------------------------------------------------------------------------- + +S32 TSPartInstance::getDetailSize(S32 dl) +{ + if (dl<0) + return 0; + else if (mSizeCutoffs && dlgetShape()->mSmallestVisibleDL) + return mSourceShape->getShape()->details[dl].size; + else return 0; +} + +S32 TSPartInstance::getPolyCount(S32 dl) +{ + if (!mPolyCount) + computePolyCount(); + + if (dl<0 || dl>=mNumDetails) + return 0; + else + return mPolyCount[dl]; +} + +void TSPartInstance::computePolyCount() +{ + if (!mSizeCutoffs) + mNumDetails = mSourceShape->getShape()->mSmallestVisibleDL+1; + + delete [] mPolyCount; + mPolyCount = new S32[mNumDetails]; + + for (S32 i=0; igetMesh(i)) + mPolyCount[i] += mMeshObjects[j]->getMesh(i)->getNumPolys(); + } + } +} + + diff --git a/ts/tsPartInstance.h b/ts/tsPartInstance.h new file mode 100644 index 0000000..54dc3ad --- /dev/null +++ b/ts/tsPartInstance.h @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSPARTINSTANCE_H_ +#define _TSPARTINSTANCE_H_ + +#ifndef _TSSHAPEINSTANCE_H_ +#include "ts/tsShapeInstance.h" +#endif + +class TSPartInstance +{ + // TSPartInstance assumes ownership (or shared ownership) of the source shape. This means that the source + // shape cannot be deleted so long as the part instance is still around. This also means that any change + // to source shapes's transforms or other animation properties will affect how the part instance displays. + // It is ok (even expected), however, to have many part instances accessing the same shape. + TSShapeInstance * mSourceShape; + + Box3F mBounds; + Point3F mCenter; + F32 mRadius; + + // detail selection uses the table pointed to by this member + // if this member is blank, then it uses source shape to determine detail... + // detail 0 draws up until size of shape is less than mSizeCutoffs[0], detail 1 until mSizeCutoffs[1], etc. + F32 * mSizeCutoffs; + S32 * mPolyCount; + S32 mNumDetails; + + // detail levels on part instance correspond directly + // to object details on objects -- this is different + // from shape instances where dl corresponds to a + // subtree number and object detail. The reason + // for this is that partinstances are derived from + // a single subtree of a shape instance, so the subtree + // is implied (implied by which objects are in the part instance)... + S32 mCurrentObjectDetail; + F32 mCurrentIntraDL; + + Vector mMeshObjects; + Vector mDecalObjects; + + static MRandomR250 smRandom; + + void addObject(S32 objectIndex); + void updateBounds(); + + void renderDetailMap(S32 od); + void renderEnvironmentMap(S32 od); + void renderFog(S32 od); + + void init(TSShapeInstance *); + + static void breakShape(TSShapeInstance *, TSPartInstance *, S32 currentNode, + Vector & partList, F32 * probShatter, + F32 * probBreak, S32 probDepth); + + // private detail selectiong methods + void selectCurrentDetail(F32 * sizeCutoffs, S32 numDetails, bool ignoreScale); + void selectCurrentDetail(F32 pixelSize, F32 * sizeCutoffs, S32 numDetails); + void computePolyCount(); + +public: + + TSPartInstance(TSShapeInstance * source); + TSPartInstance(TSShapeInstance * source, S32 objectIndex); + ~TSPartInstance(); + + const TSShape * getShape() { return mSourceShape->getShape(); } + TSShapeInstance * getSourceShapeInstance(){ return mSourceShape; } + + static void breakShape(TSShapeInstance *, S32 subShape, Vector & partList, F32 * probShatter, F32 * probBreak, S32 probDepth); + + Point3F & getCenter() { return mCenter; } + Box3F & getBounds() { return mBounds; } + F32 & getRadius() { return mRadius; } + + void render(const Point3F * objectScale = NULL) { render(mCurrentObjectDetail,objectScale); } + void render(S32 dl, const Point3F * objectScale = NULL); + + // choose detail method -- pass in NULL for first parameter to just use shapes data + void setDetailData(F32 * sizeCutoffs, S32 numDetails); + + // detail selection + void selectCurrentDetail(bool ignoreScale = false); + void selectCurrentDetail(F32 pixelSize); + void selectCurrentDetail2(F32 adjustedDist); + + // get information about details... + S32 getDetailSize(S32 dl); + S32 getPolyCount(S32 dl); + S32 getNumDetails() { return mSizeCutoffs ? mNumDetails : mSourceShape->getShape()->mSmallestVisibleDL+1; } + + S32 getCurrentObjectDetail() { return mCurrentObjectDetail; } + void setCurrentObjectDetail(S32 od) { mCurrentObjectDetail = od; } + F32 getCurrentIntraDetail() { return mCurrentIntraDL; } + void setCurrentIntraDetail(F32 intra) { mCurrentIntraDL = intra; } + + U32 mData; // for use by app +}; + +#endif + diff --git a/ts/tsShape.cc b/ts/tsShape.cc new file mode 100644 index 0000000..e6cecd0 --- /dev/null +++ b/ts/tsShape.cc @@ -0,0 +1,1666 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShape.h" +#include "ts/tsLastDetail.h" +#include "Core/stringTable.h" +#include "console/console.h" +#include "ts/tsShapeInstance.h" +#include "Collision/convex.h" +#include "PlatformWin32/platformGL.h" + +// most recent version -- this is the version we write +S32 TSShape::smVersion = 23; +// the version currently being read...valid only during a read +S32 TSShape::smReadVersion = -1; +const U32 TSShape::smMostRecentExporterVersion = DTS_EXPORTER_CURRENT_VERSION; + +F32 TSShape::smAlphaOutLastDetail = -1.0f; +F32 TSShape::smAlphaInBillboard = 0.15f; +F32 TSShape::smAlphaOutBillboard = 0.1f; +F32 TSShape::smAlphaInDefault = -1.0f; +F32 TSShape::smAlphaOutDefault = -1.0f; + +// don't bother even loading this many of the highest detail levels (but +// always load last renderable detail) +S32 TSShape::smNumSkipLoadDetails = 0; + +bool TSShape::smInitOnRead = true; + + +TSShape::TSShape() +{ + materialList = NULL; + mReadVersion = -1; // -1 means constructed from scratch (e.g., in exporter or no read yet) + mMemoryBlock = NULL; + + mSequencesConstructed = false; + + mVertexBuffer = -1; + mCallbackKey = -1; + + VECTOR_SET_ASSOCIATION(sequences); + VECTOR_SET_ASSOCIATION(billboardDetails); + VECTOR_SET_ASSOCIATION(detailCollisionAccelerators); + VECTOR_SET_ASSOCIATION(names); +} + +TSShape::~TSShape() +{ + clearDynamicData(); + delete materialList; + + U32 i; + + // before deleting meshes, we have to delete decals and get them out + // of the mesh list...this is a legacy issue from when decals were meshes + for (i=0; ivertexList; + delete [] accel->normalList; + for (U32 j = 0; j < accel->numVerts; j++) + delete [] accel->emitStrings[j]; + delete [] accel->emitStrings; + delete accel; + } + } + for (dca = 0; dca < detailCollisionAccelerators.size(); dca++) + detailCollisionAccelerators[dca] = NULL; + + delete [] mMemoryBlock; + mMemoryBlock = NULL; + + if (mVertexBuffer != -1) + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(mVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + if (mCallbackKey != -1) + TextureManager::unregisterEventCallback(mCallbackKey); +} + +void TSShape::clearDynamicData() +{ +} + +const char * TSShape::getName(S32 nameIndex) const +{ + AssertFatal(nameIndex>=0 && nameIndex=0) + { + if (nodes[parentIndex].firstChild<0) + nodes[parentIndex].firstChild=i; + else + { + S32 child = nodes[parentIndex].firstChild; + while (nodes[child].nextSibling>=0) + child = nodes[child].nextSibling; + nodes[child].nextSibling = i; + } + } + } + for (i=0; i=0) + { + if (nodes[nodeIndex].firstObject<0) + nodes[nodeIndex].firstObject = i; + else + { + S32 objectIndex = nodes[nodeIndex].firstObject; + while (objects[objectIndex].nextSibling>=0) + objectIndex = objects[objectIndex].nextSibling; + objects[objectIndex].nextSibling = i; + } + } + } + for (i=0; i=0) + decalIndex = decals[decalIndex].nextSibling; + decals[decalIndex].nextSibling = i; + } + } + + mFlags = 0; + for (i=0; i=0; i--) + { + if (igetNumPolys() : 0; + } + } + details[i].polyCount = count; + } + + // Init the collision accelerator array. Note that we don't compute the + // accelerators until the app requests them + { + U32 dca; + for (dca = 0; dca < detailCollisionAccelerators.size(); dca++) + { + ConvexHullAccelerator* accel = detailCollisionAccelerators[dca]; + if (accel != NULL) { + delete [] accel->vertexList; + delete [] accel->normalList; + for (U32 j = 0; j < accel->numVerts; j++) + delete [] accel->emitStrings[j]; + delete [] accel->emitStrings; + delete accel; + } + } + + detailCollisionAccelerators.setSize(details.size()); + for (dca = 0; dca < detailCollisionAccelerators.size(); dca++) + detailCollisionAccelerators[dca] = NULL; + } + + mMergeBufferSize=0; + for (i=0; inumMeshes; dl++) + { + TSMesh * mesh = meshes[obj->startMeshIndex+dl]; + if (mesh) + { + mesh->mergeBufferStart = mMergeBufferSize; + maxSize = getMax(maxSize,mesh->mergeIndices.size()); + } + } + mMergeBufferSize += maxSize; + } + + initMaterialList(); +} + +void TSShape::setupBillboardDetails() +{ + // set up billboard details -- only do this once, meaning that + // if we add a sequence to the shape we don't redo the billboard + // details... + S32 i; + if (billboardDetails.empty()) + { + for (i=0; i=0) + continue; // not a billboard detail + while (billboardDetails.size()<=i) + billboardDetails.push_back(NULL); + U32 props = details[i].objectDetailNum; + U32 numEquatorSteps = props & 0x7F; // bits 0..6 + U32 numPolarSteps = (props>>7) & 0x3F; // bits 7..12 + F32 polarAngle = 0.5f * M_PI * (1.0f/64.0f) * (F32) ((props>>13) & 0x3F); // bits 13..18 + S32 dl = (props>>19) & 0x0F; // 19..22 + S32 dim = (props>>23) & 0xFF; // 23..30 + bool includePoles = props & 0x80000000; // bit 31 + + billboardDetails[i] = new TSLastDetail(this,numEquatorSteps,numPolarSteps,polarAngle,includePoles,dl,dim); + } + } +} + +void TSShape::initMaterialList() +{ + S32 numSubShapes = subShapeFirstObject.size(); + #ifdef MAX_UTIL + subShapeFirstTranslucentObject.setSize(numSubShapes); + #endif + + S32 i,j,k; + // for each subshape, find the first translucent object + // also, while we're at it, set mHasTranslucency + for (S32 ss = 0; ssprimitives.size(); k++) + { + if (mesh->primitives[k].matIndex & TSDrawPrimitive::NoMaterial) + continue; + S32 flags = materialList->getFlags(mesh->primitives[k].matIndex & TSDrawPrimitive::MaterialMask); + if (flags & TSMaterialList::AuxiliaryMap) + continue; + if (flags & TSMaterialList::Translucent) + { + mFlags |= HasTranslucency; + subShapeFirstTranslucentObject[ss] = i; + break; + } + } + if (k!=mesh->primitives.size()) + break; + } + if (j!=obj.numMeshes) + break; + } + if (i!=end) + break; + } +} + +bool TSShape::buildConvexHull(S32 dl) const +{ + AssertFatal(dl>=0 && dlbuildConvexHull(); + } + return ok; +} + +Vector gTempNodeTransforms(__FILE__, __LINE__); + +void TSShape::computeBounds(S32 dl, Box3F & bounds) const +{ + // if dl==-1, nothing to do + if (dl==-1) + return; + + AssertFatal(dl>=0 && dlsubShapeNum; + S32 od = detail->objectDetailNum; + + // set up temporary storage for non-local transforms... + S32 i; + S32 start = subShapeFirstNode[ss]; + S32 end = subShapeNumNodes[ss] + start; + gTempNodeTransforms.setSize(end-start); + for (i=start; i=0) + gTempNodeTransforms[i-start].mul(gTempNodeTransforms[nodes[i].parentIndex-start],mat); + else + gTempNodeTransforms[i-start] = mat; + } + + // run through objects and updating bounds as we go + bounds.min.set( 10E30, 10E30, 10E30); + bounds.max.set(-10E30,-10E30,-10E30); + Box3F box; + start = subShapeFirstObject[ss]; + end = subShapeNumObjects[ss] + start; + for (i=start; inumMeshes ? meshes[object->startMeshIndex+od] : NULL; + if (mesh) + { + static MatrixF idMat(true); + if (object->nodeIndex<0) + mesh->computeBounds(idMat,box); + else + mesh->computeBounds(gTempNodeTransforms[object->nodeIndex-start],box); + bounds.min.setMin(box.min); + bounds.max.setMax(box.max); + } + } +} + +TSShapeAlloc TSShape::alloc; + +#define alloc TSShape::alloc + +// messy stuff: check to see if we should "skip" meshNum +// this assumes that meshes for a given object/decal are in a row +// skipDL is the lowest detail number we keep (i.e., the # of details we skip) +bool TSShape::checkSkip(S32 meshNum, S32 & curObject, S32 & curDecal, S32 skipDL) +{ + if (skipDL==0) + // easy out... + return false; + + // skip detail level exists on this subShape + S32 skipSS = details[skipDL].subShapeNum; + + if (curObject=start) + { + // we are either from this object, the next object, or a decal + if (meshNum < start + objects[curObject].numMeshes) + { + // this object... + if (subShapeFirstObject[skipSS]>curObject) + // haven't reached this subshape yet + return true; + if (skipSS+1==subShapeFirstObject.size() || curObject=start) + { + // we are either from this decal, the next decal, or error + if (meshNum < start + decals[curDecal].numMeshes) + { + // this object... + if (subShapeFirstDecal[skipSS]>curDecal) + // haven't reached this subshape yet + return true; + if (skipSS+1==subShapeFirstDecal.size() || curDecal21) + { + // more node sequence data...scale + nodeUniformScales.setSize(numNodeUniformScales); + for (i=0;i15) + { + // straight forward read one at a time + ptr32 = alloc.allocShape32(numMeshes + numSkins*numDetails); // leave room for skins on old shapes + S32 curObject = 0, curDecal = 0; // for tracking skipped meshes + for (i=0; iverts.address(); + TSMesh::smTVertsList[i] = mesh->tverts.address(); + TSMesh::smNormsList[i] = mesh->norms.address(); + TSMesh::smEncodedNormsList[i] = mesh->encodedNorms.address(); + TSMesh::smDataCopied[i] = !skip; // as long as we didn't skip this mesh, the data should be in shape now + if (meshType==TSMesh::SkinMeshType) + { + TSSkinMesh * skin = (TSSkinMesh*)mesh; + TSMesh::smVertsList[i] = skin->initialVerts.address(); + TSMesh::smNormsList[i] = skin->initialNorms.address(); + TSSkinMesh::smInitTransformList[i] = skin->initialTransforms.address(); + TSSkinMesh::smVertexIndexList[i] = skin->vertexIndex.address(); + TSSkinMesh::smBoneIndexList[i] = skin->boneIndex.address(); + TSSkinMesh::smWeightList[i] = skin->weight.address(); + TSSkinMesh::smNodeIndexList[i] = skin->nodeIndex.address(); + } + } + } + meshes.set(ptr32,numMeshes); + } + else + { + // use meshIndexList to contruct mesh list... + ptr32 = alloc.allocShape32(meshIndexListSize + numSkins*numDetails); + S32 next=0; + S32 curObject = 0, curDecal = 0; // for tracking skipped meshes + for (i=0; i=0) + { + AssertFatal(meshIndexList[i]==next,"TSShape::read: assertion failed on obsolete shape"); + S32 meshType = alloc.get32(); + TSMesh * mesh = TSMesh::assembleMesh(meshType,skip); + if (ptr32) + ptr32[i] = skip ? 0 : (S32) mesh; + next = meshIndexList[i]+1; + + // fill in location of verts, tverts, and normals for detail levels + if (mesh && meshType!=TSMesh::DecalMeshType) + { + TSMesh::smVertsList[i] = mesh->verts.address(); + TSMesh::smTVertsList[i] = mesh->tverts.address(); + TSMesh::smNormsList[i] = mesh->norms.address(); + TSMesh::smEncodedNormsList[i] = mesh->encodedNorms.address(); + TSMesh::smDataCopied[i] = !skip; // as long as we didn't skip this mesh, the data should be in shape now + if (meshType==TSMesh::SkinMeshType) + { + TSSkinMesh * skin = (TSSkinMesh*)mesh; + TSMesh::smVertsList[i] = skin->initialVerts.address(); + TSMesh::smNormsList[i] = skin->initialNorms.address(); + TSSkinMesh::smInitTransformList[i] = skin->initialTransforms.address(); + TSSkinMesh::smVertexIndexList[i] = skin->vertexIndex.address(); + TSSkinMesh::smBoneIndexList[i] = skin->boneIndex.address(); + TSSkinMesh::smWeightList[i] = skin->weight.address(); + TSSkinMesh::smNodeIndexList[i] = skin->nodeIndex.address(); + } + } + } + else if (ptr32) + ptr32[i] = 0; // no mesh + } + meshes.set(ptr32,meshIndexListSize); + } + + alloc.checkGuard(); + + // names + bool namesInStringTable = (StringTable!=NULL); + char * nameBufferStart = (char*)alloc.getPointer8(0); + char * name = nameBufferStart; + S32 nameBufferSize = 0; + names.setSize(numNames); + for (i=0; iinsert(name,false); + nameBufferSize += j + 1; + name += j + 1; + } + if (!namesInStringTable) + { + name = (char*)alloc.copyToShape8(nameBufferSize); + if (name) // make sure we did copy (might just be getting size of buffer) + for (i=0; iinitialVerts.address(); + TSMesh::smTVertsList[i] = skin->tverts.address(); + TSMesh::smNormsList[i] = skin->initialNorms.address(); + TSMesh::smEncodedNormsList[i] = skin->encodedNorms.address(); + TSMesh::smDataCopied[i] = !skip; // as long as we didn't skip this mesh, the data should be in shape now + TSSkinMesh::smInitTransformList[i] = skin->initialTransforms.address(); + TSSkinMesh::smVertexIndexList[i] = skin->vertexIndex.address(); + TSSkinMesh::smBoneIndexList[i] = skin->boneIndex.address(); + TSSkinMesh::smWeightList[i] = skin->weight.address(); + TSSkinMesh::smNodeIndexList[i] = skin->nodeIndex.address(); + } + } + + alloc.checkGuard(); + + // we now have skins in mesh list...add skin objects to object list and patch things up + fixupOldSkins(numMeshes,numSkins,numDetails,detailFirstSkin,detailNumSkins); + } + + // allocate storage space for some arrays (filled in during Shape::init)... + ptr32 = alloc.allocShape32(numDetails); + alphaIn.set(ptr32,numDetails); + ptr32 = alloc.allocShape32(numDetails); + alphaOut.set(ptr32,numDetails); + + mPreviousMerge.setSize(numObjects); + for (i = 0; i < numObjects; ++i) + mPreviousMerge[i] = -1; + + mExportMerge = smReadVersion >= 23; +} + +void TSShape::disassembleShape() +{ + S32 i; + + // set counts... + S32 numNodes = alloc.set32(nodes.size()); + S32 numObjects = alloc.set32(objects.size()); + S32 numDecals = alloc.set32(decals.size()); + S32 numSubShapes = alloc.set32(subShapeFirstNode.size()); + S32 numIflMaterials = alloc.set32(iflMaterials.size()); + S32 numNodeRotations = alloc.set32(nodeRotations.size()); + S32 numNodeTranslations = alloc.set32(nodeTranslations.size()); + S32 numNodeUniformScales = alloc.set32(nodeUniformScales.size()); + S32 numNodeAlignedScales = alloc.set32(nodeAlignedScales.size()); + S32 numNodeArbitraryScales = alloc.set32(nodeArbitraryScaleFactors.size()); + S32 numObjectStates = alloc.set32(objectStates.size()); + S32 numDecalStates = alloc.set32(decalStates.size()); + S32 numTriggers = alloc.set32(triggers.size()); + S32 numDetails = alloc.set32(details.size()); + S32 numMeshes = alloc.set32(meshes.size()); + S32 numNames = alloc.set32(names.size()); + alloc.set32((S32)mSmallestVisibleSize); + alloc.set32(mSmallestVisibleDL); + + alloc.setGuard(); + + // get bounds... + alloc.copyToBuffer32((S32*)&radius,1); + alloc.copyToBuffer32((S32*)&tubeRadius,1); + alloc.copyToBuffer32((S32*)¢er,3); + alloc.copyToBuffer32((S32*)&bounds,6); + + alloc.setGuard(); + + // copy various vectors... + alloc.copyToBuffer32((S32*)nodes.address(),numNodes*5); + alloc.setGuard(); + alloc.copyToBuffer32((S32*)objects.address(),numObjects*6); + alloc.setGuard(); + alloc.copyToBuffer32((S32*)decals.address(),numDecals*5); + alloc.setGuard(); + alloc.copyToBuffer32((S32*)iflMaterials.address(),numIflMaterials*5); + alloc.setGuard(); + alloc.copyToBuffer32((S32*)subShapeFirstNode.address(),numSubShapes); + alloc.copyToBuffer32((S32*)subShapeFirstObject.address(),numSubShapes); + alloc.copyToBuffer32((S32*)subShapeFirstDecal.address(),numSubShapes); + alloc.setGuard(); + alloc.copyToBuffer32((S32*)subShapeNumNodes.address(),numSubShapes); + alloc.copyToBuffer32((S32*)subShapeNumObjects.address(),numSubShapes); + alloc.copyToBuffer32((S32*)subShapeNumDecals.address(),numSubShapes); + alloc.setGuard(); + + // default transforms... + alloc.copyToBuffer16((S16*)defaultRotations.address(),numNodes*4); + alloc.copyToBuffer32((S32*)defaultTranslations.address(),numNodes*3); + + // animated transforms... + alloc.copyToBuffer16((S16*)nodeRotations.address(),numNodeRotations*4); + alloc.copyToBuffer32((S32*)nodeTranslations.address(),numNodeTranslations*3); + + alloc.setGuard(); + + // ...with scale + alloc.copyToBuffer32((S32*)nodeUniformScales.address(),numNodeUniformScales); + alloc.copyToBuffer32((S32*)nodeAlignedScales.address(),numNodeAlignedScales*3); + alloc.copyToBuffer32((S32*)nodeArbitraryScaleFactors.address(),numNodeArbitraryScales*3); + alloc.copyToBuffer16((S16*)nodeArbitraryScaleRots.address(),numNodeArbitraryScales*4); + + alloc.setGuard(); + + // object states.. + alloc.copyToBuffer32((S32*)objectStates.address(),numObjectStates*3); + alloc.setGuard(); + + // decal states... + alloc.copyToBuffer32((S32*)decalStates.address(),numDecalStates); + alloc.setGuard(); + + // frame triggers + alloc.copyToBuffer32((S32*)triggers.address(),numTriggers*2); + alloc.setGuard(); + + // details + alloc.copyToBuffer32((S32*)details.address(),numDetails*7); + alloc.setGuard(); + + // read in the meshes (sans skins)... + bool * isMesh = new bool[numMeshes]; // funny business because decals are pretend meshes (legacy issue) + for (i=0;igetMeshType() : (decalMesh ? TSMesh::DecalMeshType : TSMesh::NullMeshType)); + if (mesh) + mesh->disassemble(); + else if (decalMesh) + decalMesh->disassemble(); + } + delete [] isMesh; + alloc.setGuard(); + + // names + for (i=0; iwrite(smVersion | (mExporterVersion<<16)); + + alloc.setWrite(); + disassembleShape(); + + S32 * buffer32 = alloc.getBuffer32(); + S16 * buffer16 = alloc.getBuffer16(); + S8 * buffer8 = alloc.getBuffer8(); + + S32 size32 = alloc.getBufferSize32(); + S32 size16 = alloc.getBufferSize16(); + S32 size8 = alloc.getBufferSize8(); + + // convert sizes to dwords... + if (size16 & 1) + size16 += 2; + size16 >>= 1; + if (size8 & 3) + size8 += 4; + size8 >>= 2; + + S32 sizeMemBuffer, start16, start8; + sizeMemBuffer = size32 + size16 + size8; + start16 = size32; + start8 = start16+size16; + + // in dwords... + s->write(sizeMemBuffer); + s->write(start16); + s->write(start8); + + fixEndian(buffer32,buffer16,buffer8,size32,size16,size8); + + // now write buffers + s->write(size32*4,buffer32); + s->write(size16*4,buffer16); + s->write(size8 *4,buffer8); + + // write sequences + s->write(sequences.size()); + for (S32 i=0; iwrite(*s); + + delete [] buffer32; + delete [] buffer16; + delete [] buffer8; +} + +//------------------------------------------------- +// read whole shape +//------------------------------------------------- + +bool TSShape::read(Stream * s) +{ + // read version + s->read(&smReadVersion); + mExporterVersion = smReadVersion >> 16; + smReadVersion &= 0xFF; + if (smReadVersion>smVersion) + { + // error -- don't support future versions yet :> + Con::errorf(ConsoleLogEntry::General, + "Error: attempt to load a version %i dts-shape, can currently only load version %i and before.", + smReadVersion,smVersion); + return false; + } + mReadVersion = smReadVersion; + + S32 * memBuffer32; + S16 * memBuffer16; + S8 * memBuffer8; + S32 count32, count16, count8; + if (mReadVersion<19) + readOldShape(s,memBuffer32,memBuffer16,memBuffer8,count32,count16,count8); + else + { + S32 i; + U32 sizeMemBuffer, startU16, startU8; + + // in dwords... + s->read(&sizeMemBuffer); + s->read(&startU16); + s->read(&startU8); + + if (s->getStatus()!=Stream::Ok) + { + Con::errorf(ConsoleLogEntry::General, "Error: bad shape file."); + return false; + } + + S32 * tmp = new S32[sizeMemBuffer]; + s->read(sizeof(S32)*sizeMemBuffer,(U8*)tmp); + memBuffer32 = tmp; + memBuffer16 = (S16*)(tmp+startU16); + memBuffer8 = (S8*)(tmp+startU8); + + count32 = startU16; + count16 = startU8-startU16; + count8 = sizeMemBuffer-startU8; + + // read sequences + S32 numSequences; + s->read(&numSequences); + sequences.setSize(numSequences); + for (i=0; iread(*s); + } + + fixEndian(memBuffer32,memBuffer16,memBuffer8,count32,count16,count8); + + alloc.setRead(memBuffer32,memBuffer16,memBuffer8,true); + assembleShape(); // determine size of buffer needed + S32 buffSize = alloc.getSize(); + alloc.doAlloc(); + mMemoryBlock = alloc.getBuffer(); + alloc.setRead(memBuffer32,memBuffer16,memBuffer8,false); + assembleShape(); // copy to buffer + AssertFatal(alloc.getSize()==buffSize,"TSShape::read: shape data buffer size mis-calculated"); + + if (smReadVersion<19) + { + delete [] memBuffer32; + delete [] memBuffer16; + delete [] memBuffer8; + } + else + delete [] memBuffer32; // this covers all the buffers + + if (smInitOnRead) + init(); + return true; +} + +void TSShape::fixEndian(S32 * buff32, S16 * buff16, S8 *, S32 count32, S32 count16, S32) +{ + if (0x12345678!=convertLEndianToHost(0x12345678)) + { + for (S32 i=0; isize(); // next material added will be our first frame + iflMaterial.firstFrameOffTimeIndex = iflFrameOffTimes.size(); // next entry added will be our first + iflMaterial.numFrames = 0; // we'll increment as we read the file + + U32 totalTime = 0; + + // fix slashes that face the wrong way + char * pName = (char*)getName(iflMaterial.nameIndex); + S32 len = dStrlen(pName); + for (U32 j=0; j=0 && mReadVersion<16) + // older file format...file name doesn't include prefix + dSprintf(nameBuffer,sizeof(nameBuffer),"%s%s", TSMaterialList::csmIFLTexturePrefix, + getName(iflMaterial.nameIndex)); + else + { + // bad hack -- see below for similar case... + if (dStrcmp(TSMaterialList::csmIFLTexturePrefix,TSMaterialList::csmTSTexturePrefix)) + // newer file format...file name does include prefix + dSprintf(nameBuffer,sizeof(nameBuffer),"%s", getName(iflMaterial.nameIndex)); + else + // newer file format...but file name doesn't include prefix (hack for hunting...this'll be cleaned up later) + dSprintf(nameBuffer,sizeof(nameBuffer),"%s%s", TSMaterialList::csmIFLTexturePrefix, getName(iflMaterial.nameIndex)); + } + Stream * s = ResourceManager->openStream(nameBuffer); + if (!s || s->getStatus() != Stream::Ok) + { + iflMaterial.firstFrame = iflMaterial.materialSlot; // avoid over-runs + if (s) + ResourceManager->closeStream(s); + continue; + } + + char buffer[512]; + char name[256]; + U32 duration; + while (s->getStatus() == Stream::Ok) + { + s->readLine((U8*)buffer,512); + if (s->getStatus() == Stream::Ok || s->getStatus() == Stream::EOS) + { + char * pos = buffer; + while (*pos) + { + if (*pos=='\t') + *pos=' '; + pos++; + } + pos = buffer; + // strip off any preceding spaces + while (*pos && *pos==' ') + pos++; + char * pos2 = dStrchr(pos,' '); + if (pos2) + { + *pos2='\0'; + pos2++; + // strip off preceding spaces from duration + while (*pos2 && *pos2==' ') + pos2++; + } + duration = pos2 ? dAtoi(pos2) : 1; + if (duration==0) + duration = 1; // just in case... + if (!*pos) + // skip line with no material + continue; + dStrncpy(name,pos,256); + + totalTime += duration; + iflFrameOffTimes.push_back((1.0f/30.0f) * (F32)totalTime); // ifl units are frames (1 frame = 1/30 sec) + + // bad hack -- but should work + // problem is that ifl texture prefix is needed to get ifl file but individual materials + // in the ifl may or may not need prefix...the following fix works for Tribes II and Hunting + // At some point in the future, ifl's should be handled better (probably by incorporating + // them into the shape...) + if (!dStrcmp(TSMaterialList::csmIFLTexturePrefix,TSMaterialList::csmTSTexturePrefix)) + dSprintf((char*)buffer,sizeof(buffer),"%s",name); + else + dSprintf((char*)buffer, sizeof(buffer), "%s%s", TSMaterialList::csmIFLTexturePrefix, name); + materialList->push_back((char*)buffer,TSMaterialList::IflFrame); + iflMaterial.numFrames++; + } + } + + // close the file... + ResourceManager->closeStream(s); + } + initMaterialList(); + mFlags |= IflInit; +} + +ResourceInstance *constructTSShape(Stream &stream) +{ + TSShape * ret = new TSShape; + if (ret->read(&stream)) + return ret; + else + return NULL; +} + + +#if 1 +TSShape::ConvexHullAccelerator* TSShape::getAccelerator(S32 dl) +{ + AssertFatal(dl < details.size(), "Error, bad detail level!"); + if (dl == -1) + return NULL; + + if (detailCollisionAccelerators[dl] == NULL) + computeAccelerator(dl); + + AssertFatal(detailCollisionAccelerators[dl] != NULL, "This should be non-null after computing it!"); + return detailCollisionAccelerators[dl]; +} + + +void TSShape::computeAccelerator(S32 dl) +{ + AssertFatal(dl < details.size(), "Error, bad detail level!"); + + // Have we already computed this? + if (detailCollisionAccelerators[dl] != NULL) + return; + + // Create a bogus features list... + ConvexFeature cf; + MatrixF mat(true); + Point3F n(0, 0, 1); + + const TSDetail* detail = &details[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + S32 start = subShapeFirstObject[ss]; + S32 end = subShapeNumObjects[ss] + start; + if (start < end) + { + // run through objects and collide + // DMMNOTE: This assumes that the transform of the collision hulls is + // identity... + U32 surfaceKey = 0; + for (S32 i = start; i < end; i++) + { + const TSObject* obj = &objects[i]; + + if (obj->numMeshes && od < obj->numMeshes) { + TSMesh* mesh = meshes[obj->startMeshIndex + od]; + if (mesh) + mesh->getFeatures(0, mat, n, &cf, surfaceKey); + } + } + } + + Vector fixedVerts; + VECTOR_SET_ASSOCIATION(fixedVerts); + U32 i; + for (i = 0; i < cf.mVertexList.size(); i++) { + U32 j; + bool found = false; + for (j = 0; j < cf.mFaceList.size(); j++) { + if (cf.mFaceList[j].vertex[0] == i || + cf.mFaceList[j].vertex[1] == i || + cf.mFaceList[j].vertex[2] == i) { + found = true; + break; + } + } + if (!found) + continue; + + found = false; + for (j = 0; j < fixedVerts.size(); j++) { + if (fixedVerts[j] == cf.mVertexList[i]) { + found = true; + break; + } + } + if (found == true) { + // Ok, need to replace any references to vertex i in the facelists with + // a reference to vertex j in the fixed list + for (U32 k = 0; k < cf.mFaceList.size(); k++) { + for (U32 l = 0; l < 3; l++) { + if (cf.mFaceList[k].vertex[l] == i) + cf.mFaceList[k].vertex[l] = j; + } + } + } else { + for (U32 k = 0; k < cf.mFaceList.size(); k++) { + for (U32 l = 0; l < 3; l++) { + if (cf.mFaceList[k].vertex[l] == i) + cf.mFaceList[k].vertex[l] = fixedVerts.size(); + } + } + fixedVerts.push_back(cf.mVertexList[i]); + } + } + cf.mVertexList.setSize(0); + cf.mVertexList = fixedVerts; + + // Ok, so now we have a vertex list. Lets copy that out... + ConvexHullAccelerator* accel = new ConvexHullAccelerator; + detailCollisionAccelerators[dl] = accel; + accel->numVerts = cf.mVertexList.size(); + accel->vertexList = new Point3F[accel->numVerts]; + dMemcpy(accel->vertexList, cf.mVertexList.address(), sizeof(Point3F) * accel->numVerts); + + accel->normalList = new PlaneF[cf.mFaceList.size()]; + for (i = 0; i < cf.mFaceList.size(); i++) + accel->normalList[i] = cf.mFaceList[i].normal; + + accel->emitStrings = new U8*[accel->numVerts]; + dMemset(accel->emitStrings, 0, sizeof(U8*) * accel->numVerts); + + for (i = 0; i < accel->numVerts; i++) { + U32 j; + + Vector faces; + VECTOR_SET_ASSOCIATION(faces); + for (j = 0; j < cf.mFaceList.size(); j++) { + if (cf.mFaceList[j].vertex[0] == i || + cf.mFaceList[j].vertex[1] == i || + cf.mFaceList[j].vertex[2] == i) { + faces.push_back(j); + } + } + AssertFatal(faces.size() != 0, "Huh? Vertex unreferenced by any faces"); + + // Insert all faces that didn't make the first cut, but share a plane with + // a face that's on the short list. + for (j = 0; j < cf.mFaceList.size(); j++) { + bool found = false; + U32 k; + for (k = 0; k < faces.size(); k++) { + if (faces[k] == j) + found = true; + } + if (found) + continue; + + found = false; + for (k = 0; k < faces.size(); k++) { + if (mDot(accel->normalList[faces[k]], accel->normalList[j]) > 0.999) { + found = true; + break; + } + } + if (found) + faces.push_back(j); + } + + Vector vertRemaps; + VECTOR_SET_ASSOCIATION(vertRemaps); + for (j = 0; j < faces.size(); j++) { + for (U32 k = 0; k < 3; k++) { + U32 insert = cf.mFaceList[faces[j]].vertex[k]; + bool found = false; + for (U32 l = 0; l < vertRemaps.size(); l++) { + if (insert == vertRemaps[l]) { + found = true; + break; + } + } + if (!found) + vertRemaps.push_back(insert); + } + } + + Vector edges; + VECTOR_SET_ASSOCIATION(edges); + for (j = 0; j < faces.size(); j++) { + for (U32 k = 0; k < 3; k++) { + U32 edgeStart = cf.mFaceList[faces[j]].vertex[(k + 0) % 3]; + U32 edgeEnd = cf.mFaceList[faces[j]].vertex[(k + 1) % 3]; + + U32 e0 = getMin(edgeStart, edgeEnd); + U32 e1 = getMax(edgeStart, edgeEnd); + + bool found = false; + for (U32 l = 0; l < edges.size(); l++) { + if (edges[l].x == e0 && edges[l].y == e1) { + found = true; + break; + } + } + if (!found) + edges.push_back(Point2I(e0, e1)); + } + } + + AssertFatal(vertRemaps.size() < 256 && faces.size() < 256 && edges.size() < 256, + "Error, ran over the shapebase assumptions about convex hulls."); + + U32 emitStringLen = 1 + vertRemaps.size() + + 1 + (edges.size() * 2) + + 1 + (faces.size() * 4); + accel->emitStrings[i] = new U8[emitStringLen]; + + U32 currPos = 0; + + accel->emitStrings[i][currPos++] = vertRemaps.size(); + for (j = 0; j < vertRemaps.size(); j++) + accel->emitStrings[i][currPos++] = vertRemaps[j]; + + accel->emitStrings[i][currPos++] = edges.size(); + for (j = 0; j < edges.size(); j++) { + U32 l; + U32 old = edges[j].x; + bool found = false; + for (l = 0; l < vertRemaps.size(); l++) { + if (vertRemaps[l] == old) { + found = true; + accel->emitStrings[i][currPos++] = l; + break; + } + } + AssertFatal(found, "Error, couldn't find the remap!"); + + old = edges[j].y; + found = false; + for (l = 0; l < vertRemaps.size(); l++) { + if (vertRemaps[l] == old) { + found = true; + accel->emitStrings[i][currPos++] = l; + break; + } + } + AssertFatal(found, "Error, couldn't find the remap!"); + } + + accel->emitStrings[i][currPos++] = faces.size(); + for (j = 0; j < faces.size(); j++) { + accel->emitStrings[i][currPos++] = faces[j]; + for (U32 k = 0; k < 3; k++) { + U32 old = cf.mFaceList[faces[j]].vertex[k]; + bool found = false; + for (U32 l = 0; l < vertRemaps.size(); l++) { + if (vertRemaps[l] == old) { + found = true; + accel->emitStrings[i][currPos++] = l; + break; + } + } + AssertFatal(found, "Error, couldn't find the remap!"); + } + } + AssertFatal(currPos == emitStringLen, "Error, over/underflowed the emission string!"); + } +} +#endif + diff --git a/ts/tsShape.h b/ts/tsShape.h new file mode 100644 index 0000000..ffbb5ac --- /dev/null +++ b/ts/tsShape.h @@ -0,0 +1,521 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSSHAPE_H_ +#define _TSSHAPE_H_ + +#ifndef _TSMESH_H_ +#include "ts/tsMesh.h" +#endif +#ifndef _TSDECAL_H_ +#include "ts/tsDecal.h" +#endif +#ifndef _TSINTEGERSET_H_ +#include "ts/tsIntegerSet.h" +#endif +#ifndef _TSTRANSFORM_H_ +#include "ts/tsTransform.h" +#endif +#ifndef _TSSHAPEALLOC_H_ +#include "ts/tsShapeAlloc.h" +#endif +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _RESMANAGER_H_ +#include "Core/resManager.h" +#endif +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif +#ifndef _STREAM_H_ +#include "Core/stream.h" +#endif + +#define DTS_EXPORTER_CURRENT_VERSION 123 + +class TSMaterialList; +class TSLastDetail; + +class TSShape : public ResourceInstance +{ + public: + enum { UniformScale=0x01, AlignedScale=0x02, ArbitraryScale=0x04, + Blend=0x08, Cyclic=0x10, MakePath=0x20, + IflInit=0x40, HasTranslucency=0x80, + AnyScale=UniformScale|AlignedScale|ArbitraryScale }; + + // Nodes hold the transforms in the shape tree... + struct Node + { + S32 nameIndex; + S32 parentIndex; + + // computed at runtime + S32 firstObject; + S32 firstChild; + S32 nextSibling; + }; + + // Objects hold renderable items (in particular meshes)... + // Each object has a number of meshes associated with it. + // Each mesh corresponds to a different detail level. + // "meshIndicesIndex" points to numMeshes consecutive indices + // into the meshList and meshType vectors. It indexes the + // meshIndexList vector (meshIndexList is merely a clearinghouse + // for the object's mesh lists). Some indices may correspond to + // no mesh -- which means no mesh will be drawn for the part for + // the given detail level. See comments on the meshIndexList + // for how null meshes are coded. + // Note: stored this way so that there is no address specific information. + struct Object + { + S32 nameIndex; + S32 numMeshes; + S32 startMeshIndex; // index into meshes array... + S32 nodeIndex; + + // computed at load + S32 nextSibling; + S32 firstDecal; + }; + + // Decals hang off objects like objects hang off nodes. A decal is rendered on + // top of an object (normally will be translucent). Note: they hang off objects + // conceptually...however, in the shapeInstance they are in their own list and + // that list is rendered after all the objects are. + struct Decal + { + S32 nameIndex; + S32 numMeshes; + S32 startMeshIndex; // index into meshes array... + S32 objectIndex; + + // computed at load + S32 nextSibling; + }; + + // IFL materials are used to animate material lists -- i.e., run through a series + // of frames of a material... they work by replacing a material in the material + // list so that it is transparent to the rest of the code. + // Offset time of each frame is stored in iflFrameOffsets vector, starting at index position + // firstFrameOffsetIndex.. + struct IflMaterial + { + S32 nameIndex; // file name w/ extension + S32 materialSlot; + S32 firstFrame; + S32 firstFrameOffTimeIndex; + S32 numFrames; + }; + + // A Sequence holds all the information necessary to perform a particular animation (sequence). + // Sequences index a range of keyframes...keyframes are assumed to be equally spaced in time. + // Each node and object is either a member of the sequence or not. If not, they are set to + // default values when we switch to the sequence unless they are members of some other active sequence. + // Blended sequences "add" a transform to the current transform of a node. Any object animation of + // a blended sequence over-rides any existing object state. Blended sequences are always + // applied after non-blended sequences. + struct Sequence + { + S32 nameIndex; + S32 numKeyframes; + F32 duration; + S32 baseRotation; + S32 baseTranslation; + S32 baseScale; + S32 baseObjectState; + S32 baseDecalState; + S32 firstGroundFrame; + S32 numGroundFrames; + S32 firstTrigger; + S32 numTriggers; + F32 toolBegin; + + // These bitsets code whether this sequence cares certain aspects of animation + // e.g., the rotation, translation, or scale of node transforms, + // or the visibility, frame or material frame of objects. + TSIntegerSet rotationMatters; // set of nodes + TSIntegerSet translationMatters; // set of nodes + TSIntegerSet scaleMatters; // set of nodes + TSIntegerSet visMatters; // set of objects + TSIntegerSet frameMatters; // set of objects + TSIntegerSet matFrameMatters; // set of objects + TSIntegerSet decalMatters; // set of decals + TSIntegerSet iflMatters; // set of ifls + + S32 priority; + U32 flags; + U32 dirtyFlags; // determined at load time + + bool testFlags(U32 comp) const { return (flags&comp)!=0; } + bool animatesScale() const { return testFlags(AnyScale); } + bool animatesUniformScale() const { return testFlags(UniformScale); } + bool animatesAlignedScale() const { return testFlags(AlignedScale); } + bool animatesArbitraryScale() const { return testFlags(ArbitraryScale); } + bool isBlend() const { return testFlags(Blend); } + bool isCyclic() const { return testFlags(Cyclic); } + bool makePath() const { return testFlags(MakePath); } + + void read(Stream *, bool readNameIndex = true); + void write(Stream *, bool writeNameIndex = true); + }; + + // Describes state of an individual object. Includes everything in an object that can be + // controlled by animation. + struct ObjectState + { + F32 vis; + S32 frameIndex; + S32 matFrameIndex; + }; + + // Describes state of a decal. + struct DecalState + { + S32 frameIndex; + }; + + // When time on a sequence advances past a certain point, a trigger takes effect and changes + // one of the state variables to on or off...(state variables found on shape instance mTriggerStates) + struct Trigger + { + enum { StateOn = 1 << 31, InvertOnReverse = 1 << 30, StateMask = 0x1f }; + + U32 state; // see above enum + F32 pos; + }; + + // Details are used for render detail selection. As the projected size of the shape changes, + // a different node structure can be used (subShape) and a different objectDetail can be selected + // for each object drawn. Either of these two parameters can also stay constant, but presumably + // not both. If size is negative then the detail level will never be selected by the standard + // detail selection process. It will have to be selected by name. Such details are "utility + // details" because they exist to hold data (node positions or collision information) but not + // normally to be drawn. By default there will always be a "Ground" utility detail. + struct Detail + { + S32 nameIndex; + S32 subShapeNum; + S32 objectDetailNum; + F32 size; + F32 averageError; + F32 maxError; + S32 polyCount; + }; + + // For speeding up buildpolylist and support calls. + struct ConvexHullAccelerator { + U32 numVerts; + Point3F* vertexList; + Point3F* normalList; + U8** emitStrings; + }; + ConvexHullAccelerator* getAccelerator(S32 dl); + + // shape vector data...non-resizable in game + ToolVector nodes; + ToolVector objects; + ToolVector decals; + ToolVector iflMaterials; + ToolVector objectStates; + ToolVector decalStates; + ToolVector subShapeFirstNode; + ToolVector subShapeFirstObject; + ToolVector subShapeFirstDecal; + ToolVector detailFirstSkin; + ToolVector subShapeNumNodes; + ToolVector subShapeNumObjects; + ToolVector subShapeNumDecals; + ToolVector details; + ToolVector defaultRotations; + ToolVector defaultTranslations; + + // set up at load time...but memory allocated along with loaded data + ToolVector subShapeFirstTranslucentObject; + ToolVector meshes; + ToolVector alphaIn; // these vectors describe how to transition between detail + ToolVector alphaOut; // levels using alpha..."alpha-in" next detail as intraDL goes + // from alphaIn+alphaOut to alphaOut..."alpha-out" current + // detail level as intraDL goes from alphaOut to 0. + // NOTE: + // intraDL is at 1 when if shape were any closer to us we'd be at dl-1, + // intraDL is at 0 when if shape were any farther away we'd be at dl+1 + + // re-sizeable vectors... + Vector sequences; + Vector nodeRotations; + Vector nodeTranslations; + Vector nodeUniformScales; + Vector nodeAlignedScales; + Vector nodeArbitraryScaleRots; + Vector nodeArbitraryScaleFactors; + Vector groundRotations; + Vector groundTranslations; + Vector triggers; + Vector iflFrameOffTimes; + Vector billboardDetails; + Vector detailCollisionAccelerators; + Vector names; + + // most vectors are stored in a single memory block + // except when compiled using MAX_UTIL defined + // in that case, ToolVector becomes Vector<> and the + // vectors are resizeable + S8 * mMemoryBlock; + + TSMaterialList * materialList; + + // bounding information + F32 radius; + F32 tubeRadius; + Point3F center; + Box3F bounds; + + // various... + U32 mExporterVersion; + F32 mSmallestVisibleSize; // computed at load time from details vector + S32 mSmallestVisibleDL; // ditto + S32 mReadVersion; // file version that this shape was read from + U32 mFlags; // hasTranslucancy, iflInit + U32 data; // let the user do whatever with this + + bool mSequencesConstructed; + S32 mVertexBuffer; + U32 mCallbackKey; + bool mExportMerge; + bool mMorphable; + Vector mPreviousMerge; + S32 mMergeBufferSize; + + // shape class has few methods -- + // just constructor/destructor, io, and lookup methods + + // constructor/destructor + TSShape(); + ~TSShape(); + void init(); + void initMaterialList(); // you can swap in a new material list, but call this if you do + void clearDynamicData(); + void setupBillboardDetails(); + + bool getSequencesConstructed() const { return mSequencesConstructed; } + void setSequencesConstructed(const bool c) { mSequencesConstructed = c; } + + // look up animation info -- indexed by keyframe number and offset (which objecct/node/decal + // of the animated objects/nodes/decals you want information for). + QuatF & getRotation(const Sequence & seq, S32 keyframeNum, S32 rotNum, QuatF *) const; + const Point3F & getTranslation(const Sequence & seq, S32 keyframeNum, S32 tranNum) const; + F32 getUniformScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum) const; + const Point3F & getAlignedScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum) const; + TSScale & getArbitraryScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum, TSScale *) const; + const ObjectState & getObjectState(const Sequence & seq, S32 keyframeNum, S32 objectNum) const; + const DecalState & getDecalState(const Sequence & seq, S32 keyframeNum, S32 decalNum) const; + + // build LOS collision detail + void computeAccelerator(S32 dl); + bool buildConvexHull(S32 dl) const; + void computeBounds(S32 dl, Box3F & bounds) const; // uses default transforms to compute bounding box around a detail level + // see like named method on shapeInstance if you want to use animated transforms + + // lookup methods + S32 findName(const char *) const; + const char * getName(S32) const; + + S32 findNode(S32 nameIndex) const; + S32 findNode(const char * name) const { return findNode(findName(name)); } + + S32 findObject(S32 nameIndex) const; + S32 findObject(const char * name) const { return findObject(findName(name)); } + + S32 findDecal(S32 nameIndex) const; + S32 findDecal(const char * name) const { return findDecal(findName(name)); } + + S32 findIflMaterial(S32 nameIndex) const; + S32 findIflMaterial(const char * name) const { return findIflMaterial(findName(name)); } + + S32 findDetail(S32 nameIndex) const; + S32 findDetail(const char * name) const { return findDetail(findName(name)); } + + S32 findSequence(S32 nameIndex) const; + S32 findSequence(const char * name) const { return findSequence(findName(name)); } + + bool hasTranslucency() const { return mFlags & HasTranslucency; } + + // these control default values for alpha transitions between detail levels + static F32 smAlphaOutLastDetail; + static F32 smAlphaInBillboard; + static F32 smAlphaOutBillboard; + static F32 smAlphaInDefault; + static F32 smAlphaOutDefault; + + // don't load this many of the highest detail levels (although we always + // load one renderable detail if there is one) + static S32 smNumSkipLoadDetails; + + // by default we initialize shape when we read... + static bool smInitOnRead; + + // version info + // -- smVersion is most recent version...the one we write + // -- smReadVersion is version currently being read + // smReadVersion is only valid during a read + static S32 smVersion; + static S32 smReadVersion; + static const U32 smMostRecentExporterVersion; + + // persist methods + void write(Stream *); + bool read(Stream *); + void readOldShape(Stream * s, S32 * &, S16 * &, S8 * &, S32 &, S32 &, S32 &); + void writeName(Stream *, S32 nameIndex); + S32 readName(Stream *, bool addName); + + void exportSequences(Stream *); + bool importSequences(Stream *); + + void readIflMaterials(); + + // persist helper functions + static TSShapeAlloc alloc; + void fixEndian(S32 *, S16 *, S8 *, S32, S32, S32); + + // memory buffer transfer methods (uses TSShape::Alloc structure) + void assembleShape(); + void disassembleShape(); + + // mem buffer transfer helper (indicate when we don't want to include a particular mesh/decal) + bool checkSkip(S32 meshNum, S32 & curObject, S32 & curDecal, S32 skipDL); + + // used when reading old shapes/sequences + void rearrangeKeyframeData(Sequence &, S32 keyframeStart, U8 * pns32 = NULL, U8 * pns16 = NULL, U8 * pos = NULL, U8 * pds = NULL, S32 szNS32=-1, S32 szNS16=-1, S32 szOS32=-1, S32 szDS32=-1); + void rearrangeStates(S32 start, S32 rows, S32 cols, U8 * data, S32 size); + + void fixupOldSkins(S32 numMeshes, S32 numSkins, S32 numDetails, S32 * detailFirstSkin, S32 * detailNumSkins); +}; + +class TSMaterialList : public MaterialList +{ + typedef MaterialList Parent; + + Vector mFlags; + Vector mReflectanceMaps; + Vector mBumpMaps; + Vector mDetailMaps; + Vector mDetailScales; + Vector mReflectionAmounts; + + bool mNamesTransformed; + + void allocate(U32 sz); + + public: + static const char* csmTSTexturePrefix; // currently "skins/", set in tsShape.cc + static const char* csmOldTSTexturePrefix; // currently "", set in tsShape.cc + static const char* csmIFLTexturePrefix; + + public: + + enum + { + S_Wrap = 1 << 0, + T_Wrap = 1 << 1, + Translucent = 1 << 2, + Additive = 1 << 3, + Subtractive = 1 << 4, + SelfIlluminating = 1 << 5, + NeverEnvMap = 1 << 6, + NoMipMap = 1 << 7, + MipMap_ZeroBorder = 1 << 8, + IflMaterial = 1 << 27, + IflFrame = 1 << 28, + DetailMapOnly = 1 << 29, + BumpMapOnly = 1 << 30, + ReflectanceMapOnly = 1 << 31, + AuxiliaryMap = DetailMapOnly | BumpMapOnly | ReflectanceMapOnly + }; + + TSMaterialList(U32 materialCount, const char **materialNames, const U32 * materialFlags, + const U32 * reflectanceMaps, const U32 * bumpMaps, const U32 * detailMaps, + const F32 * detailScales, const F32 * reflectionAmounts); + TSMaterialList(); + TSMaterialList(const TSMaterialList*); + ~TSMaterialList(); + void free(); + + void tsmlTransform(); + + void load(U32 index); + bool load(TextureHandleType type, bool clampToEdge = false) { return Parent::load(type,clampToEdge); } + + TextureHandle * getReflectionMap(U32 index) { return mReflectanceMaps[index] == 0xFFFFFFFF ? NULL : &getMaterial(mReflectanceMaps[index]); } + F32 getReflectionAmount(U32 index) { return mReflectionAmounts[index]; } + TextureHandle * getBumpMap(U32 index) { return mBumpMaps[index] == 0xFFFFFFFF ? NULL : &getMaterial(mBumpMaps[index]); } + TextureHandle * getDetailMap(U32 index) { return mDetailMaps[index] == 0xFFFFFFFF ? NULL : &getMaterial(mDetailMaps[index]); } + F32 getDetailMapScale(U32 index) { return mDetailScales[index]; } + bool reflectionInAlpha(U32 index) { return mReflectanceMaps[index] == index; } + + U32 getFlags(U32 index); + void setFlags(U32 index, U32 value); + + void remap(U32 toIndex, U32 fromIndex); // support for ifl sequences + + // pre-load only ... support for ifl sequences + void push_back(const char * name, U32 flags, + U32 a=0xFFFFFFFF, U32 b=0xFFFFFFFF, U32 c=0xFFFFFFFF, + F32 dm=1.0f, F32 em=1.0f); + + bool write(Stream &); + bool read(Stream &); +}; + +extern ResourceInstance *constructTSShape(Stream &stream); + +#define TSNode TSShape::Node +#define TSObject TSShape::Object +#define TSDecal TSShape::Decal +#define TSSequence TSShape::Sequence +#define TSDetail TSShape::Detail + +inline QuatF & TSShape::getRotation(const Sequence & seq, S32 keyframeNum, S32 rotNum, QuatF * quat) const +{ + return nodeRotations[seq.baseRotation + rotNum*seq.numKeyframes + keyframeNum].getQuatF(quat); +} + +inline const Point3F & TSShape::getTranslation(const Sequence & seq, S32 keyframeNum, S32 tranNum) const +{ + return nodeTranslations[seq.baseTranslation + tranNum*seq.numKeyframes + keyframeNum]; +} + +inline F32 TSShape::getUniformScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum) const +{ + return nodeUniformScales[seq.baseScale + scaleNum*seq.numKeyframes + keyframeNum]; +} + +inline const Point3F & TSShape::getAlignedScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum) const +{ + return nodeAlignedScales[seq.baseScale + scaleNum*seq.numKeyframes + keyframeNum]; +} + +inline TSScale & TSShape::getArbitraryScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum, TSScale * scale) const +{ + nodeArbitraryScaleRots[seq.baseScale + scaleNum*seq.numKeyframes + keyframeNum].getQuatF(&scale->mRotate); + scale->mScale = nodeArbitraryScaleFactors[seq.baseScale + scaleNum*seq.numKeyframes + keyframeNum]; + return *scale; +} + +inline const TSShape::ObjectState & TSShape::getObjectState(const TSSequence & seq, S32 keyframeNum, S32 objectNum) const +{ + return objectStates[seq.baseObjectState + objectNum*seq.numKeyframes + keyframeNum]; +} + +inline const TSShape::DecalState & TSShape::getDecalState(const TSSequence & seq, S32 keyframeNum, S32 decalNum) const +{ + return decalStates[seq.baseDecalState + decalNum*seq.numKeyframes + keyframeNum]; +} + +#endif diff --git a/ts/tsShapeAlloc.cc b/ts/tsShapeAlloc.cc new file mode 100644 index 0000000..c2e5a36 --- /dev/null +++ b/ts/tsShapeAlloc.cc @@ -0,0 +1,211 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShapeAlloc.h" + +#define readOnly() AssertFatal(mMode==TSShapeAlloc::ReadMode, "TSShapeAlloc: write-only function called when reading") +#define writeOnly() AssertFatal(mMode==TSShapeAlloc::WriteMode,"TSShapeAlloc: read-only function called when writing") + +void TSShapeAlloc::setRead(S32 * memBuffer32, S16 * memBuffer16, S8 * memBuffer8, bool clear) +{ + mMemBuffer32 = memBuffer32; + mMemBuffer16 = memBuffer16; + mMemBuffer8 = memBuffer8 ; + + mMemGuard32 = 0; + mMemGuard16 = 0; + mMemGuard8 = 0; + + mSaveGuard32 = 0; + mSaveGuard16 = 0; + mSaveGuard8 = 0; + + if (clear) + { + mDest = NULL; + mSize = 0; + } + + setSkipMode(false); + mMode = TSShapeAlloc::ReadMode; +} + +void TSShapeAlloc::setWrite() +{ + mMemBuffer32 = 0; + mMemBuffer16 = 0; + mMemBuffer8 = 0; + + mSize32 = mFullSize32 = 0; + mSize16 = mFullSize16 = 0; + mSize8 = mFullSize8 = 0; + + mMemGuard32 = 0; + mMemGuard16 = 0; + mMemGuard8 = 0; + + setSkipMode(false); // doesn't really do anything here... + mMode = TSShapeAlloc::WriteMode; +} + +void TSShapeAlloc::doAlloc() +{ + readOnly(); + + mDest = new S8[mSize]; + mSize = 0; +} + +void TSShapeAlloc::align32() +{ + readOnly(); + + S32 aligned = mSize+3 & (~0x3); + allocShape8(aligned-mSize); +} + +#define IMPLEMENT_ALLOC(suffix,type) \ + \ +type TSShapeAlloc::get##suffix() \ +{ \ + readOnly(); \ + return *(mMemBuffer##suffix++); \ +} \ + \ +void TSShapeAlloc::get##suffix(type * dest, S32 num) \ +{ \ + readOnly(); \ + dMemcpy(dest,mMemBuffer##suffix,sizeof(type)*num); \ + mMemBuffer##suffix += num; \ +} \ + \ +type * TSShapeAlloc::allocShape##suffix(S32 num) \ +{ \ + readOnly(); \ + type * ret = (type*) mDest; \ + if (mDest) \ + mDest += mMult*num*sizeof(type); \ + mSize += sizeof(type)*mMult*num; \ + return ret; \ +} \ + \ +type * TSShapeAlloc::getPointer##suffix(S32 num) \ +{ \ + readOnly(); \ + type * ret = (type*)mMemBuffer##suffix; \ + mMemBuffer##suffix += num; \ + return ret; \ +} \ + \ +type * TSShapeAlloc::copyToShape##suffix(S32 num, bool returnSomething) \ +{ \ + readOnly(); \ + type * ret = (!returnSomething || mDest) ? (type*)mDest : mMemBuffer##suffix; \ + if (mDest) \ + { \ + dMemcpy((S8*)mDest,(S8*)mMemBuffer##suffix, \ + mMult*num*sizeof(type)); \ + mDest += mMult*num*sizeof(type); \ + } \ + mMemBuffer##suffix += num; \ + mSize += sizeof(type)*mMult*num; \ + return ret; \ +} \ + \ +bool TSShapeAlloc::checkGuard##suffix() \ +{ \ + readOnly(); \ + mSaveGuard##suffix=get##suffix(); \ + bool ret = (mSaveGuard##suffix==mMemGuard##suffix); \ + mMemGuard##suffix += 1; \ + return ret; \ +} \ + \ +type TSShapeAlloc::getPrevGuard##suffix() \ +{ \ + readOnly(); \ + return mMemGuard##suffix - 1; \ +} \ + \ +type TSShapeAlloc::getSaveGuard##suffix() \ +{ \ + readOnly(); \ + return mSaveGuard##suffix; \ +} \ + \ +type * TSShapeAlloc::getBuffer##suffix() \ +{ \ + writeOnly(); \ + return mMemBuffer##suffix; \ +} \ + \ +S32 TSShapeAlloc::getBufferSize##suffix() \ +{ \ + writeOnly(); \ + return mSize##suffix; \ +} \ + \ +type * TSShapeAlloc::extend##suffix(S32 add) \ +{ \ + writeOnly(); \ + if (mSize##suffix+add>mFullSize##suffix) \ + { \ + S32 numPages = 1+(mFullSize##suffix+add)/TSShapeAlloc::PageSize; \ + mFullSize##suffix = numPages*TSShapeAlloc::PageSize; \ + type * temp = new type[mFullSize##suffix]; \ + dMemcpy(temp,mMemBuffer##suffix, mSize##suffix * sizeof(type)); \ + delete [] mMemBuffer##suffix; \ + mMemBuffer##suffix = temp; \ + } \ + type * ret = mMemBuffer##suffix + mSize##suffix; \ + mSize##suffix += add; \ + return ret; \ +} \ + \ +type TSShapeAlloc::set##suffix(type entry) \ +{ \ + writeOnly(); \ + *extend##suffix(1) = entry; \ + return entry; \ +} \ + \ +void TSShapeAlloc::copyToBuffer##suffix(type * entries, S32 count) \ +{ \ + writeOnly(); \ + dMemcpy((U8*)extend##suffix(count),(U8*)entries,count*sizeof(type)); \ +} \ + \ +void TSShapeAlloc::setGuard##suffix() \ +{ \ + writeOnly(); \ + set##suffix(mMemGuard##suffix); \ + mMemGuard##suffix += 1; \ +} + +IMPLEMENT_ALLOC(32,S32) +IMPLEMENT_ALLOC(16,S16) +IMPLEMENT_ALLOC(8,S8) + +void TSShapeAlloc::checkGuard() +{ + bool check32 = checkGuard32(); + bool check16 = checkGuard16(); + bool check8 = checkGuard8(); + + AssertFatal(check32,avar("TSShapeAlloc::checkGuard32: found %i, wanted %i",getSaveGuard32(),getPrevGuard32())); + AssertFatal(check16,avar("TSShapeAlloc::checkGuard16: found %i, wanted %i",getSaveGuard16(),getPrevGuard16())); + AssertFatal(check8 ,avar("TSShapeAlloc::checkGuard8: found %i, wanted %i",getSaveGuard8() ,getPrevGuard8())); +} + +void TSShapeAlloc::setGuard() +{ + setGuard32(); + setGuard16(); + setGuard8(); +} + + diff --git a/ts/tsShapeAlloc.h b/ts/tsShapeAlloc.h new file mode 100644 index 0000000..a4cab65 --- /dev/null +++ b/ts/tsShapeAlloc.h @@ -0,0 +1,126 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSSHAPEALLOC_H_ +#define _TSSHAPEALLOC_H_ + +#ifndef _PLATFORM_H_ +#include "Platform/platform.h" +#endif +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif + +// alloc structure used in the reading/writing of shapes +// in read mode we assemble contents of 32-bit, 16-bit, and 8-bit buffers +// into a single destination buffer +// in write mode we dissemble a stream of memory (which may be scattered in physical memory) +// into 32-bit, 16-bit, 8-bit, Point3F, and Point2F buffers using function calls similar +// to the read calls + +// read usage: +// 1. call "setRead" with each incoming memory buffers and clear=true +// 2. run through set of operations for allocating and transfering memory to target buffer +// these are the operations under "DECLARE_ALLOC" that call readOnly in the .cc file +// 3. call "doAlloc" to create buffer exactly as large as needed +// 4. repeat step 1 & 2 to do the actual transfer of memory, except with clear=false +// (note: first time through nothing was copied to the shape, we only kept track +// of the size of the transfer). +// 5. call getBuffer to get the target (destination buffer) + +// write usage: +// 1. call "setWrite" (no parameters) +// 2. run through set of operations for allocating and transfering memory to internal buffers +// these are the operations under "DECLARE_ALLOC" that call writeOnly in the .cc file +// 3. call getBuffer32 and getBufferSize32 to get 32-bit buffer and size. Similarly for +// 16-bit, 8-bit (getBuffer16, getBuffer8) + +// TSShape::assesmbleShape and TSShape::dissembleShape can be used as examples + +class TSShapeAlloc +{ + S32 mMode; // read or write + + // reading and writing (when reading these are the input; when writing these are the output) + S32 * mMemBuffer32; + S16 * mMemBuffer16; + S8 * mMemBuffer8; + + // for writing only... + S32 mSize32; + S32 mSize16; + S32 mSize8; + S32 mFullSize32; + S32 mFullSize16; + S32 mFullSize8; + + // reading and writing... + S32 mMemGuard32; + S16 mMemGuard16; + S8 mMemGuard8; + + // reading + S32 mSaveGuard32; + S16 mSaveGuard16; + S8 mSaveGuard8; + + // reading only...this is the output + S8 * mDest; + S32 mSize; + S32 mMult; // mult incoming sizes by this (when 0, then mDest doesn't grow --> skip mode) + + public: + + enum { ReadMode = 0, WriteMode = 1, PageSize = 1024 }; // PageSize must be multiple of 4 so that we can always + // "over-read" up to next dword + + void setRead(S32 * buff32, S16 * buff16, S8 * buff8, bool clear); + void setWrite(); + + // reading only... + void doAlloc(); + void align32(); // align on dword boundary + S8 * getBuffer() { return mDest; } + S32 getSize() { return mSize; } + void setSkipMode(bool skip) { mMult = skip ? 0 : 1; } + + // reading operations: + // get: reads one or more entries of type from input buffer (doesn't affect output buffer) + // copyToShape: copies entries of type from input buffer to output buffer + // allocShape: creates room for entries of type in output buffer (no effect on input buffer) + // getPointer: gets pointer to next entries of type in input buffer (no effect on input buffer) + // Note: all operations advance current "position" of input and output buffers + // writing operations: + // set: adds one entry to appropriate buffer + // copyToBuffer: adds count entries to approrpiate buffer + // getBuffer: returns associated buffer (i.e., getBuffer32 gets 32bit buffer) + // getBufferSize: returns size of associated buffer + #define DECLARE_ALLOC(suffix,type) \ + type get##suffix(); \ + void get##suffix(type*,S32); \ + type * copyToShape##suffix(S32,bool returnSomething=false); \ + type * getPointer##suffix(S32); \ + type * allocShape##suffix(S32); \ + bool checkGuard##suffix(); \ + type getPrevGuard##suffix(); \ + type getSaveGuard##suffix(); \ + type * getBuffer##suffix(); \ + S32 getBufferSize##suffix(); \ + void setGuard##suffix(); \ + type * extend##suffix(S32); \ + type set##suffix(type); \ + void copyToBuffer##suffix(type*,S32); + + DECLARE_ALLOC(32,S32) + DECLARE_ALLOC(16,S16) + DECLARE_ALLOC(8,S8) + + void checkGuard(); + void setGuard(); +}; + +#endif // _H_TS_SHAPE_ALLOC_ diff --git a/ts/tsShapeConstruct.cc b/ts/tsShapeConstruct.cc new file mode 100644 index 0000000..8632a66 --- /dev/null +++ b/ts/tsShapeConstruct.cc @@ -0,0 +1,168 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShapeConstruct.h" +#include "console/consoleTypes.h" +#include "Core/fileStream.h" +#include "Core/bitStream.h" + +IMPLEMENT_CO_DATABLOCK_V1(TSShapeConstructor); + +const char * TSShapeConstructor::csmShapeDirectory = "shapes/"; + +S32 gNumTransforms = 0; +S32 gRotTransforms = 0; +S32 gTransTransforms = 0; + +TSShapeConstructor::TSShapeConstructor() +{ + mShape = NULL; + for (S32 i = 0; iload(fileBuf); + if(!bool(hShape)) + return false; + + if (hShape->getSequencesConstructed()) + return true; + + // we want to write into the resource: + TSShape * shape = const_cast((const TSShape*)hShape); + + Stream * f; + for (S32 i=0; ifindSequence(nameStart)) +// continue; +// } + + + // look for sequences in same directory as shapes... + dSprintf(fileBuf, sizeof(fileBuf), "%s%s", csmShapeDirectory, mSequence[i]); + // spaces and tabs indicate the end of the file name: + char * terminate1 = dStrchr(fileBuf,' '); + char * terminate2 = dStrchr(fileBuf,'\t'); + if (terminate1 && terminate2) + // select the earliest one: + *(terminate1openStream(fileBuf); + if (!f || f->getStatus() != Stream::Ok) + { + Con::errorf(ConsoleLogEntry::General,"Missing sequence %s for %s",mSequence[i],mShape); + continue; + } + if (!shape->importSequences(f) || f->getStatus()!=Stream::Ok) + { + Con::errorf(ConsoleLogEntry::General,"Load sequence %s failed for %s",mSequence[i],mShape); + return false; + } + ResourceManager->closeStream(f); + + // if there was a space or tab in mSequence[i], then a new name was supplied for it + // change it here...will still be in fileBuf + if (terminate1 || terminate2) + { + // select the latter one: + char * nameStart = terminate1sequences.last().nameIndex = shape->findName(nameStart); + if (shape->sequences.last().nameIndex == -1) + { + shape->sequences.last().nameIndex = shape->names.size(); + shape->names.increment(); + shape->names.last() = StringTable->insert(nameStart,false); + } + } + } + else + break; + } + + hShape->setSequencesConstructed(true); + return true; +} + +void TSShapeConstructor::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->writeString(mShape); + + S32 count = 0; + for (S32 b=0; bwriteInt(count,NumSequenceBits); + + for (S32 i=0; iwriteString(mSequence[i]); +} + +void TSShapeConstructor::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mShape = stream->readSTString(); + + S32 i = 0, count = stream->readInt(NumSequenceBits); + for (; ireadSTString(); + while (i hShape; + +public: + + TSShapeConstructor(); + ~TSShapeConstructor(); + bool onAdd(); + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + DECLARE_CONOBJECT(TSShapeConstructor); + static void consoleInit(); + static void initPersistFields(); + + static const char * csmShapeDirectory; +}; + +#endif diff --git a/ts/tsShapeInstance.cc b/ts/tsShapeInstance.cc new file mode 100644 index 0000000..f06779a --- /dev/null +++ b/ts/tsShapeInstance.cc @@ -0,0 +1,1833 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShapeInstance.h" +#include "dgl/dgl.h" +#include "ts/tsLastDetail.h" +#include "console/consoleTypes.h" +#include "ts/tsDecal.h" +#include "platform/profiler.h" +#include "sim/frameAllocator.h" + +TSShapeInstance::RenderData TSShapeInstance::smRenderData; +MatrixF * TSShapeInstance::ObjectInstance::smTransforms = NULL; +S32 TSShapeInstance::smMaxSnapshotScale = 2; +bool TSShapeInstance::smNoRenderTranslucent = false; +bool TSShapeInstance::smNoRenderNonTranslucent = false; +F32 TSShapeInstance::smDetailAdjust = 1.0f; +F32 TSShapeInstance::smScreenError = 5.0f; +S32 TSShapeInstance::smNumSkipRenderDetails = 0; +bool TSShapeInstance::smSkipFirstFog = false; +bool TSShapeInstance::smSkipFog = false; + +Vector TSShapeInstance::smNodeCurrentRotations(__FILE__, __LINE__); +Vector TSShapeInstance::smNodeCurrentTranslations(__FILE__, __LINE__); +Vector TSShapeInstance::smNodeCurrentUniformScales(__FILE__, __LINE__); +Vector TSShapeInstance::smNodeCurrentAlignedScales(__FILE__, __LINE__); +Vector TSShapeInstance::smNodeCurrentArbitraryScales(__FILE__, __LINE__); + +Vector TSShapeInstance::smRotationThreads(__FILE__, __LINE__); +Vector TSShapeInstance::smTranslationThreads(__FILE__, __LINE__); +Vector TSShapeInstance::smScaleThreads(__FILE__, __LINE__); + +namespace { + +void tsShapeTextureEventCB(const U32 eventCode, const U32 userData) +{ + TSShape* pShape = reinterpret_cast(userData); + + if (eventCode == TextureManager::BeginZombification && + pShape->mVertexBuffer != -1) + { + // ideally we would de-register the callback here, but that would screw up the loop + if (dglDoesSupportVertexBuffer()) + glFreeVertexBufferEXT(pShape->mVertexBuffer); + else + AssertFatal(false,"Vertex buffer should have already been freed!"); + pShape->mVertexBuffer = -1; + for (U32 i = 0; i < pShape->objects.size(); ++i) + pShape->mPreviousMerge[i] = -1; + } +} +} + +//------------------------------------------------------------------------------------- +// constructors, destructors, initialization +//------------------------------------------------------------------------------------- + +TSShapeInstance::TSShapeInstance(const Resource & shape, bool loadMaterials) +{ + VECTOR_SET_ASSOCIATION(mMeshObjects); + VECTOR_SET_ASSOCIATION(mDecalObjects); + VECTOR_SET_ASSOCIATION(mIflMaterialInstances); + VECTOR_SET_ASSOCIATION(mNodeTransforms); + VECTOR_SET_ASSOCIATION(mNodeReferenceRotations); + VECTOR_SET_ASSOCIATION(mNodeReferenceTranslations); + VECTOR_SET_ASSOCIATION(mNodeReferenceUniformScales); + VECTOR_SET_ASSOCIATION(mNodeReferenceScaleFactors); + VECTOR_SET_ASSOCIATION(mNodeReferenceArbitraryScaleRots); + VECTOR_SET_ASSOCIATION(mThreadList); + VECTOR_SET_ASSOCIATION(mTransitionThreads); + + hShape = shape; + mShape = hShape; + buildInstanceData(mShape, loadMaterials); +} + +TSShapeInstance::TSShapeInstance(TSShape * _shape, bool loadMaterials) +{ + VECTOR_SET_ASSOCIATION(mMeshObjects); + VECTOR_SET_ASSOCIATION(mDecalObjects); + VECTOR_SET_ASSOCIATION(mIflMaterialInstances); + VECTOR_SET_ASSOCIATION(mNodeTransforms); + VECTOR_SET_ASSOCIATION(mNodeReferenceRotations); + VECTOR_SET_ASSOCIATION(mNodeReferenceTranslations); + VECTOR_SET_ASSOCIATION(mNodeReferenceUniformScales); + VECTOR_SET_ASSOCIATION(mNodeReferenceScaleFactors); + VECTOR_SET_ASSOCIATION(mNodeReferenceArbitraryScaleRots); + VECTOR_SET_ASSOCIATION(mThreadList); + VECTOR_SET_ASSOCIATION(mTransitionThreads); + + mShape = _shape; + buildInstanceData(mShape, loadMaterials); +} + +TSShapeInstance::~TSShapeInstance() +{ + U32 i; + for (i=0; isetupBillboardDetails(); + setMaterialList(mShape->materialList); + } + + // set up node data + S32 numNodes = mShape->nodes.size(); + mNodeTransforms.setSize(numNodes); + + // add objects to trees + S32 numObjects = mShape->objects.size(); + mMeshObjects.setSize(numObjects); + for (i=0; iobjects[i]; + MeshObjectInstance * objInst = &mMeshObjects[i]; + + // call objInst constructor + constructInPlace(objInst); + + // hook up the object to it's node + objInst->nodeIndex = obj->nodeIndex; + + // set up list of meshes + if (obj->numMeshes) + objInst->meshList = &mShape->meshes[obj->startMeshIndex]; + else + objInst->meshList = NULL; + + objInst->object = obj; + } + + // set up decal objects + mDecalObjects.setSize(mShape->decals.size()); + for (i=0; idecals.size(); i++) + { + const TSShape::Decal * decal = &mShape->decals[i]; + DecalObjectInstance * decalInst = &mDecalObjects[i]; + + // call constructor + constructInPlace(decalInst); + decalInst->decalObject = decal; + + // hook up to node + decalInst->targetObject = &mMeshObjects[decal->objectIndex]; + decalInst->nodeIndex = decalInst->targetObject->nodeIndex; + + // set up list of decal meshes + if (decal->numMeshes) + { + decalInst->decalList = (TSDecalMesh**)&mShape->meshes[decal->startMeshIndex]; + for (S32 j=0; jnumMeshes; j++) + if (decalInst->getDecalMesh(j)) + { + // point the decal mesh at it's target... + // this is safe since meshes aren't shared between shapes + TSDecalMesh * decalMesh = const_cast(decalInst->getDecalMesh(j)); + decalMesh->targetMesh = decalInst->targetObject->getMesh(j); + if (!decalMesh->targetMesh) + { + // detecting this a little late, but we don't need this decal since it isn't doing anything + // should only happen on shapes exported before dtsexp 1.18 + delete decalMesh; + TSDecalMesh ** dm = const_cast(decalInst->decalList+j); + *dm = NULL; + } + } + } + else + decalInst->decalList = NULL; + decalInst->frame = mShape->decalStates[i].frameIndex; + } + + // construct ifl material objects + if(loadMaterials) + { + for (i=0; iiflMaterials.size(); i++) + { + mIflMaterialInstances.increment(); + mIflMaterialInstances.last().iflMaterial = &mShape->iflMaterials[i]; + mIflMaterialInstances.last().frame = -1; + } + } + // check to see which dl's have detail texturing + mMaxDetailMapDL = -1; + if(loadMaterials) + { + for (dl=0; dldetails.size(); dl++) + { + // check meshes on this detail level... + S32 ss = mShape->details[dl].subShapeNum; + S32 od = mShape->details[dl].objectDetailNum; + if (ss<0) + continue; // this is a billboard detail level + S32 start = mShape->subShapeFirstObject[ss]; + S32 end = mShape->subShapeNumObjects[ss] + start; + for (i=start; iprimitives.size(); j++) + { + if (mesh->primitives[j].matIndex & TSDrawPrimitive::NoMaterial) + continue; + if (mMaterialList->getDetailMap(mesh->primitives[j].matIndex & TSDrawPrimitive::MaterialMask)) + { + mesh->setFlags(TSMesh::HasDetailTexture); + if (dl>mMaxDetailMapDL) + mMaxDetailMapDL = dl; + } + } + } + } + } + + // set up subtree data + S32 ss = mShape->subShapeFirstNode.size(); // we have this many subtrees + mDirtyFlags = new U32[ss]; + + mGroundThread = NULL; + mCurrentDetailLevel = 0; + + animateSubtrees(); +} + +void TSShapeInstance::setMaterialList(TSMaterialList * ml) +{ + // get rid of old list + if (mOwnMaterialList) + delete mMaterialList; + mMaterialList = ml; + mOwnMaterialList = false; + + if (mMaterialList && StringTable) // material lists need the string table to load... + { + // read ifl materials if necessary -- this is here rather than in shape because we can't open 2 files at once :( + if (mShape->materialList == mMaterialList) + ((TSShape*)mShape)->readIflMaterials(); + + // Transform the names in the material list if necessary + mMaterialList->tsmlTransform(); + mMaterialList->load(MeshTexture,true); + + // check for reflectance map not in alpha of texture -- will require more work to emap + for (S32 i=0; igetMaterialCount(); i++) + { + if (mMaterialList->getFlags(i) & (TSMaterialList::AuxiliaryMap|TSMaterialList::NeverEnvMap)) + continue; + if (!mMaterialList->reflectionInAlpha(i)) + { + mAlphaIsReflectanceMap = false; + break; // found our exception + } + } + } +} + +void TSShapeInstance::cloneMaterialList() +{ + if (mOwnMaterialList) + return; + mMaterialList = new TSMaterialList(mMaterialList); + mOwnMaterialList = true; +} + +//------------------------------------------------------------------------------------- +// Render & detail selection +//------------------------------------------------------------------------------------- +void TSShapeInstance::render(const Point3F * objectScale) +{ + if (mCurrentDetailLevel<0) + return; + PROFILE_START(TSShapeInstanceRender); + dglSetRenderPrimType(3); + + // alphaIn: we start to alpha-in next detail level when intraDL > 1-alphaIn-alphaOut + // (finishing when intraDL = 1-alphaOut) + // alphaOut: start to alpha-out this detail level when intraDL > 1-alphaOut + // NOTE: + // intraDL is at 1 when if shape were any closer to us we'd be at dl-1, + // intraDL is at 0 when if shape were any farther away we'd be at dl+1 + F32 alphaOut = mShape->alphaOut[mCurrentDetailLevel]; + F32 alphaIn = mShape->alphaIn[mCurrentDetailLevel]; + F32 saveAA = mAlphaAlways ? mAlphaAlwaysValue : 1.0f; + + if (mCurrentIntraDetailLevel>alphaIn+alphaOut) + render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale); + else if (mCurrentIntraDetailLevel>alphaOut) + { + // draw this detail level w/ alpha=1 and next detail level w/ + // alpha=1-(intraDl-alphaOut)/alphaIn + + // first draw next detail level + if (mCurrentDetailLevel+1details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f) + { + setAlphaAlways(saveAA * (alphaIn+alphaOut-mCurrentIntraDetailLevel)/alphaIn); + render(mCurrentDetailLevel+1,0.0f,objectScale); + } + + setAlphaAlways(saveAA); + render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale); + } + else + { + // draw next detail level w/ alpha=1 and this detail level w/ + // alpha = 1-intraDL/alphaOut + + // first draw next detail level + if (mCurrentDetailLevel+1details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f) + render(mCurrentDetailLevel+1,0.0f,objectScale); + + setAlphaAlways(saveAA * mCurrentIntraDetailLevel / alphaOut); + render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale); + setAlphaAlways(saveAA); + } + dglSetRenderPrimType(0); + PROFILE_END(); +} + +bool TSShapeInstance::hasTranslucency() +{ + if(!mShape->details.size()) + return false; + + const TSDetail * detail = &mShape->details[0]; + S32 ss = detail->subShapeNum; + + return mShape->subShapeFirstTranslucentObject[ss] != mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss]; +} + +bool TSShapeInstance::hasSolid() +{ + if(!mShape->details.size()) + return false; + + const TSDetail * detail = &mShape->details[0]; + S32 ss = detail->subShapeNum; + + return mShape->subShapeFirstTranslucentObject[ss] != mShape->subShapeFirstObject[ss]; +} + +void TSShapeInstance::render(S32 dl, F32 intraDL, const Point3F * objectScale) +{ + // if dl==-1, nothing to do + if (dl==-1) + return; + + AssertFatal(dl>=0 && dldetails.size(),"TSShapeInstance::render"); + + S32 i; + + const TSDetail * detail = &mShape->details[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + // set up static data + setStatics(dl,intraDL,objectScale); + + // if we're a billboard detail, draw it and exit + PROFILE_START(TSShapeInstanceRenderBillboards); + if (ss<0) + { + if (!smNoRenderTranslucent) + mShape->billboardDetails[dl]->render(mAlphaAlways ? mAlphaAlwaysValue : 1.0f, smRenderData.fogOn); + PROFILE_END(); + return; + } + PROFILE_END(); + + PROFILE_START(TSShapeInstanceMaterials); + // set up animating ifl materials + for (i=0; iiflMaterial; + mMaterialList->remap(iflMaterial->materialSlot, iflMaterial->firstFrame + iflMaterialInstance->frame); + } + + // decide how to use gl resources + setupTexturing(dl,intraDL); + + // set up gl environment for drawing mesh materials + TSMesh::initMaterials(); + PROFILE_END(); + + S32 start; + S32 end; + + bool supportBuffers = dglDoesSupportVertexBuffer(); + if (!supportBuffers || !renderMeshesX(ss,od)) + { + // run through the meshes + smRenderData.currentTransform = NULL; + S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss]; + S32 end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss]; + for (i=start; isubShapeFirstDecal[ss]; + end = mShape->subShapeNumDecals[ss] + start; + if( smRenderData.renderDecals && !smNoRenderTranslucent && startmVertexBuffer) == -1) + { + // find out before we calc the needed buffer size if there are any free + if (!glAvailableVertexBufferEXT()) + { + PROFILE_END(); + return false; + } + + GLsizei size = 0; + + start = mShape->subShapeFirstObject[0]; + end = mShape->subShapeFirstObject[0] + mShape->subShapeNumObjects[0]; + for (i = start; i < end; ++i) + size += mMeshObjects[i].getSizeVB(size); + + mShape->mMorphable = false; + for (i = start; i < end; ++i) + if (mMeshObjects[i].hasMergeIndices()) + { + mShape->mMorphable = true; + break; + } + + vb = mShape->mVertexBuffer = glAllocateVertexBufferEXT(size,GL_TRIBESMTNVFMT_EXT,true); + if (vb == -1) + { + PROFILE_END(); + return false; + } + if (mShape->mCallbackKey == -1) + mShape->mCallbackKey = TextureManager::registerEventCallback(tsShapeTextureEventCB, U32(mShape)); + + // run through the meshes -- fill vertex buffer + glLockVertexBufferEXT(vb,0); + for (i = start; i < end; ++i) + mMeshObjects[i].fillVB(vb,mMaterialList); + glUnlockVertexBufferEXT(vb); + } + + // run through the meshes + start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss]; + end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss]; + + if (mShape->mMorphable) + { + PROFILE_START(TSShapeInstanceMorphVB); + glLockVertexBufferEXT(vb,0); + for (i = start; i < end; ++i) + mMeshObjects[i].morphVB(vb,mShape->mPreviousMerge[i],od,mMaterialList); + glUnlockVertexBufferEXT(vb); + PROFILE_END(); + } + + smRenderData.currentTransform = NULL; + PROFILE_START(TSShapeInstanceRenderVB); + for (i = start; i < end; ++i) + mMeshObjects[i].renderVB(vb,od,mMaterialList); + PROFILE_END(); + + PROFILE_END(); + return true; +} + +bool TSShapeInstance::renderDecalsX(S32 ss, S32 od) +{ + return false; + + ss,od; +// I don't know why, but this doesn't quite work -- no time to fix +#if 0 + if (supportBuffers) + { + S32 i,start,end,vb; + vb = mShape->mVertexBuffer; + + start = mShape->subShapeFirstDecal[ss]; + end = mShape->subShapeNumDecals[ss] + start; + if (smRenderData.renderDecals && starttargetMesh; + TSDecalMesh *decal; + + + if (!target0 || + mDecalObjects[i].targetObject->visible <= 0.01f || + !(decal = mDecalObjects[i].getDecalMesh(od)) || + mDecalObjects[i].frame < 0 || + !decal->targetMesh || + decal->texgenS.empty() || + decal->texgenT.empty()) + continue; + + GLuint foffset = mDecalObjects[i].frame*target0->numMatFrames*target0->vertsPerFrame; + + glSetVertexBufferEXT(vb); + glOffsetVertexBufferEXT(vb,target0->vbOffset+foffset); + mDecalObjects[i].render(od,mMaterialList); + } + + // if we have a matrix pushed, pop it now + if (smRenderData.currentTransform) + glPopMatrix(); + + // restore gl state + TSDecalMesh::resetDecalMaterials(); + } + } + else +#endif +} + +void TSShapeInstance::setStatics(S32 dl, F32 intraDL, const Point3F * objectScale) +{ + ObjectInstance::smTransforms = mNodeTransforms.address(); + smRenderData.objectScale = objectScale; + smRenderData.detailLevel = dl; + smRenderData.intraDetailLevel = intraDL; + smRenderData.alwaysAlpha = mAlphaAlways; + smRenderData.alwaysAlphaValue = getAlphaAlwaysValue(); + smRenderData.balloonShape = mBalloonShape; + smRenderData.balloonValue = getBalloonValue(); + + smRenderData.useOverride = mUseOverrideTexture; + smRenderData.override = mOverrideTexture; + + S32 ss = mShape->details[dl].subShapeNum; + S32 od = mShape->details[dl].objectDetailNum; + S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss]; + S32 end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss]; + TSMesh::smSaveVerts.setSize(mShape->mMergeBufferSize); + TSMesh::smSaveTVerts.setSize(mShape->mMergeBufferSize); + for (S32 i=start; isaveMergeVerts(); + } +} + +void TSShapeInstance::clearStatics() +{ + ObjectInstance::smTransforms = NULL; + smRenderData.override = NULL; + + S32 ss = mShape->details[smRenderData.detailLevel].subShapeNum; + S32 od = mShape->details[smRenderData.detailLevel].objectDetailNum; + S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss]; + S32 end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss]; + for (S32 i=start; irestoreMergeVerts(); + } +} + +void TSShapeInstance::setupTexturing(S32 dl, F32 intraDL) +{ + // first we'll decide what maps we want + // then we'll decide how we can implement them (1-pass or 2-pass or not at all) + + // we need to set up these variables + S32 & emapMethod = smRenderData.environmentMapMethod; + S32 & dmapMethod = smRenderData.detailMapMethod; + S32 & fogMethod = smRenderData.fogMethod; + S32 & dmapTE = smRenderData.detailMapTE; + S32 & emapTE = smRenderData.environmentMapTE; + S32 & baseTE = smRenderData.baseTE; + S32 & fogTE = smRenderData.fogTE; + + baseTE = 0; // initially assume base texture will go in first TE + + // ------------------------------------------------- + // what do we want to do? + + bool wantEMap = ((mShape->mExportMerge && dl<=mShape->mSmallestVisibleDL/2) || + (!mShape->mExportMerge && dl<=mMaxEnvironmentMapDL)) && + mEnvironmentMapOn && (TextureObject*)mEnvironmentMap && mEnvironmentMapAlpha>0.01f; + bool wantDMap = dl<=mMaxDetailMapDL; + bool wantFog = smRenderData.fogOn && !smSkipFog; + smRenderData.detailMapAlpha = (dl0.5f) ? 1.0f : 2.0f * intraDL; + smRenderData.environmentMapAlpha = mEnvironmentMapAlpha * + ( + (((mShape->mExportMerge && dlmSmallestVisibleDL/2) || + (!mShape->mExportMerge && dl0.5f) ? 1.0f : 2.0f * intraDL ); + smRenderData.environmentMapGLName = wantEMap ? mEnvironmentMap.getGLName() : 0; + + // ------------------------------------------------- + // what can we do? + + if (!dglDoesSupportARBMultitexture()) + { + // we don't support multitexturing -- early out + emapMethod = NO_ENVIRONMENT_MAP; + dmapMethod = (wantDMap && mAllowTwoPassDetailMap) ? DETAIL_MAP_TWO_PASS : NO_DETAIL_MAP; + fogMethod = wantFog ? FOG_TWO_PASS : NO_FOG; + + return; + } + + // how many texture environments (TE's) do we have? + GLint numTE = 1, numUsedTE = 1; + if (dglDoesSupportARBMultitexture()) + glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&numTE); + + // what we do with the TE's will depend on whether the following extension is supported + if (dglDoesSupportTextureEnvCombine()) + { + // environment map... + if (wantEMap) + { + if (mAlphaIsReflectanceMap) + { + emapMethod = ENVIRONMENT_MAP_MULTI_1; + emapTE = numUsedTE; + numUsedTE++; + } + else if (numUsedTE+3<=numTE) + { + emapMethod = ENVIRONMENT_MAP_MULTI_3; + emapTE = numUsedTE; + numUsedTE += 3; + } + else if (mAllowTwoPassEnvironmentMap) + emapMethod = ENVIRONMENT_MAP_TWO_PASS; + else + emapMethod = NO_ENVIRONMENT_MAP; + } + else + emapMethod = NO_ENVIRONMENT_MAP; + + // detail map... + if (wantDMap) + { + if (smRenderData.detailMapAlpha>0.99f && numTE>=numUsedTE+1) + { + dmapMethod = DETAIL_MAP_MULTI_1; + dmapTE = numUsedTE; + numUsedTE++; + } + else if (smRenderData.detailMapAlpha<=0.9f && numTE>=numUsedTE+2) + { + dmapMethod = DETAIL_MAP_MULTI_2; + dmapTE = 0; // detail texture goes in first unit... + baseTE++; // so we bump this back one... + emapTE++; // this one gets bumped back 2... + numUsedTE += 2; // end up using two additional units.. + } + else + dmapMethod = mAllowTwoPassDetailMap ? DETAIL_MAP_TWO_PASS : NO_DETAIL_MAP; + } + else + dmapMethod = NO_DETAIL_MAP; + + // fog... + if (wantFog) + { + // DMMUNDO! + if (numTE>=numUsedTE+1 && emapMethod!=ENVIRONMENT_MAP_TWO_PASS) + { + fogMethod = smRenderData.fogMapHandle ? FOG_MULTI_1_TEXGEN : FOG_MULTI_1; + fogTE = numUsedTE; + numUsedTE++; + } + else + fogMethod = smRenderData.fogMapHandle ? FOG_TWO_PASS_TEXGEN : FOG_TWO_PASS; + } + else + fogMethod = NO_FOG; + } + else + { + // we can't single pass environment map without texture combine extension... + wantEMap = wantEMap && mAllowTwoPassEnvironmentMap; + emapMethod = wantEMap ? ENVIRONMENT_MAP_TWO_PASS : NO_ENVIRONMENT_MAP; + // ditto for detail map... + wantDMap = wantDMap && mAllowTwoPassDetailMap; + dmapMethod = wantDMap ? DETAIL_MAP_TWO_PASS : NO_DETAIL_MAP; + fogMethod = wantFog ? FOG_TWO_PASS : NO_FOG; + } + + if (emapMethod == NO_ENVIRONMENT_MAP) + smRenderData.environmentMapAlpha = 1.0f; +} + +void TSShapeInstance::setupFog(F32 fogAmount, const ColorF & fogColor) +{ + smRenderData.fogOn = (fogAmount > 1.0 / 64.0f); + smRenderData.fogMapHandle = NULL; + + bool refresh = false; + + if (!smRenderData.fogBitmap) + { + smRenderData.fogBitmap = new GBitmap(8,8,false,GBitmap::RGBA); + + // clear the bitmap (defaults to 0xff) so if fogColor is 0,0,0 + // we will have a valid bitmap + dMemset(smRenderData.fogBitmap->getWritableBits(), 0, 256); + } + + if (smRenderData.fogColor.x != fogColor.red || + smRenderData.fogColor.y != fogColor.green || + smRenderData.fogColor.z != fogColor.blue) + { + U8 *bits = smRenderData.fogBitmap->getWritableBits(); + U8 red = 255*fogColor.red; + U8 green = 255*fogColor.green; + U8 blue = 255*fogColor.blue; + + for (U8 i = 0; i < 64; ++i) + { + *bits++ = red; + *bits++ = green; + *bits++ = blue; + bits++; + } + refresh = true; + } + + // the ATI Rage 128 needs a forthcoming driver to do do constant alpha blend + if (smRenderData.fogTexture) + { + if (smRenderData.fogColor.w != fogAmount) + { + U8 *bits = smRenderData.fogBitmap->getWritableBits(); + U8 fog = 255 * fogAmount; + + for (U8 i = 0; i < 64; ++i) + { + bits[3] = fog; + bits += 4; + } + refresh = true; + } + } + + if (!smRenderData.fogHandle) + smRenderData.fogHandle = new TextureHandle("fog_texture", smRenderData.fogBitmap); + else + if (refresh) + smRenderData.fogHandle->refresh(); + + smRenderData.fogColor.set(fogColor.red,fogColor.green,fogColor.blue,fogAmount); +} + +void TSShapeInstance::setupFog(F32 fogAmount, TextureHandle * fogMap, Point4F & s, Point4F & t) +{ + smRenderData.fogColor.w = fogAmount; + smRenderData.fogOn = true; + smRenderData.fogMapHandle = fogMap; + smRenderData.fogTexGenS = s; + smRenderData.fogTexGenT = t; +} + +bool TSShapeInstance::twoPassEnvironmentMap() +{ + return (smRenderData.environmentMapMethod==ENVIRONMENT_MAP_TWO_PASS); +} + +bool TSShapeInstance::twoPassDetailMap() +{ + return (smRenderData.detailMapMethod==DETAIL_MAP_TWO_PASS); +} + +bool TSShapeInstance::twoPassFog() +{ + return (smRenderData.fogMethod==FOG_TWO_PASS || smRenderData.fogMethod==FOG_TWO_PASS_TEXGEN); +} + +void TSShapeInstance::renderEnvironmentMap() +{ + AssertFatal((TextureObject*)mEnvironmentMap,"TSShapeInstance::renderEnvironmentMap (1)"); + AssertFatal(mEnvironmentMapOn,"TSShapeInstance::renderEnvironmentMap (2)"); + AssertFatal(dglDoesSupportARBMultitexture(),"TSShapeInstance::renderEnvironmentMap (3)"); + AssertFatal(smRenderData.environmentMapMethod==ENVIRONMENT_MAP_TWO_PASS,"TSShapeInstance::renderEnvironmentMap (4)"); + + S32 dl = smRenderData.detailLevel; + const TSDetail * detail = &mShape->details[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + S32 start = mShape->subShapeFirstObject[ss]; + S32 end = mShape->subShapeNumObjects[ss] + start; + if (start>=end) + return; + + // set up gl environment for emap... + TSMesh::initEnvironmentMapMaterials(); + + // run through objects and render + smRenderData.currentTransform = NULL; + for (S32 i=start; idetails[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + bool wasLit = glIsEnabled(GL_LIGHTING); + glDisable(GL_LIGHTING); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnableClientState(GL_VERTEX_ARRAY); + + if (smRenderData.fogMethod==FOG_TWO_PASS_TEXGEN) + { + // set up fog map + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.fogMapHandle->getGLName()); + + // set up texgen equations + glTexGeni(GL_S,GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glTexGeni(GL_T,GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); + glEnable(GL_TEXTURE_GEN_S); + glEnable(GL_TEXTURE_GEN_T); + glTexGenfv(GL_S,GL_OBJECT_PLANE,&TSShapeInstance::smRenderData.fogTexGenS.x); + glTexGenfv(GL_T,GL_OBJECT_PLANE,&TSShapeInstance::smRenderData.fogTexGenT.x); + } + else + { + // just one fog color per shape... + glColor4fv(smRenderData.fogColor); + // texture should be disabled already... + } + + smRenderData.currentTransform = NULL; + S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss]; + S32 end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss]; + for (S32 i=start; idetails[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + S32 start = mShape->subShapeFirstObject[ss]; + S32 end = mShape->subShapeNumObjects[ss] + start; + + // set up gl environment for the detail map + TSMesh::initDetailMapMaterials(); + + // run through objects and render detail maps + smRenderData.currentTransform = NULL; + for (S32 i=start; i1.0f ? 1.0f : (intraDL<0.0f ? 0.0f : intraDL); + + // restrict chosen detail level by cutoff value + S32 cutoff = getMin(smNumSkipRenderDetails,mShape->mSmallestVisibleDL); + if (mCurrentDetailLevel>=0 && mCurrentDetailLevelmSmallestVisibleDL>=0 && mShape->details[0].maxError>=0) + // use new scheme + return selectCurrentDetailEx(ignoreScale); + + MatrixF toCam; + Point3F p; + dglGetModelview(&toCam); + toCam.mulP(mShape->center,&p); + F32 dist = mDot(p,p); + F32 scale = 1.0f; + if (!ignoreScale) + { + // any scale? + Point3F x,y,z; + toCam.getRow(0,&x); + toCam.getRow(1,&y); + toCam.getRow(2,&z); + F32 scalex = mDot(x,x); + F32 scaley = mDot(y,y); + F32 scalez = mDot(z,z); + scale = scalex; + if (scaley > scale) + scale = scaley; + if (scalez > scale) + scale = scalez; + } + dist /= scale; + dist = mSqrt(dist); + + F32 pixelRadius = dglProjectRadius(dist,mShape->radius) * dglGetPixelScale() * smDetailAdjust; + + return selectCurrentDetail(pixelRadius); +} + +S32 TSShapeInstance::selectCurrentDetailEx(bool ignoreScale) +{ + MatrixF toCam; + Point3F p; + dglGetModelview(&toCam); + toCam.mulP(mShape->center,&p); + F32 dist = mDot(p,p); + F32 scale = 1.0f; + if (!ignoreScale) + { + // any scale? + Point3F x,y,z; + toCam.getRow(0,&x); + toCam.getRow(1,&y); + toCam.getRow(2,&z); + F32 scalex = mDot(x,x); + F32 scaley = mDot(y,y); + F32 scalez = mDot(z,z); + scale = scalex; + if (scaley > scale) + scale = scaley; + if (scalez > scale) + scale = scalez; + } + dist /= scale; + dist = mSqrt(dist); + + // find tolerance + F32 proj = dglProjectRadius(dist,1.0f) * dglGetPixelScale(); // pixel size of 1 meter at given distance + return selectCurrentDetailEx(smScreenError/proj); +} + +S32 TSShapeInstance::selectCurrentDetail(Point3F offset, F32 invScale) +{ + F32 dist = mSqrt(mDot(offset,offset)); + dist *= invScale; + return selectCurrentDetail2(dist); +} + +S32 TSShapeInstance::selectCurrentDetail2(F32 adjustedDist) +{ + if (mShape->mSmallestVisibleDL>=0 && mShape->details[0].maxError>=0) + // use new scheme + return selectCurrentDetail2Ex(adjustedDist); + + F32 pixelRadius = dglProjectRadius(adjustedDist,mShape->radius) * dglGetPixelScale() * smDetailAdjust; + return selectCurrentDetail(pixelRadius); +} + +S32 TSShapeInstance::selectCurrentDetail2Ex(F32 adjustedDist) +{ + // find tolerance + F32 proj = dglProjectRadius(adjustedDist,1.0f) * dglGetPixelScale(); // pixel size of 1 meter at given distance + return selectCurrentDetailEx(smScreenError/proj); +} + +S32 TSShapeInstance::selectCurrentDetail(F32 size) +{ + // check to see if not visible first... + if (size<=mShape->mSmallestVisibleSize) + { + // don't render... + mCurrentDetailLevel=-1; + mCurrentIntraDetailLevel = 0.0f; + return -1; + } + + // same detail level as last time? + // only search for detail level if the current one isn't the right one already + if ( mCurrentDetailLevel<0 || + (mCurrentDetailLevel==0 && size<=mShape->details[0].size) || + (mCurrentDetailLevel>0 && (size<=mShape->details[mCurrentDetailLevel].size || size>mShape->details[mCurrentDetailLevel-1].size))) + { + // scan shape for highest detail size smaller than us... + // shapes details are sorted from largest to smallest... + // a detail of size <= 0 means it isn't a renderable detail level (utility detail) + for (S32 i=0; idetails.size(); i++) + { + if (size>mShape->details[i].size) + { + mCurrentDetailLevel = i; + break; + } + AssertFatal(i+1details.size() && mShape->details[i+1].size>=0,"TSShapeInstance::selectCurrentDetail"); + } + } + + F32 curSize = mShape->details[mCurrentDetailLevel].size; + F32 nextSize = mCurrentDetailLevel==0 ? 2.0f * curSize : mShape->details[mCurrentDetailLevel-1].size; + mCurrentIntraDetailLevel = nextSize-curSize>0.01f ? (size-curSize) / (nextSize-curSize) : 1.0f; + mCurrentIntraDetailLevel = mCurrentIntraDetailLevel>1.0f ? 1.0f : (mCurrentIntraDetailLevel<0.0f ? 0.0f : mCurrentIntraDetailLevel); + + // now restrict chosen detail level by cutoff value + S32 cutoff = getMin(smNumSkipRenderDetails,mShape->mSmallestVisibleDL); + if (mCurrentDetailLevel>=0 && mCurrentDetailLevelmSmallestVisibleDL<0) + prevErr=0.0f; + else + prevErr = 10.0f * mShape->details[mShape->mSmallestVisibleDL].averageError * 20.0f; + if (mShape->mSmallestVisibleDL<0 || prevErrmSmallestVisibleDL; + mCurrentIntraDetailLevel = 0.0f; + return mCurrentDetailLevel; + } + + // this function is a little odd + // the reason is that the detail numbers correspond to + // when we stop using a given detail level... + // we search the details from most error to least error + // until we fit under the tolerance (errorTOL) and then + // we use the next highest detail (higher error) + for (S32 i=mShape->mSmallestVisibleDL; i>=0; i--) + { + F32 err0 = 10.0f * mShape->details[i].averageError; + if (err0 < errorTOL) + { + // ok, stop here + + // intraDL = 1 corresponds to fully this detail + // intraDL = 0 corresponds to the next lower (higher number) detail + mCurrentDetailLevel = i; + mCurrentIntraDetailLevel = 1.0f - (errorTOL-err0)/(prevErr-err0); + return mCurrentDetailLevel; + } + prevErr=err0; + } + + // get here if we are drawing at DL==0 + mCurrentDetailLevel = 1.0f; + mCurrentIntraDetailLevel = 1.0f; + return mCurrentDetailLevel; +} + +GBitmap * TSShapeInstance::snapshot(TSShape * shape, U32 width, U32 height, bool mip, MatrixF & cameraPos, S32 dl, F32 intraDL, bool hiQuality) +{ + TSShapeInstance * shapeInstance = new TSShapeInstance(shape, true); + shapeInstance->setCurrentDetail(dl,intraDL); + shapeInstance->animate(); + GBitmap * bmp = shapeInstance->snapshot(width,height,mip,cameraPos,hiQuality); + + delete shapeInstance; + + return bmp; +} + +GBitmap * TSShapeInstance::snapshot(U32 width, U32 height, bool mip, MatrixF & cameraMatrix,bool hiQuality) +{ + S32 screenWidth = Platform::getWindowSize().x; + S32 screenHeight = Platform::getWindowSize().y; + S32 xcenter = screenWidth >> 1; + S32 ycenter = screenHeight >> 1; + + if (screenWidth==0 || screenHeight==0) + return NULL; // probably in exporter... + + AssertFatal(widthsmMaxSnapshotScale) + scale = smMaxSnapshotScale; + + // height and width of intermediate bitmaps + U32 bmpWidth = width*scale; + U32 bmpHeight = height*scale; + + Point4F saveClearColor; + glGetFloatv(GL_COLOR_CLEAR_VALUE,(F32*)&saveClearColor); + + // setup viewport and frustrum (do orthographic projection) + dglSetViewport(RectI(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight)); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + dglSetFrustum(-mShape->radius, mShape->radius, -mShape->radius, mShape->radius, 1, 20.0f * mShape->radius,true); + + // position camera... + glMatrixMode(GL_MODELVIEW); + Point3F y; + cameraMatrix.getColumn(1,&y); + y *= -10.0f * mShape->radius; + y += mShape->center; + cameraMatrix.setColumn(3,y); + cameraMatrix.inverse(); + dglLoadMatrix(&cameraMatrix); + + // set some initial gl states + glDisable(GL_CULL_FACE); + glDisable(GL_LIGHTING); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + + // take a snapshot of the shape with a black background... + glClearColor(0,0,0,0); + glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT); + GBitmap * blackBmp = new GBitmap; + blackBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB); + render(mCurrentDetailLevel,mCurrentIntraDetailLevel); + glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)blackBmp->getBits(0)); + + // take a snapshot of the shape with a white background... + glClearColor(1,1,1,1); + glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT); + GBitmap * whiteBmp = new GBitmap; + whiteBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB); + render(mCurrentDetailLevel,mCurrentIntraDetailLevel); + glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)whiteBmp->getBits(0)); + + glDisable(GL_DEPTH_TEST); + glClearColor(saveClearColor.x,saveClearColor.y,saveClearColor.z,saveClearColor.w); + + // now separate the color and alpha channels + GBitmap * bmp = new GBitmap; + bmp->allocateBitmap(width,height,mip,GBitmap::RGBA); + U8 * wbmp = (U8*)whiteBmp->getBits(0); + U8 * bbmp = (U8*)blackBmp->getBits(0); + U8 * dst = (U8*)bmp->getBits(0); + S32 i,j; + if (hiQuality) + { + for (i=0; i 0.01f ? 1.0f / alphaTally : 0.0f; + U32 pos = 4*(i*width+j); + dst[pos+0] = (U8)(rTally * invAlpha); + dst[pos+1] = (U8)(gTally * invAlpha); + dst[pos+2] = (U8)(bTally * invAlpha); + dst[pos+3] = (U8)(((F32)alphaIntTally) / (F32) (3*alphaCount)); + } + } + } + else + { + // simpler, probably faster... + for (i=0; iextrudeMipLevels(); + + return bmp; +} + +void TSShapeInstance::renderShadow(S32 dl, const MatrixF & mat, S32 dim, U32 * bits) +{ + // if dl==-1, nothing to do + if (dl==-1) + return; + + AssertFatal(dl>=0 && dldetails.size(),"TSShapeInstance::renderShadow"); + + S32 i; + + const TSDetail * detail = &mShape->details[dl]; + S32 ss = detail->subShapeNum; + S32 od = detail->objectDetailNum; + + // assert if we're a billboard detail + AssertFatal(ss>=0,"TSShapeInstance::renderShadow: not with a billboard detail level"); + + // set up render data + setStatics(dl); + + // run through the meshes + smRenderData.currentTransform = NULL; + S32 start = mShape->subShapeFirstObject[ss]; + S32 end = start + mShape->subShapeNumObjects[ss]; + for (i=start; igetMeshType() == TSMesh::StandardMeshType && mesh->vertsPerFrame > 0) + { + mesh->vbOffset = size; + + return mesh->numFrames*mesh->numMatFrames*mesh->vertsPerFrame; + } + else + return 0; +} + +bool TSShapeInstance::MeshObjectInstance::hasMergeIndices() +{ + TSMesh *mesh = getMesh(0); + + return (mesh && mesh->getMeshType() == TSMesh::StandardMeshType && mesh->mergeIndices.size()); +} + +void TSShapeInstance::MeshObjectInstance::fillVB(S32 vb, TSMaterialList *materials) +{ + TSMesh *mesh = getMesh(0); + + if (!mesh || mesh->getMeshType() != TSMesh::StandardMeshType || mesh->vertsPerFrame <= 0) + return; + + for (S32 f = 0; f < mesh->numFrames; ++f) + for (S32 m = 0; m < mesh->numMatFrames; ++m) + mesh->fillVB(vb,f,m,materials); +} + +void TSShapeInstance::MeshObjectInstance::morphVB(S32 vb, S32 &previousMerge, S32 objectDetail, TSMaterialList *materials) +{ + if (visible > 0.01f) + { + TSMesh *m0 = getMesh(0); + TSMesh *mesh = getMesh(objectDetail); + + if (m0 && mesh) + { + // render TSSortedMesh's standard + if (m0->getMeshType() != TSMesh::StandardMeshType) + return; + + GLuint foffset = (frame*m0->numMatFrames + matFrame)*m0->vertsPerFrame; + U32 morphSize = mesh->mergeIndices.size(); + U32 merge = mesh->vertsPerFrame-morphSize; + + if (!morphSize) + return; + + if (previousMerge != -1 && previousMerge < merge) + { + U32 tmp = merge; + + merge = previousMerge; + previousMerge = tmp; + morphSize = mesh->vertsPerFrame-merge; + } + else + previousMerge = merge; + + glOffsetVertexBufferEXT(vb,m0->vbOffset + foffset + merge); + mesh->morphVB(vb,morphSize,frame,matFrame,materials); + } + } +} + +void TSShapeInstance::MeshObjectInstance::renderVB(S32 vb, S32 objectDetail, TSMaterialList *materials) +{ + if (visible > 0.01f) + { + TSMesh *m0 = getMesh(0); + TSMesh *mesh = getMesh(objectDetail); + + if (m0 && mesh) + { + // render TSSortedMesh's standard + if (m0->getMeshType() != TSMesh::StandardMeshType) + { + render(objectDetail, materials); + return; + } + + if (mesh->vertsPerFrame <= 0) + return; + + MatrixF *transform = getTransform(); + + if (transform != TSShapeInstance::smRenderData.currentTransform) + { + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + if (transform) + { + glPushMatrix(); + dglMultMatrix(transform); + } + TSShapeInstance::smRenderData.currentTransform = transform; + } + + GLuint foffset = (frame*m0->numMatFrames + matFrame)*m0->vertsPerFrame; + + glSetVertexBufferEXT(vb); + glOffsetVertexBufferEXT(vb,m0->vbOffset + foffset); + + if (visible>0.99f) + { + if (TSShapeInstance::smRenderData.balloonShape) + { + glPushMatrix(); + + F32 &bv = TSShapeInstance::smRenderData.balloonValue; + + glScalef(bv,bv,bv); + } + mesh->renderVB(frame,matFrame,materials); + if (TSShapeInstance::smRenderData.balloonShape) + glPopMatrix(); + } + else + { + mesh->setFade(visible); + mesh->renderVB(frame,matFrame,materials); + mesh->clearFade(); + } + } + } +} + +void TSShapeInstance::MeshObjectInstance::render(S32 objectDetail, TSMaterialList * materials) +{ + if (visible>0.01f) + { + TSMesh * mesh = getMesh(objectDetail); + if (mesh) + { + MatrixF * transform = getTransform(); + if (transform != TSShapeInstance::smRenderData.currentTransform) + { + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + if (transform) + { + glPushMatrix(); + dglMultMatrix(transform); + } + TSShapeInstance::smRenderData.currentTransform = transform; + } + if (visible>0.99f) + { + if (TSShapeInstance::smRenderData.balloonShape) + { + glPushMatrix(); + F32 & bv = TSShapeInstance::smRenderData.balloonValue; + glScalef(bv,bv,bv); + } + mesh->render(frame,matFrame,materials); + if (TSShapeInstance::smRenderData.balloonShape) + glPopMatrix(); + } + else + { + mesh->setFade(visible); + mesh->render(frame,matFrame,materials); + mesh->clearFade(); + } + } + } +} + +void TSShapeInstance::DecalObjectInstance::render(S32 objectDetail, TSMaterialList * materials) +{ + if (targetObject->visible>0.01f) + { + TSDecalMesh * decalMesh = getDecalMesh(objectDetail); + if (decalMesh && frame>=0) + { + MatrixF * transform = getTransform(); + if (transform != TSShapeInstance::smRenderData.currentTransform) + { + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + if (transform) + { + glPushMatrix(); + dglMultMatrix(transform); + } + TSShapeInstance::smRenderData.currentTransform = transform; + } + + if (TSShapeInstance::smRenderData.balloonShape) + { + glPushMatrix(); + F32 & bv = TSShapeInstance::smRenderData.balloonValue; + glScalef(bv,bv,bv); + } + decalMesh->render(targetObject->frame,frame,materials); + if (TSShapeInstance::smRenderData.balloonShape) + glPopMatrix(); + } + } +} + +void TSShapeInstance::MeshObjectInstance::renderEnvironmentMap(S32 objectDetail, TSMaterialList * materials) +{ + if (visible>0.01f) + { + TSMesh * mesh = getMesh(objectDetail); + if (mesh) + { + MatrixF * transform = getTransform(); + if (transform != TSShapeInstance::smRenderData.currentTransform) + { + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + if (transform) + { + glPushMatrix(); + dglMultMatrix(transform); + } + TSShapeInstance::smRenderData.currentTransform = transform; + } + + if (TSShapeInstance::smRenderData.balloonShape) + { + glPushMatrix(); + F32 & bv = TSShapeInstance::smRenderData.balloonValue; + glScalef(bv,bv,bv); + } + mesh->renderEnvironmentMap(frame,matFrame,materials); + if (TSShapeInstance::smRenderData.balloonShape) + glPopMatrix(); + } + } +} + +void TSShapeInstance::MeshObjectInstance::renderDetailMap(S32 objectDetail, TSMaterialList * materials) +{ + if (visible>0.01f) + { + TSMesh * mesh = getMesh(objectDetail); + if (mesh && mesh->getFlags(TSMesh::HasDetailTexture)) + { + MatrixF * transform = getTransform(); + if (transform != TSShapeInstance::smRenderData.currentTransform) + { + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + if (transform) + { + glPushMatrix(); + dglMultMatrix(transform); + } + TSShapeInstance::smRenderData.currentTransform = transform; + } + + if (TSShapeInstance::smRenderData.balloonShape) + { + glPushMatrix(); + F32 & bv = TSShapeInstance::smRenderData.balloonValue; + glScalef(bv,bv,bv); + } + mesh->renderDetailMap(frame,matFrame,materials); + if (TSShapeInstance::smRenderData.balloonShape) + glPopMatrix(); + } + } +} + +void TSShapeInstance::MeshObjectInstance::renderShadow(S32 objectDetail, const MatrixF & mat, S32 dim, U32 * bits, TSMaterialList * materialList) +{ + if (visible>0.01f) + { + TSMesh * mesh = getMesh(objectDetail); + if (mesh) + { + MatrixF mat2; + MatrixF * transform = getTransform(); + if (transform) + mat2.mul(mat,*transform); + else + mat2=mat; + mesh->renderShadow(frame,mat2,dim,bits,materialList); + } + } +} + +void TSShapeInstance::MeshObjectInstance::renderFog(S32 objectDetail, TSMaterialList* materials) +{ + if (visible>0.01f) + { + TSMesh * mesh = getMesh(objectDetail); + if (mesh) + { + MatrixF * transform = getTransform(); + if (transform != TSShapeInstance::smRenderData.currentTransform) + { + if (TSShapeInstance::smRenderData.currentTransform) + glPopMatrix(); + if (transform) + { + glPushMatrix(); + dglMultMatrix(transform); + } + TSShapeInstance::smRenderData.currentTransform = transform; + } + + if (TSShapeInstance::smRenderData.balloonShape) + { + glPushMatrix(); + F32 & bv = TSShapeInstance::smRenderData.balloonValue; + glScalef(bv,bv,bv); + } + mesh->renderFog(frame, materials); + if (TSShapeInstance::smRenderData.balloonShape) + glPopMatrix(); + } + } +} + +void TSShapeInstance::incDebrisRefCount() +{ + ++debrisRefCount; +} + +void TSShapeInstance::decDebrisRefCount() +{ + if( debrisRefCount == 0 ) return; + --debrisRefCount; +} + +U32 TSShapeInstance::getDebrisRefCount() +{ + return debrisRefCount; +} + + +U32 TSShapeInstance::getNumDetails() +{ + if( mShape ) + { + return mShape->details.size(); + } + + return 0; +} diff --git a/ts/tsShapeInstance.h b/ts/tsShapeInstance.h new file mode 100644 index 0000000..3c4abb9 --- /dev/null +++ b/ts/tsShapeInstance.h @@ -0,0 +1,673 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSSHAPEINSTANCE_H_ +#define _TSSHAPEINSTANCE_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif +#ifndef _TSINTEGERSET_H_ +#include "ts/tsIntegerSet.h" +#endif +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif +#ifndef _GBITMAP_H_ +#include "dgl/gBitmap.h" +#endif + +class RenderItem; +class TSThread; +class ConvexFeature; + +//------------------------------------------------------------------------------------- +// Instance versions of shape objects +//------------------------------------------------------------------------------------- +struct TSVertex +{ + Point3F p; + ColorI color; + Point2F t1; + Point2F t2; +}; + +class TSShapeInstance +{ + public: + + struct ObjectInstance; + friend class TSThread; + friend class TSPartInstance; + + static void init(); + static void destroy(); + + // An objectInstance points to the renderable items in the shape... + struct ObjectInstance + { + // this needs to be set before using an objectInstance...tells us where to + // look for the transforms...gets set be shape instance 'setStatics' method + static MatrixF * smTransforms; + + S32 nodeIndex; + MatrixF * getTransform(); + virtual void render(S32 objectDetail, TSMaterialList *); + virtual void renderEnvironmentMap(S32 objectDetail, TSMaterialList *); + virtual void renderDetailMap(S32 objectDetail, TSMaterialList *); + virtual void renderFog(S32 objectDetail, TSMaterialList*); + + // collision routines... + virtual bool buildPolyList(S32 objectDetail, AbstractPolyList *, U32 & surfaceKey); + virtual bool getFeatures(S32 objectDetail, const MatrixF& mat, const Point3F& n, ConvexFeature*, U32 & surfaceKey); + virtual void support(S32 od, const Point3F& v, F32* currMaxDP, Point3F* currSupport); + virtual bool castRay(S32 objectDetail, const Point3F & start, const Point3F & end, RayInfo *); + }; + + // these are set up by default based on shape data + struct MeshObjectInstance : ObjectInstance + { + TSMesh * const * meshList; // one mesh per detail level...Null entries allowed + const TSObject * object; + S32 frame; + S32 matFrame; + F32 visible; + + S32 getSizeVB(S32 size); + bool hasMergeIndices(); + void fillVB(S32 vb, TSMaterialList *materials); + void morphVB(S32 vb, S32 &previousMerge, S32 objectDetail, TSMaterialList *materials); + void renderVB(S32 vb, S32 objectDetail, TSMaterialList *materials); + void render(S32 objectDetail, TSMaterialList *); // this just selects the right detail level (mesh) and calls meshes render + void renderEnvironmentMap(S32 objectDetail, TSMaterialList *); + void renderDetailMap(S32 objectDetail, TSMaterialList *); + void renderShadow(S32 objectDetail, const MatrixF & mat, S32 dim, U32 * bits, TSMaterialList *); + void renderFog(S32 objectDetail, TSMaterialList*); + TSMesh * getMesh(S32 num) const { return numnumMeshes ? *(meshList+num) : NULL; } + + // collision routines... + bool buildPolyList(S32 objectDetail, AbstractPolyList *, U32 & surfaceKey); + bool getFeatures(S32 objectDetail, const MatrixF& mat, const Point3F& n, ConvexFeature*, U32 & surfaceKey); + void support(S32 od, const Point3F& v, F32* currMaxDP, Point3F* currSupport); + bool castRay(S32 objectDetail, const Point3F & start, const Point3F & end, RayInfo *); + }; + + // also set up based on shape data...they refer to mesh object instances + struct DecalObjectInstance : ObjectInstance + { + TSDecalMesh * const * decalList; + const MeshObjectInstance * targetObject; + const TSShape::Decal * decalObject; + + S32 frame; + + void render(S32 objectDetail, TSMaterialList *); + TSDecalMesh * getDecalMesh(S32 num) const { return numnumMeshes ? *(decalList+num) : NULL; } + + // we don't do these things... + // void renderEnvironmentMap(S32,TSMaterialList*) {} + // void renderDecalMap(S32,TSMaterialList*) {} + // bool getFeatures(S32 objectDetail, const MatrixF& mat, const Point3F& n, ConvexFeature*, U32 & surfaceKey); + bool buildPolyList(S32, AbstractPolyList *, U32 &) { return false; } + bool castRay(S32, const Point3F &, const Point3F &, RayInfo *) { return false; } + }; + + // ifl objects ... controlled by animation but also can be controlled by user + struct IflMaterialInstance + { + const TSShape::IflMaterial * iflMaterial; + S32 frame; + }; + +//------------------------------------------------------------------------------------- +// Lists used for storage of transforms, nodes, objects, etc... +//------------------------------------------------------------------------------------- + + public: + + Vector mMeshObjects; + Vector mDecalObjects; + Vector mIflMaterialInstances; + + // storage space for node transforms + Vector mNodeTransforms; + + // reference transform vectors -- unused until first transition + Vector mNodeReferenceRotations; + Vector mNodeReferenceTranslations; + Vector mNodeReferenceUniformScales; + Vector mNodeReferenceScaleFactors; + Vector mNodeReferenceArbitraryScaleRots; + + // workspace for node transforms + static Vector smNodeCurrentRotations; + static Vector smNodeCurrentTranslations; + static Vector smNodeCurrentUniformScales; + static Vector smNodeCurrentAlignedScales; + static Vector smNodeCurrentArbitraryScales; + + // keep track of who controls what on currently animating shape + static Vector smRotationThreads; + static Vector smTranslationThreads; + static Vector smScaleThreads; + +//------------------------------------------------------------------------------------- +// Misc. +//------------------------------------------------------------------------------------- + + protected: + + // ground transform data + MatrixF mGroundTransform; + TSThread * mGroundThread; + + bool mScaleCurrentlyAnimated; + + S32 mCurrentDetailLevel; + F32 mCurrentIntraDetailLevel; // 0-1, how far along from current to next (higher) detail level... + // 0=at this dl, 1=at higher detail level, where higher means bigger size on screen + // for dl=0, we use twice detail level 0's size as the size of the "next" dl + + Resource hShape; + TSShape * mShape; + + TSMaterialList* mMaterialList; // by default, points to hShape material list + bool mOwnMaterialList; + + TextureHandle mEnvironmentMap; + bool mEnvironmentMapOn; + F32 mEnvironmentMapAlpha; + bool mAllowTwoPassEnvironmentMap; + bool mAlphaIsReflectanceMap; + bool mAllowTwoPassDetailMap; + S32 mMaxEnvironmentMapDL; + S32 mMaxDetailMapDL; + bool mAlphaAlways; + F32 mAlphaAlwaysValue; + bool mDrawFog; + + bool mBalloonShape; + F32 mBalloonValue; + + bool mUseOverrideTexture; + TextureHandle mOverrideTexture; + + U32 debrisRefCount; + + // the threads... + Vector mThreadList; + Vector mTransitionThreads; + + // keep track of nodes that are involved in a transition + // Note: this only tracks nodes we're transitioning from... + // nodes we're transitioning to are implicitly handled + // (i.e., we don't need to keep track of them) + TSIntegerSet mTransitionRotationNodes; + TSIntegerSet mTransitionTranslationNodes; + TSIntegerSet mTransitionScaleNodes; + + // keep track of nodes with animation restrictions put on them + TSIntegerSet mMaskRotationNodes; + TSIntegerSet mMaskPosXNodes; + TSIntegerSet mMaskPosYNodes; + TSIntegerSet mMaskPosZNodes; + TSIntegerSet mDisableBlendNodes; + TSIntegerSet mHandsOffNodes; + TSIntegerSet mCallbackNodes; + + // state variables + U32 mTriggerStates; + + bool initGround(); + void addPath(TSThread * gt, F32 start, F32 end, MatrixF * mat = NULL); + + // environment/detail map methods + void setupTexturing(S32 dl, F32 intraDL); + bool twoPassEnvironmentMap(); + bool twoPassDetailMap(); + bool twoPassFog(); + void renderEnvironmentMap(); + void renderDetailMap(); + void renderFog(); + + // directX render + bool renderMeshesX(S32 ss, S32 od); + bool renderDecalsX(S32 ss, S32 od); + + public: + + TSShape* getShape() { return mShape; } + + // set non-rendering static variables used for accessing shape data + void setStatics(S32 dl = 0, F32 interDL = 0.0f, const Point3F * shapeScale = NULL); + void clearStatics(); + + TSMaterialList* getMaterialList() { return mMaterialList; } + void setMaterialList(TSMaterialList*); // we won't own the material list unless we clone it (see below) + void cloneMaterialList(); // call this to own the material list -- i.e., we'll make a copy of the currently + // set material list and be responsible for deleting it...if we don't own the list, + // then the application better not delete the set list out from under us (all this + // is handled automatically when using the shape's original list). + bool ownMaterialList() const { return mOwnMaterialList; } + + enum + { + MaskNodeRotation = 0x01, + MaskNodePosX = 0x02, + MaskNodePosY = 0x04, + MaskNodePosZ = 0x08, + MaskNodeBlend = 0x10, + MaskNodeAll = MaskNodeRotation|MaskNodePosX|MaskNodePosY|MaskNodePosZ|MaskNodeBlend, + MaskNodeAllButBlend = MaskNodeRotation|MaskNodePosX|MaskNodePosY|MaskNodePosZ, + MaskNodeAllButRotation = MaskNodePosX|MaskNodePosY|MaskNodePosZ|MaskNodeBlend, + MaskNodeAllButPosX = MaskNodeRotation|MaskNodePosY|MaskNodePosZ|MaskNodeBlend, + MaskNodeAllButPosY = MaskNodeRotation|MaskNodePosX|MaskNodePosZ|MaskNodeBlend, + MaskNodeAllButPosZ = MaskNodeRotation|MaskNodePosX|MaskNodePosY|MaskNodeBlend, + MaskNodeHandsOff = 0x20, // meaning, don't even set to default, programmer controls it (blend still applies) + MaskNodeCallback = 0x40 // meaning, get local transform via callback function (see setCallback) + // callback data2 is node index, callback return value is pointer to local transform + // Note: won't get this callback everytime you animate...application responsibility + // to make sure matrix pointer continues to point to valid and updated local transform + }; + // set node masking... + void setNodeAnimationState(S32 nodeIndex, U32 animationState); + U32 getNodeAnimationState(S32 nodeIndex); + + // check trigger values + bool getTriggerState(U32 stateNum, bool clearState = true); + void setTriggerState(U32 stateNum, bool on); + void setTriggerStateBit(U32 stateBit, bool on); + + // callback functions... + enum CallbackPurpose { LocalTransformCallback = 0 }; + typedef void * (*CallbackFunction)(CallbackPurpose, U32 data1, U32 data2); + CallbackFunction mCallback; + U32 mCallbackData; + void setCallback(CallbackFunction cb, U32 data1) { mCallback = cb; mCallbackData = data1; } + + // debris management... + void incDebrisRefCount(); + void decDebrisRefCount(); + U32 getDebrisRefCount(); + + // alpha always + void setAlphaAlways(F32 value) { mAlphaAlways = (value<0.99f); mAlphaAlwaysValue = value; } + F32 getAlphaAlwaysValue() { return mAlphaAlways ? mAlphaAlwaysValue : 1.0f; } + bool getAlphaAlways() { return mAlphaAlways; } + + // Balloon value + void setShapeBalloon(F32 value) { mBalloonShape = value > 1; mBalloonValue = value; } + F32 getBalloonValue() const { return mBalloonShape ? mBalloonValue : 1.0f; } + + // Override texture + void setOverrideTexture(TextureHandle override) { mOverrideTexture = override; mUseOverrideTexture = true; } + void clearOverrideTexture() { mOverrideTexture = NULL; mUseOverrideTexture = false; } + + enum + { + NO_ENVIRONMENT_MAP, // don't render environment map + ENVIRONMENT_MAP_MULTI_1, // render with multi-texturing (+1 texture units), shape alpha = reflectance map + ENVIRONMENT_MAP_MULTI_3, // render with multi-texturing (+3 texture units), reflectance map separate texture + ENVIRONMENT_MAP_TWO_PASS, // render in two passes -- mAllowTwoPassEnvironmentMap must be true + // Note: if reflectance map is separate from shape texture then won't render unless card has 4 texture units + // However, translucency won't work quite right if reflection map not separated -- probably ok though. + // Bottom line: previous 2 items probably only used for special shapes... + NO_DETAIL_MAP, + DETAIL_MAP_MULTI_1, + DETAIL_MAP_MULTI_2, + DETAIL_MAP_TWO_PASS, + NO_FOG, + FOG_MULTI_1, + FOG_MULTI_1_TEXGEN, + FOG_TWO_PASS, + FOG_TWO_PASS_TEXGEN + }; + const TextureHandle & getEnvironmentMap() { return mEnvironmentMap; } + F32 getEnvironmentMapAlpha() { return mEnvironmentMapAlpha; } + void setEnvironmentMap(const TextureHandle& map) { mEnvironmentMap = map; } + void setEnvironmentMapOn(bool on, F32 alpha = 0.25f) { mEnvironmentMapOn = on; mEnvironmentMapAlpha = alpha; } + +//------------------------------------------------------------------------------------- +// private methods for setting up and affecting animation +//------------------------------------------------------------------------------------- + + private: + + void updateTransitions(); + void handleDefaultScale(S32 a, S32 b, TSIntegerSet & scaleBeenSet); + void handleTransitionNodes(S32 a, S32 b); + void handleNodeScale(S32 a, S32 b); + void handleAnimatedScale(TSThread *, S32 a, S32 b, TSIntegerSet &); + void handleMaskedPositionNode(TSThread *, S32 nodeIndex, S32 offset); + void handleBlendSequence(TSThread *, S32 a, S32 b); + void checkScaleCurrentlyAnimated(); + +//------------------------------------------------------------------------------------- +// animate, render, & detail control +//------------------------------------------------------------------------------------- + + public: + + struct RenderData + { + MatrixF * currentTransform; + S32 detailLevel; + F32 intraDetailLevel; + S32 environmentMapMethod; + S32 detailMapMethod; + S32 detailMapTE; + S32 environmentMapTE; + F32 environmentMapAlpha; + U32 environmentMapGLName; + S32 baseTE; + F32 detailTextureScale; + F32 detailMapAlpha; + bool fadeSet; + bool lightingOn; + bool alwaysAlpha; + F32 alwaysAlphaValue; + bool balloonShape; + F32 balloonValue; + U32 materialFlags; + S32 materialIndex; + const Point3F * objectScale; + bool fogOn; + S32 fogMethod; + S32 fogTE; + Point4F fogColor; + Point4F fogTexGenS; + Point4F fogTexGenT; + TextureHandle * fogMapHandle; // used by texgen fog + bool useOverride; + TextureHandle override; + bool textureMatrixPushed; + bool fogTexture; + GBitmap *fogBitmap; + TextureHandle *fogHandle; + bool renderDecals; + struct VertexAlpha + { + // track various contributors to vertex alpha + F32 vis; + F32 emap; + F32 fog; + F32 always; + // current result... + F32 current; + void init() { current=vis=emap=fog=always=1.0f; } + bool set() { F32 old = current; current =vis*emap*fog*always; return (mFabs(old-current)>0.001f); } + } vertexAlpha; + }; + static RenderData smRenderData; + + // if true, skip these objects + static bool smNoRenderTranslucent; + static bool smNoRenderNonTranslucent; + + // when taking hiQuality snapshot, scale intermediate bitmaps up to this amount + static S32 smMaxSnapshotScale; + + // scale pixel size by this amount when selecting detail levels + static F32 smDetailAdjust; + // a different error metrix used by newer shapes (screen error from hi detail) + static F32 smScreenError; + // never choose detail level number below this value (except if + // only way to get a visible detail) + static S32 smNumSkipRenderDetails; + static bool smSkipFirstFog; + static bool smSkipFog; + + virtual void render(const Point3F * objectScale = NULL); + virtual void render(S32 dl, F32 intraDL = 0.0f, const Point3F * objectScale = NULL); + void renderShadow(S32 dl, const MatrixF & mat, S32 dim, U32 * bits); + void setupFog(F32 fogAmount, const ColorF & fogColor); + void setupFog(F32 fogAmount, TextureHandle * fogMap, Point4F & s, Point4F & t); + + GBitmap * snapshot(U32 width, U32 height, bool mipmap, MatrixF & cameraMatrix, bool hiQuality); + static GBitmap * snapshot(TSShape *, U32 width, U32 height, bool mipmap, MatrixF & cameraMatrix, S32 dl, F32 intraDL = 0.0f, bool hiQuality = false); + + void animate(); + void animate(S32 dl); + void animateNodes(S32 ss); + void animateVisibility(S32 ss); + void animateFrame(S32 ss); + void animateMatFrame(S32 ss); + void animateDecals(S32 ss); + void animateIfls(); + void animateSubtrees(bool forceFull = true); + void animateNodeSubtrees(bool forceFull = true); + + bool hasTranslucency(); + bool hasSolid(); + + // query about animated scale + bool animatesScale() { return (mShape->mFlags & TSShape::AnyScale) != 0; } + bool animatesUniformScale() { return (mShape->mFlags & TSShape::UniformScale) != 0; } + bool animatesAlignedScale() { return (mShape->mFlags & TSShape::AlignedScale) != 0; } + bool animatesArbitraryScale() { return (mShape->mFlags & TSShape::ArbitraryScale) != 0; } + bool scaleCurrentlyAnimated() { return mScaleCurrentlyAnimated; } + + // + bool inTransition() { return !mTransitionThreads.empty(); } + + // open up mAlphaIsReflectanceMap for custom rendering + bool queryAlphaIsReflectanceMap(){ return mAlphaIsReflectanceMap; } + void setAlphaIsReflectanceMap( bool val ){ mAlphaIsReflectanceMap = val; } + + void animateGround(); // clears previous ground transform + MatrixF & getGroundTransform() { return mGroundTransform; } + void deltaGround(TSThread *, F32 start, F32 end, MatrixF * mat = NULL); + void deltaGround1(TSThread *, F32 start, F32 end, MatrixF& mat); + + U32 getNumDetails(); + S32 getCurrentDetail(); + F32 getCurrentIntraDetail(); + void setCurrentDetail(S32 dl, F32 intraDL=1.0f); + S32 selectCurrentDetail(bool ignoreScale = false); + S32 selectCurrentDetail(Point3F offset, F32 invScale = 1.0f); + S32 selectCurrentDetail(F32 pixelSize); + S32 selectCurrentDetail2(F32 adjustedDist); + // fancy detail selection -- uses screen error + S32 selectCurrentDetailEx(bool ignoreScale = false); + S32 selectCurrentDetail2Ex(F32 adjustedDist); + S32 selectCurrentDetailEx(F32 errorTOL); + + enum + { + TransformDirty = 1 << 0, + VisDirty = 1 << 1, + FrameDirty = 1 << 2, + MatFrameDirty = 1 << 3, + DecalDirty = 1 << 4, + IflDirty = 1 << 5, + ThreadDirty = 1 << 6, + AllDirtyMask = TransformDirty | VisDirty | FrameDirty | MatFrameDirty | DecalDirty | IflDirty | ThreadDirty + }; + U32 * mDirtyFlags; + void setDirty(U32 dirty); + void clearDirty(U32 dirty); + +//------------------------------------------------------------------------------------- +// collision interface routines +//------------------------------------------------------------------------------------- + + public: + + bool buildPolyList(AbstractPolyList *, S32 dl); + bool getFeatures(const MatrixF& mat, const Point3F& n, ConvexFeature*, S32 dl); + bool castRay(const Point3F & start, const Point3F & end, RayInfo *,S32 dl); + bool quickLOS(const Point3F & start, const Point3F & end, S32 dl) { return castRay(start,end,NULL,dl); } + Point3F support(const Point3F & v, S32 dl); + void computeBounds(S32 dl, Box3F & bounds); // uses current transforms to compute bounding box around a detail level + // see like named method on shape if you want to use default transforms + +//------------------------------------------------------------------------------------- +// Thread Control +//------------------------------------------------------------------------------------- + + public: + + TSThread * addThread(); + TSThread * getThread(S32 threadNumber); // Note: threads can change order, best to hold + // onto a thread from the start + void destroyThread(TSThread * thread); + S32 threadCount(); + + void setSequence(TSThread *, S32 seq, F32 pos); + void transitionToSequence(TSThread *, S32 seq, F32 pos, F32 duration, bool continuePlay); + void clearTransition(TSThread *); + S32 getSequence(TSThread *); + + void setBlendEnabled(TSThread *, bool blendOn); + bool getBlendEnabled(TSThread *); + + F32 getTime(TSThread * thread); + F32 getPos(TSThread * thread); + + void setTime(TSThread * thread, F32 time); + void setPos(TSThread * thread, F32 pos); + + bool isInTransition(TSThread * thread); + F32 getTimeScale(TSThread * thread); + void setTimeScale(TSThread * thread, F32); + + F32 getDuration(TSThread * thread); + F32 getScaledDuration(TSThread * thread); + + S32 getKeyframeCount(TSThread * thread); + S32 getKeyframeNumber(TSThread * thread); + void setKeyframeNumber(TSThread * thread, S32 kf); + + void advanceTime(F32 delta, TSThread *); // advance time on a particular thread + void advanceTime(F32 delta); // advance time on all threads + void advancePos(F32 delta, TSThread *); // advance pos on a particular thread + void advancePos(F32 delta); // advance pos on all threads + +//------------------------------------------------------------------------------------- +// constructors, destructors, initialization, io +//------------------------------------------------------------------------------------- + + TSShapeInstance( const Resource & shape, bool loadMaterials = true); + TSShapeInstance( TSShape * pShape, bool loadMaterials = true); + ~TSShapeInstance(); + + void buildInstanceData(TSShape *, bool loadMaterials); + + void dump(Stream &); + void dumpNode(Stream &, S32 level, S32 nodeIndex, Vector & detailSizes); + void dumpDecals(Stream &, S32 indent, MeshObjectInstance *); + + U32 mData; // available for use by app...initialized to 0 +}; + +inline MatrixF * TSShapeInstance::ObjectInstance::getTransform() +{ + return nodeIndex<0 ? NULL : smTransforms + nodeIndex; +} + +//------------------------------------------------------------------------------------- +// Thread class +//------------------------------------------------------------------------------------- +// An animation thread: runtime data associated with a single sequence that is +// running (or two sequences if in transition between them). +// A shape can have multiple threads running...when multiple threads are running, +// which thread/sequence controls which node or object is determined based +// on priority of the sequence. +// Note: all thread data and methods are private (but shapeInstance is a friend). +// Users should treat thread pointers like keys -- they are used to id +// the thread when interfacing with the shape, but are not manipulated +// by anything but the shapeInstance. See "Thread control" methods +// for more info on controlling threads. + +class TSThread +{ + friend class TSShapeInstance; + + S32 priority; + + TSShapeInstance * mShapeInstance; + + const TSSequence * sequence; + F32 pos; + + F32 timeScale; + + S32 keyNum1; + S32 keyNum2; + F32 keyPos; + + bool blendDisabled; + + // if in transition... + struct TransitionData + { + bool inTransition; + + F32 duration; + F32 pos; + F32 direction; + F32 targetScale; // time scale for sequence we are transitioning to (during transition only) + // this is either 1 or 0 (if 1 target sequence plays as we transition, if 0 it doesn't) + TSIntegerSet oldRotationNodes; // nodes controlled by this thread pre-transition + TSIntegerSet oldTranslationNodes; // nodes controlled by this thread pre-transition + TSIntegerSet oldScaleNodes; // nodes controlled by this thread pre-transition + S32 oldSequence; // sequence that was set before transition began + F32 oldPos; // position of sequence before transition began + } transitionData; + + struct + { + F32 start; + F32 end; + S32 loop; + } path; + bool makePath; + + void selectKeyframes(F32 pos, const TSSequence * seq, S32 * k1, S32 * k2, F32 * kpos); + void getGround(F32 p, MatrixF * pMat); + + // called by advancePos + void animateTriggers(); + void activateTriggers(F32 a, F32 b); + + // methods -- accessible through shape only + void setSequence(S32 seq, F32 pos); + void transitionToSequence(S32 seq, F32 pos, F32 duration, bool continuePlay); + + void advanceTime(F32 delta); + void advancePos(F32 delta); + + F32 getTime(); + F32 getPos(); + + void setTime(F32); + void setPos(F32); + + bool isInTransition(); + F32 getTimeScale(); + void setTimeScale(F32); + + F32 getDuration(); + F32 getScaledDuration(); + + S32 getKeyframeCount(); + S32 getKeyframeNumber(); + void setKeyframeNumber(S32 kf); + + TSThread(TSShapeInstance*); + TSThread() {} +public: + + S32 operator<(const TSThread &) const; +}; + +typedef TSShapeInstance::ObjectInstance TSObjectInstance; + +#endif diff --git a/ts/tsShapeOldRead.cc b/ts/tsShapeOldRead.cc new file mode 100644 index 0000000..1f82a7b --- /dev/null +++ b/ts/tsShapeOldRead.cc @@ -0,0 +1,1519 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShape.h" +#include "ts/tsShapeInstance.h" + +// methods in this file are used for reading pre-version 19 shapes +// methods for reading/writing sequences still used... + +#define OldPageSize 25000 // old page size must be mutliple of 4 so that we can always "over-read" up to next dword + +struct OldAlloc +{ + static S32 sz32; + static S32 cnt32; + + static S32 sz16; + static S32 cnt16; + + static S32 sz8; + static S32 cnt8; + + static S32 guard32; + static S16 guard16; + static S8 guard8; +}; + +S32 OldAlloc::sz32; +S32 OldAlloc::cnt32; +S32 OldAlloc::sz16; +S32 OldAlloc::cnt16; +S32 OldAlloc::sz8; +S32 OldAlloc::cnt8; +S32 OldAlloc::guard32; +S16 OldAlloc::guard16; +S8 OldAlloc::guard8; + +S32 getDWordCount32() +{ + return OldAlloc::cnt32; +} + +S32 getDWordCount16() +{ + if (OldAlloc::cnt16&1) + return (OldAlloc::cnt16+2) >> 1; + else + return OldAlloc::cnt16 >> 1; +} + +S32 getDWordCount8() +{ + if (OldAlloc::cnt8&3) + return (OldAlloc::cnt8+4) >> 2; + else + return OldAlloc::cnt8 >> 2; +} + +S32 * oldInitAlloc32() +{ + OldAlloc::sz32 = 0; + OldAlloc::cnt32 = 0; + OldAlloc::guard32 = 0; + return NULL; +} + +S16 * oldInitAlloc16() +{ + OldAlloc::sz16 = 0; + OldAlloc::cnt16 = 0; + OldAlloc::guard16 = 0; + return NULL; +} + +S8 * oldInitAlloc8() +{ + OldAlloc::sz8= 0; + OldAlloc::cnt8 = 0; + OldAlloc::guard8 = 0; + return NULL; +} + +S32 oldAllocOffset(S32 *) +{ + return OldAlloc::cnt32; +} + +S32 oldAllocOffset(S16 *) +{ + return OldAlloc::cnt16; +} + +S8 * oldAlloc(S32 * & addr, S32 count) +{ + if (OldAlloc::cnt32+count>OldAlloc::sz32) + { + S32 numPages = 1+((OldAlloc::cnt32+count)/OldPageSize); + OldAlloc::sz32 = numPages * OldPageSize; + S32 * tmp = new S32[OldAlloc::sz32]; + if (addr) + dMemcpy((U8*)tmp,(U8*)addr,OldAlloc::cnt32*sizeof(U32)); + delete [] addr; + addr = tmp; + } + S8 * ret = (S8*) &addr[OldAlloc::cnt32]; + OldAlloc::cnt32 += count; + return ret; +} + +S8 * oldAlloc(S16 * & addr, S32 count) +{ + if (OldAlloc::cnt16+count>OldAlloc::sz16) + { + S32 numPages = 1+((OldAlloc::cnt16+count)/OldPageSize); + OldAlloc::sz16 = numPages * OldPageSize; + S16 * tmp = new S16[OldAlloc::sz16]; + if (addr) + dMemcpy((U8*)tmp,(U8*)addr,OldAlloc::cnt16*sizeof(S16)); + delete [] addr; + addr = tmp; + } + S8 * ret = (S8*) &addr[OldAlloc::cnt16]; + OldAlloc::cnt16 += count; + return ret; +} + +S8 * oldAlloc(S8 * & addr, S32 count) +{ + if (OldAlloc::cnt8+count>OldAlloc::sz8) + { + S32 numPages = 1+((OldAlloc::cnt8+count)/OldPageSize); + OldAlloc::sz8 = numPages * OldPageSize; + S8 * tmp = new S8[OldAlloc::sz8]; + if (addr) + dMemcpy((U8*)tmp,(U8*)addr,OldAlloc::cnt8); + delete [] addr; + addr = tmp; + } + S8 * ret = (S8*) &addr[OldAlloc::cnt8]; + OldAlloc::cnt8 += count; + return ret; +} + +S32 readAlloc32(Stream * s, S32 * & memBuffer32) +{ + S32 tmp; + s->read(sizeof(tmp),(U8*)&tmp); + U32 * ptr = (U32*)oldAlloc(memBuffer32,1); + *ptr = tmp; + return (S32)convertLEndianToHost(tmp); +} + +S32 readAlloc32(Stream * s, S32 & storage) +{ + S32 tmp; + s->read(sizeof(tmp),(U8*)&tmp); + storage = tmp; + return (S32)convertLEndianToHost(tmp); +} + +void readAlloc(Stream * s, S32 * & memBuffer32, S32 size) +{ + s->read(sizeof(S32)*size,oldAlloc(memBuffer32,size)); +} + +void readAlloc(Stream * s, S16 * & memBuffer16, S32 size) +{ + s->read(sizeof(S16)*size,oldAlloc(memBuffer16,size)); +} + +void readAlloc(Stream * s, S8 * & memBuffer8, S32 size) +{ + s->read(sizeof(S8)*size,oldAlloc(memBuffer8,size)); +} + +void oldAllocGuard(S32 * & addr32, S16 * & addr16, S8 * & addr8) +{ + S32 * ptr32 = (S32*)oldAlloc(addr32,1); + *ptr32 = convertLEndianToHost(OldAlloc::guard32++); + + S16 * ptr16 = (S16*)oldAlloc(addr16,1); + *ptr16 = convertLEndianToHost(OldAlloc::guard16++); + + S8 * ptr8 = (S8*)oldAlloc(addr8,1); + *ptr8 = OldAlloc::guard8++; +} + +#define DebugGuard() oldAllocGuard(memBuffer32,memBuffer16,memBuffer8) + +void readAllocMesh(Stream * s, S32 * & memBuffer32, S16 * & memBuffer16, S8 * & memBuffer8, U32 meshType) +{ + memBuffer8; + + if (meshType==TSMesh::NullMeshType) + return; + + // standard mesh read + + DebugGuard(); + + // numFrames, numMatFrames + readAlloc(s,memBuffer32,2); + + // parent mesh + S32 * ptr32 = (S32*)oldAlloc(memBuffer32,1); + *ptr32 = convertLEndianToHost(-1); + + // allocate memory for mBounds,mCenter, and mRadius...just filler, will be computed later + oldAlloc(memBuffer32,10); + + // read in verts + S32 sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz*3); + + // read in tverts + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz*2); + + // read in normals + s->read(&sz); // we assume same as verts + readAlloc(s,memBuffer32,sz*3); + + // read in primitives + sz = readAlloc32(s,memBuffer32); + if (TSShape::smReadVersion<18) + { + for (S32 j=0; jread(&a); + s->read(&b); + U16 * ptr16 = (U16*)oldAlloc(memBuffer16,2); + ptr16[0]=convertLEndianToHost((S16)a); + ptr16[1]=convertLEndianToHost((S16)b); + readAlloc32(s,memBuffer32); + } + } + else + { + for (S32 j=0; jread(sizeof(idx),(U8*)&idx); + idxPtr[j]=(U16)idx; + } + } + else + readAlloc(s,memBuffer16,sz); + + // mergeIndices...none + ptr32 = (S32*)oldAlloc(memBuffer32,1); + *ptr32 = convertLEndianToHost((S32)0); + + // vertsPerFrame, flags + readAlloc(s,memBuffer32,2); + + DebugGuard(); + + if (meshType==TSMesh::SkinMeshType) + { + // read in initial verts + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz*3); + + // read in initial normals + s->read(&sz); // we assume same as verts + readAlloc(s,memBuffer32,sz*3); + + // read in initial transforms + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz*16); + + // read in vertexIndx + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz); + + // read in boneIndex + s->read(&sz); // toss + readAlloc(s,memBuffer32,sz); + + // read in nodeIndex -- but let's move it... + // vertexIndex, boneIndex, and weight have same # of entries + // nodeIndex is different (it's number of bones) + // first allocate room for weights, then read in nodeIndex list + S32 weightStart = oldAllocOffset(memBuffer32); + oldAlloc(memBuffer32,sz); // this is memory for the weights + + // actually read in node index + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz); // read in nodeIndex array + + // read in weight + s->read(&sz); // toss + F32 * ptr32 = (F32*)(memBuffer32 + weightStart); + for (S32 i=0; iread(sizeof(F32),(U8*)(ptr32+i)); + + DebugGuard(); + } + if (meshType==TSMesh::SortedMeshType) + { + // clusters... + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz*8); + + // start cluster... + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz); + + // firstVert... + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz); + + // numVerts... + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz); + + // firstTVerts... + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz); + + // always write z-depth? + bool alwaysWriteZ; + s->read(&alwaysWriteZ); + S32 * ptr32 = (S32*) oldAlloc(memBuffer32,1); + *ptr32 = alwaysWriteZ ? convertLEndianToHost(1) : convertLEndianToHost(0); + + DebugGuard(); + } + if (meshType==TSMesh::DecalMeshType) + { + // startPrimitive... + sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz); + + if (TSShape::smReadVersion>=17) + { + // obsolete... + + // startTVerts... + s->read(&sz); + S32 tmp; + while (sz--) + s->read(&tmp); + + // tvertIndex... + s->read(&sz); + while (sz--) + s->read(&tmp); + } + + // materialIndex... + readAlloc32(s,memBuffer32); + + DebugGuard(); + } +} + +// versioning hack... +Vector kfStart(__FILE__, __LINE__); + +// NOTE: with version 17 and on there are no more keyframes...kept around for reading old shapes +struct Keyframe +{ + S32 firstNodeState; + S32 firstObjectState; + S32 firstDecalState; + + void read(Stream * s) { s->read(&firstNodeState);s->read(&firstObjectState);s->read(&firstDecalState);} + void write(Stream * s) { s->write(firstNodeState);s->write(firstObjectState);s->write(firstDecalState);} +}; + +// read in and throw away +Vector keyframes(__FILE__,__LINE__); + +void TSShape::readOldShape(Stream * s, + S32 * & memBuffer32, S16 * & memBuffer16, S8 * & memBuffer8, + S32 & count32, S32 & count16, S32 & count8) +{ + S32 i,tmp; + + // first allocate some memory + memBuffer32 = oldInitAlloc32(); + memBuffer16 = oldInitAlloc16(); + memBuffer8 = oldInitAlloc8(); + + // allocate storage for vector counts + oldAlloc(memBuffer32,15); + // contents of memBuffer32 might change, but eventually these will go here... + // numNodes = memBuffer32[0]; + // numObjects = memBuffer32[1]; + // numDecals = memBuffer32[2]; + // numSubShapes = memBuffer32[3]; + // numIflMaterials = memBuffer32[4]; + // numNodeStates = memBuffer32[5]; + // numObjectStates = memBuffer32[6]; + // numDecalStates = memBuffer32[7]; + // numTriggers = memBuffer32[8]; + // numDetails = memBuffer32[9]; + // numMeshes = memBuffer32[10] + // numSkins = memBuffer32[11]; + // numNames = memBuffer32[12]; + // mSmalletVisibleSize = memBuffer32[13]; + // mSmallestVisibleDL = memBuffer32[14]; + + DebugGuard(); + + // radius + readAlloc(s,memBuffer32,1); + // tube radius + readAlloc(s,memBuffer32,1); + // center + readAlloc(s,memBuffer32,3); + // bounds + readAlloc(s,memBuffer32,6); + + DebugGuard(); + + S32 numNodes = readAlloc32(s,memBuffer32[0]); + S32 nodeStart = oldAllocOffset(memBuffer32); + if (smReadVersion>=17) + { + readAlloc(s,memBuffer32,numNodes*2); + // compute other 3 members at load time... + // for now, allocate the storage and move things around + oldAlloc(memBuffer32,numNodes*3); + S32 * pNodeStart = memBuffer32+nodeStart; + for (i=numNodes-1; i>=0; i--) + dMemmove(&pNodeStart[i*5],&pNodeStart[i*2],sizeof(S32)*2); + } + else + { + // handle obsolete (bool) member + for (i=0; iread(&obsolete); + oldAlloc(memBuffer32,3); + } + } + + DebugGuard(); + + S32 numObjects = readAlloc32(s,memBuffer32[1]); + S32 objectStart = oldAllocOffset(memBuffer32); + readAlloc(s,memBuffer32,numObjects*4); + // compute other 2 members at load time... + // for now, allocate storage and move things around + oldAlloc(memBuffer32,numObjects*2); + S32 * pObjectStart = memBuffer32 + objectStart; + for (i=numObjects-1; i>=0; i--) + dMemmove(&pObjectStart[i*6],&pObjectStart[i*4],sizeof(S32)*4); + + DebugGuard(); + + S32 numDecals = readAlloc32(s,memBuffer32[2]); + S32 decalStart = oldAllocOffset(memBuffer32); + readAlloc(s,memBuffer32,numDecals*4); + // compute other 1 member at load time... + // for now, allocate storage and move things around + oldAlloc(memBuffer32,numDecals); + S32 * pDecalStart = memBuffer32 + decalStart; + for (i=numDecals-1; i>=0; i--) + dMemmove(&pDecalStart[i*5],&pDecalStart[i*4],sizeof(S32)*4); + + DebugGuard(); + + S32 numIflMaterials = readAlloc32(s,memBuffer32[4]); + S32 iflStart = oldAllocOffset(memBuffer32); + readAlloc(s,memBuffer32,numIflMaterials*2); + // compute other 3 members at load time... + // for now, allocate storage and move things around + oldAlloc(memBuffer32,numIflMaterials*3); + S32 * pIflStart = memBuffer32 + iflStart; + for (i=numIflMaterials-1; i>=0; i--) + dMemmove(&pIflStart[i*5],&pIflStart[i*2],sizeof(S32)*2); + + DebugGuard(); + + S32 numSubShapes = readAlloc32(s,memBuffer32[3]); + S32 subShapeFirstStart = oldAllocOffset(memBuffer32); + readAlloc(s,memBuffer32,numSubShapes); // subShapeFirstNode + s->read(&tmp); // toss + readAlloc(s,memBuffer32,numSubShapes); // subShapeFirstObject + s->read(&tmp); // toss + readAlloc(s,memBuffer32,numSubShapes); // subShapeFirstDecal + + DebugGuard(); + + S32 subShapeNumStart = oldAllocOffset(memBuffer32); + oldAlloc(memBuffer32,3*numSubShapes); // allocate memory for subShapeNum* vectors + + DebugGuard(); + + // compute subShapeNum* vectors + S32 * pSubShapeFirstStart = memBuffer32+subShapeFirstStart; + S32 * pSubShapeNumStart = memBuffer32+subShapeNumStart; + for (i=0; i<3; i++) + { + S32 prev = i==0 ? numNodes : (i==1 ? numObjects : numDecals); + for (S32 j=numSubShapes-1; j>=0; j--) + { + pSubShapeNumStart[j] = prev - convertLEndianToHost(pSubShapeFirstStart[j]); + prev = convertLEndianToHost(pSubShapeFirstStart[j]); + } + pSubShapeFirstStart += numSubShapes; + pSubShapeNumStart += numSubShapes; + } + + // if older than version 16, read in mesh index list for later use + if (smReadVersion<16) + { + S32 sz = readAlloc32(s,memBuffer32); + readAlloc(s,memBuffer32,sz); // this is the meshIndexList + } + + // if older than version 17, read in keyframes for later use + // but don't add to shapes buffer because we'll be done with + // them on exit from this method + if (smReadVersion<17) + { + S32 sz; + s->read(&sz); + keyframes.setSize(sz); + for (i=0; i=0; i--) + dMemmove(&pDetailStart[i*7],&pDetailStart[i*4],4*sizeof(S32)); + // now find the smallest renderable detail level and size + Detail * pd = (Detail*)pDetailStart; + memBuffer32[13] = 0; // initialize to something valid + memBuffer32[14] = 0; // initialize to something valid + for (i=0; i=0.0f) + { + memBuffer32[13] = (S32)dsize; + memBuffer32[14] = i; + } + pd[i].maxError = convertLEndianToHost(-1.0f); + pd[i].averageError = convertLEndianToHost(-1.0f); + pd[i].polyCount = convertLEndianToHost(0); + } + + DebugGuard(); + + // deal with sequences the old fashion way... + kfStart.clear(); // for older shapes... + S32 numSequences; + s->read(&numSequences); + sequences.setSize(numSequences); + for (i=0; iread(&sz); + readAlloc(s,memBuffer8,sz); + *oldAlloc(memBuffer8,1) = 0; // end the string + } + + DebugGuard(); + + // material list...read the old fashion way + S32 gotList; + s->read(&gotList); + if (gotList) + { + materialList = new TSMaterialList; + materialList->read(*s); + if (mExporterVersion<116) + { + // for any material that is translucent and doesn't tile, add zero border property + for (i=0; igetMaterialCount(); i++) + { + U32 flags = materialList->getFlags(i); + if ((flags & TSMaterialList::Translucent) && !(flags&(TSMaterialList::S_Wrap|TSMaterialList::T_Wrap))) + materialList->setFlags(i,flags|TSMaterialList::MipMap_ZeroBorder); + } + } + } + else + materialList = NULL; + + // allocate memory for these vectors here...filled in below + S32 detailFirstSkinCount = oldAllocOffset(memBuffer32); + oldAlloc(memBuffer32,numDetails); + S32 detailNumSkinCount = oldAllocOffset(memBuffer32); + oldAlloc(memBuffer32,numDetails); + + DebugGuard(); + + // skins + S32 numSkins = readAlloc32(s,memBuffer32[11]); + for (i=0; iread(&sz); + S32 * pDetailFirstSkin = memBuffer32+detailFirstSkinCount; + S32 * pDetailNumSkin = memBuffer32+detailNumSkinCount; + s->read(numDetails*sizeof(S32),(S8*)pDetailFirstSkin); + S32 prev = numSkins; + for (i=numDetails-1; i>=0; i--) + { + pDetailNumSkin[i] = convertLEndianToHost(prev - convertLEndianToHost(pDetailFirstSkin[i])); + prev = convertLEndianToHost(pDetailFirstSkin[i]); + } + } + + DebugGuard(); + + if (smReadVersion<17) + { + for (i=0; i skinsCopy; + // Note: newObjects has as much free space as we need, so we just need to keep track of the + // number of objects we use and then update objects.size + S32 numSkinObjects = 0; + S32 skinsUsed = 0; + S32 emptySkins = 0; + S32 i; + for (i=0; iwrite(a.size() - m); \ + for (S32 i=m;iwrite(a.size() - m); \ + for (S32 i=m;iwrite(a[i]); \ +} + +// same as above with m=0 +#define writeVectorStruct(a) writeVectorStructMinus(a,0) +#define writeVectorSimple(a) writeVectorSimpleMinus(a,0) + +// read a vector of structs -- over-writing any existing data +#define readVectorStruct(a) \ +{ \ + S32 sz; \ + s->read(&sz); \ + a.setSize(sz); \ + for (S32 i=0;iread(&sz); \ + a.setSize(sz); \ + for (S32 i=0;iread(&a[i]); \ +} + +// read a vector of structs -- append to any existing data +#define appendVectorStruct(a) \ +{ \ + S32 sz; \ + S32 oldSz = a.size(); \ + s->read(&sz); \ + a.setSize(oldSz + sz); \ + for (S32 i=0;iread(&sz); \ + a.setSize(oldSz + sz); \ + for (S32 i=0;iread(&a[i + oldSz]); \ +} + +//------------------------------------------------- +// export all sequences +//------------------------------------------------- +void TSShape::exportSequences(Stream * s) +{ + // write version + s->write(smVersion); + + S32 i,sz; + + // write node names + // -- this is how we will map imported sequence nodes to shape nodes + sz = nodes.size(); + s->write(sz); + for (i=0;iwrite(0); + + // on import, we will need to adjust keyframe data based on number of + // nodes/objects in this shape...number of nodes can be inferred from + // above, but number of objects cannot be. Write that quantity here: + s->write(objects.size()); + + // write node states -- skip default node states + s->write(nodeRotations.size()); + for (i=0;iwrite(nodeRotations[i].x); + s->write(nodeRotations[i].y); + s->write(nodeRotations[i].z); + s->write(nodeRotations[i].w); + } + s->write(nodeTranslations.size()); + for (i=0;iwrite(nodeTranslations[i].x); + s->write(nodeTranslations[i].y); + s->write(nodeTranslations[i].z); + } + s->write(nodeUniformScales.size()); + for (i=0;iwrite(nodeUniformScales[i]); + s->write(nodeAlignedScales.size()); + for (i=0;iwrite(nodeAlignedScales[i].x); + s->write(nodeAlignedScales[i].y); + s->write(nodeAlignedScales[i].z); + } + s->write(nodeArbitraryScaleRots.size()); + for (i=0;iwrite(nodeArbitraryScaleRots[i].x); + s->write(nodeArbitraryScaleRots[i].y); + s->write(nodeArbitraryScaleRots[i].z); + s->write(nodeArbitraryScaleRots[i].w); + } + for (i=0;iwrite(nodeArbitraryScaleFactors[i].x); + s->write(nodeArbitraryScaleFactors[i].y); + s->write(nodeArbitraryScaleFactors[i].z); + } + s->write(groundTranslations.size()); + for (i=0;iwrite(groundTranslations[i].x); + s->write(groundTranslations[i].y); + s->write(groundTranslations[i].z); + } + for (i=0;iwrite(groundRotations[i].x); + s->write(groundRotations[i].y); + s->write(groundRotations[i].z); + s->write(groundRotations[i].w); + } + + // write object states -- legacy..no object states + s->write((S32)0); + + // write sequences + s->write(sequences.size()); + for (i=0;i don't write name index + } + + // write out all the triggers... + s->write(triggers.size()); + for (i=0; iwrite(triggers[i].state); + s->write(triggers[i].pos); + } +} + +//------------------------------------------------- +// import sequences into existing shape +//------------------------------------------------- +bool TSShape::importSequences(Stream * s) +{ + // write version + s->read(&smReadVersion); + if (smReadVersion>smVersion) + { + // error -- don't support future version yet :> + Con::errorf(ConsoleLogEntry::General, + "Sequence import failed: shape exporter newer than running executable."); + return false; + } + + Vector nodeMap; // node index of each node from imported sequences + Vector objectMap; // object index of objects from imported sequences + VECTOR_SET_ASSOCIATION(nodeMap); + VECTOR_SET_ASSOCIATION(objectMap); + + S32 i,sz; + + // read node names + // -- this is how we will map imported sequence nodes to our nodes + s->read(&sz); + nodeMap.setSize(sz); + Vector checkForDups; + for (i=0;i=0) + { + while (checkForDups.size()read(&sz); sz; + + // before reading keyframes, take note of a couple numbers + S32 oldShapeNumObjects; + s->read(&oldShapeNumObjects); + + // read keyframes + if (smReadVersion<17) + { + keyframes.clear(); + appendVectorStruct(keyframes); + } + + // adjust all the new keyframes + S32 adjNodeRots = smReadVersion<22 ? nodeRotations.size() - nodeMap.size() : nodeRotations.size(); + S32 adjNodeTrans = smReadVersion<22 ? nodeTranslations.size() - nodeMap.size() : nodeTranslations.size(); + S32 adjNodeScales1 = nodeUniformScales.size(); + S32 adjNodeScales2 = nodeAlignedScales.size(); + S32 adjNodeScales3 = nodeArbitraryScaleFactors.size(); + S32 adjObjectStates = objectStates.size() - oldShapeNumObjects; + S32 adjGroundStates = smReadVersion<22 ? 0 : groundTranslations.size(); // groundTrans==groundRot + for (i=0;i21) + { + s->read(&sz); + S32 oldSz = nodeRotations.size(); + nodeRotations.setSize(sz+oldSz); + for (i=oldSz;iread(&nodeRotations[i].x); + s->read(&nodeRotations[i].y); + s->read(&nodeRotations[i].z); + s->read(&nodeRotations[i].w); + } + s->read(&sz); + oldSz = nodeTranslations.size(); + nodeTranslations.setSize(sz+oldSz); + for (i=oldSz;iread(&nodeTranslations[i].x); + s->read(&nodeTranslations[i].y); + s->read(&nodeTranslations[i].z); + } + s->read(&sz); + oldSz = nodeUniformScales.size(); + nodeUniformScales.setSize(sz+oldSz); + for (i=oldSz;iread(&nodeUniformScales[i]); + s->read(&sz); + oldSz = nodeAlignedScales.size(); + nodeAlignedScales.setSize(sz+oldSz); + for (i=oldSz;iread(&nodeAlignedScales[i].x); + s->read(&nodeAlignedScales[i].y); + s->read(&nodeAlignedScales[i].z); + } + s->read(&sz); + oldSz = nodeArbitraryScaleRots.size(); + nodeArbitraryScaleRots.setSize(sz+oldSz); + for (i=oldSz;iread(&nodeArbitraryScaleRots[i].x); + s->read(&nodeArbitraryScaleRots[i].y); + s->read(&nodeArbitraryScaleRots[i].z); + s->read(&nodeArbitraryScaleRots[i].w); + } + oldSz = nodeArbitraryScaleFactors.size(); + nodeArbitraryScaleFactors.setSize(sz+oldSz); + for (i=oldSz;iread(&nodeArbitraryScaleFactors[i].x); + s->read(&nodeArbitraryScaleFactors[i].y); + s->read(&nodeArbitraryScaleFactors[i].z); + } + s->read(&sz); + oldSz = groundTranslations.size(); + groundTranslations.setSize(sz+oldSz); + for (i=oldSz;iread(&groundTranslations[i].x); + s->read(&groundTranslations[i].y); + s->read(&groundTranslations[i].z); + } + groundRotations.setSize(sz+oldSz); + for (i=oldSz;iread(&groundRotations[i].x); + s->read(&groundRotations[i].y); + s->read(&groundRotations[i].z); + s->read(&groundRotations[i].w); + } + } + else + { + s->read(&sz); + S32 oldSz1 = nodeRotations.size(); + S32 oldSz2 = nodeTranslations.size(); + nodeRotations.setSize(oldSz1+sz); + nodeTranslations.setSize(oldSz2+sz); + for (i=0; iread(&nodeRotations[i+oldSz1].x); + s->read(&nodeRotations[i+oldSz1].y); + s->read(&nodeRotations[i+oldSz1].z); + s->read(&nodeRotations[i+oldSz1].w); + s->read(&nodeTranslations[i+oldSz2].x); + s->read(&nodeTranslations[i+oldSz2].y); + s->read(&nodeTranslations[i+oldSz2].z); + } + } + + // add these object states to our own -- shouldn't be any...assume it + s->read(&sz); + + // read sequences + s->read(&sz); + S32 startSeqNum = sequences.size(); + kfStart.clear(); // versioning hack -- holds start of range of keyframes used by each sequence loaded + for (i=0;i21) + { + seq.baseRotation += adjNodeRots; + seq.baseTranslation += adjNodeTrans; + if (seq.animatesUniformScale()) + seq.baseScale += adjNodeScales1; + else if (seq.animatesAlignedScale()) + seq.baseScale += adjNodeScales2; + else if (seq.animatesArbitraryScale()) + seq.baseScale += adjNodeScales3; + } + else if (smReadVersion>=17) + { + seq.baseRotation += adjNodeRots; // == adjNodeTrans + seq.baseTranslation += adjNodeTrans; // == adjNodeTrans + } + + // not quite so easy... + // now we have to remap nodes from shape the sequence came from to this shape + // that's where nodeMap comes in handy... + // ditto for the objects. + + // first the nodes + S32 j; + TSIntegerSet newMembership1; + TSIntegerSet newMembership2; + TSIntegerSet newMembership3; + for (j=0; j8) + { + S32 sz; + S32 oldSz = triggers.size(); + s->read(&sz); + triggers.setSize(oldSz+sz); + for (S32 i=0; iread(&triggers[i+oldSz].state); + s->read(&triggers[i+oldSz].pos); + } + } + + if (smInitOnRead) + init(); + + return true; +} + +//------------------------------------------------- +// read/write sequence +//------------------------------------------------- +void TSShape::Sequence::read(Stream * s, bool readNameIndex) +{ + if (readNameIndex) + s->read(&nameIndex); + flags = 0; + if (TSShape::smReadVersion>21) + s->read(&flags); + else + flags=0; + if (TSShape::smReadVersion<17) + { + // prior to version 17 we had a vector of keyframes and needed to track range of keyframes not just number + S32 startKeyframe, endKeyframe; + s->read(&startKeyframe); + s->read(&endKeyframe); + numKeyframes = endKeyframe - startKeyframe; + kfStart.push_back(startKeyframe); + } + else + // just need number of keyframes... + s->read(&numKeyframes); + s->read(&duration); + + if (TSShape::smReadVersion<22) + { + bool tmp; + s->read(&tmp); + if (tmp) + flags |= Blend; + s->read(&tmp); + if (tmp) + flags |= Cyclic; + s->read(&tmp); + if (tmp) + flags |= MakePath; + } + + s->read(&priority); + s->read(&firstGroundFrame); + s->read(&numGroundFrames); + if (TSShape::smReadVersion>21) + { + s->read(&baseRotation); + s->read(&baseTranslation); + s->read(&baseScale); + s->read(&baseObjectState); + s->read(&baseDecalState); + } + else if (TSShape::smReadVersion>=17) + { + s->read(&baseRotation); + baseTranslation=baseRotation; + s->read(&baseObjectState); + s->read(&baseDecalState); + } + if (TSShape::smReadVersion>8) + { + s->read(&firstTrigger); + s->read(&numTriggers); + } + else + { + firstTrigger = 0; + numTriggers = 0; + } + if (TSShape::smReadVersion>7) + s->read(&toolBegin); + else + toolBegin=0.0f; + + // now the membership sets: + rotationMatters.read(s); + if (TSShape::smReadVersion<22) + translationMatters=rotationMatters; + else + { + translationMatters.read(s); + scaleMatters.read(s); + } + if (TSShape::smReadVersion<17) + { + TSIntegerSet objectMembership; // obsolete + objectMembership.read(s); + } + if (TSShape::smReadVersion>10) + decalMatters.read(s); + if (TSShape::smReadVersion>5) + iflMatters.read(s); + visMatters.read(s); + frameMatters.read(s); + matFrameMatters.read(s); + if (TSShape::smReadVersion<17) + { + // obsolete... + TSIntegerSet nodeTransformStatic; + nodeTransformStatic.read(s); + } + + dirtyFlags = 0; + if (rotationMatters.testAll() || translationMatters.testAll() || scaleMatters.testAll()) + dirtyFlags |= TSShapeInstance::TransformDirty; + if (visMatters.testAll()) + dirtyFlags |= TSShapeInstance::VisDirty; + if (frameMatters.testAll()) + dirtyFlags |= TSShapeInstance::FrameDirty; + if (matFrameMatters.testAll()) + dirtyFlags |= TSShapeInstance::MatFrameDirty; + if (decalMatters.testAll()) + dirtyFlags |= TSShapeInstance::DecalDirty; + if (iflMatters.testAll()) + dirtyFlags |= TSShapeInstance::IflDirty; +} + +void TSShape::Sequence::write(Stream * s, bool writeNameIndex) +{ + if (writeNameIndex) + s->write(nameIndex); + s->write(flags); + s->write(numKeyframes); + s->write(duration); + s->write(priority); + s->write(firstGroundFrame); + s->write(numGroundFrames); + s->write(baseRotation); + s->write(baseTranslation); + s->write(baseScale); + s->write(baseObjectState); + s->write(baseDecalState); + s->write(firstTrigger); + s->write(numTriggers); + s->write(toolBegin); + + // now the membership sets: + rotationMatters.write(s); + translationMatters.write(s); + scaleMatters.write(s); + decalMatters.write(s); + iflMatters.write(s); + visMatters.write(s); + frameMatters.write(s); + matFrameMatters.write(s); +} + +void TSShape::writeName(Stream * s, S32 nameIndex) +{ + const char * name = ""; + if (nameIndex>=0) + name = names[nameIndex]; + S32 sz = dStrlen(name); + s->write(sz); + if (sz) + s->write(sz*sizeof(char),name); +} + +S32 TSShape::readName(Stream * s, bool addName) +{ + static char buffer[256]; + S32 sz; + S32 nameIndex = -1; + s->read(&sz); + if (sz) + { + s->read(sz*sizeof(char),buffer); + buffer[sz] = '\0'; + nameIndex = findName(buffer); + if (nameIndex<0 && addName) + { + nameIndex = names.size(); + names.increment(); + names.last() = StringTable->insert(buffer,false); // case insensitive + } + } + return nameIndex; +} diff --git a/ts/tsSortedMesh.cc b/ts/tsSortedMesh.cc new file mode 100644 index 0000000..bfb7dff --- /dev/null +++ b/ts/tsSortedMesh.cc @@ -0,0 +1,276 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsSortedMesh.h" +#include "dgl/dgl.h" +#include "Math/mMath.h" +#include "ts/tsShapeInstance.h" + +// Not worth the effort, much less the effort to comment, but if the draw types +// are consecutive use addition rather than a table to go from index to command value... +#if ((GL_TRIANGLES+1==GL_TRIANGLE_STRIP) && (GL_TRIANGLE_STRIP+1==GL_TRIANGLE_FAN)) + #define getDrawType(a) (GL_TRIANGLES+(a)) +#else + U32 drawTypes[] = { GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN }; + #define getDrawType(a) (drawTypes[a]) +#endif + +// found in tsmesh +extern void forceFaceCamera(); +extern void forceFaceCameraZAxis(); + +//----------------------------------------------------- +// TSSortedMesh render methods +//----------------------------------------------------- + +void TSSortedMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials) +{ + if (getFlags(Billboard)) + { + if (getFlags(BillboardZAxis)) + forceFaceCameraZAxis(); + else + forceFaceCamera(); + } + + MatrixF toCam; + dglGetModelview(&toCam); + toCam.inverse(); + Point3F cameraCenter; + toCam.getColumn(3,&cameraCenter); + + S32 firstVert = firstVerts[frame]; + S32 vertCount = numVerts[frame]; + // note on the following: a TSSortedMesh can animate frame or matFrame but not both. + // The reason for this is simple: the number of verts change depending on frame, so + // if both varied then there would have to be a new set of tverts for every (frame x matFrame) + // combination, rather than for every matFrame as for other mesh types. However, either frame + // or matFrame is allowed to vary, accounting for the following odd looking statement. + S32 firstTVert = firstTVerts[matFrame ? matFrame : frame]; + + // set up vertex arrays -- already enabled in TSShapeInstance::render + glVertexPointer(3,GL_FLOAT,0,&verts[firstVert]); + glNormalPointer(GL_FLOAT,0,getNormals(firstVert)); + glTexCoordPointer(2,GL_FLOAT,0,&tverts[firstTVert]); + if (TSShapeInstance::smRenderData.detailMapMethod == TSShapeInstance::DETAIL_MAP_MULTI_1 || + TSShapeInstance::smRenderData.detailMapMethod == TSShapeInstance::DETAIL_MAP_MULTI_2) + { + glClientActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.detailMapTE); + glTexCoordPointer(2,GL_FLOAT,0,&tverts[firstTVert]); + glClientActiveTextureARB(GL_TEXTURE0_ARB + TSShapeInstance::smRenderData.baseTE); + } + + // lock... + if (gGLState.suppLockedArrays) + glLockArraysEXT(0,vertCount); + + if (alwaysWriteDepth) + glDepthMask(GL_TRUE); + + Cluster * cluster; + S32 nextCluster = startCluster[frame]; + do + { + // the cluster... + cluster = &clusters[nextCluster]; + + // render the cluster... + for (S32 i=cluster->startPrimitive; iendPrimitive; i++) + { + TSDrawPrimitive & draw = primitives[i]; + AssertFatal(draw.matIndex & TSDrawPrimitive::Indexed,"TSSortedMesh::render: rendering of non-indexed meshes no longer supported"); + + // material change? + if ( (TSShapeInstance::smRenderData.materialIndex ^ draw.matIndex) & (TSDrawPrimitive::MaterialMask|TSDrawPrimitive::NoMaterial)) + { + setMaterial(draw.matIndex,materials); + if (alwaysWriteDepth) + glDepthMask(GL_TRUE); + } + + S32 drawType = getDrawType(draw.matIndex>>30); + + glDrawElements(drawType,draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]); + } + + // determine next cluster... + if (cluster->frontCluster!=cluster->backCluster) + nextCluster = (mDot(cluster->normal,cameraCenter) > cluster->k) ? cluster->frontCluster : cluster->backCluster; + else + nextCluster = cluster->frontCluster; + } while (nextCluster>=0); + + // unlock... + if (gGLState.suppLockedArrays) + glUnlockArraysEXT(); + + if ((TSShapeInstance::smRenderData.materialFlags & TSMaterialList::Translucent) && alwaysWriteDepth) + glDepthMask(GL_FALSE); +} + +void TSSortedMesh::renderFog(S32 frame) +{ + if (getFlags(Billboard)) + { + if (getFlags(BillboardZAxis)) + forceFaceCameraZAxis(); + else + forceFaceCamera(); + } + + MatrixF toCam; + dglGetModelview(&toCam); + toCam.inverse(); + Point3F cameraCenter; + toCam.getColumn(3,&cameraCenter); + + S32 firstVert = firstVerts[frame]; + S32 vertCount = numVerts[frame]; + + // set up vertex arrays -- already enabled in TSShapeInstance::render + glVertexPointer(3,GL_FLOAT,0,&verts[firstVert]); + + // lock... + if (gGLState.suppLockedArrays) + glLockArraysEXT(0,vertCount); + + Cluster * cluster; + S32 nextCluster = startCluster[frame]; + do + { + // the cluster... + cluster = &clusters[nextCluster]; + + // render the cluster... + for (S32 i=cluster->startPrimitive; iendPrimitive; i++) + { + TSDrawPrimitive & draw = primitives[i]; + glDrawElements(getDrawType(draw.matIndex>>30),draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]); + } + + // determine next cluster... + if (cluster->frontCluster!=cluster->backCluster) + nextCluster = (mDot(cluster->normal,cameraCenter) > cluster->k) ? cluster->frontCluster : cluster->backCluster; + else + nextCluster = cluster->frontCluster; + } while (nextCluster>=0); + + // unlock... + if (gGLState.suppLockedArrays) + glUnlockArraysEXT(); +} + +//----------------------------------------------------- +// TSSortedMesh collision methods +//----------------------------------------------------- + +bool TSSortedMesh::buildPolyList(S32 frame, AbstractPolyList * polyList, U32 & surfaceKey) +{ + frame, polyList, surfaceKey; + return false; +} + +bool TSSortedMesh::castRay(S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo) +{ + frame, start, end, rayInfo; + return false; +} + +bool TSSortedMesh::buildConvexHull() +{ + return false; +} + +S32 TSSortedMesh::getNumPolys() +{ + S32 count = 0; + S32 cIdx = !clusters.size() ? -1 : 0; + while (cIdx>=0) + { + Cluster & cluster = clusters[cIdx]; + for (S32 i=cluster.startPrimitive; i clusters; + ToolVector startCluster; // indexed by frame number + ToolVector firstVerts; // indexed by frame number + ToolVector numVerts; // indexed by frame number + ToolVector firstTVerts; // indexed by frame number or matFrame number, depending on which one animates (never both) + + // sometimes, we want to write the depth value to the frame buffer even when object is translucent + bool alwaysWriteDepth; + + // render methods.. + void render(S32 frame, S32 matFrame, TSMaterialList *); + void renderFog(S32 frame); + + // collision methods... + bool buildPolyList(S32 frame, AbstractPolyList * polyList, U32 & surfaceKey); + bool castRay(S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo); + bool buildConvexHull(); // does nothing, skins don't use this + S32 getNumPolys(); + + void assemble(bool skip); + void disassemble(); + + TSSortedMesh() { + meshType = SortedMeshType; + } +}; + +#endif // _TS_SORTED_MESH + + diff --git a/ts/tsThread.cc b/ts/tsThread.cc new file mode 100644 index 0000000..5efa5f1 --- /dev/null +++ b/ts/tsThread.cc @@ -0,0 +1,799 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShapeInstance.h" + +//------------------------------------------------------------------------------------- +// This file contains the shape instance thread class (defined in tsShapeInstance.h) +// and the tsShapeInstance functions to interface with the thread class. +//------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------- +// Thread class +//------------------------------------------------------------------------------------- + +// given a position on the thread, choose correct keyframes +// slight difference between one-shot and cyclic sequences -- see comments below for details +void TSThread::selectKeyframes(F32 pos, const TSSequence * seq, S32 * k1, S32 * k2, F32 * kpos) +{ + const TSShape * shape = mShapeInstance->mShape; + S32 numKF = seq->numKeyframes; + F32 kf; + + if (seq->isCyclic()) + { + // cyclic sequence: + // pos=0 and pos=1 are equivalent, so we don't have a keyframe at pos=1 + // last keyframe corresponds to pos=n/(n-1) up to (not including) pos=1 + // (where n == num keyframes) + + AssertFatal(pos>=0.0f && pos<1.0f,"TSThread::selectKeyframes"); + + kf = pos * (F32) (numKF); + + // set keyPos + if (kpos) + *kpos = kf - (S32) kf; + + // make sure compiler doing what we want... + AssertFatal(*kpos>=0.0f && *kpos<1.0f,"TSThread::selectKeyframes"); + + S32 kfIdx1 = (S32) kf; + + // following assert could happen if pos1<1 && pos1==1...paradoxically... + AssertFatal(kfIdx1<=seq->numKeyframes,"TSThread::selectKeyframes"); + + S32 kfIdx2 = (kfIdx1==seq->numKeyframes-1) ? 0 : kfIdx1+1; + + if (k1) + *k1 = kfIdx1; + if (k2) + *k2 = kfIdx2; + } + else + { + // one-shot sequence: + // pos=0 and pos=1 are now different, so we have a keyframe at pos=1 + // last keyframe corresponds to pos=1 + // rest of the keyframes are equally spaced (so 1/(n-1) pos units long) + // (where n == num keyframes) + + AssertFatal(pos>=0.0f && pos<=1.0f,"TSThread::selectKeyframes"); + + if (pos==1.0f) + { + if (kpos) + *kpos = 0.0f; + if (k1) + *k1 = seq->numKeyframes-1; + if (k2) + *k2 = seq->numKeyframes-1; + } + else + { + kf = pos * (F32) (numKF-1); + + // set keyPos + if (kpos) + *kpos = kf - (S32) kf; + + S32 kfIdx1 = (S32) kf; + + // following assert could happen if pos1<1 && pos1==1...paradoxically... + AssertFatal(kfIdx1numKeyframes,"TSThread::advancePos"); + + S32 kfIdx2 = kfIdx1+1; + + if (k1) + *k1 = kfIdx1; + if (k2) + *k2 = kfIdx2; + } + } +} + +void TSThread::getGround(F32 t, MatrixF * pMat) +{ + static const QuatF unitRotation(0,0,0,1); + static const Point3F unitTranslation(0,0,0); + + const QuatF * q1, * q2; + QuatF rot1,rot2; + const Point3F * p1, * p2; + + // if N = sequence->numGroundFrames, then there are N+1 positions we + // interpolate betweeen: 0/N, 1/N ... N/N + // we need to convert the p passed to us into 2 ground keyframes: + + // the 0.99999f is in case 'p' is exactly 1.0f, which is legal but 'kf' + // needs to be strictly less than 'sequence->numGroundFrames' + F32 kf = 0.999999f * t * (F32) sequence->numGroundFrames; + + // get frame number and interp param (kpos) + S32 frame = (S32)kf; + F32 kpos = kf - (F32)frame; + + // now point pT1 and pT2 at transforms for keyframes 'frame' and 'frame+1' + + // following a little strange: first ground keyframe (0/N in comment above) is + // assumed to be ident. and not found in the list. + if (frame) + { + p1 = &mShapeInstance->mShape->groundTranslations[sequence->firstGroundFrame + frame - 1]; + q1 = &mShapeInstance->mShape->groundRotations[sequence->firstGroundFrame + frame - 1].getQuatF(&rot1); + } + else + { + p1 = &unitTranslation; + q1 = &unitRotation; + } + + // similar to above, ground keyframe number 'frame+1' is actually offset by 'frame' + p2 = &mShapeInstance->mShape->groundTranslations[sequence->firstGroundFrame + frame]; + q2 = &mShapeInstance->mShape->groundRotations[sequence->firstGroundFrame + frame].getQuatF(&rot2); + + QuatF q; + Point3F p; + TSTransform::interpolate(*q1,*q2,kpos,&q); + TSTransform::interpolate(*p1,*p2,kpos,&p); + TSTransform::setMatrix(q,p,pMat); +} + +void TSThread::setSequence(S32 seq, F32 toPos) +{ + const TSShape * shape = mShapeInstance->mShape; + AssertFatal(shape && shape->sequences.size()>seq && toPos>=0.0f && toPos<=1.0f,"TSThread::setSequence: invalid"); + + mShapeInstance->clearTransition(this); + + sequence = &shape->sequences[seq]; + priority = sequence->priority; + pos = toPos; + makePath = sequence->makePath(); + + // 1.0f doesn't exist on cyclic sequences + if (pos>0.9999f && sequence->isCyclic()) + pos = 0.9999f; + + // select keyframes + selectKeyframes(pos,sequence,&keyNum1,&keyNum2,&keyPos); +} + +void TSThread::transitionToSequence(S32 seq, F32 toPos, F32 duration, bool continuePlay) +{ + AssertFatal(duration>=0.0f,"TSThread::transitionToSequence: negative duration not allowed"); + + const TSShape * shape = mShapeInstance->mShape; + + // make sure these nodes are smoothly interpolated to new positions... + // basically, any node we controlled just prior to transition, or at any stage + // of the transition is interpolated. If we start to transtion from A to B, + // but before reaching B we transtion to C, we interpolate all nodes controlled + // by A, B, or C to their new position. + if (transitionData.inTransition) + { + transitionData.oldRotationNodes.overlap(sequence->rotationMatters); + transitionData.oldTranslationNodes.overlap(sequence->translationMatters); + transitionData.oldScaleNodes.overlap(sequence->scaleMatters); + } + else + { + transitionData.oldRotationNodes = sequence->rotationMatters; + transitionData.oldTranslationNodes = sequence->translationMatters; + transitionData.oldScaleNodes = sequence->scaleMatters; + } + + // set time characteristics of transition + transitionData.oldSequence = (S32)(sequence-&shape->sequences[0]); + transitionData.oldPos = pos; + transitionData.duration = duration; + transitionData.pos = 0.0f; + transitionData.direction = timeScale>0.0f ? 1.0f : -1.0f; + transitionData.targetScale = continuePlay ? 1.0f : 0.0f; + + // in transition... + transitionData.inTransition = true; + + // set target sequence data + sequence = &shape->sequences[seq]; + priority = sequence->priority; + pos = toPos; + makePath = sequence->makePath(); + + // 1.0f doesn't exist on cyclic sequences + if (pos>0.9999f && sequence->isCyclic()) + pos = 0.9999f; + + // select keyframes + selectKeyframes(pos,sequence,&keyNum1,&keyNum2,&keyPos); +} + +bool TSThread::isInTransition() +{ + return transitionData.inTransition; +} + +void TSThread::animateTriggers() +{ + if (!sequence->numTriggers) + return; + + switch (path.loop) + { + case -1 : + activateTriggers(path.start,0); + activateTriggers(1,path.end); + break; + case 0 : + activateTriggers(path.start,path.end); + break; + case 1 : + activateTriggers(path.start,1); + activateTriggers(0,path.end); + break; + default: + { + if (path.loop>0) + { + activateTriggers(path.end,1); + activateTriggers(0,path.end); + } + else + { + activateTriggers(path.end,0); + activateTriggers(1,path.end); + } + } + } +} + +void TSThread::activateTriggers(F32 a, F32 b) +{ + S32 i; + const TSShape * shape = mShapeInstance->mShape; + S32 firstTrigger = sequence->firstTrigger; + S32 numTriggers = sequence->numTriggers; + + // first find triggers at position a and b + // we assume there aren't many triggers, so + // search is linear + F32 lastPos = -1.0f; + S32 aIndex = numTriggers+firstTrigger; // initialized to handle case where pos past all triggers + S32 bIndex = numTriggers+firstTrigger; // initialized to handle case where pos past all triggers + for (i=firstTrigger; ilastPos && a<=shape->triggers[i].pos) + aIndex = i; + // is b between this trigger and previous one... + if (b>lastPos && b<=shape->triggers[i].pos) + bIndex = i; + lastPos = shape->triggers[i].pos; + } + + // activate triggers between aIndex and bIndex (depends on direction) + if (aIndex<=bIndex) + { + for (i=aIndex; itriggers[i].state; + bool on = state & TSShape::Trigger::StateOn; + mShapeInstance->setTriggerStateBit(state & TSShape::Trigger::StateMask, on); + } + } + else + { + for (i=aIndex-1; i>=bIndex; i--) + { + U32 state = shape->triggers[i].state; + bool on = state & TSShape::Trigger::StateOn; + if (state & TSShape::Trigger::InvertOnReverse) + on = !on; + mShapeInstance->setTriggerStateBit(state & TSShape::Trigger::StateMask, on); + } + } +} + +F32 TSThread::getPos() +{ + return transitionData.inTransition ? transitionData.pos : pos; +} + +F32 TSThread::getTime() +{ + return transitionData.inTransition ? transitionData.pos * transitionData.duration : pos * sequence->duration; +} + +F32 TSThread::getDuration() +{ + return transitionData.inTransition ? transitionData.duration : sequence->duration; +} + +F32 TSThread::getScaledDuration() +{ + return getDuration() / mFabs(timeScale); +} + +F32 TSThread::getTimeScale() +{ + return timeScale; +} + +void TSThread::setTimeScale(F32 ts) +{ + timeScale = ts; +} + +void TSThread::advancePos(F32 delta) +{ + if (mFabs(delta)>0.00001f) + { + // make dirty what this thread changes + U32 dirtyFlags = sequence->dirtyFlags | (transitionData.inTransition ? TSShapeInstance::TransformDirty : 0); + for (S32 i=0; igetShape()->subShapeFirstNode.size(); i++) + mShapeInstance->mDirtyFlags[i] |= dirtyFlags; + } + + if (transitionData.inTransition) + { + transitionData.pos += transitionData.direction * delta; + if (transitionData.pos<0 || transitionData.pos>=1.0f) + { + mShapeInstance->clearTransition(this); + if (transitionData.pos<0.0f) + // return to old sequence + mShapeInstance->setSequence(this,transitionData.oldSequence,transitionData.oldPos); + } + // re-adjust delta to be correct time-wise + delta *= transitionData.targetScale * transitionData.duration / sequence->duration; + } + + // even if we are in a transition, keep playing the sequence + + if (makePath) + { + path.start = pos; + pos += delta; + if (!sequence->isCyclic()) + { + pos = mClampF(pos , 0.0f, 1.0f); + path.loop = 0; + } + else + { + path.loop = (S32)pos; + if (pos < 0.0f) + path.loop--; + pos -= path.loop; + // following necessary because of floating point roundoff errors + if (pos < 0.0f) pos += 1.0f; + if (pos >= 1.0f) pos -= 1.0f; + } + path.end = pos; + + animateTriggers(); // do this automatically...no need for user to call it + + AssertFatal(pos>=0.0f && pos<=1.0f,"TSThread::advancePos (1)"); + AssertFatal(!sequence->isCyclic() || pos<1.0f,"TSThread::advancePos (2)"); + } + else + { + pos += delta; + if (!sequence->isCyclic()) + pos = mClampF(pos, 0.0f, 1.0f); + else + { + pos -= S32(pos); + // following necessary because of floating point roundoff errors + if (pos < 0.0f) pos += 1.0f; + if (pos >= 1.0f) pos -= 1.0f; + } + AssertFatal(pos>=0.0f && pos<=1.0f,"TSThread::advancePos (3)"); + AssertFatal(!sequence->isCyclic() || pos<1.0f,"TSThread::advancePos (4)"); + } + + // select keyframes + selectKeyframes(pos,sequence,&keyNum1,&keyNum2,&keyPos); +} + +void TSThread::advanceTime(F32 delta) +{ + advancePos(timeScale * delta / getDuration()); +} + +void TSThread::setPos(F32 pos) +{ + advancePos(pos-getPos()); +} + +void TSThread::setTime(F32 time) +{ + setPos(timeScale * time/getDuration()); +} + +S32 TSThread::getKeyframeCount() +{ + AssertFatal(!transitionData.inTransition,"TSThread::getKeyframeCount: not while in transition"); + + return sequence->numKeyframes + 1; +} + +S32 TSThread::getKeyframeNumber() +{ + const TSShape * shape = mShapeInstance->mShape; + + AssertFatal(!transitionData.inTransition,"TSThread::getKeyframeNumber: not while in transition"); + + return keyNum1; +} + +void TSThread::setKeyframeNumber(S32 kf) +{ + const TSShape * shape = mShapeInstance->mShape; + + AssertFatal(kf>=0 && kf<= sequence->numKeyframes, + "TSThread::setKeyframeNumber"); + AssertFatal(!transitionData.inTransition,"TSThread::setKeyframeNumber: not while in transition"); + + keyNum1 = keyNum2 = kf; + keyPos = 0; + pos = 0; +} + +TSThread::TSThread(TSShapeInstance * _shapeInst) +{ + timeScale = 1.0f; + mShapeInstance = _shapeInst; + transitionData.inTransition = false; + blendDisabled = false; + setSequence(0,0.0f); +} + +S32 TSThread::operator<(const TSThread & th2) const +{ + if (sequence->isBlend() == th2.sequence->isBlend()) + { + // both blend or neither blend, sort based on priority only -- higher priority first + S32 ret = 0; // do it this way to (hopefully) take advantage of 'conditional move' assembly instruction + if (priority > th2.priority) + ret = -1; + if (th2.priority > priority) + ret = 1; + return ret; + } + else + { + // one is blend, the other is not...sort based on blend -- non-blended first + AssertFatal(!sequence->isBlend() || !th2.sequence->isBlend(),"compareThreads: unequal 'trues'"); + S32 ret = -1; // do it this way to (hopefully) take advantage of 'conditional move' assembly instruction + if (sequence->isBlend()) + ret = 1; + return ret; + } +} + + +//------------------------------------------------------------------------------------- +// TSShapeInstance Thread Interface -- more implemented in header file +//------------------------------------------------------------------------------------- + +TSThread * TSShapeInstance::addThread() +{ + if (mShape->sequences.empty()) + return NULL; + + mThreadList.increment(); + mThreadList.last() = new TSThread(this); + setDirty(AllDirtyMask); + return mThreadList.last(); +} + +TSThread * TSShapeInstance::getThread(S32 threadNumber) +{ + AssertFatal(threadNumber < mThreadList.size() && threadNumber>=0,"TSShapeInstance::getThread"); + return mThreadList[threadNumber]; +} + +void TSShapeInstance::destroyThread(TSThread * thread) +{ + if (!thread) + return; + + clearTransition(thread); + + S32 i; + for (i=0; itransitionData.inTransition && mTransitionThreads.size()>1) || mTransitionThreads.size()>0) + // if we have transitions, make sure transforms are up to date... + animateNodeSubtrees(); + + thread->setSequence(seq,pos); + setDirty(AllDirtyMask); + mGroundThread = NULL; + + if (mScaleCurrentlyAnimated && !thread->sequence->animatesScale()) + checkScaleCurrentlyAnimated(); + else if (!mScaleCurrentlyAnimated && thread->sequence->animatesScale()) + mScaleCurrentlyAnimated=true; + + updateTransitions(); +} + +S32 TSShapeInstance::getSequence(TSThread * thread) +{ + AssertFatal( (S32)(thread->sequence - mShape->sequences.address())>=0, + "TSShapeInstance::getSequence: range error A"); + AssertFatal( (S32)(thread->sequence - mShape->sequences.address())sequences.size(), + "TSShapeInstance::getSequence: range error B"); + return (S32) (thread->sequence - mShape->sequences.address()); +} + +void TSShapeInstance::transitionToSequence(TSThread * thread, S32 seq, F32 pos, F32 duration, bool continuePlay) +{ + // make sure all transforms on all detail levels are accurate + animateNodeSubtrees(); + + thread->transitionToSequence(seq,pos,duration,continuePlay); + setDirty(AllDirtyMask); + mGroundThread = NULL; + + if (mScaleCurrentlyAnimated && !thread->sequence->animatesScale()) + checkScaleCurrentlyAnimated(); + else if (!mScaleCurrentlyAnimated && thread->sequence->animatesScale()) + mScaleCurrentlyAnimated=true; + + mTransitionRotationNodes.overlap(thread->transitionData.oldRotationNodes); + mTransitionRotationNodes.overlap(thread->sequence->rotationMatters); + + mTransitionTranslationNodes.overlap(thread->transitionData.oldTranslationNodes); + mTransitionTranslationNodes.overlap(thread->sequence->translationMatters); + + mTransitionScaleNodes.overlap(thread->transitionData.oldScaleNodes); + mTransitionScaleNodes.overlap(thread->sequence->scaleMatters); + + // if we aren't already in the list of transition threads, add us now + S32 i; + for (i=0; itransitionData.inTransition) + return; + + // if other transitions are still playing, + // make sure transforms are up to date + if (mTransitionThreads.size()>1) + animateNodeSubtrees(); + + // turn off transition... + thread->transitionData.inTransition = false; + + // remove us from transition list + S32 i; + if (mTransitionThreads.size() != 0) { + for (i=0; itransitionData.oldRotationNodes); + mTransitionRotationNodes.overlap(mTransitionThreads[i]->sequence->rotationMatters); + + mTransitionTranslationNodes.overlap(mTransitionThreads[i]->transitionData.oldTranslationNodes); + mTransitionTranslationNodes.overlap(mTransitionThreads[i]->sequence->translationMatters); + + mTransitionScaleNodes.overlap(mTransitionThreads[i]->transitionData.oldScaleNodes); + mTransitionScaleNodes.overlap(mTransitionThreads[i]->sequence->scaleMatters); + } + + setDirty(ThreadDirty); + + updateTransitions(); +} + +void TSShapeInstance::updateTransitions() +{ + if (mTransitionThreads.empty()) + return; + + S32 i; + mNodeReferenceRotations.setSize(mShape->nodes.size()); + mNodeReferenceTranslations.setSize(mShape->nodes.size()); + for (i=0; inodes.size(); i++) + { + if (mTransitionRotationNodes.test(i)) + mNodeReferenceRotations[i].set(smNodeCurrentRotations[i]); + if (mTransitionTranslationNodes.test(i)) + mNodeReferenceTranslations[i] = smNodeCurrentTranslations[i]; + } + + if (animatesScale()) + { + if (animatesUniformScale()) + { + mNodeReferenceUniformScales.setSize(mShape->nodes.size()); + for (i=0; inodes.size(); i++) + { + if (mTransitionScaleNodes.test(i)) + mNodeReferenceUniformScales[i] = smNodeCurrentUniformScales[i]; + } + } + else if (animatesAlignedScale()) + { + mNodeReferenceScaleFactors.setSize(mShape->nodes.size()); + for (i=0; inodes.size(); i++) + { + if (mTransitionScaleNodes.test(i)) + mNodeReferenceScaleFactors[i] = smNodeCurrentAlignedScales[i]; + } + } + else + { + mNodeReferenceScaleFactors.setSize(mShape->nodes.size()); + mNodeReferenceArbitraryScaleRots.setSize(mShape->nodes.size()); + for (i=0; inodes.size(); i++) + { + if (mTransitionScaleNodes.test(i)) + { + mNodeReferenceScaleFactors[i] = smNodeCurrentArbitraryScales[i].mScale; + mNodeReferenceArbitraryScaleRots[i].set(smNodeCurrentArbitraryScales[i].mRotate); + } + } + } + } + + // reset transition durations to account for new reference transforms + for (i=0; itransitionData.inTransition) + { + th->transitionData.duration *= 1.0f - th->transitionData.pos; + th->transitionData.pos = 0.0f; + } + } +} + +void TSShapeInstance::checkScaleCurrentlyAnimated() +{ + mScaleCurrentlyAnimated=true; + for (S32 i=0; isequence->animatesScale()) + return; + mScaleCurrentlyAnimated=false; +} + +void TSShapeInstance::setBlendEnabled(TSThread * thread, bool blendOn) +{ + thread->blendDisabled = !blendOn; +} + +bool TSShapeInstance::getBlendEnabled(TSThread * thread) +{ + return !thread->blendDisabled; +} + +F32 TSShapeInstance::getTime(TSThread * thread) +{ + return thread->getTime(); +} + +F32 TSShapeInstance::getPos(TSThread * thread) +{ + return thread->getPos(); +} + +void TSShapeInstance::setTime(TSThread * thread, F32 time) +{ + thread->setTime(time); +} + +void TSShapeInstance::setPos(TSThread * thread, F32 pos) +{ + thread->setPos(pos); +} + +bool TSShapeInstance::isInTransition(TSThread * thread) +{ + return thread->isInTransition(); +} + +F32 TSShapeInstance::getTimeScale(TSThread * thread) +{ + return thread->getTimeScale(); +} + +void TSShapeInstance::setTimeScale(TSThread * thread, F32 timeScale) +{ + thread->setTimeScale(timeScale); +} + +F32 TSShapeInstance::getDuration(TSThread * thread) +{ + return thread->getDuration(); +} + +F32 TSShapeInstance::getScaledDuration(TSThread * thread) +{ + return thread->getScaledDuration(); +} + +S32 TSShapeInstance::getKeyframeCount(TSThread * thread) +{ + return thread->getKeyframeCount(); +} + +S32 TSShapeInstance::getKeyframeNumber(TSThread * thread) +{ + return thread->getKeyframeNumber(); +} + +void TSShapeInstance::setKeyframeNumber(TSThread * thread, S32 kf) +{ + thread->setKeyframeNumber(kf); +} + + +// advance time on a particular thread +void TSShapeInstance::advanceTime(F32 delta, TSThread * thread) +{ + thread->advanceTime(delta); +} + +// advance time on all threads +void TSShapeInstance::advanceTime(F32 delta) +{ + for (S32 i=0; iadvanceTime(delta); +} + +// advance pos on a particular thread +void TSShapeInstance::advancePos(F32 delta, TSThread * thread) +{ + thread->advancePos(delta); +} + +// advance pos on all threads +void TSShapeInstance::advancePos(F32 delta) +{ + for (S32 i=0; iadvancePos(delta); +} + diff --git a/ts/tsTransform.cc b/ts/tsTransform.cc new file mode 100644 index 0000000..601af77 --- /dev/null +++ b/ts/tsTransform.cc @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsTransform.h" +#include "Core/stream.h" + +void Quat16::identity() +{ + x = y = z = 0; + w = MAX_VAL; +} + +QuatF & Quat16::getQuatF( QuatF * q ) const +{ + q->x = float( x ) / float(MAX_VAL); + q->y = float( y ) / float(MAX_VAL); + q->z = float( z ) / float(MAX_VAL); + q->w = float( w ) / float(MAX_VAL); + return *q; +} + +void Quat16::set( const QuatF & q ) +{ + x = (S16)(q.x * float(MAX_VAL)); + y = (S16)(q.y * float(MAX_VAL)); + z = (S16)(q.z * float(MAX_VAL)); + w = (S16)(q.w * float(MAX_VAL)); +} + +S32 Quat16::operator==( const Quat16 & q ) const +{ + return( x == q.x && y == q.y && z == q.z && w == q.w ); +} + +void Quat16::read(Stream * s) +{ + s->read(&x); + s->read(&y); + s->read(&z); + s->read(&w); +} + +void Quat16::write(Stream * s) +{ + s->write(x); + s->write(y); + s->write(z); + s->write(w); +} + +QuatF & TSTransform::interpolate( const QuatF & q1, const QuatF & q2, F32 interp, QuatF * q ) +{ + F32 Dot; + F32 Dist2; + F32 OneOverL; + F32 x1,y1,z1,w1; + F32 x2,y2,z2,w2; + + // + // This is a linear interpolation with a fast renormalization. + // + x1 = q1.x; + y1 = q1.y; + z1 = q1.z; + w1 = q1.w; + x2 = q2.x; + y2 = q2.y; + z2 = q2.z; + w2 = q2.w; + + // Determine if quats are further than 90 degrees + Dot = x1*x2 + y1*y2 + z1*z2 + w1*w2; + + // If dot is negative flip one of the quaterions + if( Dot < 0.0f ) + { + x1 = -x1; + y1 = -y1; + z1 = -z1; + w1 = -w1; + } + + // Compute interpolated values + x1 = x1 + interp*(x2 - x1); + y1 = y1 + interp*(y2 - y1); + z1 = z1 + interp*(z2 - z1); + w1 = w1 + interp*(w2 - w1); + + // Get squared distance of new quaternion + Dist2 = x1*x1 + y1*y1 + z1*z1 + w1*w1; + + // Use home-baked polynomial to compute 1/sqrt(Dist2) + // since we know the range is 0.707 >= Dist2 <= 1.0 + // we'll split in half. + + if( Dist2<0.857f ) + OneOverL = (((0.699368f)*Dist2) + -1.819985f)*Dist2 + 2.126369f; //0.0000792 + else + OneOverL = (((0.454012f)*Dist2) + -1.403517f)*Dist2 + 1.949542f; //0.0000373 + + // Renormalize + q->x = x1*OneOverL; + q->y = y1*OneOverL; + q->z = z1*OneOverL; + q->w = w1*OneOverL; + + return *q; +} + +void TSTransform::applyScale(F32 scale, MatrixF * mat) +{ + mat->scale(Point3F(scale,scale,scale)); +} + +void TSTransform::applyScale(const Point3F & scale, MatrixF * mat) +{ + mat->scale(scale); +} + +void TSTransform::applyScale(const TSScale & scale, MatrixF * mat) +{ + MatrixF mat2; + TSTransform::setMatrix(scale.mRotate,&mat2); + MatrixF mat3(mat2); + mat3.inverse(); + mat2.scale(scale.mScale); + mat2.mul(mat3); + mat->mul(mat2); +} diff --git a/ts/tsTransform.h b/ts/tsTransform.h new file mode 100644 index 0000000..2508c3b --- /dev/null +++ b/ts/tsTransform.h @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------------- +// V12 Engine +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSTRANSFORM_H_ +#define _TSTRANSFORM_H_ + +#ifndef _MMATH_H_ +#include "Math/mMath.h" +#endif + +class Stream; + +// compressed quat class +struct Quat16 +{ + enum { MAX_VAL = 0x7fff }; + + S16 x, y, z, w; + + void read(Stream *); + void write(Stream *); + + void identity(); + QuatF &getQuatF( QuatF * q ) const; + void set( const QuatF & q ); + S32 operator==( const Quat16 & q ) const; +}; + +// class to handle general scaling case +// transform = rot * scale * inv(rot) +struct TSScale +{ + QuatF mRotate; + Point3F mScale; + + void identity() { mRotate.identity(); mScale.set( 1.0f,1.0f,1.0f ); } + S32 operator==( const TSScale & other ) const { return mRotate==other.mRotate && mScale==other.mScale; } +}; + +// struct for encapsulating ts transform related static functions +struct TSTransform +{ + static Point3F & interpolate(const Point3F & p1, const Point3F & p2, F32 t, Point3F *); + static F32 interpolate(F32 p1, F32 p2, F32 t); + static QuatF & interpolate(const QuatF & q1, const QuatF & q2, F32 t, QuatF *); + static TSScale & interpolate(const TSScale & s1, const TSScale & s2, F32 t, TSScale *); + + static void setMatrix(const QuatF & q, MatrixF *); + static void setMatrix(const QuatF & q, const Point3F & p, MatrixF *); + + static void applyScale(F32 scale, MatrixF *); + static void applyScale(const Point3F & scale, MatrixF *); + static void applyScale(const TSScale & scale, MatrixF *); +}; // TSTransform + +inline Point3F & TSTransform::interpolate(const Point3F & p1, const Point3F & p2, F32 t, Point3F * p) +{ + p->x = p1.x + t * (p2.x-p1.x); + p->y = p1.y + t * (p2.y-p1.y); + p->z = p1.z + t * (p2.z-p1.z); + return *p; +} + +inline F32 TSTransform::interpolate(F32 p1, F32 p2, F32 t) +{ + return p1 + t*(p2-p1); +} + +inline TSScale & TSTransform::interpolate(const TSScale & s1, const TSScale & s2, F32 t, TSScale * s) +{ + TSTransform::interpolate(s1.mRotate,s2.mRotate,t,&s->mRotate); + TSTransform::interpolate(s1.mScale,s2.mScale,t,&s->mScale); + return *s; +} + +inline void TSTransform::setMatrix( const QuatF & q, const Point3F & p, MatrixF * pDest ) +{ + q.setMatrix(pDest); + pDest->setColumn(3,p); +} + +inline void TSTransform::setMatrix( const QuatF & q, MatrixF * pDest ) +{ + q.setMatrix(pDest); +} + +#endif diff --git a/v12 Engine Lib.dsp b/v12 Engine Lib.dsp new file mode 100644 index 0000000..a955c3c --- /dev/null +++ b/v12 Engine Lib.dsp @@ -0,0 +1,3596 @@ +# Microsoft Developer Studio Project File - Name="v12 Engine Lib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=v12 Engine Lib - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "v12 Engine Lib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "v12 Engine Lib.mak" CFG="v12 Engine Lib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "v12 Engine Lib - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "v12 Engine Lib - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "out.VC6.RELEASE" +# PROP Intermediate_Dir "out.VC6.RELEASE" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /GR /GX /O2 /I "../lib/directx" /I "../lib/zlib" /I "../lib/lungif" /I "../lib/lpng" /I "../lib/ljpeg" /I "../lib/openal/win32" /I "." /D "WIN32" /D "USEASSEMBLYTERRBLEND" /D "PNG_NO_READ_TIME" /D "PNG_NO_WRITE_TIME" /D OPENGL2D3D=\"glFOO.dll\" /D GLU2D3D=\"gluFOO.dll\" /D "NO_MILES_OPENAL" /YX /FD /c /Tp +# SUBTRACT CPP /Z +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"out.VC6.RELEASE\engine_RELEASE.lib" /NODEFAULTLIB:LIBCD + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "out.VC6.DEBUG" +# PROP Intermediate_Dir "out.VC6.DEBUG" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /Gm /GR /GX /ZI /Od /I "../lib/directx" /I "../lib/zlib" /I "../lib/lungif" /I "../lib/lpng" /I "../lib/ljpeg" /I "../lib/openal/win32" /I "." /D "DEBUG" /D "V12_DEBUG" /D "ENABLE_ASSERTS" /D "WIN32" /D "USEASSEMBLYTERRBLEND" /D "PNG_NO_READ_TIME" /D "PNG_NO_WRITE_TIME" /D "NO_MILES_OPENAL" /D OPENGL2D3D=\"glFOO.dll\" /D GLU2D3D=\"gluFOO.dll\" /YX /FD /GZ /c /Tp +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"out.VC6.DEBUG\engine_DEBUG.lib" /NODEFAULTLIB:LIBCD + +!ENDIF + +# Begin Target + +# Name "v12 Engine Lib - Win32 Release" +# Name "v12 Engine Lib - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Group "ai" + +# PROP Default_Filter "cc" +# End Group +# Begin Group "audio" + +# PROP Default_Filter "cc" +# End Group +# Begin Group "collision" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\collision\abstractPolyList.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\boxConvex.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\clippedPolyList.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\convex.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\depthSortList.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\earlyOutPolyList.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\extrudedPolyList.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\gjk.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\planeExtractor.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\polyhedron.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\polytope.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# End Group +# Begin Group "console" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\console\compiledEval.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\compiler.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\console.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\consoleFunctions.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\consoleInternal.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\consoleObject.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\consoleTypes.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\gram.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\scan.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\scriptObject.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\simBase.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\simDictionary.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\simManager.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\telnetConsole.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\telnetDebugger.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# End Group +# Begin Group "core" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\core\bitRender.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\bitStream.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\BitTables.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\dataChunker.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\dnet.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\fileObject.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\fileStream.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\filterStream.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\findMatch.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\idGenerator.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\memStream.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\nStream.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\nTypes.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\resDictionary.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\resizeStream.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\resManager.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\stringTable.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\tagDictionary.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\tVector.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\zipAggregate.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\zipHeaders.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\zipSubStream.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# End Group +# Begin Group "crypt" + +# PROP Default_Filter "cc" +# End Group +# Begin Group "dgl" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\dgl\bitmapBM8.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapBMP.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapGIF.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapJpeg.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapPng.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\dgl.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\dglMatrix.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\gBitmap.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\gFont.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\gPalette.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\gTexManager.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\lensFlare.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialList.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialPropertyMap.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\rectClipper.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\splineUtil.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\stripCache.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# End Group +# Begin Group "editor" + +# PROP Default_Filter "cc" +# End Group +# Begin Group "game" + +# PROP Default_Filter "cc" +# End Group +# Begin Group "gui" + +# PROP Default_Filter "cc" +# End Group +# Begin Group "hud" + +# PROP Default_Filter "cc" +# End Group +# Begin Group "interior" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\interior\FloorPlanRes.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\forceField.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interior.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorCollision.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorDebug.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorInstance.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorIO.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorLightAnim.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorLMManager.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorRender.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorRes.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorResObjects.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorSubObject.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\itfdump.asm + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" +# Begin Custom Build +IntDir=.\out.VC6.RELEASE/interior +InputPath=.\interior\itfdump.asm +InputName=itfdump + +"$(IntDir)/$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + nasmw.exe -f win32 $(InputPath) -o $(IntDir)/$(InputName).obj + +# End Custom Build + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" +# Begin Custom Build +IntDir=.\out.VC6.DEBUG/interior +InputPath=.\interior\itfdump.asm +InputName=itfdump + +"$(IntDir)/$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + nasmw.exe -f win32 $(InputPath) -o $(IntDir)/$(InputName).obj + +# End Custom Build + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\lightUpdateGrouper.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\mirrorSubObject.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# End Group +# Begin Group "math" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\math\mathTypes.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mathUtils.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mBox.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mConsoleFunctions.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMath_C.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMathAMD.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMathFn.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMathSSE.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMatrix.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mPlaneTransformer.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mQuadPatch.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mQuat.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mRandom.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mSolver.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mSplinePatch.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# End Group +# Begin Group "platform" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\platform\gameInterface.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\platformAssert.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\platformMemory.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\platformRedBook.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\platformVideo.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\profiler.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# End Group +# Begin Group "platformWIN32" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\platformWIN32\winAsmBlit.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winConsole.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winCPUInfo.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winD3DVideo.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDInputDevice.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDirectInput.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winFileio.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winFont.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winGL.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winInput.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMath.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMath_ASM.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMemory.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMutex.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winNet.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winOGLVideo.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winOpenAL.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winProcessControl.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winRedbook.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winSemaphore.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winStrings.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winThread.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winTime.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winV2Video.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winWindow.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# End Group +# Begin Group "sceneGraph" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\scenegraph\detailManager.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\lightManager.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneGraph.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneLighting.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneRoot.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneState.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneTraversal.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sgUtil.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\shadowVolumeBSP.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\windingClipper.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# End Group +# Begin Group "shell" + +# PROP Default_Filter "cc" +# End Group +# Begin Group "sim" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\sim\actionMap.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\cannedChatDataBlock.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\decalManager.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\frameAllocator.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netConnection.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netEvent.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netGhost.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netObject.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netStringTable.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\pathManager.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\sceneObject.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\simPath.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# End Group +# Begin Group "terrain" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\terrain\blender.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\bvQuadTree.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\FluidQuadTree.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\FluidRender.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\FluidSupport.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sky.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sun.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrCollision.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrData.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrLighting.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrRender.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrRender2.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\waterBlock.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# End Group +# Begin Group "ts" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\ts\tsAnimate.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsCollision.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsDecal.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsDump.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsIntegerSet.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsLastDetail.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsMaterialList.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsMesh.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsPartInstance.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShape.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeAlloc.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeConstruct.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeInstance.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeOldRead.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsSortedMesh.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsThread.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsTransform.cc + +!IF "$(CFG)" == "v12 Engine Lib - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine Lib - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# End Group +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Group "ai headers" + +# PROP Default_Filter "h" +# End Group +# Begin Group "audio headers" + +# PROP Default_Filter "h" +# End Group +# Begin Group "collision headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\collision\abstractPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\boxConvex.h +# End Source File +# Begin Source File + +SOURCE=.\collision\clippedPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\collision.h +# End Source File +# Begin Source File + +SOURCE=.\collision\convex.h +# End Source File +# Begin Source File + +SOURCE=.\collision\depthSortList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\earlyOutPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\extrudedPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\gjk.h +# End Source File +# Begin Source File + +SOURCE=.\collision\planeExtractor.h +# End Source File +# Begin Source File + +SOURCE=.\collision\polyhedron.h +# End Source File +# Begin Source File + +SOURCE=.\collision\polytope.h +# End Source File +# End Group +# Begin Group "console headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\console\ast.h +# End Source File +# Begin Source File + +SOURCE=.\console\compiler.h +# End Source File +# Begin Source File + +SOURCE=.\console\console.h +# End Source File +# Begin Source File + +SOURCE=.\console\consoleInternal.h +# End Source File +# Begin Source File + +SOURCE=.\console\consoleObject.h +# End Source File +# Begin Source File + +SOURCE=.\console\consoleTypes.h +# End Source File +# Begin Source File + +SOURCE=.\console\gram.h +# End Source File +# Begin Source File + +SOURCE=.\console\objectTypes.h +# End Source File +# Begin Source File + +SOURCE=.\console\simBase.h +# End Source File +# Begin Source File + +SOURCE=.\console\simDictionary.h +# End Source File +# Begin Source File + +SOURCE=.\console\telnetConsole.h +# End Source File +# Begin Source File + +SOURCE=.\console\telnetDebugger.h +# End Source File +# End Group +# Begin Group "core headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\core\bitMatrix.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitRender.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitSet.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\BitTables.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitVector.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitVectorW.h +# End Source File +# Begin Source File + +SOURCE=.\core\color.h +# End Source File +# Begin Source File + +SOURCE=.\core\coreRes.h +# End Source File +# Begin Source File + +SOURCE=.\core\dataChunker.h +# End Source File +# Begin Source File + +SOURCE=.\core\dnet.h +# End Source File +# Begin Source File + +SOURCE=.\core\fileio.h +# End Source File +# Begin Source File + +SOURCE=.\core\fileObject.h +# End Source File +# Begin Source File + +SOURCE=.\core\fileStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\filterStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\findMatch.h +# End Source File +# Begin Source File + +SOURCE=.\core\idGenerator.h +# End Source File +# Begin Source File + +SOURCE=.\core\llist.h +# End Source File +# Begin Source File + +SOURCE=.\core\memstream.h +# End Source File +# Begin Source File + +SOURCE=.\core\polyList.h +# End Source File +# Begin Source File + +SOURCE=.\core\realComp.h +# End Source File +# Begin Source File + +SOURCE=.\core\resizeStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\resManager.h +# End Source File +# Begin Source File + +SOURCE=.\core\stream.h +# End Source File +# Begin Source File + +SOURCE=.\core\stringTable.h +# End Source File +# Begin Source File + +SOURCE=.\core\tagDictionary.h +# End Source File +# Begin Source File + +SOURCE=.\core\tAlgorithm.h +# End Source File +# Begin Source File + +SOURCE=.\core\tSparseArray.h +# End Source File +# Begin Source File + +SOURCE=.\core\tVector.h +# End Source File +# Begin Source File + +SOURCE=.\core\zipAggregate.h +# End Source File +# Begin Source File + +SOURCE=.\core\zipHeaders.h +# End Source File +# Begin Source File + +SOURCE=.\core\zipSubStream.h +# End Source File +# End Group +# Begin Group "crypt headers" + +# PROP Default_Filter "h" +# End Group +# Begin Group "dgl headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\dgl\dgl.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gBitmap.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gChunkedTexManager.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gFont.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gPalette.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gTexManager.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\lensFlare.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialList.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialPropertyMap.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\rectClipper.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\splineUtil.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\stripCache.h +# End Source File +# End Group +# Begin Group "editor headers" + +# PROP Default_Filter "h" +# End Group +# Begin Group "game headers" + +# PROP Default_Filter "h" +# End Group +# Begin Group "gui headers" + +# PROP Default_Filter "h" +# End Group +# Begin Group "hud headers" + +# PROP Default_Filter "h" +# End Group +# Begin Group "interior headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\interior\FloorPlanRes.h +# End Source File +# Begin Source File + +SOURCE=.\interior\forceField.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interior.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorInstance.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorLMManager.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorRes.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorResObjects.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorSubObject.h +# End Source File +# Begin Source File + +SOURCE=.\interior\itf.h +# End Source File +# Begin Source File + +SOURCE=.\interior\lightUpdateGrouper.h +# End Source File +# Begin Source File + +SOURCE=.\interior\mirrorSubObject.h +# End Source File +# End Group +# Begin Group "math headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\math\mathIO.h +# End Source File +# Begin Source File + +SOURCE=.\math\mathTypes.h +# End Source File +# Begin Source File + +SOURCE=.\math\mathUtils.h +# End Source File +# Begin Source File + +SOURCE=.\math\mBox.h +# End Source File +# Begin Source File + +SOURCE=.\math\mConstants.h +# End Source File +# Begin Source File + +SOURCE=.\math\mMath.h +# End Source File +# Begin Source File + +SOURCE=.\math\mMathFn.h +# End Source File +# Begin Source File + +SOURCE=.\math\mMatrix.h +# End Source File +# Begin Source File + +SOURCE=.\math\mPlane.h +# End Source File +# Begin Source File + +SOURCE=.\math\mPlaneTransformer.h +# End Source File +# Begin Source File + +SOURCE=.\math\mPoint.h +# End Source File +# Begin Source File + +SOURCE=.\math\mQuadPatch.h +# End Source File +# Begin Source File + +SOURCE=.\math\mQuat.h +# End Source File +# Begin Source File + +SOURCE=.\math\mRandom.h +# End Source File +# Begin Source File + +SOURCE=.\math\mRect.h +# End Source File +# Begin Source File + +SOURCE=.\math\mSphere.h +# End Source File +# Begin Source File + +SOURCE=.\math\mSplinePatch.h +# End Source File +# Begin Source File + +SOURCE=.\math\mTrig.h +# End Source File +# End Group +# Begin Group "platform headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\platform\3DFX.H +# End Source File +# Begin Source File + +SOURCE=.\platform\event.h +# End Source File +# Begin Source File + +SOURCE=.\platform\gameInterface.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platform.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformAssert.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformAudio.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformInput.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformMutex.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformRedBook.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformSemaphore.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformThread.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformVideo.h +# End Source File +# Begin Source File + +SOURCE=.\platform\profiler.h +# End Source File +# Begin Source File + +SOURCE=.\platform\types.h +# End Source File +# Begin Source File + +SOURCE=.\platform\typesWin32.h +# End Source File +# End Group +# Begin Group "platformWIN32 headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\platformWIN32\gllist.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\platformAL.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\platformGL.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\platformWin32.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winConsole.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winD3DVideo.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDInputDevice.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDirectInput.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winOGLVideo.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winV2Video.h +# End Source File +# End Group +# Begin Group "sceneGraph headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\scenegraph\detailManager.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\lightManager.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneGraph.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneLighting.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneRoot.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneState.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sgUtil.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\shadowVolumeBSP.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\windingClipper.h +# End Source File +# End Group +# Begin Group "shell headers" + +# PROP Default_Filter "h" +# End Group +# Begin Group "sim headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\sim\actionMap.h +# End Source File +# Begin Source File + +SOURCE=.\sim\cannedChatDataBlock.h +# End Source File +# Begin Source File + +SOURCE=.\sim\decalManager.h +# End Source File +# Begin Source File + +SOURCE=.\sim\frameAllocator.h +# End Source File +# Begin Source File + +SOURCE=.\sim\netConnection.h +# End Source File +# Begin Source File + +SOURCE=.\sim\netObject.h +# End Source File +# Begin Source File + +SOURCE=.\sim\netStringTable.h +# End Source File +# Begin Source File + +SOURCE=.\sim\pathManager.h +# End Source File +# Begin Source File + +SOURCE=.\sim\sceneObject.h +# End Source File +# Begin Source File + +SOURCE=.\sim\simPath.h +# End Source File +# End Group +# Begin Group "terrain headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\terrain\blender.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\bvQuadTree.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\Fluid.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sky.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sun.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrData.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrRender.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\waterBlock.h +# End Source File +# End Group +# Begin Group "ts headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\ts\tsDecal.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsIntegerSet.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsLastDetail.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsMesh.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsPartInstance.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShape.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeAlloc.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeConstruct.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeInstance.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsSortedMesh.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsTransform.h +# End Source File +# End Group +# End Group +# End Target +# End Project diff --git a/v12 Engine.dsp b/v12 Engine.dsp new file mode 100644 index 0000000..d0da01f --- /dev/null +++ b/v12 Engine.dsp @@ -0,0 +1,7336 @@ +# Microsoft Developer Studio Project File - Name="v12 Engine" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=v12 Engine - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "v12 Engine.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "v12 Engine.mak" CFG="v12 Engine - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "v12 Engine - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "v12 Engine - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "out.VC6.RELEASE" +# PROP Intermediate_Dir "out.VC6.RELEASE" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /GR /GX /O2 /I "../lib/directx" /I "../lib/zlib" /I "../lib/lungif" /I "../lib/lpng" /I "../lib/ljpeg" /I "../lib/mss" /I "../lib/openal/win32" /I "." /D "WIN32" /D "USEASSEMBLYTERRBLEND" /D "PNG_NO_READ_TIME" /D "PNG_NO_WRITE_TIME" /D OPENGL2D3D=\"glFOO.dll\" /D GLU2D3D=\"gluFOO.dll\" /D "NO_MILES_OPENAL" /YX /FD /c /Tp +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 COMCTL32.LIB COMDLG32.LIB USER32.LIB ADVAPI32.LIB GDI32.LIB WINMM.LIB WSOCK32.LIB ljpeg.lib lpng.lib lungif.lib zlib.lib Mss32.lib vfw32.lib /nologo /subsystem:console /machine:I386 /out:"out.VC6.RELEASE/v12_RELEASE.exe" /libpath:"../lib/out.VC6.RELEASE" /libpath:"../lib/mss" +# Begin Special Build Tool +SOURCE="$(InputPath)" +PostBuild_Desc=Copying v12_RELEASE.exe to example dir +PostBuild_Cmds=copy out.VC6.RELEASE\v12_RELEASE.exe ..\example +# End Special Build Tool + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "out.VC6.DEBUG" +# PROP Intermediate_Dir "out.VC6.DEBUG" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /Gm /GR /GX /ZI /Od /I "../lib/directx" /I "../lib/zlib" /I "../lib/lungif" /I "../lib/lpng" /I "../lib/ljpeg" /I "../lib/mss" /I "../lib/openal/win32" /I "." /D "DEBUG" /D "V12_DEBUG" /D "ENABLE_ASSERTS" /D "WIN32" /D "USEASSEMBLYTERRBLEND" /D "PNG_NO_READ_TIME" /D "PNG_NO_WRITE_TIME" /D "NO_MILES_OPENAL" /D OPENGL2D3D=\"glFOO.dll\" /D GLU2D3D=\"gluFOO.dll\" /YX /FD /GZ /c /Tp +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 COMCTL32.LIB COMDLG32.LIB USER32.LIB ADVAPI32.LIB GDI32.LIB WINMM.LIB WSOCK32.LIB ljpeg.lib lpng.lib lungif.lib zlib.lib Mss32.lib vfw32.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"LIBC" /out:"out.VC6.DEBUG/v12_DEBUG.exe" /pdbtype:sept /libpath:"../lib/out.VC6.DEBUG" /libpath:"../lib/mss" +# SUBTRACT LINK32 /pdb:none +# Begin Special Build Tool +SOURCE="$(InputPath)" +PostBuild_Desc=Copying v12_DEBUG.exe to example dir +PostBuild_Cmds=copy out.VC6.DEBUG\v12_DEBUG.exe ..\example +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "v12 Engine - Win32 Release" +# Name "v12 Engine - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Group "ai" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\ai\aiConnection.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\aiConsole.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\aiDebug.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\aiNavJetting.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\aiNavStep.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\aiObjective.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\aiStep.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\aiTask.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graph.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBase.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBridge.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBuildLOS.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphConjoin.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphData.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphDebug.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphDijkstra.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphFind.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphFloorPlan.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphFloorRender.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphForceField.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGenUtils.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGroundPlan.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphIndoors.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphIsland.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphJetting.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphLocate.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphLOS.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphMake.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphMath.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphOutdoors.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphPartition.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphPath.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphQueries.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphRender.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphSearchLOS.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphSmooth.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphSpawn.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphThreats.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphTransient.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ai\graphVolume.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ai" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ai" + +!ENDIF + +# End Source File +# End Group +# Begin Group "audio" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\audio\audio.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/audio" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/audio" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\audio\audioBuffer.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/audio" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/audio" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\audio\audioCodec.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/audio" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/audio" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\audio\audioCodecMiles.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/audio" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/audio" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\audio\audioDataBlock.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/audio" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/audio" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\audio\audioMss.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/audio" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/audio" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\audio\audioNet.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/audio" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/audio" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\audio\audioThread.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/audio" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/audio" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\audio\bufferQueue.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/audio" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/audio" + +!ENDIF + +# End Source File +# End Group +# Begin Group "collision" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\collision\abstractPolyList.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\boxConvex.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\clippedPolyList.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\convex.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\depthSortList.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\earlyOutPolyList.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\extrudedPolyList.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\gjk.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\planeExtractor.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\polyhedron.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\collision\polytope.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/collision" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/collision" + +!ENDIF + +# End Source File +# End Group +# Begin Group "console" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\console\compiledEval.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\compiler.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\console.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\consoleFunctions.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\consoleInternal.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\consoleObject.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\consoleTypes.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\gram.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\scan.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\scriptObject.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\simBase.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\simDictionary.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\simManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\telnetConsole.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\console\telnetDebugger.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/console" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/console" + +!ENDIF + +# End Source File +# End Group +# Begin Group "core" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\core\bitRender.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\bitStream.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\BitTables.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\dataChunker.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\dnet.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\fileObject.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\fileStream.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\filterStream.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\findMatch.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\idGenerator.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\memStream.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\nStream.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\nTypes.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\resDictionary.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\resizeStream.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\resManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\stringTable.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\tagDictionary.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\tVector.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\zipAggregate.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\zipHeaders.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\core\zipSubStream.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/core" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/core" + +!ENDIF + +# End Source File +# End Group +# Begin Group "crypt" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\crypt\cryptMGF.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/crypt" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/crypt" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\crypt\cryptRandPool.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/crypt" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/crypt" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\crypt\cryptSHA1.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/crypt" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/crypt" + +!ENDIF + +# End Source File +# End Group +# Begin Group "dgl" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\dgl\bitmapBM8.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapBMP.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapGIF.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapJpeg.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\bitmapPng.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\dgl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\dglMatrix.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\gBitmap.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\gFont.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\gPalette.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\gTexManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\lensFlare.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialList.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialPropertyMap.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\rectClipper.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\splineUtil.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dgl\stripCache.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/dgl" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/dgl" + +!ENDIF + +# End Source File +# End Group +# Begin Group "editor" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\editor\compTest.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\creator.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\editor.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\editorButtonCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\editorCheckboxCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\editTSCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\guiTerrPreviewCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\missionAreaEditor.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformer.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformer_noise.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformerTexture.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\terrainActions.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\terrainEditor.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\editor\worldEditor.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/editor" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/editor" + +!ENDIF + +# End Source File +# End Group +# Begin Group "game" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\game\ambientAudioManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\audioEmitter.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\banList.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\bombSight.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\camera.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\cameraFXMgr.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\collisionTest.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\commanderMapIcon.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\debris.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\debugView.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\explosion.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\fireballAtmosphere.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\flyingVehicle.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\forceFieldBare.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\game.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\gameBase.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnection.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnectionEvents.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnectionMoves.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\gameFunctions.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\gameProcess.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\gameTSCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\guiNoMouseCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\guiPlayerView.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\guiServerBrowser.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\hoverVehicle.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\httpObject.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\item.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\lightning.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\linearProjectile.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\missionArea.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\missionMarker.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\motionBlurLine.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\net.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\netDispatch.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\netTest.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\particleEmitter.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\particleEngine.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\physicalZone.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\platTest.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\player.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\precipitation.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projBomb.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projectile.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projELF.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projEnergy.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projFlareGrenade.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projGrenade.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projLinearFlare.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projRepair.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projSeeker.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projShockLance.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projSniper.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projTargeting.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\projTracer.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\rigid.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\scopeAlwaysShape.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\sensor.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\serverQuery.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\shadow.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\shapeBase.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\shapeCollision.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\shapeImage.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\shieldImpact.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\shockwave.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\showTSShape.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\sphere.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\splash.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\staticShape.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\targetManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\tcpObject.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\trigger.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\tsStatic.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\turret.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\underLava.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\vehicle.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\vehicleBlocker.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\version.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\weaponBeam.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game\wheeledVehicle.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/game" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/game" + +!ENDIF + +# End Source File +# End Group +# Begin Group "gui" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\gui\channelVector.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiArrayCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiAviBitmapCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBackgroundCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBitmapCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBubbleTextCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiButtonCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiCanvas.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChannelVectorCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChatMenuTreeCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiCheckBoxCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChunkedBitmapCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsole.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsoleEditCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsoleTextCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiControl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiControlListPopup.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiDebugger.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiEditCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiFilterCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiFrameCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiInputCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiInspector.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMessageVectorCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMLTextCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMLTextEditCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMouseEventCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiPopUpCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiProgressCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiRadioCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiScrollCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiSliderCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextEditCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextEditSliderCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextListCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTreeViewCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTSControl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTypes.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiVoteCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\guiWindowCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gui\messageVector.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/gui" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/gui" + +!ENDIF + +# End Source File +# End Group +# Begin Group "hud" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\hud\hudBarBaseCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/hud" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/hud" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hud\hudBitmapCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/hud" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/hud" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hud\hudClock.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/hud" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/hud" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hud\hudCompass.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/hud" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/hud" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hud\hudCrosshair.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/hud" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/hud" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hud\hudCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/hud" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/hud" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hud\hudDamageCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/hud" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/hud" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hud\hudEnergyCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/hud" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/hud" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hud\hudZoom.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/hud" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/hud" + +!ENDIF + +# End Source File +# End Group +# Begin Group "interior" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\interior\FloorPlanRes.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\forceField.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interior.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorCollision.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorDebug.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorInstance.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorIO.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorLightAnim.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorLMManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorRender.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorRes.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorResObjects.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorSubObject.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\itfdump.asm + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" +# Begin Custom Build +IntDir=.\out.VC6.RELEASE/interior +InputPath=.\interior\itfdump.asm +InputName=itfdump + +"$(IntDir)/$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + nasmw.exe -f win32 $(InputPath) -o $(IntDir)/$(InputName).obj + +# End Custom Build + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" +# Begin Custom Build +IntDir=.\out.VC6.DEBUG/interior +InputPath=.\interior\itfdump.asm +InputName=itfdump + +"$(IntDir)/$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + nasmw.exe -f win32 $(InputPath) -o $(IntDir)/$(InputName).obj + +# End Custom Build + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\lightUpdateGrouper.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interior\mirrorSubObject.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/interior" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/interior" + +!ENDIF + +# End Source File +# End Group +# Begin Group "math" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\math\mathTypes.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mathUtils.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mBox.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mConsoleFunctions.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMath_C.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMathAMD.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMathFn.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMathSSE.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mMatrix.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mPlaneTransformer.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mQuadPatch.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mQuat.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mRandom.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mSolver.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\math\mSplinePatch.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/math" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/math" + +!ENDIF + +# End Source File +# End Group +# Begin Group "platform" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\platform\gameInterface.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\platformAssert.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\platformMemory.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\platformRedBook.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\platformVideo.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platform\profiler.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platform" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platform" + +!ENDIF + +# End Source File +# End Group +# Begin Group "platformWIN32" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\platformWIN32\winAsmBlit.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winConsole.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winCPUInfo.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winD3DVideo.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" +# ADD CPP /D OPENGL2D3D=\"glFOO.dll\" /D GLU2D3D=\"gluFOO.dll\" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDInputDevice.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDirectInput.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winFileio.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winFont.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winGL.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winInput.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMath.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMath_ASM.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMemory.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winMutex.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winNet.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winOGLVideo.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winOpenAL.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winProcessControl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winRedbook.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winSemaphore.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winStrings.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winThread.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winTime.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winV2Video.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winWindow.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/platformWIN32" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/platformWIN32" + +!ENDIF + +# End Source File +# End Group +# Begin Group "sceneGraph" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\scenegraph\detailManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\lightManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneGraph.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneLighting.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneRoot.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneState.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneTraversal.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sgUtil.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\shadowVolumeBSP.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\windingClipper.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sceneGraph" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sceneGraph" + +!ENDIF + +# End Source File +# End Group +# Begin Group "shell" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\shell\shellFancyArray.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/shell" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/shell" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\shell\shellFancyTextList.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/shell" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/shell" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\shell\shellScrollCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/shell" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/shell" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\shell\shellTextEditCtrl.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/shell" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/shell" + +!ENDIF + +# End Source File +# End Group +# Begin Group "sim" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\sim\actionMap.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\cannedChatDataBlock.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\decalManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\frameAllocator.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netConnection.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netEvent.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netGhost.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netObject.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\netStringTable.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\pathManager.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\sceneObject.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sim\simPath.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/sim" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/sim" + +!ENDIF + +# End Source File +# End Group +# Begin Group "terrain" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\terrain\blender.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\bvQuadTree.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\FluidQuadTree.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\FluidRender.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\FluidSupport.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sky.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sun.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrCollision.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrData.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrLighting.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrRender.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrRender2.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\terrain\waterBlock.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/terrain" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/terrain" + +!ENDIF + +# End Source File +# End Group +# Begin Group "ts" + +# PROP Default_Filter "cc" +# Begin Source File + +SOURCE=.\ts\tsAnimate.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsCollision.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsDecal.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsDump.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsIntegerSet.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsLastDetail.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsMaterialList.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsMesh.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsPartInstance.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShape.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeAlloc.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeConstruct.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeInstance.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeOldRead.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsSortedMesh.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsThread.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ts\tsTransform.cc + +!IF "$(CFG)" == "v12 Engine - Win32 Release" + +# PROP Intermediate_Dir "out.VC6.RELEASE/ts" + +!ELSEIF "$(CFG)" == "v12 Engine - Win32 Debug" + +# PROP Intermediate_Dir "out.VC6.DEBUG/ts" + +!ENDIF + +# End Source File +# End Group +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Group "ai headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\ai\aiConnection.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiNavJetting.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiNavStep.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiObjective.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiStep.h +# End Source File +# Begin Source File + +SOURCE=.\ai\aiTask.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graph.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBase.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBridge.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphBSP.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphData.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphDefines.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphFloorPlan.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphForceField.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGenUtils.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGroundPlan.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphGroundVisit.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphJetting.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphLocate.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphLOS.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphMath.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphNodes.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphPartition.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphPath.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphSearches.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphThreats.h +# End Source File +# Begin Source File + +SOURCE=.\ai\graphTransient.h +# End Source File +# Begin Source File + +SOURCE=.\ai\oVector.h +# End Source File +# Begin Source File + +SOURCE=.\ai\tBinHeap.h +# End Source File +# Begin Source File + +SOURCE=.\ai\texturePreload.h +# End Source File +# End Group +# Begin Group "audio headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\audio\audio.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioBuffer.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioCodec.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioCodecMiles.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioDataBlock.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioMss.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioNet.h +# End Source File +# Begin Source File + +SOURCE=.\audio\audioThread.h +# End Source File +# Begin Source File + +SOURCE=.\audio\bufferQueue.h +# End Source File +# End Group +# Begin Group "collision headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\collision\abstractPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\boxConvex.h +# End Source File +# Begin Source File + +SOURCE=.\collision\clippedPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\collision.h +# End Source File +# Begin Source File + +SOURCE=.\collision\convex.h +# End Source File +# Begin Source File + +SOURCE=.\collision\depthSortList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\earlyOutPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\extrudedPolyList.h +# End Source File +# Begin Source File + +SOURCE=.\collision\gjk.h +# End Source File +# Begin Source File + +SOURCE=.\collision\planeExtractor.h +# End Source File +# Begin Source File + +SOURCE=.\collision\polyhedron.h +# End Source File +# Begin Source File + +SOURCE=.\collision\polytope.h +# End Source File +# End Group +# Begin Group "console headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\console\ast.h +# End Source File +# Begin Source File + +SOURCE=.\console\compiler.h +# End Source File +# Begin Source File + +SOURCE=.\console\console.h +# End Source File +# Begin Source File + +SOURCE=.\console\consoleInternal.h +# End Source File +# Begin Source File + +SOURCE=.\console\consoleObject.h +# End Source File +# Begin Source File + +SOURCE=.\console\consoleTypes.h +# End Source File +# Begin Source File + +SOURCE=.\console\gram.h +# End Source File +# Begin Source File + +SOURCE=.\console\objectTypes.h +# End Source File +# Begin Source File + +SOURCE=.\console\simBase.h +# End Source File +# Begin Source File + +SOURCE=.\console\simDictionary.h +# End Source File +# Begin Source File + +SOURCE=.\console\telnetConsole.h +# End Source File +# Begin Source File + +SOURCE=.\console\telnetDebugger.h +# End Source File +# End Group +# Begin Group "core headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\core\bitMatrix.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitRender.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitSet.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\BitTables.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitVector.h +# End Source File +# Begin Source File + +SOURCE=.\core\bitVectorW.h +# End Source File +# Begin Source File + +SOURCE=.\core\color.h +# End Source File +# Begin Source File + +SOURCE=.\core\coreRes.h +# End Source File +# Begin Source File + +SOURCE=.\core\dataChunker.h +# End Source File +# Begin Source File + +SOURCE=.\core\dnet.h +# End Source File +# Begin Source File + +SOURCE=.\core\fileio.h +# End Source File +# Begin Source File + +SOURCE=.\core\fileObject.h +# End Source File +# Begin Source File + +SOURCE=.\core\fileStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\filterStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\findMatch.h +# End Source File +# Begin Source File + +SOURCE=.\core\idGenerator.h +# End Source File +# Begin Source File + +SOURCE=.\core\llist.h +# End Source File +# Begin Source File + +SOURCE=.\core\memstream.h +# End Source File +# Begin Source File + +SOURCE=.\core\polyList.h +# End Source File +# Begin Source File + +SOURCE=.\core\realComp.h +# End Source File +# Begin Source File + +SOURCE=.\core\resizeStream.h +# End Source File +# Begin Source File + +SOURCE=.\core\resManager.h +# End Source File +# Begin Source File + +SOURCE=.\core\stream.h +# End Source File +# Begin Source File + +SOURCE=.\core\stringTable.h +# End Source File +# Begin Source File + +SOURCE=.\core\tagDictionary.h +# End Source File +# Begin Source File + +SOURCE=.\core\tAlgorithm.h +# End Source File +# Begin Source File + +SOURCE=.\core\tSparseArray.h +# End Source File +# Begin Source File + +SOURCE=.\core\tVector.h +# End Source File +# Begin Source File + +SOURCE=.\core\zipAggregate.h +# End Source File +# Begin Source File + +SOURCE=.\core\zipHeaders.h +# End Source File +# Begin Source File + +SOURCE=.\core\zipSubStream.h +# End Source File +# End Group +# Begin Group "crypt headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\crypt\cryptMGF.h +# End Source File +# Begin Source File + +SOURCE=.\crypt\cryptRandPool.h +# End Source File +# Begin Source File + +SOURCE=.\crypt\cryptSHA1.h +# End Source File +# End Group +# Begin Group "dgl headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\dgl\dgl.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gBitmap.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gChunkedTexManager.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gFont.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gPalette.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\gTexManager.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\lensFlare.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialList.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\materialPropertyMap.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\rectClipper.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\splineUtil.h +# End Source File +# Begin Source File + +SOURCE=.\dgl\stripCache.h +# End Source File +# End Group +# Begin Group "editor headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\editor\creator.h +# End Source File +# Begin Source File + +SOURCE=.\editor\editor.h +# End Source File +# Begin Source File + +SOURCE=.\editor\editorButtonCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\editor\editorCheckboxCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\editor\editTSCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\editor\guiTerrPreviewCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\editor\missionAreaEditor.h +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformer.h +# End Source File +# Begin Source File + +SOURCE=.\editor\terraformer_noise.h +# End Source File +# Begin Source File + +SOURCE=.\editor\terrainActions.h +# End Source File +# Begin Source File + +SOURCE=.\editor\terrainEditor.h +# End Source File +# Begin Source File + +SOURCE=.\editor\worldEditor.h +# End Source File +# End Group +# Begin Group "game headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\game\ambientAudioManager.h +# End Source File +# Begin Source File + +SOURCE=.\game\audioEmitter.h +# End Source File +# Begin Source File + +SOURCE=.\game\auth.h +# End Source File +# Begin Source File + +SOURCE=.\game\banList.h +# End Source File +# Begin Source File + +SOURCE=.\game\bombSight.h +# End Source File +# Begin Source File + +SOURCE=.\game\camera.h +# End Source File +# Begin Source File + +SOURCE=.\game\cameraFXMgr.h +# End Source File +# Begin Source File + +SOURCE=.\game\collisionTest.h +# End Source File +# Begin Source File + +SOURCE=.\game\commanderMapIcon.h +# End Source File +# Begin Source File + +SOURCE=.\game\debris.h +# End Source File +# Begin Source File + +SOURCE=.\game\debugView.h +# End Source File +# Begin Source File + +SOURCE=.\game\explosion.h +# End Source File +# Begin Source File + +SOURCE=.\game\fireballAtmosphere.h +# End Source File +# Begin Source File + +SOURCE=.\game\flyingVehicle.h +# End Source File +# Begin Source File + +SOURCE=.\game\forceFieldBare.h +# End Source File +# Begin Source File + +SOURCE=.\game\game.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameBase.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnection.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameConnectionEvents.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameFunctions.h +# End Source File +# Begin Source File + +SOURCE=.\game\gameTSCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\game\guiPlayerView.h +# End Source File +# Begin Source File + +SOURCE=.\game\guiServerBrowser.h +# End Source File +# Begin Source File + +SOURCE=.\game\hoverVehicle.h +# End Source File +# Begin Source File + +SOURCE=.\game\httpObject.h +# End Source File +# Begin Source File + +SOURCE=.\game\item.h +# End Source File +# Begin Source File + +SOURCE=.\game\lightning.h +# End Source File +# Begin Source File + +SOURCE=.\game\linearProjectile.h +# End Source File +# Begin Source File + +SOURCE=.\game\missionArea.h +# End Source File +# Begin Source File + +SOURCE=.\game\missionMarker.h +# End Source File +# Begin Source File + +SOURCE=.\game\motionBlurLine.h +# End Source File +# Begin Source File + +SOURCE=.\game\moveManager.h +# End Source File +# Begin Source File + +SOURCE=.\game\netDispatch.h +# End Source File +# Begin Source File + +SOURCE=.\game\objectTypes.h +# End Source File +# Begin Source File + +SOURCE=.\game\particleEmitter.h +# End Source File +# Begin Source File + +SOURCE=.\game\particleEngine.h +# End Source File +# Begin Source File + +SOURCE=.\game\physicalZone.h +# End Source File +# Begin Source File + +SOURCE=.\game\player.h +# End Source File +# Begin Source File + +SOURCE=.\game\precipitation.h +# End Source File +# Begin Source File + +SOURCE=.\game\projBomb.h +# End Source File +# Begin Source File + +SOURCE=.\game\projectile.h +# End Source File +# Begin Source File + +SOURCE=.\game\projELF.h +# End Source File +# Begin Source File + +SOURCE=.\game\projEnergy.h +# End Source File +# Begin Source File + +SOURCE=.\game\projFlareGrenade.h +# End Source File +# Begin Source File + +SOURCE=.\game\projGrenade.h +# End Source File +# Begin Source File + +SOURCE=.\game\projLinearFlare.h +# End Source File +# Begin Source File + +SOURCE=.\game\projRepair.h +# End Source File +# Begin Source File + +SOURCE=.\game\projSeeker.h +# End Source File +# Begin Source File + +SOURCE=.\game\projShockLance.h +# End Source File +# Begin Source File + +SOURCE=.\game\projSniper.h +# End Source File +# Begin Source File + +SOURCE=.\game\projTargeting.h +# End Source File +# Begin Source File + +SOURCE=.\game\projTracer.h +# End Source File +# Begin Source File + +SOURCE=.\game\resource.h +# End Source File +# Begin Source File + +SOURCE=.\game\rigid.h +# End Source File +# Begin Source File + +SOURCE=.\game\sensor.h +# End Source File +# Begin Source File + +SOURCE=.\game\serverQuery.h +# End Source File +# Begin Source File + +SOURCE=.\game\shadow.h +# End Source File +# Begin Source File + +SOURCE=.\game\shapeBase.h +# End Source File +# Begin Source File + +SOURCE=.\game\shieldImpact.h +# End Source File +# Begin Source File + +SOURCE=.\game\shockwave.h +# End Source File +# Begin Source File + +SOURCE=.\game\showTSShape.h +# End Source File +# Begin Source File + +SOURCE=.\game\sphere.h +# End Source File +# Begin Source File + +SOURCE=.\game\splash.h +# End Source File +# Begin Source File + +SOURCE=.\game\staticShape.h +# End Source File +# Begin Source File + +SOURCE=.\game\targetManager.h +# End Source File +# Begin Source File + +SOURCE=.\game\tcpObject.h +# End Source File +# Begin Source File + +SOURCE=.\game\tribesGame.h +# End Source File +# Begin Source File + +SOURCE=.\game\trigger.h +# End Source File +# Begin Source File + +SOURCE=.\game\tsStatic.h +# End Source File +# Begin Source File + +SOURCE=.\game\turret.h +# End Source File +# Begin Source File + +SOURCE=.\game\underLava.h +# End Source File +# Begin Source File + +SOURCE=.\game\vehicle.h +# End Source File +# Begin Source File + +SOURCE=.\game\vehicleBlocker.h +# End Source File +# Begin Source File + +SOURCE=.\game\version.h +# End Source File +# Begin Source File + +SOURCE=.\game\weaponBeam.h +# End Source File +# Begin Source File + +SOURCE=.\game\wheeledVehicle.h +# End Source File +# End Group +# Begin Group "gui headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\gui\channelVector.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiArrayCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiAviBitmapCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBackgroundCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBitmapCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiBubbleTextCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiButtonCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiCanvas.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChannelVectorCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiChatMenuTreeCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiCheckBoxCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsole.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsoleEditCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiConsoleTextCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiControl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiDebugger.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiEditCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiFilterCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiFrameCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiInputCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiInspector.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMessageVectorCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMLTextCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMLTextEditCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiMouseEventCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiPopUpCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiProgressCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiRadioCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiScrollCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiSliderCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextEditCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextEditSliderCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTextListCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTreeViewCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTSControl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiTypes.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiVoteCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\guiWindowCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\gui\messageVector.h +# End Source File +# End Group +# Begin Group "hud headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\hud\hudBarBaseCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\hud\hudBitmapCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\hud\hudCtrl.h +# End Source File +# End Group +# Begin Group "interior headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\interior\FloorPlanRes.h +# End Source File +# Begin Source File + +SOURCE=.\interior\forceField.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interior.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorInstance.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorLMManager.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorRes.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorResObjects.h +# End Source File +# Begin Source File + +SOURCE=.\interior\interiorSubObject.h +# End Source File +# Begin Source File + +SOURCE=.\interior\itf.h +# End Source File +# Begin Source File + +SOURCE=.\interior\lightUpdateGrouper.h +# End Source File +# Begin Source File + +SOURCE=.\interior\mirrorSubObject.h +# End Source File +# End Group +# Begin Group "math headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\math\mathIO.h +# End Source File +# Begin Source File + +SOURCE=.\math\mathTypes.h +# End Source File +# Begin Source File + +SOURCE=.\math\mathUtils.h +# End Source File +# Begin Source File + +SOURCE=.\math\mBox.h +# End Source File +# Begin Source File + +SOURCE=.\math\mConstants.h +# End Source File +# Begin Source File + +SOURCE=.\math\mMath.h +# End Source File +# Begin Source File + +SOURCE=.\math\mMathFn.h +# End Source File +# Begin Source File + +SOURCE=.\math\mMatrix.h +# End Source File +# Begin Source File + +SOURCE=.\math\mPlane.h +# End Source File +# Begin Source File + +SOURCE=.\math\mPlaneTransformer.h +# End Source File +# Begin Source File + +SOURCE=.\math\mPoint.h +# End Source File +# Begin Source File + +SOURCE=.\math\mQuadPatch.h +# End Source File +# Begin Source File + +SOURCE=.\math\mQuat.h +# End Source File +# Begin Source File + +SOURCE=.\math\mRandom.h +# End Source File +# Begin Source File + +SOURCE=.\math\mRect.h +# End Source File +# Begin Source File + +SOURCE=.\math\mSphere.h +# End Source File +# Begin Source File + +SOURCE=.\math\mSplinePatch.h +# End Source File +# Begin Source File + +SOURCE=.\math\mTrig.h +# End Source File +# End Group +# Begin Group "platform headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\platform\3DFX.H +# End Source File +# Begin Source File + +SOURCE=.\platform\event.h +# End Source File +# Begin Source File + +SOURCE=.\platform\gameInterface.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platform.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformAssert.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformAudio.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformInput.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformMutex.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformRedBook.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformSemaphore.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformThread.h +# End Source File +# Begin Source File + +SOURCE=.\platform\platformVideo.h +# End Source File +# Begin Source File + +SOURCE=.\platform\profiler.h +# End Source File +# Begin Source File + +SOURCE=.\platform\types.h +# End Source File +# Begin Source File + +SOURCE=.\platform\typesWin32.h +# End Source File +# End Group +# Begin Group "platformWIN32 headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\platformWIN32\gllist.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\platformAL.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\platformGL.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\platformWin32.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winConsole.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winD3DVideo.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDInputDevice.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winDirectInput.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winOGLVideo.h +# End Source File +# Begin Source File + +SOURCE=.\platformWIN32\winV2Video.h +# End Source File +# End Group +# Begin Group "sceneGraph headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\scenegraph\detailManager.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\lightManager.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneGraph.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneLighting.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneRoot.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sceneState.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\sgUtil.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\shadowVolumeBSP.h +# End Source File +# Begin Source File + +SOURCE=.\scenegraph\windingClipper.h +# End Source File +# End Group +# Begin Group "shell headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\shell\shellFancyArray.h +# End Source File +# Begin Source File + +SOURCE=.\shell\shellFancyTextList.h +# End Source File +# Begin Source File + +SOURCE=.\shell\shellScrollCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\shell\shellTextEditCtrl.h +# End Source File +# End Group +# Begin Group "sim headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\sim\actionMap.h +# End Source File +# Begin Source File + +SOURCE=.\sim\cannedChatDataBlock.h +# End Source File +# Begin Source File + +SOURCE=.\sim\decalManager.h +# End Source File +# Begin Source File + +SOURCE=.\sim\frameAllocator.h +# End Source File +# Begin Source File + +SOURCE=.\sim\netConnection.h +# End Source File +# Begin Source File + +SOURCE=.\sim\netObject.h +# End Source File +# Begin Source File + +SOURCE=.\sim\netStringTable.h +# End Source File +# Begin Source File + +SOURCE=.\sim\pathManager.h +# End Source File +# Begin Source File + +SOURCE=.\sim\sceneObject.h +# End Source File +# Begin Source File + +SOURCE=.\sim\simPath.h +# End Source File +# End Group +# Begin Group "terrain headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\terrain\blender.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\bvQuadTree.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\Fluid.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sky.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\Sun.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrData.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\terrRender.h +# End Source File +# Begin Source File + +SOURCE=.\terrain\waterBlock.h +# End Source File +# End Group +# Begin Group "ts headers" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\ts\tsDecal.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsIntegerSet.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsLastDetail.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsMesh.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsPartInstance.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShape.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeAlloc.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeConstruct.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsShapeInstance.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsSortedMesh.h +# End Source File +# Begin Source File + +SOURCE=.\ts\tsTransform.h +# End Source File +# End Group +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/v12 Engine.dsw b/v12 Engine.dsw new file mode 100644 index 0000000..abed3cb --- /dev/null +++ b/v12 Engine.dsw @@ -0,0 +1,41 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "v12 Engine"=".\v12 Engine.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "v12 Engine Lib"=".\v12 Engine Lib.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/v12 Engine.opt b/v12 Engine.opt new file mode 100644 index 0000000..4ba25b2 Binary files /dev/null and b/v12 Engine.opt differ diff --git a/vc60.idb b/vc60.idb new file mode 100644 index 0000000..954d053 --- /dev/null +++ b/vc60.idb @@ -0,0 +1 @@ +Microsoft C/C++ program database 2.00 diff --git a/vc60.pdb b/vc60.pdb new file mode 100644 index 0000000..954d053 --- /dev/null +++ b/vc60.pdb @@ -0,0 +1 @@ +Microsoft C/C++ program database 2.00